4 # Copyright (C) 2010 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Classes and functions for import/export daemon.
26 from cStringIO import StringIO
28 from ganeti import constants
29 from ganeti import errors
30 from ganeti import utils
33 #: Buffer size: at most this many bytes are transferred at once
36 # Common options for socat
37 SOCAT_TCP_OPTS = ["keepalive", "keepidle=60", "keepintvl=10", "keepcnt=5"]
38 SOCAT_OPENSSL_OPTS = ["verify=1", "cipher=HIGH", "method=TLSv1"]
41 class CommandBuilder(object):
42 def __init__(self, mode, opts, socat_stderr_fd):
43 """Initializes this class.
45 @param mode: Daemon mode (import or export)
46 @param opts: Options object
47 @type socat_stderr_fd: int
48 @param socat_stderr_fd: File descriptor socat should write its stderr to
53 self._socat_stderr_fd = socat_stderr_fd
56 def GetBashCommand(cmd):
57 """Prepares a command to be run in Bash.
60 return ["bash", "-o", "errexit", "-o", "pipefail", "-c", cmd]
62 def _GetSocatCommand(self):
63 """Returns the socat command.
66 common_addr_opts = SOCAT_TCP_OPTS + SOCAT_OPENSSL_OPTS + [
67 "key=%s" % self._opts.key,
68 "cert=%s" % self._opts.cert,
69 "cafile=%s" % self._opts.ca,
72 if self._opts.bind is not None:
73 common_addr_opts.append("bind=%s" % self._opts.bind)
75 if self._mode == constants.IEM_IMPORT:
76 if self._opts.port is None:
79 port = self._opts.port
82 "OPENSSL-LISTEN:%s" % port,
85 # Retry to listen if connection wasn't established successfully, up to
86 # 100 times a second. Note that this still leaves room for DoS attacks.
92 elif self._mode == constants.IEM_EXPORT:
95 "OPENSSL:%s:%s" % (self._opts.host, self._opts.port),
97 # How long to wait per connection attempt
98 "connect-timeout=%s" % self._opts.connect_timeout,
100 # Retry a few times before giving up to connect (once per second)
101 "retry=%s" % self._opts.connect_retries,
106 raise errors.GenericError("Invalid mode '%s'" % self._mode)
108 for i in [addr1, addr2]:
111 raise errors.GenericError("Comma not allowed in socat option"
112 " value: %r" % value)
115 constants.SOCAT_PATH,
126 # Unidirectional mode, the first address is only used for reading, and the
127 # second address is only used for writing
130 ",".join(addr1), ",".join(addr2)
133 def _GetTransportCommand(self):
134 """Returns the command for the transport part of the daemon.
137 socat_cmd = ("%s 2>&%d" %
138 (utils.ShellQuoteArgs(self._GetSocatCommand()),
139 self._socat_stderr_fd))
141 compr = self._opts.compress
143 assert compr in constants.IEC_ALL
145 if self._mode == constants.IEM_IMPORT:
146 if compr == constants.IEC_GZIP:
147 transport_cmd = "%s | gunzip -c" % socat_cmd
149 transport_cmd = socat_cmd
150 elif self._mode == constants.IEM_EXPORT:
151 if compr == constants.IEC_GZIP:
152 transport_cmd = "gzip -c | %s" % socat_cmd
154 transport_cmd = socat_cmd
156 raise errors.GenericError("Invalid mode '%s'" % self._mode)
158 # TODO: Use "dd" to measure processed data (allows to give an ETA)
160 # TODO: Run transport as separate user
161 # The transport uses its own shell to simplify running it as a separate user
163 return self.GetBashCommand(transport_cmd)
165 def GetCommand(self):
166 """Returns the complete child process command.
169 transport_cmd = self._GetTransportCommand()
173 if self._opts.cmd_prefix:
174 buf.write(self._opts.cmd_prefix)
177 buf.write(utils.ShellQuoteArgs(transport_cmd))
179 if self._opts.cmd_suffix:
181 buf.write(self._opts.cmd_suffix)
183 return self.GetBashCommand(buf.getvalue())