blob: 5328bba5c616ee01b5a73a5aa099cb7ced1712c8 [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
James E. Blaird4371592016-06-08 16:55:04 -0700105 zuul.lib.gearserver.GearServer(4730,
106 host=host,
107 statsd_host=statsd_host,
108 statsd_port=statsd_port,
109 statsd_prefix='zuul.geard')
James E. Blaira77f28c2014-02-21 08:02:00 -0800110
James E. Blair922a9f62013-05-22 14:44:58 -0700111 # Keep running until the parent dies:
112 pipe_read = os.fdopen(pipe_read)
113 pipe_read.read()
114 os._exit(0)
115 else:
116 os.close(pipe_read)
117 self.gear_server_pid = child_pid
118 self.gear_pipe_write = pipe_write
119
120 def stop_gear_server(self):
121 if self.gear_server_pid:
122 os.kill(self.gear_server_pid, signal.SIGKILL)
123
James E. Blaire9d45c32012-05-31 09:56:45 -0700124 def main(self):
James E. Blair32663402012-06-01 10:04:18 -0700125 # See comment at top of file about zuul imports
126 import zuul.scheduler
Paul Belanger174a8272017-03-14 13:20:10 -0400127 import zuul.executor.client
James E. Blair4076e2b2014-01-28 12:42:20 -0800128 import zuul.merger.client
James E. Blair8d692392016-04-08 17:47:58 -0700129 import zuul.nodepool
James E. Blair1f4c2bb2013-04-26 08:40:46 -0700130 import zuul.webapp
James E. Blairad28e912013-11-27 10:43:22 -0800131 import zuul.rpclistener
Paul Belangerbbb48752017-02-21 10:56:40 -0500132 import zuul.zk
James E. Blair32663402012-06-01 10:04:18 -0700133
Antoine Musso29eab012014-10-28 21:35:22 +0100134 signal.signal(signal.SIGUSR2, zuul.cmd.stack_dump_handler)
James E. Blair922a9f62013-05-22 14:44:58 -0700135 if (self.config.has_option('gearman_server', 'start') and
136 self.config.getboolean('gearman_server', 'start')):
137 self.start_gear_server()
138
139 self.setup_logging('zuul', 'log_config')
James E. Blair5f0f5142017-02-14 15:36:31 -0800140 self.log = logging.getLogger("zuul.Scheduler")
James E. Blair922a9f62013-05-22 14:44:58 -0700141
Joshua Hesketh352264b2015-08-11 23:42:08 +1000142 self.sched = zuul.scheduler.Scheduler(self.config)
James E. Blairee743612012-05-29 14:49:32 -0700143
Paul Belanger9cd12862017-03-15 13:03:59 -0400144 gearman = zuul.executor.client.ExecutorClient(self.config, self.sched)
James E. Blair4076e2b2014-01-28 12:42:20 -0800145 merger = zuul.merger.client.MergeClient(self.config, self.sched)
James E. Blair8d692392016-04-08 17:47:58 -0700146 nodepool = zuul.nodepool.Nodepool(self.sched)
Joshua Hesketh850ccb62014-11-27 11:31:02 +1100147
Paul Belangerbbb48752017-02-21 10:56:40 -0500148 zookeeper = zuul.zk.ZooKeeper()
149 if self.config.has_option('zuul', 'zookeeper_hosts'):
150 zookeeper_hosts = self.config.get('zuul', 'zookeeper_hosts')
151 else:
152 zookeeper_hosts = '127.0.0.1:2181'
153
154 zookeeper.connect(zookeeper_hosts)
155
Clark Boylane0b4bdb2014-06-03 17:01:25 -0700156 if self.config.has_option('zuul', 'status_expiry'):
157 cache_expiry = self.config.getint('zuul', 'status_expiry')
158 else:
159 cache_expiry = 1
Paul Belanger88ef0ea2015-12-23 11:57:02 -0500160
161 if self.config.has_option('webapp', 'listen_address'):
162 listen_address = self.config.get('webapp', 'listen_address')
163 else:
164 listen_address = '0.0.0.0'
165
166 if self.config.has_option('webapp', 'port'):
167 port = self.config.getint('webapp', 'port')
168 else:
169 port = 8001
170
171 webapp = zuul.webapp.WebApp(
172 self.sched, port=port, cache_expiry=cache_expiry,
173 listen_address=listen_address)
James E. Blairad28e912013-11-27 10:43:22 -0800174 rpc = zuul.rpclistener.RPCListener(self.config, self.sched)
James E. Blair1e8dd892012-05-30 09:15:05 -0700175
James E. Blairfef78942016-03-11 16:28:56 -0800176 self.configure_connections()
Paul Belanger174a8272017-03-14 13:20:10 -0400177 self.sched.setExecutor(gearman)
James E. Blair4076e2b2014-01-28 12:42:20 -0800178 self.sched.setMerger(merger)
James E. Blair8d692392016-04-08 17:47:58 -0700179 self.sched.setNodepool(nodepool)
Paul Belangerbbb48752017-02-21 10:56:40 -0500180 self.sched.setZooKeeper(zookeeper)
James E. Blairee743612012-05-29 14:49:32 -0700181
Antoine Musso8f30d572014-01-15 21:57:09 +0100182 self.log.info('Starting scheduler')
James E. Blair99f93102017-02-20 17:27:40 -0500183 try:
184 self.sched.start()
Jan Hruban7083edd2015-08-21 14:00:54 +0200185 self.sched.registerConnections(self.connections, webapp)
James E. Blair99f93102017-02-20 17:27:40 -0500186 self.sched.reconfigure(self.config)
187 self.sched.resume()
188 except Exception:
189 self.log.exception("Error starting Zuul:")
190 # TODO(jeblair): If we had all threads marked as daemon,
191 # we might be able to have a nicer way of exiting here.
192 sys.exit(1)
Antoine Musso8f30d572014-01-15 21:57:09 +0100193 self.log.info('Starting Webapp')
James E. Blair1f4c2bb2013-04-26 08:40:46 -0700194 webapp.start()
Antoine Musso8f30d572014-01-15 21:57:09 +0100195 self.log.info('Starting RPC')
James E. Blairad28e912013-11-27 10:43:22 -0800196 rpc.start()
James E. Blair1f4c2bb2013-04-26 08:40:46 -0700197
James E. Blaire9d45c32012-05-31 09:56:45 -0700198 signal.signal(signal.SIGHUP, self.reconfigure_handler)
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700199 signal.signal(signal.SIGUSR1, self.exit_handler)
James E. Blair922a9f62013-05-22 14:44:58 -0700200 signal.signal(signal.SIGTERM, self.term_handler)
James E. Blaire9d45c32012-05-31 09:56:45 -0700201 while True:
Antoine Musso6ad5d5b2012-09-24 13:25:28 +0200202 try:
203 signal.pause()
204 except KeyboardInterrupt:
Morgan Fainberg4c6a7742016-05-27 08:42:17 -0700205 print("Ctrl + C: asking scheduler to exit nicely...\n")
Monty Taylor7df9e7f2012-11-25 12:12:28 -0800206 self.exit_handler(signal.SIGINT, None)
James E. Blairee743612012-05-29 14:49:32 -0700207
Monty Taylor7df9e7f2012-11-25 12:12:28 -0800208
209def main():
James E. Blair5f0f5142017-02-14 15:36:31 -0800210 scheduler = Scheduler()
211 scheduler.parse_arguments()
Antoine Musso26b1fb82013-03-21 16:31:40 +0100212
James E. Blair5f0f5142017-02-14 15:36:31 -0800213 scheduler.read_config()
James E. Blaird3bbe002012-05-31 13:06:31 -0700214
James E. Blair5f0f5142017-02-14 15:36:31 -0800215 if scheduler.args.validate:
Clint Byrum240d8602017-02-20 14:10:39 -0500216 sys.exit(scheduler.test_config())
James E. Blair47958382013-01-10 17:26:02 -0800217
James E. Blair5f0f5142017-02-14 15:36:31 -0800218 if scheduler.config.has_option('zuul', 'pidfile'):
219 pid_fn = os.path.expanduser(scheduler.config.get('zuul', 'pidfile'))
James E. Blaird3bbe002012-05-31 13:06:31 -0700220 else:
Clark Boylanb80fae02017-02-20 16:26:43 -0500221 pid_fn = '/var/run/zuul-scheduler/zuul-scheduler.pid'
Antoine Musso80925f52012-09-22 21:37:45 +0200222 pid = pid_file_module.TimeoutPIDLockFile(pid_fn, 10)
James E. Blaird3bbe002012-05-31 13:06:31 -0700223
James E. Blair5f0f5142017-02-14 15:36:31 -0800224 if scheduler.args.nodaemon:
225 scheduler.main()
James E. Blaird3bbe002012-05-31 13:06:31 -0700226 else:
227 with daemon.DaemonContext(pidfile=pid):
James E. Blair5f0f5142017-02-14 15:36:31 -0800228 scheduler.main()
James E. Blair1f4c2bb2013-04-26 08:40:46 -0700229
230
231if __name__ == "__main__":
232 sys.path.insert(0, '.')
233 main()