blob: 06ef0ba38b01985180fa7994e8f97493dbab321c [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
Tristan Cacqueray91601d72017-06-15 06:00:12 +000035from zuul.lib.config import get_default
James E. Blair9f365192016-04-18 10:34:48 -070036
James E. Blaira6a50042016-06-01 15:33:54 -070037# No zuul imports that pull in paramiko here; it must not be
James E. Blair9f365192016-04-18 10:34:48 -070038# imported until after the daemonization.
39# https://github.com/paramiko/paramiko/issues/59
40# Similar situation with gear and statsd.
41
42
Paul Belanger174a8272017-03-14 13:20:10 -040043class Executor(zuul.cmd.ZuulApp):
James E. Blair9f365192016-04-18 10:34:48 -070044
45 def parse_arguments(self):
Paul Belanger174a8272017-03-14 13:20:10 -040046 parser = argparse.ArgumentParser(description='Zuul executor.')
James E. Blair9f365192016-04-18 10:34:48 -070047 parser.add_argument('-c', dest='config',
48 help='specify the config file')
49 parser.add_argument('-d', dest='nodaemon', action='store_true',
50 help='do not run as a daemon')
51 parser.add_argument('--version', dest='version', action='version',
52 version=self._get_version(),
53 help='show zuul version')
James E. Blairf87c5ce2016-05-25 08:43:37 -070054 parser.add_argument('--keep-jobdir', dest='keep_jobdir',
55 action='store_true',
56 help='keep local jobdirs after run completes')
James E. Blaira6a50042016-06-01 15:33:54 -070057 parser.add_argument('command',
Paul Belanger174a8272017-03-14 13:20:10 -040058 choices=zuul.executor.server.COMMANDS,
James E. Blairc4b20412016-05-27 16:45:26 -070059 nargs='?')
60
James E. Blair9f365192016-04-18 10:34:48 -070061 self.args = parser.parse_args()
62
James E. Blairc4b20412016-05-27 16:45:26 -070063 def send_command(self, cmd):
James E. Blair01d733e2017-06-23 20:47:51 +010064 state_dir = get_default(self.config, 'executor', 'state_dir',
Tristan Cacqueray91601d72017-06-15 06:00:12 +000065 '/var/lib/zuul', expand_user=True)
Paul Belanger174a8272017-03-14 13:20:10 -040066 path = os.path.join(state_dir, 'executor.socket')
James E. Blairc4b20412016-05-27 16:45:26 -070067 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
68 s.connect(path)
James E. Blair04e60302017-06-08 14:32:03 -070069 cmd = '%s\n' % cmd
70 s.sendall(cmd.encode('utf8'))
James E. Blair119acf32016-04-18 15:34:36 -070071
James E. Blairc4b20412016-05-27 16:45:26 -070072 def exit_handler(self):
Paul Belanger174a8272017-03-14 13:20:10 -040073 self.executor.stop()
74 self.executor.join()
James E. Blair9f365192016-04-18 10:34:48 -070075
David Shrewsburyeb856472017-04-13 14:23:04 -040076 def start_log_streamer(self):
77 pipe_read, pipe_write = os.pipe()
78 child_pid = os.fork()
79 if child_pid == 0:
80 os.close(pipe_write)
81 import zuul.lib.log_streamer
82
83 self.log.info("Starting log streamer")
84 streamer = zuul.lib.log_streamer.LogStreamer(
James E. Blair7e6e0a12017-07-25 11:04:42 -070085 self.user, '0.0.0.0', self.finger_port, self.job_dir)
David Shrewsburyeb856472017-04-13 14:23:04 -040086
87 # Keep running until the parent dies:
88 pipe_read = os.fdopen(pipe_read)
89 pipe_read.read()
90 self.log.info("Stopping log streamer")
91 streamer.stop()
92 os._exit(0)
93 else:
94 os.close(pipe_read)
95 self.log_streamer_pid = child_pid
96
97 def change_privs(self):
98 '''
99 Drop our privileges to the zuul user.
100 '''
101 if os.getuid() != 0:
102 return
David Shrewsburye7374202017-04-27 12:28:45 -0400103 pw = pwd.getpwnam(self.user)
David Shrewsburyeb856472017-04-13 14:23:04 -0400104 os.setgroups([])
105 os.setgid(pw.pw_gid)
106 os.setuid(pw.pw_uid)
107 os.umask(0o022)
108
James E. Blairc4b20412016-05-27 16:45:26 -0700109 def main(self, daemon=True):
James E. Blair9f365192016-04-18 10:34:48 -0700110 # See comment at top of file about zuul imports
James E. Blair9f365192016-04-18 10:34:48 -0700111
Tristan Cacqueray91601d72017-06-15 06:00:12 +0000112 self.user = get_default(self.config, 'executor', 'user', 'zuul')
David Shrewsburye7374202017-04-27 12:28:45 -0400113
James E. Blair7e6e0a12017-07-25 11:04:42 -0700114 if self.config.has_option('executor', 'job_dir'):
115 self.job_dir = os.path.expanduser(
116 self.config.get('executor', 'job_dir'))
117 if not os.path.isdir(self.job_dir):
118 print("Invalid job_dir: {job_dir}".format(
119 job_dir=self.job_dir))
Monty Taylor50e6b6b2017-05-31 09:00:32 -0500120 sys.exit(1)
David Shrewsburyeb856472017-04-13 14:23:04 -0400121 else:
James E. Blair7e6e0a12017-07-25 11:04:42 -0700122 self.job_dir = tempfile.gettempdir()
James E. Blair9f365192016-04-18 10:34:48 -0700123
David Shrewsburyeb856472017-04-13 14:23:04 -0400124 self.setup_logging('executor', 'log_config')
Paul Belanger174a8272017-03-14 13:20:10 -0400125 self.log = logging.getLogger("zuul.Executor")
James E. Blair119acf32016-04-18 15:34:36 -0700126
Monty Taylor0dbe1592017-06-11 10:57:27 -0500127 self.finger_port = int(
128 get_default(self.config, 'executor', 'finger_port',
129 zuul.executor.server.DEFAULT_FINGER_PORT)
130 )
Tobias Henkelcec29662017-06-02 12:27:47 +0200131
David Shrewsburyeb856472017-04-13 14:23:04 -0400132 self.start_log_streamer()
133 self.change_privs()
David Shrewsburyd6784292017-04-13 12:08:29 -0400134
Paul Belanger9cd12862017-03-15 13:03:59 -0400135 ExecutorServer = zuul.executor.server.ExecutorServer
136 self.executor = ExecutorServer(self.config, self.connections,
James E. Blair7e6e0a12017-07-25 11:04:42 -0700137 jobdir_root=self.job_dir,
Monty Taylor0dbe1592017-06-11 10:57:27 -0500138 keep_jobdir=self.args.keep_jobdir,
139 log_streaming_port=self.finger_port)
Paul Belanger174a8272017-03-14 13:20:10 -0400140 self.executor.start()
James E. Blair9f365192016-04-18 10:34:48 -0700141
James E. Blair9f365192016-04-18 10:34:48 -0700142 signal.signal(signal.SIGUSR2, zuul.cmd.stack_dump_handler)
James E. Blairc4b20412016-05-27 16:45:26 -0700143 if daemon:
Paul Belanger174a8272017-03-14 13:20:10 -0400144 self.executor.join()
James E. Blairc4b20412016-05-27 16:45:26 -0700145 else:
146 while True:
147 try:
148 signal.pause()
149 except KeyboardInterrupt:
Paul Belanger174a8272017-03-14 13:20:10 -0400150 print("Ctrl + C: asking executor to exit nicely...\n")
James E. Blairc4b20412016-05-27 16:45:26 -0700151 self.exit_handler()
152 sys.exit(0)
James E. Blair9f365192016-04-18 10:34:48 -0700153
154
155def main():
Paul Belanger174a8272017-03-14 13:20:10 -0400156 server = Executor()
James E. Blair9f365192016-04-18 10:34:48 -0700157 server.parse_arguments()
James E. Blair9f365192016-04-18 10:34:48 -0700158 server.read_config()
James E. Blairc4b20412016-05-27 16:45:26 -0700159
Paul Belanger174a8272017-03-14 13:20:10 -0400160 if server.args.command in zuul.executor.server.COMMANDS:
James E. Blaira6a50042016-06-01 15:33:54 -0700161 server.send_command(server.args.command)
James E. Blairc4b20412016-05-27 16:45:26 -0700162 sys.exit(0)
163
Tristan Cacqueraybad3b122017-05-21 03:07:08 +0000164 server.configure_connections(source_only=True)
James E. Blair9f365192016-04-18 10:34:48 -0700165
Tristan Cacqueray91601d72017-06-15 06:00:12 +0000166 pid_fn = get_default(server.config, 'executor', 'pidfile',
167 '/var/run/zuul-executor/zuul-executor.pid',
168 expand_user=True)
James E. Blair9f365192016-04-18 10:34:48 -0700169 pid = pid_file_module.TimeoutPIDLockFile(pid_fn, 10)
170
171 if server.args.nodaemon:
James E. Blairc4b20412016-05-27 16:45:26 -0700172 server.main(False)
James E. Blair9f365192016-04-18 10:34:48 -0700173 else:
174 with daemon.DaemonContext(pidfile=pid):
James E. Blairc4b20412016-05-27 16:45:26 -0700175 server.main(True)
James E. Blair9f365192016-04-18 10:34:48 -0700176
177
178if __name__ == "__main__":
179 sys.path.insert(0, '.')
180 main()