blob: d16eb17dd15a998d8cad64e400493863c8efcaa3 [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
Clint Byrum44430ec2017-05-01 12:49:32 -070043 def parse_arguments(self, args=None):
James E. Blaire9d45c32012-05-31 09:56:45 -070044 parser = argparse.ArgumentParser(description='Project gating system.')
45 parser.add_argument('-c', dest='config',
46 help='specify the config file')
James E. Blaird3bbe002012-05-31 13:06:31 -070047 parser.add_argument('-d', dest='nodaemon', action='store_true',
48 help='do not run as a daemon')
Clint Byrum240d8602017-02-20 14:10:39 -050049 parser.add_argument('-t', dest='validate', action='store_true',
50 help='validate config file syntax (Does not'
51 'validate config repo validity)')
Antoine Mussoaabb6862014-01-14 17:32:09 +010052 parser.add_argument('--version', dest='version', action='version',
53 version=self._get_version(),
Antoine Musso26b1fb82013-03-21 16:31:40 +010054 help='show zuul version')
Clint Byrum44430ec2017-05-01 12:49:32 -070055 self.args = parser.parse_args(args)
James E. Blairee743612012-05-29 14:49:32 -070056
James E. Blaire9d45c32012-05-31 09:56:45 -070057 def reconfigure_handler(self, signum, frame):
58 signal.signal(signal.SIGHUP, signal.SIG_IGN)
Joshua Hesketh352264b2015-08-11 23:42:08 +100059 self.log.debug("Reconfiguration triggered")
James E. Blaire9d45c32012-05-31 09:56:45 -070060 self.read_config()
James E. Blairf9eeb942013-06-18 08:36:07 -070061 self.setup_logging('zuul', 'log_config')
Evgeny Antyshev0051cd42015-07-03 11:39:10 +000062 try:
63 self.sched.reconfigure(self.config)
64 except Exception:
65 self.log.exception("Reconfiguration failed:")
James E. Blaire9d45c32012-05-31 09:56:45 -070066 signal.signal(signal.SIGHUP, self.reconfigure_handler)
James E. Blair1e8dd892012-05-30 09:15:05 -070067
James E. Blair5d5bc2b2012-07-06 10:24:01 -070068 def exit_handler(self, signum, frame):
69 signal.signal(signal.SIGUSR1, signal.SIG_IGN)
70 self.sched.exit()
James E. Blair67679052013-08-26 17:05:00 -070071 self.sched.join()
72 self.stop_gear_server()
James E. Blair5d5bc2b2012-07-06 10:24:01 -070073
James E. Blair922a9f62013-05-22 14:44:58 -070074 def term_handler(self, signum, frame):
75 self.stop_gear_server()
76 os._exit(0)
77
Clint Byrum240d8602017-02-20 14:10:39 -050078 def test_config(self):
James E. Blair47958382013-01-10 17:26:02 -080079 # See comment at top of file about zuul imports
80 import zuul.scheduler
Paul Belanger174a8272017-03-14 13:20:10 -040081 import zuul.executor.client
James E. Blair47958382013-01-10 17:26:02 -080082
83 logging.basicConfig(level=logging.DEBUG)
Clint Byrum240d8602017-02-20 14:10:39 -050084 try:
85 self.sched = zuul.scheduler.Scheduler(self.config,
86 testonly=True)
87 except Exception as e:
88 self.log.error("%s" % e)
89 return -1
90 return 0
James E. Blair47958382013-01-10 17:26:02 -080091
James E. Blair922a9f62013-05-22 14:44:58 -070092 def start_gear_server(self):
93 pipe_read, pipe_write = os.pipe()
94 child_pid = os.fork()
95 if child_pid == 0:
96 os.close(pipe_write)
97 self.setup_logging('gearman_server', 'log_config')
James E. Blaird4371592016-06-08 16:55:04 -070098 import zuul.lib.gearserver
James E. Blaira77f28c2014-02-21 08:02:00 -080099 statsd_host = os.environ.get('STATSD_HOST')
100 statsd_port = int(os.environ.get('STATSD_PORT', 8125))
James E. Blair0ac452e2015-07-22 09:05:16 -0700101 if self.config.has_option('gearman_server', 'listen_address'):
102 host = self.config.get('gearman_server', 'listen_address')
103 else:
104 host = None
Paul Belanger0a21f0a2017-06-13 13:14:42 -0400105 if self.config.has_option('gearman_server', 'ssl_key'):
106 ssl_key = self.config.get('gearman_server', 'ssl_key')
107 else:
108 ssl_key = None
109 if self.config.has_option('gearman_server', 'ssl_cert'):
110 ssl_cert = self.config.get('gearman_server', 'ssl_cert')
111 else:
112 ssl_cert = None
113 if self.config.has_option('gearman_server', 'ssl_ca'):
114 ssl_ca = self.config.get('gearman_server', 'ssl_ca')
115 else:
116 ssl_ca = None
James E. Blaird4371592016-06-08 16:55:04 -0700117 zuul.lib.gearserver.GearServer(4730,
Paul Belanger0a21f0a2017-06-13 13:14:42 -0400118 ssl_key=ssl_key,
119 ssl_cert=ssl_cert,
120 ssl_ca=ssl_ca,
James E. Blaird4371592016-06-08 16:55:04 -0700121 host=host,
122 statsd_host=statsd_host,
123 statsd_port=statsd_port,
124 statsd_prefix='zuul.geard')
James E. Blaira77f28c2014-02-21 08:02:00 -0800125
James E. Blair922a9f62013-05-22 14:44:58 -0700126 # Keep running until the parent dies:
127 pipe_read = os.fdopen(pipe_read)
128 pipe_read.read()
129 os._exit(0)
130 else:
131 os.close(pipe_read)
132 self.gear_server_pid = child_pid
133 self.gear_pipe_write = pipe_write
134
135 def stop_gear_server(self):
136 if self.gear_server_pid:
137 os.kill(self.gear_server_pid, signal.SIGKILL)
138
James E. Blaire9d45c32012-05-31 09:56:45 -0700139 def main(self):
James E. Blair32663402012-06-01 10:04:18 -0700140 # See comment at top of file about zuul imports
141 import zuul.scheduler
Paul Belanger174a8272017-03-14 13:20:10 -0400142 import zuul.executor.client
James E. Blair4076e2b2014-01-28 12:42:20 -0800143 import zuul.merger.client
James E. Blair8d692392016-04-08 17:47:58 -0700144 import zuul.nodepool
James E. Blair1f4c2bb2013-04-26 08:40:46 -0700145 import zuul.webapp
James E. Blairad28e912013-11-27 10:43:22 -0800146 import zuul.rpclistener
Paul Belangerbbb48752017-02-21 10:56:40 -0500147 import zuul.zk
James E. Blair32663402012-06-01 10:04:18 -0700148
Antoine Musso29eab012014-10-28 21:35:22 +0100149 signal.signal(signal.SIGUSR2, zuul.cmd.stack_dump_handler)
James E. Blair922a9f62013-05-22 14:44:58 -0700150 if (self.config.has_option('gearman_server', 'start') and
151 self.config.getboolean('gearman_server', 'start')):
152 self.start_gear_server()
153
154 self.setup_logging('zuul', 'log_config')
James E. Blair5f0f5142017-02-14 15:36:31 -0800155 self.log = logging.getLogger("zuul.Scheduler")
James E. Blair922a9f62013-05-22 14:44:58 -0700156
Joshua Hesketh352264b2015-08-11 23:42:08 +1000157 self.sched = zuul.scheduler.Scheduler(self.config)
James E. Blairee743612012-05-29 14:49:32 -0700158
Paul Belanger9cd12862017-03-15 13:03:59 -0400159 gearman = zuul.executor.client.ExecutorClient(self.config, self.sched)
James E. Blair4076e2b2014-01-28 12:42:20 -0800160 merger = zuul.merger.client.MergeClient(self.config, self.sched)
James E. Blair8d692392016-04-08 17:47:58 -0700161 nodepool = zuul.nodepool.Nodepool(self.sched)
Joshua Hesketh850ccb62014-11-27 11:31:02 +1100162
Paul Belangerbbb48752017-02-21 10:56:40 -0500163 zookeeper = zuul.zk.ZooKeeper()
164 if self.config.has_option('zuul', 'zookeeper_hosts'):
165 zookeeper_hosts = self.config.get('zuul', 'zookeeper_hosts')
166 else:
167 zookeeper_hosts = '127.0.0.1:2181'
168
169 zookeeper.connect(zookeeper_hosts)
170
Clark Boylane0b4bdb2014-06-03 17:01:25 -0700171 if self.config.has_option('zuul', 'status_expiry'):
172 cache_expiry = self.config.getint('zuul', 'status_expiry')
173 else:
174 cache_expiry = 1
Paul Belanger88ef0ea2015-12-23 11:57:02 -0500175
176 if self.config.has_option('webapp', 'listen_address'):
177 listen_address = self.config.get('webapp', 'listen_address')
178 else:
179 listen_address = '0.0.0.0'
180
181 if self.config.has_option('webapp', 'port'):
182 port = self.config.getint('webapp', 'port')
183 else:
184 port = 8001
185
186 webapp = zuul.webapp.WebApp(
187 self.sched, port=port, cache_expiry=cache_expiry,
188 listen_address=listen_address)
James E. Blairad28e912013-11-27 10:43:22 -0800189 rpc = zuul.rpclistener.RPCListener(self.config, self.sched)
James E. Blair1e8dd892012-05-30 09:15:05 -0700190
James E. Blairfef78942016-03-11 16:28:56 -0800191 self.configure_connections()
Paul Belanger174a8272017-03-14 13:20:10 -0400192 self.sched.setExecutor(gearman)
James E. Blair4076e2b2014-01-28 12:42:20 -0800193 self.sched.setMerger(merger)
James E. Blair8d692392016-04-08 17:47:58 -0700194 self.sched.setNodepool(nodepool)
Paul Belangerbbb48752017-02-21 10:56:40 -0500195 self.sched.setZooKeeper(zookeeper)
James E. Blairee743612012-05-29 14:49:32 -0700196
Antoine Musso8f30d572014-01-15 21:57:09 +0100197 self.log.info('Starting scheduler')
James E. Blair99f93102017-02-20 17:27:40 -0500198 try:
199 self.sched.start()
Jan Hruban7083edd2015-08-21 14:00:54 +0200200 self.sched.registerConnections(self.connections, webapp)
James E. Blair99f93102017-02-20 17:27:40 -0500201 self.sched.reconfigure(self.config)
202 self.sched.resume()
203 except Exception:
204 self.log.exception("Error starting Zuul:")
205 # TODO(jeblair): If we had all threads marked as daemon,
206 # we might be able to have a nicer way of exiting here.
207 sys.exit(1)
Antoine Musso8f30d572014-01-15 21:57:09 +0100208 self.log.info('Starting Webapp')
James E. Blair1f4c2bb2013-04-26 08:40:46 -0700209 webapp.start()
Antoine Musso8f30d572014-01-15 21:57:09 +0100210 self.log.info('Starting RPC')
James E. Blairad28e912013-11-27 10:43:22 -0800211 rpc.start()
James E. Blair1f4c2bb2013-04-26 08:40:46 -0700212
James E. Blaire9d45c32012-05-31 09:56:45 -0700213 signal.signal(signal.SIGHUP, self.reconfigure_handler)
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700214 signal.signal(signal.SIGUSR1, self.exit_handler)
James E. Blair922a9f62013-05-22 14:44:58 -0700215 signal.signal(signal.SIGTERM, self.term_handler)
James E. Blaire9d45c32012-05-31 09:56:45 -0700216 while True:
Antoine Musso6ad5d5b2012-09-24 13:25:28 +0200217 try:
218 signal.pause()
219 except KeyboardInterrupt:
Morgan Fainberg4c6a7742016-05-27 08:42:17 -0700220 print("Ctrl + C: asking scheduler to exit nicely...\n")
Monty Taylor7df9e7f2012-11-25 12:12:28 -0800221 self.exit_handler(signal.SIGINT, None)
James E. Blairee743612012-05-29 14:49:32 -0700222
Monty Taylor7df9e7f2012-11-25 12:12:28 -0800223
224def main():
James E. Blair5f0f5142017-02-14 15:36:31 -0800225 scheduler = Scheduler()
226 scheduler.parse_arguments()
Antoine Musso26b1fb82013-03-21 16:31:40 +0100227
James E. Blair5f0f5142017-02-14 15:36:31 -0800228 scheduler.read_config()
James E. Blaird3bbe002012-05-31 13:06:31 -0700229
James E. Blair5f0f5142017-02-14 15:36:31 -0800230 if scheduler.args.validate:
Clint Byrum240d8602017-02-20 14:10:39 -0500231 sys.exit(scheduler.test_config())
James E. Blair47958382013-01-10 17:26:02 -0800232
James E. Blair5f0f5142017-02-14 15:36:31 -0800233 if scheduler.config.has_option('zuul', 'pidfile'):
234 pid_fn = os.path.expanduser(scheduler.config.get('zuul', 'pidfile'))
James E. Blaird3bbe002012-05-31 13:06:31 -0700235 else:
Clark Boylanb80fae02017-02-20 16:26:43 -0500236 pid_fn = '/var/run/zuul-scheduler/zuul-scheduler.pid'
Antoine Musso80925f52012-09-22 21:37:45 +0200237 pid = pid_file_module.TimeoutPIDLockFile(pid_fn, 10)
James E. Blaird3bbe002012-05-31 13:06:31 -0700238
James E. Blair5f0f5142017-02-14 15:36:31 -0800239 if scheduler.args.nodaemon:
240 scheduler.main()
James E. Blaird3bbe002012-05-31 13:06:31 -0700241 else:
242 with daemon.DaemonContext(pidfile=pid):
James E. Blair5f0f5142017-02-14 15:36:31 -0800243 scheduler.main()
James E. Blair1f4c2bb2013-04-26 08:40:46 -0700244
245
246if __name__ == "__main__":
247 sys.path.insert(0, '.')
248 main()