Statistics
| Branch: | Tag: | Revision:

root / tools / move-instance @ 40edb0cc

History | View | Annotate | Download (27.6 kB)

1 6bf273d5 Michael Hanselmann
#!/usr/bin/python
2 6bf273d5 Michael Hanselmann
#
3 6bf273d5 Michael Hanselmann
4 6bf273d5 Michael Hanselmann
# Copyright (C) 2010 Google Inc.
5 6bf273d5 Michael Hanselmann
#
6 6bf273d5 Michael Hanselmann
# This program is free software; you can redistribute it and/or modify
7 6bf273d5 Michael Hanselmann
# it under the terms of the GNU General Public License as published by
8 6bf273d5 Michael Hanselmann
# the Free Software Foundation; either version 2 of the License, or
9 6bf273d5 Michael Hanselmann
# (at your option) any later version.
10 6bf273d5 Michael Hanselmann
#
11 6bf273d5 Michael Hanselmann
# This program is distributed in the hope that it will be useful, but
12 6bf273d5 Michael Hanselmann
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 6bf273d5 Michael Hanselmann
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 6bf273d5 Michael Hanselmann
# General Public License for more details.
15 6bf273d5 Michael Hanselmann
#
16 6bf273d5 Michael Hanselmann
# You should have received a copy of the GNU General Public License
17 6bf273d5 Michael Hanselmann
# along with this program; if not, write to the Free Software
18 6bf273d5 Michael Hanselmann
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 6bf273d5 Michael Hanselmann
# 02110-1301, USA.
20 6bf273d5 Michael Hanselmann
21 6bf273d5 Michael Hanselmann
"""Tool to move instances from one cluster to another.
22 6bf273d5 Michael Hanselmann
23 6bf273d5 Michael Hanselmann
"""
24 6bf273d5 Michael Hanselmann
25 6bf273d5 Michael Hanselmann
# pylint: disable-msg=C0103
26 6bf273d5 Michael Hanselmann
# C0103: Invalid name move-instance
27 6bf273d5 Michael Hanselmann
28 6bf273d5 Michael Hanselmann
import os
29 6bf273d5 Michael Hanselmann
import sys
30 6bf273d5 Michael Hanselmann
import time
31 6bf273d5 Michael Hanselmann
import logging
32 6bf273d5 Michael Hanselmann
import optparse
33 6bf273d5 Michael Hanselmann
import threading
34 6bf273d5 Michael Hanselmann
35 6bf273d5 Michael Hanselmann
from ganeti import cli
36 6bf273d5 Michael Hanselmann
from ganeti import constants
37 6bf273d5 Michael Hanselmann
from ganeti import utils
38 6bf273d5 Michael Hanselmann
from ganeti import workerpool
39 6bf273d5 Michael Hanselmann
from ganeti import compat
40 6bf273d5 Michael Hanselmann
from ganeti import rapi
41 6bf273d5 Michael Hanselmann
42 6bf273d5 Michael Hanselmann
import ganeti.rapi.client # pylint: disable-msg=W0611
43 6bf273d5 Michael Hanselmann
import ganeti.rapi.client_utils
44 6bf273d5 Michael Hanselmann
45 6bf273d5 Michael Hanselmann
46 6bf273d5 Michael Hanselmann
SRC_RAPI_PORT_OPT = \
47 6bf273d5 Michael Hanselmann
  cli.cli_option("--src-rapi-port", action="store", type="int",
48 6bf273d5 Michael Hanselmann
                 dest="src_rapi_port", default=constants.DEFAULT_RAPI_PORT,
49 6bf273d5 Michael Hanselmann
                 help=("Source cluster RAPI port (defaults to %s)" %
50 6bf273d5 Michael Hanselmann
                       constants.DEFAULT_RAPI_PORT))
51 6bf273d5 Michael Hanselmann
52 6bf273d5 Michael Hanselmann
SRC_CA_FILE_OPT = \
53 6bf273d5 Michael Hanselmann
  cli.cli_option("--src-ca-file", action="store", type="string",
54 6bf273d5 Michael Hanselmann
                 dest="src_ca_file",
55 6bf273d5 Michael Hanselmann
                 help=("File containing source cluster Certificate"
56 6bf273d5 Michael Hanselmann
                       " Authority (CA) in PEM format"))
57 6bf273d5 Michael Hanselmann
58 6bf273d5 Michael Hanselmann
SRC_USERNAME_OPT = \
59 6bf273d5 Michael Hanselmann
  cli.cli_option("--src-username", action="store", type="string",
60 6bf273d5 Michael Hanselmann
                 dest="src_username", default=None,
61 6bf273d5 Michael Hanselmann
                 help="Source cluster username")
62 6bf273d5 Michael Hanselmann
63 6bf273d5 Michael Hanselmann
SRC_PASSWORD_FILE_OPT = \
64 6bf273d5 Michael Hanselmann
  cli.cli_option("--src-password-file", action="store", type="string",
65 6bf273d5 Michael Hanselmann
                 dest="src_password_file",
66 6bf273d5 Michael Hanselmann
                 help="File containing source cluster password")
67 6bf273d5 Michael Hanselmann
68 6bf273d5 Michael Hanselmann
DEST_RAPI_PORT_OPT = \
69 6bf273d5 Michael Hanselmann
  cli.cli_option("--dest-rapi-port", action="store", type="int",
70 6bf273d5 Michael Hanselmann
                 dest="dest_rapi_port", default=constants.DEFAULT_RAPI_PORT,
71 6bf273d5 Michael Hanselmann
                 help=("Destination cluster RAPI port (defaults to source"
72 6bf273d5 Michael Hanselmann
                       " cluster RAPI port)"))
73 6bf273d5 Michael Hanselmann
74 6bf273d5 Michael Hanselmann
DEST_CA_FILE_OPT = \
75 6bf273d5 Michael Hanselmann
  cli.cli_option("--dest-ca-file", action="store", type="string",
76 6bf273d5 Michael Hanselmann
                 dest="dest_ca_file",
77 6bf273d5 Michael Hanselmann
                 help=("File containing destination cluster Certificate"
78 6bf273d5 Michael Hanselmann
                       " Authority (CA) in PEM format (defaults to source"
79 6bf273d5 Michael Hanselmann
                       " cluster CA)"))
80 6bf273d5 Michael Hanselmann
81 6bf273d5 Michael Hanselmann
DEST_USERNAME_OPT = \
82 6bf273d5 Michael Hanselmann
  cli.cli_option("--dest-username", action="store", type="string",
83 6bf273d5 Michael Hanselmann
                 dest="dest_username", default=None,
84 6bf273d5 Michael Hanselmann
                 help=("Destination cluster username (defaults to"
85 6bf273d5 Michael Hanselmann
                       " source cluster username)"))
86 6bf273d5 Michael Hanselmann
87 6bf273d5 Michael Hanselmann
DEST_PASSWORD_FILE_OPT = \
88 6bf273d5 Michael Hanselmann
  cli.cli_option("--dest-password-file", action="store", type="string",
89 6bf273d5 Michael Hanselmann
                 dest="dest_password_file",
90 6bf273d5 Michael Hanselmann
                 help=("File containing destination cluster password"
91 6bf273d5 Michael Hanselmann
                       " (defaults to source cluster password)"))
92 6bf273d5 Michael Hanselmann
93 6bf273d5 Michael Hanselmann
DEST_INSTANCE_NAME_OPT = \
94 6bf273d5 Michael Hanselmann
  cli.cli_option("--dest-instance-name", action="store", type="string",
95 6bf273d5 Michael Hanselmann
                 dest="dest_instance_name",
96 6bf273d5 Michael Hanselmann
                 help=("Instance name on destination cluster (only"
97 6bf273d5 Michael Hanselmann
                       " when moving exactly one instance)"))
98 6bf273d5 Michael Hanselmann
99 6bf273d5 Michael Hanselmann
DEST_PRIMARY_NODE_OPT = \
100 6bf273d5 Michael Hanselmann
  cli.cli_option("--dest-primary-node", action="store", type="string",
101 6bf273d5 Michael Hanselmann
                 dest="dest_primary_node",
102 6bf273d5 Michael Hanselmann
                 help=("Primary node on destination cluster (only"
103 6bf273d5 Michael Hanselmann
                       " when moving exactly one instance)"))
104 6bf273d5 Michael Hanselmann
105 6bf273d5 Michael Hanselmann
DEST_SECONDARY_NODE_OPT = \
106 6bf273d5 Michael Hanselmann
  cli.cli_option("--dest-secondary-node", action="store", type="string",
107 6bf273d5 Michael Hanselmann
                 dest="dest_secondary_node",
108 6bf273d5 Michael Hanselmann
                 help=("Secondary node on destination cluster (only"
109 6bf273d5 Michael Hanselmann
                       " when moving exactly one instance)"))
110 6bf273d5 Michael Hanselmann
111 6bf273d5 Michael Hanselmann
PARALLEL_OPT = \
112 6bf273d5 Michael Hanselmann
  cli.cli_option("-p", "--parallel", action="store", type="int", default=1,
113 6bf273d5 Michael Hanselmann
                 dest="parallel", metavar="<number>",
114 6bf273d5 Michael Hanselmann
                 help="Number of instances to be moved simultaneously")
115 6bf273d5 Michael Hanselmann
116 6bf273d5 Michael Hanselmann
117 6bf273d5 Michael Hanselmann
class Error(Exception):
118 6bf273d5 Michael Hanselmann
  """Generic error.
119 6bf273d5 Michael Hanselmann
120 6bf273d5 Michael Hanselmann
  """
121 6bf273d5 Michael Hanselmann
122 6bf273d5 Michael Hanselmann
123 6bf273d5 Michael Hanselmann
class Abort(Error):
124 6bf273d5 Michael Hanselmann
  """Special exception for aborting import/export.
125 6bf273d5 Michael Hanselmann
126 6bf273d5 Michael Hanselmann
  """
127 6bf273d5 Michael Hanselmann
128 6bf273d5 Michael Hanselmann
129 6bf273d5 Michael Hanselmann
class RapiClientFactory:
130 6bf273d5 Michael Hanselmann
  """Factory class for creating RAPI clients.
131 6bf273d5 Michael Hanselmann
132 6bf273d5 Michael Hanselmann
  @ivar src_cluster_name: Source cluster name
133 6bf273d5 Michael Hanselmann
  @ivar dest_cluster_name: Destination cluster name
134 6bf273d5 Michael Hanselmann
  @ivar GetSourceClient: Callable returning new client for source cluster
135 6bf273d5 Michael Hanselmann
  @ivar GetDestClient: Callable returning new client for destination cluster
136 6bf273d5 Michael Hanselmann
137 6bf273d5 Michael Hanselmann
  """
138 6bf273d5 Michael Hanselmann
  def __init__(self, options, src_cluster_name, dest_cluster_name):
139 6bf273d5 Michael Hanselmann
    """Initializes this class.
140 6bf273d5 Michael Hanselmann
141 6bf273d5 Michael Hanselmann
    @param options: Program options
142 6bf273d5 Michael Hanselmann
    @type src_cluster_name: string
143 6bf273d5 Michael Hanselmann
    @param src_cluster_name: Source cluster name
144 6bf273d5 Michael Hanselmann
    @type dest_cluster_name: string
145 6bf273d5 Michael Hanselmann
    @param dest_cluster_name: Destination cluster name
146 6bf273d5 Michael Hanselmann
147 6bf273d5 Michael Hanselmann
    """
148 6bf273d5 Michael Hanselmann
    self.src_cluster_name = src_cluster_name
149 6bf273d5 Michael Hanselmann
    self.dest_cluster_name = dest_cluster_name
150 6bf273d5 Michael Hanselmann
151 2a7c3583 Michael Hanselmann
    # TODO: Implement timeouts for RAPI connections
152 6bf273d5 Michael Hanselmann
    # TODO: Support for using system default paths for verifying SSL certificate
153 6bf273d5 Michael Hanselmann
    logging.debug("Using '%s' as source CA", options.src_ca_file)
154 2a7c3583 Michael Hanselmann
    src_curl_config = rapi.client.GenericCurlConfig(cafile=options.src_ca_file)
155 6bf273d5 Michael Hanselmann
156 6bf273d5 Michael Hanselmann
    if options.dest_ca_file:
157 6bf273d5 Michael Hanselmann
      logging.debug("Using '%s' as destination CA", options.dest_ca_file)
158 2a7c3583 Michael Hanselmann
      dest_curl_config = \
159 2a7c3583 Michael Hanselmann
        rapi.client.GenericCurlConfig(cafile=options.dest_ca_file)
160 6bf273d5 Michael Hanselmann
    else:
161 6bf273d5 Michael Hanselmann
      logging.debug("Using source CA for destination")
162 2a7c3583 Michael Hanselmann
      dest_curl_config = src_curl_config
163 6bf273d5 Michael Hanselmann
164 6bf273d5 Michael Hanselmann
    logging.debug("Source RAPI server is %s:%s",
165 6bf273d5 Michael Hanselmann
                  src_cluster_name, options.src_rapi_port)
166 6bf273d5 Michael Hanselmann
    logging.debug("Source username is '%s'", options.src_username)
167 6bf273d5 Michael Hanselmann
168 6bf273d5 Michael Hanselmann
    if options.src_username is None:
169 6bf273d5 Michael Hanselmann
      src_username = ""
170 6bf273d5 Michael Hanselmann
    else:
171 6bf273d5 Michael Hanselmann
      src_username = options.src_username
172 6bf273d5 Michael Hanselmann
173 6bf273d5 Michael Hanselmann
    if options.src_password_file:
174 6bf273d5 Michael Hanselmann
      logging.debug("Reading '%s' for source password",
175 6bf273d5 Michael Hanselmann
                    options.src_password_file)
176 6bf273d5 Michael Hanselmann
      src_password = utils.ReadOneLineFile(options.src_password_file,
177 6bf273d5 Michael Hanselmann
                                           strict=True)
178 6bf273d5 Michael Hanselmann
    else:
179 6bf273d5 Michael Hanselmann
      logging.debug("Source has no password")
180 6bf273d5 Michael Hanselmann
      src_password = None
181 6bf273d5 Michael Hanselmann
182 6bf273d5 Michael Hanselmann
    self.GetSourceClient = lambda: \
183 6bf273d5 Michael Hanselmann
      rapi.client.GanetiRapiClient(src_cluster_name,
184 6bf273d5 Michael Hanselmann
                                   port=options.src_rapi_port,
185 2a7c3583 Michael Hanselmann
                                   curl_config_fn=src_curl_config,
186 6bf273d5 Michael Hanselmann
                                   username=src_username,
187 6bf273d5 Michael Hanselmann
                                   password=src_password)
188 6bf273d5 Michael Hanselmann
189 6bf273d5 Michael Hanselmann
    if options.dest_rapi_port:
190 6bf273d5 Michael Hanselmann
      dest_rapi_port = options.dest_rapi_port
191 6bf273d5 Michael Hanselmann
    else:
192 6bf273d5 Michael Hanselmann
      dest_rapi_port = options.src_rapi_port
193 6bf273d5 Michael Hanselmann
194 6bf273d5 Michael Hanselmann
    if options.dest_username is None:
195 6bf273d5 Michael Hanselmann
      dest_username = src_username
196 6bf273d5 Michael Hanselmann
    else:
197 6bf273d5 Michael Hanselmann
      dest_username = options.dest_username
198 6bf273d5 Michael Hanselmann
199 6bf273d5 Michael Hanselmann
    logging.debug("Destination RAPI server is %s:%s",
200 6bf273d5 Michael Hanselmann
                  dest_cluster_name, dest_rapi_port)
201 6bf273d5 Michael Hanselmann
    logging.debug("Destination username is '%s'", dest_username)
202 6bf273d5 Michael Hanselmann
203 6bf273d5 Michael Hanselmann
    if options.dest_password_file:
204 6bf273d5 Michael Hanselmann
      logging.debug("Reading '%s' for destination password",
205 6bf273d5 Michael Hanselmann
                    options.dest_password_file)
206 6bf273d5 Michael Hanselmann
      dest_password = utils.ReadOneLineFile(options.dest_password_file,
207 6bf273d5 Michael Hanselmann
                                            strict=True)
208 6bf273d5 Michael Hanselmann
    else:
209 6bf273d5 Michael Hanselmann
      logging.debug("Using source password for destination")
210 6bf273d5 Michael Hanselmann
      dest_password = src_password
211 6bf273d5 Michael Hanselmann
212 6bf273d5 Michael Hanselmann
    self.GetDestClient = lambda: \
213 6bf273d5 Michael Hanselmann
      rapi.client.GanetiRapiClient(dest_cluster_name,
214 6bf273d5 Michael Hanselmann
                                   port=dest_rapi_port,
215 2a7c3583 Michael Hanselmann
                                   curl_config_fn=dest_curl_config,
216 6bf273d5 Michael Hanselmann
                                   username=dest_username,
217 6bf273d5 Michael Hanselmann
                                   password=dest_password)
218 6bf273d5 Michael Hanselmann
219 6bf273d5 Michael Hanselmann
220 6bf273d5 Michael Hanselmann
class MoveJobPollReportCb(cli.JobPollReportCbBase):
221 6bf273d5 Michael Hanselmann
  def __init__(self, abort_check_fn, remote_import_fn):
222 6bf273d5 Michael Hanselmann
    """Initializes this class.
223 6bf273d5 Michael Hanselmann
224 6bf273d5 Michael Hanselmann
    @type abort_check_fn: callable
225 6bf273d5 Michael Hanselmann
    @param abort_check_fn: Function to check whether move is aborted
226 6bf273d5 Michael Hanselmann
    @type remote_import_fn: callable or None
227 6bf273d5 Michael Hanselmann
    @param remote_import_fn: Callback for reporting received remote import
228 6bf273d5 Michael Hanselmann
                             information
229 6bf273d5 Michael Hanselmann
230 6bf273d5 Michael Hanselmann
    """
231 6bf273d5 Michael Hanselmann
    cli.JobPollReportCbBase.__init__(self)
232 6bf273d5 Michael Hanselmann
    self._abort_check_fn = abort_check_fn
233 6bf273d5 Michael Hanselmann
    self._remote_import_fn = remote_import_fn
234 6bf273d5 Michael Hanselmann
235 6bf273d5 Michael Hanselmann
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
236 6bf273d5 Michael Hanselmann
    """Handles a log message.
237 6bf273d5 Michael Hanselmann
238 6bf273d5 Michael Hanselmann
    """
239 6bf273d5 Michael Hanselmann
    if log_type == constants.ELOG_REMOTE_IMPORT:
240 6bf273d5 Michael Hanselmann
      logging.debug("Received remote import information")
241 6bf273d5 Michael Hanselmann
242 6bf273d5 Michael Hanselmann
      if not self._remote_import_fn:
243 6bf273d5 Michael Hanselmann
        raise RuntimeError("Received unexpected remote import information")
244 6bf273d5 Michael Hanselmann
245 6bf273d5 Michael Hanselmann
      assert "x509_ca" in log_msg
246 6bf273d5 Michael Hanselmann
      assert "disks" in log_msg
247 6bf273d5 Michael Hanselmann
248 6bf273d5 Michael Hanselmann
      self._remote_import_fn(log_msg)
249 6bf273d5 Michael Hanselmann
250 6bf273d5 Michael Hanselmann
      return
251 6bf273d5 Michael Hanselmann
252 6bf273d5 Michael Hanselmann
    logging.info("[%s] %s", time.ctime(utils.MergeTime(timestamp)),
253 8a7f1c61 Michael Hanselmann
                 cli.FormatLogMessage(log_type, log_msg))
254 6bf273d5 Michael Hanselmann
255 6bf273d5 Michael Hanselmann
  def ReportNotChanged(self, job_id, status):
256 6bf273d5 Michael Hanselmann
    """Called if a job hasn't changed in a while.
257 6bf273d5 Michael Hanselmann
258 6bf273d5 Michael Hanselmann
    """
259 6bf273d5 Michael Hanselmann
    try:
260 6bf273d5 Michael Hanselmann
      # Check whether we were told to abort by the other thread
261 6bf273d5 Michael Hanselmann
      self._abort_check_fn()
262 6bf273d5 Michael Hanselmann
    except Abort:
263 6bf273d5 Michael Hanselmann
      logging.warning("Aborting despite job %s still running", job_id)
264 6bf273d5 Michael Hanselmann
      raise
265 6bf273d5 Michael Hanselmann
266 6bf273d5 Michael Hanselmann
267 6bf273d5 Michael Hanselmann
class InstanceMove(object):
268 6bf273d5 Michael Hanselmann
  """Status class for instance moves.
269 6bf273d5 Michael Hanselmann
270 6bf273d5 Michael Hanselmann
  """
271 6bf273d5 Michael Hanselmann
  def __init__(self, src_instance_name, dest_instance_name,
272 6bf273d5 Michael Hanselmann
               dest_pnode, dest_snode, dest_iallocator):
273 6bf273d5 Michael Hanselmann
    """Initializes this class.
274 6bf273d5 Michael Hanselmann
275 6bf273d5 Michael Hanselmann
    @type src_instance_name: string
276 6bf273d5 Michael Hanselmann
    @param src_instance_name: Instance name on source cluster
277 6bf273d5 Michael Hanselmann
    @type dest_instance_name: string
278 6bf273d5 Michael Hanselmann
    @param dest_instance_name: Instance name on destination cluster
279 6bf273d5 Michael Hanselmann
    @type dest_pnode: string or None
280 6bf273d5 Michael Hanselmann
    @param dest_pnode: Name of primary node on destination cluster
281 6bf273d5 Michael Hanselmann
    @type dest_snode: string or None
282 6bf273d5 Michael Hanselmann
    @param dest_snode: Name of secondary node on destination cluster
283 6bf273d5 Michael Hanselmann
    @type dest_iallocator: string or None
284 6bf273d5 Michael Hanselmann
    @param dest_iallocator: Name of iallocator to use
285 6bf273d5 Michael Hanselmann
286 6bf273d5 Michael Hanselmann
    """
287 6bf273d5 Michael Hanselmann
    self.src_instance_name = src_instance_name
288 6bf273d5 Michael Hanselmann
    self.dest_instance_name = dest_instance_name
289 6bf273d5 Michael Hanselmann
    self.dest_pnode = dest_pnode
290 6bf273d5 Michael Hanselmann
    self.dest_snode = dest_snode
291 6bf273d5 Michael Hanselmann
    self.dest_iallocator = dest_iallocator
292 6bf273d5 Michael Hanselmann
293 6bf273d5 Michael Hanselmann
    self.error_message = None
294 6bf273d5 Michael Hanselmann
295 6bf273d5 Michael Hanselmann
296 6bf273d5 Michael Hanselmann
class MoveRuntime(object):
297 6bf273d5 Michael Hanselmann
  """Class to keep track of instance move.
298 6bf273d5 Michael Hanselmann
299 6bf273d5 Michael Hanselmann
  """
300 6bf273d5 Michael Hanselmann
  def __init__(self, move):
301 6bf273d5 Michael Hanselmann
    """Initializes this class.
302 6bf273d5 Michael Hanselmann
303 6bf273d5 Michael Hanselmann
    @type move: L{InstanceMove}
304 6bf273d5 Michael Hanselmann
305 6bf273d5 Michael Hanselmann
    """
306 6bf273d5 Michael Hanselmann
    self.move = move
307 6bf273d5 Michael Hanselmann
308 6bf273d5 Michael Hanselmann
    # Thread synchronization
309 6bf273d5 Michael Hanselmann
    self.lock = threading.Lock()
310 6bf273d5 Michael Hanselmann
    self.source_to_dest = threading.Condition(self.lock)
311 6bf273d5 Michael Hanselmann
    self.dest_to_source = threading.Condition(self.lock)
312 6bf273d5 Michael Hanselmann
313 6bf273d5 Michael Hanselmann
    # Source information
314 6bf273d5 Michael Hanselmann
    self.src_error_message = None
315 6bf273d5 Michael Hanselmann
    self.src_expinfo = None
316 6bf273d5 Michael Hanselmann
    self.src_instinfo = None
317 6bf273d5 Michael Hanselmann
318 6bf273d5 Michael Hanselmann
    # Destination information
319 6bf273d5 Michael Hanselmann
    self.dest_error_message = None
320 6bf273d5 Michael Hanselmann
    self.dest_impinfo = None
321 6bf273d5 Michael Hanselmann
322 6bf273d5 Michael Hanselmann
  def HandleErrors(self, prefix, fn, *args):
323 6bf273d5 Michael Hanselmann
    """Wrapper to catch errors and abort threads.
324 6bf273d5 Michael Hanselmann
325 6bf273d5 Michael Hanselmann
    @type prefix: string
326 6bf273d5 Michael Hanselmann
    @param prefix: Variable name prefix ("src" or "dest")
327 6bf273d5 Michael Hanselmann
    @type fn: callable
328 6bf273d5 Michael Hanselmann
    @param fn: Function
329 6bf273d5 Michael Hanselmann
330 6bf273d5 Michael Hanselmann
    """
331 6bf273d5 Michael Hanselmann
    assert prefix in ("dest", "src")
332 6bf273d5 Michael Hanselmann
333 6bf273d5 Michael Hanselmann
    try:
334 6bf273d5 Michael Hanselmann
      # Call inner function
335 6bf273d5 Michael Hanselmann
      fn(*args)
336 6bf273d5 Michael Hanselmann
337 6bf273d5 Michael Hanselmann
      errmsg = None
338 6bf273d5 Michael Hanselmann
    except Abort:
339 6bf273d5 Michael Hanselmann
      errmsg = "Aborted"
340 6bf273d5 Michael Hanselmann
    except Exception, err:
341 6bf273d5 Michael Hanselmann
      logging.exception("Caught unhandled exception")
342 6bf273d5 Michael Hanselmann
      errmsg = str(err)
343 6bf273d5 Michael Hanselmann
344 424f51ec Michael Hanselmann
    setattr(self, "%s_error_message" % prefix, errmsg)
345 424f51ec Michael Hanselmann
346 6bf273d5 Michael Hanselmann
    self.lock.acquire()
347 6bf273d5 Michael Hanselmann
    try:
348 6bf273d5 Michael Hanselmann
      self.source_to_dest.notifyAll()
349 6bf273d5 Michael Hanselmann
      self.dest_to_source.notifyAll()
350 6bf273d5 Michael Hanselmann
    finally:
351 6bf273d5 Michael Hanselmann
      self.lock.release()
352 6bf273d5 Michael Hanselmann
353 6bf273d5 Michael Hanselmann
  def CheckAbort(self):
354 6bf273d5 Michael Hanselmann
    """Check whether thread should be aborted.
355 6bf273d5 Michael Hanselmann
356 6bf273d5 Michael Hanselmann
    @raise Abort: When thread should be aborted
357 6bf273d5 Michael Hanselmann
358 6bf273d5 Michael Hanselmann
    """
359 424f51ec Michael Hanselmann
    if not (self.src_error_message is None and
360 424f51ec Michael Hanselmann
            self.dest_error_message is None):
361 6bf273d5 Michael Hanselmann
      logging.info("Aborting")
362 6bf273d5 Michael Hanselmann
      raise Abort()
363 6bf273d5 Michael Hanselmann
364 6bf273d5 Michael Hanselmann
  def Wait(self, cond, check_fn):
365 6bf273d5 Michael Hanselmann
    """Waits for a condition to become true.
366 6bf273d5 Michael Hanselmann
367 6bf273d5 Michael Hanselmann
    @type cond: threading.Condition
368 6bf273d5 Michael Hanselmann
    @param cond: Threading condition
369 6bf273d5 Michael Hanselmann
    @type check_fn: callable
370 6bf273d5 Michael Hanselmann
    @param check_fn: Function to check whether condition is true
371 6bf273d5 Michael Hanselmann
372 6bf273d5 Michael Hanselmann
    """
373 6bf273d5 Michael Hanselmann
    cond.acquire()
374 6bf273d5 Michael Hanselmann
    try:
375 6bf273d5 Michael Hanselmann
      while check_fn(self):
376 6bf273d5 Michael Hanselmann
        self.CheckAbort()
377 6bf273d5 Michael Hanselmann
        cond.wait()
378 6bf273d5 Michael Hanselmann
    finally:
379 6bf273d5 Michael Hanselmann
      cond.release()
380 6bf273d5 Michael Hanselmann
381 6bf273d5 Michael Hanselmann
  def PollJob(self, cl, job_id, remote_import_fn=None):
382 6bf273d5 Michael Hanselmann
    """Wrapper for polling a job.
383 6bf273d5 Michael Hanselmann
384 6bf273d5 Michael Hanselmann
    @type cl: L{rapi.client.GanetiRapiClient}
385 6bf273d5 Michael Hanselmann
    @param cl: RAPI client
386 6bf273d5 Michael Hanselmann
    @type job_id: string
387 6bf273d5 Michael Hanselmann
    @param job_id: Job ID
388 6bf273d5 Michael Hanselmann
    @type remote_import_fn: callable or None
389 6bf273d5 Michael Hanselmann
    @param remote_import_fn: Callback for reporting received remote import
390 6bf273d5 Michael Hanselmann
                             information
391 6bf273d5 Michael Hanselmann
392 6bf273d5 Michael Hanselmann
    """
393 6bf273d5 Michael Hanselmann
    return rapi.client_utils.PollJob(cl, job_id,
394 6bf273d5 Michael Hanselmann
                                     MoveJobPollReportCb(self.CheckAbort,
395 6bf273d5 Michael Hanselmann
                                                         remote_import_fn))
396 6bf273d5 Michael Hanselmann
397 6bf273d5 Michael Hanselmann
398 6bf273d5 Michael Hanselmann
class MoveDestExecutor(object):
399 6bf273d5 Michael Hanselmann
  def __init__(self, dest_client, mrt):
400 6bf273d5 Michael Hanselmann
    """Destination side of an instance move.
401 6bf273d5 Michael Hanselmann
402 6bf273d5 Michael Hanselmann
    @type dest_client: L{rapi.client.GanetiRapiClient}
403 6bf273d5 Michael Hanselmann
    @param dest_client: RAPI client
404 6bf273d5 Michael Hanselmann
    @type mrt: L{MoveRuntime}
405 6bf273d5 Michael Hanselmann
    @param mrt: Instance move runtime information
406 6bf273d5 Michael Hanselmann
407 6bf273d5 Michael Hanselmann
    """
408 6bf273d5 Michael Hanselmann
    logging.debug("Waiting for instance information to become available")
409 6bf273d5 Michael Hanselmann
    mrt.Wait(mrt.source_to_dest,
410 6bf273d5 Michael Hanselmann
             lambda mrt: mrt.src_instinfo is None or mrt.src_expinfo is None)
411 6bf273d5 Michael Hanselmann
412 6bf273d5 Michael Hanselmann
    logging.info("Creating instance %s in remote-import mode",
413 6bf273d5 Michael Hanselmann
                 mrt.move.dest_instance_name)
414 6bf273d5 Michael Hanselmann
    job_id = self._CreateInstance(dest_client, mrt.move.dest_instance_name,
415 6bf273d5 Michael Hanselmann
                                  mrt.move.dest_pnode, mrt.move.dest_snode,
416 6bf273d5 Michael Hanselmann
                                  mrt.move.dest_iallocator,
417 6bf273d5 Michael Hanselmann
                                  mrt.src_instinfo, mrt.src_expinfo)
418 6bf273d5 Michael Hanselmann
    mrt.PollJob(dest_client, job_id,
419 6bf273d5 Michael Hanselmann
                remote_import_fn=compat.partial(self._SetImportInfo, mrt))
420 6bf273d5 Michael Hanselmann
421 6bf273d5 Michael Hanselmann
    logging.info("Import successful")
422 6bf273d5 Michael Hanselmann
423 6bf273d5 Michael Hanselmann
  @staticmethod
424 6bf273d5 Michael Hanselmann
  def _SetImportInfo(mrt, impinfo):
425 6bf273d5 Michael Hanselmann
    """Sets the remote import information and notifies source thread.
426 6bf273d5 Michael Hanselmann
427 6bf273d5 Michael Hanselmann
    @type mrt: L{MoveRuntime}
428 6bf273d5 Michael Hanselmann
    @param mrt: Instance move runtime information
429 6bf273d5 Michael Hanselmann
    @param impinfo: Remote import information
430 6bf273d5 Michael Hanselmann
431 6bf273d5 Michael Hanselmann
    """
432 6bf273d5 Michael Hanselmann
    mrt.dest_to_source.acquire()
433 6bf273d5 Michael Hanselmann
    try:
434 6bf273d5 Michael Hanselmann
      mrt.dest_impinfo = impinfo
435 6bf273d5 Michael Hanselmann
      mrt.dest_to_source.notifyAll()
436 6bf273d5 Michael Hanselmann
    finally:
437 6bf273d5 Michael Hanselmann
      mrt.dest_to_source.release()
438 6bf273d5 Michael Hanselmann
439 6bf273d5 Michael Hanselmann
  @staticmethod
440 6bf273d5 Michael Hanselmann
  def _CreateInstance(cl, name, snode, pnode, iallocator, instance, expinfo):
441 6bf273d5 Michael Hanselmann
    """Starts the instance creation in remote import mode.
442 6bf273d5 Michael Hanselmann
443 6bf273d5 Michael Hanselmann
    @type cl: L{rapi.client.GanetiRapiClient}
444 6bf273d5 Michael Hanselmann
    @param cl: RAPI client
445 6bf273d5 Michael Hanselmann
    @type name: string
446 6bf273d5 Michael Hanselmann
    @param name: Instance name
447 6bf273d5 Michael Hanselmann
    @type pnode: string or None
448 6bf273d5 Michael Hanselmann
    @param pnode: Name of primary node on destination cluster
449 6bf273d5 Michael Hanselmann
    @type snode: string or None
450 6bf273d5 Michael Hanselmann
    @param snode: Name of secondary node on destination cluster
451 6bf273d5 Michael Hanselmann
    @type iallocator: string or None
452 6bf273d5 Michael Hanselmann
    @param iallocator: Name of iallocator to use
453 6bf273d5 Michael Hanselmann
    @type instance: dict
454 6bf273d5 Michael Hanselmann
    @param instance: Instance details from source cluster
455 6bf273d5 Michael Hanselmann
    @type expinfo: dict
456 6bf273d5 Michael Hanselmann
    @param expinfo: Prepared export information from source cluster
457 6bf273d5 Michael Hanselmann
    @return: Job ID
458 6bf273d5 Michael Hanselmann
459 6bf273d5 Michael Hanselmann
    """
460 6bf273d5 Michael Hanselmann
    disk_template = instance["disk_template"]
461 6bf273d5 Michael Hanselmann
462 6bf273d5 Michael Hanselmann
    disks = [{
463 40edb0cc Michael Hanselmann
      constants.IDISK_SIZE: i["size"],
464 40edb0cc Michael Hanselmann
      constants.IDISK_MODE: i["mode"],
465 6bf273d5 Michael Hanselmann
      } for i in instance["disks"]]
466 6bf273d5 Michael Hanselmann
467 6bf273d5 Michael Hanselmann
    nics = [{
468 40edb0cc Michael Hanselmann
      constants.INIC_IP: ip,
469 40edb0cc Michael Hanselmann
      constants.INIC_MAC: mac,
470 40edb0cc Michael Hanselmann
      constants.INIC_MODE: mode,
471 40edb0cc Michael Hanselmann
      constants.INIC_LINK: link,
472 6bf273d5 Michael Hanselmann
      } for ip, mac, mode, link in instance["nics"]]
473 6bf273d5 Michael Hanselmann
474 6bf273d5 Michael Hanselmann
    # TODO: Should this be the actual up/down status? (run_state)
475 6bf273d5 Michael Hanselmann
    start = (instance["config_state"] == "up")
476 6bf273d5 Michael Hanselmann
477 6bf273d5 Michael Hanselmann
    assert len(disks) == len(instance["disks"])
478 6bf273d5 Michael Hanselmann
    assert len(nics) == len(instance["nics"])
479 6bf273d5 Michael Hanselmann
480 6bf273d5 Michael Hanselmann
    return cl.CreateInstance(constants.INSTANCE_REMOTE_IMPORT,
481 6bf273d5 Michael Hanselmann
                             name, disk_template, disks, nics,
482 6bf273d5 Michael Hanselmann
                             os=instance["os"],
483 6bf273d5 Michael Hanselmann
                             pnode=pnode,
484 6bf273d5 Michael Hanselmann
                             snode=snode,
485 6bf273d5 Michael Hanselmann
                             start=start,
486 6bf273d5 Michael Hanselmann
                             ip_check=False,
487 6bf273d5 Michael Hanselmann
                             iallocator=iallocator,
488 6bf273d5 Michael Hanselmann
                             hypervisor=instance["hypervisor"],
489 6bf273d5 Michael Hanselmann
                             source_handshake=expinfo["handshake"],
490 6bf273d5 Michael Hanselmann
                             source_x509_ca=expinfo["x509_ca"],
491 6bf273d5 Michael Hanselmann
                             source_instance_name=instance["name"],
492 6bf273d5 Michael Hanselmann
                             beparams=instance["be_instance"],
493 a59b0dc4 Michael Hanselmann
                             hvparams=instance["hv_instance"],
494 a59b0dc4 Michael Hanselmann
                             osparams=instance["os_instance"])
495 6bf273d5 Michael Hanselmann
496 6bf273d5 Michael Hanselmann
497 6bf273d5 Michael Hanselmann
class MoveSourceExecutor(object):
498 6bf273d5 Michael Hanselmann
  def __init__(self, src_client, mrt):
499 6bf273d5 Michael Hanselmann
    """Source side of an instance move.
500 6bf273d5 Michael Hanselmann
501 6bf273d5 Michael Hanselmann
    @type src_client: L{rapi.client.GanetiRapiClient}
502 6bf273d5 Michael Hanselmann
    @param src_client: RAPI client
503 6bf273d5 Michael Hanselmann
    @type mrt: L{MoveRuntime}
504 6bf273d5 Michael Hanselmann
    @param mrt: Instance move runtime information
505 6bf273d5 Michael Hanselmann
506 6bf273d5 Michael Hanselmann
    """
507 6bf273d5 Michael Hanselmann
    logging.info("Checking whether instance exists")
508 6bf273d5 Michael Hanselmann
    self._CheckInstance(src_client, mrt.move.src_instance_name)
509 6bf273d5 Michael Hanselmann
510 6bf273d5 Michael Hanselmann
    logging.info("Retrieving instance information from source cluster")
511 6bf273d5 Michael Hanselmann
    instinfo = self._GetInstanceInfo(src_client, mrt.PollJob,
512 6bf273d5 Michael Hanselmann
                                     mrt.move.src_instance_name)
513 6bf273d5 Michael Hanselmann
514 6bf273d5 Michael Hanselmann
    logging.info("Preparing export on source cluster")
515 6bf273d5 Michael Hanselmann
    expinfo = self._PrepareExport(src_client, mrt.PollJob,
516 6bf273d5 Michael Hanselmann
                                  mrt.move.src_instance_name)
517 6bf273d5 Michael Hanselmann
    assert "handshake" in expinfo
518 6bf273d5 Michael Hanselmann
    assert "x509_key_name" in expinfo
519 6bf273d5 Michael Hanselmann
    assert "x509_ca" in expinfo
520 6bf273d5 Michael Hanselmann
521 6bf273d5 Michael Hanselmann
    # Hand information to destination thread
522 6bf273d5 Michael Hanselmann
    mrt.source_to_dest.acquire()
523 6bf273d5 Michael Hanselmann
    try:
524 6bf273d5 Michael Hanselmann
      mrt.src_instinfo = instinfo
525 6bf273d5 Michael Hanselmann
      mrt.src_expinfo = expinfo
526 6bf273d5 Michael Hanselmann
      mrt.source_to_dest.notifyAll()
527 6bf273d5 Michael Hanselmann
    finally:
528 6bf273d5 Michael Hanselmann
      mrt.source_to_dest.release()
529 6bf273d5 Michael Hanselmann
530 6bf273d5 Michael Hanselmann
    logging.info("Waiting for destination information to become available")
531 6bf273d5 Michael Hanselmann
    mrt.Wait(mrt.dest_to_source, lambda mrt: mrt.dest_impinfo is None)
532 6bf273d5 Michael Hanselmann
533 6bf273d5 Michael Hanselmann
    logging.info("Starting remote export on source cluster")
534 6bf273d5 Michael Hanselmann
    self._ExportInstance(src_client, mrt.PollJob, mrt.move.src_instance_name,
535 6bf273d5 Michael Hanselmann
                         expinfo["x509_key_name"], mrt.dest_impinfo)
536 6bf273d5 Michael Hanselmann
537 6bf273d5 Michael Hanselmann
    logging.info("Export successful")
538 6bf273d5 Michael Hanselmann
539 6bf273d5 Michael Hanselmann
  @staticmethod
540 6bf273d5 Michael Hanselmann
  def _CheckInstance(cl, name):
541 6bf273d5 Michael Hanselmann
    """Checks whether the instance exists on the source cluster.
542 6bf273d5 Michael Hanselmann
543 6bf273d5 Michael Hanselmann
    @type cl: L{rapi.client.GanetiRapiClient}
544 6bf273d5 Michael Hanselmann
    @param cl: RAPI client
545 6bf273d5 Michael Hanselmann
    @type name: string
546 6bf273d5 Michael Hanselmann
    @param name: Instance name
547 6bf273d5 Michael Hanselmann
548 6bf273d5 Michael Hanselmann
    """
549 6bf273d5 Michael Hanselmann
    try:
550 6bf273d5 Michael Hanselmann
      cl.GetInstance(name)
551 6bf273d5 Michael Hanselmann
    except rapi.client.GanetiApiError, err:
552 6bf273d5 Michael Hanselmann
      if err.code == rapi.client.HTTP_NOT_FOUND:
553 6bf273d5 Michael Hanselmann
        raise Error("Instance %s not found (%s)" % (name, str(err)))
554 6bf273d5 Michael Hanselmann
      raise
555 6bf273d5 Michael Hanselmann
556 6bf273d5 Michael Hanselmann
  @staticmethod
557 6bf273d5 Michael Hanselmann
  def _GetInstanceInfo(cl, poll_job_fn, name):
558 6bf273d5 Michael Hanselmann
    """Retrieves detailed instance information from source cluster.
559 6bf273d5 Michael Hanselmann
560 6bf273d5 Michael Hanselmann
    @type cl: L{rapi.client.GanetiRapiClient}
561 6bf273d5 Michael Hanselmann
    @param cl: RAPI client
562 6bf273d5 Michael Hanselmann
    @type poll_job_fn: callable
563 6bf273d5 Michael Hanselmann
    @param poll_job_fn: Function to poll for job result
564 6bf273d5 Michael Hanselmann
    @type name: string
565 6bf273d5 Michael Hanselmann
    @param name: Instance name
566 6bf273d5 Michael Hanselmann
567 6bf273d5 Michael Hanselmann
    """
568 6bf273d5 Michael Hanselmann
    job_id = cl.GetInstanceInfo(name, static=True)
569 6bf273d5 Michael Hanselmann
    result = poll_job_fn(cl, job_id)
570 6bf273d5 Michael Hanselmann
    assert len(result[0].keys()) == 1
571 6bf273d5 Michael Hanselmann
    return result[0][result[0].keys()[0]]
572 6bf273d5 Michael Hanselmann
573 6bf273d5 Michael Hanselmann
  @staticmethod
574 6bf273d5 Michael Hanselmann
  def _PrepareExport(cl, poll_job_fn, name):
575 6bf273d5 Michael Hanselmann
    """Prepares export on source cluster.
576 6bf273d5 Michael Hanselmann
577 6bf273d5 Michael Hanselmann
    @type cl: L{rapi.client.GanetiRapiClient}
578 6bf273d5 Michael Hanselmann
    @param cl: RAPI client
579 6bf273d5 Michael Hanselmann
    @type poll_job_fn: callable
580 6bf273d5 Michael Hanselmann
    @param poll_job_fn: Function to poll for job result
581 6bf273d5 Michael Hanselmann
    @type name: string
582 6bf273d5 Michael Hanselmann
    @param name: Instance name
583 6bf273d5 Michael Hanselmann
584 6bf273d5 Michael Hanselmann
    """
585 6bf273d5 Michael Hanselmann
    job_id = cl.PrepareExport(name, constants.EXPORT_MODE_REMOTE)
586 6bf273d5 Michael Hanselmann
    return poll_job_fn(cl, job_id)[0]
587 6bf273d5 Michael Hanselmann
588 6bf273d5 Michael Hanselmann
  @staticmethod
589 6bf273d5 Michael Hanselmann
  def _ExportInstance(cl, poll_job_fn, name, x509_key_name, impinfo):
590 6bf273d5 Michael Hanselmann
    """Exports instance from source cluster.
591 6bf273d5 Michael Hanselmann
592 6bf273d5 Michael Hanselmann
    @type cl: L{rapi.client.GanetiRapiClient}
593 6bf273d5 Michael Hanselmann
    @param cl: RAPI client
594 6bf273d5 Michael Hanselmann
    @type poll_job_fn: callable
595 6bf273d5 Michael Hanselmann
    @param poll_job_fn: Function to poll for job result
596 6bf273d5 Michael Hanselmann
    @type name: string
597 6bf273d5 Michael Hanselmann
    @param name: Instance name
598 6bf273d5 Michael Hanselmann
    @param x509_key_name: Source X509 key
599 6bf273d5 Michael Hanselmann
    @param impinfo: Import information from destination cluster
600 6bf273d5 Michael Hanselmann
601 6bf273d5 Michael Hanselmann
    """
602 6bf273d5 Michael Hanselmann
    job_id = cl.ExportInstance(name, constants.EXPORT_MODE_REMOTE,
603 6bf273d5 Michael Hanselmann
                               impinfo["disks"], shutdown=True,
604 6bf273d5 Michael Hanselmann
                               remove_instance=True,
605 6bf273d5 Michael Hanselmann
                               x509_key_name=x509_key_name,
606 6bf273d5 Michael Hanselmann
                               destination_x509_ca=impinfo["x509_ca"])
607 6bf273d5 Michael Hanselmann
    (fin_resu, dresults) = poll_job_fn(cl, job_id)[0]
608 6bf273d5 Michael Hanselmann
609 6bf273d5 Michael Hanselmann
    if not (fin_resu and compat.all(dresults)):
610 6bf273d5 Michael Hanselmann
      raise Error("Export failed for disks %s" %
611 6bf273d5 Michael Hanselmann
                  utils.CommaJoin(str(idx) for idx, result
612 6bf273d5 Michael Hanselmann
                                  in enumerate(dresults) if not result))
613 6bf273d5 Michael Hanselmann
614 6bf273d5 Michael Hanselmann
615 6bf273d5 Michael Hanselmann
class MoveSourceWorker(workerpool.BaseWorker):
616 6bf273d5 Michael Hanselmann
  def RunTask(self, rapi_factory, move): # pylint: disable-msg=W0221
617 6bf273d5 Michael Hanselmann
    """Executes an instance move.
618 6bf273d5 Michael Hanselmann
619 6bf273d5 Michael Hanselmann
    @type rapi_factory: L{RapiClientFactory}
620 6bf273d5 Michael Hanselmann
    @param rapi_factory: RAPI client factory
621 6bf273d5 Michael Hanselmann
    @type move: L{InstanceMove}
622 6bf273d5 Michael Hanselmann
    @param move: Instance move information
623 6bf273d5 Michael Hanselmann
624 6bf273d5 Michael Hanselmann
    """
625 6bf273d5 Michael Hanselmann
    try:
626 6bf273d5 Michael Hanselmann
      logging.info("Preparing to move %s from cluster %s to %s as %s",
627 6bf273d5 Michael Hanselmann
                   move.src_instance_name, rapi_factory.src_cluster_name,
628 6bf273d5 Michael Hanselmann
                   rapi_factory.dest_cluster_name, move.dest_instance_name)
629 6bf273d5 Michael Hanselmann
630 6bf273d5 Michael Hanselmann
      mrt = MoveRuntime(move)
631 6bf273d5 Michael Hanselmann
632 6bf273d5 Michael Hanselmann
      logging.debug("Starting destination thread")
633 6bf273d5 Michael Hanselmann
      dest_thread = threading.Thread(name="DestFor%s" % self.getName(),
634 6bf273d5 Michael Hanselmann
                                     target=mrt.HandleErrors,
635 6bf273d5 Michael Hanselmann
                                     args=("dest", MoveDestExecutor,
636 6bf273d5 Michael Hanselmann
                                           rapi_factory.GetDestClient(),
637 6bf273d5 Michael Hanselmann
                                           mrt, ))
638 6bf273d5 Michael Hanselmann
      dest_thread.start()
639 6bf273d5 Michael Hanselmann
      try:
640 6bf273d5 Michael Hanselmann
        mrt.HandleErrors("src", MoveSourceExecutor,
641 6bf273d5 Michael Hanselmann
                         rapi_factory.GetSourceClient(), mrt)
642 6bf273d5 Michael Hanselmann
      finally:
643 6bf273d5 Michael Hanselmann
        dest_thread.join()
644 6bf273d5 Michael Hanselmann
645 6bf273d5 Michael Hanselmann
      if mrt.src_error_message or mrt.dest_error_message:
646 6bf273d5 Michael Hanselmann
        move.error_message = ("Source error: %s, destination error: %s" %
647 6bf273d5 Michael Hanselmann
                              (mrt.src_error_message, mrt.dest_error_message))
648 6bf273d5 Michael Hanselmann
      else:
649 6bf273d5 Michael Hanselmann
        move.error_message = None
650 6bf273d5 Michael Hanselmann
    except Exception, err: # pylint: disable-msg=W0703
651 6bf273d5 Michael Hanselmann
      logging.exception("Caught unhandled exception")
652 6bf273d5 Michael Hanselmann
      move.error_message = str(err)
653 6bf273d5 Michael Hanselmann
654 6bf273d5 Michael Hanselmann
655 6bf273d5 Michael Hanselmann
def CheckRapiSetup(rapi_factory):
656 6bf273d5 Michael Hanselmann
  """Checks the RAPI setup by retrieving the version.
657 6bf273d5 Michael Hanselmann
658 6bf273d5 Michael Hanselmann
  @type rapi_factory: L{RapiClientFactory}
659 6bf273d5 Michael Hanselmann
  @param rapi_factory: RAPI client factory
660 6bf273d5 Michael Hanselmann
661 6bf273d5 Michael Hanselmann
  """
662 6bf273d5 Michael Hanselmann
  src_client = rapi_factory.GetSourceClient()
663 6bf273d5 Michael Hanselmann
  logging.info("Connecting to source RAPI server")
664 6bf273d5 Michael Hanselmann
  logging.info("Source cluster RAPI version: %s", src_client.GetVersion())
665 6bf273d5 Michael Hanselmann
666 6bf273d5 Michael Hanselmann
  dest_client = rapi_factory.GetDestClient()
667 6bf273d5 Michael Hanselmann
  logging.info("Connecting to destination RAPI server")
668 6bf273d5 Michael Hanselmann
  logging.info("Destination cluster RAPI version: %s", dest_client.GetVersion())
669 6bf273d5 Michael Hanselmann
670 6bf273d5 Michael Hanselmann
671 6bf273d5 Michael Hanselmann
def SetupLogging(options):
672 6bf273d5 Michael Hanselmann
  """Setting up logging infrastructure.
673 6bf273d5 Michael Hanselmann
674 6bf273d5 Michael Hanselmann
  @param options: Parsed command line options
675 6bf273d5 Michael Hanselmann
676 6bf273d5 Michael Hanselmann
  """
677 6bf273d5 Michael Hanselmann
  fmt = "%(asctime)s: %(threadName)s "
678 6bf273d5 Michael Hanselmann
  if options.debug or options.verbose:
679 6bf273d5 Michael Hanselmann
    fmt += "%(levelname)s "
680 6bf273d5 Michael Hanselmann
  fmt += "%(message)s"
681 6bf273d5 Michael Hanselmann
682 6bf273d5 Michael Hanselmann
  formatter = logging.Formatter(fmt)
683 6bf273d5 Michael Hanselmann
684 6bf273d5 Michael Hanselmann
  stderr_handler = logging.StreamHandler()
685 6bf273d5 Michael Hanselmann
  stderr_handler.setFormatter(formatter)
686 6bf273d5 Michael Hanselmann
  if options.debug:
687 6bf273d5 Michael Hanselmann
    stderr_handler.setLevel(logging.NOTSET)
688 6bf273d5 Michael Hanselmann
  elif options.verbose:
689 6bf273d5 Michael Hanselmann
    stderr_handler.setLevel(logging.INFO)
690 6bf273d5 Michael Hanselmann
  else:
691 6bf273d5 Michael Hanselmann
    stderr_handler.setLevel(logging.ERROR)
692 6bf273d5 Michael Hanselmann
693 6bf273d5 Michael Hanselmann
  root_logger = logging.getLogger("")
694 6bf273d5 Michael Hanselmann
  root_logger.setLevel(logging.NOTSET)
695 6bf273d5 Michael Hanselmann
  root_logger.addHandler(stderr_handler)
696 6bf273d5 Michael Hanselmann
697 6bf273d5 Michael Hanselmann
698 6bf273d5 Michael Hanselmann
def ParseOptions():
699 6bf273d5 Michael Hanselmann
  """Parses options passed to program.
700 6bf273d5 Michael Hanselmann
701 6bf273d5 Michael Hanselmann
  """
702 6bf273d5 Michael Hanselmann
  program = os.path.basename(sys.argv[0])
703 6bf273d5 Michael Hanselmann
704 6bf273d5 Michael Hanselmann
  parser = optparse.OptionParser(usage=("%prog [--debug|--verbose]"
705 6bf273d5 Michael Hanselmann
                                        " <source-cluster> <dest-cluster>"
706 6bf273d5 Michael Hanselmann
                                        " <instance...>"),
707 6bf273d5 Michael Hanselmann
                                 prog=program)
708 6bf273d5 Michael Hanselmann
  parser.add_option(cli.DEBUG_OPT)
709 6bf273d5 Michael Hanselmann
  parser.add_option(cli.VERBOSE_OPT)
710 6bf273d5 Michael Hanselmann
  parser.add_option(cli.IALLOCATOR_OPT)
711 6bf273d5 Michael Hanselmann
  parser.add_option(SRC_RAPI_PORT_OPT)
712 6bf273d5 Michael Hanselmann
  parser.add_option(SRC_CA_FILE_OPT)
713 6bf273d5 Michael Hanselmann
  parser.add_option(SRC_USERNAME_OPT)
714 6bf273d5 Michael Hanselmann
  parser.add_option(SRC_PASSWORD_FILE_OPT)
715 6bf273d5 Michael Hanselmann
  parser.add_option(DEST_RAPI_PORT_OPT)
716 6bf273d5 Michael Hanselmann
  parser.add_option(DEST_CA_FILE_OPT)
717 6bf273d5 Michael Hanselmann
  parser.add_option(DEST_USERNAME_OPT)
718 6bf273d5 Michael Hanselmann
  parser.add_option(DEST_PASSWORD_FILE_OPT)
719 6bf273d5 Michael Hanselmann
  parser.add_option(DEST_INSTANCE_NAME_OPT)
720 6bf273d5 Michael Hanselmann
  parser.add_option(DEST_PRIMARY_NODE_OPT)
721 6bf273d5 Michael Hanselmann
  parser.add_option(DEST_SECONDARY_NODE_OPT)
722 6bf273d5 Michael Hanselmann
  parser.add_option(PARALLEL_OPT)
723 6bf273d5 Michael Hanselmann
724 6bf273d5 Michael Hanselmann
  (options, args) = parser.parse_args()
725 6bf273d5 Michael Hanselmann
726 6bf273d5 Michael Hanselmann
  return (parser, options, args)
727 6bf273d5 Michael Hanselmann
728 6bf273d5 Michael Hanselmann
729 6bf273d5 Michael Hanselmann
def CheckOptions(parser, options, args):
730 6bf273d5 Michael Hanselmann
  """Checks options and arguments for validity.
731 6bf273d5 Michael Hanselmann
732 6bf273d5 Michael Hanselmann
  """
733 6bf273d5 Michael Hanselmann
  if len(args) < 3:
734 6bf273d5 Michael Hanselmann
    parser.error("Not enough arguments")
735 6bf273d5 Michael Hanselmann
736 6bf273d5 Michael Hanselmann
  src_cluster_name = args.pop(0)
737 6bf273d5 Michael Hanselmann
  dest_cluster_name = args.pop(0)
738 6bf273d5 Michael Hanselmann
  instance_names = args
739 6bf273d5 Michael Hanselmann
740 6bf273d5 Michael Hanselmann
  assert len(instance_names) > 0
741 6bf273d5 Michael Hanselmann
742 6bf273d5 Michael Hanselmann
  # TODO: Remove once using system default paths for SSL certificate
743 6bf273d5 Michael Hanselmann
  # verification is implemented
744 6bf273d5 Michael Hanselmann
  if not options.src_ca_file:
745 6bf273d5 Michael Hanselmann
    parser.error("Missing source cluster CA file")
746 6bf273d5 Michael Hanselmann
747 6bf273d5 Michael Hanselmann
  if options.parallel < 1:
748 6bf273d5 Michael Hanselmann
    parser.error("Number of simultaneous moves must be >= 1")
749 6bf273d5 Michael Hanselmann
750 6bf273d5 Michael Hanselmann
  if not (bool(options.iallocator) ^
751 6bf273d5 Michael Hanselmann
          bool(options.dest_primary_node or options.dest_secondary_node)):
752 6bf273d5 Michael Hanselmann
    parser.error("Destination node and iallocator options exclude each other")
753 6bf273d5 Michael Hanselmann
754 6bf273d5 Michael Hanselmann
  if len(instance_names) == 1:
755 6bf273d5 Michael Hanselmann
    # Moving one instance only
756 6bf273d5 Michael Hanselmann
    if not (options.iallocator or
757 6bf273d5 Michael Hanselmann
            options.dest_primary_node or
758 6bf273d5 Michael Hanselmann
            options.dest_secondary_node):
759 6bf273d5 Michael Hanselmann
      parser.error("An iallocator or the destination node is required")
760 6bf273d5 Michael Hanselmann
  else:
761 6bf273d5 Michael Hanselmann
    # Moving more than one instance
762 6bf273d5 Michael Hanselmann
    if (options.dest_instance_name or options.dest_primary_node or
763 6bf273d5 Michael Hanselmann
        options.dest_secondary_node):
764 6bf273d5 Michael Hanselmann
      parser.error("The options --dest-instance-name, --dest-primary-node and"
765 6bf273d5 Michael Hanselmann
                   " --dest-secondary-node can only be used when moving exactly"
766 6bf273d5 Michael Hanselmann
                   " one instance")
767 6bf273d5 Michael Hanselmann
768 6bf273d5 Michael Hanselmann
    if not options.iallocator:
769 6bf273d5 Michael Hanselmann
      parser.error("An iallocator must be specified for moving more than one"
770 6bf273d5 Michael Hanselmann
                   " instance")
771 6bf273d5 Michael Hanselmann
772 6bf273d5 Michael Hanselmann
  return (src_cluster_name, dest_cluster_name, instance_names)
773 6bf273d5 Michael Hanselmann
774 6bf273d5 Michael Hanselmann
775 2a7c3583 Michael Hanselmann
@rapi.client.UsesRapiClient
776 6bf273d5 Michael Hanselmann
def main():
777 6bf273d5 Michael Hanselmann
  """Main routine.
778 6bf273d5 Michael Hanselmann
779 6bf273d5 Michael Hanselmann
  """
780 6bf273d5 Michael Hanselmann
  (parser, options, args) = ParseOptions()
781 6bf273d5 Michael Hanselmann
782 6bf273d5 Michael Hanselmann
  SetupLogging(options)
783 6bf273d5 Michael Hanselmann
784 6bf273d5 Michael Hanselmann
  (src_cluster_name, dest_cluster_name, instance_names) = \
785 6bf273d5 Michael Hanselmann
    CheckOptions(parser, options, args)
786 6bf273d5 Michael Hanselmann
787 6bf273d5 Michael Hanselmann
  logging.info("Source cluster: %s", src_cluster_name)
788 6bf273d5 Michael Hanselmann
  logging.info("Destination cluster: %s", dest_cluster_name)
789 6bf273d5 Michael Hanselmann
  logging.info("Instances to be moved: %s", utils.CommaJoin(instance_names))
790 6bf273d5 Michael Hanselmann
791 6bf273d5 Michael Hanselmann
  rapi_factory = RapiClientFactory(options, src_cluster_name, dest_cluster_name)
792 6bf273d5 Michael Hanselmann
793 6bf273d5 Michael Hanselmann
  CheckRapiSetup(rapi_factory)
794 6bf273d5 Michael Hanselmann
795 6bf273d5 Michael Hanselmann
  assert (len(instance_names) == 1 or
796 6bf273d5 Michael Hanselmann
          not (options.dest_primary_node or options.dest_secondary_node))
797 6bf273d5 Michael Hanselmann
  assert len(instance_names) == 1 or options.iallocator
798 6bf273d5 Michael Hanselmann
  assert (len(instance_names) > 1 or options.iallocator or
799 6bf273d5 Michael Hanselmann
          options.dest_primary_node or options.dest_secondary_node)
800 6bf273d5 Michael Hanselmann
801 6bf273d5 Michael Hanselmann
  # Prepare list of instance moves
802 6bf273d5 Michael Hanselmann
  moves = []
803 6bf273d5 Michael Hanselmann
  for src_instance_name in instance_names:
804 6bf273d5 Michael Hanselmann
    if options.dest_instance_name:
805 6bf273d5 Michael Hanselmann
      assert len(instance_names) == 1
806 6bf273d5 Michael Hanselmann
      # Rename instance
807 6bf273d5 Michael Hanselmann
      dest_instance_name = options.dest_instance_name
808 6bf273d5 Michael Hanselmann
    else:
809 6bf273d5 Michael Hanselmann
      dest_instance_name = src_instance_name
810 6bf273d5 Michael Hanselmann
811 6bf273d5 Michael Hanselmann
    moves.append(InstanceMove(src_instance_name, dest_instance_name,
812 6bf273d5 Michael Hanselmann
                              options.dest_primary_node,
813 6bf273d5 Michael Hanselmann
                              options.dest_secondary_node,
814 6bf273d5 Michael Hanselmann
                              options.iallocator))
815 6bf273d5 Michael Hanselmann
816 6bf273d5 Michael Hanselmann
  assert len(moves) == len(instance_names)
817 6bf273d5 Michael Hanselmann
818 6bf273d5 Michael Hanselmann
  # Start workerpool
819 6bf273d5 Michael Hanselmann
  wp = workerpool.WorkerPool("Move", options.parallel, MoveSourceWorker)
820 6bf273d5 Michael Hanselmann
  try:
821 6bf273d5 Michael Hanselmann
    # Add instance moves to workerpool
822 6bf273d5 Michael Hanselmann
    for move in moves:
823 b2e8a4d9 Michael Hanselmann
      wp.AddTask((rapi_factory, move))
824 6bf273d5 Michael Hanselmann
825 6bf273d5 Michael Hanselmann
    # Wait for all moves to finish
826 6bf273d5 Michael Hanselmann
    wp.Quiesce()
827 6bf273d5 Michael Hanselmann
828 6bf273d5 Michael Hanselmann
  finally:
829 6bf273d5 Michael Hanselmann
    wp.TerminateWorkers()
830 6bf273d5 Michael Hanselmann
831 6bf273d5 Michael Hanselmann
  # There should be no threads running at this point, hence not using locks
832 6bf273d5 Michael Hanselmann
  # anymore
833 6bf273d5 Michael Hanselmann
834 6bf273d5 Michael Hanselmann
  logging.info("Instance move results:")
835 6bf273d5 Michael Hanselmann
836 6bf273d5 Michael Hanselmann
  for move in moves:
837 6bf273d5 Michael Hanselmann
    if move.dest_instance_name == move.src_instance_name:
838 6bf273d5 Michael Hanselmann
      name = move.src_instance_name
839 6bf273d5 Michael Hanselmann
    else:
840 6bf273d5 Michael Hanselmann
      name = "%s as %s" % (move.src_instance_name, move.dest_instance_name)
841 6bf273d5 Michael Hanselmann
842 424f51ec Michael Hanselmann
    if move.error_message:
843 6bf273d5 Michael Hanselmann
      msg = "Failed (%s)" % move.error_message
844 424f51ec Michael Hanselmann
    else:
845 424f51ec Michael Hanselmann
      msg = "Success"
846 6bf273d5 Michael Hanselmann
847 6bf273d5 Michael Hanselmann
    logging.info("%s: %s", name, msg)
848 6bf273d5 Michael Hanselmann
849 424f51ec Michael Hanselmann
  if compat.any(move.error_message for move in moves):
850 424f51ec Michael Hanselmann
    sys.exit(constants.EXIT_FAILURE)
851 6bf273d5 Michael Hanselmann
852 424f51ec Michael Hanselmann
  sys.exit(constants.EXIT_SUCCESS)
853 6bf273d5 Michael Hanselmann
854 6bf273d5 Michael Hanselmann
855 6bf273d5 Michael Hanselmann
if __name__ == "__main__":
856 6bf273d5 Michael Hanselmann
  main()