Add option to validate job names

Add an option to the syntax validator to test that job
referenced in the layout are defined in a file.  Creating the
file with the list of jobs is an exercise for user.

Change-Id: Iceb74440cb004e9ebe6fc08a4eedf7715de2d485
diff --git a/zuul/cmd/server.py b/zuul/cmd/server.py
index c04ba45..9b4cc48 100755
--- a/zuul/cmd/server.py
+++ b/zuul/cmd/server.py
@@ -49,8 +49,11 @@
                             help='specify the layout file')
         parser.add_argument('-d', dest='nodaemon', action='store_true',
                             help='do not run as a daemon')
-        parser.add_argument('-t', dest='validate', action='store_true',
-                            help='validate layout file syntax')
+        parser.add_argument('-t', dest='validate', nargs='?', const=True,
+                            metavar='JOB_LIST',
+                            help='validate layout file syntax (optionally '
+                            'providing the path to a file with a list of '
+                            'available job names)')
         parser.add_argument('--version', dest='version', action='store_true',
                             help='show zuul version')
         self.args = parser.parse_args()
@@ -94,7 +97,7 @@
         self.stop_gear_server()
         os._exit(0)
 
-    def test_config(self):
+    def test_config(self, job_list_path):
         # See comment at top of file about zuul imports
         import zuul.scheduler
         import zuul.launcher.gearman
@@ -102,7 +105,25 @@
 
         logging.basicConfig(level=logging.DEBUG)
         self.sched = zuul.scheduler.Scheduler()
-        self.sched.testConfig(self.config.get('zuul', 'layout_config'))
+        layout = self.sched.testConfig(self.config.get('zuul',
+                                                       'layout_config'))
+        if not job_list_path:
+            return False
+
+        failure = False
+        path = os.path.expanduser(job_list_path)
+        if not os.path.exists(path):
+            raise Exception("Unable to find job list: %s" % path)
+        jobs = set()
+        for line in open(path):
+            v = line.strip()
+            if v:
+                jobs.add(v)
+        for job in sorted(layout.jobs):
+            if job not in jobs:
+                print "Job %s not defined" % job
+                failure = True
+        return failure
 
     def start_gear_server(self):
         pipe_read, pipe_write = os.pipe()
@@ -177,8 +198,10 @@
         server.config.set('zuul', 'layout_config', server.args.layout)
 
     if server.args.validate:
-        server.test_config()
-        sys.exit(0)
+        path = server.args.validate
+        if path is True:
+            path = None
+        sys.exit(server.test_config(path))
 
     if server.config.has_option('zuul', 'state_dir'):
         state_dir = os.path.expanduser(server.config.get('zuul', 'state_dir'))
diff --git a/zuul/scheduler.py b/zuul/scheduler.py
index 4bcd21c..d82215a 100644
--- a/zuul/scheduler.py
+++ b/zuul/scheduler.py
@@ -87,7 +87,7 @@
         self.wake_event.set()
 
     def testConfig(self, config_path):
-        self._parseConfig(config_path)
+        return self._parseConfig(config_path)
 
     def _parseConfig(self, config_path):
         layout = model.Layout()