Merge "Update zuul-changes script for v3" into feature/zuulv3
diff --git a/tests/fixtures/config/single-tenant/git/common-config/zuul.yaml b/tests/fixtures/config/single-tenant/git/common-config/zuul.yaml
index 14f43f4..2160ef9 100644
--- a/tests/fixtures/config/single-tenant/git/common-config/zuul.yaml
+++ b/tests/fixtures/config/single-tenant/git/common-config/zuul.yaml
@@ -39,6 +39,7 @@
       gerrit:
         - event: ref-updated
           ref: ^(?!refs/).*$
+    precedence: low
 
 - job:
     name: base
diff --git a/tests/fixtures/layouts/multiple-templates.yaml b/tests/fixtures/layouts/multiple-templates.yaml
new file mode 100644
index 0000000..7272cad
--- /dev/null
+++ b/tests/fixtures/layouts/multiple-templates.yaml
@@ -0,0 +1,44 @@
+- pipeline:
+    name: check
+    manager: independent
+    trigger:
+      gerrit:
+        - event: patchset-created
+    success:
+      gerrit:
+        Verified: 1
+    failure:
+      gerrit:
+        Verified: -1
+
+- job:
+    name: base
+    parent: null
+
+- job:
+    name: py27
+
+- project-template:
+    name: python-jobs
+    check:
+      jobs:
+        - py27
+
+- project-template:
+    name: python-trusty-jobs
+    check:
+      jobs:
+        - py27:
+            tags:
+              - trusty
+
+- project:
+    name: org/project1
+    templates:
+      - python-jobs
+      - python-trusty-jobs
+
+- project:
+    name: org/project2
+    templates:
+      - python-jobs
diff --git a/tests/unit/test_scheduler.py b/tests/unit/test_scheduler.py
index 2dcd9bf..7f8c0a3 100755
--- a/tests/unit/test_scheduler.py
+++ b/tests/unit/test_scheduler.py
@@ -4613,6 +4613,56 @@
         self.assertIn('project-test1 : SKIPPED', A.messages[1])
         self.assertIn('project-test2 : SKIPPED', A.messages[1])
 
+    def test_nodepool_priority(self):
+        "Test that nodes are requested at the correct priority"
+
+        self.fake_nodepool.paused = True
+
+        A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
+        self.fake_gerrit.addEvent(A.getRefUpdatedEvent())
+
+        B = self.fake_gerrit.addFakeChange('org/project1', 'master', 'B')
+        self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1))
+
+        C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
+        C.addApproval('Code-Review', 2)
+        self.fake_gerrit.addEvent(C.addApproval('Approved', 1))
+
+        self.waitUntilSettled()
+
+        reqs = self.fake_nodepool.getNodeRequests()
+
+        # The requests come back sorted by oid. Since we have three requests
+        # for the three changes each with a different priority.
+        # Also they get a serial number based on order they were received
+        # so the number on the endof the oid should map to order submitted.
+
+        # * gate first - high priority - change C
+        self.assertEqual(reqs[0]['_oid'], '100-0000000002')
+        self.assertEqual(reqs[0]['node_types'], ['label1'])
+        # * check second - normal priority - change B
+        self.assertEqual(reqs[1]['_oid'], '200-0000000001')
+        self.assertEqual(reqs[1]['node_types'], ['label1'])
+        # * post third - low priority - change A
+        # additionally, the post job defined uses an ubuntu-xenial node,
+        # so we include that check just as an extra verification
+        self.assertEqual(reqs[2]['_oid'], '300-0000000000')
+        self.assertEqual(reqs[2]['node_types'], ['ubuntu-xenial'])
+
+        self.fake_nodepool.paused = False
+        self.waitUntilSettled()
+
+    @simple_layout('layouts/multiple-templates.yaml')
+    def test_multiple_project_templates(self):
+        # Test that applying multiple project templates to a project
+        # doesn't alter them when used for a second project.
+        A = self.fake_gerrit.addFakeChange('org/project2', 'master', 'A')
+        self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
+        self.waitUntilSettled()
+
+        build = self.getJobFromHistory('py27')
+        self.assertEqual(build.parameters['zuul']['jobtags'], [])
+
 
 class TestExecutor(ZuulTestCase):
     tenant_config_file = 'config/single-tenant/main.yaml'
diff --git a/zuul/configloader.py b/zuul/configloader.py
index d597ee0..97dfd21 100644
--- a/zuul/configloader.py
+++ b/zuul/configloader.py
@@ -486,6 +486,7 @@
 
         job = model.Job(conf['name'])
         job.source_context = conf.get('_source_context')
+        job.source_line = conf.get('_start_mark').line +1
 
         is_variant = layout.hasJob(conf['name'])
         if 'parent' in conf:
@@ -1403,19 +1404,20 @@
                             (job.source_context,))
                         continue
                     loaded = conf_root
-                    job.source_context.path = fn
+                    source_context = job.source_context.copy()
+                    source_context.path = fn
                     TenantParser.log.info(
                         "Loading configuration from %s" %
-                        (job.source_context,))
-                    project = job.source_context.project
-                    branch = job.source_context.branch
-                    if job.source_context.trusted:
+                        (source_context,))
+                    project = source_context.project
+                    branch = source_context.branch
+                    if source_context.trusted:
                         incdata = TenantParser._parseConfigProjectLayout(
-                            job.files[fn], job.source_context)
+                            job.files[fn], source_context)
                         config_projects_config.extend(incdata)
                     else:
                         incdata = TenantParser._parseUntrustedProjectLayout(
-                            job.files[fn], job.source_context)
+                            job.files[fn], source_context)
                         untrusted_projects_config.extend(incdata)
                     new_project_unparsed_config[project].extend(incdata)
                     if branch in new_project_unparsed_branch_config.get(
diff --git a/zuul/model.py b/zuul/model.py
index 4c5a51f..eff0ae3 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -44,6 +44,12 @@
     'high': PRECEDENCE_HIGH,
 }
 
+PRIORITY_MAP = {
+    PRECEDENCE_NORMAL: 200,
+    PRECEDENCE_LOW: 300,
+    PRECEDENCE_HIGH: 100,
+}
+
 # Request states
 STATE_REQUESTED = 'requested'
 STATE_PENDING = 'pending'
@@ -525,6 +531,10 @@
         self.canceled = False
 
     @property
+    def priority(self):
+        return PRIORITY_MAP[self.build_set.item.pipeline.precedence]
+
+    @property
     def fulfilled(self):
         return (self._state == STATE_FULFILLED) and not self.failed
 
@@ -817,6 +827,7 @@
         self.other_attributes = dict(
             name=None,
             source_context=None,
+            source_line=None,
             inheritance_path=(),
         )
 
@@ -851,9 +862,11 @@
         return self.name
 
     def __repr__(self):
-        return '<Job %s branches: %s source: %s>' % (self.name,
-                                                     self.branch_matcher,
-                                                     self.source_context)
+        return '<Job %s branches: %s source: %s#%s>' % (
+            self.name,
+            self.branch_matcher,
+            self.source_context,
+            self.source_line)
 
     def __getattr__(self, name):
         v = self.__dict__.get(name)
@@ -1030,7 +1043,9 @@
             if jobname in self.jobs:
                 self.jobs[jobname].extend(jobs)
             else:
-                self.jobs[jobname] = jobs
+                # Be sure to make a copy here since this list may be
+                # modified.
+                self.jobs[jobname] = jobs[:]
 
 
 class JobGraph(object):
diff --git a/zuul/zk.py b/zuul/zk.py
index dcaa172..a3efef2 100644
--- a/zuul/zk.py
+++ b/zuul/zk.py
@@ -147,12 +147,10 @@
             from ZooKeeper).  The watcher should return False when
             further updates are no longer necessary.
         '''
-        priority = 100  # TODO(jeblair): integrate into nodereq
-
         data = node_request.toDict()
         data['created_time'] = time.time()
 
-        path = '%s/%s-' % (self.REQUEST_ROOT, priority)
+        path = '%s/%s-' % (self.REQUEST_ROOT, node_request.priority)
         path = self.client.create(path, self._dictToStr(data),
                                   makepath=True,
                                   sequence=True, ephemeral=True)