Merge "Fully qualify project configuration names" into feature/zuulv3
diff --git a/tests/base.py b/tests/base.py
index 16332bf..78b2ea0 100755
--- a/tests/base.py
+++ b/tests/base.py
@@ -1927,7 +1927,9 @@
     def getPipeline(self, name):
         return self.sched.abide.tenants.values()[0].layout.pipelines.get(name)
 
-    def updateConfigLayout(self, path):
+    def updateConfigLayout(self, path, project_repos=None):
+        if project_repos is None:
+            project_repos = []
         root = os.path.join(self.test_root, "config")
         if not os.path.exists(root):
             os.makedirs(root)
@@ -1939,7 +1941,26 @@
       gerrit:
         config-repos:
           - %s
-        """ % path)
+        project-repos:
+          - org/project
+          - org/project1
+          - org/project2
+          - org/project3
+          - org/project4
+          - org/project5
+          - org/project6
+          - org/one-job-project
+          - org/nonvoting-project
+          - org/templated-project
+          - org/layered-project
+          - org/node-project
+          - org/conflict-project
+          - org/noop-project
+          - org/experimental-project
+          - org/no-jobs-project\n""" % path)
+
+        for repo in project_repos:
+            f.write("          - %s\n" % repo)
         f.close()
         self.config.set('zuul', 'tenant_config',
                         os.path.join(FIXTURE_DIR, f.name))
diff --git a/tests/fixtures/config/duplicate-pipeline/main.yaml b/tests/fixtures/config/duplicate-pipeline/main.yaml
index ba2d8f5..5e1bc6e 100755
--- a/tests/fixtures/config/duplicate-pipeline/main.yaml
+++ b/tests/fixtures/config/duplicate-pipeline/main.yaml
@@ -4,3 +4,5 @@
       gerrit:
         config-repos:
           - common-config
+        project-repos:
+          - org/project
diff --git a/tests/fixtures/config/merges/main.yaml b/tests/fixtures/config/merges/main.yaml
index a22ed5c..039706f 100644
--- a/tests/fixtures/config/merges/main.yaml
+++ b/tests/fixtures/config/merges/main.yaml
@@ -4,3 +4,9 @@
       gerrit:
         config-repos:
           - common-config
+        project-repos:
+          - org/project-cherry-pick
+          - org/project-merge
+          - org/project-merge-branches
+          - org/project-merge-resolve
+
diff --git a/tests/fixtures/config/multi-tenant-semaphore/main.yaml b/tests/fixtures/config/multi-tenant-semaphore/main.yaml
index b1c47b1..7e05d13 100644
--- a/tests/fixtures/config/multi-tenant-semaphore/main.yaml
+++ b/tests/fixtures/config/multi-tenant-semaphore/main.yaml
@@ -5,6 +5,9 @@
         config-repos:
           - common-config
           - tenant-one-config
+        project-repos:
+          - org/project1
+          - org/project2
 
 - tenant:
     name: tenant-two
@@ -13,3 +16,6 @@
         config-repos:
           - common-config
           - tenant-two-config
+        project-repos:
+          - org/project1
+          - org/project2
diff --git a/tests/fixtures/config/multi-tenant/main.yaml b/tests/fixtures/config/multi-tenant/main.yaml
index b1c47b1..4ce2510 100644
--- a/tests/fixtures/config/multi-tenant/main.yaml
+++ b/tests/fixtures/config/multi-tenant/main.yaml
@@ -5,6 +5,8 @@
         config-repos:
           - common-config
           - tenant-one-config
+        project-repos:
+          - org/project1
 
 - tenant:
     name: tenant-two
@@ -13,3 +15,5 @@
         config-repos:
           - common-config
           - tenant-two-config
+        project-repos:
+          - org/project2
diff --git a/tests/fixtures/config/one-job-project/main.yaml b/tests/fixtures/config/one-job-project/main.yaml
index a22ed5c..2211390 100644
--- a/tests/fixtures/config/one-job-project/main.yaml
+++ b/tests/fixtures/config/one-job-project/main.yaml
@@ -4,3 +4,5 @@
       gerrit:
         config-repos:
           - common-config
+        project-repos:
+          - org/one-job-project
diff --git a/tests/fixtures/config/openstack/main.yaml b/tests/fixtures/config/openstack/main.yaml
index 95a0952..aa2615d 100644
--- a/tests/fixtures/config/openstack/main.yaml
+++ b/tests/fixtures/config/openstack/main.yaml
@@ -4,3 +4,6 @@
       gerrit:
         config-repos:
           - project-config
+        project-repos:
+          - openstack/nova
+          - openstack/keystone
\ No newline at end of file
diff --git a/tests/fixtures/config/requirements/email/main.yaml b/tests/fixtures/config/requirements/email/main.yaml
index a22ed5c..c388705 100644
--- a/tests/fixtures/config/requirements/email/main.yaml
+++ b/tests/fixtures/config/requirements/email/main.yaml
@@ -4,3 +4,6 @@
       gerrit:
         config-repos:
           - common-config
+        project-repos:
+          - org/project1
+          - org/project2
diff --git a/tests/fixtures/config/requirements/newer-than/main.yaml b/tests/fixtures/config/requirements/newer-than/main.yaml
index a22ed5c..c388705 100644
--- a/tests/fixtures/config/requirements/newer-than/main.yaml
+++ b/tests/fixtures/config/requirements/newer-than/main.yaml
@@ -4,3 +4,6 @@
       gerrit:
         config-repos:
           - common-config
+        project-repos:
+          - org/project1
+          - org/project2
diff --git a/tests/fixtures/config/requirements/older-than/main.yaml b/tests/fixtures/config/requirements/older-than/main.yaml
index a22ed5c..c388705 100644
--- a/tests/fixtures/config/requirements/older-than/main.yaml
+++ b/tests/fixtures/config/requirements/older-than/main.yaml
@@ -4,3 +4,6 @@
       gerrit:
         config-repos:
           - common-config
+        project-repos:
+          - org/project1
+          - org/project2
diff --git a/tests/fixtures/config/requirements/reject-username/main.yaml b/tests/fixtures/config/requirements/reject-username/main.yaml
index a22ed5c..c388705 100644
--- a/tests/fixtures/config/requirements/reject-username/main.yaml
+++ b/tests/fixtures/config/requirements/reject-username/main.yaml
@@ -4,3 +4,6 @@
       gerrit:
         config-repos:
           - common-config
+        project-repos:
+          - org/project1
+          - org/project2
diff --git a/tests/fixtures/config/requirements/reject/main.yaml b/tests/fixtures/config/requirements/reject/main.yaml
index a22ed5c..c388705 100644
--- a/tests/fixtures/config/requirements/reject/main.yaml
+++ b/tests/fixtures/config/requirements/reject/main.yaml
@@ -4,3 +4,6 @@
       gerrit:
         config-repos:
           - common-config
+        project-repos:
+          - org/project1
+          - org/project2
diff --git a/tests/fixtures/config/requirements/state/main.yaml b/tests/fixtures/config/requirements/state/main.yaml
index a22ed5c..70af14b 100644
--- a/tests/fixtures/config/requirements/state/main.yaml
+++ b/tests/fixtures/config/requirements/state/main.yaml
@@ -4,3 +4,7 @@
       gerrit:
         config-repos:
           - common-config
+        project-repos:
+          - current-project
+          - open-project
+          - status-project
diff --git a/tests/fixtures/config/requirements/username/main.yaml b/tests/fixtures/config/requirements/username/main.yaml
index a22ed5c..c388705 100644
--- a/tests/fixtures/config/requirements/username/main.yaml
+++ b/tests/fixtures/config/requirements/username/main.yaml
@@ -4,3 +4,6 @@
       gerrit:
         config-repos:
           - common-config
+        project-repos:
+          - org/project1
+          - org/project2
diff --git a/tests/fixtures/config/requirements/vote1/main.yaml b/tests/fixtures/config/requirements/vote1/main.yaml
index a22ed5c..c388705 100644
--- a/tests/fixtures/config/requirements/vote1/main.yaml
+++ b/tests/fixtures/config/requirements/vote1/main.yaml
@@ -4,3 +4,6 @@
       gerrit:
         config-repos:
           - common-config
+        project-repos:
+          - org/project1
+          - org/project2
diff --git a/tests/fixtures/config/requirements/vote2/main.yaml b/tests/fixtures/config/requirements/vote2/main.yaml
index a22ed5c..c388705 100644
--- a/tests/fixtures/config/requirements/vote2/main.yaml
+++ b/tests/fixtures/config/requirements/vote2/main.yaml
@@ -4,3 +4,6 @@
       gerrit:
         config-repos:
           - common-config
+        project-repos:
+          - org/project1
+          - org/project2
diff --git a/tests/fixtures/config/single-tenant/main.yaml b/tests/fixtures/config/single-tenant/main.yaml
index d9868fa..096e34e 100644
--- a/tests/fixtures/config/single-tenant/main.yaml
+++ b/tests/fixtures/config/single-tenant/main.yaml
@@ -6,3 +6,18 @@
           - common-config
         project-repos:
           - org/project
+          - org/project1
+          - org/project2
+          - org/project3
+          - org/project4
+          - org/project5
+          - org/project6
+          - org/one-job-project
+          - org/nonvoting-project
+          - org/templated-project
+          - org/layered-project
+          - org/node-project
+          - org/conflict-project
+          - org/noop-project
+          - org/experimental-project
+          - org/no-jobs-project
diff --git a/tests/fixtures/config/success-url/main.yaml b/tests/fixtures/config/success-url/main.yaml
index a22ed5c..841f74d 100644
--- a/tests/fixtures/config/success-url/main.yaml
+++ b/tests/fixtures/config/success-url/main.yaml
@@ -4,3 +4,5 @@
       gerrit:
         config-repos:
           - common-config
+        project-repos:
+          - org/docs
diff --git a/tests/fixtures/config/templated-project/main.yaml b/tests/fixtures/config/templated-project/main.yaml
index a22ed5c..3b297a7 100644
--- a/tests/fixtures/config/templated-project/main.yaml
+++ b/tests/fixtures/config/templated-project/main.yaml
@@ -4,3 +4,6 @@
       gerrit:
         config-repos:
           - common-config
+        project-repos:
+          - org/templated-project
+          - org/layered-project
diff --git a/tests/fixtures/config/zuul-connections-multiple-gerrits/git/common-config/zuul.yaml b/tests/fixtures/config/zuul-connections-multiple-gerrits/git/common-config/zuul.yaml
index 302dfcf..8353732 100644
--- a/tests/fixtures/config/zuul-connections-multiple-gerrits/git/common-config/zuul.yaml
+++ b/tests/fixtures/config/zuul-connections-multiple-gerrits/git/common-config/zuul.yaml
@@ -33,10 +33,13 @@
     name: project-test2
 
 - project:
-    name: org/project1
+    name: review.example.com/org/project1
     review_check:
       jobs:
         - project-test1
+
+- project:
+    name: another.example.com/org/project1
     another_check:
       jobs:
         - project-test2
diff --git a/tests/fixtures/config/zuul-connections-multiple-gerrits/main.yaml b/tests/fixtures/config/zuul-connections-multiple-gerrits/main.yaml
index 730cc7e..72e43f5 100644
--- a/tests/fixtures/config/zuul-connections-multiple-gerrits/main.yaml
+++ b/tests/fixtures/config/zuul-connections-multiple-gerrits/main.yaml
@@ -4,3 +4,8 @@
       review_gerrit:
         config-repos:
           - common-config
+        project-repos:
+          - org/project1
+      another_gerrit:
+        project-repos:
+          - org/project1
diff --git a/tests/fixtures/config/zuultrigger/parent-change-enqueued/main.yaml b/tests/fixtures/config/zuultrigger/parent-change-enqueued/main.yaml
index a22ed5c..d9868fa 100644
--- a/tests/fixtures/config/zuultrigger/parent-change-enqueued/main.yaml
+++ b/tests/fixtures/config/zuultrigger/parent-change-enqueued/main.yaml
@@ -4,3 +4,5 @@
       gerrit:
         config-repos:
           - common-config
+        project-repos:
+          - org/project
diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py
index a40054f..04b0e0a 100644
--- a/tests/unit/test_model.py
+++ b/tests/unit/test_model.py
@@ -165,6 +165,7 @@
         layout.addPipeline(pipeline)
         queue = model.ChangeQueue(pipeline)
         project = model.Project('project', self.source)
+        tenant.addProjectRepo(project)
 
         base = configloader.JobParser.fromYaml(tenant, layout, {
             '_source_context': self.context,
@@ -431,6 +432,7 @@
     def test_job_inheritance_job_tree(self):
         tenant = model.Tenant('tenant')
         layout = model.Layout()
+        tenant.addProjectRepo(self.project)
 
         pipeline = model.Pipeline('gate', layout)
         layout.addPipeline(pipeline)
@@ -511,6 +513,7 @@
         layout.addPipeline(pipeline)
         queue = model.ChangeQueue(pipeline)
         project = model.Project('project', self.source)
+        tenant.addProjectRepo(project)
 
         base = configloader.JobParser.fromYaml(tenant, layout, {
             '_source_context': self.context,
@@ -591,6 +594,7 @@
         self.layout.addJob(job)
 
         project2 = model.Project('project2', self.source)
+        self.tenant.addProjectRepo(project2)
         context2 = model.SourceContext(project2, 'master',
                                        'test', True)
 
diff --git a/tests/unit/test_scheduler.py b/tests/unit/test_scheduler.py
index 41e8463..e1d00f6 100755
--- a/tests/unit/test_scheduler.py
+++ b/tests/unit/test_scheduler.py
@@ -1498,8 +1498,8 @@
         # https://bugs.executepad.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(
-            tenant.layout.project_configs.get('org/project1'))
+        trusted, project = tenant.getProject('org/project1')
+        url = self.fake_gerrit.getGitUrl(project)
         self.merge_server.merger.addProject('org/project1', url)
         A = self.fake_gerrit.addFakeChange('org/project1', 'master', 'A')
         A.addPatchset(large=True)
@@ -2881,7 +2881,7 @@
         self.assertEqual(A.reported, 2)
 
     def test_repo_deleted(self):
-        self.updateConfigLayout('layout-repo-deleted')
+        self.updateConfigLayout('layout-repo-deleted', ['org/delete-project'])
         self.sched.reconfigure(self.config)
 
         self.init_repo("org/delete-project")
diff --git a/zuul/configloader.py b/zuul/configloader.py
index 3deb0f6..a65e4a3 100644
--- a/zuul/configloader.py
+++ b/zuul/configloader.py
@@ -46,6 +46,17 @@
     pass
 
 
+class ProjectNotFoundError(Exception):
+    def __init__(self, project):
+        message = textwrap.dedent("""\
+        The project {project} was not found.  All projects
+        referenced within a Zuul configuration must first be
+        added to the main configuration file by the Zuul
+        administrator.""")
+        message = textwrap.fill(message.format(project=project))
+        super(ProjectNotFoundError, self).__init__(message)
+
+
 def indent(s):
     return '\n'.join(['  ' + x for x in s.split('\n')])
 
@@ -54,7 +65,7 @@
 def configuration_exceptions(stanza, conf):
     try:
         yield
-    except vs.Invalid as e:
+    except Exception as e:
         conf = copy.deepcopy(conf)
         context = conf.pop('_source_context')
         start_mark = conf.pop('_start_mark')
@@ -488,7 +499,13 @@
         for conf in conf_list:
             with configuration_exceptions('project', conf):
                 ProjectParser.getSchema(layout)(conf)
-        project = model.ProjectConfig(conf_list[0]['name'])
+
+        with configuration_exceptions('project', conf_list[0]):
+            project_name = conf_list[0]['name']
+            (trusted, project) = tenant.getProject(project_name)
+            if project is None:
+                raise ProjectNotFoundError(project_name)
+            project_config = model.ProjectConfig(project.canonical_name)
 
         configs = []
         for conf in conf_list:
@@ -504,14 +521,14 @@
                             for name in conf_templates])
             configs.append(project_template)
             mode = conf.get('merge-mode')
-            if mode and project.merge_mode is None:
+            if mode and project_config.merge_mode is None:
                 # Set the merge mode to the first one that we find and
                 # ignore subsequent settings.
-                project.merge_mode = model.MERGER_MAP[mode]
-        if project.merge_mode is None:
+                project_config.merge_mode = model.MERGER_MAP[mode]
+        if project_config.merge_mode is None:
             # If merge mode was not specified in any project stanza,
             # set it to the default.
-            project.merge_mode = model.MERGER_MAP['merge-resolve']
+            project_config.merge_mode = model.MERGER_MAP['merge-resolve']
         for pipeline in layout.pipelines.values():
             project_pipeline = model.ProjectPipelineConfig()
             queue_name = None
@@ -532,9 +549,8 @@
             if queue_name:
                 project_pipeline.queue_name = queue_name
             if pipeline_defined:
-                project.pipelines[pipeline.name] = project_pipeline
-
-        return project
+                project_config.pipelines[pipeline.name] = project_pipeline
+        return project_config
 
 
 class PipelineParser(object):
@@ -787,7 +803,6 @@
                                                   unparsed_config,
                                                   scheduler,
                                                   connections)
-        tenant.layout.tenant = tenant
         return tenant
 
     @staticmethod
@@ -992,6 +1007,8 @@
             layout.addProjectConfig(ProjectParser.fromYaml(
                 tenant, layout, config_project))
 
+        layout.tenant = tenant
+
         for pipeline in layout.pipelines.values():
             pipeline.manager._postConfig(layout)
 
diff --git a/zuul/driver/timer/__init__.py b/zuul/driver/timer/__init__.py
index 7ad8756..00ddb26 100644
--- a/zuul/driver/timer/__init__.py
+++ b/zuul/driver/timer/__init__.py
@@ -76,12 +76,12 @@
 
     def _onTrigger(self, tenant, pipeline_name, timespec):
         for project_name in tenant.layout.project_configs.keys():
+            project_hostname, project_name = project_name.split('/', 1)
             event = TriggerEvent()
             event.type = 'timer'
             event.timespec = timespec
             event.forced_pipeline = pipeline_name
-            # TODOv3(jeblair): add project hostname in future change
-            event.project_hostname = ''
+            event.project_hostname = project_hostname
             event.project_name = project_name
             self.log.debug("Adding event %s" % event)
             self.sched.addEvent(event)
diff --git a/zuul/executor/client.py b/zuul/executor/client.py
index 461af0b..0439126 100644
--- a/zuul/executor/client.py
+++ b/zuul/executor/client.py
@@ -52,7 +52,7 @@
                 url=item.pipeline.source.getGitUrl(
                     item.change.project),
                 connection_name=connection_name,
-                merge_mode=item.current_build_set.getMergeMode(project),
+                merge_mode=item.current_build_set.getMergeMode(),
                 refspec=refspec,
                 branch=branch,
                 ref=item.current_build_set.ref,
diff --git a/zuul/manager/__init__.py b/zuul/manager/__init__.py
index 9507d15..9661f54 100644
--- a/zuul/manager/__init__.py
+++ b/zuul/manager/__init__.py
@@ -461,7 +461,7 @@
                     url=self.pipeline.source.getGitUrl(
                         item.change.project),
                     connection_name=connection_name,
-                    merge_mode=item.current_build_set.getMergeMode(project),
+                    merge_mode=item.current_build_set.getMergeMode(),
                     refspec=refspec,
                     branch=branch,
                     ref=item.current_build_set.ref,
diff --git a/zuul/manager/dependent.py b/zuul/manager/dependent.py
index 4c48568..abba8da 100644
--- a/zuul/manager/dependent.py
+++ b/zuul/manager/dependent.py
@@ -38,13 +38,14 @@
         self.log.debug("Building shared change queues")
         change_queues = {}
         project_configs = self.pipeline.layout.project_configs
+        tenant = self.pipeline.layout.tenant
 
         for project_config in project_configs.values():
             project_pipeline_config = project_config.pipelines.get(
                 self.pipeline.name)
             if project_pipeline_config is None:
                 continue
-            project = self.pipeline.source.getProject(project_config.name)
+            (trusted, project) = tenant.getProject(project_config.name)
             queue_name = project_pipeline_config.queue_name
             if queue_name and queue_name in change_queues:
                 change_queue = change_queues[queue_name]
diff --git a/zuul/model.py b/zuul/model.py
index 77c5990..7dfd10f 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -1239,10 +1239,14 @@
     def getTries(self, job_name):
         return self.tries.get(job_name)
 
-    def getMergeMode(self, job_name):
-        if not self.layout or job_name not in self.layout.project_configs:
-            return MERGER_MERGE_RESOLVE
-        return self.layout.project_configs[job_name].merge_mode
+    def getMergeMode(self):
+        if self.layout:
+            project = self.item.change.project
+            project_config = self.layout.project_configs.get(
+                project.canonical_name)
+            if project_config:
+                return project_config.merge_mode
+        return MERGER_MERGE_RESOLVE
 
 
 class QueueItem(object):
@@ -1843,7 +1847,7 @@
         return self.project_hostname + '/' + self.project_name
 
     def __repr__(self):
-        ret = '<TriggerEvent %s %s' % (self.type, self.project_name)
+        ret = '<TriggerEvent %s %s' % (self.type, self.canonical_project_name)
 
         if self.branch:
             ret += " %s" % self.branch
@@ -2401,7 +2405,7 @@
 
     def createJobGraph(self, item):
         project_config = self.project_configs.get(
-            item.change.project.name, None)
+            item.change.project.canonical_name, None)
         ret = JobGraph()
         # NOTE(pabelanger): It is possible for a foreign project not to have a
         # configured pipeline, if so return an empty JobGraph.
diff --git a/zuul/scheduler.py b/zuul/scheduler.py
index 0fa1763..0f937e8 100644
--- a/zuul/scheduler.py
+++ b/zuul/scheduler.py
@@ -611,7 +611,7 @@
 
     def _doEnqueueEvent(self, event):
         tenant = self.abide.tenants.get(event.tenant_name)
-        project = tenant.layout.project_configs.get(event.project_name)
+        (trusted, project) = tenant.getProject(event.project_name)
         pipeline = tenant.layout.pipelines[event.forced_pipeline]
         change = pipeline.source.getChange(event, project)
         self.log.debug("Event %s for change %s was directly assigned "