Statistics
| Branch: | Tag: | Revision:

root / daemons / import-export @ cdd7f900

History | View | Annotate | Download (14.7 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 select
34 2d76b580 Michael Hanselmann
import signal
35 2d76b580 Michael Hanselmann
import subprocess
36 2d76b580 Michael Hanselmann
import sys
37 2d76b580 Michael Hanselmann
import time
38 2d76b580 Michael Hanselmann
39 2d76b580 Michael Hanselmann
from ganeti import constants
40 2d76b580 Michael Hanselmann
from ganeti import cli
41 2d76b580 Michael Hanselmann
from ganeti import utils
42 2d76b580 Michael Hanselmann
from ganeti import serializer
43 2d76b580 Michael Hanselmann
from ganeti import objects
44 2d76b580 Michael Hanselmann
from ganeti import locking
45 bb44b1ae Michael Hanselmann
from ganeti import impexpd
46 2d76b580 Michael Hanselmann
47 2d76b580 Michael Hanselmann
48 2d76b580 Michael Hanselmann
#: How many lines to keep in the status file
49 2d76b580 Michael Hanselmann
MAX_RECENT_OUTPUT_LINES = 20
50 2d76b580 Michael Hanselmann
51 2d76b580 Michael Hanselmann
#: Don't update status file more than once every 5 seconds (unless forced)
52 2d76b580 Michael Hanselmann
MIN_UPDATE_INTERVAL = 5.0
53 2d76b580 Michael Hanselmann
54 2d76b580 Michael Hanselmann
#: Give child process up to 5 seconds to exit after sending a signal
55 2d76b580 Michael Hanselmann
CHILD_LINGER_TIMEOUT = 5.0
56 2d76b580 Michael Hanselmann
57 043f2292 Michael Hanselmann
#: How long to wait for a connection to be established
58 043f2292 Michael Hanselmann
DEFAULT_CONNECT_TIMEOUT = 60
59 043f2292 Michael Hanselmann
60 2d76b580 Michael Hanselmann
61 2d76b580 Michael Hanselmann
# Global variable for options
62 2d76b580 Michael Hanselmann
options = None
63 2d76b580 Michael Hanselmann
64 2d76b580 Michael Hanselmann
65 2d76b580 Michael Hanselmann
def SetupLogging():
66 2d76b580 Michael Hanselmann
  """Configures the logging module.
67 2d76b580 Michael Hanselmann
68 2d76b580 Michael Hanselmann
  """
69 2d76b580 Michael Hanselmann
  formatter = logging.Formatter("%(asctime)s: %(message)s")
70 2d76b580 Michael Hanselmann
71 2d76b580 Michael Hanselmann
  stderr_handler = logging.StreamHandler()
72 2d76b580 Michael Hanselmann
  stderr_handler.setFormatter(formatter)
73 2d76b580 Michael Hanselmann
  stderr_handler.setLevel(logging.NOTSET)
74 2d76b580 Michael Hanselmann
75 2d76b580 Michael Hanselmann
  root_logger = logging.getLogger("")
76 2d76b580 Michael Hanselmann
  root_logger.addHandler(stderr_handler)
77 2d76b580 Michael Hanselmann
78 2d76b580 Michael Hanselmann
  if options.debug:
79 2d76b580 Michael Hanselmann
    root_logger.setLevel(logging.NOTSET)
80 2d76b580 Michael Hanselmann
  elif options.verbose:
81 2d76b580 Michael Hanselmann
    root_logger.setLevel(logging.INFO)
82 2d76b580 Michael Hanselmann
  else:
83 2d76b580 Michael Hanselmann
    root_logger.setLevel(logging.ERROR)
84 2d76b580 Michael Hanselmann
85 2d76b580 Michael Hanselmann
  # Create special logger for child process output
86 2d76b580 Michael Hanselmann
  child_logger = logging.Logger("child output")
87 2d76b580 Michael Hanselmann
  child_logger.addHandler(stderr_handler)
88 2d76b580 Michael Hanselmann
  child_logger.setLevel(logging.NOTSET)
89 2d76b580 Michael Hanselmann
90 2d76b580 Michael Hanselmann
  return child_logger
91 2d76b580 Michael Hanselmann
92 2d76b580 Michael Hanselmann
93 2d76b580 Michael Hanselmann
class StatusFile:
94 2d76b580 Michael Hanselmann
  """Status file manager.
95 2d76b580 Michael Hanselmann
96 2d76b580 Michael Hanselmann
  """
97 2d76b580 Michael Hanselmann
  def __init__(self, path):
98 2d76b580 Michael Hanselmann
    """Initializes class.
99 2d76b580 Michael Hanselmann
100 2d76b580 Michael Hanselmann
    """
101 2d76b580 Michael Hanselmann
    self._path = path
102 2d76b580 Michael Hanselmann
    self._data = objects.ImportExportStatus(ctime=time.time(),
103 2d76b580 Michael Hanselmann
                                            mtime=None,
104 2d76b580 Michael Hanselmann
                                            recent_output=[])
105 2d76b580 Michael Hanselmann
106 2d76b580 Michael Hanselmann
  def AddRecentOutput(self, line):
107 2d76b580 Michael Hanselmann
    """Adds a new line of recent output.
108 2d76b580 Michael Hanselmann
109 2d76b580 Michael Hanselmann
    """
110 2d76b580 Michael Hanselmann
    self._data.recent_output.append(line)
111 2d76b580 Michael Hanselmann
112 2d76b580 Michael Hanselmann
    # Remove old lines
113 2d76b580 Michael Hanselmann
    del self._data.recent_output[:-MAX_RECENT_OUTPUT_LINES]
114 2d76b580 Michael Hanselmann
115 2d76b580 Michael Hanselmann
  def SetListenPort(self, port):
116 2d76b580 Michael Hanselmann
    """Sets the port the daemon is listening on.
117 2d76b580 Michael Hanselmann
118 2d76b580 Michael Hanselmann
    @type port: int
119 2d76b580 Michael Hanselmann
    @param port: TCP/UDP port
120 2d76b580 Michael Hanselmann
121 2d76b580 Michael Hanselmann
    """
122 2d76b580 Michael Hanselmann
    assert isinstance(port, (int, long)) and 0 < port < 2**16
123 2d76b580 Michael Hanselmann
    self._data.listen_port = port
124 2d76b580 Michael Hanselmann
125 2d76b580 Michael Hanselmann
  def GetListenPort(self):
126 2d76b580 Michael Hanselmann
    """Returns the port the daemon is listening on.
127 2d76b580 Michael Hanselmann
128 2d76b580 Michael Hanselmann
    """
129 2d76b580 Michael Hanselmann
    return self._data.listen_port
130 2d76b580 Michael Hanselmann
131 2d76b580 Michael Hanselmann
  def SetConnected(self):
132 2d76b580 Michael Hanselmann
    """Sets the connected flag.
133 2d76b580 Michael Hanselmann
134 2d76b580 Michael Hanselmann
    """
135 2d76b580 Michael Hanselmann
    self._data.connected = True
136 2d76b580 Michael Hanselmann
137 043f2292 Michael Hanselmann
  def GetConnected(self):
138 043f2292 Michael Hanselmann
    """Determines whether the daemon is connected.
139 043f2292 Michael Hanselmann
140 043f2292 Michael Hanselmann
    """
141 043f2292 Michael Hanselmann
    return self._data.connected
142 043f2292 Michael Hanselmann
143 2d76b580 Michael Hanselmann
  def SetExitStatus(self, exit_status, error_message):
144 2d76b580 Michael Hanselmann
    """Sets the exit status and an error message.
145 2d76b580 Michael Hanselmann
146 2d76b580 Michael Hanselmann
    """
147 2d76b580 Michael Hanselmann
    # Require error message when status isn't 0
148 2d76b580 Michael Hanselmann
    assert exit_status == 0 or error_message
149 2d76b580 Michael Hanselmann
150 2d76b580 Michael Hanselmann
    self._data.exit_status = exit_status
151 2d76b580 Michael Hanselmann
    self._data.error_message = error_message
152 2d76b580 Michael Hanselmann
153 2d76b580 Michael Hanselmann
  def ExitStatusIsSuccess(self):
154 2d76b580 Michael Hanselmann
    """Returns whether the exit status means "success".
155 2d76b580 Michael Hanselmann
156 2d76b580 Michael Hanselmann
    """
157 2d76b580 Michael Hanselmann
    return not bool(self._data.error_message)
158 2d76b580 Michael Hanselmann
159 2d76b580 Michael Hanselmann
  def Update(self, force):
160 2d76b580 Michael Hanselmann
    """Updates the status file.
161 2d76b580 Michael Hanselmann
162 2d76b580 Michael Hanselmann
    @type force: bool
163 2d76b580 Michael Hanselmann
    @param force: Write status file in any case, not only when minimum interval
164 2d76b580 Michael Hanselmann
                  is expired
165 2d76b580 Michael Hanselmann
166 2d76b580 Michael Hanselmann
    """
167 2d76b580 Michael Hanselmann
    if not (force or
168 2d76b580 Michael Hanselmann
            self._data.mtime is None or
169 2d76b580 Michael Hanselmann
            time.time() > (self._data.mtime + MIN_UPDATE_INTERVAL)):
170 2d76b580 Michael Hanselmann
      return
171 2d76b580 Michael Hanselmann
172 2d76b580 Michael Hanselmann
    logging.debug("Updating status file %s", self._path)
173 2d76b580 Michael Hanselmann
174 2d76b580 Michael Hanselmann
    self._data.mtime = time.time()
175 2d76b580 Michael Hanselmann
    utils.WriteFile(self._path,
176 2d76b580 Michael Hanselmann
                    data=serializer.DumpJson(self._data.ToDict(), indent=True),
177 2d76b580 Michael Hanselmann
                    mode=0400)
178 2d76b580 Michael Hanselmann
179 2d76b580 Michael Hanselmann
180 29da446a Michael Hanselmann
def ProcessChildIO(child, socat_stderr_read_fd, status_file, child_logger,
181 043f2292 Michael Hanselmann
                   signal_notify, signal_handler, mode):
182 2d76b580 Michael Hanselmann
  """Handles the child processes' output.
183 2d76b580 Michael Hanselmann
184 2d76b580 Michael Hanselmann
  """
185 29da446a Michael Hanselmann
  assert not (signal_handler.signum - set([signal.SIGTERM, signal.SIGINT])), \
186 29da446a Michael Hanselmann
         "Other signals are not handled in this function"
187 29da446a Michael Hanselmann
188 29da446a Michael Hanselmann
  # Buffer size 0 is important, otherwise .read() with a specified length
189 29da446a Michael Hanselmann
  # might buffer data while poll(2) won't mark its file descriptor as
190 29da446a Michael Hanselmann
  # readable again.
191 29da446a Michael Hanselmann
  socat_stderr_read = os.fdopen(socat_stderr_read_fd, "r", 0)
192 2d76b580 Michael Hanselmann
193 34c9ee7b Michael Hanselmann
  child_io_proc = impexpd.ChildIOProcessor(options.debug, status_file,
194 34c9ee7b Michael Hanselmann
                                           child_logger)
195 2d76b580 Michael Hanselmann
  try:
196 34c9ee7b Michael Hanselmann
    fdmap = {
197 34c9ee7b Michael Hanselmann
      child.stderr.fileno():
198 34c9ee7b Michael Hanselmann
        (child.stderr, child_io_proc.GetLineSplitter(impexpd.PROG_OTHER)),
199 34c9ee7b Michael Hanselmann
      socat_stderr_read.fileno():
200 34c9ee7b Michael Hanselmann
        (socat_stderr_read, child_io_proc.GetLineSplitter(impexpd.PROG_SOCAT)),
201 34c9ee7b Michael Hanselmann
      signal_notify.fileno(): (signal_notify, None),
202 34c9ee7b Michael Hanselmann
      }
203 34c9ee7b Michael Hanselmann
204 34c9ee7b Michael Hanselmann
    poller = select.poll()
205 34c9ee7b Michael Hanselmann
    for fd in fdmap:
206 34c9ee7b Michael Hanselmann
      utils.SetNonblockFlag(fd, True)
207 34c9ee7b Michael Hanselmann
      poller.register(fd, select.POLLIN)
208 34c9ee7b Michael Hanselmann
209 34c9ee7b Michael Hanselmann
    if options.connect_timeout and mode == constants.IEM_IMPORT:
210 34c9ee7b Michael Hanselmann
      listen_timeout = locking.RunningTimeout(options.connect_timeout, True)
211 34c9ee7b Michael Hanselmann
    else:
212 34c9ee7b Michael Hanselmann
      listen_timeout = None
213 34c9ee7b Michael Hanselmann
214 34c9ee7b Michael Hanselmann
    exit_timeout = None
215 34c9ee7b Michael Hanselmann
216 34c9ee7b Michael Hanselmann
    while True:
217 34c9ee7b Michael Hanselmann
      # Break out of loop if only signal notify FD is left
218 34c9ee7b Michael Hanselmann
      if len(fdmap) == 1 and signal_notify.fileno() in fdmap:
219 34c9ee7b Michael Hanselmann
        break
220 34c9ee7b Michael Hanselmann
221 34c9ee7b Michael Hanselmann
      timeout = None
222 34c9ee7b Michael Hanselmann
223 34c9ee7b Michael Hanselmann
      if listen_timeout and not exit_timeout:
224 34c9ee7b Michael Hanselmann
        if status_file.GetConnected():
225 34c9ee7b Michael Hanselmann
          listen_timeout = None
226 34c9ee7b Michael Hanselmann
        elif listen_timeout.Remaining() < 0:
227 34c9ee7b Michael Hanselmann
          logging.info("Child process didn't establish connection in time")
228 34c9ee7b Michael Hanselmann
          child.Kill(signal.SIGTERM)
229 34c9ee7b Michael Hanselmann
          exit_timeout = \
230 34c9ee7b Michael Hanselmann
            locking.RunningTimeout(CHILD_LINGER_TIMEOUT, True)
231 34c9ee7b Michael Hanselmann
          # Next block will calculate timeout
232 34c9ee7b Michael Hanselmann
        else:
233 34c9ee7b Michael Hanselmann
          # Not yet connected, check again in a second
234 34c9ee7b Michael Hanselmann
          timeout = 1000
235 34c9ee7b Michael Hanselmann
236 34c9ee7b Michael Hanselmann
      if exit_timeout:
237 34c9ee7b Michael Hanselmann
        timeout = exit_timeout.Remaining() * 1000
238 34c9ee7b Michael Hanselmann
        if timeout < 0:
239 34c9ee7b Michael Hanselmann
          logging.info("Child process didn't exit in time")
240 2d76b580 Michael Hanselmann
          break
241 2d76b580 Michael Hanselmann
242 34c9ee7b Michael Hanselmann
      for fd, event in utils.RetryOnSignal(poller.poll, timeout):
243 34c9ee7b Michael Hanselmann
        if event & (select.POLLIN | event & select.POLLPRI):
244 34c9ee7b Michael Hanselmann
          (from_, to) = fdmap[fd]
245 34c9ee7b Michael Hanselmann
246 34c9ee7b Michael Hanselmann
          # Read up to 1 KB of data
247 34c9ee7b Michael Hanselmann
          data = from_.read(1024)
248 34c9ee7b Michael Hanselmann
          if data:
249 34c9ee7b Michael Hanselmann
            if to:
250 34c9ee7b Michael Hanselmann
              to.write(data)
251 34c9ee7b Michael Hanselmann
            elif fd == signal_notify.fileno():
252 34c9ee7b Michael Hanselmann
              # Signal handling
253 34c9ee7b Michael Hanselmann
              if signal_handler.called:
254 34c9ee7b Michael Hanselmann
                signal_handler.Clear()
255 34c9ee7b Michael Hanselmann
                if exit_timeout:
256 34c9ee7b Michael Hanselmann
                  logging.info("Child process still has about %0.2f seconds"
257 34c9ee7b Michael Hanselmann
                               " to exit", exit_timeout.Remaining())
258 34c9ee7b Michael Hanselmann
                else:
259 34c9ee7b Michael Hanselmann
                  logging.info("Giving child process %0.2f seconds to exit",
260 34c9ee7b Michael Hanselmann
                               CHILD_LINGER_TIMEOUT)
261 34c9ee7b Michael Hanselmann
                  exit_timeout = \
262 34c9ee7b Michael Hanselmann
                    locking.RunningTimeout(CHILD_LINGER_TIMEOUT, True)
263 043f2292 Michael Hanselmann
          else:
264 2d76b580 Michael Hanselmann
            poller.unregister(fd)
265 2d76b580 Michael Hanselmann
            del fdmap[fd]
266 2d76b580 Michael Hanselmann
267 34c9ee7b Michael Hanselmann
        elif event & (select.POLLNVAL | select.POLLHUP |
268 34c9ee7b Michael Hanselmann
                      select.POLLERR):
269 34c9ee7b Michael Hanselmann
          poller.unregister(fd)
270 34c9ee7b Michael Hanselmann
          del fdmap[fd]
271 34c9ee7b Michael Hanselmann
272 34c9ee7b Michael Hanselmann
      child_io_proc.FlushAll()
273 2d76b580 Michael Hanselmann
274 34c9ee7b Michael Hanselmann
    # If there was a timeout calculator, we were waiting for the child to
275 34c9ee7b Michael Hanselmann
    # finish, e.g. due to a signal
276 34c9ee7b Michael Hanselmann
    return not bool(exit_timeout)
277 2d76b580 Michael Hanselmann
  finally:
278 34c9ee7b Michael Hanselmann
    child_io_proc.CloseAll()
279 2d76b580 Michael Hanselmann
280 2d76b580 Michael Hanselmann
281 2d76b580 Michael Hanselmann
def ParseOptions():
282 2d76b580 Michael Hanselmann
  """Parses the options passed to the program.
283 2d76b580 Michael Hanselmann
284 2d76b580 Michael Hanselmann
  @return: Arguments to program
285 2d76b580 Michael Hanselmann
286 2d76b580 Michael Hanselmann
  """
287 2d76b580 Michael Hanselmann
  global options # pylint: disable-msg=W0603
288 2d76b580 Michael Hanselmann
289 2d76b580 Michael Hanselmann
  parser = optparse.OptionParser(usage=("%%prog <status-file> {%s|%s}" %
290 2d76b580 Michael Hanselmann
                                        (constants.IEM_IMPORT,
291 2d76b580 Michael Hanselmann
                                         constants.IEM_EXPORT)))
292 2d76b580 Michael Hanselmann
  parser.add_option(cli.DEBUG_OPT)
293 2d76b580 Michael Hanselmann
  parser.add_option(cli.VERBOSE_OPT)
294 2d76b580 Michael Hanselmann
  parser.add_option("--key", dest="key", action="store", type="string",
295 2d76b580 Michael Hanselmann
                    help="RSA key file")
296 2d76b580 Michael Hanselmann
  parser.add_option("--cert", dest="cert", action="store", type="string",
297 2d76b580 Michael Hanselmann
                    help="X509 certificate file")
298 2d76b580 Michael Hanselmann
  parser.add_option("--ca", dest="ca", action="store", type="string",
299 2d76b580 Michael Hanselmann
                    help="X509 CA file")
300 2d76b580 Michael Hanselmann
  parser.add_option("--bind", dest="bind", action="store", type="string",
301 2d76b580 Michael Hanselmann
                    help="Bind address")
302 2d76b580 Michael Hanselmann
  parser.add_option("--host", dest="host", action="store", type="string",
303 2d76b580 Michael Hanselmann
                    help="Remote hostname")
304 2d76b580 Michael Hanselmann
  parser.add_option("--port", dest="port", action="store", type="int",
305 2d76b580 Michael Hanselmann
                    help="Remote port")
306 043f2292 Michael Hanselmann
  parser.add_option("--connect-retries", dest="connect_retries", action="store",
307 043f2292 Michael Hanselmann
                    type="int", default=0,
308 043f2292 Michael Hanselmann
                    help=("How many times the connection should be retried"
309 043f2292 Michael Hanselmann
                          " (export only)"))
310 043f2292 Michael Hanselmann
  parser.add_option("--connect-timeout", dest="connect_timeout", action="store",
311 043f2292 Michael Hanselmann
                    type="int", default=DEFAULT_CONNECT_TIMEOUT,
312 043f2292 Michael Hanselmann
                    help="Timeout for connection to be established (seconds)")
313 7e3c1da6 Michael Hanselmann
  parser.add_option("--compress", dest="compress", action="store",
314 7e3c1da6 Michael Hanselmann
                    type="choice", help="Compression method",
315 7e3c1da6 Michael Hanselmann
                    metavar="[%s]" % "|".join(constants.IEC_ALL),
316 7e3c1da6 Michael Hanselmann
                    choices=list(constants.IEC_ALL), default=constants.IEC_GZIP)
317 2d76b580 Michael Hanselmann
  parser.add_option("--cmd-prefix", dest="cmd_prefix", action="store",
318 2d76b580 Michael Hanselmann
                    type="string", help="Command prefix")
319 2d76b580 Michael Hanselmann
  parser.add_option("--cmd-suffix", dest="cmd_suffix", action="store",
320 2d76b580 Michael Hanselmann
                    type="string", help="Command suffix")
321 2d76b580 Michael Hanselmann
322 2d76b580 Michael Hanselmann
  (options, args) = parser.parse_args()
323 2d76b580 Michael Hanselmann
324 2d76b580 Michael Hanselmann
  if len(args) != 2:
325 2d76b580 Michael Hanselmann
    # Won't return
326 2d76b580 Michael Hanselmann
    parser.error("Expected exactly two arguments")
327 2d76b580 Michael Hanselmann
328 2d76b580 Michael Hanselmann
  (status_file_path, mode) = args
329 2d76b580 Michael Hanselmann
330 2d76b580 Michael Hanselmann
  if mode not in (constants.IEM_IMPORT,
331 2d76b580 Michael Hanselmann
                  constants.IEM_EXPORT):
332 2d76b580 Michael Hanselmann
    # Won't return
333 2d76b580 Michael Hanselmann
    parser.error("Invalid mode: %s" % mode)
334 2d76b580 Michael Hanselmann
335 2d76b580 Michael Hanselmann
  return (status_file_path, mode)
336 2d76b580 Michael Hanselmann
337 2d76b580 Michael Hanselmann
338 4ca693ca Michael Hanselmann
class ChildProcess(subprocess.Popen):
339 4ca693ca Michael Hanselmann
  def __init__(self, cmd, noclose_fds):
340 4ca693ca Michael Hanselmann
    """Initializes this class.
341 4ca693ca Michael Hanselmann
342 4ca693ca Michael Hanselmann
    """
343 4ca693ca Michael Hanselmann
    self._noclose_fds = noclose_fds
344 4ca693ca Michael Hanselmann
345 4ca693ca Michael Hanselmann
    # Not using close_fds because doing so would also close the socat stderr
346 4ca693ca Michael Hanselmann
    # pipe, which we still need.
347 4ca693ca Michael Hanselmann
    subprocess.Popen.__init__(self, cmd, shell=False, close_fds=False,
348 4ca693ca Michael Hanselmann
                              stderr=subprocess.PIPE, stdout=None, stdin=None,
349 4ca693ca Michael Hanselmann
                              preexec_fn=self._ChildPreexec)
350 4ca693ca Michael Hanselmann
    self._SetProcessGroup()
351 4ca693ca Michael Hanselmann
352 4ca693ca Michael Hanselmann
  def _ChildPreexec(self):
353 4ca693ca Michael Hanselmann
    """Called before child executable is execve'd.
354 4ca693ca Michael Hanselmann
355 4ca693ca Michael Hanselmann
    """
356 4ca693ca Michael Hanselmann
    # Move to separate process group. By sending a signal to its process group
357 4ca693ca Michael Hanselmann
    # we can kill the child process and all grandchildren.
358 4ca693ca Michael Hanselmann
    os.setpgid(0, 0)
359 4ca693ca Michael Hanselmann
360 4ca693ca Michael Hanselmann
    # Close almost all file descriptors
361 4ca693ca Michael Hanselmann
    utils.CloseFDs(noclose_fds=self._noclose_fds)
362 4ca693ca Michael Hanselmann
363 4ca693ca Michael Hanselmann
  def _SetProcessGroup(self):
364 4ca693ca Michael Hanselmann
    """Sets the child's process group.
365 4ca693ca Michael Hanselmann
366 4ca693ca Michael Hanselmann
    """
367 4ca693ca Michael Hanselmann
    assert self.pid, "Can't be called in child process"
368 4ca693ca Michael Hanselmann
369 4ca693ca Michael Hanselmann
    # Avoid race condition by setting child's process group (as good as
370 4ca693ca Michael Hanselmann
    # possible in Python) before sending signals to child. For an
371 4ca693ca Michael Hanselmann
    # explanation, see preexec function for child.
372 4ca693ca Michael Hanselmann
    try:
373 4ca693ca Michael Hanselmann
      os.setpgid(self.pid, self.pid)
374 4ca693ca Michael Hanselmann
    except EnvironmentError, err:
375 4ca693ca Michael Hanselmann
      # If the child process was faster we receive EPERM or EACCES
376 4ca693ca Michael Hanselmann
      if err.errno not in (errno.EPERM, errno.EACCES):
377 4ca693ca Michael Hanselmann
        raise
378 4ca693ca Michael Hanselmann
379 4ca693ca Michael Hanselmann
  def Kill(self, signum):
380 4ca693ca Michael Hanselmann
    """Sends signal to child process.
381 4ca693ca Michael Hanselmann
382 4ca693ca Michael Hanselmann
    """
383 4ca693ca Michael Hanselmann
    logging.info("Sending signal %s to child process", signum)
384 4ca693ca Michael Hanselmann
    os.killpg(self.pid, signum)
385 4ca693ca Michael Hanselmann
386 4ca693ca Michael Hanselmann
  def ForceQuit(self):
387 4ca693ca Michael Hanselmann
    """Ensure child process is no longer running.
388 4ca693ca Michael Hanselmann
389 4ca693ca Michael Hanselmann
    """
390 4ca693ca Michael Hanselmann
    # Final check if child process is still alive
391 4ca693ca Michael Hanselmann
    if utils.RetryOnSignal(self.poll) is None:
392 4ca693ca Michael Hanselmann
      logging.error("Child process still alive, sending SIGKILL")
393 4ca693ca Michael Hanselmann
      self.Kill(signal.SIGKILL)
394 4ca693ca Michael Hanselmann
      utils.RetryOnSignal(self.wait)
395 4ca693ca Michael Hanselmann
396 4ca693ca Michael Hanselmann
397 2d76b580 Michael Hanselmann
def main():
398 2d76b580 Michael Hanselmann
  """Main function.
399 2d76b580 Michael Hanselmann
400 2d76b580 Michael Hanselmann
  """
401 2d76b580 Michael Hanselmann
  # Option parsing
402 2d76b580 Michael Hanselmann
  (status_file_path, mode) = ParseOptions()
403 2d76b580 Michael Hanselmann
404 2d76b580 Michael Hanselmann
  # Configure logging
405 2d76b580 Michael Hanselmann
  child_logger = SetupLogging()
406 2d76b580 Michael Hanselmann
407 2d76b580 Michael Hanselmann
  status_file = StatusFile(status_file_path)
408 2d76b580 Michael Hanselmann
  try:
409 2d76b580 Michael Hanselmann
    try:
410 2d76b580 Michael Hanselmann
      # Pipe to receive socat's stderr output
411 2d76b580 Michael Hanselmann
      (socat_stderr_read_fd, socat_stderr_write_fd) = os.pipe()
412 2d76b580 Michael Hanselmann
413 2d76b580 Michael Hanselmann
      # Get child process command
414 bb44b1ae Michael Hanselmann
      cmd_builder = impexpd.CommandBuilder(mode, options, socat_stderr_write_fd)
415 bb44b1ae Michael Hanselmann
      cmd = cmd_builder.GetCommand()
416 2d76b580 Michael Hanselmann
417 2d76b580 Michael Hanselmann
      logging.debug("Starting command %r", cmd)
418 2d76b580 Michael Hanselmann
419 4ca693ca Michael Hanselmann
      # Start child process
420 4ca693ca Michael Hanselmann
      child = ChildProcess(cmd, [socat_stderr_write_fd])
421 2d76b580 Michael Hanselmann
      try:
422 2d76b580 Michael Hanselmann
        def _ForwardSignal(signum, _):
423 29da446a Michael Hanselmann
          """Forwards signals to child process.
424 2d76b580 Michael Hanselmann
425 29da446a Michael Hanselmann
          """
426 4ca693ca Michael Hanselmann
          child.Kill(signum)
427 2d76b580 Michael Hanselmann
428 29da446a Michael Hanselmann
        signal_wakeup = utils.SignalWakeupFd()
429 2d76b580 Michael Hanselmann
        try:
430 29da446a Michael Hanselmann
          # TODO: There is a race condition between starting the child and
431 29da446a Michael Hanselmann
          # handling the signals here. While there might be a way to work around
432 29da446a Michael Hanselmann
          # it by registering the handlers before starting the child and
433 29da446a Michael Hanselmann
          # deferring sent signals until the child is available, doing so can be
434 29da446a Michael Hanselmann
          # complicated.
435 29da446a Michael Hanselmann
          signal_handler = utils.SignalHandler([signal.SIGTERM, signal.SIGINT],
436 29da446a Michael Hanselmann
                                               handler_fn=_ForwardSignal,
437 29da446a Michael Hanselmann
                                               wakeup=signal_wakeup)
438 29da446a Michael Hanselmann
          try:
439 29da446a Michael Hanselmann
            # Close child's side
440 29da446a Michael Hanselmann
            utils.RetryOnSignal(os.close, socat_stderr_write_fd)
441 29da446a Michael Hanselmann
442 29da446a Michael Hanselmann
            if ProcessChildIO(child, socat_stderr_read_fd, status_file,
443 043f2292 Michael Hanselmann
                              child_logger, signal_wakeup, signal_handler,
444 043f2292 Michael Hanselmann
                              mode):
445 29da446a Michael Hanselmann
              # The child closed all its file descriptors and there was no
446 29da446a Michael Hanselmann
              # signal
447 29da446a Michael Hanselmann
              # TODO: Implement timeout instead of waiting indefinitely
448 29da446a Michael Hanselmann
              utils.RetryOnSignal(child.wait)
449 29da446a Michael Hanselmann
          finally:
450 29da446a Michael Hanselmann
            signal_handler.Reset()
451 2d76b580 Michael Hanselmann
        finally:
452 29da446a Michael Hanselmann
          signal_wakeup.Reset()
453 2d76b580 Michael Hanselmann
      finally:
454 4ca693ca Michael Hanselmann
        child.ForceQuit()
455 2d76b580 Michael Hanselmann
456 2d76b580 Michael Hanselmann
      if child.returncode == 0:
457 2d76b580 Michael Hanselmann
        errmsg = None
458 2d76b580 Michael Hanselmann
      elif child.returncode < 0:
459 2d76b580 Michael Hanselmann
        errmsg = "Exited due to signal %s" % (-child.returncode, )
460 2d76b580 Michael Hanselmann
      else:
461 2d76b580 Michael Hanselmann
        errmsg = "Exited with status %s" % (child.returncode, )
462 2d76b580 Michael Hanselmann
463 2d76b580 Michael Hanselmann
      status_file.SetExitStatus(child.returncode, errmsg)
464 2d76b580 Michael Hanselmann
    except Exception, err: # pylint: disable-msg=W0703
465 2d76b580 Michael Hanselmann
      logging.exception("Unhandled error occurred")
466 2d76b580 Michael Hanselmann
      status_file.SetExitStatus(constants.EXIT_FAILURE,
467 2d76b580 Michael Hanselmann
                                "Unhandled error occurred: %s" % (err, ))
468 2d76b580 Michael Hanselmann
469 2d76b580 Michael Hanselmann
    if status_file.ExitStatusIsSuccess():
470 2d76b580 Michael Hanselmann
      sys.exit(constants.EXIT_SUCCESS)
471 2d76b580 Michael Hanselmann
472 2d76b580 Michael Hanselmann
    sys.exit(constants.EXIT_FAILURE)
473 2d76b580 Michael Hanselmann
  finally:
474 2d76b580 Michael Hanselmann
    status_file.Update(True)
475 2d76b580 Michael Hanselmann
476 2d76b580 Michael Hanselmann
477 2d76b580 Michael Hanselmann
if __name__ == "__main__":
478 2d76b580 Michael Hanselmann
  main()