Statistics
| Branch: | Tag: | Revision:

root / tools / move-instance @ 1fe10404

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