root / daemons / import-export @ 7e3c1da6
History | View | Annotate | Download (21.1 kB)
1 | 2d76b580 | Michael Hanselmann | #!/usr/bin/python |
---|---|---|---|
2 | 2d76b580 | Michael Hanselmann | # |
3 | 2d76b580 | Michael Hanselmann | |
4 | 2d76b580 | Michael Hanselmann | # Copyright (C) 2010 Google Inc. |
5 | 2d76b580 | Michael Hanselmann | # |
6 | 2d76b580 | Michael Hanselmann | # This program is free software; you can redistribute it and/or modify |
7 | 2d76b580 | Michael Hanselmann | # it under the terms of the GNU General Public License as published by |
8 | 2d76b580 | Michael Hanselmann | # the Free Software Foundation; either version 2 of the License, or |
9 | 2d76b580 | Michael Hanselmann | # (at your option) any later version. |
10 | 2d76b580 | Michael Hanselmann | # |
11 | 2d76b580 | Michael Hanselmann | # This program is distributed in the hope that it will be useful, but |
12 | 2d76b580 | Michael Hanselmann | # WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | 2d76b580 | Michael Hanselmann | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | 2d76b580 | Michael Hanselmann | # General Public License for more details. |
15 | 2d76b580 | Michael Hanselmann | # |
16 | 2d76b580 | Michael Hanselmann | # You should have received a copy of the GNU General Public License |
17 | 2d76b580 | Michael Hanselmann | # along with this program; if not, write to the Free Software |
18 | 2d76b580 | Michael Hanselmann | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
19 | 2d76b580 | Michael Hanselmann | # 02110-1301, USA. |
20 | 2d76b580 | Michael Hanselmann | |
21 | 2d76b580 | Michael Hanselmann | |
22 | 2d76b580 | Michael Hanselmann | """Import/export daemon. |
23 | 2d76b580 | Michael Hanselmann | |
24 | 2d76b580 | Michael Hanselmann | """ |
25 | 2d76b580 | Michael Hanselmann | |
26 | 2d76b580 | Michael Hanselmann | # pylint: disable-msg=C0103 |
27 | 2d76b580 | Michael Hanselmann | # C0103: Invalid name import-export |
28 | 2d76b580 | Michael Hanselmann | |
29 | 2d76b580 | Michael Hanselmann | import errno |
30 | 2d76b580 | Michael Hanselmann | import logging |
31 | 2d76b580 | Michael Hanselmann | import optparse |
32 | 2d76b580 | Michael Hanselmann | import os |
33 | 2d76b580 | Michael Hanselmann | import re |
34 | 2d76b580 | Michael Hanselmann | import select |
35 | 2d76b580 | Michael Hanselmann | import signal |
36 | 2d76b580 | Michael Hanselmann | import socket |
37 | 2d76b580 | Michael Hanselmann | import subprocess |
38 | 2d76b580 | Michael Hanselmann | import sys |
39 | 2d76b580 | Michael Hanselmann | import time |
40 | 2d76b580 | Michael Hanselmann | from cStringIO import StringIO |
41 | 2d76b580 | Michael Hanselmann | |
42 | 2d76b580 | Michael Hanselmann | from ganeti import constants |
43 | 2d76b580 | Michael Hanselmann | from ganeti import cli |
44 | 2d76b580 | Michael Hanselmann | from ganeti import utils |
45 | 2d76b580 | Michael Hanselmann | from ganeti import serializer |
46 | 2d76b580 | Michael Hanselmann | from ganeti import objects |
47 | 2d76b580 | Michael Hanselmann | from ganeti import locking |
48 | 2d76b580 | Michael Hanselmann | |
49 | 2d76b580 | Michael Hanselmann | |
50 | 2d76b580 | Michael Hanselmann | #: Used to recognize point at which socat(1) starts to listen on its socket. |
51 | 2d76b580 | Michael Hanselmann | #: The local address is required for the remote peer to connect (in particular |
52 | 2d76b580 | Michael Hanselmann | #: the port number). |
53 | 2d76b580 | Michael Hanselmann | LISTENING_RE = re.compile(r"^listening on\s+" |
54 | 2d76b580 | Michael Hanselmann | r"AF=(?P<family>\d+)\s+" |
55 | 2d76b580 | Michael Hanselmann | r"(?P<address>.+):(?P<port>\d+)$", re.I) |
56 | 2d76b580 | Michael Hanselmann | |
57 | 2d76b580 | Michael Hanselmann | #: Used to recognize point at which socat(1) is sending data over the wire |
58 | 2d76b580 | Michael Hanselmann | TRANSFER_LOOP_RE = re.compile(r"^starting data transfer loop with FDs\s+.*$", |
59 | 2d76b580 | Michael Hanselmann | re.I) |
60 | 2d76b580 | Michael Hanselmann | |
61 | 2d76b580 | Michael Hanselmann | SOCAT_LOG_DEBUG = "D" |
62 | 2d76b580 | Michael Hanselmann | SOCAT_LOG_INFO = "I" |
63 | 2d76b580 | Michael Hanselmann | SOCAT_LOG_NOTICE = "N" |
64 | 2d76b580 | Michael Hanselmann | SOCAT_LOG_WARNING = "W" |
65 | 2d76b580 | Michael Hanselmann | SOCAT_LOG_ERROR = "E" |
66 | 2d76b580 | Michael Hanselmann | SOCAT_LOG_FATAL = "F" |
67 | 2d76b580 | Michael Hanselmann | |
68 | 2d76b580 | Michael Hanselmann | SOCAT_LOG_IGNORE = frozenset([ |
69 | 2d76b580 | Michael Hanselmann | SOCAT_LOG_DEBUG, |
70 | 2d76b580 | Michael Hanselmann | SOCAT_LOG_INFO, |
71 | 2d76b580 | Michael Hanselmann | SOCAT_LOG_NOTICE, |
72 | 2d76b580 | Michael Hanselmann | ]) |
73 | 2d76b580 | Michael Hanselmann | |
74 | 2d76b580 | Michael Hanselmann | #: Socat buffer size: at most this many bytes are transferred per step |
75 | 2d76b580 | Michael Hanselmann | SOCAT_BUFSIZE = 1024 * 1024 |
76 | 2d76b580 | Michael Hanselmann | |
77 | 2d76b580 | Michael Hanselmann | #: How many lines to keep in the status file |
78 | 2d76b580 | Michael Hanselmann | MAX_RECENT_OUTPUT_LINES = 20 |
79 | 2d76b580 | Michael Hanselmann | |
80 | 2d76b580 | Michael Hanselmann | #: Don't update status file more than once every 5 seconds (unless forced) |
81 | 2d76b580 | Michael Hanselmann | MIN_UPDATE_INTERVAL = 5.0 |
82 | 2d76b580 | Michael Hanselmann | |
83 | 2d76b580 | Michael Hanselmann | #: Give child process up to 5 seconds to exit after sending a signal |
84 | 2d76b580 | Michael Hanselmann | CHILD_LINGER_TIMEOUT = 5.0 |
85 | 2d76b580 | Michael Hanselmann | |
86 | 043f2292 | Michael Hanselmann | #: How long to wait for a connection to be established |
87 | 043f2292 | Michael Hanselmann | DEFAULT_CONNECT_TIMEOUT = 60 |
88 | 043f2292 | Michael Hanselmann | |
89 | 2d76b580 | Michael Hanselmann | # Common options for socat |
90 | 2d76b580 | Michael Hanselmann | SOCAT_TCP_OPTS = ["keepalive", "keepidle=60", "keepintvl=10", "keepcnt=5"] |
91 | 2d76b580 | Michael Hanselmann | SOCAT_OPENSSL_OPTS = ["verify=1", "cipher=HIGH", "method=TLSv1"] |
92 | 2d76b580 | Michael Hanselmann | |
93 | 2d76b580 | Michael Hanselmann | |
94 | 2d76b580 | Michael Hanselmann | # Global variable for options |
95 | 2d76b580 | Michael Hanselmann | options = None |
96 | 2d76b580 | Michael Hanselmann | |
97 | 2d76b580 | Michael Hanselmann | |
98 | 2d76b580 | Michael Hanselmann | class Error(Exception): |
99 | 2d76b580 | Michael Hanselmann | """Generic exception""" |
100 | 2d76b580 | Michael Hanselmann | |
101 | 2d76b580 | Michael Hanselmann | |
102 | 2d76b580 | Michael Hanselmann | def SetupLogging(): |
103 | 2d76b580 | Michael Hanselmann | """Configures the logging module. |
104 | 2d76b580 | Michael Hanselmann | |
105 | 2d76b580 | Michael Hanselmann | """ |
106 | 2d76b580 | Michael Hanselmann | formatter = logging.Formatter("%(asctime)s: %(message)s") |
107 | 2d76b580 | Michael Hanselmann | |
108 | 2d76b580 | Michael Hanselmann | stderr_handler = logging.StreamHandler() |
109 | 2d76b580 | Michael Hanselmann | stderr_handler.setFormatter(formatter) |
110 | 2d76b580 | Michael Hanselmann | stderr_handler.setLevel(logging.NOTSET) |
111 | 2d76b580 | Michael Hanselmann | |
112 | 2d76b580 | Michael Hanselmann | root_logger = logging.getLogger("") |
113 | 2d76b580 | Michael Hanselmann | root_logger.addHandler(stderr_handler) |
114 | 2d76b580 | Michael Hanselmann | |
115 | 2d76b580 | Michael Hanselmann | if options.debug: |
116 | 2d76b580 | Michael Hanselmann | root_logger.setLevel(logging.NOTSET) |
117 | 2d76b580 | Michael Hanselmann | elif options.verbose: |
118 | 2d76b580 | Michael Hanselmann | root_logger.setLevel(logging.INFO) |
119 | 2d76b580 | Michael Hanselmann | else: |
120 | 2d76b580 | Michael Hanselmann | root_logger.setLevel(logging.ERROR) |
121 | 2d76b580 | Michael Hanselmann | |
122 | 2d76b580 | Michael Hanselmann | # Create special logger for child process output |
123 | 2d76b580 | Michael Hanselmann | child_logger = logging.Logger("child output") |
124 | 2d76b580 | Michael Hanselmann | child_logger.addHandler(stderr_handler) |
125 | 2d76b580 | Michael Hanselmann | child_logger.setLevel(logging.NOTSET) |
126 | 2d76b580 | Michael Hanselmann | |
127 | 2d76b580 | Michael Hanselmann | return child_logger |
128 | 2d76b580 | Michael Hanselmann | |
129 | 2d76b580 | Michael Hanselmann | |
130 | 2d76b580 | Michael Hanselmann | def _VerifyListening(family, address, port): |
131 | 2d76b580 | Michael Hanselmann | """Verify address given as listening address by socat. |
132 | 2d76b580 | Michael Hanselmann | |
133 | 2d76b580 | Michael Hanselmann | """ |
134 | 2d76b580 | Michael Hanselmann | # TODO: Implement IPv6 support |
135 | 2d76b580 | Michael Hanselmann | if family != socket.AF_INET: |
136 | 2d76b580 | Michael Hanselmann | raise Error("Address family %r not supported" % family) |
137 | 2d76b580 | Michael Hanselmann | |
138 | 2d76b580 | Michael Hanselmann | try: |
139 | 2d76b580 | Michael Hanselmann | packed_address = socket.inet_pton(family, address) |
140 | 2d76b580 | Michael Hanselmann | except socket.error: |
141 | 2d76b580 | Michael Hanselmann | raise Error("Invalid address %r for family %s" % (address, family)) |
142 | 2d76b580 | Michael Hanselmann | |
143 | 2d76b580 | Michael Hanselmann | return (socket.inet_ntop(family, packed_address), port) |
144 | 2d76b580 | Michael Hanselmann | |
145 | 2d76b580 | Michael Hanselmann | |
146 | 2d76b580 | Michael Hanselmann | class StatusFile: |
147 | 2d76b580 | Michael Hanselmann | """Status file manager. |
148 | 2d76b580 | Michael Hanselmann | |
149 | 2d76b580 | Michael Hanselmann | """ |
150 | 2d76b580 | Michael Hanselmann | def __init__(self, path): |
151 | 2d76b580 | Michael Hanselmann | """Initializes class. |
152 | 2d76b580 | Michael Hanselmann | |
153 | 2d76b580 | Michael Hanselmann | """ |
154 | 2d76b580 | Michael Hanselmann | self._path = path |
155 | 2d76b580 | Michael Hanselmann | self._data = objects.ImportExportStatus(ctime=time.time(), |
156 | 2d76b580 | Michael Hanselmann | mtime=None, |
157 | 2d76b580 | Michael Hanselmann | recent_output=[]) |
158 | 2d76b580 | Michael Hanselmann | |
159 | 2d76b580 | Michael Hanselmann | def AddRecentOutput(self, line): |
160 | 2d76b580 | Michael Hanselmann | """Adds a new line of recent output. |
161 | 2d76b580 | Michael Hanselmann | |
162 | 2d76b580 | Michael Hanselmann | """ |
163 | 2d76b580 | Michael Hanselmann | self._data.recent_output.append(line) |
164 | 2d76b580 | Michael Hanselmann | |
165 | 2d76b580 | Michael Hanselmann | # Remove old lines |
166 | 2d76b580 | Michael Hanselmann | del self._data.recent_output[:-MAX_RECENT_OUTPUT_LINES] |
167 | 2d76b580 | Michael Hanselmann | |
168 | 2d76b580 | Michael Hanselmann | def SetListenPort(self, port): |
169 | 2d76b580 | Michael Hanselmann | """Sets the port the daemon is listening on. |
170 | 2d76b580 | Michael Hanselmann | |
171 | 2d76b580 | Michael Hanselmann | @type port: int |
172 | 2d76b580 | Michael Hanselmann | @param port: TCP/UDP port |
173 | 2d76b580 | Michael Hanselmann | |
174 | 2d76b580 | Michael Hanselmann | """ |
175 | 2d76b580 | Michael Hanselmann | assert isinstance(port, (int, long)) and 0 < port < 2**16 |
176 | 2d76b580 | Michael Hanselmann | self._data.listen_port = port |
177 | 2d76b580 | Michael Hanselmann | |
178 | 2d76b580 | Michael Hanselmann | def GetListenPort(self): |
179 | 2d76b580 | Michael Hanselmann | """Returns the port the daemon is listening on. |
180 | 2d76b580 | Michael Hanselmann | |
181 | 2d76b580 | Michael Hanselmann | """ |
182 | 2d76b580 | Michael Hanselmann | return self._data.listen_port |
183 | 2d76b580 | Michael Hanselmann | |
184 | 2d76b580 | Michael Hanselmann | def SetConnected(self): |
185 | 2d76b580 | Michael Hanselmann | """Sets the connected flag. |
186 | 2d76b580 | Michael Hanselmann | |
187 | 2d76b580 | Michael Hanselmann | """ |
188 | 2d76b580 | Michael Hanselmann | self._data.connected = True |
189 | 2d76b580 | Michael Hanselmann | |
190 | 043f2292 | Michael Hanselmann | def GetConnected(self): |
191 | 043f2292 | Michael Hanselmann | """Determines whether the daemon is connected. |
192 | 043f2292 | Michael Hanselmann | |
193 | 043f2292 | Michael Hanselmann | """ |
194 | 043f2292 | Michael Hanselmann | return self._data.connected |
195 | 043f2292 | Michael Hanselmann | |
196 | 2d76b580 | Michael Hanselmann | def SetExitStatus(self, exit_status, error_message): |
197 | 2d76b580 | Michael Hanselmann | """Sets the exit status and an error message. |
198 | 2d76b580 | Michael Hanselmann | |
199 | 2d76b580 | Michael Hanselmann | """ |
200 | 2d76b580 | Michael Hanselmann | # Require error message when status isn't 0 |
201 | 2d76b580 | Michael Hanselmann | assert exit_status == 0 or error_message |
202 | 2d76b580 | Michael Hanselmann | |
203 | 2d76b580 | Michael Hanselmann | self._data.exit_status = exit_status |
204 | 2d76b580 | Michael Hanselmann | self._data.error_message = error_message |
205 | 2d76b580 | Michael Hanselmann | |
206 | 2d76b580 | Michael Hanselmann | def ExitStatusIsSuccess(self): |
207 | 2d76b580 | Michael Hanselmann | """Returns whether the exit status means "success". |
208 | 2d76b580 | Michael Hanselmann | |
209 | 2d76b580 | Michael Hanselmann | """ |
210 | 2d76b580 | Michael Hanselmann | return not bool(self._data.error_message) |
211 | 2d76b580 | Michael Hanselmann | |
212 | 2d76b580 | Michael Hanselmann | def Update(self, force): |
213 | 2d76b580 | Michael Hanselmann | """Updates the status file. |
214 | 2d76b580 | Michael Hanselmann | |
215 | 2d76b580 | Michael Hanselmann | @type force: bool |
216 | 2d76b580 | Michael Hanselmann | @param force: Write status file in any case, not only when minimum interval |
217 | 2d76b580 | Michael Hanselmann | is expired |
218 | 2d76b580 | Michael Hanselmann | |
219 | 2d76b580 | Michael Hanselmann | """ |
220 | 2d76b580 | Michael Hanselmann | if not (force or |
221 | 2d76b580 | Michael Hanselmann | self._data.mtime is None or |
222 | 2d76b580 | Michael Hanselmann | time.time() > (self._data.mtime + MIN_UPDATE_INTERVAL)): |
223 | 2d76b580 | Michael Hanselmann | return |
224 | 2d76b580 | Michael Hanselmann | |
225 | 2d76b580 | Michael Hanselmann | logging.debug("Updating status file %s", self._path) |
226 | 2d76b580 | Michael Hanselmann | |
227 | 2d76b580 | Michael Hanselmann | self._data.mtime = time.time() |
228 | 2d76b580 | Michael Hanselmann | utils.WriteFile(self._path, |
229 | 2d76b580 | Michael Hanselmann | data=serializer.DumpJson(self._data.ToDict(), indent=True), |
230 | 2d76b580 | Michael Hanselmann | mode=0400) |
231 | 2d76b580 | Michael Hanselmann | |
232 | 2d76b580 | Michael Hanselmann | |
233 | 2d76b580 | Michael Hanselmann | def _ProcessSocatOutput(status_file, level, msg): |
234 | 2d76b580 | Michael Hanselmann | """Interprets socat log output. |
235 | 2d76b580 | Michael Hanselmann | |
236 | 2d76b580 | Michael Hanselmann | """ |
237 | 2d76b580 | Michael Hanselmann | if level == SOCAT_LOG_NOTICE: |
238 | 2d76b580 | Michael Hanselmann | if status_file.GetListenPort() is None: |
239 | 2d76b580 | Michael Hanselmann | # TODO: Maybe implement timeout to not listen forever |
240 | 2d76b580 | Michael Hanselmann | m = LISTENING_RE.match(msg) |
241 | 2d76b580 | Michael Hanselmann | if m: |
242 | 2d76b580 | Michael Hanselmann | (_, port) = _VerifyListening(int(m.group("family")), m.group("address"), |
243 | 2d76b580 | Michael Hanselmann | int(m.group("port"))) |
244 | 2d76b580 | Michael Hanselmann | |
245 | 2d76b580 | Michael Hanselmann | status_file.SetListenPort(port) |
246 | 2d76b580 | Michael Hanselmann | return True |
247 | 2d76b580 | Michael Hanselmann | |
248 | 043f2292 | Michael Hanselmann | if not status_file.GetConnected(): |
249 | 043f2292 | Michael Hanselmann | m = TRANSFER_LOOP_RE.match(msg) |
250 | 043f2292 | Michael Hanselmann | if m: |
251 | 043f2292 | Michael Hanselmann | status_file.SetConnected() |
252 | 043f2292 | Michael Hanselmann | return True |
253 | 2d76b580 | Michael Hanselmann | |
254 | 2d76b580 | Michael Hanselmann | return False |
255 | 2d76b580 | Michael Hanselmann | |
256 | 2d76b580 | Michael Hanselmann | |
257 | 2d76b580 | Michael Hanselmann | def ProcessOutput(line, status_file, logger, socat): |
258 | 2d76b580 | Michael Hanselmann | """Takes care of child process output. |
259 | 2d76b580 | Michael Hanselmann | |
260 | 2d76b580 | Michael Hanselmann | @param status_file: Status file manager |
261 | 2d76b580 | Michael Hanselmann | @param logger: Child output logger |
262 | 2d76b580 | Michael Hanselmann | @type socat: bool |
263 | 2d76b580 | Michael Hanselmann | @param socat: Whether it's a socat output line |
264 | 2d76b580 | Michael Hanselmann | @type line: string |
265 | 2d76b580 | Michael Hanselmann | @param line: Child output line |
266 | 2d76b580 | Michael Hanselmann | |
267 | 2d76b580 | Michael Hanselmann | """ |
268 | 2d76b580 | Michael Hanselmann | force_update = False |
269 | 2d76b580 | Michael Hanselmann | forward_line = line |
270 | 2d76b580 | Michael Hanselmann | |
271 | 2d76b580 | Michael Hanselmann | if socat: |
272 | 2d76b580 | Michael Hanselmann | level = None |
273 | 2d76b580 | Michael Hanselmann | parts = line.split(None, 4) |
274 | 2d76b580 | Michael Hanselmann | |
275 | 2d76b580 | Michael Hanselmann | if len(parts) == 5: |
276 | 2d76b580 | Michael Hanselmann | (_, _, _, level, msg) = parts |
277 | 2d76b580 | Michael Hanselmann | |
278 | 2d76b580 | Michael Hanselmann | force_update = _ProcessSocatOutput(status_file, level, msg) |
279 | 2d76b580 | Michael Hanselmann | |
280 | 2d76b580 | Michael Hanselmann | if options.debug or (level and level not in SOCAT_LOG_IGNORE): |
281 | 2d76b580 | Michael Hanselmann | forward_line = "socat: %s %s" % (level, msg) |
282 | 2d76b580 | Michael Hanselmann | else: |
283 | 2d76b580 | Michael Hanselmann | forward_line = None |
284 | 2d76b580 | Michael Hanselmann | else: |
285 | 2d76b580 | Michael Hanselmann | forward_line = "socat: %s" % line |
286 | 2d76b580 | Michael Hanselmann | |
287 | 2d76b580 | Michael Hanselmann | if forward_line: |
288 | 2d76b580 | Michael Hanselmann | logger.info(forward_line) |
289 | 2d76b580 | Michael Hanselmann | status_file.AddRecentOutput(forward_line) |
290 | 2d76b580 | Michael Hanselmann | |
291 | 2d76b580 | Michael Hanselmann | status_file.Update(force_update) |
292 | 2d76b580 | Michael Hanselmann | |
293 | 2d76b580 | Michael Hanselmann | |
294 | 2d76b580 | Michael Hanselmann | def GetBashCommand(cmd): |
295 | 2d76b580 | Michael Hanselmann | """Prepares a command to be run in Bash. |
296 | 2d76b580 | Michael Hanselmann | |
297 | 2d76b580 | Michael Hanselmann | """ |
298 | 2d76b580 | Michael Hanselmann | return ["bash", "-o", "errexit", "-o", "pipefail", "-c", cmd] |
299 | 2d76b580 | Michael Hanselmann | |
300 | 2d76b580 | Michael Hanselmann | |
301 | 2d76b580 | Michael Hanselmann | def GetSocatCommand(mode): |
302 | 2d76b580 | Michael Hanselmann | """Returns the socat command. |
303 | 2d76b580 | Michael Hanselmann | |
304 | 2d76b580 | Michael Hanselmann | """ |
305 | 2d76b580 | Michael Hanselmann | common_addr_opts = SOCAT_TCP_OPTS + SOCAT_OPENSSL_OPTS + [ |
306 | 2d76b580 | Michael Hanselmann | "key=%s" % options.key, |
307 | 2d76b580 | Michael Hanselmann | "cert=%s" % options.cert, |
308 | 2d76b580 | Michael Hanselmann | "cafile=%s" % options.ca, |
309 | 2d76b580 | Michael Hanselmann | ] |
310 | 2d76b580 | Michael Hanselmann | |
311 | 2d76b580 | Michael Hanselmann | if options.bind is not None: |
312 | 2d76b580 | Michael Hanselmann | common_addr_opts.append("bind=%s" % options.bind) |
313 | 2d76b580 | Michael Hanselmann | |
314 | 2d76b580 | Michael Hanselmann | if mode == constants.IEM_IMPORT: |
315 | 2d76b580 | Michael Hanselmann | if options.port is None: |
316 | 2d76b580 | Michael Hanselmann | port = 0 |
317 | 2d76b580 | Michael Hanselmann | else: |
318 | 2d76b580 | Michael Hanselmann | port = options.port |
319 | 2d76b580 | Michael Hanselmann | |
320 | 2d76b580 | Michael Hanselmann | addr1 = [ |
321 | 2d76b580 | Michael Hanselmann | "OPENSSL-LISTEN:%s" % port, |
322 | 2d76b580 | Michael Hanselmann | "reuseaddr", |
323 | 043f2292 | Michael Hanselmann | |
324 | 043f2292 | Michael Hanselmann | # Retry to listen if connection wasn't established successfully, up to |
325 | 043f2292 | Michael Hanselmann | # 100 times a second. Note that this still leaves room for DoS attacks. |
326 | 043f2292 | Michael Hanselmann | "forever", |
327 | 043f2292 | Michael Hanselmann | "intervall=0.01", |
328 | 2d76b580 | Michael Hanselmann | ] + common_addr_opts |
329 | 2d76b580 | Michael Hanselmann | addr2 = ["stdout"] |
330 | 2d76b580 | Michael Hanselmann | |
331 | 2d76b580 | Michael Hanselmann | elif mode == constants.IEM_EXPORT: |
332 | 2d76b580 | Michael Hanselmann | addr1 = ["stdin"] |
333 | 2d76b580 | Michael Hanselmann | addr2 = [ |
334 | 2d76b580 | Michael Hanselmann | "OPENSSL:%s:%s" % (options.host, options.port), |
335 | 043f2292 | Michael Hanselmann | |
336 | 043f2292 | Michael Hanselmann | # How long to wait per connection attempt |
337 | 043f2292 | Michael Hanselmann | "connect-timeout=%s" % options.connect_timeout, |
338 | 043f2292 | Michael Hanselmann | |
339 | 043f2292 | Michael Hanselmann | # Retry a few times before giving up to connect (once per second) |
340 | 043f2292 | Michael Hanselmann | "retry=%s" % options.connect_retries, |
341 | 043f2292 | Michael Hanselmann | "intervall=1", |
342 | 2d76b580 | Michael Hanselmann | ] + common_addr_opts |
343 | 2d76b580 | Michael Hanselmann | |
344 | 2d76b580 | Michael Hanselmann | else: |
345 | 2d76b580 | Michael Hanselmann | raise Error("Invalid mode") |
346 | 2d76b580 | Michael Hanselmann | |
347 | 2d76b580 | Michael Hanselmann | for i in [addr1, addr2]: |
348 | 2d76b580 | Michael Hanselmann | for value in i: |
349 | 2d76b580 | Michael Hanselmann | if "," in value: |
350 | 2d76b580 | Michael Hanselmann | raise Error("Comma not allowed in socat option value: %r" % value) |
351 | 2d76b580 | Michael Hanselmann | |
352 | 2d76b580 | Michael Hanselmann | return [ |
353 | 2d76b580 | Michael Hanselmann | constants.SOCAT_PATH, |
354 | 2d76b580 | Michael Hanselmann | |
355 | 2d76b580 | Michael Hanselmann | # Log to stderr |
356 | 2d76b580 | Michael Hanselmann | "-ls", |
357 | 2d76b580 | Michael Hanselmann | |
358 | 2d76b580 | Michael Hanselmann | # Log level |
359 | 2d76b580 | Michael Hanselmann | "-d", "-d", |
360 | 2d76b580 | Michael Hanselmann | |
361 | 2d76b580 | Michael Hanselmann | # Buffer size |
362 | 2d76b580 | Michael Hanselmann | "-b%s" % SOCAT_BUFSIZE, |
363 | 2d76b580 | Michael Hanselmann | |
364 | 2d76b580 | Michael Hanselmann | # Unidirectional mode, the first address is only used for reading, and the |
365 | 2d76b580 | Michael Hanselmann | # second address is only used for writing |
366 | 2d76b580 | Michael Hanselmann | "-u", |
367 | 2d76b580 | Michael Hanselmann | |
368 | 2d76b580 | Michael Hanselmann | ",".join(addr1), ",".join(addr2) |
369 | 2d76b580 | Michael Hanselmann | ] |
370 | 2d76b580 | Michael Hanselmann | |
371 | 2d76b580 | Michael Hanselmann | |
372 | 2d76b580 | Michael Hanselmann | def GetTransportCommand(mode, socat_stderr_fd): |
373 | 2d76b580 | Michael Hanselmann | """Returns the command for the transport part of the daemon. |
374 | 2d76b580 | Michael Hanselmann | |
375 | 2d76b580 | Michael Hanselmann | @param mode: Daemon mode (import or export) |
376 | 2d76b580 | Michael Hanselmann | @type socat_stderr_fd: int |
377 | 2d76b580 | Michael Hanselmann | @param socat_stderr_fd: File descriptor socat should write its stderr to |
378 | 2d76b580 | Michael Hanselmann | |
379 | 2d76b580 | Michael Hanselmann | """ |
380 | 2d76b580 | Michael Hanselmann | socat_cmd = ("%s 2>&%d" % |
381 | 2d76b580 | Michael Hanselmann | (utils.ShellQuoteArgs(GetSocatCommand(mode)), |
382 | 2d76b580 | Michael Hanselmann | socat_stderr_fd)) |
383 | 2d76b580 | Michael Hanselmann | |
384 | 7e3c1da6 | Michael Hanselmann | compr = options.compress |
385 | 7e3c1da6 | Michael Hanselmann | |
386 | 7e3c1da6 | Michael Hanselmann | assert compr in constants.IEC_ALL |
387 | 2d76b580 | Michael Hanselmann | |
388 | 2d76b580 | Michael Hanselmann | if mode == constants.IEM_IMPORT: |
389 | 7e3c1da6 | Michael Hanselmann | if compr == constants.IEC_GZIP: |
390 | 7e3c1da6 | Michael Hanselmann | transport_cmd = "%s | gunzip -c" % socat_cmd |
391 | 7e3c1da6 | Michael Hanselmann | else: |
392 | 7e3c1da6 | Michael Hanselmann | transport_cmd = socat_cmd |
393 | 2d76b580 | Michael Hanselmann | elif mode == constants.IEM_EXPORT: |
394 | 7e3c1da6 | Michael Hanselmann | if compr == constants.IEC_GZIP: |
395 | 7e3c1da6 | Michael Hanselmann | transport_cmd = "gzip -c | %s" % socat_cmd |
396 | 7e3c1da6 | Michael Hanselmann | else: |
397 | 7e3c1da6 | Michael Hanselmann | transport_cmd = socat_cmd |
398 | 2d76b580 | Michael Hanselmann | else: |
399 | 2d76b580 | Michael Hanselmann | raise Error("Invalid mode") |
400 | 2d76b580 | Michael Hanselmann | |
401 | 2d76b580 | Michael Hanselmann | # TODO: Use "dd" to measure processed data (allows to give an ETA) |
402 | 2d76b580 | Michael Hanselmann | |
403 | 2d76b580 | Michael Hanselmann | # TODO: Run transport as separate user |
404 | 2d76b580 | Michael Hanselmann | # The transport uses its own shell to simplify running it as a separate user |
405 | 2d76b580 | Michael Hanselmann | # in the future. |
406 | 2d76b580 | Michael Hanselmann | return GetBashCommand(transport_cmd) |
407 | 2d76b580 | Michael Hanselmann | |
408 | 2d76b580 | Michael Hanselmann | |
409 | 2d76b580 | Michael Hanselmann | def GetCommand(mode, socat_stderr_fd): |
410 | 2d76b580 | Michael Hanselmann | """Returns the complete child process command. |
411 | 2d76b580 | Michael Hanselmann | |
412 | 2d76b580 | Michael Hanselmann | """ |
413 | 2d76b580 | Michael Hanselmann | buf = StringIO() |
414 | 2d76b580 | Michael Hanselmann | |
415 | 2d76b580 | Michael Hanselmann | if options.cmd_prefix: |
416 | 2d76b580 | Michael Hanselmann | buf.write(options.cmd_prefix) |
417 | 2d76b580 | Michael Hanselmann | buf.write(" ") |
418 | 2d76b580 | Michael Hanselmann | |
419 | 2d76b580 | Michael Hanselmann | buf.write(utils.ShellQuoteArgs(GetTransportCommand(mode, socat_stderr_fd))) |
420 | 2d76b580 | Michael Hanselmann | |
421 | 2d76b580 | Michael Hanselmann | if options.cmd_suffix: |
422 | 2d76b580 | Michael Hanselmann | buf.write(" ") |
423 | 2d76b580 | Michael Hanselmann | buf.write(options.cmd_suffix) |
424 | 2d76b580 | Michael Hanselmann | |
425 | 2d76b580 | Michael Hanselmann | return GetBashCommand(buf.getvalue()) |
426 | 2d76b580 | Michael Hanselmann | |
427 | 2d76b580 | Michael Hanselmann | |
428 | 29da446a | Michael Hanselmann | def ProcessChildIO(child, socat_stderr_read_fd, status_file, child_logger, |
429 | 043f2292 | Michael Hanselmann | signal_notify, signal_handler, mode): |
430 | 2d76b580 | Michael Hanselmann | """Handles the child processes' output. |
431 | 2d76b580 | Michael Hanselmann | |
432 | 2d76b580 | Michael Hanselmann | """ |
433 | 29da446a | Michael Hanselmann | assert not (signal_handler.signum - set([signal.SIGTERM, signal.SIGINT])), \ |
434 | 29da446a | Michael Hanselmann | "Other signals are not handled in this function" |
435 | 29da446a | Michael Hanselmann | |
436 | 29da446a | Michael Hanselmann | # Buffer size 0 is important, otherwise .read() with a specified length |
437 | 29da446a | Michael Hanselmann | # might buffer data while poll(2) won't mark its file descriptor as |
438 | 29da446a | Michael Hanselmann | # readable again. |
439 | 29da446a | Michael Hanselmann | socat_stderr_read = os.fdopen(socat_stderr_read_fd, "r", 0) |
440 | 2d76b580 | Michael Hanselmann | |
441 | 2d76b580 | Michael Hanselmann | script_stderr_lines = utils.LineSplitter(ProcessOutput, status_file, |
442 | 2d76b580 | Michael Hanselmann | child_logger, False) |
443 | 2d76b580 | Michael Hanselmann | try: |
444 | 2d76b580 | Michael Hanselmann | socat_stderr_lines = utils.LineSplitter(ProcessOutput, status_file, |
445 | 2d76b580 | Michael Hanselmann | child_logger, True) |
446 | 2d76b580 | Michael Hanselmann | try: |
447 | 2d76b580 | Michael Hanselmann | fdmap = { |
448 | 2d76b580 | Michael Hanselmann | child.stderr.fileno(): (child.stderr, script_stderr_lines), |
449 | 2d76b580 | Michael Hanselmann | socat_stderr_read.fileno(): (socat_stderr_read, socat_stderr_lines), |
450 | 2d76b580 | Michael Hanselmann | signal_notify.fileno(): (signal_notify, None), |
451 | 2d76b580 | Michael Hanselmann | } |
452 | 2d76b580 | Michael Hanselmann | |
453 | 29da446a | Michael Hanselmann | poller = select.poll() |
454 | 2d76b580 | Michael Hanselmann | for fd in fdmap: |
455 | 2d76b580 | Michael Hanselmann | utils.SetNonblockFlag(fd, True) |
456 | 2d76b580 | Michael Hanselmann | poller.register(fd, select.POLLIN) |
457 | 2d76b580 | Michael Hanselmann | |
458 | 043f2292 | Michael Hanselmann | if options.connect_timeout and mode == constants.IEM_IMPORT: |
459 | 043f2292 | Michael Hanselmann | listen_timeout = locking.RunningTimeout(options.connect_timeout, True) |
460 | 043f2292 | Michael Hanselmann | else: |
461 | 043f2292 | Michael Hanselmann | listen_timeout = None |
462 | 043f2292 | Michael Hanselmann | |
463 | 043f2292 | Michael Hanselmann | exit_timeout = None |
464 | 043f2292 | Michael Hanselmann | |
465 | 2d76b580 | Michael Hanselmann | while True: |
466 | 2d76b580 | Michael Hanselmann | # Break out of loop if only signal notify FD is left |
467 | 2d76b580 | Michael Hanselmann | if len(fdmap) == 1 and signal_notify.fileno() in fdmap: |
468 | 2d76b580 | Michael Hanselmann | break |
469 | 2d76b580 | Michael Hanselmann | |
470 | 043f2292 | Michael Hanselmann | timeout = None |
471 | 043f2292 | Michael Hanselmann | |
472 | 043f2292 | Michael Hanselmann | if listen_timeout and not exit_timeout: |
473 | 043f2292 | Michael Hanselmann | if status_file.GetConnected(): |
474 | 043f2292 | Michael Hanselmann | listen_timeout = None |
475 | 043f2292 | Michael Hanselmann | elif listen_timeout.Remaining() < 0: |
476 | 043f2292 | Michael Hanselmann | logging.info("Child process didn't establish connection in time") |
477 | 043f2292 | Michael Hanselmann | child.Kill(signal.SIGTERM) |
478 | 043f2292 | Michael Hanselmann | exit_timeout = \ |
479 | 043f2292 | Michael Hanselmann | locking.RunningTimeout(CHILD_LINGER_TIMEOUT, True) |
480 | 043f2292 | Michael Hanselmann | # Next block will calculate timeout |
481 | 043f2292 | Michael Hanselmann | else: |
482 | 043f2292 | Michael Hanselmann | # Not yet connected, check again in a second |
483 | 043f2292 | Michael Hanselmann | timeout = 1000 |
484 | 043f2292 | Michael Hanselmann | |
485 | 043f2292 | Michael Hanselmann | if exit_timeout: |
486 | 043f2292 | Michael Hanselmann | timeout = exit_timeout.Remaining() * 1000 |
487 | 2d76b580 | Michael Hanselmann | if timeout < 0: |
488 | 2d76b580 | Michael Hanselmann | logging.info("Child process didn't exit in time") |
489 | 2d76b580 | Michael Hanselmann | break |
490 | 2d76b580 | Michael Hanselmann | |
491 | 2d76b580 | Michael Hanselmann | for fd, event in utils.RetryOnSignal(poller.poll, timeout): |
492 | 2d76b580 | Michael Hanselmann | if event & (select.POLLIN | event & select.POLLPRI): |
493 | 2d76b580 | Michael Hanselmann | (from_, to) = fdmap[fd] |
494 | 2d76b580 | Michael Hanselmann | |
495 | 2d76b580 | Michael Hanselmann | # Read up to 1 KB of data |
496 | 2d76b580 | Michael Hanselmann | data = from_.read(1024) |
497 | 2d76b580 | Michael Hanselmann | if data: |
498 | 2d76b580 | Michael Hanselmann | if to: |
499 | 2d76b580 | Michael Hanselmann | to.write(data) |
500 | 2d76b580 | Michael Hanselmann | elif fd == signal_notify.fileno(): |
501 | 2d76b580 | Michael Hanselmann | # Signal handling |
502 | 2d76b580 | Michael Hanselmann | if signal_handler.called: |
503 | 2d76b580 | Michael Hanselmann | signal_handler.Clear() |
504 | 043f2292 | Michael Hanselmann | if exit_timeout: |
505 | 2d76b580 | Michael Hanselmann | logging.info("Child process still has about %0.2f seconds" |
506 | 043f2292 | Michael Hanselmann | " to exit", exit_timeout.Remaining()) |
507 | 2d76b580 | Michael Hanselmann | else: |
508 | 2d76b580 | Michael Hanselmann | logging.info("Giving child process %0.2f seconds to exit", |
509 | 2d76b580 | Michael Hanselmann | CHILD_LINGER_TIMEOUT) |
510 | 043f2292 | Michael Hanselmann | exit_timeout = \ |
511 | 2d76b580 | Michael Hanselmann | locking.RunningTimeout(CHILD_LINGER_TIMEOUT, True) |
512 | 2d76b580 | Michael Hanselmann | else: |
513 | 2d76b580 | Michael Hanselmann | poller.unregister(fd) |
514 | 2d76b580 | Michael Hanselmann | del fdmap[fd] |
515 | 2d76b580 | Michael Hanselmann | |
516 | 2d76b580 | Michael Hanselmann | elif event & (select.POLLNVAL | select.POLLHUP | |
517 | 2d76b580 | Michael Hanselmann | select.POLLERR): |
518 | 2d76b580 | Michael Hanselmann | poller.unregister(fd) |
519 | 2d76b580 | Michael Hanselmann | del fdmap[fd] |
520 | 2d76b580 | Michael Hanselmann | |
521 | 2d76b580 | Michael Hanselmann | script_stderr_lines.flush() |
522 | 2d76b580 | Michael Hanselmann | socat_stderr_lines.flush() |
523 | 2d76b580 | Michael Hanselmann | |
524 | 2d76b580 | Michael Hanselmann | # If there was a timeout calculator, we were waiting for the child to |
525 | 2d76b580 | Michael Hanselmann | # finish, e.g. due to a signal |
526 | 043f2292 | Michael Hanselmann | return not bool(exit_timeout) |
527 | 2d76b580 | Michael Hanselmann | finally: |
528 | 2d76b580 | Michael Hanselmann | socat_stderr_lines.close() |
529 | 2d76b580 | Michael Hanselmann | finally: |
530 | 2d76b580 | Michael Hanselmann | script_stderr_lines.close() |
531 | 2d76b580 | Michael Hanselmann | |
532 | 2d76b580 | Michael Hanselmann | |
533 | 2d76b580 | Michael Hanselmann | def ParseOptions(): |
534 | 2d76b580 | Michael Hanselmann | """Parses the options passed to the program. |
535 | 2d76b580 | Michael Hanselmann | |
536 | 2d76b580 | Michael Hanselmann | @return: Arguments to program |
537 | 2d76b580 | Michael Hanselmann | |
538 | 2d76b580 | Michael Hanselmann | """ |
539 | 2d76b580 | Michael Hanselmann | global options # pylint: disable-msg=W0603 |
540 | 2d76b580 | Michael Hanselmann | |
541 | 2d76b580 | Michael Hanselmann | parser = optparse.OptionParser(usage=("%%prog <status-file> {%s|%s}" % |
542 | 2d76b580 | Michael Hanselmann | (constants.IEM_IMPORT, |
543 | 2d76b580 | Michael Hanselmann | constants.IEM_EXPORT))) |
544 | 2d76b580 | Michael Hanselmann | parser.add_option(cli.DEBUG_OPT) |
545 | 2d76b580 | Michael Hanselmann | parser.add_option(cli.VERBOSE_OPT) |
546 | 2d76b580 | Michael Hanselmann | parser.add_option("--key", dest="key", action="store", type="string", |
547 | 2d76b580 | Michael Hanselmann | help="RSA key file") |
548 | 2d76b580 | Michael Hanselmann | parser.add_option("--cert", dest="cert", action="store", type="string", |
549 | 2d76b580 | Michael Hanselmann | help="X509 certificate file") |
550 | 2d76b580 | Michael Hanselmann | parser.add_option("--ca", dest="ca", action="store", type="string", |
551 | 2d76b580 | Michael Hanselmann | help="X509 CA file") |
552 | 2d76b580 | Michael Hanselmann | parser.add_option("--bind", dest="bind", action="store", type="string", |
553 | 2d76b580 | Michael Hanselmann | help="Bind address") |
554 | 2d76b580 | Michael Hanselmann | parser.add_option("--host", dest="host", action="store", type="string", |
555 | 2d76b580 | Michael Hanselmann | help="Remote hostname") |
556 | 2d76b580 | Michael Hanselmann | parser.add_option("--port", dest="port", action="store", type="int", |
557 | 2d76b580 | Michael Hanselmann | help="Remote port") |
558 | 043f2292 | Michael Hanselmann | parser.add_option("--connect-retries", dest="connect_retries", action="store", |
559 | 043f2292 | Michael Hanselmann | type="int", default=0, |
560 | 043f2292 | Michael Hanselmann | help=("How many times the connection should be retried" |
561 | 043f2292 | Michael Hanselmann | " (export only)")) |
562 | 043f2292 | Michael Hanselmann | parser.add_option("--connect-timeout", dest="connect_timeout", action="store", |
563 | 043f2292 | Michael Hanselmann | type="int", default=DEFAULT_CONNECT_TIMEOUT, |
564 | 043f2292 | Michael Hanselmann | help="Timeout for connection to be established (seconds)") |
565 | 7e3c1da6 | Michael Hanselmann | parser.add_option("--compress", dest="compress", action="store", |
566 | 7e3c1da6 | Michael Hanselmann | type="choice", help="Compression method", |
567 | 7e3c1da6 | Michael Hanselmann | metavar="[%s]" % "|".join(constants.IEC_ALL), |
568 | 7e3c1da6 | Michael Hanselmann | choices=list(constants.IEC_ALL), default=constants.IEC_GZIP) |
569 | 2d76b580 | Michael Hanselmann | parser.add_option("--cmd-prefix", dest="cmd_prefix", action="store", |
570 | 2d76b580 | Michael Hanselmann | type="string", help="Command prefix") |
571 | 2d76b580 | Michael Hanselmann | parser.add_option("--cmd-suffix", dest="cmd_suffix", action="store", |
572 | 2d76b580 | Michael Hanselmann | type="string", help="Command suffix") |
573 | 2d76b580 | Michael Hanselmann | |
574 | 2d76b580 | Michael Hanselmann | (options, args) = parser.parse_args() |
575 | 2d76b580 | Michael Hanselmann | |
576 | 2d76b580 | Michael Hanselmann | if len(args) != 2: |
577 | 2d76b580 | Michael Hanselmann | # Won't return |
578 | 2d76b580 | Michael Hanselmann | parser.error("Expected exactly two arguments") |
579 | 2d76b580 | Michael Hanselmann | |
580 | 2d76b580 | Michael Hanselmann | (status_file_path, mode) = args |
581 | 2d76b580 | Michael Hanselmann | |
582 | 2d76b580 | Michael Hanselmann | if mode not in (constants.IEM_IMPORT, |
583 | 2d76b580 | Michael Hanselmann | constants.IEM_EXPORT): |
584 | 2d76b580 | Michael Hanselmann | # Won't return |
585 | 2d76b580 | Michael Hanselmann | parser.error("Invalid mode: %s" % mode) |
586 | 2d76b580 | Michael Hanselmann | |
587 | 2d76b580 | Michael Hanselmann | return (status_file_path, mode) |
588 | 2d76b580 | Michael Hanselmann | |
589 | 2d76b580 | Michael Hanselmann | |
590 | 4ca693ca | Michael Hanselmann | class ChildProcess(subprocess.Popen): |
591 | 4ca693ca | Michael Hanselmann | def __init__(self, cmd, noclose_fds): |
592 | 4ca693ca | Michael Hanselmann | """Initializes this class. |
593 | 4ca693ca | Michael Hanselmann | |
594 | 4ca693ca | Michael Hanselmann | """ |
595 | 4ca693ca | Michael Hanselmann | self._noclose_fds = noclose_fds |
596 | 4ca693ca | Michael Hanselmann | |
597 | 4ca693ca | Michael Hanselmann | # Not using close_fds because doing so would also close the socat stderr |
598 | 4ca693ca | Michael Hanselmann | # pipe, which we still need. |
599 | 4ca693ca | Michael Hanselmann | subprocess.Popen.__init__(self, cmd, shell=False, close_fds=False, |
600 | 4ca693ca | Michael Hanselmann | stderr=subprocess.PIPE, stdout=None, stdin=None, |
601 | 4ca693ca | Michael Hanselmann | preexec_fn=self._ChildPreexec) |
602 | 4ca693ca | Michael Hanselmann | self._SetProcessGroup() |
603 | 4ca693ca | Michael Hanselmann | |
604 | 4ca693ca | Michael Hanselmann | def _ChildPreexec(self): |
605 | 4ca693ca | Michael Hanselmann | """Called before child executable is execve'd. |
606 | 4ca693ca | Michael Hanselmann | |
607 | 4ca693ca | Michael Hanselmann | """ |
608 | 4ca693ca | Michael Hanselmann | # Move to separate process group. By sending a signal to its process group |
609 | 4ca693ca | Michael Hanselmann | # we can kill the child process and all grandchildren. |
610 | 4ca693ca | Michael Hanselmann | os.setpgid(0, 0) |
611 | 4ca693ca | Michael Hanselmann | |
612 | 4ca693ca | Michael Hanselmann | # Close almost all file descriptors |
613 | 4ca693ca | Michael Hanselmann | utils.CloseFDs(noclose_fds=self._noclose_fds) |
614 | 4ca693ca | Michael Hanselmann | |
615 | 4ca693ca | Michael Hanselmann | def _SetProcessGroup(self): |
616 | 4ca693ca | Michael Hanselmann | """Sets the child's process group. |
617 | 4ca693ca | Michael Hanselmann | |
618 | 4ca693ca | Michael Hanselmann | """ |
619 | 4ca693ca | Michael Hanselmann | assert self.pid, "Can't be called in child process" |
620 | 4ca693ca | Michael Hanselmann | |
621 | 4ca693ca | Michael Hanselmann | # Avoid race condition by setting child's process group (as good as |
622 | 4ca693ca | Michael Hanselmann | # possible in Python) before sending signals to child. For an |
623 | 4ca693ca | Michael Hanselmann | # explanation, see preexec function for child. |
624 | 4ca693ca | Michael Hanselmann | try: |
625 | 4ca693ca | Michael Hanselmann | os.setpgid(self.pid, self.pid) |
626 | 4ca693ca | Michael Hanselmann | except EnvironmentError, err: |
627 | 4ca693ca | Michael Hanselmann | # If the child process was faster we receive EPERM or EACCES |
628 | 4ca693ca | Michael Hanselmann | if err.errno not in (errno.EPERM, errno.EACCES): |
629 | 4ca693ca | Michael Hanselmann | raise |
630 | 4ca693ca | Michael Hanselmann | |
631 | 4ca693ca | Michael Hanselmann | def Kill(self, signum): |
632 | 4ca693ca | Michael Hanselmann | """Sends signal to child process. |
633 | 4ca693ca | Michael Hanselmann | |
634 | 4ca693ca | Michael Hanselmann | """ |
635 | 4ca693ca | Michael Hanselmann | logging.info("Sending signal %s to child process", signum) |
636 | 4ca693ca | Michael Hanselmann | os.killpg(self.pid, signum) |
637 | 4ca693ca | Michael Hanselmann | |
638 | 4ca693ca | Michael Hanselmann | def ForceQuit(self): |
639 | 4ca693ca | Michael Hanselmann | """Ensure child process is no longer running. |
640 | 4ca693ca | Michael Hanselmann | |
641 | 4ca693ca | Michael Hanselmann | """ |
642 | 4ca693ca | Michael Hanselmann | # Final check if child process is still alive |
643 | 4ca693ca | Michael Hanselmann | if utils.RetryOnSignal(self.poll) is None: |
644 | 4ca693ca | Michael Hanselmann | logging.error("Child process still alive, sending SIGKILL") |
645 | 4ca693ca | Michael Hanselmann | self.Kill(signal.SIGKILL) |
646 | 4ca693ca | Michael Hanselmann | utils.RetryOnSignal(self.wait) |
647 | 4ca693ca | Michael Hanselmann | |
648 | 4ca693ca | Michael Hanselmann | |
649 | 2d76b580 | Michael Hanselmann | def main(): |
650 | 2d76b580 | Michael Hanselmann | """Main function. |
651 | 2d76b580 | Michael Hanselmann | |
652 | 2d76b580 | Michael Hanselmann | """ |
653 | 2d76b580 | Michael Hanselmann | # Option parsing |
654 | 2d76b580 | Michael Hanselmann | (status_file_path, mode) = ParseOptions() |
655 | 2d76b580 | Michael Hanselmann | |
656 | 2d76b580 | Michael Hanselmann | # Configure logging |
657 | 2d76b580 | Michael Hanselmann | child_logger = SetupLogging() |
658 | 2d76b580 | Michael Hanselmann | |
659 | 2d76b580 | Michael Hanselmann | status_file = StatusFile(status_file_path) |
660 | 2d76b580 | Michael Hanselmann | try: |
661 | 2d76b580 | Michael Hanselmann | try: |
662 | 2d76b580 | Michael Hanselmann | # Pipe to receive socat's stderr output |
663 | 2d76b580 | Michael Hanselmann | (socat_stderr_read_fd, socat_stderr_write_fd) = os.pipe() |
664 | 2d76b580 | Michael Hanselmann | |
665 | 2d76b580 | Michael Hanselmann | # Get child process command |
666 | 2d76b580 | Michael Hanselmann | cmd = GetCommand(mode, socat_stderr_write_fd) |
667 | 2d76b580 | Michael Hanselmann | |
668 | 2d76b580 | Michael Hanselmann | logging.debug("Starting command %r", cmd) |
669 | 2d76b580 | Michael Hanselmann | |
670 | 4ca693ca | Michael Hanselmann | # Start child process |
671 | 4ca693ca | Michael Hanselmann | child = ChildProcess(cmd, [socat_stderr_write_fd]) |
672 | 2d76b580 | Michael Hanselmann | try: |
673 | 2d76b580 | Michael Hanselmann | def _ForwardSignal(signum, _): |
674 | 29da446a | Michael Hanselmann | """Forwards signals to child process. |
675 | 2d76b580 | Michael Hanselmann | |
676 | 29da446a | Michael Hanselmann | """ |
677 | 4ca693ca | Michael Hanselmann | child.Kill(signum) |
678 | 2d76b580 | Michael Hanselmann | |
679 | 29da446a | Michael Hanselmann | signal_wakeup = utils.SignalWakeupFd() |
680 | 2d76b580 | Michael Hanselmann | try: |
681 | 29da446a | Michael Hanselmann | # TODO: There is a race condition between starting the child and |
682 | 29da446a | Michael Hanselmann | # handling the signals here. While there might be a way to work around |
683 | 29da446a | Michael Hanselmann | # it by registering the handlers before starting the child and |
684 | 29da446a | Michael Hanselmann | # deferring sent signals until the child is available, doing so can be |
685 | 29da446a | Michael Hanselmann | # complicated. |
686 | 29da446a | Michael Hanselmann | signal_handler = utils.SignalHandler([signal.SIGTERM, signal.SIGINT], |
687 | 29da446a | Michael Hanselmann | handler_fn=_ForwardSignal, |
688 | 29da446a | Michael Hanselmann | wakeup=signal_wakeup) |
689 | 29da446a | Michael Hanselmann | try: |
690 | 29da446a | Michael Hanselmann | # Close child's side |
691 | 29da446a | Michael Hanselmann | utils.RetryOnSignal(os.close, socat_stderr_write_fd) |
692 | 29da446a | Michael Hanselmann | |
693 | 29da446a | Michael Hanselmann | if ProcessChildIO(child, socat_stderr_read_fd, status_file, |
694 | 043f2292 | Michael Hanselmann | child_logger, signal_wakeup, signal_handler, |
695 | 043f2292 | Michael Hanselmann | mode): |
696 | 29da446a | Michael Hanselmann | # The child closed all its file descriptors and there was no |
697 | 29da446a | Michael Hanselmann | # signal |
698 | 29da446a | Michael Hanselmann | # TODO: Implement timeout instead of waiting indefinitely |
699 | 29da446a | Michael Hanselmann | utils.RetryOnSignal(child.wait) |
700 | 29da446a | Michael Hanselmann | finally: |
701 | 29da446a | Michael Hanselmann | signal_handler.Reset() |
702 | 2d76b580 | Michael Hanselmann | finally: |
703 | 29da446a | Michael Hanselmann | signal_wakeup.Reset() |
704 | 2d76b580 | Michael Hanselmann | finally: |
705 | 4ca693ca | Michael Hanselmann | child.ForceQuit() |
706 | 2d76b580 | Michael Hanselmann | |
707 | 2d76b580 | Michael Hanselmann | if child.returncode == 0: |
708 | 2d76b580 | Michael Hanselmann | errmsg = None |
709 | 2d76b580 | Michael Hanselmann | elif child.returncode < 0: |
710 | 2d76b580 | Michael Hanselmann | errmsg = "Exited due to signal %s" % (-child.returncode, ) |
711 | 2d76b580 | Michael Hanselmann | else: |
712 | 2d76b580 | Michael Hanselmann | errmsg = "Exited with status %s" % (child.returncode, ) |
713 | 2d76b580 | Michael Hanselmann | |
714 | 2d76b580 | Michael Hanselmann | status_file.SetExitStatus(child.returncode, errmsg) |
715 | 2d76b580 | Michael Hanselmann | except Exception, err: # pylint: disable-msg=W0703 |
716 | 2d76b580 | Michael Hanselmann | logging.exception("Unhandled error occurred") |
717 | 2d76b580 | Michael Hanselmann | status_file.SetExitStatus(constants.EXIT_FAILURE, |
718 | 2d76b580 | Michael Hanselmann | "Unhandled error occurred: %s" % (err, )) |
719 | 2d76b580 | Michael Hanselmann | |
720 | 2d76b580 | Michael Hanselmann | if status_file.ExitStatusIsSuccess(): |
721 | 2d76b580 | Michael Hanselmann | sys.exit(constants.EXIT_SUCCESS) |
722 | 2d76b580 | Michael Hanselmann | |
723 | 2d76b580 | Michael Hanselmann | sys.exit(constants.EXIT_FAILURE) |
724 | 2d76b580 | Michael Hanselmann | finally: |
725 | 2d76b580 | Michael Hanselmann | status_file.Update(True) |
726 | 2d76b580 | Michael Hanselmann | |
727 | 2d76b580 | Michael Hanselmann | |
728 | 2d76b580 | Michael Hanselmann | if __name__ == "__main__": |
729 | 2d76b580 | Michael Hanselmann | main() |