Statistics
| Branch: | Tag: | Revision:

root / tools / move-instance @ fcad7225

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