blob: 1beb3f34a601681ce719e1adef639b35f47549cb [file] [log] [blame]
#
#
# 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)