blob: 4fb4a59123957858421c020d58fff852795759ea [file] [log] [blame]
James E. Blair9f365192016-04-18 10:34:48 -07001#!/usr/bin/env python
2# Copyright 2012 Hewlett-Packard Development Company, L.P.
3# Copyright 2013-2014 OpenStack Foundation
4#
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
17import argparse
18import daemon
19import extras
20
21# as of python-daemon 1.6 it doesn't bundle pidlockfile anymore
22# instead it depends on lockfile-0.9.1 which uses pidfile.
23pid_file_module = extras.try_imports(['daemon.pidlockfile', 'daemon.pidfile'])
24
James E. Blair119acf32016-04-18 15:34:36 -070025import logging
James E. Blair9f365192016-04-18 10:34:48 -070026import os
David Shrewsburyeb856472017-04-13 14:23:04 -040027import pwd
James E. Blairc4b20412016-05-27 16:45:26 -070028import socket
James E. Blair9f365192016-04-18 10:34:48 -070029import sys
30import signal
David Shrewsburyeb856472017-04-13 14:23:04 -040031import tempfile
James E. Blair9f365192016-04-18 10:34:48 -070032
33import zuul.cmd
Paul Belanger174a8272017-03-14 13:20:10 -040034import zuul.executor.server
James E. Blair9f365192016-04-18 10:34:48 -070035
James E. Blaira6a50042016-06-01 15:33:54 -070036# No zuul imports that pull in paramiko here; it must not be
James E. Blair9f365192016-04-18 10:34:48 -070037# imported until after the daemonization.
38# https://github.com/paramiko/paramiko/issues/59
39# Similar situation with gear and statsd.
40
41
Tobias Henkelcec29662017-06-02 12:27:47 +020042DEFAULT_FINGER_PORT = 79
David Shrewsburyeb856472017-04-13 14:23:04 -040043
44
Paul Belanger174a8272017-03-14 13:20:10 -040045class Executor(zuul.cmd.ZuulApp):
James E. Blair9f365192016-04-18 10:34:48 -070046
47 def parse_arguments(self):
Paul Belanger174a8272017-03-14 13:20:10 -040048 parser = argparse.ArgumentParser(description='Zuul executor.')
James E. Blair9f365192016-04-18 10:34:48 -070049 parser.add_argument('-c', dest='config',
50 help='specify the config file')
51 parser.add_argument('-d', dest='nodaemon', action='store_true',
52 help='do not run as a daemon')
53 parser.add_argument('--version', dest='version', action='version',
54 version=self._get_version(),
55 help='show zuul version')
James E. Blairf87c5ce2016-05-25 08:43:37 -070056 parser.add_argument('--keep-jobdir', dest='keep_jobdir',
57 action='store_true',
58 help='keep local jobdirs after run completes')
James E. Blaira6a50042016-06-01 15:33:54 -070059 parser.add_argument('command',
Paul Belanger174a8272017-03-14 13:20:10 -040060 choices=zuul.executor.server.COMMANDS,
James E. Blairc4b20412016-05-27 16:45:26 -070061 nargs='?')
62
James E. Blair9f365192016-04-18 10:34:48 -070063 self.args = parser.parse_args()
64
James E. Blairc4b20412016-05-27 16:45:26 -070065 def send_command(self, cmd):
66 if self.config.has_option('zuul', 'state_dir'):
67 state_dir = os.path.expanduser(
68 self.config.get('zuul', 'state_dir'))
69 else:
70 state_dir = '/var/lib/zuul'
Paul Belanger174a8272017-03-14 13:20:10 -040071 path = os.path.join(state_dir, 'executor.socket')
James E. Blairc4b20412016-05-27 16:45:26 -070072 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
73 s.connect(path)
James E. Blair04e60302017-06-08 14:32:03 -070074 cmd = '%s\n' % cmd
75 s.sendall(cmd.encode('utf8'))
James E. Blair119acf32016-04-18 15:34:36 -070076
James E. Blairc4b20412016-05-27 16:45:26 -070077 def exit_handler(self):
Paul Belanger174a8272017-03-14 13:20:10 -040078 self.executor.stop()
79 self.executor.join()
James E. Blair9f365192016-04-18 10:34:48 -070080
David Shrewsburyeb856472017-04-13 14:23:04 -040081 def start_log_streamer(self):
82 pipe_read, pipe_write = os.pipe()
83 child_pid = os.fork()
84 if child_pid == 0:
85 os.close(pipe_write)
86 import zuul.lib.log_streamer
87
88 self.log.info("Starting log streamer")
89 streamer = zuul.lib.log_streamer.LogStreamer(
Tobias Henkelcec29662017-06-02 12:27:47 +020090 self.user, '0.0.0.0', self.finger_port, self.jobroot_dir)
David Shrewsburyeb856472017-04-13 14:23:04 -040091
92 # Keep running until the parent dies:
93 pipe_read = os.fdopen(pipe_read)
94 pipe_read.read()
95 self.log.info("Stopping log streamer")
96 streamer.stop()
97 os._exit(0)
98 else:
99 os.close(pipe_read)
100 self.log_streamer_pid = child_pid
101
102 def change_privs(self):
103 '''
104 Drop our privileges to the zuul user.
105 '''
106 if os.getuid() != 0:
107 return
David Shrewsburye7374202017-04-27 12:28:45 -0400108 pw = pwd.getpwnam(self.user)
David Shrewsburyeb856472017-04-13 14:23:04 -0400109 os.setgroups([])
110 os.setgid(pw.pw_gid)
111 os.setuid(pw.pw_uid)
112 os.umask(0o022)
113
James E. Blairc4b20412016-05-27 16:45:26 -0700114 def main(self, daemon=True):
James E. Blair9f365192016-04-18 10:34:48 -0700115 # See comment at top of file about zuul imports
James E. Blair9f365192016-04-18 10:34:48 -0700116
David Shrewsburye7374202017-04-27 12:28:45 -0400117 if self.config.has_option('executor', 'user'):
118 self.user = self.config.get('executor', 'user')
119 else:
120 self.user = 'zuul'
121
David Shrewsburyeb856472017-04-13 14:23:04 -0400122 if self.config.has_option('zuul', 'jobroot_dir'):
123 self.jobroot_dir = os.path.expanduser(
124 self.config.get('zuul', 'jobroot_dir'))
125 else:
David Shrewsburye7374202017-04-27 12:28:45 -0400126 self.jobroot_dir = tempfile.gettempdir()
James E. Blair9f365192016-04-18 10:34:48 -0700127
David Shrewsburyeb856472017-04-13 14:23:04 -0400128 self.setup_logging('executor', 'log_config')
Paul Belanger174a8272017-03-14 13:20:10 -0400129 self.log = logging.getLogger("zuul.Executor")
James E. Blair119acf32016-04-18 15:34:36 -0700130
Tobias Henkelcec29662017-06-02 12:27:47 +0200131 if self.config.has_option('executor', 'finger_port'):
132 self.finger_port = int(self.config.get('executor', 'finger_port'))
133 else:
134 self.finger_port = DEFAULT_FINGER_PORT
135
David Shrewsburyeb856472017-04-13 14:23:04 -0400136 self.start_log_streamer()
137 self.change_privs()
David Shrewsburyd6784292017-04-13 12:08:29 -0400138
Paul Belanger9cd12862017-03-15 13:03:59 -0400139 ExecutorServer = zuul.executor.server.ExecutorServer
140 self.executor = ExecutorServer(self.config, self.connections,
David Shrewsburyeb856472017-04-13 14:23:04 -0400141 jobdir_root=self.jobroot_dir,
Paul Belanger9cd12862017-03-15 13:03:59 -0400142 keep_jobdir=self.args.keep_jobdir)
Paul Belanger174a8272017-03-14 13:20:10 -0400143 self.executor.start()
James E. Blair9f365192016-04-18 10:34:48 -0700144
James E. Blair9f365192016-04-18 10:34:48 -0700145 signal.signal(signal.SIGUSR2, zuul.cmd.stack_dump_handler)
James E. Blairc4b20412016-05-27 16:45:26 -0700146 if daemon:
Paul Belanger174a8272017-03-14 13:20:10 -0400147 self.executor.join()
James E. Blairc4b20412016-05-27 16:45:26 -0700148 else:
149 while True:
150 try:
151 signal.pause()
152 except KeyboardInterrupt:
Paul Belanger174a8272017-03-14 13:20:10 -0400153 print("Ctrl + C: asking executor to exit nicely...\n")
James E. Blairc4b20412016-05-27 16:45:26 -0700154 self.exit_handler()
155 sys.exit(0)
James E. Blair9f365192016-04-18 10:34:48 -0700156
157
158def main():
Paul Belanger174a8272017-03-14 13:20:10 -0400159 server = Executor()
James E. Blair9f365192016-04-18 10:34:48 -0700160 server.parse_arguments()
James E. Blair9f365192016-04-18 10:34:48 -0700161 server.read_config()
James E. Blairc4b20412016-05-27 16:45:26 -0700162
Paul Belanger174a8272017-03-14 13:20:10 -0400163 if server.args.command in zuul.executor.server.COMMANDS:
James E. Blaira6a50042016-06-01 15:33:54 -0700164 server.send_command(server.args.command)
James E. Blairc4b20412016-05-27 16:45:26 -0700165 sys.exit(0)
166
Tristan Cacqueraybad3b122017-05-21 03:07:08 +0000167 server.configure_connections(source_only=True)
James E. Blair9f365192016-04-18 10:34:48 -0700168
Paul Belanger174a8272017-03-14 13:20:10 -0400169 if server.config.has_option('executor', 'pidfile'):
170 pid_fn = os.path.expanduser(server.config.get('executor', 'pidfile'))
James E. Blair9f365192016-04-18 10:34:48 -0700171 else:
Paul Belanger174a8272017-03-14 13:20:10 -0400172 pid_fn = '/var/run/zuul-executor/zuul-executor.pid'
James E. Blair9f365192016-04-18 10:34:48 -0700173 pid = pid_file_module.TimeoutPIDLockFile(pid_fn, 10)
174
175 if server.args.nodaemon:
James E. Blairc4b20412016-05-27 16:45:26 -0700176 server.main(False)
James E. Blair9f365192016-04-18 10:34:48 -0700177 else:
178 with daemon.DaemonContext(pidfile=pid):
James E. Blairc4b20412016-05-27 16:45:26 -0700179 server.main(True)
James E. Blair9f365192016-04-18 10:34:48 -0700180
181
182if __name__ == "__main__":
183 sys.path.insert(0, '.')
184 main()