blob: 931639f5aca4efbefdaeb4c47b6fd90deab8cd86 [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)
74 s.sendall('%s\n' % cmd)
James E. Blair119acf32016-04-18 15:34:36 -070075
James E. Blairc4b20412016-05-27 16:45:26 -070076 def exit_handler(self):
Paul Belanger174a8272017-03-14 13:20:10 -040077 self.executor.stop()
78 self.executor.join()
James E. Blair9f365192016-04-18 10:34:48 -070079
David Shrewsburyeb856472017-04-13 14:23:04 -040080 def start_log_streamer(self):
81 pipe_read, pipe_write = os.pipe()
82 child_pid = os.fork()
83 if child_pid == 0:
84 os.close(pipe_write)
85 import zuul.lib.log_streamer
86
87 self.log.info("Starting log streamer")
88 streamer = zuul.lib.log_streamer.LogStreamer(
Tobias Henkelcec29662017-06-02 12:27:47 +020089 self.user, '0.0.0.0', self.finger_port, self.jobroot_dir)
David Shrewsburyeb856472017-04-13 14:23:04 -040090
91 # Keep running until the parent dies:
92 pipe_read = os.fdopen(pipe_read)
93 pipe_read.read()
94 self.log.info("Stopping log streamer")
95 streamer.stop()
96 os._exit(0)
97 else:
98 os.close(pipe_read)
99 self.log_streamer_pid = child_pid
100
101 def change_privs(self):
102 '''
103 Drop our privileges to the zuul user.
104 '''
105 if os.getuid() != 0:
106 return
David Shrewsburye7374202017-04-27 12:28:45 -0400107 pw = pwd.getpwnam(self.user)
David Shrewsburyeb856472017-04-13 14:23:04 -0400108 os.setgroups([])
109 os.setgid(pw.pw_gid)
110 os.setuid(pw.pw_uid)
111 os.umask(0o022)
112
James E. Blairc4b20412016-05-27 16:45:26 -0700113 def main(self, daemon=True):
James E. Blair9f365192016-04-18 10:34:48 -0700114 # See comment at top of file about zuul imports
James E. Blair9f365192016-04-18 10:34:48 -0700115
David Shrewsburye7374202017-04-27 12:28:45 -0400116 if self.config.has_option('executor', 'user'):
117 self.user = self.config.get('executor', 'user')
118 else:
119 self.user = 'zuul'
120
David Shrewsburyeb856472017-04-13 14:23:04 -0400121 if self.config.has_option('zuul', 'jobroot_dir'):
122 self.jobroot_dir = os.path.expanduser(
123 self.config.get('zuul', 'jobroot_dir'))
124 else:
David Shrewsburye7374202017-04-27 12:28:45 -0400125 self.jobroot_dir = tempfile.gettempdir()
James E. Blair9f365192016-04-18 10:34:48 -0700126
David Shrewsburyeb856472017-04-13 14:23:04 -0400127 self.setup_logging('executor', 'log_config')
Paul Belanger174a8272017-03-14 13:20:10 -0400128 self.log = logging.getLogger("zuul.Executor")
James E. Blair119acf32016-04-18 15:34:36 -0700129
Tobias Henkelcec29662017-06-02 12:27:47 +0200130 if self.config.has_option('executor', 'finger_port'):
131 self.finger_port = int(self.config.get('executor', 'finger_port'))
132 else:
133 self.finger_port = DEFAULT_FINGER_PORT
134
David Shrewsburyeb856472017-04-13 14:23:04 -0400135 self.start_log_streamer()
136 self.change_privs()
David Shrewsburyd6784292017-04-13 12:08:29 -0400137
Paul Belanger9cd12862017-03-15 13:03:59 -0400138 ExecutorServer = zuul.executor.server.ExecutorServer
139 self.executor = ExecutorServer(self.config, self.connections,
David Shrewsburyeb856472017-04-13 14:23:04 -0400140 jobdir_root=self.jobroot_dir,
Paul Belanger9cd12862017-03-15 13:03:59 -0400141 keep_jobdir=self.args.keep_jobdir)
Paul Belanger174a8272017-03-14 13:20:10 -0400142 self.executor.start()
James E. Blair9f365192016-04-18 10:34:48 -0700143
James E. Blair9f365192016-04-18 10:34:48 -0700144 signal.signal(signal.SIGUSR2, zuul.cmd.stack_dump_handler)
James E. Blairc4b20412016-05-27 16:45:26 -0700145 if daemon:
Paul Belanger174a8272017-03-14 13:20:10 -0400146 self.executor.join()
James E. Blairc4b20412016-05-27 16:45:26 -0700147 else:
148 while True:
149 try:
150 signal.pause()
151 except KeyboardInterrupt:
Paul Belanger174a8272017-03-14 13:20:10 -0400152 print("Ctrl + C: asking executor to exit nicely...\n")
James E. Blairc4b20412016-05-27 16:45:26 -0700153 self.exit_handler()
154 sys.exit(0)
James E. Blair9f365192016-04-18 10:34:48 -0700155
156
157def main():
Paul Belanger174a8272017-03-14 13:20:10 -0400158 server = Executor()
James E. Blair9f365192016-04-18 10:34:48 -0700159 server.parse_arguments()
James E. Blair9f365192016-04-18 10:34:48 -0700160 server.read_config()
James E. Blairc4b20412016-05-27 16:45:26 -0700161
Paul Belanger174a8272017-03-14 13:20:10 -0400162 if server.args.command in zuul.executor.server.COMMANDS:
James E. Blaira6a50042016-06-01 15:33:54 -0700163 server.send_command(server.args.command)
James E. Blairc4b20412016-05-27 16:45:26 -0700164 sys.exit(0)
165
Tristan Cacqueraybad3b122017-05-21 03:07:08 +0000166 server.configure_connections(source_only=True)
James E. Blair9f365192016-04-18 10:34:48 -0700167
Paul Belanger174a8272017-03-14 13:20:10 -0400168 if server.config.has_option('executor', 'pidfile'):
169 pid_fn = os.path.expanduser(server.config.get('executor', 'pidfile'))
James E. Blair9f365192016-04-18 10:34:48 -0700170 else:
Paul Belanger174a8272017-03-14 13:20:10 -0400171 pid_fn = '/var/run/zuul-executor/zuul-executor.pid'
James E. Blair9f365192016-04-18 10:34:48 -0700172 pid = pid_file_module.TimeoutPIDLockFile(pid_fn, 10)
173
174 if server.args.nodaemon:
James E. Blairc4b20412016-05-27 16:45:26 -0700175 server.main(False)
James E. Blair9f365192016-04-18 10:34:48 -0700176 else:
177 with daemon.DaemonContext(pidfile=pid):
James E. Blairc4b20412016-05-27 16:45:26 -0700178 server.main(True)
James E. Blair9f365192016-04-18 10:34:48 -0700179
180
181if __name__ == "__main__":
182 sys.path.insert(0, '.')
183 main()