blob: 109929c6918ecd1774a9fd2fe5deca5fceec981a [file] [log] [blame]
James E. Blair1f4c2bb2013-04-26 08:40:46 -07001#!/usr/bin/env python
James E. Blair1ce97ad2012-05-29 15:38:19 -07002# Copyright 2012 Hewlett-Packard Development Company, L.P.
James E. Blair47958382013-01-10 17:26:02 -08003# Copyright 2013 OpenStack Foundation
James E. Blair1ce97ad2012-05-29 15:38:19 -07004#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16
James E. Blairee743612012-05-29 14:49:32 -070017import argparse
James E. Blaird3bbe002012-05-31 13:06:31 -070018import daemon
Monty Taylora94bf172012-11-25 13:00:03 -080019import extras
Antoine Musso80925f52012-09-22 21:37:45 +020020
Monty Taylora94bf172012-11-25 13:00:03 -080021# as of python-daemon 1.6 it doesn't bundle pidlockfile anymore
22# instead it depends on lockfile-0.9.1 which uses pidfile.
James E. Blair138d3362012-11-30 15:10:43 -080023pid_file_module = extras.try_imports(['daemon.pidlockfile', 'daemon.pidfile'])
Antoine Musso80925f52012-09-22 21:37:45 +020024
Clark Boylanfba9b242013-08-20 10:11:17 -070025import logging
James E. Blairee743612012-05-29 14:49:32 -070026import os
James E. Blair47958382013-01-10 17:26:02 -080027import sys
James E. Blaire9d45c32012-05-31 09:56:45 -070028import signal
Antoine Musso37758752014-04-05 23:01:35 +020029
30import zuul.cmd
James E. Blairee743612012-05-29 14:49:32 -070031
James E. Blair32663402012-06-01 10:04:18 -070032# No zuul imports here because they pull in paramiko which must not be
33# imported until after the daemonization.
34# https://github.com/paramiko/paramiko/issues/59
James E. Blairdda083b2014-01-09 07:04:37 +080035# Similar situation with gear and statsd.
James E. Blairee743612012-05-29 14:49:32 -070036
James E. Blairee743612012-05-29 14:49:32 -070037
James E. Blair5f0f5142017-02-14 15:36:31 -080038class Scheduler(zuul.cmd.ZuulApp):
James E. Blaire9d45c32012-05-31 09:56:45 -070039 def __init__(self):
James E. Blair5f0f5142017-02-14 15:36:31 -080040 super(Scheduler, self).__init__()
James E. Blair922a9f62013-05-22 14:44:58 -070041 self.gear_server_pid = None
James E. Blair1e8dd892012-05-30 09:15:05 -070042
James E. Blaire9d45c32012-05-31 09:56:45 -070043 def parse_arguments(self):
44 parser = argparse.ArgumentParser(description='Project gating system.')
45 parser.add_argument('-c', dest='config',
46 help='specify the config file')
James E. Blair47958382013-01-10 17:26:02 -080047 parser.add_argument('-l', dest='layout',
48 help='specify the layout file')
James E. Blaird3bbe002012-05-31 13:06:31 -070049 parser.add_argument('-d', dest='nodaemon', action='store_true',
50 help='do not run as a daemon')
James E. Blair04948c72013-07-25 23:03:17 -070051 parser.add_argument('-t', dest='validate', nargs='?', const=True,
52 metavar='JOB_LIST',
53 help='validate layout file syntax (optionally '
54 'providing the path to a file with a list of '
55 'available job names)')
Antoine Mussoaabb6862014-01-14 17:32:09 +010056 parser.add_argument('--version', dest='version', action='version',
57 version=self._get_version(),
Antoine Musso26b1fb82013-03-21 16:31:40 +010058 help='show zuul version')
James E. Blaire9d45c32012-05-31 09:56:45 -070059 self.args = parser.parse_args()
James E. Blairee743612012-05-29 14:49:32 -070060
James E. Blaire9d45c32012-05-31 09:56:45 -070061 def reconfigure_handler(self, signum, frame):
62 signal.signal(signal.SIGHUP, signal.SIG_IGN)
Joshua Hesketh352264b2015-08-11 23:42:08 +100063 self.log.debug("Reconfiguration triggered")
James E. Blaire9d45c32012-05-31 09:56:45 -070064 self.read_config()
James E. Blairf9eeb942013-06-18 08:36:07 -070065 self.setup_logging('zuul', 'log_config')
Evgeny Antyshev0051cd42015-07-03 11:39:10 +000066 try:
67 self.sched.reconfigure(self.config)
68 except Exception:
69 self.log.exception("Reconfiguration failed:")
James E. Blaire9d45c32012-05-31 09:56:45 -070070 signal.signal(signal.SIGHUP, self.reconfigure_handler)
James E. Blair1e8dd892012-05-30 09:15:05 -070071
James E. Blair5d5bc2b2012-07-06 10:24:01 -070072 def exit_handler(self, signum, frame):
73 signal.signal(signal.SIGUSR1, signal.SIG_IGN)
74 self.sched.exit()
James E. Blair67679052013-08-26 17:05:00 -070075 self.sched.join()
76 self.stop_gear_server()
James E. Blair5d5bc2b2012-07-06 10:24:01 -070077
James E. Blair922a9f62013-05-22 14:44:58 -070078 def term_handler(self, signum, frame):
79 self.stop_gear_server()
80 os._exit(0)
81
James E. Blair04948c72013-07-25 23:03:17 -070082 def test_config(self, job_list_path):
James E. Blair47958382013-01-10 17:26:02 -080083 # See comment at top of file about zuul imports
84 import zuul.scheduler
James E. Blair1f4c2bb2013-04-26 08:40:46 -070085 import zuul.launcher.gearman
James E. Blair47958382013-01-10 17:26:02 -080086 import zuul.trigger.gerrit
87
88 logging.basicConfig(level=logging.DEBUG)
James E. Blaire4d229c2016-05-25 15:25:41 -070089 self.sched = zuul.scheduler.Scheduler(self.config,
90 testonly=True)
Joshua Hesketh352264b2015-08-11 23:42:08 +100091 self.configure_connections()
Joshua Hesketh9a256752016-04-04 13:38:51 +100092 self.sched.registerConnections(self.connections, load=False)
James E. Blair04948c72013-07-25 23:03:17 -070093 layout = self.sched.testConfig(self.config.get('zuul',
Joshua Hesketh352264b2015-08-11 23:42:08 +100094 'layout_config'),
95 self.connections)
James E. Blair04948c72013-07-25 23:03:17 -070096 if not job_list_path:
97 return False
98
99 failure = False
100 path = os.path.expanduser(job_list_path)
101 if not os.path.exists(path):
102 raise Exception("Unable to find job list: %s" % path)
103 jobs = set()
James E. Blairb47a3ae2014-03-21 11:21:10 -0700104 jobs.add('noop')
James E. Blair04948c72013-07-25 23:03:17 -0700105 for line in open(path):
106 v = line.strip()
107 if v:
108 jobs.add(v)
109 for job in sorted(layout.jobs):
110 if job not in jobs:
Doug Hellmann81223282016-01-25 15:41:35 -0500111 print("FAILURE: Job %s not defined" % job)
James E. Blair04948c72013-07-25 23:03:17 -0700112 failure = True
113 return failure
James E. Blair47958382013-01-10 17:26:02 -0800114
James E. Blair922a9f62013-05-22 14:44:58 -0700115 def start_gear_server(self):
116 pipe_read, pipe_write = os.pipe()
117 child_pid = os.fork()
118 if child_pid == 0:
119 os.close(pipe_write)
120 self.setup_logging('gearman_server', 'log_config')
James E. Blaird4371592016-06-08 16:55:04 -0700121 import zuul.lib.gearserver
James E. Blaira77f28c2014-02-21 08:02:00 -0800122 statsd_host = os.environ.get('STATSD_HOST')
123 statsd_port = int(os.environ.get('STATSD_PORT', 8125))
James E. Blair0ac452e2015-07-22 09:05:16 -0700124 if self.config.has_option('gearman_server', 'listen_address'):
125 host = self.config.get('gearman_server', 'listen_address')
126 else:
127 host = None
James E. Blaird4371592016-06-08 16:55:04 -0700128 zuul.lib.gearserver.GearServer(4730,
129 host=host,
130 statsd_host=statsd_host,
131 statsd_port=statsd_port,
132 statsd_prefix='zuul.geard')
James E. Blaira77f28c2014-02-21 08:02:00 -0800133
James E. Blair922a9f62013-05-22 14:44:58 -0700134 # Keep running until the parent dies:
135 pipe_read = os.fdopen(pipe_read)
136 pipe_read.read()
137 os._exit(0)
138 else:
139 os.close(pipe_read)
140 self.gear_server_pid = child_pid
141 self.gear_pipe_write = pipe_write
142
143 def stop_gear_server(self):
144 if self.gear_server_pid:
145 os.kill(self.gear_server_pid, signal.SIGKILL)
146
James E. Blaire9d45c32012-05-31 09:56:45 -0700147 def main(self):
James E. Blair32663402012-06-01 10:04:18 -0700148 # See comment at top of file about zuul imports
149 import zuul.scheduler
Joshua Hesketh0c54b2a2016-04-11 21:23:33 +1000150 import zuul.launcher.client
James E. Blair4076e2b2014-01-28 12:42:20 -0800151 import zuul.merger.client
James E. Blair8d692392016-04-08 17:47:58 -0700152 import zuul.nodepool
Joshua Hesketh36c3fa52014-01-22 11:40:52 +1100153 import zuul.lib.swift
James E. Blair1f4c2bb2013-04-26 08:40:46 -0700154 import zuul.webapp
James E. Blairad28e912013-11-27 10:43:22 -0800155 import zuul.rpclistener
Paul Belangerbbb48752017-02-21 10:56:40 -0500156 import zuul.zk
James E. Blair32663402012-06-01 10:04:18 -0700157
Antoine Musso29eab012014-10-28 21:35:22 +0100158 signal.signal(signal.SIGUSR2, zuul.cmd.stack_dump_handler)
James E. Blair922a9f62013-05-22 14:44:58 -0700159 if (self.config.has_option('gearman_server', 'start') and
160 self.config.getboolean('gearman_server', 'start')):
161 self.start_gear_server()
162
163 self.setup_logging('zuul', 'log_config')
James E. Blair5f0f5142017-02-14 15:36:31 -0800164 self.log = logging.getLogger("zuul.Scheduler")
James E. Blair922a9f62013-05-22 14:44:58 -0700165
Joshua Hesketh352264b2015-08-11 23:42:08 +1000166 self.sched = zuul.scheduler.Scheduler(self.config)
167 # TODO(jhesketh): Move swift into a connection?
Joshua Hesketh36c3fa52014-01-22 11:40:52 +1100168 self.swift = zuul.lib.swift.Swift(self.config)
James E. Blairee743612012-05-29 14:49:32 -0700169
Joshua Hesketh0c54b2a2016-04-11 21:23:33 +1000170 gearman = zuul.launcher.client.LaunchClient(self.config, self.sched,
171 self.swift)
James E. Blair4076e2b2014-01-28 12:42:20 -0800172 merger = zuul.merger.client.MergeClient(self.config, self.sched)
James E. Blair8d692392016-04-08 17:47:58 -0700173 nodepool = zuul.nodepool.Nodepool(self.sched)
Joshua Hesketh850ccb62014-11-27 11:31:02 +1100174
Paul Belangerbbb48752017-02-21 10:56:40 -0500175 zookeeper = zuul.zk.ZooKeeper()
176 if self.config.has_option('zuul', 'zookeeper_hosts'):
177 zookeeper_hosts = self.config.get('zuul', 'zookeeper_hosts')
178 else:
179 zookeeper_hosts = '127.0.0.1:2181'
180
181 zookeeper.connect(zookeeper_hosts)
182
Clark Boylane0b4bdb2014-06-03 17:01:25 -0700183 if self.config.has_option('zuul', 'status_expiry'):
184 cache_expiry = self.config.getint('zuul', 'status_expiry')
185 else:
186 cache_expiry = 1
Paul Belanger88ef0ea2015-12-23 11:57:02 -0500187
188 if self.config.has_option('webapp', 'listen_address'):
189 listen_address = self.config.get('webapp', 'listen_address')
190 else:
191 listen_address = '0.0.0.0'
192
193 if self.config.has_option('webapp', 'port'):
194 port = self.config.getint('webapp', 'port')
195 else:
196 port = 8001
197
198 webapp = zuul.webapp.WebApp(
199 self.sched, port=port, cache_expiry=cache_expiry,
200 listen_address=listen_address)
James E. Blairad28e912013-11-27 10:43:22 -0800201 rpc = zuul.rpclistener.RPCListener(self.config, self.sched)
James E. Blair1e8dd892012-05-30 09:15:05 -0700202
James E. Blairfef78942016-03-11 16:28:56 -0800203 self.configure_connections()
James E. Blair1f4c2bb2013-04-26 08:40:46 -0700204 self.sched.setLauncher(gearman)
James E. Blair4076e2b2014-01-28 12:42:20 -0800205 self.sched.setMerger(merger)
James E. Blair8d692392016-04-08 17:47:58 -0700206 self.sched.setNodepool(nodepool)
Paul Belangerbbb48752017-02-21 10:56:40 -0500207 self.sched.setZooKeeper(zookeeper)
James E. Blairee743612012-05-29 14:49:32 -0700208
Antoine Musso8f30d572014-01-15 21:57:09 +0100209 self.log.info('Starting scheduler')
James E. Blair99f93102017-02-20 17:27:40 -0500210 try:
211 self.sched.start()
212 self.sched.registerConnections(self.connections)
213 self.sched.reconfigure(self.config)
214 self.sched.resume()
215 except Exception:
216 self.log.exception("Error starting Zuul:")
217 # TODO(jeblair): If we had all threads marked as daemon,
218 # we might be able to have a nicer way of exiting here.
219 sys.exit(1)
Antoine Musso8f30d572014-01-15 21:57:09 +0100220 self.log.info('Starting Webapp')
James E. Blair1f4c2bb2013-04-26 08:40:46 -0700221 webapp.start()
Antoine Musso8f30d572014-01-15 21:57:09 +0100222 self.log.info('Starting RPC')
James E. Blairad28e912013-11-27 10:43:22 -0800223 rpc.start()
James E. Blair1f4c2bb2013-04-26 08:40:46 -0700224
James E. Blaire9d45c32012-05-31 09:56:45 -0700225 signal.signal(signal.SIGHUP, self.reconfigure_handler)
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700226 signal.signal(signal.SIGUSR1, self.exit_handler)
James E. Blair922a9f62013-05-22 14:44:58 -0700227 signal.signal(signal.SIGTERM, self.term_handler)
James E. Blaire9d45c32012-05-31 09:56:45 -0700228 while True:
Antoine Musso6ad5d5b2012-09-24 13:25:28 +0200229 try:
230 signal.pause()
231 except KeyboardInterrupt:
Morgan Fainberg4c6a7742016-05-27 08:42:17 -0700232 print("Ctrl + C: asking scheduler to exit nicely...\n")
Monty Taylor7df9e7f2012-11-25 12:12:28 -0800233 self.exit_handler(signal.SIGINT, None)
James E. Blairee743612012-05-29 14:49:32 -0700234
Monty Taylor7df9e7f2012-11-25 12:12:28 -0800235
236def main():
James E. Blair5f0f5142017-02-14 15:36:31 -0800237 scheduler = Scheduler()
238 scheduler.parse_arguments()
Antoine Musso26b1fb82013-03-21 16:31:40 +0100239
James E. Blair5f0f5142017-02-14 15:36:31 -0800240 scheduler.read_config()
James E. Blaird3bbe002012-05-31 13:06:31 -0700241
James E. Blair5f0f5142017-02-14 15:36:31 -0800242 if scheduler.args.layout:
243 scheduler.config.set('zuul', 'layout_config', scheduler.args.layout)
James E. Blair47958382013-01-10 17:26:02 -0800244
James E. Blair5f0f5142017-02-14 15:36:31 -0800245 if scheduler.args.validate:
246 path = scheduler.args.validate
James E. Blair04948c72013-07-25 23:03:17 -0700247 if path is True:
248 path = None
James E. Blair5f0f5142017-02-14 15:36:31 -0800249 sys.exit(scheduler.test_config(path))
James E. Blair47958382013-01-10 17:26:02 -0800250
James E. Blair5f0f5142017-02-14 15:36:31 -0800251 if scheduler.config.has_option('zuul', 'pidfile'):
252 pid_fn = os.path.expanduser(scheduler.config.get('zuul', 'pidfile'))
James E. Blaird3bbe002012-05-31 13:06:31 -0700253 else:
Clark Boylanb80fae02017-02-20 16:26:43 -0500254 pid_fn = '/var/run/zuul-scheduler/zuul-scheduler.pid'
Antoine Musso80925f52012-09-22 21:37:45 +0200255 pid = pid_file_module.TimeoutPIDLockFile(pid_fn, 10)
James E. Blaird3bbe002012-05-31 13:06:31 -0700256
James E. Blair5f0f5142017-02-14 15:36:31 -0800257 if scheduler.args.nodaemon:
258 scheduler.main()
James E. Blaird3bbe002012-05-31 13:06:31 -0700259 else:
260 with daemon.DaemonContext(pidfile=pid):
James E. Blair5f0f5142017-02-14 15:36:31 -0800261 scheduler.main()
James E. Blair1f4c2bb2013-04-26 08:40:46 -0700262
263
264if __name__ == "__main__":
265 sys.path.insert(0, '.')
266 main()