Statistics
| Branch: | Tag: | Revision:

root / daemons / import-export @ 560cbec1

History | View | Annotate | Download (18.4 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 f9323011 Michael Hanselmann
                   dd_pid_read_fd, exp_size_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 f9323011 Michael Hanselmann
  exp_size_read = os.fdopen(exp_size_read_fd, "r", 0)
225 c08d76f5 Michael Hanselmann
226 c08d76f5 Michael Hanselmann
  tp_samples = DD_THROUGHPUT_SAMPLES
227 2d76b580 Michael Hanselmann
228 f9323011 Michael Hanselmann
  if options.exp_size == constants.IE_CUSTOM_SIZE:
229 f9323011 Michael Hanselmann
    exp_size = None
230 f9323011 Michael Hanselmann
  else:
231 f9323011 Michael Hanselmann
    exp_size = options.exp_size
232 f9323011 Michael Hanselmann
233 34c9ee7b Michael Hanselmann
  child_io_proc = impexpd.ChildIOProcessor(options.debug, status_file,
234 f9323011 Michael Hanselmann
                                           child_logger, tp_samples,
235 f9323011 Michael Hanselmann
                                           exp_size)
236 2d76b580 Michael Hanselmann
  try:
237 34c9ee7b Michael Hanselmann
    fdmap = {
238 34c9ee7b Michael Hanselmann
      child.stderr.fileno():
239 34c9ee7b Michael Hanselmann
        (child.stderr, child_io_proc.GetLineSplitter(impexpd.PROG_OTHER)),
240 34c9ee7b Michael Hanselmann
      socat_stderr_read.fileno():
241 34c9ee7b Michael Hanselmann
        (socat_stderr_read, child_io_proc.GetLineSplitter(impexpd.PROG_SOCAT)),
242 c08d76f5 Michael Hanselmann
      dd_pid_read.fileno():
243 c08d76f5 Michael Hanselmann
        (dd_pid_read, child_io_proc.GetLineSplitter(impexpd.PROG_DD_PID)),
244 c08d76f5 Michael Hanselmann
      dd_stderr_read.fileno():
245 c08d76f5 Michael Hanselmann
        (dd_stderr_read, child_io_proc.GetLineSplitter(impexpd.PROG_DD)),
246 f9323011 Michael Hanselmann
      exp_size_read.fileno():
247 f9323011 Michael Hanselmann
        (exp_size_read, child_io_proc.GetLineSplitter(impexpd.PROG_EXP_SIZE)),
248 34c9ee7b Michael Hanselmann
      signal_notify.fileno(): (signal_notify, None),
249 34c9ee7b Michael Hanselmann
      }
250 34c9ee7b Michael Hanselmann
251 34c9ee7b Michael Hanselmann
    poller = select.poll()
252 34c9ee7b Michael Hanselmann
    for fd in fdmap:
253 34c9ee7b Michael Hanselmann
      utils.SetNonblockFlag(fd, True)
254 34c9ee7b Michael Hanselmann
      poller.register(fd, select.POLLIN)
255 34c9ee7b Michael Hanselmann
256 34c9ee7b Michael Hanselmann
    if options.connect_timeout and mode == constants.IEM_IMPORT:
257 34c9ee7b Michael Hanselmann
      listen_timeout = locking.RunningTimeout(options.connect_timeout, True)
258 34c9ee7b Michael Hanselmann
    else:
259 34c9ee7b Michael Hanselmann
      listen_timeout = None
260 34c9ee7b Michael Hanselmann
261 34c9ee7b Michael Hanselmann
    exit_timeout = None
262 c08d76f5 Michael Hanselmann
    dd_stats_timeout = None
263 34c9ee7b Michael Hanselmann
264 34c9ee7b Michael Hanselmann
    while True:
265 34c9ee7b Michael Hanselmann
      # Break out of loop if only signal notify FD is left
266 34c9ee7b Michael Hanselmann
      if len(fdmap) == 1 and signal_notify.fileno() in fdmap:
267 34c9ee7b Michael Hanselmann
        break
268 34c9ee7b Michael Hanselmann
269 34c9ee7b Michael Hanselmann
      timeout = None
270 34c9ee7b Michael Hanselmann
271 34c9ee7b Michael Hanselmann
      if listen_timeout and not exit_timeout:
272 34c9ee7b Michael Hanselmann
        if status_file.GetConnected():
273 34c9ee7b Michael Hanselmann
          listen_timeout = None
274 34c9ee7b Michael Hanselmann
        elif listen_timeout.Remaining() < 0:
275 34c9ee7b Michael Hanselmann
          logging.info("Child process didn't establish connection in time")
276 34c9ee7b Michael Hanselmann
          child.Kill(signal.SIGTERM)
277 34c9ee7b Michael Hanselmann
          exit_timeout = \
278 34c9ee7b Michael Hanselmann
            locking.RunningTimeout(CHILD_LINGER_TIMEOUT, True)
279 34c9ee7b Michael Hanselmann
          # Next block will calculate timeout
280 34c9ee7b Michael Hanselmann
        else:
281 34c9ee7b Michael Hanselmann
          # Not yet connected, check again in a second
282 34c9ee7b Michael Hanselmann
          timeout = 1000
283 34c9ee7b Michael Hanselmann
284 34c9ee7b Michael Hanselmann
      if exit_timeout:
285 34c9ee7b Michael Hanselmann
        timeout = exit_timeout.Remaining() * 1000
286 34c9ee7b Michael Hanselmann
        if timeout < 0:
287 34c9ee7b Michael Hanselmann
          logging.info("Child process didn't exit in time")
288 2d76b580 Michael Hanselmann
          break
289 2d76b580 Michael Hanselmann
290 c08d76f5 Michael Hanselmann
      if (not dd_stats_timeout) or dd_stats_timeout.Remaining() < 0:
291 c08d76f5 Michael Hanselmann
        notify_status = child_io_proc.NotifyDd()
292 c08d76f5 Michael Hanselmann
        if notify_status:
293 c08d76f5 Michael Hanselmann
          # Schedule next notification
294 c08d76f5 Michael Hanselmann
          dd_stats_timeout = locking.RunningTimeout(DD_STATISTICS_INTERVAL,
295 c08d76f5 Michael Hanselmann
                                                    True)
296 c08d76f5 Michael Hanselmann
        else:
297 c08d76f5 Michael Hanselmann
          # Try again soon (dd isn't ready yet)
298 c08d76f5 Michael Hanselmann
          dd_stats_timeout = locking.RunningTimeout(1.0, True)
299 c08d76f5 Michael Hanselmann
300 c08d76f5 Michael Hanselmann
      if dd_stats_timeout:
301 c08d76f5 Michael Hanselmann
        dd_timeout = max(0, dd_stats_timeout.Remaining() * 1000)
302 c08d76f5 Michael Hanselmann
303 c08d76f5 Michael Hanselmann
        if timeout is None:
304 c08d76f5 Michael Hanselmann
          timeout = dd_timeout
305 c08d76f5 Michael Hanselmann
        else:
306 c08d76f5 Michael Hanselmann
          timeout = min(timeout, dd_timeout)
307 c08d76f5 Michael Hanselmann
308 34c9ee7b Michael Hanselmann
      for fd, event in utils.RetryOnSignal(poller.poll, timeout):
309 34c9ee7b Michael Hanselmann
        if event & (select.POLLIN | event & select.POLLPRI):
310 34c9ee7b Michael Hanselmann
          (from_, to) = fdmap[fd]
311 34c9ee7b Michael Hanselmann
312 34c9ee7b Michael Hanselmann
          # Read up to 1 KB of data
313 34c9ee7b Michael Hanselmann
          data = from_.read(1024)
314 34c9ee7b Michael Hanselmann
          if data:
315 34c9ee7b Michael Hanselmann
            if to:
316 34c9ee7b Michael Hanselmann
              to.write(data)
317 34c9ee7b Michael Hanselmann
            elif fd == signal_notify.fileno():
318 34c9ee7b Michael Hanselmann
              # Signal handling
319 34c9ee7b Michael Hanselmann
              if signal_handler.called:
320 34c9ee7b Michael Hanselmann
                signal_handler.Clear()
321 34c9ee7b Michael Hanselmann
                if exit_timeout:
322 34c9ee7b Michael Hanselmann
                  logging.info("Child process still has about %0.2f seconds"
323 34c9ee7b Michael Hanselmann
                               " to exit", exit_timeout.Remaining())
324 34c9ee7b Michael Hanselmann
                else:
325 34c9ee7b Michael Hanselmann
                  logging.info("Giving child process %0.2f seconds to exit",
326 34c9ee7b Michael Hanselmann
                               CHILD_LINGER_TIMEOUT)
327 34c9ee7b Michael Hanselmann
                  exit_timeout = \
328 34c9ee7b Michael Hanselmann
                    locking.RunningTimeout(CHILD_LINGER_TIMEOUT, True)
329 043f2292 Michael Hanselmann
          else:
330 2d76b580 Michael Hanselmann
            poller.unregister(fd)
331 2d76b580 Michael Hanselmann
            del fdmap[fd]
332 2d76b580 Michael Hanselmann
333 34c9ee7b Michael Hanselmann
        elif event & (select.POLLNVAL | select.POLLHUP |
334 34c9ee7b Michael Hanselmann
                      select.POLLERR):
335 34c9ee7b Michael Hanselmann
          poller.unregister(fd)
336 34c9ee7b Michael Hanselmann
          del fdmap[fd]
337 34c9ee7b Michael Hanselmann
338 34c9ee7b Michael Hanselmann
      child_io_proc.FlushAll()
339 2d76b580 Michael Hanselmann
340 34c9ee7b Michael Hanselmann
    # If there was a timeout calculator, we were waiting for the child to
341 34c9ee7b Michael Hanselmann
    # finish, e.g. due to a signal
342 34c9ee7b Michael Hanselmann
    return not bool(exit_timeout)
343 2d76b580 Michael Hanselmann
  finally:
344 34c9ee7b Michael Hanselmann
    child_io_proc.CloseAll()
345 2d76b580 Michael Hanselmann
346 2d76b580 Michael Hanselmann
347 2d76b580 Michael Hanselmann
def ParseOptions():
348 2d76b580 Michael Hanselmann
  """Parses the options passed to the program.
349 2d76b580 Michael Hanselmann
350 2d76b580 Michael Hanselmann
  @return: Arguments to program
351 2d76b580 Michael Hanselmann
352 2d76b580 Michael Hanselmann
  """
353 2d76b580 Michael Hanselmann
  global options # pylint: disable-msg=W0603
354 2d76b580 Michael Hanselmann
355 2d76b580 Michael Hanselmann
  parser = optparse.OptionParser(usage=("%%prog <status-file> {%s|%s}" %
356 2d76b580 Michael Hanselmann
                                        (constants.IEM_IMPORT,
357 2d76b580 Michael Hanselmann
                                         constants.IEM_EXPORT)))
358 2d76b580 Michael Hanselmann
  parser.add_option(cli.DEBUG_OPT)
359 2d76b580 Michael Hanselmann
  parser.add_option(cli.VERBOSE_OPT)
360 2d76b580 Michael Hanselmann
  parser.add_option("--key", dest="key", action="store", type="string",
361 2d76b580 Michael Hanselmann
                    help="RSA key file")
362 2d76b580 Michael Hanselmann
  parser.add_option("--cert", dest="cert", action="store", type="string",
363 2d76b580 Michael Hanselmann
                    help="X509 certificate file")
364 2d76b580 Michael Hanselmann
  parser.add_option("--ca", dest="ca", action="store", type="string",
365 2d76b580 Michael Hanselmann
                    help="X509 CA file")
366 2d76b580 Michael Hanselmann
  parser.add_option("--bind", dest="bind", action="store", type="string",
367 2d76b580 Michael Hanselmann
                    help="Bind address")
368 2d76b580 Michael Hanselmann
  parser.add_option("--host", dest="host", action="store", type="string",
369 2d76b580 Michael Hanselmann
                    help="Remote hostname")
370 2d76b580 Michael Hanselmann
  parser.add_option("--port", dest="port", action="store", type="int",
371 2d76b580 Michael Hanselmann
                    help="Remote port")
372 043f2292 Michael Hanselmann
  parser.add_option("--connect-retries", dest="connect_retries", action="store",
373 043f2292 Michael Hanselmann
                    type="int", default=0,
374 043f2292 Michael Hanselmann
                    help=("How many times the connection should be retried"
375 043f2292 Michael Hanselmann
                          " (export only)"))
376 043f2292 Michael Hanselmann
  parser.add_option("--connect-timeout", dest="connect_timeout", action="store",
377 043f2292 Michael Hanselmann
                    type="int", default=DEFAULT_CONNECT_TIMEOUT,
378 043f2292 Michael Hanselmann
                    help="Timeout for connection to be established (seconds)")
379 7e3c1da6 Michael Hanselmann
  parser.add_option("--compress", dest="compress", action="store",
380 7e3c1da6 Michael Hanselmann
                    type="choice", help="Compression method",
381 7e3c1da6 Michael Hanselmann
                    metavar="[%s]" % "|".join(constants.IEC_ALL),
382 7e3c1da6 Michael Hanselmann
                    choices=list(constants.IEC_ALL), default=constants.IEC_GZIP)
383 f9323011 Michael Hanselmann
  parser.add_option("--expected-size", dest="exp_size", action="store",
384 f9323011 Michael Hanselmann
                    type="string", default=None,
385 f9323011 Michael Hanselmann
                    help="Expected import/export size (MiB)")
386 2d76b580 Michael Hanselmann
  parser.add_option("--cmd-prefix", dest="cmd_prefix", action="store",
387 2d76b580 Michael Hanselmann
                    type="string", help="Command prefix")
388 2d76b580 Michael Hanselmann
  parser.add_option("--cmd-suffix", dest="cmd_suffix", action="store",
389 2d76b580 Michael Hanselmann
                    type="string", help="Command suffix")
390 2d76b580 Michael Hanselmann
391 2d76b580 Michael Hanselmann
  (options, args) = parser.parse_args()
392 2d76b580 Michael Hanselmann
393 2d76b580 Michael Hanselmann
  if len(args) != 2:
394 2d76b580 Michael Hanselmann
    # Won't return
395 2d76b580 Michael Hanselmann
    parser.error("Expected exactly two arguments")
396 2d76b580 Michael Hanselmann
397 2d76b580 Michael Hanselmann
  (status_file_path, mode) = args
398 2d76b580 Michael Hanselmann
399 2d76b580 Michael Hanselmann
  if mode not in (constants.IEM_IMPORT,
400 2d76b580 Michael Hanselmann
                  constants.IEM_EXPORT):
401 2d76b580 Michael Hanselmann
    # Won't return
402 2d76b580 Michael Hanselmann
    parser.error("Invalid mode: %s" % mode)
403 2d76b580 Michael Hanselmann
404 f9323011 Michael Hanselmann
  if (options.exp_size is not None and
405 f9323011 Michael Hanselmann
      options.exp_size != constants.IE_CUSTOM_SIZE):
406 f9323011 Michael Hanselmann
    try:
407 f9323011 Michael Hanselmann
      options.exp_size = int(options.exp_size)
408 f9323011 Michael Hanselmann
    except (ValueError, TypeError), err:
409 f9323011 Michael Hanselmann
      # Won't return
410 f9323011 Michael Hanselmann
      parser.error("Invalid value for --expected-size: %s (%s)" %
411 f9323011 Michael Hanselmann
                   (options.exp_size, err))
412 f9323011 Michael Hanselmann
413 2d76b580 Michael Hanselmann
  return (status_file_path, mode)
414 2d76b580 Michael Hanselmann
415 2d76b580 Michael Hanselmann
416 4ca693ca Michael Hanselmann
class ChildProcess(subprocess.Popen):
417 f9323011 Michael Hanselmann
  def __init__(self, env, cmd, noclose_fds):
418 4ca693ca Michael Hanselmann
    """Initializes this class.
419 4ca693ca Michael Hanselmann
420 4ca693ca Michael Hanselmann
    """
421 4ca693ca Michael Hanselmann
    self._noclose_fds = noclose_fds
422 4ca693ca Michael Hanselmann
423 4ca693ca Michael Hanselmann
    # Not using close_fds because doing so would also close the socat stderr
424 4ca693ca Michael Hanselmann
    # pipe, which we still need.
425 f9323011 Michael Hanselmann
    subprocess.Popen.__init__(self, cmd, env=env, shell=False, close_fds=False,
426 4ca693ca Michael Hanselmann
                              stderr=subprocess.PIPE, stdout=None, stdin=None,
427 4ca693ca Michael Hanselmann
                              preexec_fn=self._ChildPreexec)
428 4ca693ca Michael Hanselmann
    self._SetProcessGroup()
429 4ca693ca Michael Hanselmann
430 4ca693ca Michael Hanselmann
  def _ChildPreexec(self):
431 4ca693ca Michael Hanselmann
    """Called before child executable is execve'd.
432 4ca693ca Michael Hanselmann
433 4ca693ca Michael Hanselmann
    """
434 4ca693ca Michael Hanselmann
    # Move to separate process group. By sending a signal to its process group
435 4ca693ca Michael Hanselmann
    # we can kill the child process and all grandchildren.
436 4ca693ca Michael Hanselmann
    os.setpgid(0, 0)
437 4ca693ca Michael Hanselmann
438 4ca693ca Michael Hanselmann
    # Close almost all file descriptors
439 4ca693ca Michael Hanselmann
    utils.CloseFDs(noclose_fds=self._noclose_fds)
440 4ca693ca Michael Hanselmann
441 4ca693ca Michael Hanselmann
  def _SetProcessGroup(self):
442 4ca693ca Michael Hanselmann
    """Sets the child's process group.
443 4ca693ca Michael Hanselmann
444 4ca693ca Michael Hanselmann
    """
445 4ca693ca Michael Hanselmann
    assert self.pid, "Can't be called in child process"
446 4ca693ca Michael Hanselmann
447 4ca693ca Michael Hanselmann
    # Avoid race condition by setting child's process group (as good as
448 4ca693ca Michael Hanselmann
    # possible in Python) before sending signals to child. For an
449 4ca693ca Michael Hanselmann
    # explanation, see preexec function for child.
450 4ca693ca Michael Hanselmann
    try:
451 4ca693ca Michael Hanselmann
      os.setpgid(self.pid, self.pid)
452 4ca693ca Michael Hanselmann
    except EnvironmentError, err:
453 4ca693ca Michael Hanselmann
      # If the child process was faster we receive EPERM or EACCES
454 4ca693ca Michael Hanselmann
      if err.errno not in (errno.EPERM, errno.EACCES):
455 4ca693ca Michael Hanselmann
        raise
456 4ca693ca Michael Hanselmann
457 4ca693ca Michael Hanselmann
  def Kill(self, signum):
458 4ca693ca Michael Hanselmann
    """Sends signal to child process.
459 4ca693ca Michael Hanselmann
460 4ca693ca Michael Hanselmann
    """
461 4ca693ca Michael Hanselmann
    logging.info("Sending signal %s to child process", signum)
462 560cbec1 Michael Hanselmann
    utils.IgnoreProcessNotFound(os.killpg, self.pid, signum)
463 4ca693ca Michael Hanselmann
464 4ca693ca Michael Hanselmann
  def ForceQuit(self):
465 4ca693ca Michael Hanselmann
    """Ensure child process is no longer running.
466 4ca693ca Michael Hanselmann
467 4ca693ca Michael Hanselmann
    """
468 4ca693ca Michael Hanselmann
    # Final check if child process is still alive
469 4ca693ca Michael Hanselmann
    if utils.RetryOnSignal(self.poll) is None:
470 4ca693ca Michael Hanselmann
      logging.error("Child process still alive, sending SIGKILL")
471 4ca693ca Michael Hanselmann
      self.Kill(signal.SIGKILL)
472 4ca693ca Michael Hanselmann
      utils.RetryOnSignal(self.wait)
473 4ca693ca Michael Hanselmann
474 4ca693ca Michael Hanselmann
475 2d76b580 Michael Hanselmann
def main():
476 2d76b580 Michael Hanselmann
  """Main function.
477 2d76b580 Michael Hanselmann
478 2d76b580 Michael Hanselmann
  """
479 2d76b580 Michael Hanselmann
  # Option parsing
480 2d76b580 Michael Hanselmann
  (status_file_path, mode) = ParseOptions()
481 2d76b580 Michael Hanselmann
482 2d76b580 Michael Hanselmann
  # Configure logging
483 2d76b580 Michael Hanselmann
  child_logger = SetupLogging()
484 2d76b580 Michael Hanselmann
485 2d76b580 Michael Hanselmann
  status_file = StatusFile(status_file_path)
486 2d76b580 Michael Hanselmann
  try:
487 2d76b580 Michael Hanselmann
    try:
488 2d76b580 Michael Hanselmann
      # Pipe to receive socat's stderr output
489 2d76b580 Michael Hanselmann
      (socat_stderr_read_fd, socat_stderr_write_fd) = os.pipe()
490 2d76b580 Michael Hanselmann
491 c08d76f5 Michael Hanselmann
      # Pipe to receive dd's stderr output
492 c08d76f5 Michael Hanselmann
      (dd_stderr_read_fd, dd_stderr_write_fd) = os.pipe()
493 c08d76f5 Michael Hanselmann
494 c08d76f5 Michael Hanselmann
      # Pipe to receive dd's PID
495 c08d76f5 Michael Hanselmann
      (dd_pid_read_fd, dd_pid_write_fd) = os.pipe()
496 c08d76f5 Michael Hanselmann
497 f9323011 Michael Hanselmann
      # Pipe to receive size predicted by export script
498 f9323011 Michael Hanselmann
      (exp_size_read_fd, exp_size_write_fd) = os.pipe()
499 f9323011 Michael Hanselmann
500 2d76b580 Michael Hanselmann
      # Get child process command
501 c08d76f5 Michael Hanselmann
      cmd_builder = impexpd.CommandBuilder(mode, options, socat_stderr_write_fd,
502 c08d76f5 Michael Hanselmann
                                           dd_stderr_write_fd, dd_pid_write_fd)
503 bb44b1ae Michael Hanselmann
      cmd = cmd_builder.GetCommand()
504 2d76b580 Michael Hanselmann
505 f9323011 Michael Hanselmann
      # Prepare command environment
506 f9323011 Michael Hanselmann
      cmd_env = os.environ.copy()
507 f9323011 Michael Hanselmann
508 f9323011 Michael Hanselmann
      if options.exp_size == constants.IE_CUSTOM_SIZE:
509 f9323011 Michael Hanselmann
        cmd_env["EXP_SIZE_FD"] = str(exp_size_write_fd)
510 f9323011 Michael Hanselmann
511 2d76b580 Michael Hanselmann
      logging.debug("Starting command %r", cmd)
512 2d76b580 Michael Hanselmann
513 4ca693ca Michael Hanselmann
      # Start child process
514 f9323011 Michael Hanselmann
      child = ChildProcess(cmd_env, cmd,
515 f9323011 Michael Hanselmann
                           [socat_stderr_write_fd, dd_stderr_write_fd,
516 f9323011 Michael Hanselmann
                            dd_pid_write_fd, exp_size_write_fd])
517 2d76b580 Michael Hanselmann
      try:
518 2d76b580 Michael Hanselmann
        def _ForwardSignal(signum, _):
519 29da446a Michael Hanselmann
          """Forwards signals to child process.
520 2d76b580 Michael Hanselmann
521 29da446a Michael Hanselmann
          """
522 4ca693ca Michael Hanselmann
          child.Kill(signum)
523 2d76b580 Michael Hanselmann
524 29da446a Michael Hanselmann
        signal_wakeup = utils.SignalWakeupFd()
525 2d76b580 Michael Hanselmann
        try:
526 29da446a Michael Hanselmann
          # TODO: There is a race condition between starting the child and
527 29da446a Michael Hanselmann
          # handling the signals here. While there might be a way to work around
528 29da446a Michael Hanselmann
          # it by registering the handlers before starting the child and
529 29da446a Michael Hanselmann
          # deferring sent signals until the child is available, doing so can be
530 29da446a Michael Hanselmann
          # complicated.
531 29da446a Michael Hanselmann
          signal_handler = utils.SignalHandler([signal.SIGTERM, signal.SIGINT],
532 29da446a Michael Hanselmann
                                               handler_fn=_ForwardSignal,
533 29da446a Michael Hanselmann
                                               wakeup=signal_wakeup)
534 29da446a Michael Hanselmann
          try:
535 29da446a Michael Hanselmann
            # Close child's side
536 29da446a Michael Hanselmann
            utils.RetryOnSignal(os.close, socat_stderr_write_fd)
537 c08d76f5 Michael Hanselmann
            utils.RetryOnSignal(os.close, dd_stderr_write_fd)
538 c08d76f5 Michael Hanselmann
            utils.RetryOnSignal(os.close, dd_pid_write_fd)
539 f9323011 Michael Hanselmann
            utils.RetryOnSignal(os.close, exp_size_write_fd)
540 29da446a Michael Hanselmann
541 c08d76f5 Michael Hanselmann
            if ProcessChildIO(child, socat_stderr_read_fd, dd_stderr_read_fd,
542 f9323011 Michael Hanselmann
                              dd_pid_read_fd, exp_size_read_fd,
543 f9323011 Michael Hanselmann
                              status_file, child_logger,
544 c08d76f5 Michael Hanselmann
                              signal_wakeup, signal_handler, mode):
545 29da446a Michael Hanselmann
              # The child closed all its file descriptors and there was no
546 29da446a Michael Hanselmann
              # signal
547 29da446a Michael Hanselmann
              # TODO: Implement timeout instead of waiting indefinitely
548 29da446a Michael Hanselmann
              utils.RetryOnSignal(child.wait)
549 29da446a Michael Hanselmann
          finally:
550 29da446a Michael Hanselmann
            signal_handler.Reset()
551 2d76b580 Michael Hanselmann
        finally:
552 29da446a Michael Hanselmann
          signal_wakeup.Reset()
553 2d76b580 Michael Hanselmann
      finally:
554 4ca693ca Michael Hanselmann
        child.ForceQuit()
555 2d76b580 Michael Hanselmann
556 2d76b580 Michael Hanselmann
      if child.returncode == 0:
557 2d76b580 Michael Hanselmann
        errmsg = None
558 2d76b580 Michael Hanselmann
      elif child.returncode < 0:
559 2d76b580 Michael Hanselmann
        errmsg = "Exited due to signal %s" % (-child.returncode, )
560 2d76b580 Michael Hanselmann
      else:
561 2d76b580 Michael Hanselmann
        errmsg = "Exited with status %s" % (child.returncode, )
562 2d76b580 Michael Hanselmann
563 2d76b580 Michael Hanselmann
      status_file.SetExitStatus(child.returncode, errmsg)
564 2d76b580 Michael Hanselmann
    except Exception, err: # pylint: disable-msg=W0703
565 2d76b580 Michael Hanselmann
      logging.exception("Unhandled error occurred")
566 2d76b580 Michael Hanselmann
      status_file.SetExitStatus(constants.EXIT_FAILURE,
567 2d76b580 Michael Hanselmann
                                "Unhandled error occurred: %s" % (err, ))
568 2d76b580 Michael Hanselmann
569 2d76b580 Michael Hanselmann
    if status_file.ExitStatusIsSuccess():
570 2d76b580 Michael Hanselmann
      sys.exit(constants.EXIT_SUCCESS)
571 2d76b580 Michael Hanselmann
572 2d76b580 Michael Hanselmann
    sys.exit(constants.EXIT_FAILURE)
573 2d76b580 Michael Hanselmann
  finally:
574 2d76b580 Michael Hanselmann
    status_file.Update(True)
575 2d76b580 Michael Hanselmann
576 2d76b580 Michael Hanselmann
577 2d76b580 Michael Hanselmann
if __name__ == "__main__":
578 2d76b580 Michael Hanselmann
  main()