Statistics
| Branch: | Tag: | Revision:

root / daemons / import-export @ c08d76f5

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