#!/usr/bin/python
#
-# Copyright (C) 2010 Google Inc.
+# Copyright (C) 2010, 2011, 2012 Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
"""
-# pylint: disable-msg=C0103
+# pylint: disable=C0103
# C0103: Invalid name move-instance
import os
from ganeti import constants
from ganeti import utils
from ganeti import workerpool
+from ganeti import objects
from ganeti import compat
from ganeti import rapi
-import ganeti.rapi.client # pylint: disable-msg=W0611
+import ganeti.rapi.client # pylint: disable=W0611
import ganeti.rapi.client_utils
+from ganeti.rapi.client import UsesRapiClient
SRC_RAPI_PORT_OPT = \
"""
def __init__(self, src_instance_name, dest_instance_name,
- dest_pnode, dest_snode, dest_iallocator):
+ dest_pnode, dest_snode, dest_iallocator,
+ hvparams, beparams, osparams, nics):
"""Initializes this class.
@type src_instance_name: string
@param dest_snode: Name of secondary node on destination cluster
@type dest_iallocator: string or None
@param dest_iallocator: Name of iallocator to use
+ @type hvparams: dict or None
+ @param hvparams: Hypervisor parameters to override
+ @type beparams: dict or None
+ @param beparams: Backend parameters to override
+ @type osparams: dict or None
+ @param osparams: OS parameters to override
+ @type nics: dict or None
+ @param nics: NICs to override
"""
self.src_instance_name = src_instance_name
self.dest_pnode = dest_pnode
self.dest_snode = dest_snode
self.dest_iallocator = dest_iallocator
+ self.hvparams = hvparams
+ self.beparams = beparams
+ self.osparams = osparams
+ self.nics = nics
self.error_message = None
job_id = self._CreateInstance(dest_client, mrt.move.dest_instance_name,
mrt.move.dest_pnode, mrt.move.dest_snode,
mrt.move.dest_iallocator,
- mrt.src_instinfo, mrt.src_expinfo)
+ mrt.src_instinfo, mrt.src_expinfo,
+ mrt.move.hvparams, mrt.move.beparams,
+ mrt.move.beparams, mrt.move.nics)
mrt.PollJob(dest_client, job_id,
remote_import_fn=compat.partial(self._SetImportInfo, mrt))
mrt.dest_to_source.release()
@staticmethod
- def _CreateInstance(cl, name, snode, pnode, iallocator, instance, expinfo):
+ def _CreateInstance(cl, name, pnode, snode, iallocator, instance, expinfo,
+ override_hvparams, override_beparams, override_osparams,
+ override_nics):
"""Starts the instance creation in remote import mode.
@type cl: L{rapi.client.GanetiRapiClient}
@param instance: Instance details from source cluster
@type expinfo: dict
@param expinfo: Prepared export information from source cluster
+ @type override_hvparams: dict or None
+ @param override_hvparams: Hypervisor parameters to override
+ @type override_beparams: dict or None
+ @param override_beparams: Backend parameters to override
+ @type override_osparams: dict or None
+ @param override_osparams: OS parameters to override
+ @type override_nics: dict or None
+ @param override_nics: NICs to override
@return: Job ID
"""
disk_template = instance["disk_template"]
disks = [{
- "size": i["size"],
- "mode": i["mode"],
+ constants.IDISK_SIZE: i["size"],
+ constants.IDISK_MODE: i["mode"],
+ constants.IDISK_NAME: str(i.get("name")),
} for i in instance["disks"]]
nics = [{
- "ip": ip,
- "mac": mac,
- "mode": mode,
- "link": link,
- } for ip, mac, mode, link in instance["nics"]]
+ constants.INIC_IP: ip,
+ constants.INIC_MAC: mac,
+ constants.INIC_MODE: mode,
+ constants.INIC_LINK: link,
+ constants.INIC_NETWORK: network,
+ constants.INIC_NAME: nic_name
+ } for nic_name, _, ip, mac, mode, link, network, _ in instance["nics"]]
+
+ if len(override_nics) > len(nics):
+ raise Error("Can not create new NICs")
+
+ if override_nics:
+ assert len(override_nics) <= len(nics)
+ for idx, (nic, override) in enumerate(zip(nics, override_nics)):
+ nics[idx] = objects.FillDict(nic, override)
# TODO: Should this be the actual up/down status? (run_state)
start = (instance["config_state"] == "up")
assert len(disks) == len(instance["disks"])
assert len(nics) == len(instance["nics"])
+ inst_beparams = instance["be_instance"]
+ if not inst_beparams:
+ inst_beparams = {}
+
+ inst_hvparams = instance["hv_instance"]
+ if not inst_hvparams:
+ inst_hvparams = {}
+
+ inst_osparams = instance["os_instance"]
+ if not inst_osparams:
+ inst_osparams = {}
+
return cl.CreateInstance(constants.INSTANCE_REMOTE_IMPORT,
name, disk_template, disks, nics,
os=instance["os"],
source_handshake=expinfo["handshake"],
source_x509_ca=expinfo["x509_ca"],
source_instance_name=instance["name"],
- beparams=instance["be_instance"],
- hvparams=instance["hv_instance"])
+ beparams=objects.FillDict(inst_beparams,
+ override_beparams),
+ hvparams=objects.FillDict(inst_hvparams,
+ override_hvparams),
+ osparams=objects.FillDict(inst_osparams,
+ override_osparams))
class MoveSourceExecutor(object):
logging.info("Retrieving instance information from source cluster")
instinfo = self._GetInstanceInfo(src_client, mrt.PollJob,
mrt.move.src_instance_name)
+ if instinfo["disk_template"] == constants.DT_FILE:
+ raise Error("Inter-cluster move of file-based instances is not"
+ " supported.")
logging.info("Preparing export on source cluster")
expinfo = self._PrepareExport(src_client, mrt.PollJob,
class MoveSourceWorker(workerpool.BaseWorker):
- def RunTask(self, rapi_factory, move): # pylint: disable-msg=W0221
+ def RunTask(self, rapi_factory, move): # pylint: disable=W0221
"""Executes an instance move.
@type rapi_factory: L{RapiClientFactory}
(mrt.src_error_message, mrt.dest_error_message))
else:
move.error_message = None
- except Exception, err: # pylint: disable-msg=W0703
+ except Exception, err: # pylint: disable=W0703
logging.exception("Caught unhandled exception")
move.error_message = str(err)
logging.info("Destination cluster RAPI version: %s", dest_client.GetVersion())
-def SetupLogging(options):
- """Setting up logging infrastructure.
-
- @param options: Parsed command line options
-
- """
- fmt = "%(asctime)s: %(threadName)s "
- if options.debug or options.verbose:
- fmt += "%(levelname)s "
- fmt += "%(message)s"
-
- formatter = logging.Formatter(fmt)
-
- stderr_handler = logging.StreamHandler()
- stderr_handler.setFormatter(formatter)
- if options.debug:
- stderr_handler.setLevel(logging.NOTSET)
- elif options.verbose:
- stderr_handler.setLevel(logging.INFO)
- else:
- stderr_handler.setLevel(logging.ERROR)
-
- root_logger = logging.getLogger("")
- root_logger.setLevel(logging.NOTSET)
- root_logger.addHandler(stderr_handler)
-
-
def ParseOptions():
"""Parses options passed to program.
parser.add_option(cli.DEBUG_OPT)
parser.add_option(cli.VERBOSE_OPT)
parser.add_option(cli.IALLOCATOR_OPT)
+ parser.add_option(cli.BACKEND_OPT)
+ parser.add_option(cli.HVOPTS_OPT)
+ parser.add_option(cli.OSPARAMS_OPT)
+ parser.add_option(cli.NET_OPT)
parser.add_option(SRC_RAPI_PORT_OPT)
parser.add_option(SRC_CA_FILE_OPT)
parser.add_option(SRC_USERNAME_OPT)
options.dest_primary_node or
options.dest_secondary_node):
parser.error("An iallocator or the destination node is required")
+
+ if options.hvparams:
+ utils.ForceDictType(options.hvparams, constants.HVS_PARAMETER_TYPES)
+
+ if options.beparams:
+ utils.ForceDictType(options.beparams, constants.BES_PARAMETER_TYPES)
+
+ if options.nics:
+ options.nics = cli.ParseNicOption(options.nics)
else:
# Moving more than one instance
if (options.dest_instance_name or options.dest_primary_node or
- options.dest_secondary_node):
- parser.error("The options --dest-instance-name, --dest-primary-node and"
- " --dest-secondary-node can only be used when moving exactly"
- " one instance")
+ options.dest_secondary_node or options.hvparams or
+ options.beparams or options.osparams or options.nics):
+ parser.error("The options --dest-instance-name, --dest-primary-node,"
+ " --dest-secondary-node, --hypervisor-parameters,"
+ " --backend-parameters, --os-parameters and --net can"
+ " only be used when moving exactly one instance")
if not options.iallocator:
parser.error("An iallocator must be specified for moving more than one"
return (src_cluster_name, dest_cluster_name, instance_names)
-@rapi.client.UsesRapiClient
+@UsesRapiClient
def main():
"""Main routine.
"""
(parser, options, args) = ParseOptions()
- SetupLogging(options)
+ utils.SetupToolLogging(options.debug, options.verbose, threadname=True)
(src_cluster_name, dest_cluster_name, instance_names) = \
CheckOptions(parser, options, args)
assert len(instance_names) == 1 or options.iallocator
assert (len(instance_names) > 1 or options.iallocator or
options.dest_primary_node or options.dest_secondary_node)
+ assert (len(instance_names) == 1 or
+ not (options.hvparams or options.beparams or options.osparams or
+ options.nics))
# Prepare list of instance moves
moves = []
moves.append(InstanceMove(src_instance_name, dest_instance_name,
options.dest_primary_node,
options.dest_secondary_node,
- options.iallocator))
+ options.iallocator, options.hvparams,
+ options.beparams, options.osparams,
+ options.nics))
assert len(moves) == len(instance_names)