blob: ff9f2d9dd8b7b9e680d6f83c65eb45956ac8024e [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
Antoine Musso37758752014-04-05 23:01:35 +020038class Server(zuul.cmd.ZuulApp):
James E. Blaire9d45c32012-05-31 09:56:45 -070039 def __init__(self):
Antoine Musso37758752014-04-05 23:01:35 +020040 super(Server, 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
James E. Blair32663402012-06-01 10:04:18 -0700156
Antoine Musso29eab012014-10-28 21:35:22 +0100157 signal.signal(signal.SIGUSR2, zuul.cmd.stack_dump_handler)
James E. Blair922a9f62013-05-22 14:44:58 -0700158 if (self.config.has_option('gearman_server', 'start') and
159 self.config.getboolean('gearman_server', 'start')):
160 self.start_gear_server()
161
162 self.setup_logging('zuul', 'log_config')
Antoine Musso8f30d572014-01-15 21:57:09 +0100163 self.log = logging.getLogger("zuul.Server")
James E. Blair922a9f62013-05-22 14:44:58 -0700164
Joshua Hesketh352264b2015-08-11 23:42:08 +1000165 self.sched = zuul.scheduler.Scheduler(self.config)
166 # TODO(jhesketh): Move swift into a connection?
Joshua Hesketh36c3fa52014-01-22 11:40:52 +1100167 self.swift = zuul.lib.swift.Swift(self.config)
James E. Blairee743612012-05-29 14:49:32 -0700168
Joshua Hesketh0c54b2a2016-04-11 21:23:33 +1000169 gearman = zuul.launcher.client.LaunchClient(self.config, self.sched,
170 self.swift)
James E. Blair4076e2b2014-01-28 12:42:20 -0800171 merger = zuul.merger.client.MergeClient(self.config, self.sched)
James E. Blair8d692392016-04-08 17:47:58 -0700172 nodepool = zuul.nodepool.Nodepool(self.sched)
Joshua Hesketh850ccb62014-11-27 11:31:02 +1100173
Clark Boylane0b4bdb2014-06-03 17:01:25 -0700174 if self.config.has_option('zuul', 'status_expiry'):
175 cache_expiry = self.config.getint('zuul', 'status_expiry')
176 else:
177 cache_expiry = 1
Paul Belanger88ef0ea2015-12-23 11:57:02 -0500178
179 if self.config.has_option('webapp', 'listen_address'):
180 listen_address = self.config.get('webapp', 'listen_address')
181 else:
182 listen_address = '0.0.0.0'
183
184 if self.config.has_option('webapp', 'port'):
185 port = self.config.getint('webapp', 'port')
186 else:
187 port = 8001
188
189 webapp = zuul.webapp.WebApp(
190 self.sched, port=port, cache_expiry=cache_expiry,
191 listen_address=listen_address)
James E. Blairad28e912013-11-27 10:43:22 -0800192 rpc = zuul.rpclistener.RPCListener(self.config, self.sched)
James E. Blair1e8dd892012-05-30 09:15:05 -0700193
James E. Blairfef78942016-03-11 16:28:56 -0800194 self.configure_connections()
James E. Blair1f4c2bb2013-04-26 08:40:46 -0700195 self.sched.setLauncher(gearman)
James E. Blair4076e2b2014-01-28 12:42:20 -0800196 self.sched.setMerger(merger)
James E. Blair8d692392016-04-08 17:47:58 -0700197 self.sched.setNodepool(nodepool)
James E. Blairee743612012-05-29 14:49:32 -0700198
Antoine Musso8f30d572014-01-15 21:57:09 +0100199 self.log.info('Starting scheduler')
James E. Blaire9d45c32012-05-31 09:56:45 -0700200 self.sched.start()
Joshua Hesketh352264b2015-08-11 23:42:08 +1000201 self.sched.registerConnections(self.connections)
James E. Blaire9d45c32012-05-31 09:56:45 -0700202 self.sched.reconfigure(self.config)
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700203 self.sched.resume()
Antoine Musso8f30d572014-01-15 21:57:09 +0100204 self.log.info('Starting Webapp')
James E. Blair1f4c2bb2013-04-26 08:40:46 -0700205 webapp.start()
Antoine Musso8f30d572014-01-15 21:57:09 +0100206 self.log.info('Starting RPC')
James E. Blairad28e912013-11-27 10:43:22 -0800207 rpc.start()
James E. Blair1f4c2bb2013-04-26 08:40:46 -0700208
James E. Blaire9d45c32012-05-31 09:56:45 -0700209 signal.signal(signal.SIGHUP, self.reconfigure_handler)
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700210 signal.signal(signal.SIGUSR1, self.exit_handler)
James E. Blair922a9f62013-05-22 14:44:58 -0700211 signal.signal(signal.SIGTERM, self.term_handler)
James E. Blaire9d45c32012-05-31 09:56:45 -0700212 while True:
Antoine Musso6ad5d5b2012-09-24 13:25:28 +0200213 try:
214 signal.pause()
215 except KeyboardInterrupt:
Morgan Fainberg4c6a7742016-05-27 08:42:17 -0700216 print("Ctrl + C: asking scheduler to exit nicely...\n")
Monty Taylor7df9e7f2012-11-25 12:12:28 -0800217 self.exit_handler(signal.SIGINT, None)
James E. Blairee743612012-05-29 14:49:32 -0700218
Monty Taylor7df9e7f2012-11-25 12:12:28 -0800219
220def main():
James E. Blaire9d45c32012-05-31 09:56:45 -0700221 server = Server()
James E. Blaird3bbe002012-05-31 13:06:31 -0700222 server.parse_arguments()
Antoine Musso26b1fb82013-03-21 16:31:40 +0100223
James E. Blaird3bbe002012-05-31 13:06:31 -0700224 server.read_config()
James E. Blaird3bbe002012-05-31 13:06:31 -0700225
James E. Blair47958382013-01-10 17:26:02 -0800226 if server.args.layout:
227 server.config.set('zuul', 'layout_config', server.args.layout)
228
229 if server.args.validate:
James E. Blair04948c72013-07-25 23:03:17 -0700230 path = server.args.validate
231 if path is True:
232 path = None
233 sys.exit(server.test_config(path))
James E. Blair47958382013-01-10 17:26:02 -0800234
James E. Blaird3bbe002012-05-31 13:06:31 -0700235 if server.config.has_option('zuul', 'pidfile'):
236 pid_fn = os.path.expanduser(server.config.get('zuul', 'pidfile'))
237 else:
238 pid_fn = '/var/run/zuul/zuul.pid'
Antoine Musso80925f52012-09-22 21:37:45 +0200239 pid = pid_file_module.TimeoutPIDLockFile(pid_fn, 10)
James E. Blaird3bbe002012-05-31 13:06:31 -0700240
241 if server.args.nodaemon:
242 server.main()
243 else:
244 with daemon.DaemonContext(pidfile=pid):
245 server.main()
James E. Blair1f4c2bb2013-04-26 08:40:46 -0700246
247
248if __name__ == "__main__":
249 sys.path.insert(0, '.')
250 main()