Statistics
| Branch: | Tag: | Revision:

root / tools / move-instance @ 1a732a74

History | View | Annotate | Download (30.1 kB)

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