| # |
| # |
| |
| # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2014 Google Inc. |
| # All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are |
| # met: |
| # |
| # 1. Redistributions of source code must retain the above copyright notice, |
| # this list of conditions and the following disclaimer. |
| # |
| # 2. Redistributions in binary form must reproduce the above copyright |
| # notice, this list of conditions and the following disclaimer in the |
| # documentation and/or other materials provided with the distribution. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS |
| # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
| # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR |
| # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
| # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
| # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| """Instance related commands""" |
| |
| # pylint: disable=W0401,W0614,C0103 |
| # W0401: Wildcard import ganeti.cli |
| # W0614: Unused import %s from wildcard import (since we need cli) |
| # C0103: Invalid name gnt-instance |
| |
| import copy |
| import itertools |
| import simplejson |
| import logging |
| |
| from ganeti.cli import * |
| from ganeti import opcodes |
| from ganeti import constants |
| from ganeti import compat |
| from ganeti import utils |
| from ganeti import errors |
| from ganeti import netutils |
| from ganeti import ssh |
| from ganeti import objects |
| from ganeti import ht |
| |
| |
| _EXPAND_CLUSTER = "cluster" |
| _EXPAND_NODES_BOTH = "nodes" |
| _EXPAND_NODES_PRI = "nodes-pri" |
| _EXPAND_NODES_SEC = "nodes-sec" |
| _EXPAND_NODES_BOTH_BY_TAGS = "nodes-by-tags" |
| _EXPAND_NODES_PRI_BY_TAGS = "nodes-pri-by-tags" |
| _EXPAND_NODES_SEC_BY_TAGS = "nodes-sec-by-tags" |
| _EXPAND_INSTANCES = "instances" |
| _EXPAND_INSTANCES_BY_TAGS = "instances-by-tags" |
| |
| _EXPAND_NODES_TAGS_MODES = compat.UniqueFrozenset([ |
| _EXPAND_NODES_BOTH_BY_TAGS, |
| _EXPAND_NODES_PRI_BY_TAGS, |
| _EXPAND_NODES_SEC_BY_TAGS, |
| ]) |
| |
| #: default list of options for L{ListInstances} |
| _LIST_DEF_FIELDS = [ |
| "name", "hypervisor", "os", "pnode", "status", "oper_ram", |
| ] |
| |
| _MISSING = object() |
| _ENV_OVERRIDE = compat.UniqueFrozenset(["list"]) |
| |
| _INST_DATA_VAL = ht.TListOf(ht.TDict) |
| |
| |
| def _ExpandMultiNames(mode, names, client=None): |
| """Expand the given names using the passed mode. |
| |
| For _EXPAND_CLUSTER, all instances will be returned. For |
| _EXPAND_NODES_PRI/SEC, all instances having those nodes as |
| primary/secondary will be returned. For _EXPAND_NODES_BOTH, all |
| instances having those nodes as either primary or secondary will be |
| returned. For _EXPAND_INSTANCES, the given instances will be |
| returned. |
| |
| @param mode: one of L{_EXPAND_CLUSTER}, L{_EXPAND_NODES_BOTH}, |
| L{_EXPAND_NODES_PRI}, L{_EXPAND_NODES_SEC} or |
| L{_EXPAND_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 |
| |
| """ |
| # pylint: disable=W0142 |
| |
| if client is None: |
| client = GetClient() |
| if mode == _EXPAND_CLUSTER: |
| if names: |
| raise errors.OpPrereqError("Cluster filter mode takes no arguments", |
| errors.ECODE_INVAL) |
| idata = client.QueryInstances([], ["name"], False) |
| inames = [row[0] for row in idata] |
| |
| elif (mode in _EXPAND_NODES_TAGS_MODES or |
| mode in (_EXPAND_NODES_BOTH, _EXPAND_NODES_PRI, _EXPAND_NODES_SEC)): |
| if mode in _EXPAND_NODES_TAGS_MODES: |
| if not names: |
| raise errors.OpPrereqError("No node tags passed", errors.ECODE_INVAL) |
| ndata = client.QueryNodes([], ["name", "pinst_list", |
| "sinst_list", "tags"], False) |
| ndata = [row for row in ndata if set(row[3]).intersection(names)] |
| else: |
| if not names: |
| raise errors.OpPrereqError("No node names passed", errors.ECODE_INVAL) |
| ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"], |
| False) |
| |
| 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 in (_EXPAND_NODES_BOTH, _EXPAND_NODES_BOTH_BY_TAGS): |
| inames = pri_names + sec_names |
| elif mode in (_EXPAND_NODES_PRI, _EXPAND_NODES_PRI_BY_TAGS): |
| inames = pri_names |
| elif mode in (_EXPAND_NODES_SEC, _EXPAND_NODES_SEC_BY_TAGS): |
| inames = sec_names |
| else: |
| raise errors.ProgrammerError("Unhandled shutdown type") |
| elif mode == _EXPAND_INSTANCES: |
| if not names: |
| raise errors.OpPrereqError("No instance names passed", |
| errors.ECODE_INVAL) |
| idata = client.QueryInstances(names, ["name"], False) |
| inames = [row[0] for row in idata] |
| elif mode == _EXPAND_INSTANCES_BY_TAGS: |
| if not names: |
| raise errors.OpPrereqError("No instance tags passed", |
| errors.ECODE_INVAL) |
| idata = client.QueryInstances([], ["name", "tags"], False) |
| inames = [row[0] for row in idata if set(row[1]).intersection(names)] |
| else: |
| raise errors.OpPrereqError("Unknown mode '%s'" % mode, errors.ECODE_INVAL) |
| |
| return inames |
| |
| |
| def _EnsureInstancesExist(client, names): |
| """Check for and ensure the given instance names exist. |
| |
| This function will raise an OpPrereqError in case they don't |
| exist. Otherwise it will exit cleanly. |
| |
| @type client: L{ganeti.luxi.Client} |
| @param client: the client to use for the query |
| @type names: list |
| @param names: the list of instance names to query |
| @raise errors.OpPrereqError: in case any instance is missing |
| |
| """ |
| # TODO: change LUInstanceQuery to that it actually returns None |
| # instead of raising an exception, or devise a better mechanism |
| result = client.QueryInstances(names, ["name"], False) |
| for orig_name, row in zip(names, result): |
| if row[0] is None: |
| raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name, |
| errors.ECODE_NOENT) |
| |
| |
| def GenericManyOps(operation, fn): |
| """Generic multi-instance operations. |
| |
| The will return a wrapper that processes the options and arguments |
| given, and uses the passed function to build the opcode needed for |
| the specific operation. Thus all the generic loop/confirmation code |
| is abstracted into this function. |
| |
| """ |
| def realfn(opts, args): |
| if opts.multi_mode is None: |
| opts.multi_mode = _EXPAND_INSTANCES |
| cl = GetClient() |
| inames = _ExpandMultiNames(opts.multi_mode, args, client=cl) |
| if not inames: |
| if opts.multi_mode == _EXPAND_CLUSTER: |
| ToStdout("Cluster is empty, no instances to shutdown") |
| return 0 |
| raise errors.OpPrereqError("Selection filter does not match" |
| " any instances", errors.ECODE_INVAL) |
| multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1 |
| if not (opts.force_multi or not multi_on |
| or ConfirmOperation(inames, "instances", operation)): |
| return 1 |
| jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts) |
| for name in inames: |
| op = fn(name, opts) |
| jex.QueueJob(name, op) |
| results = jex.WaitOrShow(not opts.submit_only) |
| rcode = compat.all(row[0] for row in results) |
| return int(not rcode) |
| return realfn |
| |
| |
| 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 |
| |
| """ |
| selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS) |
| |
| fmtoverride = dict.fromkeys(["tags", "disk.sizes", "nic.macs", "nic.ips", |
| "nic.modes", "nic.links", "nic.bridges", |
| "nic.networks", |
| "snodes", "snodes.group", "snodes.group.uuid"], |
| (lambda value: ",".join(str(item) |
| for item in value), |
| False)) |
| |
| cl = GetClient() |
| |
| return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units, |
| opts.separator, not opts.no_headers, |
| format_override=fmtoverride, verbose=opts.verbose, |
| force_filter=opts.force_filter, cl=cl) |
| |
| |
| def ListInstanceFields(opts, args): |
| """List instance fields. |
| |
| @param opts: the command line options selected by the user |
| @type args: list |
| @param args: fields to list, or empty for all |
| @rtype: int |
| @return: the desired exit code |
| |
| """ |
| return GenericListFields(constants.QR_INSTANCE, args, opts.separator, |
| not opts.no_headers) |
| |
| |
| def AddInstance(opts, args): |
| """Add an instance to the cluster. |
| |
| This is just a wrapper over L{GenericInstanceCreate}. |
| |
| """ |
| return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args) |
| |
| |
| def BatchCreate(opts, args): |
| """Create instances using a definition file. |
| |
| This function reads a json file with L{opcodes.OpInstanceCreate} |
| serialisations. |
| |
| @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 |
| |
| """ |
| (json_filename,) = args |
| cl = GetClient() |
| |
| try: |
| instance_data = simplejson.loads(utils.ReadFile(json_filename)) |
| except Exception, err: # pylint: disable=W0703 |
| ToStderr("Can't parse the instance definition file: %s" % str(err)) |
| return 1 |
| |
| if not _INST_DATA_VAL(instance_data): |
| ToStderr("The instance definition file is not %s" % _INST_DATA_VAL) |
| return 1 |
| |
| instances = [] |
| possible_params = set(opcodes.OpInstanceCreate.GetAllSlots()) |
| for (idx, inst) in enumerate(instance_data): |
| unknown = set(inst.keys()) - possible_params |
| |
| if unknown: |
| # TODO: Suggest closest match for more user friendly experience |
| raise errors.OpPrereqError("Unknown fields in definition %s: %s" % |
| (idx, utils.CommaJoin(unknown)), |
| errors.ECODE_INVAL) |
| |
| op = opcodes.OpInstanceCreate(**inst) # pylint: disable=W0142 |
| op.Validate(False) |
| instances.append(op) |
| |
| op = opcodes.OpInstanceMultiAlloc(iallocator=opts.iallocator, |
| instances=instances) |
| result = SubmitOrSend(op, opts, cl=cl) |
| |
| # Keep track of submitted jobs |
| jex = JobExecutor(cl=cl, opts=opts) |
| |
| for (status, job_id) in result[constants.JOB_IDS_KEY]: |
| jex.AddJobId(None, status, job_id) |
| |
| results = jex.GetResults() |
| bad_cnt = len([row for row in results if not row[0]]) |
| if bad_cnt == 0: |
| ToStdout("All instances created successfully.") |
| rcode = constants.EXIT_SUCCESS |
| else: |
| ToStdout("There were %s errors during the creation.", bad_cnt) |
| rcode = constants.EXIT_FAILURE |
| |
| return rcode |
| |
| |
| 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 |
| |
| """ |
| # first, compute the desired name list |
| if opts.multi_mode is None: |
| opts.multi_mode = _EXPAND_INSTANCES |
| |
| inames = _ExpandMultiNames(opts.multi_mode, args) |
| if not inames: |
| raise errors.OpPrereqError("Selection filter does not match any instances", |
| errors.ECODE_INVAL) |
| |
| # second, if requested, ask for an OS |
| if opts.select_os is True: |
| op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[]) |
| result = SubmitOpCode(op, opts=opts) |
| |
| if not result: |
| ToStdout("Can't get the OS list") |
| return 1 |
| |
| ToStdout("Available OS templates:") |
| number = 0 |
| choices = [] |
| for (name, variants) in result: |
| for entry in CalculateOSNames(name, variants): |
| ToStdout("%3s: %s", number, entry) |
| choices.append(("%s" % number, entry, entry)) |
| number += 1 |
| |
| choices.append(("x", "exit", "Exit gnt-instance reinstall")) |
| selected = AskUser("Enter OS template number (or x to abort):", |
| choices) |
| |
| if selected == "exit": |
| ToStderr("User aborted reinstall, exiting") |
| return 1 |
| |
| os_name = selected |
| os_msg = "change the OS to '%s'" % selected |
| else: |
| os_name = opts.os |
| if opts.os is not None: |
| os_msg = "change the OS to '%s'" % os_name |
| else: |
| os_msg = "keep the same OS" |
| |
| # third, get confirmation: multi-reinstall requires --force-multi, |
| # single-reinstall either --force or --force-multi (--force-multi is |
| # a stronger --force) |
| multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1 |
| if multi_on: |
| warn_msg = ("Note: this will remove *all* data for the" |
| " below instances! It will %s.\n" % os_msg) |
| if not (opts.force_multi or |
| ConfirmOperation(inames, "instances", "reinstall", extra=warn_msg)): |
| return 1 |
| else: |
| if not (opts.force or opts.force_multi): |
| usertext = ("This will reinstall the instance '%s' (and %s) which" |
| " removes all data. Continue?") % (inames[0], os_msg) |
| if not AskUser(usertext): |
| return 1 |
| |
| jex = JobExecutor(verbose=multi_on, opts=opts) |
| for instance_name in inames: |
| op = opcodes.OpInstanceReinstall(instance_name=instance_name, |
| os_type=os_name, |
| force_variant=opts.force_variant, |
| osparams=opts.osparams, |
| osparams_private=opts.osparams_private, |
| osparams_secret=opts.osparams_secret) |
| jex.QueueJob(instance_name, op) |
| |
| results = jex.WaitOrShow(not opts.submit_only) |
| |
| if compat.all(map(compat.fst, results)): |
| return constants.EXIT_SUCCESS |
| else: |
| return constants.EXIT_FAILURE |
| |
| |
| 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 |
| cl = GetClient() |
| |
| if not force: |
| _EnsureInstancesExist(cl, [instance_name]) |
| |
| 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.OpInstanceRemove(instance_name=instance_name, |
| ignore_failures=opts.ignore_failures, |
| shutdown_timeout=opts.shutdown_timeout) |
| SubmitOrSend(op, opts, cl=cl) |
| 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 |
| |
| """ |
| if not opts.name_check: |
| if not AskUser("As you disabled the check of the DNS entry, please verify" |
| " that '%s' is a FQDN. Continue?" % args[1]): |
| return 1 |
| |
| op = opcodes.OpInstanceRename(instance_name=args[0], |
| new_name=args[1], |
| ip_check=opts.ip_check, |
| name_check=opts.name_check) |
| result = SubmitOrSend(op, opts) |
| |
| if result: |
| ToStdout("Instance '%s' renamed to '%s'", args[0], result) |
| |
| 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.OpInstanceActivateDisks(instance_name=instance_name, |
| ignore_size=opts.ignore_size, |
| wait_for_sync=opts.wait_for_sync) |
| 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.OpInstanceDeactivateDisks(instance_name=instance_name, |
| force=opts.force) |
| SubmitOrSend(op, opts) |
| return 0 |
| |
| |
| def RecreateDisks(opts, args): |
| """Recreate an instance's disks. |
| |
| @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] |
| |
| disks = [] |
| |
| if opts.disks: |
| for didx, ddict in opts.disks: |
| didx = int(didx) |
| |
| if not ht.TDict(ddict): |
| msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict) |
| raise errors.OpPrereqError(msg, errors.ECODE_INVAL) |
| |
| if constants.IDISK_SIZE in ddict: |
| try: |
| ddict[constants.IDISK_SIZE] = \ |
| utils.ParseUnit(ddict[constants.IDISK_SIZE]) |
| except ValueError, err: |
| raise errors.OpPrereqError("Invalid disk size for disk %d: %s" % |
| (didx, err), errors.ECODE_INVAL) |
| |
| if constants.IDISK_SPINDLES in ddict: |
| try: |
| ddict[constants.IDISK_SPINDLES] = \ |
| int(ddict[constants.IDISK_SPINDLES]) |
| except ValueError, err: |
| raise errors.OpPrereqError("Invalid spindles for disk %d: %s" % |
| (didx, err), errors.ECODE_INVAL) |
| |
| disks.append((didx, ddict)) |
| |
| # TODO: Verify modifyable parameters (already done in |
| # LUInstanceRecreateDisks, but it'd be nice to have in the client) |
| |
| if opts.node: |
| if opts.iallocator: |
| msg = "At most one of either --nodes or --iallocator can be passed" |
| raise errors.OpPrereqError(msg, errors.ECODE_INVAL) |
| pnode, snode = SplitNodeOption(opts.node) |
| nodes = [pnode] |
| if snode is not None: |
| nodes.append(snode) |
| else: |
| nodes = [] |
| |
| op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name, |
| disks=disks, nodes=nodes, |
| iallocator=opts.iallocator) |
| 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 three elements, the target instance name, |
| the target disk id, and the target growth |
| @rtype: int |
| @return: the desired exit code |
| |
| """ |
| instance = args[0] |
| disk = args[1] |
| try: |
| disk = int(disk) |
| except (TypeError, ValueError), err: |
| raise errors.OpPrereqError("Invalid disk index: %s" % str(err), |
| errors.ECODE_INVAL) |
| try: |
| amount = utils.ParseUnit(args[2]) |
| except errors.UnitParseError: |
| raise errors.OpPrereqError("Can't parse the given amount '%s'" % args[2], |
| errors.ECODE_INVAL) |
| op = opcodes.OpInstanceGrowDisk(instance_name=instance, |
| disk=disk, amount=amount, |
| wait_for_sync=opts.wait_for_sync, |
| absolute=opts.absolute, |
| ignore_ipolicy=opts.ignore_ipolicy |
| ) |
| SubmitOrSend(op, opts) |
| return 0 |
| |
| |
| def _StartupInstance(name, opts): |
| """Startup instances. |
| |
| This returns the opcode to start an instance, and its decorator will |
| wrap this into a loop starting all desired instances. |
| |
| @param name: the name of the instance to act on |
| @param opts: the command line options selected by the user |
| @return: the opcode needed for the operation |
| |
| """ |
| op = opcodes.OpInstanceStartup(instance_name=name, |
| force=opts.force, |
| ignore_offline_nodes=opts.ignore_offline, |
| no_remember=opts.no_remember, |
| startup_paused=opts.startup_paused) |
| # do not add these parameters to the opcode unless they're defined |
| if opts.hvparams: |
| op.hvparams = opts.hvparams |
| if opts.beparams: |
| op.beparams = opts.beparams |
| return op |
| |
| |
| def _RebootInstance(name, opts): |
| """Reboot instance(s). |
| |
| This returns the opcode to reboot an instance, and its decorator |
| will wrap this into a loop rebooting all desired instances. |
| |
| @param name: the name of the instance to act on |
| @param opts: the command line options selected by the user |
| @return: the opcode needed for the operation |
| |
| """ |
| return opcodes.OpInstanceReboot(instance_name=name, |
| reboot_type=opts.reboot_type, |
| ignore_secondaries=opts.ignore_secondaries, |
| shutdown_timeout=opts.shutdown_timeout) |
| |
| |
| def _ShutdownInstance(name, opts): |
| """Shutdown an instance. |
| |
| This returns the opcode to shutdown an instance, and its decorator |
| will wrap this into a loop shutting down all desired instances. |
| |
| @param name: the name of the instance to act on |
| @param opts: the command line options selected by the user |
| @return: the opcode needed for the operation |
| |
| """ |
| return opcodes.OpInstanceShutdown(instance_name=name, |
| force=opts.force, |
| timeout=opts.timeout, |
| ignore_offline_nodes=opts.ignore_offline, |
| no_remember=opts.no_remember) |
| |
| |
| 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 |
| |
| """ |
| new_2ndary = opts.dst_node |
| iallocator = opts.iallocator |
| if opts.disks is None: |
| disks = [] |
| else: |
| try: |
| disks = [int(i) for i in opts.disks.split(",")] |
| except (TypeError, ValueError), err: |
| raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err), |
| errors.ECODE_INVAL) |
| cnt = [opts.on_primary, opts.on_secondary, opts.auto, |
| 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, -a, -n and -I" |
| " options must be passed", errors.ECODE_INVAL) |
| elif opts.on_primary: |
| mode = constants.REPLACE_DISK_PRI |
| elif opts.on_secondary: |
| mode = constants.REPLACE_DISK_SEC |
| elif opts.auto: |
| mode = constants.REPLACE_DISK_AUTO |
| if disks: |
| raise errors.OpPrereqError("Cannot specify disks when using automatic" |
| " mode", errors.ECODE_INVAL) |
| elif new_2ndary is not None or iallocator is not None: |
| # replace secondary |
| mode = constants.REPLACE_DISK_CHG |
| |
| op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks, |
| remote_node=new_2ndary, mode=mode, |
| iallocator=iallocator, |
| early_release=opts.early_release, |
| ignore_ipolicy=opts.ignore_ipolicy) |
| 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 |
| |
| """ |
| cl = GetClient() |
| instance_name = args[0] |
| force = opts.force |
| iallocator = opts.iallocator |
| target_node = opts.dst_node |
| |
| if iallocator and target_node: |
| raise errors.OpPrereqError("Specify either an iallocator (-I), or a target" |
| " node (-n) but not both", errors.ECODE_INVAL) |
| |
| if not force: |
| _EnsureInstancesExist(cl, [instance_name]) |
| |
| 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.OpInstanceFailover(instance_name=instance_name, |
| ignore_consistency=opts.ignore_consistency, |
| shutdown_timeout=opts.shutdown_timeout, |
| iallocator=iallocator, |
| target_node=target_node, |
| ignore_ipolicy=opts.ignore_ipolicy) |
| SubmitOrSend(op, opts, cl=cl) |
| 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 |
| |
| """ |
| cl = GetClient() |
| instance_name = args[0] |
| force = opts.force |
| iallocator = opts.iallocator |
| target_node = opts.dst_node |
| |
| if iallocator and target_node: |
| raise errors.OpPrereqError("Specify either an iallocator (-I), or a target" |
| " node (-n) but not both", errors.ECODE_INVAL) |
| |
| if not force: |
| _EnsureInstancesExist(cl, [instance_name]) |
| |
| 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 += (" might impact the instance if anything goes wrong" |
| " (e.g. due to bugs in the hypervisor). Continue?") |
| if not AskUser(usertext): |
| return 1 |
| |
| # this should be removed once --non-live is deprecated |
| if not opts.live and opts.migration_mode is not None: |
| raise errors.OpPrereqError("Only one of the --non-live and " |
| "--migration-mode options can be passed", |
| errors.ECODE_INVAL) |
| if not opts.live: # --non-live passed |
| mode = constants.HT_MIGRATION_NONLIVE |
| else: |
| mode = opts.migration_mode |
| |
| op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode, |
| cleanup=opts.cleanup, iallocator=iallocator, |
| target_node=target_node, |
| allow_failover=opts.allow_failover, |
| allow_runtime_changes=opts.allow_runtime_chgs, |
| ignore_ipolicy=opts.ignore_ipolicy, |
| ignore_hvversions=opts.ignore_hvversions) |
| SubmitOrSend(op, cl=cl, opts=opts) |
| return 0 |
| |
| |
| def MoveInstance(opts, args): |
| """Move 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 |
| |
| """ |
| cl = GetClient() |
| instance_name = args[0] |
| force = opts.force |
| |
| if not force: |
| usertext = ("Instance %s will be moved." |
| " This requires a shutdown of the instance. Continue?" % |
| (instance_name,)) |
| if not AskUser(usertext): |
| return 1 |
| |
| op = opcodes.OpInstanceMove(instance_name=instance_name, |
| target_node=opts.node, |
| compress=opts.compress, |
| shutdown_timeout=opts.shutdown_timeout, |
| ignore_consistency=opts.ignore_consistency, |
| ignore_ipolicy=opts.ignore_ipolicy) |
| SubmitOrSend(op, opts, cl=cl) |
| 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] |
| |
| cl = GetClient() |
| try: |
| cluster_name = cl.QueryConfigValues(["cluster_name"])[0] |
| idata = cl.QueryInstances([instance_name], ["console", "oper_state"], False) |
| if not idata: |
| raise errors.OpPrereqError("Instance '%s' does not exist" % instance_name, |
| errors.ECODE_NOENT) |
| finally: |
| # Ensure client connection is closed while external commands are run |
| cl.Close() |
| |
| del cl |
| |
| ((console_data, oper_state), ) = idata |
| if not console_data: |
| if oper_state: |
| # Instance is running |
| raise errors.OpExecError("Console information for instance %s is" |
| " unavailable" % instance_name) |
| else: |
| raise errors.OpExecError("Instance %s is not running, can't get console" % |
| instance_name) |
| |
| return _DoConsole(objects.InstanceConsole.FromDict(console_data), |
| opts.show_command, cluster_name) |
| |
| |
| def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout, |
| _runcmd_fn=utils.RunCmd): |
| """Acts based on the result of L{opcodes.OpInstanceConsole}. |
| |
| @type console: L{objects.InstanceConsole} |
| @param console: Console object |
| @type show_command: bool |
| @param show_command: Whether to just display commands |
| @type cluster_name: string |
| @param cluster_name: Cluster name as retrieved from master daemon |
| |
| """ |
| console.Validate() |
| |
| if console.kind == constants.CONS_MESSAGE: |
| feedback_fn(console.message) |
| elif console.kind == constants.CONS_VNC: |
| feedback_fn("Instance %s has VNC listening on %s:%s (display %s)," |
| " URL <vnc://%s:%s/>", |
| console.instance, console.host, console.port, |
| console.display, console.host, console.port) |
| elif console.kind == constants.CONS_SPICE: |
| feedback_fn("Instance %s has SPICE listening on %s:%s", console.instance, |
| console.host, console.port) |
| elif console.kind == constants.CONS_SSH: |
| # Convert to string if not already one |
| if isinstance(console.command, basestring): |
| cmd = console.command |
| else: |
| cmd = utils.ShellQuoteArgs(console.command) |
| |
| srun = ssh.SshRunner(cluster_name=cluster_name) |
| ssh_cmd = srun.BuildCmd(console.host, console.user, cmd, |
| port=console.port, |
| batch=True, quiet=False, tty=True) |
| |
| if show_command: |
| feedback_fn(utils.ShellQuoteArgs(ssh_cmd)) |
| else: |
| result = _runcmd_fn(ssh_cmd, interactive=True) |
| if result.failed: |
| logging.error("Console command \"%s\" failed with reason '%s' and" |
| " output %r", result.cmd, result.fail_reason, |
| result.output) |
| raise errors.OpExecError("Connection to console of instance %s failed," |
| " please check cluster configuration" % |
| console.instance) |
| else: |
| raise errors.GenericError("Unknown console type '%s'" % console.kind) |
| |
| return constants.EXIT_SUCCESS |
| |
| |
| def _FormatDiskDetails(dev_type, dev, roman): |
| """Formats the logical_id of a disk. |
| |
| """ |
| |
| if dev_type == constants.DT_DRBD8: |
| drbd_info = dev["drbd_info"] |
| data = [ |
| ("nodeA", "%s, minor=%s" % |
| (drbd_info["primary_node"], |
| compat.TryToRoman(drbd_info["primary_minor"], |
| convert=roman))), |
| ("nodeB", "%s, minor=%s" % |
| (drbd_info["secondary_node"], |
| compat.TryToRoman(drbd_info["secondary_minor"], |
| convert=roman))), |
| ("port", str(compat.TryToRoman(drbd_info["port"], convert=roman))), |
| ] |
| elif dev_type == constants.DT_PLAIN: |
| vg_name, lv_name = dev["logical_id"] |
| data = ["%s/%s" % (vg_name, lv_name)] |
| else: |
| data = [str(dev["logical_id"])] |
| |
| return data |
| |
| |
| def _FormatBlockDevInfo(idx, top_level, dev, roman): |
| """Show block device information. |
| |
| This is only used by L{ShowInstanceConfig}, but it's too big to be |
| left for an inline definition. |
| |
| @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 roman: boolean |
| @param roman: whether to try to use roman integers |
| @return: a list of either strings, tuples or lists |
| (which should be formatted at a higher indent level) |
| |
| """ |
| def helper(dtype, status): |
| """Format one line for physical device status. |
| |
| @type dtype: str |
| @param dtype: a constant from the L{constants.DTS_BLOCK} set |
| @type status: tuple |
| @param status: a tuple as returned from L{backend.FindBlockDevice} |
| @return: the string representing the status |
| |
| """ |
| if not status: |
| return "not active" |
| txt = "" |
| (path, major, minor, syncp, estt, degr, ldisk_status) = status |
| if major is None: |
| major_string = "N/A" |
| else: |
| major_string = str(compat.TryToRoman(major, convert=roman)) |
| |
| if minor is None: |
| minor_string = "N/A" |
| else: |
| minor_string = str(compat.TryToRoman(minor, convert=roman)) |
| |
| txt += ("%s (%s:%s)" % (path, major_string, minor_string)) |
| if dtype in (constants.DT_DRBD8, ): |
| if syncp is not None: |
| sync_text = "*RECOVERING* %5.2f%%," % syncp |
| if estt: |
| sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman) |
| else: |
| sync_text += " ETA unknown" |
| else: |
| sync_text = "in sync" |
| if degr: |
| degr_text = "*DEGRADED*" |
| else: |
| degr_text = "ok" |
| if ldisk_status == constants.LDS_FAULTY: |
| ldisk_text = " *MISSING DISK*" |
| elif ldisk_status == constants.LDS_UNKNOWN: |
| ldisk_text = " *UNCERTAIN STATE*" |
| else: |
| ldisk_text = "" |
| txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text)) |
| elif dtype == constants.DT_PLAIN: |
| if ldisk_status == constants.LDS_FAULTY: |
| 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 %s" % compat.TryToRoman(idx, convert=roman) |
| else: |
| txt = "child %s" % compat.TryToRoman(idx, convert=roman) |
| if isinstance(dev["size"], int): |
| nice_size = utils.FormatUnit(dev["size"], "h", roman) |
| else: |
| nice_size = str(dev["size"]) |
| data = [(txt, "%s, size %s" % (dev["dev_type"], nice_size))] |
| if top_level: |
| if dev["spindles"] is not None: |
| data.append(("spindles", dev["spindles"])) |
| data.append(("access mode", dev["mode"])) |
| if dev["logical_id"] is not None: |
| try: |
| l_id = _FormatDiskDetails(dev["dev_type"], dev, roman) |
| 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) |
| |
| if dev["pstatus"]: |
| data.append(("on primary", helper(dev["dev_type"], dev["pstatus"]))) |
| |
| if dev["sstatus"]: |
| data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"]))) |
| |
| data.append(("name", dev["name"])) |
| data.append(("UUID", dev["uuid"])) |
| |
| if dev["children"]: |
| data.append(("child devices", [ |
| _FormatBlockDevInfo(c_idx, False, child, roman) |
| for c_idx, child in enumerate(dev["children"]) |
| ])) |
| return data |
| |
| |
| def _FormatInstanceNicInfo(idx, nic, roman=False): |
| """Helper function for L{_FormatInstanceInfo()}""" |
| (name, uuid, ip, mac, mode, link, vlan, _, netinfo) = nic |
| network_name = None |
| if netinfo: |
| network_name = netinfo["name"] |
| return [ |
| ("nic/%s" % str(compat.TryToRoman(idx, roman)), ""), |
| ("MAC", str(mac)), |
| ("IP", str(ip)), |
| ("mode", str(mode)), |
| ("link", str(link)), |
| ("vlan", str(compat.TryToRoman(vlan, roman))), |
| ("network", str(network_name)), |
| ("UUID", str(uuid)), |
| ("name", str(name)), |
| ] |
| |
| |
| def _FormatInstanceNodesInfo(instance): |
| """Helper function for L{_FormatInstanceInfo()}""" |
| pgroup = ("%s (UUID %s)" % |
| (instance["pnode_group_name"], instance["pnode_group_uuid"])) |
| secs = utils.CommaJoin(("%s (group %s, group UUID %s)" % |
| (name, group_name, group_uuid)) |
| for (name, group_name, group_uuid) in |
| zip(instance["snodes"], |
| instance["snodes_group_names"], |
| instance["snodes_group_uuids"])) |
| return [ |
| [ |
| ("primary", instance["pnode"]), |
| ("group", pgroup), |
| ], |
| [("secondaries", secs)], |
| ] |
| |
| |
| def _GetVncConsoleInfo(instance): |
| """Helper function for L{_FormatInstanceInfo()}""" |
| vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS, |
| None) |
| if vnc_bind_address: |
| port = instance["network_port"] |
| display = int(port) - constants.VNC_BASE_PORT |
| if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY: |
| vnc_console_port = "%s:%s (display %s)" % (instance["pnode"], |
| port, |
| display) |
| elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address): |
| vnc_console_port = ("%s:%s (node %s) (display %s)" % |
| (vnc_bind_address, port, |
| instance["pnode"], display)) |
| else: |
| # vnc bind address is a file |
| vnc_console_port = "%s:%s" % (instance["pnode"], |
| vnc_bind_address) |
| ret = "vnc to %s" % vnc_console_port |
| else: |
| ret = None |
| return ret |
| |
| |
| def _FormatInstanceInfo(instance, roman_integers): |
| """Format instance information for L{cli.PrintGenericInfo()}""" |
| istate = "configured to be %s" % instance["config_state"] |
| if instance["run_state"]: |
| istate += ", actual state is %s" % instance["run_state"] |
| info = [ |
| ("Instance name", instance["name"]), |
| ("UUID", instance["uuid"]), |
| ("Serial number", |
| str(compat.TryToRoman(instance["serial_no"], convert=roman_integers))), |
| ("Creation time", utils.FormatTime(instance["ctime"])), |
| ("Modification time", utils.FormatTime(instance["mtime"])), |
| ("State", istate), |
| ("Nodes", _FormatInstanceNodesInfo(instance)), |
| ("Operating system", instance["os"]), |
| ("Operating system parameters", |
| FormatParamsDictInfo(instance["os_instance"], instance["os_actual"], |
| roman_integers)), |
| ] |
| |
| if "network_port" in instance: |
| info.append(("Allocated network port", |
| str(compat.TryToRoman(instance["network_port"], |
| convert=roman_integers)))) |
| info.append(("Hypervisor", instance["hypervisor"])) |
| console = _GetVncConsoleInfo(instance) |
| if console: |
| info.append(("console connection", console)) |
| # deprecated "memory" value, kept for one version for compatibility |
| # TODO(ganeti 2.7) remove. |
| be_actual = copy.deepcopy(instance["be_actual"]) |
| be_actual["memory"] = be_actual[constants.BE_MAXMEM] |
| info.extend([ |
| ("Hypervisor parameters", |
| FormatParamsDictInfo(instance["hv_instance"], instance["hv_actual"], |
| roman_integers)), |
| ("Back-end parameters", |
| FormatParamsDictInfo(instance["be_instance"], be_actual, |
| roman_integers)), |
| ("NICs", [ |
| _FormatInstanceNicInfo(idx, nic, roman_integers) |
| for (idx, nic) in enumerate(instance["nics"]) |
| ]), |
| ("Disk template", instance["disk_template"]), |
| ("Disks", [ |
| _FormatBlockDevInfo(idx, True, device, roman_integers) |
| for (idx, device) in enumerate(instance["disks"]) |
| ]), |
| ]) |
| return info |
| |
| |
| 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 |
| |
| """ |
| if not args and not opts.show_all: |
| ToStderr("No instance selected." |
| " Please pass in --all if you want to query all instances.\n" |
| "Note that this can take a long time on a big cluster.") |
| return 1 |
| elif args and opts.show_all: |
| ToStderr("Cannot use --all if you specify instance names.") |
| return 1 |
| |
| retcode = 0 |
| op = opcodes.OpInstanceQueryData(instances=args, static=opts.static, |
| use_locking=not opts.static) |
| result = SubmitOpCode(op, opts=opts) |
| if not result: |
| ToStdout("No instances.") |
| return 1 |
| |
| PrintGenericInfo([ |
| _FormatInstanceInfo(instance, opts.roman_integers) |
| for instance in result.values() |
| ]) |
| return retcode |
| |
| |
| def _ConvertNicDiskModifications(mods): |
| """Converts NIC/disk modifications from CLI to opcode. |
| |
| When L{opcodes.OpInstanceSetParams} was changed to support adding/removing |
| disks at arbitrary indices, its parameter format changed. This function |
| converts legacy requests (e.g. "--net add" or "--disk add:size=4G") to the |
| newer format and adds support for new-style requests (e.g. "--new 4:add"). |
| |
| @type mods: list of tuples |
| @param mods: Modifications as given by command line parser |
| @rtype: list of tuples |
| @return: Modifications as understood by L{opcodes.OpInstanceSetParams} |
| |
| """ |
| result = [] |
| |
| for (identifier, params) in mods: |
| if identifier == constants.DDM_ADD: |
| # Add item as last item (legacy interface) |
| action = constants.DDM_ADD |
| identifier = -1 |
| elif identifier == constants.DDM_REMOVE: |
| # Remove last item (legacy interface) |
| action = constants.DDM_REMOVE |
| identifier = -1 |
| else: |
| # Modifications and adding/removing at arbitrary indices |
| add = params.pop(constants.DDM_ADD, _MISSING) |
| remove = params.pop(constants.DDM_REMOVE, _MISSING) |
| modify = params.pop(constants.DDM_MODIFY, _MISSING) |
| |
| if modify is _MISSING: |
| if not (add is _MISSING or remove is _MISSING): |
| raise errors.OpPrereqError("Cannot add and remove at the same time", |
| errors.ECODE_INVAL) |
| elif add is not _MISSING: |
| action = constants.DDM_ADD |
| elif remove is not _MISSING: |
| action = constants.DDM_REMOVE |
| else: |
| action = constants.DDM_MODIFY |
| |
| elif add is _MISSING and remove is _MISSING: |
| action = constants.DDM_MODIFY |
| else: |
| raise errors.OpPrereqError("Cannot modify and add/remove at the" |
| " same time", errors.ECODE_INVAL) |
| |
| assert not (constants.DDMS_VALUES_WITH_MODIFY & set(params.keys())) |
| |
| if action == constants.DDM_REMOVE and params: |
| raise errors.OpPrereqError("Not accepting parameters on removal", |
| errors.ECODE_INVAL) |
| |
| result.append((action, identifier, params)) |
| |
| return result |
| |
| |
| def _ParseExtStorageParams(params): |
| """Parses the disk params for ExtStorage conversions. |
| |
| """ |
| if params: |
| if constants.IDISK_PROVIDER not in params: |
| raise errors.OpPrereqError("Missing required parameter '%s' when" |
| " converting to an ExtStorage disk template" % |
| constants.IDISK_PROVIDER, errors.ECODE_INVAL) |
| else: |
| for param in params.keys(): |
| if (param != constants.IDISK_PROVIDER and |
| param in constants.IDISK_PARAMS): |
| raise errors.OpPrereqError("Invalid parameter '%s' when converting" |
| " to an ExtStorage template (it is not" |
| " allowed modifying existing disk" |
| " parameters)" % param, errors.ECODE_INVAL) |
| |
| return params |
| |
| |
| def _ParseDiskSizes(mods): |
| """Parses disk sizes in parameters. |
| |
| """ |
| for (action, _, params) in mods: |
| if params and constants.IDISK_SPINDLES in params: |
| params[constants.IDISK_SPINDLES] = \ |
| int(params[constants.IDISK_SPINDLES]) |
| if params and constants.IDISK_SIZE in params: |
| params[constants.IDISK_SIZE] = \ |
| utils.ParseUnit(params[constants.IDISK_SIZE]) |
| elif action == constants.DDM_ADD: |
| raise errors.OpPrereqError("Missing required parameter 'size'", |
| errors.ECODE_INVAL) |
| |
| return mods |
| |
| |
| 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.nics or opts.disks or opts.disk_template or opts.hvparams or |
| opts.beparams or opts.os or opts.osparams or opts.osparams_private |
| or opts.offline_inst or opts.online_inst or opts.runtime_mem or |
| opts.new_primary_node or opts.instance_communication is not None): |
| ToStderr("Please give at least one of the parameters.") |
| return 1 |
| |
| for param in opts.beparams: |
| if isinstance(opts.beparams[param], basestring): |
| if opts.beparams[param].lower() == "default": |
| opts.beparams[param] = constants.VALUE_DEFAULT |
| |
| utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT, |
| allowed_values=[constants.VALUE_DEFAULT]) |
| |
| for param in opts.hvparams: |
| if isinstance(opts.hvparams[param], basestring): |
| if opts.hvparams[param].lower() == "default": |
| opts.hvparams[param] = constants.VALUE_DEFAULT |
| |
| utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES, |
| allowed_values=[constants.VALUE_DEFAULT]) |
| FixHvParams(opts.hvparams) |
| |
| nics = _ConvertNicDiskModifications(opts.nics) |
| for action, _, __ in nics: |
| if action == constants.DDM_MODIFY and opts.hotplug and not opts.force: |
| usertext = ("You are about to hot-modify a NIC. This will be done" |
| " by removing the existing NIC and then adding a new one." |
| " Network connection might be lost. Continue?") |
| if not AskUser(usertext): |
| return 1 |
| |
| disks = _ParseDiskSizes(_ConvertNicDiskModifications(opts.disks)) |
| |
| # verify the user provided parameters for disk template conversions |
| if opts.disk_template: |
| if (not opts.node and |
| opts.disk_template in constants.DTS_INT_MIRROR): |
| ToStderr("Changing the disk template to a mirrored one requires" |
| " specifying a secondary node") |
| return 1 |
| elif (opts.ext_params and |
| opts.disk_template != constants.DT_EXT): |
| ToStderr("Specifying ExtStorage parameters requires converting" |
| " to the '%s' disk template" % constants.DT_EXT) |
| return 1 |
| elif (not opts.ext_params and |
| opts.disk_template == constants.DT_EXT): |
| ToStderr("Provider option is missing, use either the" |
| " '--ext-params' or '-e' option") |
| return 1 |
| |
| if ((opts.file_driver or |
| opts.file_storage_dir) and |
| not opts.disk_template in constants.DTS_FILEBASED): |
| ToStderr("Specifying file-based configuration arguments requires" |
| " converting to a file-based disk template") |
| return 1 |
| |
| ext_params = _ParseExtStorageParams(opts.ext_params) |
| |
| if opts.offline_inst: |
| offline = True |
| elif opts.online_inst: |
| offline = False |
| else: |
| offline = None |
| |
| instance_comm = opts.instance_communication |
| |
| op = opcodes.OpInstanceSetParams(instance_name=args[0], |
| nics=nics, |
| disks=disks, |
| hotplug=opts.hotplug, |
| hotplug_if_possible=opts.hotplug_if_possible, |
| disk_template=opts.disk_template, |
| ext_params=ext_params, |
| file_driver=opts.file_driver, |
| file_storage_dir=opts.file_storage_dir, |
| remote_node=opts.node, |
| pnode=opts.new_primary_node, |
| hvparams=opts.hvparams, |
| beparams=opts.beparams, |
| runtime_mem=opts.runtime_mem, |
| os_name=opts.os, |
| osparams=opts.osparams, |
| osparams_private=opts.osparams_private, |
| force_variant=opts.force_variant, |
| force=opts.force, |
| wait_for_sync=opts.wait_for_sync, |
| offline=offline, |
| conflicts_check=opts.conflicts_check, |
| ignore_ipolicy=opts.ignore_ipolicy, |
| instance_communication=instance_comm) |
| |
| # 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 most parameters take effect" |
| " only at the next (re)start of the instance initiated by" |
| " ganeti; restarting from within the instance will" |
| " not be enough.") |
| if opts.hvparams: |
| ToStdout("Note that changing hypervisor parameters without performing a" |
| " restart might lead to a crash while performing a live" |
| " migration. This will be addressed in future Ganeti versions.") |
| return 0 |
| |
| |
| def ChangeGroup(opts, args): |
| """Moves an instance to another group. |
| |
| """ |
| (instance_name, ) = args |
| |
| cl = GetClient() |
| |
| op = opcodes.OpInstanceChangeGroup(instance_name=instance_name, |
| iallocator=opts.iallocator, |
| target_groups=opts.to, |
| early_release=opts.early_release) |
| result = SubmitOrSend(op, opts, cl=cl) |
| |
| # Keep track of submitted jobs |
| jex = JobExecutor(cl=cl, opts=opts) |
| |
| for (status, job_id) in result[constants.JOB_IDS_KEY]: |
| jex.AddJobId(None, status, job_id) |
| |
| results = jex.GetResults() |
| bad_cnt = len([row for row in results if not row[0]]) |
| if bad_cnt == 0: |
| ToStdout("Instance '%s' changed group successfully.", instance_name) |
| rcode = constants.EXIT_SUCCESS |
| else: |
| ToStdout("There were %s errors while changing group of instance '%s'.", |
| bad_cnt, instance_name) |
| rcode = constants.EXIT_FAILURE |
| |
| return rcode |
| |
| |
| # multi-instance selection options |
| m_force_multi = cli_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 = cli_option("--primary", dest="multi_mode", |
| help="Filter by nodes (primary only)", |
| const=_EXPAND_NODES_PRI, action="store_const") |
| |
| m_sec_node_opt = cli_option("--secondary", dest="multi_mode", |
| help="Filter by nodes (secondary only)", |
| const=_EXPAND_NODES_SEC, action="store_const") |
| |
| m_node_opt = cli_option("--node", dest="multi_mode", |
| help="Filter by nodes (primary and secondary)", |
| const=_EXPAND_NODES_BOTH, action="store_const") |
| |
| m_clust_opt = cli_option("--all", dest="multi_mode", |
| help="Select all instances in the cluster", |
| const=_EXPAND_CLUSTER, action="store_const") |
| |
| m_inst_opt = cli_option("--instance", dest="multi_mode", |
| help="Filter by instance name [default]", |
| const=_EXPAND_INSTANCES, action="store_const") |
| |
| m_node_tags_opt = cli_option("--node-tags", dest="multi_mode", |
| help="Filter by node tag", |
| const=_EXPAND_NODES_BOTH_BY_TAGS, |
| action="store_const") |
| |
| m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode", |
| help="Filter by primary node tag", |
| const=_EXPAND_NODES_PRI_BY_TAGS, |
| action="store_const") |
| |
| m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode", |
| help="Filter by secondary node tag", |
| const=_EXPAND_NODES_SEC_BY_TAGS, |
| action="store_const") |
| |
| m_inst_tags_opt = cli_option("--tags", dest="multi_mode", |
| help="Filter by instance tag", |
| const=_EXPAND_INSTANCES_BY_TAGS, |
| action="store_const") |
| |
| # this is defined separately due to readability only |
| add_opts = [ |
| NOSTART_OPT, |
| OS_OPT, |
| FORCE_VARIANT_OPT, |
| NO_INSTALL_OPT, |
| IGNORE_IPOLICY_OPT, |
| INSTANCE_COMMUNICATION_OPT, |
| HELPER_STARTUP_TIMEOUT_OPT, |
| HELPER_SHUTDOWN_TIMEOUT_OPT, |
| ] |
| |
| commands = { |
| "add": ( |
| AddInstance, [ArgHost(min=1, max=1)], |
| COMMON_CREATE_OPTS + add_opts, |
| "[...] -t disk-type -n node[:secondary-node] -o os-type <name>", |
| "Creates and adds a new instance to the cluster"), |
| "batch-create": ( |
| BatchCreate, [ArgFile(min=1, max=1)], |
| [DRY_RUN_OPT, PRIORITY_OPT, IALLOCATOR_OPT] + SUBMIT_OPTS, |
| "<instances.json>", |
| "Create a bunch of instances based on specs in the file."), |
| "console": ( |
| ConnectToInstanceConsole, ARGS_ONE_INSTANCE, |
| [SHOWCMD_OPT, PRIORITY_OPT], |
| "[--show-cmd] <instance>", "Opens a console on the specified instance"), |
| "failover": ( |
| FailoverInstance, ARGS_ONE_INSTANCE, |
| [FORCE_OPT, IGNORE_CONSIST_OPT] + SUBMIT_OPTS + |
| [SHUTDOWN_TIMEOUT_OPT, |
| DRY_RUN_OPT, PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT, |
| IGNORE_IPOLICY_OPT, CLEANUP_OPT], |
| "[-f] <instance>", "Stops the instance, changes its primary node and" |
| " (if it was originally running) starts it on the new node" |
| " (the secondary for mirrored instances or any node" |
| " for shared storage)."), |
| "migrate": ( |
| MigrateInstance, ARGS_ONE_INSTANCE, |
| [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT, |
| PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT, ALLOW_FAILOVER_OPT, |
| IGNORE_IPOLICY_OPT, IGNORE_HVVERSIONS_OPT, NORUNTIME_CHGS_OPT] |
| + SUBMIT_OPTS, |
| "[-f] <instance>", "Migrate instance to its secondary node" |
| " (only for mirrored instances)"), |
| "move": ( |
| MoveInstance, ARGS_ONE_INSTANCE, |
| [FORCE_OPT] + SUBMIT_OPTS + |
| [SINGLE_NODE_OPT, COMPRESS_OPT, |
| SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_CONSIST_OPT, |
| IGNORE_IPOLICY_OPT], |
| "[-f] <instance>", "Move instance to an arbitrary node" |
| " (only for instances of type file and lv)"), |
| "info": ( |
| ShowInstanceConfig, ARGS_MANY_INSTANCES, |
| [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT], |
| "[-s] {--all | <instance>...}", |
| "Show information on the specified instance(s)"), |
| "list": ( |
| ListInstances, ARGS_MANY_INSTANCES, |
| [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT, |
| FORCE_FILTER_OPT], |
| "[<instance>...]", |
| "Lists the instances and their status. The available fields can be shown" |
| " using the \"list-fields\" command (see the man page for details)." |
| " The default field list is (in order): %s." % |
| utils.CommaJoin(_LIST_DEF_FIELDS), |
| ), |
| "list-fields": ( |
| ListInstanceFields, [ArgUnknown()], |
| [NOHDR_OPT, SEP_OPT], |
| "[fields...]", |
| "Lists all available fields for instances"), |
| "reinstall": ( |
| ReinstallInstance, [ArgInstance()], |
| [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt, |
| m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt, |
| m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT] |
| + SUBMIT_OPTS + [DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT, |
| OSPARAMS_PRIVATE_OPT, OSPARAMS_SECRET_OPT], |
| "[-f] <instance>", "Reinstall a stopped instance"), |
| "remove": ( |
| RemoveInstance, ARGS_ONE_INSTANCE, |
| [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT] + SUBMIT_OPTS |
| + [DRY_RUN_OPT, PRIORITY_OPT], |
| "[-f] <instance>", "Shuts down the instance and removes it"), |
| "rename": ( |
| RenameInstance, |
| [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)], |
| [NOIPCHECK_OPT, NONAMECHECK_OPT] + SUBMIT_OPTS |
| + [DRY_RUN_OPT, PRIORITY_OPT], |
| "<instance> <new_name>", "Rename the instance"), |
| "replace-disks": ( |
| ReplaceDisks, ARGS_ONE_INSTANCE, |
| [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT, |
| NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT] + SUBMIT_OPTS |
| + [DRY_RUN_OPT, PRIORITY_OPT, IGNORE_IPOLICY_OPT], |
| "[-s|-p|-a|-n NODE|-I NAME] <instance>", |
| "Replaces disks for the instance"), |
| "modify": ( |
| SetInstanceParams, ARGS_ONE_INSTANCE, |
| [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT] + SUBMIT_OPTS + |
| [DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT, |
| OSPARAMS_OPT, OSPARAMS_PRIVATE_OPT, DRY_RUN_OPT, PRIORITY_OPT, NWSYNC_OPT, |
| OFFLINE_INST_OPT, ONLINE_INST_OPT, IGNORE_IPOLICY_OPT, RUNTIME_MEM_OPT, |
| NOCONFLICTSCHECK_OPT, NEW_PRIMARY_OPT, HOTPLUG_OPT, |
| HOTPLUG_IF_POSSIBLE_OPT, INSTANCE_COMMUNICATION_OPT, |
| EXT_PARAMS_OPT, FILESTORE_DRIVER_OPT, FILESTORE_DIR_OPT], |
| "<instance>", "Alters the parameters of an instance"), |
| "shutdown": ( |
| GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()], |
| [FORCE_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt, |
| m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt, |
| m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT] + SUBMIT_OPTS |
| + [DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT, NO_REMEMBER_OPT], |
| "<instance>", "Stops an instance"), |
| "startup": ( |
| GenericManyOps("startup", _StartupInstance), [ArgInstance()], |
| [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt, |
| m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt, |
| m_inst_tags_opt, m_clust_opt, m_inst_opt] + SUBMIT_OPTS + |
| [HVOPTS_OPT, |
| BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT, |
| NO_REMEMBER_OPT, STARTUP_PAUSED_OPT], |
| "<instance>", "Starts an instance"), |
| "reboot": ( |
| GenericManyOps("reboot", _RebootInstance), [ArgInstance()], |
| [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt, |
| m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt] + SUBMIT_OPTS + |
| [m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt, |
| m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT], |
| "<instance>", "Reboots an instance"), |
| "activate-disks": ( |
| ActivateDisks, ARGS_ONE_INSTANCE, |
| SUBMIT_OPTS + [IGNORE_SIZE_OPT, PRIORITY_OPT, WFSYNC_OPT], |
| "<instance>", "Activate an instance's disks"), |
| "deactivate-disks": ( |
| DeactivateDisks, ARGS_ONE_INSTANCE, |
| [FORCE_OPT] + SUBMIT_OPTS + [DRY_RUN_OPT, PRIORITY_OPT], |
| "[-f] <instance>", "Deactivate an instance's disks"), |
| "recreate-disks": ( |
| RecreateDisks, ARGS_ONE_INSTANCE, |
| SUBMIT_OPTS + |
| [DISK_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT, |
| IALLOCATOR_OPT], |
| "<instance>", "Recreate an instance's disks"), |
| "grow-disk": ( |
| GrowDisk, |
| [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1), |
| ArgUnknown(min=1, max=1)], |
| SUBMIT_OPTS + |
| [NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT, ABSOLUTE_OPT, IGNORE_IPOLICY_OPT], |
| "<instance> <disk> <size>", "Grow an instance's disk"), |
| "change-group": ( |
| ChangeGroup, ARGS_ONE_INSTANCE, |
| [TO_GROUP_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT, PRIORITY_OPT] |
| + SUBMIT_OPTS, |
| "[-I <iallocator>] [--to <group>]", "Change group of instance"), |
| "list-tags": ( |
| ListTags, ARGS_ONE_INSTANCE, [], |
| "<instance_name>", "List the tags of the given instance"), |
| "add-tags": ( |
| AddTags, [ArgInstance(min=1, max=1), ArgUnknown()], |
| [TAG_SRC_OPT, PRIORITY_OPT] + SUBMIT_OPTS, |
| "<instance_name> tag...", "Add tags to the given instance"), |
| "remove-tags": ( |
| RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()], |
| [TAG_SRC_OPT, PRIORITY_OPT] + SUBMIT_OPTS, |
| "<instance_name> tag...", "Remove tags from given instance"), |
| } |
| |
| #: dictionary with aliases for commands |
| aliases = { |
| "start": "startup", |
| "stop": "shutdown", |
| "show": "info", |
| } |
| |
| |
| def Main(): |
| return GenericMain(commands, aliases=aliases, |
| override={"tag_type": constants.TAG_INSTANCE}, |
| env_override=_ENV_OVERRIDE) |