Statistics
| Branch: | Tag: | Revision:

root / daemons / import-export @ acd65a16

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