Merge "Add stats for executor and merger count" into feature/zuulv3
diff --git a/doc/source/user/config.rst b/doc/source/user/config.rst
index c4014e2..b2074d1 100644
--- a/doc/source/user/config.rst
+++ b/doc/source/user/config.rst
@@ -973,6 +973,12 @@
 project-pipeline definition is what determines how a project
 participates in a pipeline.
 
+Multiple project definitions may appear for the same project (for
+example, in a central :term:`config projects <config-project>` as wall
+as in a repo's own ``.zuul.yaml``).  In this case, all of the project
+definitions are combined (the jobs listed in all of the definitions
+will be run).
+
 Consider the following project definition::
 
   - project:
@@ -1019,8 +1025,7 @@
 
 .. attr:: project
 
-   In addition to a project-pipeline definition for one or more
-   pipelines, the following attributes may appear in a project:
+   The following attributes may appear in a project:
 
    .. attr:: name
       :required:
diff --git a/doc/source/user/jobs.rst b/doc/source/user/jobs.rst
index cf607b9..f65ee19 100644
--- a/doc/source/user/jobs.rst
+++ b/doc/source/user/jobs.rst
@@ -83,9 +83,12 @@
 
 * Job variables
 
+* Parent job results
+
 Meaning that a site-wide variable with the same name as any other will
 override its value, and similarly, secrets override job variables of
-the same name.  Each of the three sources is described below.
+the same name which override data returned from parent jobs.  Each of
+the sources is described below.
 
 
 Job Variables
@@ -482,6 +485,11 @@
 <admin_sitewide_variables>` for information on how a site
 administrator may define these variables.
 
+Parent Job Results
+~~~~~~~~~~~~~~~~~~
+
+A job may return data to Zuul for later use by jobs which depend on
+it.  For details, see :ref:`return_values`.
 
 SSH Keys
 --------
@@ -503,9 +511,9 @@
 Return Values
 -------------
 
-The job may return some values to Zuul to affect its behavior.  To
-return a value, use the *zuul_return* Ansible module in a job
-playbook.  For example:
+A job may return some values to Zuul to affect its behavior and for
+use by other jobs..  To return a value, use the ``zuul_return``
+Ansible module in a job playbook.  For example:
 
 .. code-block:: yaml
 
@@ -514,12 +522,11 @@
         data:
           foo: bar
 
-Will return the dictionary "{'foo': 'bar'}" to Zuul.
+Will return the dictionary ``{'foo': 'bar'}`` to Zuul.
 
 .. TODO: xref to section describing formatting
 
-Several uses of these values are planned, but the only currently
-implemented use is to set the log URL for a build.  To do so, set the
+To set the log URL for a build, use *zuul_return* to set the
 **zuul.log_url** value.  For example:
 
 .. code-block:: yaml
@@ -529,3 +536,10 @@
         data:
           zuul:
             log_url: http://logs.example.com/path/to/build/logs
+
+Any values other than those in the ``zuul`` hierarchy will be supplied
+as Ansible variables to child jobs.  These variables have less
+precedence than any other type of variable in Zuul, so be sure their
+names are not shared by any job variables.  If more than one parent
+job returns the same variable, the value from the later job in the job
+graph will take precedence.
diff --git a/tests/fixtures/config/data-return/git/common-config/playbooks/child.yaml b/tests/fixtures/config/data-return/git/common-config/playbooks/child.yaml
new file mode 100644
index 0000000..d147e13
--- /dev/null
+++ b/tests/fixtures/config/data-return/git/common-config/playbooks/child.yaml
@@ -0,0 +1,7 @@
+- hosts: localhost
+  tasks:
+    - name: Assert returned variables are valid
+      assert:
+        that:
+          - child.value1 == 'data-return-relative'
+          - child.value2 == 'data-return'
diff --git a/tests/fixtures/config/data-return/git/common-config/playbooks/data-return-relative.yaml b/tests/fixtures/config/data-return/git/common-config/playbooks/data-return-relative.yaml
new file mode 100644
index 0000000..e9193a8
--- /dev/null
+++ b/tests/fixtures/config/data-return/git/common-config/playbooks/data-return-relative.yaml
@@ -0,0 +1,8 @@
+- hosts: localhost
+  tasks:
+    - zuul_return:
+        data:
+          zuul:
+            log_url: http://example.com/test/log/url/
+          child:
+            value1: data-return-relative
diff --git a/tests/fixtures/config/data-return/git/common-config/playbooks/data-return.yaml b/tests/fixtures/config/data-return/git/common-config/playbooks/data-return.yaml
index 5e412c3..db54277 100644
--- a/tests/fixtures/config/data-return/git/common-config/playbooks/data-return.yaml
+++ b/tests/fixtures/config/data-return/git/common-config/playbooks/data-return.yaml
@@ -4,3 +4,6 @@
         data:
           zuul:
             log_url: http://example.com/test/log/url/
+          child:
+            value1: data-return
+            value2: data-return
diff --git a/tests/fixtures/config/data-return/git/common-config/zuul.yaml b/tests/fixtures/config/data-return/git/common-config/zuul.yaml
index 906dc5b..2d5c51f 100644
--- a/tests/fixtures/config/data-return/git/common-config/zuul.yaml
+++ b/tests/fixtures/config/data-return/git/common-config/zuul.yaml
@@ -21,12 +21,18 @@
 
 - job:
     name: data-return-relative
-    run: playbooks/data-return
     success-url: docs/index.html
 
+- job:
+    name: child
+
 - project:
     name: org/project
     check:
       jobs:
         - data-return
         - data-return-relative
+        - child:
+            dependencies:
+              - data-return
+              - data-return-relative
diff --git a/tests/fixtures/zuul-executor-hostname.conf b/tests/fixtures/zuul-executor-hostname.conf
index 7db144d..8199aba 100644
--- a/tests/fixtures/zuul-executor-hostname.conf
+++ b/tests/fixtures/zuul-executor-hostname.conf
@@ -16,7 +16,7 @@
 
 [executor]
 git_dir=/tmp/zuul-test/executor-git
-hostname=test-executor-hostname.openstack.org
+hostname=test-executor-hostname.example.com
 
 [connection gerrit]
 driver=gerrit
diff --git a/tests/unit/test_executor.py b/tests/unit/test_executor.py
index ac7ae34..f051ec4 100755
--- a/tests/unit/test_executor.py
+++ b/tests/unit/test_executor.py
@@ -408,5 +408,5 @@
     tenant_config_file = 'config/single-tenant/main.yaml'
 
     def test_executor_hostname(self):
-        self.assertEqual('test-executor-hostname.openstack.org',
+        self.assertEqual('test-executor-hostname.example.com',
                          self.executor_server.hostname)
diff --git a/tests/unit/test_v3.py b/tests/unit/test_v3.py
index 20f39bb..b4702f9 100755
--- a/tests/unit/test_v3.py
+++ b/tests/unit/test_v3.py
@@ -1744,14 +1744,13 @@
     tenant_config_file = 'config/data-return/main.yaml'
 
     def test_data_return(self):
-        # This exercises a proposed change to a role being checked out
-        # and used.
         A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
         self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
         self.waitUntilSettled()
         self.assertHistory([
             dict(name='data-return', result='SUCCESS', changes='1,1'),
             dict(name='data-return-relative', result='SUCCESS', changes='1,1'),
+            dict(name='child', result='SUCCESS', changes='1,1'),
         ], ordered=False)
         self.assertIn('- data-return http://example.com/test/log/url/',
                       A.messages[-1])
diff --git a/zuul/model.py b/zuul/model.py
index d2595c3..e4b6eb5 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -836,6 +836,7 @@
             source_context=None,
             source_line=None,
             inheritance_path=(),
+            parent_data=None,
         )
 
         self.inheritable_attributes = {}
@@ -932,6 +933,21 @@
         Job._deepUpdate(v, other_vars)
         self.variables = v
 
+    def updateParentData(self, other_vars):
+        # Update variables, but give the current values priority (used
+        # for job return data which is lower precedence than defined
+        # job vars).
+        v = self.parent_data or {}
+        Job._deepUpdate(v, other_vars)
+        # To avoid running afoul of checks that jobs don't set zuul
+        # variables, remove them from parent data here.
+        if 'zuul' in v:
+            del v['zuul']
+        self.parent_data = v
+        v = copy.deepcopy(self.parent_data)
+        Job._deepUpdate(v, self.variables)
+        self.variables = v
+
     def updateProjects(self, other_projects):
         required_projects = self.required_projects.copy()
         required_projects.update(other_projects)
@@ -1571,8 +1587,8 @@
             else:
                 jobs_not_started.add(job)
 
-        # Attempt to request nodes for jobs in the order jobs appear
-        # in configuration.
+        # Attempt to run jobs in the order they appear in
+        # configuration.
         for job in self.job_graph.getJobs():
             if job not in jobs_not_started:
                 continue
@@ -1582,6 +1598,9 @@
                 if parent_job.name not in successful_job_names:
                     all_parent_jobs_successful = False
                     break
+                parent_build = self.current_build_set.getBuild(parent_job.name)
+                if parent_build.result_data:
+                    job.updateParentData(parent_build.result_data)
             if all_parent_jobs_successful:
                 nodeset = self.current_build_set.getJobNodeSet(job.name)
                 if nodeset is None: