Merge "Update webapp status json to support tenants" into feature/zuulv3
diff --git a/tests/base.py b/tests/base.py
index 073b8ad..b343655 100755
--- a/tests/base.py
+++ b/tests/base.py
@@ -41,7 +41,7 @@
 import fixtures
 import statsd
 import testtools
-from git import GitCommandError
+from git.exc import NoSuchPathError
 
 import zuul.connection.gerrit
 import zuul.connection.smtp
@@ -640,17 +640,20 @@
         :rtype: bool
 
         """
-        project = self.parameters['ZUUL_PROJECT']
-        path = os.path.join(self.jobdir.git_root, project)
-        repo = git.Repo(path)
-        ref = self.parameters['ZUUL_REF']
-        repo_messages = [c.message.strip() for c in repo.iter_commits(ref)]
-        commit_messages = ['%s-1' % change.subject for change in changes]
-        self.log.debug("Checking if build %s has changes; commit_messages %s;"
-                       " repo_messages %s" % (self, commit_messages,
-                                              repo_messages))
-        for msg in commit_messages:
-            if msg not in repo_messages:
+        for change in changes:
+            path = os.path.join(self.jobdir.git_root, change.project)
+            try:
+                repo = git.Repo(path)
+            except NoSuchPathError as e:
+                self.log.debug('%s' % e)
+                return False
+            ref = self.parameters['ZUUL_REF']
+            repo_messages = [c.message.strip() for c in repo.iter_commits(ref)]
+            commit_message = '%s-1' % change.subject
+            self.log.debug("Checking if build %s has changes; commit_message "
+                           "%s; repo_messages %s" % (self, commit_message,
+                                                     repo_messages))
+            if commit_message not in repo_messages:
                 self.log.debug("  messages do not match")
                 return False
         self.log.debug("  OK")
@@ -1262,19 +1265,6 @@
         commit = repo.index.commit('Creating a fake commit')
         return commit.hexsha
 
-    def ref_has_change(self, ref, change):
-        # TODOv3(jeblair): this should probably be removed in favor of
-        # build.hasChanges
-        path = os.path.join(self.git_root, change.project)
-        repo = git.Repo(path)
-        try:
-            for commit in repo.iter_commits(ref):
-                if commit.message.strip() == ('%s-1' % change.subject):
-                    return True
-        except GitCommandError:
-            pass
-        return False
-
     def orderedRelease(self):
         # Run one build at a time to ensure non-race order:
         while len(self.builds):
@@ -1540,13 +1530,16 @@
         os.makedirs(root)
         f = tempfile.NamedTemporaryFile(dir=root, delete=False)
         f.write("""
-tenants:
-  - name: openstack
-    include:
-      - %s
-        """ % os.path.abspath(path))
+- tenant:
+    name: openstack
+    source:
+      gerrit:
+        config-repos:
+          - %s
+        """ % path)
         f.close()
-        self.config.set('zuul', 'tenant_config', f.name)
+        self.config.set('zuul', 'tenant_config',
+                        os.path.join(FIXTURE_DIR, f.name))
 
     def addCommitToRepo(self, project, message, files,
                         branch='master', tag=None):
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 20906e6..0ddb6e5 100644
--- a/tests/fixtures/config/single-tenant/git/common-config/zuul.yaml
+++ b/tests/fixtures/config/single-tenant/git/common-config/zuul.yaml
@@ -70,6 +70,10 @@
     name: project-test2
 
 - job:
+    name: project1-project2-integration
+    queue-name: integration
+
+- job:
     name: experimental-project-test
 
 - project:
@@ -95,6 +99,7 @@
             jobs:
               - project-test1
               - project-test2
+              - project1-project2-integration
     gate:
       queue: integrated
       jobs:
@@ -102,6 +107,7 @@
             jobs:
               - project-test1
               - project-test2
+              - project1-project2-integration
 
 - project:
     name: org/project2
@@ -120,3 +126,12 @@
         - project-merge:
             jobs:
               - experimental-project-test
+
+- project:
+    name: org/noop-project
+    check:
+      jobs:
+        - noop
+    gate:
+      jobs:
+        - noop
diff --git a/tests/fixtures/config/single-tenant/git/layout-disabled-at/zuul.yaml b/tests/fixtures/config/single-tenant/git/layout-disabled-at/zuul.yaml
new file mode 100644
index 0000000..4cf6f16
--- /dev/null
+++ b/tests/fixtures/config/single-tenant/git/layout-disabled-at/zuul.yaml
@@ -0,0 +1,30 @@
+- pipeline:
+    name: check
+    manager: independent
+    source:
+      gerrit
+    trigger:
+      gerrit:
+        - event: patchset-created
+    success:
+      gerrit:
+        verified: 1
+    failure:
+      gerrit:
+        verified: -1
+    disabled:
+      smtp:
+        to: you@example.com
+    disable-after-consecutive-failures: 3
+
+- job:
+    name: project-test1
+    nodes:
+      - name: controller
+        image: image1
+
+- project:
+    name: org/project
+    check:
+      jobs:
+        - project-test1
diff --git a/tests/fixtures/config/single-tenant/git/layout-irrelevant-files/zuul.yaml b/tests/fixtures/config/single-tenant/git/layout-irrelevant-files/zuul.yaml
new file mode 100644
index 0000000..f243bcc
--- /dev/null
+++ b/tests/fixtures/config/single-tenant/git/layout-irrelevant-files/zuul.yaml
@@ -0,0 +1,27 @@
+- pipeline:
+    name: check
+    manager: independent
+    source:
+      gerrit
+    trigger:
+      gerrit:
+        - event: patchset-created
+    success:
+      gerrit:
+        verified: 1
+    failure:
+      gerrit:
+        verified: -1
+
+
+- job:
+    name: project-test-irrelevant-files
+
+- project:
+    name: org/project
+    check:
+      jobs:
+        - project-test-irrelevant-files:
+            irrelevant-files:
+              - ^README$
+              - ^ignoreme$
diff --git a/tests/fixtures/config/single-tenant/git/layout-repo-deleted/zuul.yaml b/tests/fixtures/config/single-tenant/git/layout-repo-deleted/zuul.yaml
new file mode 100644
index 0000000..2bffc3e
--- /dev/null
+++ b/tests/fixtures/config/single-tenant/git/layout-repo-deleted/zuul.yaml
@@ -0,0 +1,72 @@
+- pipeline:
+    name: check
+    manager: independent
+    source:
+      gerrit
+    trigger:
+      gerrit:
+        - event: patchset-created
+    success:
+      gerrit:
+        verified: 1
+    failure:
+      gerrit:
+        verified: -1
+
+- pipeline:
+    name: gate
+    manager: dependent
+    success-message: Build succeeded (gate).
+    source:
+      gerrit
+    trigger:
+      gerrit:
+        - event: comment-added
+          approval:
+            - approved: 1
+    success:
+      gerrit:
+        verified: 2
+        submit: true
+    failure:
+      gerrit:
+        verified: -2
+    start:
+      gerrit:
+        verified: 0
+    precedence: high
+
+- job:
+    name: project-merge
+    hold-following-changes: true
+
+- job:
+    name: project-test1
+    nodes:
+      - name: controller
+        image: image1
+
+- job:
+    name: project-test1
+    branches: stable
+    nodes:
+      - name: controller
+        image: image2
+
+- job:
+    name: project-test2
+
+- project:
+    name: org/delete-project
+    check:
+      jobs:
+        - project-merge:
+            jobs:
+              - project-test1
+              - project-test2
+    gate:
+      jobs:
+        - project-merge:
+            jobs:
+              - project-test1
+              - project-test2
diff --git a/tests/fixtures/config/single-tenant/git/layout-smtp/zuul.yaml b/tests/fixtures/config/single-tenant/git/layout-smtp/zuul.yaml
new file mode 100644
index 0000000..9effb1f
--- /dev/null
+++ b/tests/fixtures/config/single-tenant/git/layout-smtp/zuul.yaml
@@ -0,0 +1,81 @@
+- pipeline:
+    name: check
+    manager: independent
+    source:
+      gerrit
+    trigger:
+      gerrit:
+        - event: patchset-created
+    start:
+      smtp:
+        to: you@example.com
+    success:
+      gerrit:
+        verified: 1
+      smtp:
+        to: alternative_me@example.com
+        from: zuul_from@example.com
+    failure:
+      gerrit:
+        verified: -1
+
+- pipeline:
+    name: gate
+    manager: dependent
+    success-message: Build succeeded (gate).
+    source:
+      gerrit
+    trigger:
+      gerrit:
+        - event: comment-added
+          approval:
+            - approved: 1
+    success:
+      gerrit:
+        verified: 2
+        submit: true
+    failure:
+      gerrit:
+        verified: -2
+    start:
+      gerrit:
+        verified: 0
+    precedence: high
+
+- job:
+    name: project-merge
+    hold-following-changes: true
+
+- job:
+    name: project-test1
+    nodes:
+      - name: controller
+        image: image1
+
+- job:
+    name: project-test1
+    branches: stable
+    nodes:
+      - name: controller
+        image: image2
+
+- job:
+    name: project-test2
+
+- job:
+    name: experimental-project-test
+
+- project:
+    name: org/project
+    check:
+      jobs:
+        - project-merge:
+            jobs:
+              - project-test1
+              - project-test2
+    gate:
+      jobs:
+        - project-merge:
+            jobs:
+              - project-test1
+              - project-test2
diff --git a/tests/fixtures/config/single-tenant/git/org_delete-project/README b/tests/fixtures/config/single-tenant/git/org_delete-project/README
new file mode 100644
index 0000000..9daeafb
--- /dev/null
+++ b/tests/fixtures/config/single-tenant/git/org_delete-project/README
@@ -0,0 +1 @@
+test
diff --git a/tests/fixtures/config/single-tenant/git/org_noop-project/README b/tests/fixtures/config/single-tenant/git/org_noop-project/README
new file mode 100644
index 0000000..9daeafb
--- /dev/null
+++ b/tests/fixtures/config/single-tenant/git/org_noop-project/README
@@ -0,0 +1 @@
+test
diff --git a/tests/fixtures/config/single-tenant/git/org_unknown/README b/tests/fixtures/config/single-tenant/git/org_unknown/README
new file mode 100644
index 0000000..9daeafb
--- /dev/null
+++ b/tests/fixtures/config/single-tenant/git/org_unknown/README
@@ -0,0 +1 @@
+test
diff --git a/tests/fixtures/layout-repo-deleted.yaml b/tests/fixtures/layout-repo-deleted.yaml
deleted file mode 100644
index 967009a..0000000
--- a/tests/fixtures/layout-repo-deleted.yaml
+++ /dev/null
@@ -1,52 +0,0 @@
-pipelines:
-  - name: check
-    manager: IndependentPipelineManager
-    trigger:
-      gerrit:
-        - event: patchset-created
-    success:
-      gerrit:
-        verified: 1
-    failure:
-      gerrit:
-        verified: -1
-
-  - name: post
-    manager: IndependentPipelineManager
-    trigger:
-      gerrit:
-        - event: ref-updated
-          ref: ^(?!refs/).*$
-
-  - name: gate
-    manager: DependentPipelineManager
-    failure-message: Build failed.  For information on how to proceed, see http://wiki.example.org/Test_Failures
-    trigger:
-      gerrit:
-        - event: comment-added
-          approval:
-            - approved: 1
-    success:
-      gerrit:
-        verified: 2
-        submit: true
-    failure:
-      gerrit:
-        verified: -2
-    start:
-      gerrit:
-        verified: 0
-    precedence: high
-
-projects:
-  - name: org/delete-project
-    check:
-      - project-merge:
-        - project-test1
-        - project-test2
-    gate:
-      - project-merge:
-        - project-test1
-        - project-test2
-    post:
-      - project-post
diff --git a/tests/fixtures/layout-skip-if.yaml b/tests/fixtures/layout-skip-if.yaml
deleted file mode 100644
index 0cfb445..0000000
--- a/tests/fixtures/layout-skip-if.yaml
+++ /dev/null
@@ -1,29 +0,0 @@
-pipelines:
-  - name: check
-    manager: IndependentPipelineManager
-    trigger:
-      gerrit:
-        - event: patchset-created
-    success:
-      gerrit:
-        verified: 1
-    failure:
-      gerrit:
-        verified: -1
-
-
-jobs:
-  # Defining a metajob will validate that the skip-if attribute of the
-  # metajob is correctly copied to the job.
-  - name: ^.*skip-if$
-    skip-if:
-      - project: ^org/project$
-        branch: ^master$
-        all-files-match-any:
-          - ^README$
-  - name: project-test-skip-if
-
-projects:
-  - name: org/project
-    check:
-      - project-test-skip-if
diff --git a/tests/test_merger_repo.py b/tests/test_merger_repo.py
index 7bf08ee..5062c14 100644
--- a/tests/test_merger_repo.py
+++ b/tests/test_merger_repo.py
@@ -31,14 +31,12 @@
 class TestMergerRepo(ZuulTestCase):
 
     log = logging.getLogger("zuul.test.merger.repo")
+    tenant_config_file = 'config/single-tenant/main.yaml'
     workspace_root = None
 
     def setUp(self):
-        self.skip("Disabled for early v3 development")
-
-    # def setUp(self):
-    #     super(TestMergerRepo, self).setUp()
-    #     self.workspace_root = os.path.join(self.test_root, 'workspace')
+        super(TestMergerRepo, self).setUp()
+        self.workspace_root = os.path.join(self.test_root, 'workspace')
 
     def test_ensure_cloned(self):
         parent_path = os.path.join(self.upstream_root, 'org/project1')
diff --git a/tests/test_requirements.py b/tests/test_requirements.py
index 1cad659..1f179e6 100644
--- a/tests/test_requirements.py
+++ b/tests/test_requirements.py
@@ -16,6 +16,7 @@
 
 import logging
 import time
+from unittest import skip
 
 from tests.base import ZuulTestCase
 
@@ -27,14 +28,13 @@
 class TestRequirements(ZuulTestCase):
     """Test pipeline and trigger requirements"""
 
-    def setUp(self):
-        self.skip("Disabled for early v3 development")
-
+    @skip("Disabled for early v3 development")
     def test_pipeline_require_approval_newer_than(self):
         "Test pipeline requirement: approval newer than"
         return self._test_require_approval_newer_than('org/project1',
                                                       'project1-pipeline')
 
+    @skip("Disabled for early v3 development")
     def test_trigger_require_approval_newer_than(self):
         "Test trigger requirement: approval newer than"
         return self._test_require_approval_newer_than('org/project2',
@@ -68,11 +68,13 @@
         self.assertEqual(len(self.history), 1)
         self.assertEqual(self.history[0].name, job)
 
+    @skip("Disabled for early v3 development")
     def test_pipeline_require_approval_older_than(self):
         "Test pipeline requirement: approval older than"
         return self._test_require_approval_older_than('org/project1',
                                                       'project1-pipeline')
 
+    @skip("Disabled for early v3 development")
     def test_trigger_require_approval_older_than(self):
         "Test trigger requirement: approval older than"
         return self._test_require_approval_older_than('org/project2',
@@ -106,11 +108,13 @@
         self.assertEqual(len(self.history), 1)
         self.assertEqual(self.history[0].name, job)
 
+    @skip("Disabled for early v3 development")
     def test_pipeline_require_approval_username(self):
         "Test pipeline requirement: approval username"
         return self._test_require_approval_username('org/project1',
                                                     'project1-pipeline')
 
+    @skip("Disabled for early v3 development")
     def test_trigger_require_approval_username(self):
         "Test trigger requirement: approval username"
         return self._test_require_approval_username('org/project2',
@@ -137,11 +141,13 @@
         self.assertEqual(len(self.history), 1)
         self.assertEqual(self.history[0].name, job)
 
+    @skip("Disabled for early v3 development")
     def test_pipeline_require_approval_email(self):
         "Test pipeline requirement: approval email"
         return self._test_require_approval_email('org/project1',
                                                  'project1-pipeline')
 
+    @skip("Disabled for early v3 development")
     def test_trigger_require_approval_email(self):
         "Test trigger requirement: approval email"
         return self._test_require_approval_email('org/project2',
@@ -168,11 +174,13 @@
         self.assertEqual(len(self.history), 1)
         self.assertEqual(self.history[0].name, job)
 
+    @skip("Disabled for early v3 development")
     def test_pipeline_require_approval_vote1(self):
         "Test pipeline requirement: approval vote with one value"
         return self._test_require_approval_vote1('org/project1',
                                                  'project1-pipeline')
 
+    @skip("Disabled for early v3 development")
     def test_trigger_require_approval_vote1(self):
         "Test trigger requirement: approval vote with one value"
         return self._test_require_approval_vote1('org/project2',
@@ -205,11 +213,13 @@
         self.assertEqual(len(self.history), 1)
         self.assertEqual(self.history[0].name, job)
 
+    @skip("Disabled for early v3 development")
     def test_pipeline_require_approval_vote2(self):
         "Test pipeline requirement: approval vote with two values"
         return self._test_require_approval_vote2('org/project1',
                                                  'project1-pipeline')
 
+    @skip("Disabled for early v3 development")
     def test_trigger_require_approval_vote2(self):
         "Test trigger requirement: approval vote with two values"
         return self._test_require_approval_vote2('org/project2',
@@ -262,6 +272,7 @@
         self.assertEqual(len(self.history), 2)
         self.assertEqual(self.history[1].name, job)
 
+    @skip("Disabled for early v3 development")
     def test_pipeline_require_current_patchset(self):
         "Test pipeline requirement: current-patchset"
         self.updateConfigLayout(
@@ -290,6 +301,7 @@
         self.waitUntilSettled()
         self.assertEqual(len(self.history), 3)
 
+    @skip("Disabled for early v3 development")
     def test_pipeline_require_open(self):
         "Test pipeline requirement: open"
         self.updateConfigLayout(
@@ -308,6 +320,7 @@
         self.waitUntilSettled()
         self.assertEqual(len(self.history), 1)
 
+    @skip("Disabled for early v3 development")
     def test_pipeline_require_status(self):
         "Test pipeline requirement: status"
         self.updateConfigLayout(
@@ -358,11 +371,13 @@
         self.waitUntilSettled()
         self.assertEqual(len(self.history), 1)
 
+    @skip("Disabled for early v3 development")
     def test_pipeline_reject_username(self):
         "Test negative pipeline requirement: no comment from jenkins"
         return self._test_require_reject_username('org/project1',
                                                   'project1-pipeline')
 
+    @skip("Disabled for early v3 development")
     def test_trigger_reject_username(self):
         "Test negative trigger requirement: no comment from jenkins"
         return self._test_require_reject_username('org/project2',
@@ -418,10 +433,12 @@
         self.assertEqual(len(self.history), 3)
         self.assertEqual(self.history[2].name, job)
 
+    @skip("Disabled for early v3 development")
     def test_pipeline_require_reject(self):
         "Test pipeline requirement: rejections absent"
         return self._test_require_reject('org/project1', 'project1-pipeline')
 
+    @skip("Disabled for early v3 development")
     def test_trigger_require_reject(self):
         "Test trigger requirement: rejections absent"
         return self._test_require_reject('org/project2', 'project2-trigger')
diff --git a/tests/test_scheduler.py b/tests/test_scheduler.py
index dc479ab..f3ae204 100755
--- a/tests/test_scheduler.py
+++ b/tests/test_scheduler.py
@@ -20,7 +20,6 @@
 import re
 import shutil
 import time
-import yaml
 from unittest import skip
 
 import git
@@ -284,6 +283,7 @@
             dict(name='project-test2', changes='1,1'),
             dict(name='project-test1', changes='2,1'),
             dict(name='project-test2', changes='2,1'),
+            dict(name='project1-project2-integration', changes='2,1'),
             dict(name='project-test1', changes='2,1 3,1'),
             dict(name='project-test2', changes='2,1 3,1'),
         ])
@@ -297,6 +297,10 @@
             dict(name='project-test2', result='SUCCESS', changes='1,1'),
             dict(name='project-test1', result='SUCCESS', changes='2,1'),
             dict(name='project-test2', result='SUCCESS', changes='2,1'),
+            dict(
+                name='project1-project2-integration',
+                result='SUCCESS',
+                changes='2,1'),
             dict(name='project-test1', result='SUCCESS', changes='2,1 3,1'),
             dict(name='project-test2', result='SUCCESS', changes='2,1 3,1'),
         ])
@@ -500,7 +504,6 @@
         self.assertEqual(B.reported, 2)
         self.assertEqual(C.reported, 2)
 
-    @skip("Disabled for early v3 development")
     def test_failed_change_at_head_with_queue(self):
         "Test that if a change at the head fails, queued jobs are canceled"
 
@@ -522,8 +525,10 @@
         queue = self.gearman_server.getQueue()
         self.assertEqual(len(self.builds), 0)
         self.assertEqual(len(queue), 1)
-        self.assertEqual(queue[0].name, 'build:project-merge')
-        self.assertTrue(self.job_has_changes(queue[0], A))
+        self.assertEqual(queue[0].name, 'launcher:launch')
+        job_args = json.loads(queue[0].arguments)
+        self.assertEqual(job_args['job'], 'project-merge')
+        self.assertEqual(job_args['items'][0]['number'], '%d' % A.number)
 
         self.gearman_server.release('.*-merge')
         self.waitUntilSettled()
@@ -535,12 +540,19 @@
 
         self.assertEqual(len(self.builds), 0)
         self.assertEqual(len(queue), 6)
-        self.assertEqual(queue[0].name, 'build:project-test1')
-        self.assertEqual(queue[1].name, 'build:project-test2')
-        self.assertEqual(queue[2].name, 'build:project-test1')
-        self.assertEqual(queue[3].name, 'build:project-test2')
-        self.assertEqual(queue[4].name, 'build:project-test1')
-        self.assertEqual(queue[5].name, 'build:project-test2')
+
+        self.assertEqual(
+            json.loads(queue[0].arguments)['job'], 'project-test1')
+        self.assertEqual(
+            json.loads(queue[1].arguments)['job'], 'project-test2')
+        self.assertEqual(
+            json.loads(queue[2].arguments)['job'], 'project-test1')
+        self.assertEqual(
+            json.loads(queue[3].arguments)['job'], 'project-test2')
+        self.assertEqual(
+            json.loads(queue[4].arguments)['job'], 'project-test1')
+        self.assertEqual(
+            json.loads(queue[5].arguments)['job'], 'project-test2')
 
         self.release(queue[0])
         self.waitUntilSettled()
@@ -710,37 +722,6 @@
         self.assertEqual(B.reported, 2)
         self.assertEqual(C.reported, 2)
 
-    @skip("Disabled for early v3 development")
-    def test_parse_skip_if(self):
-        job_yaml = """
-jobs:
-  - name: job_name
-    skip-if:
-      - project: ^project_name$
-        branch: ^stable/icehouse$
-        all-files-match-any:
-          - ^filename$
-      - project: ^project2_name$
-        all-files-match-any:
-          - ^filename2$
-    """.strip()
-        data = yaml.load(job_yaml)
-        config_job = data.get('jobs')[0]
-        cm = zuul.change_matcher
-        expected = cm.MatchAny([
-            cm.MatchAll([
-                cm.ProjectMatcher('^project_name$'),
-                cm.BranchMatcher('^stable/icehouse$'),
-                cm.MatchAllFiles([cm.FileMatcher('^filename$')]),
-            ]),
-            cm.MatchAll([
-                cm.ProjectMatcher('^project2_name$'),
-                cm.MatchAllFiles([cm.FileMatcher('^filename2$')]),
-            ]),
-        ])
-        matcher = self.sched._parseSkipIf(config_job)
-        self.assertEqual(expected, matcher)
-
     def test_patch_order(self):
         "Test that dependent patches are tested in the right order"
         A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
@@ -1325,7 +1306,6 @@
         self.assertIn('Build succeeded', E.messages[1])
         self.assertEqual(len(self.history), 18)
 
-    @skip("Disabled for early v3 development")
     def test_head_is_dequeued_once(self):
         "Test that if a change at the head fails it is dequeued only once"
         # If it's dequeued more than once, we should see extra
@@ -1339,8 +1319,8 @@
         B.addApproval('code-review', 2)
         C.addApproval('code-review', 2)
 
-        self.launch_server.failJob('project1-test1', A)
-        self.launch_server.failJob('project1-test2', A)
+        self.launch_server.failJob('project-test1', A)
+        self.launch_server.failJob('project-test2', A)
         self.launch_server.failJob('project1-project2-integration', A)
 
         self.fake_gerrit.addEvent(A.addApproval('approved', 1))
@@ -1350,8 +1330,8 @@
         self.waitUntilSettled()
 
         self.assertEqual(len(self.builds), 1)
-        self.assertEqual(self.builds[0].name, 'project1-merge')
-        self.assertTrue(self.job_has_changes(self.builds[0], A))
+        self.assertEqual(self.builds[0].name, 'project-merge')
+        self.assertTrue(self.builds[0].hasChanges(A))
 
         self.launch_server.release('.*-merge')
         self.waitUntilSettled()
@@ -1361,14 +1341,14 @@
         self.waitUntilSettled()
 
         self.assertEqual(len(self.builds), 9)
-        self.assertEqual(self.builds[0].name, 'project1-test1')
-        self.assertEqual(self.builds[1].name, 'project1-test2')
+        self.assertEqual(self.builds[0].name, 'project-test1')
+        self.assertEqual(self.builds[1].name, 'project-test2')
         self.assertEqual(self.builds[2].name, 'project1-project2-integration')
-        self.assertEqual(self.builds[3].name, 'project1-test1')
-        self.assertEqual(self.builds[4].name, 'project1-test2')
+        self.assertEqual(self.builds[3].name, 'project-test1')
+        self.assertEqual(self.builds[4].name, 'project-test2')
         self.assertEqual(self.builds[5].name, 'project1-project2-integration')
-        self.assertEqual(self.builds[6].name, 'project1-test1')
-        self.assertEqual(self.builds[7].name, 'project1-test2')
+        self.assertEqual(self.builds[6].name, 'project-test1')
+        self.assertEqual(self.builds[7].name, 'project-test2')
         self.assertEqual(self.builds[8].name, 'project1-project2-integration')
 
         self.release(self.builds[0])
@@ -1574,13 +1554,13 @@
         self.assertEqual(A.data['status'], 'MERGED')
         self.assertEqual(A.reported, 2)
 
-    @skip("Disabled for early v3 development")
     def test_merger_repack_large_change(self):
         "Test that the merger works with large changes after a repack"
         # https://bugs.launchpad.net/zuul/+bug/1078946
         # This test assumes the repo is already cloned; make sure it is
+        tenant = self.sched.abide.tenants.get('tenant-one')
         url = self.fake_gerrit.getGitUrl(
-            self.sched.layout.projects['org/project1'])
+            tenant.layout.project_configs.get('org/project1'))
         self.merge_server.merger.addProject('org/project1', url)
         A = self.fake_gerrit.addFakeChange('org/project1', 'master', 'A')
         A.addPatchset(large=True)
@@ -1592,11 +1572,11 @@
         A.addApproval('code-review', 2)
         self.fake_gerrit.addEvent(A.addApproval('approved', 1))
         self.waitUntilSettled()
-        self.assertEqual(self.getJobFromHistory('project1-merge').result,
+        self.assertEqual(self.getJobFromHistory('project-merge').result,
                          'SUCCESS')
-        self.assertEqual(self.getJobFromHistory('project1-test1').result,
+        self.assertEqual(self.getJobFromHistory('project-test1').result,
                          'SUCCESS')
-        self.assertEqual(self.getJobFromHistory('project1-test2').result,
+        self.assertEqual(self.getJobFromHistory('project-test2').result,
                          'SUCCESS')
         self.assertEqual(A.data['status'], 'MERGED')
         self.assertEqual(A.reported, 2)
@@ -2010,7 +1990,6 @@
         self.assertEqual(len(self.history), 10)
         self.assertEqual(self.countJobResults(self.history, 'ABORTED'), 1)
 
-    @skip("Disabled for early v3 development")
     def test_noop_job(self):
         "Test that the internal noop job works"
         A = self.fake_gerrit.addFakeChange('org/noop-project', 'master', 'A')
@@ -2042,7 +2021,6 @@
 
         self.assertEqual(len(self.history), 0)
 
-    @skip("Disabled for early v3 development")
     def test_zuul_refs(self):
         "Test that zuul refs exist and have the right changes"
         self.launch_server.hold_jobs_in_build = True
@@ -2075,15 +2053,22 @@
         self.waitUntilSettled()
 
         a_zref = b_zref = c_zref = d_zref = None
+        a_build = b_build = c_build = d_build = None
         for x in self.builds:
             if x.parameters['ZUUL_CHANGE'] == '3':
                 a_zref = x.parameters['ZUUL_REF']
-            if x.parameters['ZUUL_CHANGE'] == '4':
+                a_build = x
+            elif x.parameters['ZUUL_CHANGE'] == '4':
                 b_zref = x.parameters['ZUUL_REF']
-            if x.parameters['ZUUL_CHANGE'] == '5':
+                b_build = x
+            elif x.parameters['ZUUL_CHANGE'] == '5':
                 c_zref = x.parameters['ZUUL_REF']
-            if x.parameters['ZUUL_CHANGE'] == '6':
+                c_build = x
+            elif x.parameters['ZUUL_CHANGE'] == '6':
                 d_zref = x.parameters['ZUUL_REF']
+                d_build = x
+            if a_build and b_build and c_build and d_build:
+                break
 
         # There are... four... refs.
         self.assertIsNotNone(a_zref)
@@ -2095,27 +2080,20 @@
         refs = set([a_zref, b_zref, c_zref, d_zref])
         self.assertEqual(len(refs), 4)
 
-        # a ref should have a, not b, and should not be in project2
-        self.assertTrue(self.ref_has_change(a_zref, A))
-        self.assertFalse(self.ref_has_change(a_zref, B))
-        self.assertFalse(self.ref_has_change(a_zref, M2))
+        # should have a, not b, and should not be in project2
+        self.assertTrue(a_build.hasChanges(A))
+        self.assertFalse(a_build.hasChanges(B, M2))
 
-        # b ref should have a and b, and should not be in project2
-        self.assertTrue(self.ref_has_change(b_zref, A))
-        self.assertTrue(self.ref_has_change(b_zref, B))
-        self.assertFalse(self.ref_has_change(b_zref, M2))
+        # should have a and b, and should not be in project2
+        self.assertTrue(b_build.hasChanges(A, B))
+        self.assertFalse(b_build.hasChanges(M2))
 
-        # c ref should have a and b in 1, c in 2
-        self.assertTrue(self.ref_has_change(c_zref, A))
-        self.assertTrue(self.ref_has_change(c_zref, B))
-        self.assertTrue(self.ref_has_change(c_zref, C))
-        self.assertFalse(self.ref_has_change(c_zref, D))
+        # should have a and b in 1, c in 2
+        self.assertTrue(c_build.hasChanges(A, B, C))
+        self.assertFalse(c_build.hasChanges(D))
 
-        # d ref should have a and b in 1, c and d in 2
-        self.assertTrue(self.ref_has_change(d_zref, A))
-        self.assertTrue(self.ref_has_change(d_zref, B))
-        self.assertTrue(self.ref_has_change(d_zref, C))
-        self.assertTrue(self.ref_has_change(d_zref, D))
+        # should have a and b in 1, c and d in 2
+        self.assertTrue(d_build.hasChanges(A, B, C, D))
 
         self.launch_server.hold_jobs_in_build = False
         self.launch_server.release()
@@ -2232,35 +2210,36 @@
         self.assertEqual(B.data['status'], 'MERGED')
         self.assertEqual(B.reported, 2)
 
-    @skip("Disabled for early v3 development")
-    def _test_skip_if_jobs(self, branch, should_skip):
-        "Test that jobs with a skip-if filter run only when appropriate"
-        self.updateConfigLayout(
-            'tests/fixtures/layout-skip-if.yaml')
+    def _test_irrelevant_files_jobs(self, should_skip):
+        "Test that jobs with irrelevant-files filter run only when appropriate"
+        self.updateConfigLayout('layout-irrelevant-files')
         self.sched.reconfigure(self.config)
-        self.registerJobs()
+
+        if should_skip:
+            files = {'ignoreme': 'ignored\n'}
+        else:
+            files = {'respectme': 'please!\n'}
 
         change = self.fake_gerrit.addFakeChange('org/project',
-                                                branch,
-                                                'test skip-if')
+                                                'master',
+                                                'test irrelevant-files',
+                                                files=files)
         self.fake_gerrit.addEvent(change.getPatchsetCreatedEvent(1))
         self.waitUntilSettled()
 
         tested_change_ids = [x.changes[0] for x in self.history
-                             if x.name == 'project-test-skip-if']
+                             if x.name == 'project-test-irrelevant-files']
 
         if should_skip:
             self.assertEqual([], tested_change_ids)
         else:
             self.assertIn(change.data['number'], tested_change_ids)
 
-    @skip("Disabled for early v3 development")
-    def test_skip_if_match_skips_job(self):
-        self._test_skip_if_jobs(branch='master', should_skip=True)
+    def test_irrelevant_files_match_skips_job(self):
+        self._test_irrelevant_files_jobs(should_skip=True)
 
-    @skip("Disabled for early v3 development")
-    def test_skip_if_no_match_runs_job(self):
-        self._test_skip_if_jobs(branch='mp', should_skip=False)
+    def test_irrelevant_files_no_match_runs_job(self):
+        self._test_irrelevant_files_jobs(should_skip=False)
 
     @skip("Disabled for early v3 development")
     def test_test_config(self):
@@ -2463,7 +2442,6 @@
                          'debian')
         self.assertIsNone(self.getJobFromHistory('node-project-test2').node)
 
-    @skip("Disabled for early v3 development")
     def test_live_reconfiguration(self):
         "Test that live reconfiguration works"
         self.launch_server.hold_jobs_in_build = True
@@ -2865,10 +2843,8 @@
         self.assertEqual(A.data['status'], 'MERGED')
         self.assertEqual(A.reported, 2)
 
-    @skip("Disabled for early v3 development")
     def test_repo_deleted(self):
-        self.updateConfigLayout(
-            'tests/fixtures/layout-repo-deleted.yaml')
+        self.updateConfigLayout('layout-repo-deleted')
         self.sched.reconfigure(self.config)
 
         self.init_repo("org/delete-project")
@@ -3005,10 +2981,8 @@
             self.assertEqual(len(self.builds), 0)
             self.assertEqual(len(self.history), x * 2)
 
-    @skip("Disabled for early v3 development")
     def test_check_smtp_pool(self):
-        self.updateConfigLayout(
-            'tests/fixtures/layout-smtp.yaml')
+        self.updateConfigLayout('layout-smtp')
         self.sched.reconfigure(self.config)
 
         A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
@@ -3816,7 +3790,6 @@
             'SUCCESS')
         self.assertEqual(A.reported, 1)
 
-    @skip("Disabled for early v3 development")
     def test_crd_gate(self):
         "Test cross-repo dependencies"
         A = self.fake_gerrit.addFakeChange('org/project1', 'master', 'A')
@@ -3856,7 +3829,7 @@
         self.assertEqual(A.data['status'], 'NEW')
         self.assertEqual(B.data['status'], 'NEW')
 
-        for connection in self.connections.values():
+        for connection in self.connections.connections.values():
             connection.maintainCache([])
 
         self.launch_server.hold_jobs_in_build = True
@@ -3879,12 +3852,14 @@
         self.assertEqual(A.reported, 2)
         self.assertEqual(B.reported, 2)
 
-        self.assertEqual(self.getJobFromHistory('project1-merge').changes,
-                         '2,1 1,1')
+        changes = self.getJobFromHistory(
+            'project-merge', 'org/project1').changes
+        self.assertEqual(changes, '2,1 1,1')
 
-    @skip("Disabled for early v3 development")
     def test_crd_branch(self):
         "Test cross-repo dependencies in multiple branches"
+
+        self.create_branch('org/project2', 'mp')
         A = self.fake_gerrit.addFakeChange('org/project1', 'master', 'A')
         B = self.fake_gerrit.addFakeChange('org/project2', 'master', 'B')
         C = self.fake_gerrit.addFakeChange('org/project2', 'mp', 'C')
@@ -3920,10 +3895,10 @@
         self.assertEqual(B.reported, 2)
         self.assertEqual(C.reported, 2)
 
-        self.assertEqual(self.getJobFromHistory('project1-merge').changes,
-                         '2,1 3,1 1,1')
+        changes = self.getJobFromHistory(
+            'project-merge', 'org/project1').changes
+        self.assertEqual(changes, '2,1 3,1 1,1')
 
-    @skip("Disabled for early v3 development")
     def test_crd_multiline(self):
         "Test multiple depends-on lines in commit"
         A = self.fake_gerrit.addFakeChange('org/project1', 'master', 'A')
@@ -3960,8 +3935,9 @@
         self.assertEqual(B.reported, 2)
         self.assertEqual(C.reported, 2)
 
-        self.assertEqual(self.getJobFromHistory('project1-merge').changes,
-                         '2,1 3,1 1,1')
+        changes = self.getJobFromHistory(
+            'project-merge', 'org/project1').changes
+        self.assertEqual(changes, '2,1 3,1 1,1')
 
     def test_crd_unshared_gate(self):
         "Test cross-repo dependencies in unshared gate queues"
@@ -4001,7 +3977,6 @@
         self.assertEqual(A.data['status'], 'MERGED')
         self.assertEqual(A.reported, 2)
 
-    @skip("Disabled for early v3 development")
     def test_crd_gate_reverse(self):
         "Test reverse cross-repo dependencies"
         A = self.fake_gerrit.addFakeChange('org/project1', 'master', 'A')
@@ -4038,8 +4013,9 @@
         self.assertEqual(A.reported, 2)
         self.assertEqual(B.reported, 2)
 
-        self.assertEqual(self.getJobFromHistory('project1-merge').changes,
-                         '2,1 1,1')
+        changes = self.getJobFromHistory(
+            'project-merge', 'org/project1').changes
+        self.assertEqual(changes, '2,1 1,1')
 
     def test_crd_cycle(self):
         "Test cross-repo dependency cycles"
@@ -4063,7 +4039,6 @@
         self.assertEqual(A.data['status'], 'NEW')
         self.assertEqual(B.data['status'], 'NEW')
 
-    @skip("Disabled for early v3 development")
     def test_crd_gate_unknown(self):
         "Test unknown projects in dependent pipeline"
         self.init_repo("org/unknown")
@@ -4106,10 +4081,10 @@
         self.assertEqual(B.data['status'], 'MERGED')
         self.assertEqual(B.reported, 0)
 
-    @skip("Disabled for early v3 development")
     def test_crd_check(self):
         "Test cross-repo dependencies in independent pipelines"
 
+        self.launch_server.hold_jobs_in_build = True
         self.gearman_server.hold_jobs_in_queue = True
         A = self.fake_gerrit.addFakeChange('org/project1', 'master', 'A')
         B = self.fake_gerrit.addFakeChange('org/project2', 'master', 'B')
@@ -4127,27 +4102,37 @@
         self.gearman_server.release()
         self.waitUntilSettled()
 
-        path = os.path.join(self.git_root, "org/project1")
+        self.launch_server.release('.*-merge')
+        self.waitUntilSettled()
+
+        path = os.path.join(self.builds[0].jobdir.git_root, "org/project1")
         repo = git.Repo(path)
         repo_messages = [c.message.strip() for c in repo.iter_commits(ref)]
         repo_messages.reverse()
-        correct_messages = ['initial commit', 'A-1']
+        correct_messages = [
+            'initial commit', 'add content from fixture', 'A-1']
         self.assertEqual(repo_messages, correct_messages)
 
-        path = os.path.join(self.git_root, "org/project2")
+        path = os.path.join(self.builds[0].jobdir.git_root, "org/project2")
         repo = git.Repo(path)
         repo_messages = [c.message.strip() for c in repo.iter_commits(ref)]
         repo_messages.reverse()
-        correct_messages = ['initial commit', 'B-1']
+        correct_messages = [
+            'initial commit', 'add content from fixture', 'B-1']
         self.assertEqual(repo_messages, correct_messages)
 
+        self.launch_server.hold_jobs_in_build = False
+        self.launch_server.release()
+        self.waitUntilSettled()
+
         self.assertEqual(A.data['status'], 'NEW')
         self.assertEqual(B.data['status'], 'NEW')
         self.assertEqual(A.reported, 1)
         self.assertEqual(B.reported, 0)
 
         self.assertEqual(self.history[0].changes, '2,1 1,1')
-        self.assertEqual(len(self.sched.layout.pipelines['check'].queues), 0)
+        tenant = self.sched.abide.tenants.get('tenant-one')
+        self.assertEqual(len(tenant.layout.pipelines['check'].queues), 0)
 
     def test_crd_check_git_depends(self):
         "Test single-repo dependencies in independent pipelines"
@@ -4223,7 +4208,6 @@
         self.assertIn('Build succeeded', A.messages[0])
         self.assertIn('Build succeeded', B.messages[0])
 
-    @skip("Disabled for early v3 development")
     def _test_crd_check_reconfiguration(self, project1, project2):
         "Test cross-repo dependencies re-enqueued in independent pipelines"
 
@@ -4242,8 +4226,9 @@
 
         # Make sure the items still share a change queue, and the
         # first one is not live.
-        self.assertEqual(len(self.sched.layout.pipelines['check'].queues), 1)
-        queue = self.sched.layout.pipelines['check'].queues[0]
+        tenant = self.sched.abide.tenants.get('tenant-one')
+        self.assertEqual(len(tenant.layout.pipelines['check'].queues), 1)
+        queue = tenant.layout.pipelines['check'].queues[0]
         first_item = queue.queue[0]
         for item in queue.queue:
             self.assertEqual(item.queue, first_item.queue)
@@ -4260,13 +4245,11 @@
         self.assertEqual(B.reported, 0)
 
         self.assertEqual(self.history[0].changes, '2,1 1,1')
-        self.assertEqual(len(self.sched.layout.pipelines['check'].queues), 0)
+        self.assertEqual(len(tenant.layout.pipelines['check'].queues), 0)
 
-    @skip("Disabled for early v3 development")
     def test_crd_check_reconfiguration(self):
         self._test_crd_check_reconfiguration('org/project1', 'org/project2')
 
-    @skip("Disabled for early v3 development")
     def test_crd_undefined_project(self):
         """Test that undefined projects in dependencies are handled for
         independent pipelines"""
@@ -4362,7 +4345,6 @@
         self.waitUntilSettled()
         self.assertEqual(self.history[-1].changes, '3,2 2,1 1,2')
 
-    @skip("Disabled for early v3 development")
     def test_crd_check_unknown(self):
         "Test unknown projects in independent pipeline"
         self.init_repo("org/unknown")
@@ -4382,7 +4364,6 @@
         self.assertEqual(B.data['status'], 'NEW')
         self.assertEqual(B.reported, 0)
 
-    @skip("Disabled for early v3 development")
     def test_crd_cycle_join(self):
         "Test an updated change creates a cycle"
         A = self.fake_gerrit.addFakeChange('org/project2', 'master', 'A')
@@ -4408,10 +4389,18 @@
         # call the method that would ultimately be called by the event
         # processing.
 
-        source = self.sched.layout.pipelines['gate'].source
+        tenant = self.sched.abide.tenants.get('tenant-one')
+        source = tenant.layout.pipelines['gate'].source
+
+        # TODO(pabelanger): As we add more source / trigger APIs we should make
+        # it easier for users to create events for testing.
+        event = zuul.model.TriggerEvent()
+        event.trigger_name = 'gerrit'
+        event.change_number = '1'
+        event.patch_number = '2'
         with testtools.ExpectedException(
             Exception, "Dependency cycle detected"):
-            source._getChange(u'1', u'2', True)
+            source.getChange(event, True)
         self.log.debug("Got expected dependency cycle exception")
 
         # Now if we update B to remove the depends-on, everything
@@ -4419,21 +4408,22 @@
 
         B.addPatchset()
         B.data['commitMessage'] = '%s\n' % (B.subject,)
-        source._getChange(u'1', u'2', True)
-        source._getChange(u'2', u'2', True)
 
-    @skip("Disabled for early v3 development")
+        source.getChange(event, True)
+        event.change_number = '2'
+        source.getChange(event, True)
+
     def test_disable_at(self):
         "Test a pipeline will only report to the disabled trigger when failing"
 
-        self.updateConfigLayout(
-            'tests/fixtures/layout-disable-at.yaml')
+        self.updateConfigLayout('layout-disabled-at')
         self.sched.reconfigure(self.config)
 
-        self.assertEqual(3, self.sched.layout.pipelines['check'].disable_at)
+        tenant = self.sched.abide.tenants.get('openstack')
+        self.assertEqual(3, tenant.layout.pipelines['check'].disable_at)
         self.assertEqual(
-            0, self.sched.layout.pipelines['check']._consecutive_failures)
-        self.assertFalse(self.sched.layout.pipelines['check']._disabled)
+            0, tenant.layout.pipelines['check']._consecutive_failures)
+        self.assertFalse(tenant.layout.pipelines['check']._disabled)
 
         A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
         B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
@@ -4464,15 +4454,15 @@
         self.waitUntilSettled()
 
         self.assertEqual(
-            2, self.sched.layout.pipelines['check']._consecutive_failures)
-        self.assertFalse(self.sched.layout.pipelines['check']._disabled)
+            2, tenant.layout.pipelines['check']._consecutive_failures)
+        self.assertFalse(tenant.layout.pipelines['check']._disabled)
 
         self.fake_gerrit.addEvent(C.getPatchsetCreatedEvent(1))
         self.waitUntilSettled()
 
         self.assertEqual(
-            0, self.sched.layout.pipelines['check']._consecutive_failures)
-        self.assertFalse(self.sched.layout.pipelines['check']._disabled)
+            0, tenant.layout.pipelines['check']._consecutive_failures)
+        self.assertFalse(tenant.layout.pipelines['check']._disabled)
 
         self.fake_gerrit.addEvent(D.getPatchsetCreatedEvent(1))
         self.fake_gerrit.addEvent(E.getPatchsetCreatedEvent(1))
@@ -4481,8 +4471,8 @@
 
         # We should be disabled now
         self.assertEqual(
-            3, self.sched.layout.pipelines['check']._consecutive_failures)
-        self.assertTrue(self.sched.layout.pipelines['check']._disabled)
+            3, tenant.layout.pipelines['check']._consecutive_failures)
+        self.assertTrue(tenant.layout.pipelines['check']._disabled)
 
         # We need to wait between each of these patches to make sure the
         # smtp messages come back in an expected order
@@ -4512,30 +4502,35 @@
         self.assertEqual(3, len(self.smtp_messages))
         self.assertEqual(0, len(G.messages))
         self.assertIn('Build failed.', self.smtp_messages[0]['body'])
-        self.assertIn('/7/1/check', self.smtp_messages[0]['body'])
+        self.assertIn(
+            'project-test1 https://server/job', self.smtp_messages[0]['body'])
         self.assertEqual(0, len(H.messages))
         self.assertIn('Build failed.', self.smtp_messages[1]['body'])
-        self.assertIn('/8/1/check', self.smtp_messages[1]['body'])
+        self.assertIn(
+            'project-test1 https://server/job', self.smtp_messages[1]['body'])
         self.assertEqual(0, len(I.messages))
         self.assertIn('Build succeeded.', self.smtp_messages[2]['body'])
-        self.assertIn('/9/1/check', self.smtp_messages[2]['body'])
+        self.assertIn(
+            'project-test1 https://server/job', self.smtp_messages[2]['body'])
 
         # Now reload the configuration (simulate a HUP) to check the pipeline
         # comes out of disabled
         self.sched.reconfigure(self.config)
 
-        self.assertEqual(3, self.sched.layout.pipelines['check'].disable_at)
+        tenant = self.sched.abide.tenants.get('openstack')
+
+        self.assertEqual(3, tenant.layout.pipelines['check'].disable_at)
         self.assertEqual(
-            0, self.sched.layout.pipelines['check']._consecutive_failures)
-        self.assertFalse(self.sched.layout.pipelines['check']._disabled)
+            0, tenant.layout.pipelines['check']._consecutive_failures)
+        self.assertFalse(tenant.layout.pipelines['check']._disabled)
 
         self.fake_gerrit.addEvent(J.getPatchsetCreatedEvent(1))
         self.fake_gerrit.addEvent(K.getPatchsetCreatedEvent(1))
         self.waitUntilSettled()
 
         self.assertEqual(
-            2, self.sched.layout.pipelines['check']._consecutive_failures)
-        self.assertFalse(self.sched.layout.pipelines['check']._disabled)
+            2, tenant.layout.pipelines['check']._consecutive_failures)
+        self.assertFalse(tenant.layout.pipelines['check']._disabled)
 
         # J and K went back to gerrit
         self.assertEqual(1, len(J.messages))
diff --git a/zuul/model.py b/zuul/model.py
index 77b1259..ddb0dde 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -435,7 +435,7 @@
         for k, v in self.attributes.items():
             setattr(self, k, v)
 
-    def __equals__(self, other):
+    def __eq__(self, other):
         # Compare the name and all inheritable attributes to determine
         # whether two jobs with the same name are identically
         # configured.  Useful upon reconfiguration.
@@ -1771,11 +1771,12 @@
             self._createJobTree(change, tree.job_trees, frozen_tree)
 
     def createJobTree(self, item):
-        project_config = self.project_configs[item.change.project.name]
+        project_config = self.project_configs.get(
+            item.change.project.name, None)
         ret = JobTree(None)
         # NOTE(pabelanger): It is possible for a foreign project not to have a
         # configured pipeline, if so return an empty JobTree.
-        if item.pipeline.name in project_config.pipelines:
+        if project_config and item.pipeline.name in project_config.pipelines:
             project_tree = \
                 project_config.pipelines[item.pipeline.name].job_tree
             self._createJobTree(item.change, project_tree.job_trees, ret)
diff --git a/zuul/scheduler.py b/zuul/scheduler.py
index b000a6f..1d27523 100644
--- a/zuul/scheduler.py
+++ b/zuul/scheduler.py
@@ -500,9 +500,9 @@
                                                           last_head):
                         new_jobs = item.getJobs()
                         for build in item.current_build_set.getBuilds():
-                            job = item.layout.getJob(build.job.name)
-                            if job and job in new_jobs:
-                                build.job = job
+                            jobtree = item.job_tree.getJobTreeForJob(build.job)
+                            if jobtree and jobtree.job in new_jobs:
+                                build.job = jobtree.job
                             else:
                                 item.removeBuild(build)
                                 builds_to_cancel.append(build)
@@ -601,13 +601,14 @@
             self.log.debug("Waiting on merger")
             return False
         waiting = False
-        for pipeline in self.layout.pipelines.values():
-            for item in pipeline.getAllItems():
-                for build in item.current_build_set.getBuilds():
-                    if build.result is None:
-                        self.log.debug("%s waiting on %s" %
-                                       (pipeline.manager, build))
-                        waiting = True
+        for tenant in self.abide.tenants.values():
+            for pipeline in tenant.layout.pipelines.values():
+                for item in pipeline.getAllItems():
+                    for build in item.current_build_set.getBuilds():
+                        if build.result is None:
+                            self.log.debug("%s waiting on %s" %
+                                           (pipeline.manager, build))
+                            waiting = True
         if not waiting:
             self.log.debug("All builds are complete")
             return True