Statistics
| Branch: | Tag: | Revision:

root / daemons / import-export @ 26d3fd2f

History | View | Annotate | Download (19.1 kB)

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