Statistics
| Branch: | Tag: | Revision:

root / tools / move-instance @ f7f03738

History | View | Annotate | Download (29.8 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 6bf273d5 Michael Hanselmann
      } for i in instance["disks"]]
493 6bf273d5 Michael Hanselmann
494 6bf273d5 Michael Hanselmann
    nics = [{
495 40edb0cc Michael Hanselmann
      constants.INIC_IP: ip,
496 40edb0cc Michael Hanselmann
      constants.INIC_MAC: mac,
497 40edb0cc Michael Hanselmann
      constants.INIC_MODE: mode,
498 40edb0cc Michael Hanselmann
      constants.INIC_LINK: link,
499 f500d80b Iustin Pop
      constants.INIC_NETWORK: network
500 f500d80b Iustin Pop
      } for ip, mac, mode, link, network, _ in instance["nics"]]
501 6bf273d5 Michael Hanselmann
502 a111ebde Michael Hanselmann
    if len(override_nics) > len(nics):
503 a111ebde Michael Hanselmann
      raise Error("Can not create new NICs")
504 a111ebde Michael Hanselmann
505 a111ebde Michael Hanselmann
    if override_nics:
506 a111ebde Michael Hanselmann
      assert len(override_nics) <= len(nics)
507 a111ebde Michael Hanselmann
      for idx, (nic, override) in enumerate(zip(nics, override_nics)):
508 a111ebde Michael Hanselmann
        nics[idx] = objects.FillDict(nic, override)
509 a111ebde Michael Hanselmann
510 6bf273d5 Michael Hanselmann
    # TODO: Should this be the actual up/down status? (run_state)
511 6bf273d5 Michael Hanselmann
    start = (instance["config_state"] == "up")
512 6bf273d5 Michael Hanselmann
513 6bf273d5 Michael Hanselmann
    assert len(disks) == len(instance["disks"])
514 6bf273d5 Michael Hanselmann
    assert len(nics) == len(instance["nics"])
515 6bf273d5 Michael Hanselmann
516 a111ebde Michael Hanselmann
    inst_beparams = instance["be_instance"]
517 a111ebde Michael Hanselmann
    if not inst_beparams:
518 a111ebde Michael Hanselmann
      inst_beparams = {}
519 a111ebde Michael Hanselmann
520 a111ebde Michael Hanselmann
    inst_hvparams = instance["hv_instance"]
521 a111ebde Michael Hanselmann
    if not inst_hvparams:
522 a111ebde Michael Hanselmann
      inst_hvparams = {}
523 a111ebde Michael Hanselmann
524 a111ebde Michael Hanselmann
    inst_osparams = instance["os_instance"]
525 a111ebde Michael Hanselmann
    if not inst_osparams:
526 a111ebde Michael Hanselmann
      inst_osparams = {}
527 a111ebde Michael Hanselmann
528 6bf273d5 Michael Hanselmann
    return cl.CreateInstance(constants.INSTANCE_REMOTE_IMPORT,
529 6bf273d5 Michael Hanselmann
                             name, disk_template, disks, nics,
530 6bf273d5 Michael Hanselmann
                             os=instance["os"],
531 6bf273d5 Michael Hanselmann
                             pnode=pnode,
532 6bf273d5 Michael Hanselmann
                             snode=snode,
533 6bf273d5 Michael Hanselmann
                             start=start,
534 6bf273d5 Michael Hanselmann
                             ip_check=False,
535 6bf273d5 Michael Hanselmann
                             iallocator=iallocator,
536 6bf273d5 Michael Hanselmann
                             hypervisor=instance["hypervisor"],
537 6bf273d5 Michael Hanselmann
                             source_handshake=expinfo["handshake"],
538 6bf273d5 Michael Hanselmann
                             source_x509_ca=expinfo["x509_ca"],
539 6bf273d5 Michael Hanselmann
                             source_instance_name=instance["name"],
540 a111ebde Michael Hanselmann
                             beparams=objects.FillDict(inst_beparams,
541 a111ebde Michael Hanselmann
                                                       override_beparams),
542 a111ebde Michael Hanselmann
                             hvparams=objects.FillDict(inst_hvparams,
543 a111ebde Michael Hanselmann
                                                       override_hvparams),
544 a111ebde Michael Hanselmann
                             osparams=objects.FillDict(inst_osparams,
545 a111ebde Michael Hanselmann
                                                       override_osparams))
546 6bf273d5 Michael Hanselmann
547 6bf273d5 Michael Hanselmann
548 6bf273d5 Michael Hanselmann
class MoveSourceExecutor(object):
549 6bf273d5 Michael Hanselmann
  def __init__(self, src_client, mrt):
550 6bf273d5 Michael Hanselmann
    """Source side of an instance move.
551 6bf273d5 Michael Hanselmann
552 6bf273d5 Michael Hanselmann
    @type src_client: L{rapi.client.GanetiRapiClient}
553 6bf273d5 Michael Hanselmann
    @param src_client: RAPI client
554 6bf273d5 Michael Hanselmann
    @type mrt: L{MoveRuntime}
555 6bf273d5 Michael Hanselmann
    @param mrt: Instance move runtime information
556 6bf273d5 Michael Hanselmann
557 6bf273d5 Michael Hanselmann
    """
558 6bf273d5 Michael Hanselmann
    logging.info("Checking whether instance exists")
559 6bf273d5 Michael Hanselmann
    self._CheckInstance(src_client, mrt.move.src_instance_name)
560 6bf273d5 Michael Hanselmann
561 6bf273d5 Michael Hanselmann
    logging.info("Retrieving instance information from source cluster")
562 6bf273d5 Michael Hanselmann
    instinfo = self._GetInstanceInfo(src_client, mrt.PollJob,
563 6bf273d5 Michael Hanselmann
                                     mrt.move.src_instance_name)
564 6bf273d5 Michael Hanselmann
565 6bf273d5 Michael Hanselmann
    logging.info("Preparing export on source cluster")
566 6bf273d5 Michael Hanselmann
    expinfo = self._PrepareExport(src_client, mrt.PollJob,
567 6bf273d5 Michael Hanselmann
                                  mrt.move.src_instance_name)
568 6bf273d5 Michael Hanselmann
    assert "handshake" in expinfo
569 6bf273d5 Michael Hanselmann
    assert "x509_key_name" in expinfo
570 6bf273d5 Michael Hanselmann
    assert "x509_ca" in expinfo
571 6bf273d5 Michael Hanselmann
572 6bf273d5 Michael Hanselmann
    # Hand information to destination thread
573 6bf273d5 Michael Hanselmann
    mrt.source_to_dest.acquire()
574 6bf273d5 Michael Hanselmann
    try:
575 6bf273d5 Michael Hanselmann
      mrt.src_instinfo = instinfo
576 6bf273d5 Michael Hanselmann
      mrt.src_expinfo = expinfo
577 6bf273d5 Michael Hanselmann
      mrt.source_to_dest.notifyAll()
578 6bf273d5 Michael Hanselmann
    finally:
579 6bf273d5 Michael Hanselmann
      mrt.source_to_dest.release()
580 6bf273d5 Michael Hanselmann
581 6bf273d5 Michael Hanselmann
    logging.info("Waiting for destination information to become available")
582 6bf273d5 Michael Hanselmann
    mrt.Wait(mrt.dest_to_source, lambda mrt: mrt.dest_impinfo is None)
583 6bf273d5 Michael Hanselmann
584 6bf273d5 Michael Hanselmann
    logging.info("Starting remote export on source cluster")
585 6bf273d5 Michael Hanselmann
    self._ExportInstance(src_client, mrt.PollJob, mrt.move.src_instance_name,
586 6bf273d5 Michael Hanselmann
                         expinfo["x509_key_name"], mrt.dest_impinfo)
587 6bf273d5 Michael Hanselmann
588 6bf273d5 Michael Hanselmann
    logging.info("Export successful")
589 6bf273d5 Michael Hanselmann
590 6bf273d5 Michael Hanselmann
  @staticmethod
591 6bf273d5 Michael Hanselmann
  def _CheckInstance(cl, name):
592 6bf273d5 Michael Hanselmann
    """Checks whether the instance exists on the source cluster.
593 6bf273d5 Michael Hanselmann
594 6bf273d5 Michael Hanselmann
    @type cl: L{rapi.client.GanetiRapiClient}
595 6bf273d5 Michael Hanselmann
    @param cl: RAPI client
596 6bf273d5 Michael Hanselmann
    @type name: string
597 6bf273d5 Michael Hanselmann
    @param name: Instance name
598 6bf273d5 Michael Hanselmann
599 6bf273d5 Michael Hanselmann
    """
600 6bf273d5 Michael Hanselmann
    try:
601 6bf273d5 Michael Hanselmann
      cl.GetInstance(name)
602 6bf273d5 Michael Hanselmann
    except rapi.client.GanetiApiError, err:
603 6bf273d5 Michael Hanselmann
      if err.code == rapi.client.HTTP_NOT_FOUND:
604 6bf273d5 Michael Hanselmann
        raise Error("Instance %s not found (%s)" % (name, str(err)))
605 6bf273d5 Michael Hanselmann
      raise
606 6bf273d5 Michael Hanselmann
607 6bf273d5 Michael Hanselmann
  @staticmethod
608 6bf273d5 Michael Hanselmann
  def _GetInstanceInfo(cl, poll_job_fn, name):
609 6bf273d5 Michael Hanselmann
    """Retrieves detailed instance information from source cluster.
610 6bf273d5 Michael Hanselmann
611 6bf273d5 Michael Hanselmann
    @type cl: L{rapi.client.GanetiRapiClient}
612 6bf273d5 Michael Hanselmann
    @param cl: RAPI client
613 6bf273d5 Michael Hanselmann
    @type poll_job_fn: callable
614 6bf273d5 Michael Hanselmann
    @param poll_job_fn: Function to poll for job result
615 6bf273d5 Michael Hanselmann
    @type name: string
616 6bf273d5 Michael Hanselmann
    @param name: Instance name
617 6bf273d5 Michael Hanselmann
618 6bf273d5 Michael Hanselmann
    """
619 6bf273d5 Michael Hanselmann
    job_id = cl.GetInstanceInfo(name, static=True)
620 6bf273d5 Michael Hanselmann
    result = poll_job_fn(cl, job_id)
621 6bf273d5 Michael Hanselmann
    assert len(result[0].keys()) == 1
622 6bf273d5 Michael Hanselmann
    return result[0][result[0].keys()[0]]
623 6bf273d5 Michael Hanselmann
624 6bf273d5 Michael Hanselmann
  @staticmethod
625 6bf273d5 Michael Hanselmann
  def _PrepareExport(cl, poll_job_fn, name):
626 6bf273d5 Michael Hanselmann
    """Prepares export on source cluster.
627 6bf273d5 Michael Hanselmann
628 6bf273d5 Michael Hanselmann
    @type cl: L{rapi.client.GanetiRapiClient}
629 6bf273d5 Michael Hanselmann
    @param cl: RAPI client
630 6bf273d5 Michael Hanselmann
    @type poll_job_fn: callable
631 6bf273d5 Michael Hanselmann
    @param poll_job_fn: Function to poll for job result
632 6bf273d5 Michael Hanselmann
    @type name: string
633 6bf273d5 Michael Hanselmann
    @param name: Instance name
634 6bf273d5 Michael Hanselmann
635 6bf273d5 Michael Hanselmann
    """
636 6bf273d5 Michael Hanselmann
    job_id = cl.PrepareExport(name, constants.EXPORT_MODE_REMOTE)
637 6bf273d5 Michael Hanselmann
    return poll_job_fn(cl, job_id)[0]
638 6bf273d5 Michael Hanselmann
639 6bf273d5 Michael Hanselmann
  @staticmethod
640 6bf273d5 Michael Hanselmann
  def _ExportInstance(cl, poll_job_fn, name, x509_key_name, impinfo):
641 6bf273d5 Michael Hanselmann
    """Exports instance from source cluster.
642 6bf273d5 Michael Hanselmann
643 6bf273d5 Michael Hanselmann
    @type cl: L{rapi.client.GanetiRapiClient}
644 6bf273d5 Michael Hanselmann
    @param cl: RAPI client
645 6bf273d5 Michael Hanselmann
    @type poll_job_fn: callable
646 6bf273d5 Michael Hanselmann
    @param poll_job_fn: Function to poll for job result
647 6bf273d5 Michael Hanselmann
    @type name: string
648 6bf273d5 Michael Hanselmann
    @param name: Instance name
649 6bf273d5 Michael Hanselmann
    @param x509_key_name: Source X509 key
650 6bf273d5 Michael Hanselmann
    @param impinfo: Import information from destination cluster
651 6bf273d5 Michael Hanselmann
652 6bf273d5 Michael Hanselmann
    """
653 6bf273d5 Michael Hanselmann
    job_id = cl.ExportInstance(name, constants.EXPORT_MODE_REMOTE,
654 6bf273d5 Michael Hanselmann
                               impinfo["disks"], shutdown=True,
655 6bf273d5 Michael Hanselmann
                               remove_instance=True,
656 6bf273d5 Michael Hanselmann
                               x509_key_name=x509_key_name,
657 6bf273d5 Michael Hanselmann
                               destination_x509_ca=impinfo["x509_ca"])
658 6bf273d5 Michael Hanselmann
    (fin_resu, dresults) = poll_job_fn(cl, job_id)[0]
659 6bf273d5 Michael Hanselmann
660 6bf273d5 Michael Hanselmann
    if not (fin_resu and compat.all(dresults)):
661 6bf273d5 Michael Hanselmann
      raise Error("Export failed for disks %s" %
662 6bf273d5 Michael Hanselmann
                  utils.CommaJoin(str(idx) for idx, result
663 6bf273d5 Michael Hanselmann
                                  in enumerate(dresults) if not result))
664 6bf273d5 Michael Hanselmann
665 6bf273d5 Michael Hanselmann
666 6bf273d5 Michael Hanselmann
class MoveSourceWorker(workerpool.BaseWorker):
667 b459a848 Andrea Spadaccini
  def RunTask(self, rapi_factory, move): # pylint: disable=W0221
668 6bf273d5 Michael Hanselmann
    """Executes an instance move.
669 6bf273d5 Michael Hanselmann
670 6bf273d5 Michael Hanselmann
    @type rapi_factory: L{RapiClientFactory}
671 6bf273d5 Michael Hanselmann
    @param rapi_factory: RAPI client factory
672 6bf273d5 Michael Hanselmann
    @type move: L{InstanceMove}
673 6bf273d5 Michael Hanselmann
    @param move: Instance move information
674 6bf273d5 Michael Hanselmann
675 6bf273d5 Michael Hanselmann
    """
676 6bf273d5 Michael Hanselmann
    try:
677 6bf273d5 Michael Hanselmann
      logging.info("Preparing to move %s from cluster %s to %s as %s",
678 6bf273d5 Michael Hanselmann
                   move.src_instance_name, rapi_factory.src_cluster_name,
679 6bf273d5 Michael Hanselmann
                   rapi_factory.dest_cluster_name, move.dest_instance_name)
680 6bf273d5 Michael Hanselmann
681 6bf273d5 Michael Hanselmann
      mrt = MoveRuntime(move)
682 6bf273d5 Michael Hanselmann
683 6bf273d5 Michael Hanselmann
      logging.debug("Starting destination thread")
684 6bf273d5 Michael Hanselmann
      dest_thread = threading.Thread(name="DestFor%s" % self.getName(),
685 6bf273d5 Michael Hanselmann
                                     target=mrt.HandleErrors,
686 6bf273d5 Michael Hanselmann
                                     args=("dest", MoveDestExecutor,
687 6bf273d5 Michael Hanselmann
                                           rapi_factory.GetDestClient(),
688 6bf273d5 Michael Hanselmann
                                           mrt, ))
689 6bf273d5 Michael Hanselmann
      dest_thread.start()
690 6bf273d5 Michael Hanselmann
      try:
691 6bf273d5 Michael Hanselmann
        mrt.HandleErrors("src", MoveSourceExecutor,
692 6bf273d5 Michael Hanselmann
                         rapi_factory.GetSourceClient(), mrt)
693 6bf273d5 Michael Hanselmann
      finally:
694 6bf273d5 Michael Hanselmann
        dest_thread.join()
695 6bf273d5 Michael Hanselmann
696 6bf273d5 Michael Hanselmann
      if mrt.src_error_message or mrt.dest_error_message:
697 6bf273d5 Michael Hanselmann
        move.error_message = ("Source error: %s, destination error: %s" %
698 6bf273d5 Michael Hanselmann
                              (mrt.src_error_message, mrt.dest_error_message))
699 6bf273d5 Michael Hanselmann
      else:
700 6bf273d5 Michael Hanselmann
        move.error_message = None
701 b459a848 Andrea Spadaccini
    except Exception, err: # pylint: disable=W0703
702 6bf273d5 Michael Hanselmann
      logging.exception("Caught unhandled exception")
703 6bf273d5 Michael Hanselmann
      move.error_message = str(err)
704 6bf273d5 Michael Hanselmann
705 6bf273d5 Michael Hanselmann
706 6bf273d5 Michael Hanselmann
def CheckRapiSetup(rapi_factory):
707 6bf273d5 Michael Hanselmann
  """Checks the RAPI setup by retrieving the version.
708 6bf273d5 Michael Hanselmann
709 6bf273d5 Michael Hanselmann
  @type rapi_factory: L{RapiClientFactory}
710 6bf273d5 Michael Hanselmann
  @param rapi_factory: RAPI client factory
711 6bf273d5 Michael Hanselmann
712 6bf273d5 Michael Hanselmann
  """
713 6bf273d5 Michael Hanselmann
  src_client = rapi_factory.GetSourceClient()
714 6bf273d5 Michael Hanselmann
  logging.info("Connecting to source RAPI server")
715 6bf273d5 Michael Hanselmann
  logging.info("Source cluster RAPI version: %s", src_client.GetVersion())
716 6bf273d5 Michael Hanselmann
717 6bf273d5 Michael Hanselmann
  dest_client = rapi_factory.GetDestClient()
718 6bf273d5 Michael Hanselmann
  logging.info("Connecting to destination RAPI server")
719 6bf273d5 Michael Hanselmann
  logging.info("Destination cluster RAPI version: %s", dest_client.GetVersion())
720 6bf273d5 Michael Hanselmann
721 6bf273d5 Michael Hanselmann
722 6bf273d5 Michael Hanselmann
def ParseOptions():
723 6bf273d5 Michael Hanselmann
  """Parses options passed to program.
724 6bf273d5 Michael Hanselmann
725 6bf273d5 Michael Hanselmann
  """
726 6bf273d5 Michael Hanselmann
  program = os.path.basename(sys.argv[0])
727 6bf273d5 Michael Hanselmann
728 6bf273d5 Michael Hanselmann
  parser = optparse.OptionParser(usage=("%prog [--debug|--verbose]"
729 6bf273d5 Michael Hanselmann
                                        " <source-cluster> <dest-cluster>"
730 6bf273d5 Michael Hanselmann
                                        " <instance...>"),
731 6bf273d5 Michael Hanselmann
                                 prog=program)
732 6bf273d5 Michael Hanselmann
  parser.add_option(cli.DEBUG_OPT)
733 6bf273d5 Michael Hanselmann
  parser.add_option(cli.VERBOSE_OPT)
734 6bf273d5 Michael Hanselmann
  parser.add_option(cli.IALLOCATOR_OPT)
735 a111ebde Michael Hanselmann
  parser.add_option(cli.BACKEND_OPT)
736 a111ebde Michael Hanselmann
  parser.add_option(cli.HVOPTS_OPT)
737 a111ebde Michael Hanselmann
  parser.add_option(cli.OSPARAMS_OPT)
738 a111ebde Michael Hanselmann
  parser.add_option(cli.NET_OPT)
739 6bf273d5 Michael Hanselmann
  parser.add_option(SRC_RAPI_PORT_OPT)
740 6bf273d5 Michael Hanselmann
  parser.add_option(SRC_CA_FILE_OPT)
741 6bf273d5 Michael Hanselmann
  parser.add_option(SRC_USERNAME_OPT)
742 6bf273d5 Michael Hanselmann
  parser.add_option(SRC_PASSWORD_FILE_OPT)
743 6bf273d5 Michael Hanselmann
  parser.add_option(DEST_RAPI_PORT_OPT)
744 6bf273d5 Michael Hanselmann
  parser.add_option(DEST_CA_FILE_OPT)
745 6bf273d5 Michael Hanselmann
  parser.add_option(DEST_USERNAME_OPT)
746 6bf273d5 Michael Hanselmann
  parser.add_option(DEST_PASSWORD_FILE_OPT)
747 6bf273d5 Michael Hanselmann
  parser.add_option(DEST_INSTANCE_NAME_OPT)
748 6bf273d5 Michael Hanselmann
  parser.add_option(DEST_PRIMARY_NODE_OPT)
749 6bf273d5 Michael Hanselmann
  parser.add_option(DEST_SECONDARY_NODE_OPT)
750 6bf273d5 Michael Hanselmann
  parser.add_option(PARALLEL_OPT)
751 6bf273d5 Michael Hanselmann
752 6bf273d5 Michael Hanselmann
  (options, args) = parser.parse_args()
753 6bf273d5 Michael Hanselmann
754 6bf273d5 Michael Hanselmann
  return (parser, options, args)
755 6bf273d5 Michael Hanselmann
756 6bf273d5 Michael Hanselmann
757 6bf273d5 Michael Hanselmann
def CheckOptions(parser, options, args):
758 6bf273d5 Michael Hanselmann
  """Checks options and arguments for validity.
759 6bf273d5 Michael Hanselmann
760 6bf273d5 Michael Hanselmann
  """
761 6bf273d5 Michael Hanselmann
  if len(args) < 3:
762 6bf273d5 Michael Hanselmann
    parser.error("Not enough arguments")
763 6bf273d5 Michael Hanselmann
764 6bf273d5 Michael Hanselmann
  src_cluster_name = args.pop(0)
765 6bf273d5 Michael Hanselmann
  dest_cluster_name = args.pop(0)
766 6bf273d5 Michael Hanselmann
  instance_names = args
767 6bf273d5 Michael Hanselmann
768 6bf273d5 Michael Hanselmann
  assert len(instance_names) > 0
769 6bf273d5 Michael Hanselmann
770 6bf273d5 Michael Hanselmann
  # TODO: Remove once using system default paths for SSL certificate
771 6bf273d5 Michael Hanselmann
  # verification is implemented
772 6bf273d5 Michael Hanselmann
  if not options.src_ca_file:
773 6bf273d5 Michael Hanselmann
    parser.error("Missing source cluster CA file")
774 6bf273d5 Michael Hanselmann
775 6bf273d5 Michael Hanselmann
  if options.parallel < 1:
776 6bf273d5 Michael Hanselmann
    parser.error("Number of simultaneous moves must be >= 1")
777 6bf273d5 Michael Hanselmann
778 6bf273d5 Michael Hanselmann
  if not (bool(options.iallocator) ^
779 6bf273d5 Michael Hanselmann
          bool(options.dest_primary_node or options.dest_secondary_node)):
780 6bf273d5 Michael Hanselmann
    parser.error("Destination node and iallocator options exclude each other")
781 6bf273d5 Michael Hanselmann
782 6bf273d5 Michael Hanselmann
  if len(instance_names) == 1:
783 6bf273d5 Michael Hanselmann
    # Moving one instance only
784 6bf273d5 Michael Hanselmann
    if not (options.iallocator or
785 6bf273d5 Michael Hanselmann
            options.dest_primary_node or
786 6bf273d5 Michael Hanselmann
            options.dest_secondary_node):
787 6bf273d5 Michael Hanselmann
      parser.error("An iallocator or the destination node is required")
788 a111ebde Michael Hanselmann
789 a111ebde Michael Hanselmann
    if options.hvparams:
790 a111ebde Michael Hanselmann
      utils.ForceDictType(options.hvparams, constants.HVS_PARAMETER_TYPES)
791 a111ebde Michael Hanselmann
792 a111ebde Michael Hanselmann
    if options.beparams:
793 a111ebde Michael Hanselmann
      utils.ForceDictType(options.beparams, constants.BES_PARAMETER_TYPES)
794 a111ebde Michael Hanselmann
795 a111ebde Michael Hanselmann
    if options.nics:
796 a111ebde Michael Hanselmann
      options.nics = cli.ParseNicOption(options.nics)
797 6bf273d5 Michael Hanselmann
  else:
798 6bf273d5 Michael Hanselmann
    # Moving more than one instance
799 6bf273d5 Michael Hanselmann
    if (options.dest_instance_name or options.dest_primary_node or
800 a111ebde Michael Hanselmann
        options.dest_secondary_node or options.hvparams or
801 a111ebde Michael Hanselmann
        options.beparams or options.osparams or options.nics):
802 a111ebde Michael Hanselmann
      parser.error("The options --dest-instance-name, --dest-primary-node,"
803 a111ebde Michael Hanselmann
                   " --dest-secondary-node, --hypervisor-parameters,"
804 a111ebde Michael Hanselmann
                   " --backend-parameters, --os-parameters and --net can"
805 a111ebde Michael Hanselmann
                   " only be used when moving exactly one instance")
806 6bf273d5 Michael Hanselmann
807 6bf273d5 Michael Hanselmann
    if not options.iallocator:
808 6bf273d5 Michael Hanselmann
      parser.error("An iallocator must be specified for moving more than one"
809 6bf273d5 Michael Hanselmann
                   " instance")
810 6bf273d5 Michael Hanselmann
811 6bf273d5 Michael Hanselmann
  return (src_cluster_name, dest_cluster_name, instance_names)
812 6bf273d5 Michael Hanselmann
813 6bf273d5 Michael Hanselmann
814 fc3f75dd Iustin Pop
@UsesRapiClient
815 6bf273d5 Michael Hanselmann
def main():
816 6bf273d5 Michael Hanselmann
  """Main routine.
817 6bf273d5 Michael Hanselmann
818 6bf273d5 Michael Hanselmann
  """
819 6bf273d5 Michael Hanselmann
  (parser, options, args) = ParseOptions()
820 6bf273d5 Michael Hanselmann
821 796b5152 Michael Hanselmann
  utils.SetupToolLogging(options.debug, options.verbose, threadname=True)
822 6bf273d5 Michael Hanselmann
823 6bf273d5 Michael Hanselmann
  (src_cluster_name, dest_cluster_name, instance_names) = \
824 6bf273d5 Michael Hanselmann
    CheckOptions(parser, options, args)
825 6bf273d5 Michael Hanselmann
826 6bf273d5 Michael Hanselmann
  logging.info("Source cluster: %s", src_cluster_name)
827 6bf273d5 Michael Hanselmann
  logging.info("Destination cluster: %s", dest_cluster_name)
828 6bf273d5 Michael Hanselmann
  logging.info("Instances to be moved: %s", utils.CommaJoin(instance_names))
829 6bf273d5 Michael Hanselmann
830 6bf273d5 Michael Hanselmann
  rapi_factory = RapiClientFactory(options, src_cluster_name, dest_cluster_name)
831 6bf273d5 Michael Hanselmann
832 6bf273d5 Michael Hanselmann
  CheckRapiSetup(rapi_factory)
833 6bf273d5 Michael Hanselmann
834 6bf273d5 Michael Hanselmann
  assert (len(instance_names) == 1 or
835 6bf273d5 Michael Hanselmann
          not (options.dest_primary_node or options.dest_secondary_node))
836 6bf273d5 Michael Hanselmann
  assert len(instance_names) == 1 or options.iallocator
837 6bf273d5 Michael Hanselmann
  assert (len(instance_names) > 1 or options.iallocator or
838 6bf273d5 Michael Hanselmann
          options.dest_primary_node or options.dest_secondary_node)
839 a111ebde Michael Hanselmann
  assert (len(instance_names) == 1 or
840 a111ebde Michael Hanselmann
          not (options.hvparams or options.beparams or options.osparams or
841 a111ebde Michael Hanselmann
               options.nics))
842 6bf273d5 Michael Hanselmann
843 6bf273d5 Michael Hanselmann
  # Prepare list of instance moves
844 6bf273d5 Michael Hanselmann
  moves = []
845 6bf273d5 Michael Hanselmann
  for src_instance_name in instance_names:
846 6bf273d5 Michael Hanselmann
    if options.dest_instance_name:
847 6bf273d5 Michael Hanselmann
      assert len(instance_names) == 1
848 6bf273d5 Michael Hanselmann
      # Rename instance
849 6bf273d5 Michael Hanselmann
      dest_instance_name = options.dest_instance_name
850 6bf273d5 Michael Hanselmann
    else:
851 6bf273d5 Michael Hanselmann
      dest_instance_name = src_instance_name
852 6bf273d5 Michael Hanselmann
853 6bf273d5 Michael Hanselmann
    moves.append(InstanceMove(src_instance_name, dest_instance_name,
854 6bf273d5 Michael Hanselmann
                              options.dest_primary_node,
855 6bf273d5 Michael Hanselmann
                              options.dest_secondary_node,
856 a111ebde Michael Hanselmann
                              options.iallocator, options.hvparams,
857 a111ebde Michael Hanselmann
                              options.beparams, options.osparams,
858 a111ebde Michael Hanselmann
                              options.nics))
859 6bf273d5 Michael Hanselmann
860 6bf273d5 Michael Hanselmann
  assert len(moves) == len(instance_names)
861 6bf273d5 Michael Hanselmann
862 6bf273d5 Michael Hanselmann
  # Start workerpool
863 6bf273d5 Michael Hanselmann
  wp = workerpool.WorkerPool("Move", options.parallel, MoveSourceWorker)
864 6bf273d5 Michael Hanselmann
  try:
865 6bf273d5 Michael Hanselmann
    # Add instance moves to workerpool
866 6bf273d5 Michael Hanselmann
    for move in moves:
867 b2e8a4d9 Michael Hanselmann
      wp.AddTask((rapi_factory, move))
868 6bf273d5 Michael Hanselmann
869 6bf273d5 Michael Hanselmann
    # Wait for all moves to finish
870 6bf273d5 Michael Hanselmann
    wp.Quiesce()
871 6bf273d5 Michael Hanselmann
872 6bf273d5 Michael Hanselmann
  finally:
873 6bf273d5 Michael Hanselmann
    wp.TerminateWorkers()
874 6bf273d5 Michael Hanselmann
875 6bf273d5 Michael Hanselmann
  # There should be no threads running at this point, hence not using locks
876 6bf273d5 Michael Hanselmann
  # anymore
877 6bf273d5 Michael Hanselmann
878 6bf273d5 Michael Hanselmann
  logging.info("Instance move results:")
879 6bf273d5 Michael Hanselmann
880 6bf273d5 Michael Hanselmann
  for move in moves:
881 6bf273d5 Michael Hanselmann
    if move.dest_instance_name == move.src_instance_name:
882 6bf273d5 Michael Hanselmann
      name = move.src_instance_name
883 6bf273d5 Michael Hanselmann
    else:
884 6bf273d5 Michael Hanselmann
      name = "%s as %s" % (move.src_instance_name, move.dest_instance_name)
885 6bf273d5 Michael Hanselmann
886 424f51ec Michael Hanselmann
    if move.error_message:
887 6bf273d5 Michael Hanselmann
      msg = "Failed (%s)" % move.error_message
888 424f51ec Michael Hanselmann
    else:
889 424f51ec Michael Hanselmann
      msg = "Success"
890 6bf273d5 Michael Hanselmann
891 6bf273d5 Michael Hanselmann
    logging.info("%s: %s", name, msg)
892 6bf273d5 Michael Hanselmann
893 424f51ec Michael Hanselmann
  if compat.any(move.error_message for move in moves):
894 424f51ec Michael Hanselmann
    sys.exit(constants.EXIT_FAILURE)
895 6bf273d5 Michael Hanselmann
896 424f51ec Michael Hanselmann
  sys.exit(constants.EXIT_SUCCESS)
897 6bf273d5 Michael Hanselmann
898 6bf273d5 Michael Hanselmann
899 6bf273d5 Michael Hanselmann
if __name__ == "__main__":
900 6bf273d5 Michael Hanselmann
  main()