in the form::
{"instance-name":{
- "disk_size": 25,
- "swap_size": 1024,
+ "disk_size": [20480],
"template": "drbd",
"backend": {
"memory": 512,
"vcpus": 1 },
- "os": "etch-image",
+ "os": "debootstrap",
"primary_node": "firstnode",
"secondary_node": "secondnode",
"iallocator": "dumb"}
@return: the desired exit code
"""
- _DEFAULT_SPECS = {"disk_size": 20 * 1024,
- "swap_size": 4 * 1024,
+ _DEFAULT_SPECS = {"disk_size": [20 * 1024],
"backend": {},
"iallocator": None,
"primary_node": None,
# Iterate over the instances and do:
# * Populate the specs with default value
# * Validate the instance specs
- for (name, specs) in instance_data.iteritems():
+ i_names = utils.NiceSort(instance_data.keys())
+ for name in i_names:
+ specs = instance_data[name]
specs = _PopulateWithDefaults(specs)
_Validate(specs)
if specs['hypervisor']:
hypervisor, hvparams = specs['hypervisor'].iteritems()
+ disks = []
+ for elem in specs['disk_size']:
+ try:
+ size = utils.ParseUnit(elem)
+ except ValueError, err:
+ raise errors.OpPrereqError("Invalid disk size '%s' for"
+ " instance %s: %s" %
+ (elem, name, err))
+ disks.append({"size": size})
+
+ nic0 = {'ip': specs['ip'], 'bridge': specs['bridge'], 'mac': specs['mac']}
+
op = opcodes.OpCreateInstance(instance_name=name,
- disk_size=specs['disk_size'],
- swap_size=specs['swap_size'],
+ disks=disks,
disk_template=specs['template'],
mode=constants.INSTANCE_CREATE,
os_type=specs['os'],
pnode=specs['primary_node'],
snode=specs['secondary_node'],
- ip=specs['ip'], bridge=specs['bridge'],
+ nics=[nic0],
start=specs['start'],
ip_check=specs['ip_check'],
wait_for_sync=True,
- mac=specs['mac'],
iallocator=specs['iallocator'],
hypervisor=hypervisor,
hvparams=hvparams,
disks = [int(i) for i in opts.disks.split(",")]
except ValueError, err:
raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
- if opts.on_primary == opts.on_secondary: # no -p or -s passed, or both passed
- mode = constants.REPLACE_DISK_ALL
- elif opts.on_primary: # only on primary:
+ cnt = [opts.on_primary, opts.on_secondary,
+ new_2ndary is not None, iallocator is not None].count(True)
+ if cnt != 1:
+ raise errors.OpPrereqError("One and only one of the -p, -s, -n and -i"
+ " options must be passed")
+ elif opts.on_primary:
mode = constants.REPLACE_DISK_PRI
- if new_2ndary is not None or iallocator is not None:
- raise errors.OpPrereqError("Can't change secondary node on primary disk"
- " replacement")
- elif opts.on_secondary is not None or iallocator is not None:
- # only on secondary
+ elif opts.on_secondary:
mode = constants.REPLACE_DISK_SEC
+ elif new_2ndary is not None or iallocator is not None:
+ # replace secondary
+ mode = constants.REPLACE_DISK_CHG
op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
remote_node=new_2ndary, mode=mode,
return 0
+def MigrateInstance(opts, args):
+ """Migrate an instance.
+
+ The migrate is done without shutdown.
+
+ @param opts: the command line options selected by the user
+ @type args: list
+ @param args: should contain only one element, the instance name
+ @rtype: int
+ @return: the desired exit code
+
+ """
+ instance_name = args[0]
+ force = opts.force
+
+ if not force:
+ if opts.cleanup:
+ usertext = ("Instance %s will be recovered from a failed migration."
+ " Note that the migration procedure (including cleanup)" %
+ (instance_name,))
+ else:
+ usertext = ("Instance %s will be migrated. Note that migration" %
+ (instance_name,))
+ usertext += (" is **experimental** in this version."
+ " This might impact the instance if anything goes wrong."
+ " Continue?")
+ if not AskUser(usertext):
+ return 1
+
+ op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
+ cleanup=opts.cleanup)
+ SubmitOpCode(op)
+ return 0
+
+
def ConnectToInstanceConsole(opts, args):
"""Connect to the console of an instance.
os._exit(1)
-def _FormatBlockDevInfo(buf, dev, indent_level, static):
+def _FormatLogicalID(dev_type, logical_id):
+ """Formats the logical_id of a disk.
+
+ """
+ if dev_type == constants.LD_DRBD8:
+ node_a, node_b, port, minor_a, minor_b, key = logical_id
+ data = [
+ ("nodeA", "%s, minor=%s" % (node_a, minor_a)),
+ ("nodeB", "%s, minor=%s" % (node_b, minor_b)),
+ ("port", port),
+ ("auth key", key),
+ ]
+ elif dev_type == constants.LD_LV:
+ vg_name, lv_name = logical_id
+ data = ["%s/%s" % (vg_name, lv_name)]
+ else:
+ data = [str(logical_id)]
+
+ return data
+
+
+def _FormatBlockDevInfo(idx, top_level, dev, static):
"""Show block device information.
This is only used by L{ShowInstanceConfig}, but it's too big to be
left for an inline definition.
- @type buf: StringIO
- @param buf: buffer that will accumulate the output
+ @type idx: int
+ @param idx: the index of the current disk
+ @type top_level: boolean
+ @param top_level: if this a top-level disk?
@type dev: dict
@param dev: dictionary with disk information
- @type indent_level: int
- @param indent_level: the indendation level we are at, used for
- the layout of the device tree
@type static: boolean
@param static: wheter the device information doesn't contain
runtime information but only static data
+ @return: a list of either strings, tuples or lists
+ (which should be formatted at a higher indent level)
"""
- def helper(buf, dtype, status):
+ def helper(dtype, status):
"""Format one line for physical device status.
- @type buf: StringIO
- @param buf: buffer that will accumulate the output
@type dtype: str
@param dtype: a constant from the L{constants.LDS_BLOCK} set
@type status: tuple
@param status: a tuple as returned from L{backend.FindBlockDevice}
+ @return: the string representing the status
"""
if not status:
- buf.write("not active\n")
+ return "not active"
+ txt = ""
+ (path, major, minor, syncp, estt, degr, ldisk) = status
+ if major is None:
+ major_string = "N/A"
else:
- (path, major, minor, syncp, estt, degr, ldisk) = status
- if major is None:
- major_string = "N/A"
- else:
- major_string = str(major)
+ major_string = str(major)
- if minor is None:
- minor_string = "N/A"
- else:
- minor_string = str(minor)
-
- buf.write("%s (%s:%s)" % (path, major_string, minor_string))
- if dtype in (constants.LD_DRBD8, ):
- if syncp is not None:
- sync_text = "*RECOVERING* %5.2f%%," % syncp
- if estt:
- sync_text += " ETA %ds" % estt
- else:
- sync_text += " ETA unknown"
- else:
- sync_text = "in sync"
- if degr:
- degr_text = "*DEGRADED*"
- else:
- degr_text = "ok"
- if ldisk:
- ldisk_text = " *MISSING DISK*"
- else:
- ldisk_text = ""
- buf.write(" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
- elif dtype == constants.LD_LV:
- if ldisk:
- ldisk_text = " *FAILED* (failed drive?)"
+ if minor is None:
+ minor_string = "N/A"
+ else:
+ minor_string = str(minor)
+
+ txt += ("%s (%s:%s)" % (path, major_string, minor_string))
+ if dtype in (constants.LD_DRBD8, ):
+ if syncp is not None:
+ sync_text = "*RECOVERING* %5.2f%%," % syncp
+ if estt:
+ sync_text += " ETA %ds" % estt
else:
- ldisk_text = ""
- buf.write(ldisk_text)
- buf.write("\n")
-
- if dev["iv_name"] is not None:
- data = " - %s, " % dev["iv_name"]
+ sync_text += " ETA unknown"
+ else:
+ sync_text = "in sync"
+ if degr:
+ degr_text = "*DEGRADED*"
+ else:
+ degr_text = "ok"
+ if ldisk:
+ ldisk_text = " *MISSING DISK*"
+ else:
+ ldisk_text = ""
+ txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
+ elif dtype == constants.LD_LV:
+ if ldisk:
+ ldisk_text = " *FAILED* (failed drive?)"
+ else:
+ ldisk_text = ""
+ txt += ldisk_text
+ return txt
+
+ # the header
+ if top_level:
+ if dev["iv_name"] is not None:
+ txt = dev["iv_name"]
+ else:
+ txt = "disk %d" % idx
else:
- data = " - "
- data += "access mode: %s, " % dev["mode"]
- data += "type: %s" % dev["dev_type"]
+ txt = "child %d" % idx
+ d1 = ["- %s: %s" % (txt, dev["dev_type"])]
+ data = []
+ if top_level:
+ data.append(("access mode", dev["mode"]))
if dev["logical_id"] is not None:
- data += ", logical_id: %s" % (dev["logical_id"],)
+ try:
+ l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"])
+ except ValueError:
+ l_id = [str(dev["logical_id"])]
+ if len(l_id) == 1:
+ data.append(("logical_id", l_id[0]))
+ else:
+ data.extend(l_id)
elif dev["physical_id"] is not None:
- data += ", physical_id: %s" % (dev["physical_id"],)
- buf.write("%*s%s\n" % (2*indent_level, "", data))
+ data.append("physical_id:")
+ data.append([dev["physical_id"]])
if not static:
- buf.write("%*s primary: " % (2*indent_level, ""))
- helper(buf, dev["dev_type"], dev["pstatus"])
-
+ data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
if dev["sstatus"] and not static:
- buf.write("%*s secondary: " % (2*indent_level, ""))
- helper(buf, dev["dev_type"], dev["sstatus"])
+ data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
if dev["children"]:
- for child in dev["children"]:
- _FormatBlockDevInfo(buf, child, indent_level+1, static)
+ data.append("child devices:")
+ for c_idx, child in enumerate(dev["children"]):
+ data.append(_FormatBlockDevInfo(c_idx, False, child, static))
+ d1.append(data)
+ return d1
+
+
+def _FormatList(buf, data, indent_level):
+ """Formats a list of data at a given indent level.
+
+ If the element of the list is:
+ - a string, it is simply formatted as is
+ - a tuple, it will be split into key, value and the all the
+ values in a list will be aligned all at the same start column
+ - a list, will be recursively formatted
+ @type buf: StringIO
+ @param buf: the buffer into which we write the output
+ @param data: the list to format
+ @type indent_level: int
+ @param indent_level: the indent level to format at
+
+ """
+ max_tlen = max([len(elem[0]) for elem in data
+ if isinstance(elem, tuple)] or [0])
+ for elem in data:
+ if isinstance(elem, basestring):
+ buf.write("%*s%s\n" % (2*indent_level, "", elem))
+ elif isinstance(elem, tuple):
+ key, value = elem
+ spacer = "%*s" % (max_tlen - len(key), "")
+ buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
+ elif isinstance(elem, list):
+ _FormatList(buf, elem, indent_level+1)
def ShowInstanceConfig(opts, args):
"""Compute instance run-time status.
for idx, (mac, ip, bridge) in enumerate(instance["nics"]):
buf.write(" - nic/%d: MAC: %s, IP: %s, bridge: %s\n" %
(idx, mac, ip, bridge))
- buf.write(" Block devices:\n")
+ buf.write(" Disks:\n")
- for device in instance["disks"]:
- _FormatBlockDevInfo(buf, device, 1, opts.static)
+ for idx, device in enumerate(instance["disks"]):
+ _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
ToStdout(buf.getvalue().rstrip('\n'))
return retcode
ToStderr("Please give at least one of the parameters.")
return 1
- if constants.BE_MEMORY in opts.beparams:
- opts.beparams[constants.BE_MEMORY] = utils.ParseUnit(
- opts.beparams[constants.BE_MEMORY])
+ for param in opts.beparams:
+ if opts.beparams[param].lower() == "default":
+ opts.beparams[param] = constants.VALUE_DEFAULT
+ elif opts.beparams[param].lower() == "none":
+ opts.beparams[param] = constants.VALUE_NONE
+ elif param == constants.BE_MEMORY:
+ opts.beparams[constants.BE_MEMORY] = \
+ utils.ParseUnit(opts.beparams[constants.BE_MEMORY])
+
+ for param in opts.hypervisor:
+ if opts.hypervisor[param].lower() == "default":
+ opts.hypervisor[param] = constants.VALUE_DEFAULT
+ elif opts.hypervisor[param].lower() == "none":
+ opts.hypervisor[param] = constants.VALUE_NONE
for idx, (nic_op, nic_dict) in enumerate(opts.nics):
try:
make_option("-n", "--node", dest="node",
help="Target node and optional secondary node",
metavar="<pnode>[:<snode>]"),
- cli_option("-s", "--os-size", dest="size", help="Disk size, in MiB unless"
- " a suffix is used",
- default=20 * 1024, type="unit", metavar="<size>"),
- cli_option("--swap-size", dest="swap", help="Swap size, in MiB unless a"
- " suffix is used",
- default=4 * 1024, type="unit", metavar="<size>"),
os_opt,
keyval_option("-B", "--backend", dest="beparams",
type="keyval", default={},
metavar="<DIR>"),
make_option("--file-driver", dest="file_driver", help="Driver to use"
" for image files", default="loop", metavar="<DRIVER>"),
- make_option("--iallocator", metavar="<NAME>",
+ make_option("-I", "--iallocator", metavar="<NAME>",
help="Select nodes for the instance automatically using the"
" <NAME> iallocator plugin", default=None, type="string"),
ikv_option("-H", "--hypervisor", dest="hypervisor",
"[-f] <instance>",
"Stops the instance and starts it on the backup node, using"
" the remote mirror (only for instances of type drbd)"),
+ 'migrate': (MigrateInstance, ARGS_ONE,
+ [DEBUG_OPT, FORCE_OPT,
+ make_option("--non-live", dest="live",
+ default=True, action="store_false",
+ help="Do a non-live migration (this usually means"
+ " freeze the instance, save the state,"
+ " transfer and only then resume running on the"
+ " secondary node)"),
+ make_option("--cleanup", dest="cleanup",
+ default=False, action="store_true",
+ help="Instead of performing the migration, try to"
+ " recover from a failed cleanup. This is safe"
+ " to run even if the instance is healthy, but it"
+ " will create extra replication traffic and "
+ " disrupt briefly the replication (like during the"
+ " migration"),
+ ],
+ "[-f] <instance>",
+ "Migrate instance to its secondary node"
+ " (only for instances of type drbd)"),
'info': (ShowInstanceConfig, ARGS_ANY,
[DEBUG_OPT,
make_option("-s", "--static", dest="static",
[DEBUG_OPT,
make_option("-n", "--new-secondary", dest="new_secondary",
help=("New secondary node (for secondary"
- " node change)"), metavar="NODE"),
+ " node change)"), metavar="NODE",
+ default=None),
make_option("-p", "--on-primary", dest="on_primary",
default=False, action="store_true",
help=("Replace the disk(s) on the primary"
help=("Comma-separated list of disks"
" to replace (e.g. sda) (optional,"
" defaults to all disks")),
- make_option("--iallocator", metavar="<NAME>",
+ make_option("-i", "--iallocator", metavar="<NAME>",
help="Select new secondary for the instance"
" automatically using the"
" <NAME> iallocator plugin (enables"