#!/usr/bin/python # # Copyright (C) 2006, 2007 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 # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. # pylint: disable-msg=W0401,W0614 # W0401: Wildcard import ganeti.cli # W0614: Unused import %s from wildcard import (since we need cli) import sys import os import itertools import simplejson from optparse import make_option from cStringIO import StringIO from ganeti.cli import * from ganeti import cli from ganeti import opcodes from ganeti import constants from ganeti import utils from ganeti import errors _SHUTDOWN_CLUSTER = "cluster" _SHUTDOWN_NODES_BOTH = "nodes" _SHUTDOWN_NODES_PRI = "nodes-pri" _SHUTDOWN_NODES_SEC = "nodes-sec" _SHUTDOWN_INSTANCES = "instances" _VALUE_TRUE = "true" #: default list of options for L{ListInstances} _LIST_DEF_FIELDS = [ "name", "hypervisor", "os", "pnode", "status", "oper_ram", ] def _ExpandMultiNames(mode, names): """Expand the given names using the passed mode. For _SHUTDOWN_CLUSTER, all instances will be returned. For _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as primary/secondary will be returned. For _SHUTDOWN_NODES_BOTH, all instances having those nodes as either primary or secondary will be returned. For _SHUTDOWN_INSTANCES, the given instances will be returned. @param mode: one of L{_SHUTDOWN_CLUSTER}, L{_SHUTDOWN_NODES_BOTH}, L{_SHUTDOWN_NODES_PRI}, L{_SHUTDOWN_NODES_SEC} or L{_SHUTDOWN_INSTANCES} @param names: a list of names; for cluster, it must be empty, and for node and instance it must be a list of valid item names (short names are valid as usual, e.g. node1 instead of node1.example.com) @rtype: list @return: the list of names after the expansion @raise errors.ProgrammerError: for unknown selection type @raise errors.OpPrereqError: for invalid input parameters """ if mode == _SHUTDOWN_CLUSTER: if names: raise errors.OpPrereqError("Cluster filter mode takes no arguments") client = GetClient() idata = client.QueryInstances([], ["name"]) inames = [row[0] for row in idata] elif mode in (_SHUTDOWN_NODES_BOTH, _SHUTDOWN_NODES_PRI, _SHUTDOWN_NODES_SEC): if not names: raise errors.OpPrereqError("No node names passed") client = GetClient() ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"]) ipri = [row[1] for row in ndata] pri_names = list(itertools.chain(*ipri)) isec = [row[2] for row in ndata] sec_names = list(itertools.chain(*isec)) if mode == _SHUTDOWN_NODES_BOTH: inames = pri_names + sec_names elif mode == _SHUTDOWN_NODES_PRI: inames = pri_names elif mode == _SHUTDOWN_NODES_SEC: inames = sec_names else: raise errors.ProgrammerError("Unhandled shutdown type") elif mode == _SHUTDOWN_INSTANCES: if not names: raise errors.OpPrereqError("No instance names passed") client = GetClient() idata = client.QueryInstances(names, ["name"]) inames = [row[0] for row in idata] else: raise errors.OpPrereqError("Unknown mode '%s'" % mode) return inames def _ConfirmOperation(inames, text): """Ask the user to confirm an operation on a list of instances. This function is used to request confirmation for doing an operation on a given list of instances. @type inames: list @param inames: the list of names that we display when we ask for confirmation @type text: str @param text: the operation that the user should confirm (e.g. I{shutdown} or I{startup}) @rtype: boolean @return: True or False depending on user's confirmation. """ count = len(inames) msg = ("The %s will operate on %d instances.\n" "Do you want to continue?" % (text, count)) affected = ("\nAffected instances:\n" + "\n".join([" %s" % name for name in inames])) choices = [('y', True, 'Yes, execute the %s' % text), ('n', False, 'No, abort the %s' % text)] if count > 20: choices.insert(1, ('v', 'v', 'View the list of affected instances')) ask = msg else: ask = msg + affected choice = AskUser(ask, choices) if choice == 'v': choices.pop(1) choice = AskUser(msg + affected, choices) return choice def _TransformPath(user_input): """Transform a user path into a canonical value. This function transforms the a path passed as textual information into the constants that the LU code expects. """ if user_input: if user_input.lower() == "default": result_path = constants.VALUE_DEFAULT elif user_input.lower() == "none": result_path = constants.VALUE_NONE else: if not os.path.isabs(user_input): raise errors.OpPrereqError("Path '%s' is not an absolute filename" % user_input) result_path = user_input else: result_path = constants.VALUE_DEFAULT return result_path def ListInstances(opts, args): """List instances and their properties. @param opts: the command line options selected by the user @type args: list @param args: should be an empty list @rtype: int @return: the desired exit code """ if opts.output is None: selected_fields = _LIST_DEF_FIELDS elif opts.output.startswith("+"): selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",") else: selected_fields = opts.output.split(",") output = GetClient().QueryInstances([], selected_fields) if not opts.no_headers: headers = { "name": "Instance", "os": "OS", "pnode": "Primary_node", "snodes": "Secondary_Nodes", "admin_state": "Autostart", "oper_state": "Running", "oper_ram": "Memory", "disk_template": "Disk_template", "ip": "IP_address", "mac": "MAC_address", "bridge": "Bridge", "sda_size": "Disk/0", "sdb_size": "Disk/1", "status": "Status", "tags": "Tags", "network_port": "Network_port", "hv/kernel_path": "Kernel_path", "hv/initrd_path": "Initrd_path", "hv/boot_order": "HVM_boot_order", "hv/acpi": "HVM_ACPI", "hv/pae": "HVM_PAE", "hv/cdrom_image_path": "HVM_CDROM_image_path", "hv/nic_type": "HVM_NIC_type", "hv/disk_type": "HVM_Disk_type", "hv/vnc_bind_address": "VNC_bind_address", "serial_no": "SerialNo", "hypervisor": "Hypervisor", "hvparams": "Hypervisor_parameters", "be/memory": "Configured_memory", "be/vcpus": "VCPUs", "be/auto_balance": "Auto_balance", "disk.count": "Disks", "disk.sizes": "Disk_sizes", "nic.count": "NICs", "nic.ips": "NIC_IPs", "nic.bridges": "NIC_bridges", "nic.macs": "NIC_MACs", } else: headers = None unitfields = ["be/memory", "oper_ram", "sd(a|b)_size", "disk\.size/.*"] numfields = ["be/memory", "oper_ram", "sd(a|b)_size", "be/vcpus", "serial_no", "(disk|nic)\.count", "disk\.size/.*"] list_type_fields = ("tags", "disk.sizes", "nic.macs", "nic.ips", "nic.bridges") # change raw values to nicer strings for row in output: for idx, field in enumerate(selected_fields): val = row[idx] if field == "snodes": val = ",".join(val) or "-" elif field == "admin_state": if val: val = "yes" else: val = "no" elif field == "oper_state": if val is None: val = "(node down)" elif val: # True val = "running" else: val = "stopped" elif field == "oper_ram": if val is None: val = "(node down)" elif field == "sda_size" or field == "sdb_size": if val is None: val = "N/A" elif field in list_type_fields: val = ",".join(str(item) for item in val) elif val is None: val = "-" row[idx] = str(val) data = GenerateTable(separator=opts.separator, headers=headers, fields=selected_fields, unitfields=unitfields, numfields=numfields, data=output, units=opts.units) for line in data: ToStdout(line) return 0 def AddInstance(opts, args): """Add an instance to the cluster. @param opts: the command line options selected by the user @type args: list @param args: should contain only one element, the new instance name @rtype: int @return: the desired exit code """ instance = args[0] (pnode, snode) = SplitNodeOption(opts.node) hypervisor = None hvparams = {} if opts.hypervisor: hypervisor, hvparams = opts.hypervisor if opts.nics: try: nic_max = max(int(nidx[0])+1 for nidx in opts.nics) except ValueError, err: raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err)) nics = [{}] * nic_max for nidx, ndict in opts.nics.items(): nidx = int(nidx) nics[nidx] = ndict else: # default of one nic, all auto nics = [{}] if not opts.disks and opts.disk_template != constants.DT_DISKLESS: raise errors.OpPrereqError("No disk information specified") elif opts.disks and opts.disk_template == constants.DT_DISKLESS: raise errors.OpPrereqError("Diskless instance but disk information passeD") else: try: disk_max = max(int(didx[0])+1 for didx in opts.disks) except ValueError, err: raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err)) disks = [{}] * disk_max for didx, ddict in opts.disks: didx = int(didx) if "size" not in ddict: raise errors.OpPrereqError("Missing size for disk %d" % didx) try: ddict["size"] = utils.ParseUnit(ddict["size"]) except ValueError, err: raise errors.OpPrereqError("Invalid disk size for disk %d: %s" % (didx, err)) disks[didx] = ddict ValidateBeParams(opts.beparams) ## kernel_path = _TransformPath(opts.kernel_path) ## initrd_path = _TransformPath(opts.initrd_path) ## hvm_acpi = opts.hvm_acpi == _VALUE_TRUE ## hvm_pae = opts.hvm_pae == _VALUE_TRUE ## if ((opts.hvm_cdrom_image_path is not None) and ## (opts.hvm_cdrom_image_path.lower() == constants.VALUE_NONE)): ## hvm_cdrom_image_path = None ## else: ## hvm_cdrom_image_path = opts.hvm_cdrom_image_path op = opcodes.OpCreateInstance(instance_name=instance, disks=disks, disk_template=opts.disk_template, nics=nics, mode=constants.INSTANCE_CREATE, os_type=opts.os, pnode=pnode, snode=snode, start=opts.start, ip_check=opts.ip_check, wait_for_sync=opts.wait_for_sync, hypervisor=hypervisor, hvparams=hvparams, beparams=opts.beparams, iallocator=opts.iallocator, file_storage_dir=opts.file_storage_dir, file_driver=opts.file_driver, ) SubmitOrSend(op, opts) return 0 def BatchCreate(opts, args): """Create instances using a definition file. This function reads a json file with instances defined in the form:: {"instance-name":{ "disk_size": 25, "swap_size": 1024, "template": "drbd", "backend": { "memory": 512, "vcpus": 1 }, "os": "etch-image", "primary_node": "firstnode", "secondary_node": "secondnode", "iallocator": "dumb"} } Note that I{primary_node} and I{secondary_node} have precedence over I{iallocator}. @param opts: the command line options selected by the user @type args: list @param args: should contain one element, the json filename @rtype: int @return: the desired exit code """ _DEFAULT_SPECS = {"disk_size": 20 * 1024, "swap_size": 4 * 1024, "backend": {}, "iallocator": None, "primary_node": None, "secondary_node": None, "ip": 'none', "mac": 'auto', "bridge": None, "start": True, "ip_check": True, "hypervisor": None, "file_storage_dir": None, "file_driver": 'loop'} def _PopulateWithDefaults(spec): """Returns a new hash combined with default values.""" mydict = _DEFAULT_SPECS.copy() mydict.update(spec) return mydict def _Validate(spec): """Validate the instance specs.""" # Validate fields required under any circumstances for required_field in ('os', 'template'): if required_field not in spec: raise errors.OpPrereqError('Required field "%s" is missing.' % required_field) # Validate special fields if spec['primary_node'] is not None: if (spec['template'] in constants.DTS_NET_MIRROR and spec['secondary_node'] is None): raise errors.OpPrereqError('Template requires secondary node, but' ' there was no secondary provided.') elif spec['iallocator'] is None: raise errors.OpPrereqError('You have to provide at least a primary_node' ' or an iallocator.') if (spec['hypervisor'] and not isinstance(spec['hypervisor'], dict)): raise errors.OpPrereqError('Hypervisor parameters must be a dict.') json_filename = args[0] fd = open(json_filename, 'r') try: instance_data = simplejson.load(fd) finally: fd.close() # Iterate over the instances and do: # * Populate the specs with default value # * Validate the instance specs for (name, specs) in instance_data.iteritems(): specs = _PopulateWithDefaults(specs) _Validate(specs) hypervisor = None hvparams = {} if specs['hypervisor']: hypervisor, hvparams = specs['hypervisor'].iteritems() op = opcodes.OpCreateInstance(instance_name=name, disk_size=specs['disk_size'], swap_size=specs['swap_size'], 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'], start=specs['start'], ip_check=specs['ip_check'], wait_for_sync=True, mac=specs['mac'], iallocator=specs['iallocator'], hypervisor=hypervisor, hvparams=hvparams, beparams=specs['backend'], file_storage_dir=specs['file_storage_dir'], file_driver=specs['file_driver']) ToStdout("%s: %s", name, cli.SendJob([op])) return 0 def ReinstallInstance(opts, args): """Reinstall an instance. @param opts: the command line options selected by the user @type args: list @param args: should contain only one element, the name of the instance to be reinstalled @rtype: int @return: the desired exit code """ instance_name = args[0] if opts.select_os is True: op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[]) result = SubmitOpCode(op) if not result: ToStdout("Can't get the OS list") return 1 ToStdout("Available OS templates:") number = 0 choices = [] for entry in result: ToStdout("%3s: %s", number, entry[0]) choices.append(("%s" % number, entry[0], entry[0])) number = number + 1 choices.append(('x', 'exit', 'Exit gnt-instance reinstall')) selected = AskUser("Enter OS template name or number (or x to abort):", choices) if selected == 'exit': ToStdout("User aborted reinstall, exiting") return 1 os_name = selected else: os_name = opts.os if not opts.force: usertext = ("This will reinstall the instance %s and remove" " all data. Continue?") % instance_name if not AskUser(usertext): return 1 op = opcodes.OpReinstallInstance(instance_name=instance_name, os_type=os_name) SubmitOrSend(op, opts) return 0 def RemoveInstance(opts, args): """Remove an instance. @param opts: the command line options selected by the user @type args: list @param args: should contain only one element, the name of the instance to be removed @rtype: int @return: the desired exit code """ instance_name = args[0] force = opts.force if not force: usertext = ("This will remove the volumes of the instance %s" " (including mirrors), thus removing all the data" " of the instance. Continue?") % instance_name if not AskUser(usertext): return 1 op = opcodes.OpRemoveInstance(instance_name=instance_name, ignore_failures=opts.ignore_failures) SubmitOrSend(op, opts) return 0 def RenameInstance(opts, args): """Rename an instance. @param opts: the command line options selected by the user @type args: list @param args: should contain two elements, the old and the new instance names @rtype: int @return: the desired exit code """ op = opcodes.OpRenameInstance(instance_name=args[0], new_name=args[1], ignore_ip=opts.ignore_ip) SubmitOrSend(op, opts) return 0 def ActivateDisks(opts, args): """Activate an instance's disks. This serves two purposes: - it allows (as long as the instance is not running) mounting the disks and modifying them from the node - it repairs inactive secondary drbds @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] op = opcodes.OpActivateInstanceDisks(instance_name=instance_name) disks_info = SubmitOrSend(op, opts) for host, iname, nname in disks_info: ToStdout("%s:%s:%s", host, iname, nname) return 0 def DeactivateDisks(opts, args): """Deactivate an instance's disks.. This function takes the instance name, looks for its primary node and the tries to shutdown its block devices on that node. @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] op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name) SubmitOrSend(op, opts) return 0 def GrowDisk(opts, args): """Grow an instance's disks. @param opts: the command line options selected by the user @type args: list @param args: should contain two elements, the instance name whose disks we grow and the disk name, e.g. I{sda} @rtype: int @return: the desired exit code """ instance = args[0] disk = args[1] try: disk = int(disk) except ValueError, err: raise errors.OpPrereqError("Invalid disk index: %s" % str(err)) amount = utils.ParseUnit(args[2]) op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount, wait_for_sync=opts.wait_for_sync) SubmitOrSend(op, opts) return 0 def StartupInstance(opts, args): """Startup instances. Depending on the options given, this will start one or more instances. @param opts: the command line options selected by the user @type args: list @param args: the instance or node names based on which we create the final selection (in conjunction with the opts argument) @rtype: int @return: the desired exit code """ if opts.multi_mode is None: opts.multi_mode = _SHUTDOWN_INSTANCES inames = _ExpandMultiNames(opts.multi_mode, args) if not inames: raise errors.OpPrereqError("Selection filter does not match any instances") multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1 if not (opts.force_multi or not multi_on or _ConfirmOperation(inames, "startup")): return 1 for name in inames: op = opcodes.OpStartupInstance(instance_name=name, force=opts.force, extra_args=opts.extra_args) if multi_on: ToStdout("Starting up %s", name) try: SubmitOrSend(op, opts) except JobSubmittedException, err: _, txt = FormatError(err) ToStdout("%s", txt) return 0 def RebootInstance(opts, args): """Reboot instance(s). Depending on the parameters given, this will reboot one or more instances. @param opts: the command line options selected by the user @type args: list @param args: the instance or node names based on which we create the final selection (in conjunction with the opts argument) @rtype: int @return: the desired exit code """ if opts.multi_mode is None: opts.multi_mode = _SHUTDOWN_INSTANCES inames = _ExpandMultiNames(opts.multi_mode, args) if not inames: raise errors.OpPrereqError("Selection filter does not match any instances") multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1 if not (opts.force_multi or not multi_on or _ConfirmOperation(inames, "reboot")): return 1 for name in inames: op = opcodes.OpRebootInstance(instance_name=name, reboot_type=opts.reboot_type, ignore_secondaries=opts.ignore_secondaries) SubmitOrSend(op, opts) return 0 def ShutdownInstance(opts, args): """Shutdown an instance. @param opts: the command line options selected by the user @type args: list @param args: the instance or node names based on which we create the final selection (in conjunction with the opts argument) @rtype: int @return: the desired exit code """ if opts.multi_mode is None: opts.multi_mode = _SHUTDOWN_INSTANCES inames = _ExpandMultiNames(opts.multi_mode, args) if not inames: raise errors.OpPrereqError("Selection filter does not match any instances") multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1 if not (opts.force_multi or not multi_on or _ConfirmOperation(inames, "shutdown")): return 1 for name in inames: op = opcodes.OpShutdownInstance(instance_name=name) if multi_on: ToStdout("Shutting down %s", name) try: SubmitOrSend(op, opts) except JobSubmittedException, err: _, txt = FormatError(err) ToStdout("%s", txt) return 0 def ReplaceDisks(opts, args): """Replace the disks of an instance @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] new_2ndary = opts.new_secondary iallocator = opts.iallocator if opts.disks is None: disks = [] else: try: 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: 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 mode = constants.REPLACE_DISK_SEC op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks, remote_node=new_2ndary, mode=mode, iallocator=iallocator) SubmitOrSend(op, opts) return 0 def FailoverInstance(opts, args): """Failover an instance. The failover is done by shutting it down on its present node and starting it on the secondary. @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: usertext = ("Failover will happen to image %s." " This requires a shutdown of the instance. Continue?" % (instance_name,)) if not AskUser(usertext): return 1 op = opcodes.OpFailoverInstance(instance_name=instance_name, ignore_consistency=opts.ignore_consistency) SubmitOrSend(op, opts) return 0 def ConnectToInstanceConsole(opts, args): """Connect to the console of an instance. @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] op = opcodes.OpConnectConsole(instance_name=instance_name) cmd = SubmitOpCode(op) if opts.show_command: ToStdout("%s", utils.ShellQuoteArgs(cmd)) else: try: os.execvp(cmd[0], cmd) finally: ToStderr("Can't run console command %s with arguments:\n'%s'", cmd[0], " ".join(cmd)) os._exit(1) def _FormatBlockDevInfo(buf, dev, indent_level, 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 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 """ def helper(buf, 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} """ if not status: buf.write("not active\n") else: (path, major, minor, syncp, estt, degr, ldisk) = status if major is None: major_string = "N/A" else: 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?)" else: ldisk_text = "" buf.write(ldisk_text) buf.write("\n") if dev["iv_name"] is not None: data = " - %s, " % dev["iv_name"] else: data = " - " data += "type: %s" % dev["dev_type"] if dev["logical_id"] is not None: data += ", logical_id: %s" % (dev["logical_id"],) elif dev["physical_id"] is not None: data += ", physical_id: %s" % (dev["physical_id"],) buf.write("%*s%s\n" % (2*indent_level, "", data)) if not static: buf.write("%*s primary: " % (2*indent_level, "")) helper(buf, 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"]) if dev["children"]: for child in dev["children"]: _FormatBlockDevInfo(buf, child, indent_level+1, static) def ShowInstanceConfig(opts, args): """Compute instance run-time status. @param opts: the command line options selected by the user @type args: list @param args: either an empty list, and then we query all instances, or should contain a list of instance names @rtype: int @return: the desired exit code """ retcode = 0 op = opcodes.OpQueryInstanceData(instances=args, static=opts.static) result = SubmitOpCode(op) if not result: ToStdout("No instances.") return 1 buf = StringIO() retcode = 0 for instance_name in result: instance = result[instance_name] buf.write("Instance name: %s\n" % instance["name"]) buf.write("State: configured to be %s" % instance["config_state"]) if not opts.static: buf.write(", actual state is %s" % instance["run_state"]) buf.write("\n") ##buf.write("Considered for memory checks in cluster verify: %s\n" % ## instance["auto_balance"]) buf.write(" Nodes:\n") buf.write(" - primary: %s\n" % instance["pnode"]) buf.write(" - secondaries: %s\n" % ", ".join(instance["snodes"])) buf.write(" Operating system: %s\n" % instance["os"]) if instance.has_key("network_port"): buf.write(" Allocated network port: %s\n" % instance["network_port"]) buf.write(" Hypervisor: %s\n" % instance["hypervisor"]) if instance["hypervisor"] == constants.HT_XEN_PVM: hvattrs = ((constants.HV_KERNEL_PATH, "kernel path"), (constants.HV_INITRD_PATH, "initrd path")) elif instance["hypervisor"] == constants.HT_XEN_HVM: hvattrs = ((constants.HV_BOOT_ORDER, "boot order"), (constants.HV_ACPI, "ACPI"), (constants.HV_PAE, "PAE"), (constants.HV_CDROM_IMAGE_PATH, "virtual CDROM"), (constants.HV_NIC_TYPE, "NIC type"), (constants.HV_DISK_TYPE, "Disk type"), (constants.HV_VNC_BIND_ADDRESS, "VNC bind address"), ) # custom console information for HVM vnc_bind_address = instance["hv_actual"][constants.HV_VNC_BIND_ADDRESS] if vnc_bind_address == constants.BIND_ADDRESS_GLOBAL: vnc_console_port = "%s:%s" % (instance["pnode"], instance["network_port"]) elif vnc_bind_address == constants.LOCALHOST_IP_ADDRESS: vnc_console_port = "%s:%s on node %s" % (vnc_bind_address, instance["network_port"], instance["pnode"]) else: vnc_console_port = "%s:%s" % (vnc_bind_address, instance["network_port"]) buf.write(" - console connection: vnc to %s\n" % vnc_console_port) else: # auto-handle other hypervisor types hvattrs = [(key, key) for key in instance["hv_actual"]] for key, desc in hvattrs: if key in instance["hv_instance"]: val = instance["hv_instance"][key] else: val = "default (%s)" % instance["hv_actual"][key] buf.write(" - %s: %s\n" % (desc, val)) buf.write(" Hardware:\n") buf.write(" - VCPUs: %d\n" % instance["be_actual"][constants.BE_VCPUS]) buf.write(" - memory: %dMiB\n" % instance["be_actual"][constants.BE_MEMORY]) buf.write(" - NICs:\n") 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") for device in instance["disks"]: _FormatBlockDevInfo(buf, device, 1, opts.static) ToStdout(buf.getvalue().rstrip('\n')) return retcode def SetInstanceParams(opts, args): """Modifies an instance. All parameters take effect only at the next restart of the instance. @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 """ if not (opts.ip or opts.bridge or opts.mac or opts.hypervisor or opts.beparams): 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]) op = opcodes.OpSetInstanceParams(instance_name=args[0], ip=opts.ip, bridge=opts.bridge, mac=opts.mac, hvparams=opts.hypervisor, beparams=opts.beparams, force=opts.force) # even if here we process the result, we allow submit only result = SubmitOrSend(op, opts) if result: ToStdout("Modified instance %s", args[0]) for param, data in result: ToStdout(" - %-5s -> %s", param, data) ToStdout("Please don't forget that these parameters take effect" " only at the next start of the instance.") return 0 # options used in more than one cmd node_opt = make_option("-n", "--node", dest="node", help="Target node", metavar="") os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run", metavar="") # multi-instance selection options m_force_multi = make_option("--force-multiple", dest="force_multi", help="Do not ask for confirmation when more than" " one instance is affected", action="store_true", default=False) m_pri_node_opt = make_option("--primary", dest="multi_mode", help="Filter by nodes (primary only)", const=_SHUTDOWN_NODES_PRI, action="store_const") m_sec_node_opt = make_option("--secondary", dest="multi_mode", help="Filter by nodes (secondary only)", const=_SHUTDOWN_NODES_SEC, action="store_const") m_node_opt = make_option("--node", dest="multi_mode", help="Filter by nodes (primary and secondary)", const=_SHUTDOWN_NODES_BOTH, action="store_const") m_clust_opt = make_option("--all", dest="multi_mode", help="Select all instances in the cluster", const=_SHUTDOWN_CLUSTER, action="store_const") m_inst_opt = make_option("--instance", dest="multi_mode", help="Filter by instance name [default]", const=_SHUTDOWN_INSTANCES, action="store_const") # this is defined separately due to readability only add_opts = [ DEBUG_OPT, make_option("-n", "--node", dest="node", help="Target node and optional secondary node", metavar="[:]"), cli_option("-s", "--os-size", dest="size", help="Disk size, in MiB unless" " a suffix is used", default=20 * 1024, type="unit", metavar=""), cli_option("--swap-size", dest="swap", help="Swap size, in MiB unless a" " suffix is used", default=4 * 1024, type="unit", metavar=""), os_opt, keyval_option("-B", "--backend", dest="beparams", type="keyval", default={}, help="Backend parameters"), make_option("-t", "--disk-template", dest="disk_template", help="Custom disk setup (diskless, file, plain or drbd)", default=None, metavar="TEMPL"), ikv_option("--disk", help="Disk information", default=[], dest="disks", action="append", type="identkeyval"), ikv_option("--net", help="NIC information", default=[], dest="nics", action="append", type="identkeyval"), make_option("--no-wait-for-sync", dest="wait_for_sync", default=True, action="store_false", help="Don't wait for sync (DANGEROUS!)"), make_option("--no-start", dest="start", default=True, action="store_false", help="Don't start the instance after" " creation"), make_option("--no-ip-check", dest="ip_check", default=True, action="store_false", help="Don't check that the instance's IP" " is alive (only valid with --no-start)"), make_option("--file-storage-dir", dest="file_storage_dir", help="Relative path under default cluster-wide file storage dir" " to store file-based disks", default=None, metavar=""), make_option("--file-driver", dest="file_driver", help="Driver to use" " for image files", default="loop", metavar=""), make_option("--iallocator", metavar="", help="Select nodes for the instance automatically using the" " iallocator plugin", default=None, type="string"), ikv_option("-H", "--hypervisor", dest="hypervisor", help="Hypervisor and hypervisor options, in the format" " hypervisor:option=value,option=value,...", default=None, type="identkeyval"), SUBMIT_OPT, ] commands = { 'add': (AddInstance, ARGS_ONE, add_opts, "[...] -t disk-type -n node[:secondary-node] -o os-type ", "Creates and adds a new instance to the cluster"), 'batch-create': (BatchCreate, ARGS_ONE, [DEBUG_OPT], "", "Create a bunch of instances based on specs in the file."), 'console': (ConnectToInstanceConsole, ARGS_ONE, [DEBUG_OPT, make_option("--show-cmd", dest="show_command", action="store_true", default=False, help=("Show command instead of executing it"))], "[--show-cmd] ", "Opens a console on the specified instance"), 'failover': (FailoverInstance, ARGS_ONE, [DEBUG_OPT, FORCE_OPT, make_option("--ignore-consistency", dest="ignore_consistency", action="store_true", default=False, help="Ignore the consistency of the disks on" " the secondary"), SUBMIT_OPT, ], "[-f] ", "Stops the instance and starts it on the backup node, using" " the remote mirror (only for instances of type drbd)"), 'info': (ShowInstanceConfig, ARGS_ANY, [DEBUG_OPT, make_option("-s", "--static", dest="static", action="store_true", default=False, help="Only show configuration data, not runtime data"), ], "[-s] [...]", "Show information on the specified instance(s)"), 'list': (ListInstances, ARGS_NONE, [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT], "", "Lists the instances and their status. The available fields are" " (see the man page for details): status, oper_state, oper_ram," " name, os, pnode, snodes, admin_state, admin_ram, disk_template," " ip, mac, bridge, sda_size, sdb_size, vcpus, serial_no," " hypervisor." " The default field" " list is (in order): %s." % ", ".join(_LIST_DEF_FIELDS), ), 'reinstall': (ReinstallInstance, ARGS_ONE, [DEBUG_OPT, FORCE_OPT, os_opt, make_option("--select-os", dest="select_os", action="store_true", default=False, help="Interactive OS reinstall, lists available" " OS templates for selection"), SUBMIT_OPT, ], "[-f] ", "Reinstall a stopped instance"), 'remove': (RemoveInstance, ARGS_ONE, [DEBUG_OPT, FORCE_OPT, make_option("--ignore-failures", dest="ignore_failures", action="store_true", default=False, help=("Remove the instance from the cluster even" " if there are failures during the removal" " process (shutdown, disk removal, etc.)")), SUBMIT_OPT, ], "[-f] ", "Shuts down the instance and removes it"), 'rename': (RenameInstance, ARGS_FIXED(2), [DEBUG_OPT, make_option("--no-ip-check", dest="ignore_ip", help="Do not check that the IP of the new name" " is alive", default=False, action="store_true"), SUBMIT_OPT, ], " ", "Rename the instance"), 'replace-disks': (ReplaceDisks, ARGS_ONE, [DEBUG_OPT, make_option("-n", "--new-secondary", dest="new_secondary", help=("New secondary node (for secondary" " node change)"), metavar="NODE"), make_option("-p", "--on-primary", dest="on_primary", default=False, action="store_true", help=("Replace the disk(s) on the primary" " node (only for the drbd template)")), make_option("-s", "--on-secondary", dest="on_secondary", default=False, action="store_true", help=("Replace the disk(s) on the secondary" " node (only for the drbd template)")), make_option("--disks", dest="disks", default=None, help=("Comma-separated list of disks" " to replace (e.g. sda) (optional," " defaults to all disks")), make_option("--iallocator", metavar="", help="Select new secondary for the instance" " automatically using the" " iallocator plugin (enables" " secondary node replacement)", default=None, type="string"), SUBMIT_OPT, ], "[-s|-p|-n NODE] ", "Replaces all disks for the instance"), 'modify': (SetInstanceParams, ARGS_ONE, [DEBUG_OPT, FORCE_OPT, make_option("-i", "--ip", dest="ip", help="IP address ('none' or numeric IP)", default=None, type="string", metavar="
"), make_option("-b", "--bridge", dest="bridge", help="Bridge to connect this instance to", default=None, type="string", metavar=""), make_option("--mac", dest="mac", help="MAC address", default=None, type="string", metavar=""), keyval_option("-H", "--hypervisor", type="keyval", default={}, dest="hypervisor", help="Change hypervisor parameters"), keyval_option("-B", "--backend", type="keyval", default={}, dest="beparams", help="Change backend parameters"), SUBMIT_OPT, ], "", "Alters the parameters of an instance"), 'shutdown': (ShutdownInstance, ARGS_ANY, [DEBUG_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_force_multi, SUBMIT_OPT, ], "", "Stops an instance"), 'startup': (StartupInstance, ARGS_ANY, [DEBUG_OPT, FORCE_OPT, m_force_multi, make_option("-e", "--extra", dest="extra_args", help="Extra arguments for the instance's kernel", default=None, type="string", metavar=""), m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, ], "", "Starts an instance"), 'reboot': (RebootInstance, ARGS_ANY, [DEBUG_OPT, m_force_multi, make_option("-e", "--extra", dest="extra_args", help="Extra arguments for the instance's kernel", default=None, type="string", metavar=""), make_option("-t", "--type", dest="reboot_type", help="Type of reboot: soft/hard/full", default=constants.INSTANCE_REBOOT_HARD, type="string", metavar=""), make_option("--ignore-secondaries", dest="ignore_secondaries", default=False, action="store_true", help="Ignore errors from secondaries"), m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, ], "", "Reboots an instance"), 'activate-disks': (ActivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT], "", "Activate an instance's disks"), 'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT], "", "Deactivate an instance's disks"), 'grow-disk': (GrowDisk, ARGS_FIXED(3), [DEBUG_OPT, SUBMIT_OPT, make_option("--no-wait-for-sync", dest="wait_for_sync", default=True, action="store_false", help="Don't wait for sync (DANGEROUS!)"), ], " ", "Grow an instance's disk"), 'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT], "", "List the tags of the given instance"), 'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT], " tag...", "Add tags to the given instance"), 'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT], " tag...", "Remove tags from given instance"), } #: dictionary with aliases for commands aliases = { 'activate_block_devs': 'activate-disks', 'replace_disks': 'replace-disks', 'start': 'startup', 'stop': 'shutdown', } if __name__ == '__main__': sys.exit(GenericMain(commands, aliases=aliases, override={"tag_type": constants.TAG_INSTANCE}))