Merge "Update testing doc to reflect unit subdir move" into feature/zuulv3
diff --git a/.zuul.yaml b/.zuul.yaml
index db03673..bb9a96d 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -2,6 +2,8 @@
     name: python-linters
     pre-run: pre
     post-run: post
+    success-url: http://zuulv3-dev.openstack.org/logs/{build.uuid}/
+    failure-url: http://zuulv3-dev.openstack.org/logs/{build.uuid}/
     nodes:
       - name: worker
         image: ubuntu-xenial
diff --git a/TESTING.rst b/TESTING.rst
index 069dffd..d2cd4c1 100644
--- a/TESTING.rst
+++ b/TESTING.rst
@@ -17,6 +17,16 @@
 
   pip install tox
 
+As of zuul v3, a running zookeeper is required to execute tests.
+
+*Install zookeeper*::
+
+  [apt-get | yum] install zookeeperd
+
+*Start zookeeper*::
+
+  service zookeeper start
+
 Run The Tests
 -------------
 
diff --git a/playbooks/post.yaml b/playbooks/post.yaml
index 31570bc..a11e50a 100644
--- a/playbooks/post.yaml
+++ b/playbooks/post.yaml
@@ -8,7 +8,12 @@
 
     - name: Collect tox logs.
       synchronize:
-        dest: "{{ zuul.launcher.log_root }}/tox/"
+        dest: "{{ zuul.launcher.log_root }}/tox"
         mode: pull
         src: "/home/zuul/workspace/src/{{ zuul.project }}/.tox/pep8/log/"
 
+    - name: publish tox logs.
+      copy:
+        dest: "/opt/zuul-logs/{{ zuul.uuid}}"
+        src: "{{ zuul.launcher.log_root }}/"
+      delegate_to: 127.0.0.1
diff --git a/tests/base.py b/tests/base.py
index e6542e7..7ee9b9c 100755
--- a/tests/base.py
+++ b/tests/base.py
@@ -749,11 +749,11 @@
 
 
 class RecordingAnsibleJob(zuul.launcher.server.AnsibleJob):
-    def runPlaybooks(self):
+    def runPlaybooks(self, args):
         build = self.launcher_server.job_builds[self.job.unique]
         build.jobdir = self.jobdir
 
-        result = super(RecordingAnsibleJob, self).runPlaybooks()
+        result = super(RecordingAnsibleJob, self).runPlaybooks(args)
 
         self.launcher_server.lock.acquire()
         self.launcher_server.build_history.append(
@@ -1812,12 +1812,23 @@
                 f.write(content)
             repo.index.add([fn])
         commit = repo.index.commit(message)
+        before = repo.heads[branch].commit
         repo.heads[branch].commit = commit
         repo.head.reference = branch
         repo.git.clean('-x', '-f', '-d')
         repo.heads[branch].checkout()
         if tag:
             repo.create_tag(tag)
+        return before
+
+    def commitLayoutUpdate(self, orig_name, source_name):
+        source_path = os.path.join(self.test_root, 'upstream',
+                                   source_name, 'zuul.yaml')
+        with open(source_path, 'r') as nt:
+            before = self.addCommitToRepo(
+                orig_name, 'Pulling content from %s' % source_name,
+                {'zuul.yaml': nt.read()})
+        return before
 
     def addEvent(self, connection, event):
         """Inject a Fake (Gerrit) event.
diff --git a/tests/fixtures/config/ansible/git/common-config/playbooks/timeout.yaml b/tests/fixtures/config/ansible/git/common-config/playbooks/timeout.yaml
new file mode 100644
index 0000000..4af20eb
--- /dev/null
+++ b/tests/fixtures/config/ansible/git/common-config/playbooks/timeout.yaml
@@ -0,0 +1,4 @@
+- hosts: all
+  tasks:
+    - name: Pause for 60 seconds, so zuul aborts our job.
+      shell: sleep 60
diff --git a/tests/fixtures/config/ansible/git/common-config/zuul.yaml b/tests/fixtures/config/ansible/git/common-config/zuul.yaml
index baa7aba..30148f0 100644
--- a/tests/fixtures/config/ansible/git/common-config/zuul.yaml
+++ b/tests/fixtures/config/ansible/git/common-config/zuul.yaml
@@ -44,3 +44,8 @@
       flagpath: "{{zuul._test.test_root}}/{{zuul.uuid}}.flag"
     roles:
       - zuul: bare-role
+
+- job:
+    parent: python27
+    name: timeout
+    timeout: 1
diff --git a/tests/fixtures/config/ansible/git/org_project/.zuul.yaml b/tests/fixtures/config/ansible/git/org_project/.zuul.yaml
index 6abfc47..c76ba70 100644
--- a/tests/fixtures/config/ansible/git/org_project/.zuul.yaml
+++ b/tests/fixtures/config/ansible/git/org_project/.zuul.yaml
@@ -9,3 +9,4 @@
       jobs:
         - python27
         - faillocal
+        - timeout
diff --git a/tests/fixtures/config/single-tenant/git/layout-timer/playbooks/project-bitrot-stable-old.yaml b/tests/fixtures/config/single-tenant/git/layout-timer/playbooks/project-bitrot-stable-old.yaml
new file mode 100644
index 0000000..f679dce
--- /dev/null
+++ b/tests/fixtures/config/single-tenant/git/layout-timer/playbooks/project-bitrot-stable-old.yaml
@@ -0,0 +1,2 @@
+- hosts: all
+  tasks: []
diff --git a/tests/fixtures/config/single-tenant/git/layout-timer/playbooks/project-bitrot-stable-older.yaml b/tests/fixtures/config/single-tenant/git/layout-timer/playbooks/project-bitrot-stable-older.yaml
new file mode 100644
index 0000000..f679dce
--- /dev/null
+++ b/tests/fixtures/config/single-tenant/git/layout-timer/playbooks/project-bitrot-stable-older.yaml
@@ -0,0 +1,2 @@
+- hosts: all
+  tasks: []
diff --git a/tests/fixtures/config/single-tenant/git/layout-timer/playbooks/project-test1.yaml b/tests/fixtures/config/single-tenant/git/layout-timer/playbooks/project-test1.yaml
new file mode 100644
index 0000000..f679dce
--- /dev/null
+++ b/tests/fixtures/config/single-tenant/git/layout-timer/playbooks/project-test1.yaml
@@ -0,0 +1,2 @@
+- hosts: all
+  tasks: []
diff --git a/tests/fixtures/config/single-tenant/git/layout-timer/playbooks/project-test2.yaml b/tests/fixtures/config/single-tenant/git/layout-timer/playbooks/project-test2.yaml
new file mode 100644
index 0000000..f679dce
--- /dev/null
+++ b/tests/fixtures/config/single-tenant/git/layout-timer/playbooks/project-test2.yaml
@@ -0,0 +1,2 @@
+- hosts: all
+  tasks: []
diff --git a/tests/fixtures/config/single-tenant/git/layout-timer/zuul.yaml b/tests/fixtures/config/single-tenant/git/layout-timer/zuul.yaml
new file mode 100644
index 0000000..f69a91d
--- /dev/null
+++ b/tests/fixtures/config/single-tenant/git/layout-timer/zuul.yaml
@@ -0,0 +1,52 @@
+- pipeline:
+    name: check
+    manager: independent
+    source:
+      gerrit
+    trigger:
+      gerrit:
+        - event: patchset-created
+    success:
+      gerrit:
+        verified: 1
+    failure:
+      gerrit:
+        verified: -1
+
+- pipeline:
+    name: periodic
+    manager: independent
+    source:
+      gerrit
+    trigger:
+      timer:
+        - time: '* * * * * */1'
+
+- job:
+    name: project-test1
+
+- job:
+    name: project-test2
+
+- job:
+    name: project-bitrot-stable-old
+    nodes:
+      - name: static
+        image: ubuntu-xenial
+
+- job:
+    name: project-bitrot-stable-older
+    nodes:
+      - name: static
+        image: ubuntu-trusty
+
+- project:
+    name: org/project
+    check:
+      jobs:
+        - project-test1
+        - project-test2
+    periodic:
+      jobs:
+        - project-bitrot-stable-old
+        - project-bitrot-stable-older
diff --git a/tests/fixtures/layout-timer.yaml b/tests/fixtures/layout-timer.yaml
deleted file mode 100644
index 4904f87..0000000
--- a/tests/fixtures/layout-timer.yaml
+++ /dev/null
@@ -1,28 +0,0 @@
-pipelines:
-  - name: check
-    manager: IndependentPipelineManager
-    trigger:
-      gerrit:
-        - event: patchset-created
-    success:
-      gerrit:
-        verified: 1
-    failure:
-      gerrit:
-        verified: -1
-
-  - name: periodic
-    manager: IndependentPipelineManager
-    trigger:
-      timer:
-        - time: '* * * * * */1'
-
-projects:
-  - name: org/project
-    check:
-      - project-merge:
-        - project-test1
-        - project-test2
-    periodic:
-      - project-bitrot-stable-old
-      - project-bitrot-stable-older
diff --git a/tests/unit/test_scheduler.py b/tests/unit/test_scheduler.py
index beddae6..07d832f 100755
--- a/tests/unit/test_scheduler.py
+++ b/tests/unit/test_scheduler.py
@@ -1663,11 +1663,7 @@
         # Stop queuing timer triggered jobs so that the assertions
         # below don't race against more jobs being queued.
         # Must be in same repo, so overwrite config with another one
-        no_timer_path = os.path.join(self.test_root, 'upstream',
-                                     'layout-no-timer', 'zuul.yaml')
-        with open(no_timer_path, 'r') as nt:
-            self.addCommitToRepo('layout-idle', 'Removing timer jobs',
-                                 {'zuul.yaml': nt.read()})
+        self.commitLayoutUpdate('layout-idle', 'layout-no-timer')
 
         self.sched.reconfigure(self.config)
         self.assertEqual(len(self.builds), 2, "Two timer jobs")
@@ -2772,14 +2768,11 @@
             self.assertEqual(results.get(build.name, ''),
                              build.parameters.get('BUILD_TAGS'))
 
-    @skip("Disabled for early v3 development")
     def test_timer(self):
         "Test that a periodic job is triggered"
         self.launch_server.hold_jobs_in_build = True
-        self.updateConfigLayout(
-            'tests/fixtures/layout-timer.yaml')
+        self.updateConfigLayout('layout-timer')
         self.sched.reconfigure(self.config)
-        self.registerJobs()
 
         # The pipeline triggers every second, so we should have seen
         # several by now.
@@ -2790,17 +2783,16 @@
 
         port = self.webapp.server.socket.getsockname()[1]
 
-        req = urllib.request.Request("http://localhost:%s/status.json" % port)
+        req = urllib.request.Request(
+            "http://localhost:%s/openstack/status" % port)
         f = urllib.request.urlopen(req)
         data = f.read()
 
         self.launch_server.hold_jobs_in_build = False
         # Stop queuing timer triggered jobs so that the assertions
         # below don't race against more jobs being queued.
-        self.updateConfigLayout(
-            'tests/fixtures/layout-no-timer.yaml')
+        self.commitLayoutUpdate('layout-timer', 'layout-no-timer')
         self.sched.reconfigure(self.config)
-        self.registerJobs()
         self.launch_server.release()
         self.waitUntilSettled()
 
@@ -2821,19 +2813,16 @@
         self.assertIn('project-bitrot-stable-old', status_jobs)
         self.assertIn('project-bitrot-stable-older', status_jobs)
 
-    @skip("Disabled for early v3 development")
     def test_idle(self):
         "Test that frequent periodic jobs work"
         self.launch_server.hold_jobs_in_build = True
+        self.updateConfigLayout('layout-idle')
 
         for x in range(1, 3):
             # Test that timer triggers periodic jobs even across
             # layout config reloads.
             # Start timer trigger
-            self.updateConfigLayout(
-                'tests/fixtures/layout-idle.yaml')
             self.sched.reconfigure(self.config)
-            self.registerJobs()
             self.waitUntilSettled()
 
             # The pipeline triggers every second, so we should have seen
@@ -2842,17 +2831,20 @@
 
             # Stop queuing timer triggered jobs so that the assertions
             # below don't race against more jobs being queued.
-            self.updateConfigLayout(
-                'tests/fixtures/layout-no-timer.yaml')
+            before = self.commitLayoutUpdate('layout-idle', 'layout-no-timer')
             self.sched.reconfigure(self.config)
-            self.registerJobs()
             self.waitUntilSettled()
-
-            self.assertEqual(len(self.builds), 2)
+            self.assertEqual(len(self.builds), 2,
+                             'Timer builds iteration #%d' % x)
             self.launch_server.release('.*')
             self.waitUntilSettled()
             self.assertEqual(len(self.builds), 0)
             self.assertEqual(len(self.history), x * 2)
+            # Revert back to layout-idle
+            repo = git.Repo(os.path.join(self.test_root,
+                                         'upstream',
+                                         'layout-idle'))
+            repo.git.reset('--hard', before)
 
     def test_check_smtp_pool(self):
         self.updateConfigLayout('layout-smtp')
diff --git a/tests/unit/test_v3.py b/tests/unit/test_v3.py
index 97002b2..f69ffe6 100644
--- a/tests/unit/test_v3.py
+++ b/tests/unit/test_v3.py
@@ -195,6 +195,8 @@
         A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
         self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
         self.waitUntilSettled()
+        build = self.getJobFromHistory('timeout')
+        self.assertEqual(build.result, 'ABORTED')
         build = self.getJobFromHistory('faillocal')
         self.assertEqual(build.result, 'FAILURE')
         build = self.getJobFromHistory('python27')
diff --git a/zuul/launcher/client.py b/zuul/launcher/client.py
index ffb9f7e..6abd6f4 100644
--- a/zuul/launcher/client.py
+++ b/zuul/launcher/client.py
@@ -337,6 +337,7 @@
         merger_items = map(make_merger_item, all_items)
 
         params['job'] = job.name
+        params['timeout'] = job.timeout
         params['items'] = merger_items
         params['projects'] = []
 
diff --git a/zuul/launcher/server.py b/zuul/launcher/server.py
index c10b30e..1b8d2c6 100644
--- a/zuul/launcher/server.py
+++ b/zuul/launcher/server.py
@@ -35,9 +35,6 @@
 import zuul.ansible.library
 from zuul.lib import commandsocket
 
-ANSIBLE_WATCHDOG_GRACE = 5 * 60
-
-
 COMMANDS = ['stop', 'pause', 'unpause', 'graceful', 'verbose',
             'unverbose']
 
@@ -591,7 +588,7 @@
         self.job.sendWorkData(json.dumps(data))
         self.job.sendWorkStatus(0, 100)
 
-        result = self.runPlaybooks()
+        result = self.runPlaybooks(args)
 
         if result is None:
             self.job.sendWorkFail()
@@ -599,17 +596,20 @@
         result = dict(result=result)
         self.job.sendWorkComplete(json.dumps(result))
 
-    def runPlaybooks(self):
+    def runPlaybooks(self, args):
         result = None
 
         for playbook in self.jobdir.pre_playbooks:
-            pre_status, pre_code = self.runAnsiblePlaybook(playbook)
+            # TODOv3(pabelanger): Implement pre-run timeout setting.
+            pre_status, pre_code = self.runAnsiblePlaybook(
+                playbook, args['timeout'])
             if pre_status != self.RESULT_NORMAL or pre_code != 0:
                 # These should really never fail, so return None and have
                 # zuul try again
                 return result
 
-        job_status, job_code = self.runAnsiblePlaybook(self.jobdir.playbook)
+        job_status, job_code = self.runAnsiblePlaybook(
+            self.jobdir.playbook, args['timeout'])
         if job_status == self.RESULT_TIMED_OUT:
             return 'TIMED_OUT'
         if job_status == self.RESULT_ABORTED:
@@ -626,8 +626,9 @@
             result = 'FAILURE'
 
         for playbook in self.jobdir.post_playbooks:
+            # TODOv3(pabelanger): Implement post-run timeout setting.
             post_status, post_code = self.runAnsiblePlaybook(
-                playbook, success)
+                playbook, args['timeout'], success)
             if post_status != self.RESULT_NORMAL or post_code != 0:
                 result = 'POST_FAILURE'
         return result
@@ -911,23 +912,24 @@
             )
 
         ret = None
-        watchdog = Watchdog(timeout + ANSIBLE_WATCHDOG_GRACE,
-                            self._ansibleTimeout,
-                            ("Ansible timeout exceeded",))
-        watchdog.start()
+        if timeout:
+            watchdog = Watchdog(timeout, self._ansibleTimeout,
+                                ("Ansible timeout exceeded",))
+            watchdog.start()
         try:
             for line in iter(self.proc.stdout.readline, b''):
                 line = line[:1024].rstrip()
                 self.log.debug("Ansible output: %s" % (line,))
             ret = self.proc.wait()
         finally:
-            watchdog.stop()
+            if timeout:
+                watchdog.stop()
         self.log.debug("Ansible exit code: %s" % (ret,))
 
         with self.proc_lock:
             self.proc = None
 
-        if watchdog.timed_out:
+        if timeout and watchdog.timed_out:
             return (self.RESULT_TIMED_OUT, None)
         if ret == 3:
             # AnsibleHostUnreachable: We had a network issue connecting to
@@ -939,7 +941,7 @@
 
         return (self.RESULT_NORMAL, ret)
 
-    def runAnsiblePlaybook(self, playbook, success=None):
+    def runAnsiblePlaybook(self, playbook, timeout, success=None):
         env_copy = os.environ.copy()
         env_copy['LOGNAME'] = 'zuul'
 
@@ -955,8 +957,5 @@
 
         cmd.extend(['-e@%s' % self.jobdir.vars, verbose])
 
-        # TODOv3: get this from the job
-        timeout = 60
-
         return self.runAnsible(
             cmd=cmd, timeout=timeout, trusted=playbook.trusted)