4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 """Instance related commands"""
23 # pylint: disable=W0401,W0614,C0103
24 # W0401: Wildcard import ganeti.cli
25 # W0614: Unused import %s from wildcard import (since we need cli)
26 # C0103: Invalid name gnt-instance
31 from cStringIO import StringIO
33 from ganeti.cli import *
34 from ganeti import opcodes
35 from ganeti import constants
36 from ganeti import compat
37 from ganeti import utils
38 from ganeti import errors
39 from ganeti import netutils
40 from ganeti import ssh
41 from ganeti import objects
44 _EXPAND_CLUSTER = "cluster"
45 _EXPAND_NODES_BOTH = "nodes"
46 _EXPAND_NODES_PRI = "nodes-pri"
47 _EXPAND_NODES_SEC = "nodes-sec"
48 _EXPAND_NODES_BOTH_BY_TAGS = "nodes-by-tags"
49 _EXPAND_NODES_PRI_BY_TAGS = "nodes-pri-by-tags"
50 _EXPAND_NODES_SEC_BY_TAGS = "nodes-sec-by-tags"
51 _EXPAND_INSTANCES = "instances"
52 _EXPAND_INSTANCES_BY_TAGS = "instances-by-tags"
54 _EXPAND_NODES_TAGS_MODES = frozenset([
55 _EXPAND_NODES_BOTH_BY_TAGS,
56 _EXPAND_NODES_PRI_BY_TAGS,
57 _EXPAND_NODES_SEC_BY_TAGS,
61 #: default list of options for L{ListInstances}
63 "name", "hypervisor", "os", "pnode", "status", "oper_ram",
67 _ENV_OVERRIDE = frozenset(["list"])
70 def _ExpandMultiNames(mode, names, client=None):
71 """Expand the given names using the passed mode.
73 For _EXPAND_CLUSTER, all instances will be returned. For
74 _EXPAND_NODES_PRI/SEC, all instances having those nodes as
75 primary/secondary will be returned. For _EXPAND_NODES_BOTH, all
76 instances having those nodes as either primary or secondary will be
77 returned. For _EXPAND_INSTANCES, the given instances will be
80 @param mode: one of L{_EXPAND_CLUSTER}, L{_EXPAND_NODES_BOTH},
81 L{_EXPAND_NODES_PRI}, L{_EXPAND_NODES_SEC} or
83 @param names: a list of names; for cluster, it must be empty,
84 and for node and instance it must be a list of valid item
85 names (short names are valid as usual, e.g. node1 instead of
88 @return: the list of names after the expansion
89 @raise errors.ProgrammerError: for unknown selection type
90 @raise errors.OpPrereqError: for invalid input parameters
93 # pylint: disable=W0142
97 if mode == _EXPAND_CLUSTER:
99 raise errors.OpPrereqError("Cluster filter mode takes no arguments",
101 idata = client.QueryInstances([], ["name"], False)
102 inames = [row[0] for row in idata]
104 elif (mode in _EXPAND_NODES_TAGS_MODES or
105 mode in (_EXPAND_NODES_BOTH, _EXPAND_NODES_PRI, _EXPAND_NODES_SEC)):
106 if mode in _EXPAND_NODES_TAGS_MODES:
108 raise errors.OpPrereqError("No node tags passed", errors.ECODE_INVAL)
109 ndata = client.QueryNodes([], ["name", "pinst_list",
110 "sinst_list", "tags"], False)
111 ndata = [row for row in ndata if set(row[3]).intersection(names)]
114 raise errors.OpPrereqError("No node names passed", errors.ECODE_INVAL)
115 ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
118 ipri = [row[1] for row in ndata]
119 pri_names = list(itertools.chain(*ipri))
120 isec = [row[2] for row in ndata]
121 sec_names = list(itertools.chain(*isec))
122 if mode in (_EXPAND_NODES_BOTH, _EXPAND_NODES_BOTH_BY_TAGS):
123 inames = pri_names + sec_names
124 elif mode in (_EXPAND_NODES_PRI, _EXPAND_NODES_PRI_BY_TAGS):
126 elif mode in (_EXPAND_NODES_SEC, _EXPAND_NODES_SEC_BY_TAGS):
129 raise errors.ProgrammerError("Unhandled shutdown type")
130 elif mode == _EXPAND_INSTANCES:
132 raise errors.OpPrereqError("No instance names passed",
134 idata = client.QueryInstances(names, ["name"], False)
135 inames = [row[0] for row in idata]
136 elif mode == _EXPAND_INSTANCES_BY_TAGS:
138 raise errors.OpPrereqError("No instance tags passed",
140 idata = client.QueryInstances([], ["name", "tags"], False)
141 inames = [row[0] for row in idata if set(row[1]).intersection(names)]
143 raise errors.OpPrereqError("Unknown mode '%s'" % mode, errors.ECODE_INVAL)
148 def _EnsureInstancesExist(client, names):
149 """Check for and ensure the given instance names exist.
151 This function will raise an OpPrereqError in case they don't
152 exist. Otherwise it will exit cleanly.
154 @type client: L{ganeti.luxi.Client}
155 @param client: the client to use for the query
157 @param names: the list of instance names to query
158 @raise errors.OpPrereqError: in case any instance is missing
161 # TODO: change LUInstanceQuery to that it actually returns None
162 # instead of raising an exception, or devise a better mechanism
163 result = client.QueryInstances(names, ["name"], False)
164 for orig_name, row in zip(names, result):
166 raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name,
170 def GenericManyOps(operation, fn):
171 """Generic multi-instance operations.
173 The will return a wrapper that processes the options and arguments
174 given, and uses the passed function to build the opcode needed for
175 the specific operation. Thus all the generic loop/confirmation code
176 is abstracted into this function.
179 def realfn(opts, args):
180 if opts.multi_mode is None:
181 opts.multi_mode = _EXPAND_INSTANCES
183 inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
185 if opts.multi_mode == _EXPAND_CLUSTER:
186 ToStdout("Cluster is empty, no instances to shutdown")
188 raise errors.OpPrereqError("Selection filter does not match"
189 " any instances", errors.ECODE_INVAL)
190 multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1
191 if not (opts.force_multi or not multi_on
192 or ConfirmOperation(inames, "instances", operation)):
194 jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
197 jex.QueueJob(name, op)
198 results = jex.WaitOrShow(not opts.submit_only)
199 rcode = compat.all(row[0] for row in results)
200 return int(not rcode)
204 def ListInstances(opts, args):
205 """List instances and their properties.
207 @param opts: the command line options selected by the user
209 @param args: should be an empty list
211 @return: the desired exit code
214 selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
216 fmtoverride = dict.fromkeys(["tags", "disk.sizes", "nic.macs", "nic.ips",
217 "nic.modes", "nic.links", "nic.bridges",
218 "snodes", "snodes.group", "snodes.group.uuid"],
219 (lambda value: ",".join(str(item)
223 return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units,
224 opts.separator, not opts.no_headers,
225 format_override=fmtoverride, verbose=opts.verbose,
226 force_filter=opts.force_filter)
229 def ListInstanceFields(opts, args):
230 """List instance fields.
232 @param opts: the command line options selected by the user
234 @param args: fields to list, or empty for all
236 @return: the desired exit code
239 return GenericListFields(constants.QR_INSTANCE, args, opts.separator,
243 def AddInstance(opts, args):
244 """Add an instance to the cluster.
246 This is just a wrapper over GenericInstanceCreate.
249 return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
252 def BatchCreate(opts, args):
253 """Create instances using a definition file.
255 This function reads a json file with instances defined
259 "disk_size": [20480],
265 "primary_node": "firstnode",
266 "secondary_node": "secondnode",
267 "iallocator": "dumb"}
270 Note that I{primary_node} and I{secondary_node} have precedence over
273 @param opts: the command line options selected by the user
275 @param args: should contain one element, the json filename
277 @return: the desired exit code
280 _DEFAULT_SPECS = {"disk_size": [20 * 1024],
283 "primary_node": None,
284 "secondary_node": None,
291 "file_storage_dir": None,
292 "force_variant": False,
293 "file_driver": "loop"}
295 def _PopulateWithDefaults(spec):
296 """Returns a new hash combined with default values."""
297 mydict = _DEFAULT_SPECS.copy()
302 """Validate the instance specs."""
303 # Validate fields required under any circumstances
304 for required_field in ("os", "template"):
305 if required_field not in spec:
306 raise errors.OpPrereqError('Required field "%s" is missing.' %
307 required_field, errors.ECODE_INVAL)
308 # Validate special fields
309 if spec["primary_node"] is not None:
310 if (spec["template"] in constants.DTS_INT_MIRROR and
311 spec["secondary_node"] is None):
312 raise errors.OpPrereqError("Template requires secondary node, but"
313 " there was no secondary provided.",
315 elif spec["iallocator"] is None:
316 raise errors.OpPrereqError("You have to provide at least a primary_node"
317 " or an iallocator.",
320 if (spec["hvparams"] and
321 not isinstance(spec["hvparams"], dict)):
322 raise errors.OpPrereqError("Hypervisor parameters must be a dict.",
325 json_filename = args[0]
327 instance_data = simplejson.loads(utils.ReadFile(json_filename))
328 except Exception, err: # pylint: disable=W0703
329 ToStderr("Can't parse the instance definition file: %s" % str(err))
332 if not isinstance(instance_data, dict):
333 ToStderr("The instance definition file is not in dict format.")
336 jex = JobExecutor(opts=opts)
338 # Iterate over the instances and do:
339 # * Populate the specs with default value
340 # * Validate the instance specs
341 i_names = utils.NiceSort(instance_data.keys()) # pylint: disable=E1103
343 specs = instance_data[name]
344 specs = _PopulateWithDefaults(specs)
347 hypervisor = specs["hypervisor"]
348 hvparams = specs["hvparams"]
351 for elem in specs["disk_size"]:
353 size = utils.ParseUnit(elem)
354 except (TypeError, ValueError), err:
355 raise errors.OpPrereqError("Invalid disk size '%s' for"
357 (elem, name, err), errors.ECODE_INVAL)
358 disks.append({"size": size})
360 utils.ForceDictType(specs["backend"], constants.BES_PARAMETER_TYPES)
361 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
364 for field in constants.INIC_PARAMS:
368 tmp_nics[0][field] = specs[field]
370 if specs["nics"] is not None and tmp_nics:
371 raise errors.OpPrereqError("'nics' list incompatible with using"
372 " individual nic fields as well",
374 elif specs["nics"] is not None:
375 tmp_nics = specs["nics"]
379 op = opcodes.OpInstanceCreate(instance_name=name,
381 disk_template=specs["template"],
382 mode=constants.INSTANCE_CREATE,
384 force_variant=specs["force_variant"],
385 pnode=specs["primary_node"],
386 snode=specs["secondary_node"],
388 start=specs["start"],
389 ip_check=specs["ip_check"],
390 name_check=specs["name_check"],
392 iallocator=specs["iallocator"],
393 hypervisor=hypervisor,
395 beparams=specs["backend"],
396 file_storage_dir=specs["file_storage_dir"],
397 file_driver=specs["file_driver"])
399 jex.QueueJob(name, op)
400 # we never want to wait, just show the submitted job IDs
401 jex.WaitOrShow(False)
406 def ReinstallInstance(opts, args):
407 """Reinstall an instance.
409 @param opts: the command line options selected by the user
411 @param args: should contain only one element, the name of the
412 instance to be reinstalled
414 @return: the desired exit code
417 # first, compute the desired name list
418 if opts.multi_mode is None:
419 opts.multi_mode = _EXPAND_INSTANCES
421 inames = _ExpandMultiNames(opts.multi_mode, args)
423 raise errors.OpPrereqError("Selection filter does not match any instances",
426 # second, if requested, ask for an OS
427 if opts.select_os is True:
428 op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[])
429 result = SubmitOpCode(op, opts=opts)
432 ToStdout("Can't get the OS list")
435 ToStdout("Available OS templates:")
438 for (name, variants) in result:
439 for entry in CalculateOSNames(name, variants):
440 ToStdout("%3s: %s", number, entry)
441 choices.append(("%s" % number, entry, entry))
444 choices.append(("x", "exit", "Exit gnt-instance reinstall"))
445 selected = AskUser("Enter OS template number (or x to abort):",
448 if selected == "exit":
449 ToStderr("User aborted reinstall, exiting")
453 os_msg = "change the OS to '%s'" % selected
456 if opts.os is not None:
457 os_msg = "change the OS to '%s'" % os_name
459 os_msg = "keep the same OS"
461 # third, get confirmation: multi-reinstall requires --force-multi,
462 # single-reinstall either --force or --force-multi (--force-multi is
463 # a stronger --force)
464 multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1
466 warn_msg = ("Note: this will remove *all* data for the"
467 " below instances! It will %s.\n" % os_msg)
468 if not (opts.force_multi or
469 ConfirmOperation(inames, "instances", "reinstall", extra=warn_msg)):
472 if not (opts.force or opts.force_multi):
473 usertext = ("This will reinstall the instance '%s' (and %s) which"
474 " removes all data. Continue?") % (inames[0], os_msg)
475 if not AskUser(usertext):
478 jex = JobExecutor(verbose=multi_on, opts=opts)
479 for instance_name in inames:
480 op = opcodes.OpInstanceReinstall(instance_name=instance_name,
482 force_variant=opts.force_variant,
483 osparams=opts.osparams)
484 jex.QueueJob(instance_name, op)
486 jex.WaitOrShow(not opts.submit_only)
490 def RemoveInstance(opts, args):
491 """Remove an instance.
493 @param opts: the command line options selected by the user
495 @param args: should contain only one element, the name of
496 the instance to be removed
498 @return: the desired exit code
501 instance_name = args[0]
506 _EnsureInstancesExist(cl, [instance_name])
508 usertext = ("This will remove the volumes of the instance %s"
509 " (including mirrors), thus removing all the data"
510 " of the instance. Continue?") % instance_name
511 if not AskUser(usertext):
514 op = opcodes.OpInstanceRemove(instance_name=instance_name,
515 ignore_failures=opts.ignore_failures,
516 shutdown_timeout=opts.shutdown_timeout)
517 SubmitOrSend(op, opts, cl=cl)
521 def RenameInstance(opts, args):
522 """Rename an instance.
524 @param opts: the command line options selected by the user
526 @param args: should contain two elements, the old and the
529 @return: the desired exit code
532 if not opts.name_check:
533 if not AskUser("As you disabled the check of the DNS entry, please verify"
534 " that '%s' is a FQDN. Continue?" % args[1]):
537 op = opcodes.OpInstanceRename(instance_name=args[0],
539 ip_check=opts.ip_check,
540 name_check=opts.name_check)
541 result = SubmitOrSend(op, opts)
544 ToStdout("Instance '%s' renamed to '%s'", args[0], result)
549 def ActivateDisks(opts, args):
550 """Activate an instance's disks.
552 This serves two purposes:
553 - it allows (as long as the instance is not running)
554 mounting the disks and modifying them from the node
555 - it repairs inactive secondary drbds
557 @param opts: the command line options selected by the user
559 @param args: should contain only one element, the instance name
561 @return: the desired exit code
564 instance_name = args[0]
565 op = opcodes.OpInstanceActivateDisks(instance_name=instance_name,
566 ignore_size=opts.ignore_size)
567 disks_info = SubmitOrSend(op, opts)
568 for host, iname, nname in disks_info:
569 ToStdout("%s:%s:%s", host, iname, nname)
573 def DeactivateDisks(opts, args):
574 """Deactivate an instance's disks.
576 This function takes the instance name, looks for its primary node
577 and the tries to shutdown its block devices on that node.
579 @param opts: the command line options selected by the user
581 @param args: should contain only one element, the instance name
583 @return: the desired exit code
586 instance_name = args[0]
587 op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name,
589 SubmitOrSend(op, opts)
593 def RecreateDisks(opts, args):
594 """Recreate an instance's disks.
596 @param opts: the command line options selected by the user
598 @param args: should contain only one element, the instance name
600 @return: the desired exit code
603 instance_name = args[0]
606 opts.disks = [int(v) for v in opts.disks.split(",")]
607 except (ValueError, TypeError), err:
608 ToStderr("Invalid disks value: %s" % str(err))
614 pnode, snode = SplitNodeOption(opts.node)
616 if snode is not None:
621 op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
624 SubmitOrSend(op, opts)
628 def GrowDisk(opts, args):
629 """Grow an instance's disks.
631 @param opts: the command line options selected by the user
633 @param args: should contain two elements, the instance name
634 whose disks we grow and the disk name, e.g. I{sda}
636 @return: the desired exit code
643 except (TypeError, ValueError), err:
644 raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
646 amount = utils.ParseUnit(args[2])
647 op = opcodes.OpInstanceGrowDisk(instance_name=instance,
648 disk=disk, amount=amount,
649 wait_for_sync=opts.wait_for_sync)
650 SubmitOrSend(op, opts)
654 def _StartupInstance(name, opts):
655 """Startup instances.
657 This returns the opcode to start an instance, and its decorator will
658 wrap this into a loop starting all desired instances.
660 @param name: the name of the instance to act on
661 @param opts: the command line options selected by the user
662 @return: the opcode needed for the operation
665 op = opcodes.OpInstanceStartup(instance_name=name,
667 ignore_offline_nodes=opts.ignore_offline,
668 no_remember=opts.no_remember,
669 startup_paused=opts.startup_paused)
670 # do not add these parameters to the opcode unless they're defined
672 op.hvparams = opts.hvparams
674 op.beparams = opts.beparams
678 def _RebootInstance(name, opts):
679 """Reboot instance(s).
681 This returns the opcode to reboot an instance, and its decorator
682 will wrap this into a loop rebooting all desired instances.
684 @param name: the name of the instance to act on
685 @param opts: the command line options selected by the user
686 @return: the opcode needed for the operation
689 return opcodes.OpInstanceReboot(instance_name=name,
690 reboot_type=opts.reboot_type,
691 ignore_secondaries=opts.ignore_secondaries,
692 shutdown_timeout=opts.shutdown_timeout)
695 def _ShutdownInstance(name, opts):
696 """Shutdown an instance.
698 This returns the opcode to shutdown an instance, and its decorator
699 will wrap this into a loop shutting down all desired instances.
701 @param name: the name of the instance to act on
702 @param opts: the command line options selected by the user
703 @return: the opcode needed for the operation
706 return opcodes.OpInstanceShutdown(instance_name=name,
707 timeout=opts.timeout,
708 ignore_offline_nodes=opts.ignore_offline,
709 no_remember=opts.no_remember)
712 def ReplaceDisks(opts, args):
713 """Replace the disks of an instance
715 @param opts: the command line options selected by the user
717 @param args: should contain only one element, the instance name
719 @return: the desired exit code
722 new_2ndary = opts.dst_node
723 iallocator = opts.iallocator
724 if opts.disks is None:
728 disks = [int(i) for i in opts.disks.split(",")]
729 except (TypeError, ValueError), err:
730 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
732 cnt = [opts.on_primary, opts.on_secondary, opts.auto,
733 new_2ndary is not None, iallocator is not None].count(True)
735 raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -I"
736 " options must be passed", errors.ECODE_INVAL)
737 elif opts.on_primary:
738 mode = constants.REPLACE_DISK_PRI
739 elif opts.on_secondary:
740 mode = constants.REPLACE_DISK_SEC
742 mode = constants.REPLACE_DISK_AUTO
744 raise errors.OpPrereqError("Cannot specify disks when using automatic"
745 " mode", errors.ECODE_INVAL)
746 elif new_2ndary is not None or iallocator is not None:
748 mode = constants.REPLACE_DISK_CHG
750 op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
751 remote_node=new_2ndary, mode=mode,
752 iallocator=iallocator,
753 early_release=opts.early_release)
754 SubmitOrSend(op, opts)
758 def FailoverInstance(opts, args):
759 """Failover an instance.
761 The failover is done by shutting it down on its present node and
762 starting it on the secondary.
764 @param opts: the command line options selected by the user
766 @param args: should contain only one element, the instance name
768 @return: the desired exit code
772 instance_name = args[0]
774 iallocator = opts.iallocator
775 target_node = opts.dst_node
777 if iallocator and target_node:
778 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
779 " node (-n) but not both", errors.ECODE_INVAL)
782 _EnsureInstancesExist(cl, [instance_name])
784 usertext = ("Failover will happen to image %s."
785 " This requires a shutdown of the instance. Continue?" %
787 if not AskUser(usertext):
790 op = opcodes.OpInstanceFailover(instance_name=instance_name,
791 ignore_consistency=opts.ignore_consistency,
792 shutdown_timeout=opts.shutdown_timeout,
793 iallocator=iallocator,
794 target_node=target_node)
795 SubmitOrSend(op, opts, cl=cl)
799 def MigrateInstance(opts, args):
800 """Migrate an instance.
802 The migrate is done without shutdown.
804 @param opts: the command line options selected by the user
806 @param args: should contain only one element, the instance name
808 @return: the desired exit code
812 instance_name = args[0]
814 iallocator = opts.iallocator
815 target_node = opts.dst_node
817 if iallocator and target_node:
818 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
819 " node (-n) but not both", errors.ECODE_INVAL)
822 _EnsureInstancesExist(cl, [instance_name])
825 usertext = ("Instance %s will be recovered from a failed migration."
826 " Note that the migration procedure (including cleanup)" %
829 usertext = ("Instance %s will be migrated. Note that migration" %
831 usertext += (" might impact the instance if anything goes wrong"
832 " (e.g. due to bugs in the hypervisor). Continue?")
833 if not AskUser(usertext):
836 # this should be removed once --non-live is deprecated
837 if not opts.live and opts.migration_mode is not None:
838 raise errors.OpPrereqError("Only one of the --non-live and "
839 "--migration-mode options can be passed",
841 if not opts.live: # --non-live passed
842 mode = constants.HT_MIGRATION_NONLIVE
844 mode = opts.migration_mode
846 op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
847 cleanup=opts.cleanup, iallocator=iallocator,
848 target_node=target_node,
849 allow_failover=opts.allow_failover)
850 SubmitOpCode(op, cl=cl, opts=opts)
854 def MoveInstance(opts, args):
857 @param opts: the command line options selected by the user
859 @param args: should contain only one element, the instance name
861 @return: the desired exit code
865 instance_name = args[0]
869 usertext = ("Instance %s will be moved."
870 " This requires a shutdown of the instance. Continue?" %
872 if not AskUser(usertext):
875 op = opcodes.OpInstanceMove(instance_name=instance_name,
876 target_node=opts.node,
877 shutdown_timeout=opts.shutdown_timeout,
878 ignore_consistency=opts.ignore_consistency)
879 SubmitOrSend(op, opts, cl=cl)
883 def ConnectToInstanceConsole(opts, args):
884 """Connect to the console of an instance.
886 @param opts: the command line options selected by the user
888 @param args: should contain only one element, the instance name
890 @return: the desired exit code
893 instance_name = args[0]
897 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
898 ((console_data, oper_state), ) = \
899 cl.QueryInstances([instance_name], ["console", "oper_state"], False)
901 # Ensure client connection is closed while external commands are run
908 # Instance is running
909 raise errors.OpExecError("Console information for instance %s is"
910 " unavailable" % instance_name)
912 raise errors.OpExecError("Instance %s is not running, can't get console" %
915 return _DoConsole(objects.InstanceConsole.FromDict(console_data),
916 opts.show_command, cluster_name)
919 def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
920 _runcmd_fn=utils.RunCmd):
921 """Acts based on the result of L{opcodes.OpInstanceConsole}.
923 @type console: L{objects.InstanceConsole}
924 @param console: Console object
925 @type show_command: bool
926 @param show_command: Whether to just display commands
927 @type cluster_name: string
928 @param cluster_name: Cluster name as retrieved from master daemon
931 assert console.Validate()
933 if console.kind == constants.CONS_MESSAGE:
934 feedback_fn(console.message)
935 elif console.kind == constants.CONS_VNC:
936 feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
937 " URL <vnc://%s:%s/>",
938 console.instance, console.host, console.port,
939 console.display, console.host, console.port)
940 elif console.kind == constants.CONS_SPICE:
941 feedback_fn("Instance %s has SPICE listening on %s:%s", console.instance,
942 console.host, console.port)
943 elif console.kind == constants.CONS_SSH:
944 # Convert to string if not already one
945 if isinstance(console.command, basestring):
946 cmd = console.command
948 cmd = utils.ShellQuoteArgs(console.command)
950 srun = ssh.SshRunner(cluster_name=cluster_name)
951 ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
952 batch=True, quiet=False, tty=True)
955 feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
957 result = _runcmd_fn(ssh_cmd, interactive=True)
959 logging.error("Console command \"%s\" failed with reason '%s' and"
960 " output %r", result.cmd, result.fail_reason,
962 raise errors.OpExecError("Connection to console of instance %s failed,"
963 " please check cluster configuration" %
966 raise errors.GenericError("Unknown console type '%s'" % console.kind)
968 return constants.EXIT_SUCCESS
971 def _FormatLogicalID(dev_type, logical_id, roman):
972 """Formats the logical_id of a disk.
975 if dev_type == constants.LD_DRBD8:
976 node_a, node_b, port, minor_a, minor_b, key = logical_id
978 ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
980 ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
982 ("port", compat.TryToRoman(port, convert=roman)),
985 elif dev_type == constants.LD_LV:
986 vg_name, lv_name = logical_id
987 data = ["%s/%s" % (vg_name, lv_name)]
989 data = [str(logical_id)]
994 def _FormatBlockDevInfo(idx, top_level, dev, roman):
995 """Show block device information.
997 This is only used by L{ShowInstanceConfig}, but it's too big to be
998 left for an inline definition.
1001 @param idx: the index of the current disk
1002 @type top_level: boolean
1003 @param top_level: if this a top-level disk?
1005 @param dev: dictionary with disk information
1006 @type roman: boolean
1007 @param roman: whether to try to use roman integers
1008 @return: a list of either strings, tuples or lists
1009 (which should be formatted at a higher indent level)
1012 def helper(dtype, status):
1013 """Format one line for physical device status.
1016 @param dtype: a constant from the L{constants.LDS_BLOCK} set
1018 @param status: a tuple as returned from L{backend.FindBlockDevice}
1019 @return: the string representing the status
1025 (path, major, minor, syncp, estt, degr, ldisk_status) = status
1027 major_string = "N/A"
1029 major_string = str(compat.TryToRoman(major, convert=roman))
1032 minor_string = "N/A"
1034 minor_string = str(compat.TryToRoman(minor, convert=roman))
1036 txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1037 if dtype in (constants.LD_DRBD8, ):
1038 if syncp is not None:
1039 sync_text = "*RECOVERING* %5.2f%%," % syncp
1041 sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1043 sync_text += " ETA unknown"
1045 sync_text = "in sync"
1047 degr_text = "*DEGRADED*"
1050 if ldisk_status == constants.LDS_FAULTY:
1051 ldisk_text = " *MISSING DISK*"
1052 elif ldisk_status == constants.LDS_UNKNOWN:
1053 ldisk_text = " *UNCERTAIN STATE*"
1056 txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1057 elif dtype == constants.LD_LV:
1058 if ldisk_status == constants.LDS_FAULTY:
1059 ldisk_text = " *FAILED* (failed drive?)"
1067 if dev["iv_name"] is not None:
1068 txt = dev["iv_name"]
1070 txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1072 txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1073 if isinstance(dev["size"], int):
1074 nice_size = utils.FormatUnit(dev["size"], "h")
1076 nice_size = dev["size"]
1077 d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1080 data.append(("access mode", dev["mode"]))
1081 if dev["logical_id"] is not None:
1083 l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1085 l_id = [str(dev["logical_id"])]
1087 data.append(("logical_id", l_id[0]))
1090 elif dev["physical_id"] is not None:
1091 data.append("physical_id:")
1092 data.append([dev["physical_id"]])
1095 data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1098 data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1101 data.append("child devices:")
1102 for c_idx, child in enumerate(dev["children"]):
1103 data.append(_FormatBlockDevInfo(c_idx, False, child, roman))
1108 def _FormatList(buf, data, indent_level):
1109 """Formats a list of data at a given indent level.
1111 If the element of the list is:
1112 - a string, it is simply formatted as is
1113 - a tuple, it will be split into key, value and the all the
1114 values in a list will be aligned all at the same start column
1115 - a list, will be recursively formatted
1118 @param buf: the buffer into which we write the output
1119 @param data: the list to format
1120 @type indent_level: int
1121 @param indent_level: the indent level to format at
1124 max_tlen = max([len(elem[0]) for elem in data
1125 if isinstance(elem, tuple)] or [0])
1127 if isinstance(elem, basestring):
1128 buf.write("%*s%s\n" % (2 * indent_level, "", elem))
1129 elif isinstance(elem, tuple):
1131 spacer = "%*s" % (max_tlen - len(key), "")
1132 buf.write("%*s%s:%s %s\n" % (2 * indent_level, "", key, spacer, value))
1133 elif isinstance(elem, list):
1134 _FormatList(buf, elem, indent_level + 1)
1137 def ShowInstanceConfig(opts, args):
1138 """Compute instance run-time status.
1140 @param opts: the command line options selected by the user
1142 @param args: either an empty list, and then we query all
1143 instances, or should contain a list of instance names
1145 @return: the desired exit code
1148 if not args and not opts.show_all:
1149 ToStderr("No instance selected."
1150 " Please pass in --all if you want to query all instances.\n"
1151 "Note that this can take a long time on a big cluster.")
1153 elif args and opts.show_all:
1154 ToStderr("Cannot use --all if you specify instance names.")
1158 op = opcodes.OpInstanceQueryData(instances=args, static=opts.static,
1159 use_locking=not opts.static)
1160 result = SubmitOpCode(op, opts=opts)
1162 ToStdout("No instances.")
1167 for instance_name in result:
1168 instance = result[instance_name]
1169 buf.write("Instance name: %s\n" % instance["name"])
1170 buf.write("UUID: %s\n" % instance["uuid"])
1171 buf.write("Serial number: %s\n" %
1172 compat.TryToRoman(instance["serial_no"],
1173 convert=opts.roman_integers))
1174 buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1175 buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1176 buf.write("State: configured to be %s" % instance["config_state"])
1177 if instance["run_state"]:
1178 buf.write(", actual state is %s" % instance["run_state"])
1180 ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1181 ## instance["auto_balance"])
1182 buf.write(" Nodes:\n")
1183 buf.write(" - primary: %s\n" % instance["pnode"])
1184 buf.write(" - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1185 buf.write(" Operating system: %s\n" % instance["os"])
1186 FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1188 if "network_port" in instance:
1189 buf.write(" Allocated network port: %s\n" %
1190 compat.TryToRoman(instance["network_port"],
1191 convert=opts.roman_integers))
1192 buf.write(" Hypervisor: %s\n" % instance["hypervisor"])
1194 # custom VNC console information
1195 vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1197 if vnc_bind_address:
1198 port = instance["network_port"]
1199 display = int(port) - constants.VNC_BASE_PORT
1200 if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1201 vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1204 elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1205 vnc_console_port = ("%s:%s (node %s) (display %s)" %
1206 (vnc_bind_address, port,
1207 instance["pnode"], display))
1209 # vnc bind address is a file
1210 vnc_console_port = "%s:%s" % (instance["pnode"],
1212 buf.write(" - console connection: vnc to %s\n" % vnc_console_port)
1214 FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1216 buf.write(" Hardware:\n")
1217 buf.write(" - VCPUs: %s\n" %
1218 compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1219 convert=opts.roman_integers))
1220 buf.write(" - memory: %sMiB\n" %
1221 compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
1222 convert=opts.roman_integers))
1223 buf.write(" - NICs:\n")
1224 for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1225 buf.write(" - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1226 (idx, mac, ip, mode, link))
1227 buf.write(" Disk template: %s\n" % instance["disk_template"])
1228 buf.write(" Disks:\n")
1230 for idx, device in enumerate(instance["disks"]):
1231 _FormatList(buf, _FormatBlockDevInfo(idx, True, device,
1232 opts.roman_integers), 2)
1234 ToStdout(buf.getvalue().rstrip("\n"))
1238 def SetInstanceParams(opts, args):
1239 """Modifies an instance.
1241 All parameters take effect only at the next restart of the instance.
1243 @param opts: the command line options selected by the user
1245 @param args: should contain only one element, the instance name
1247 @return: the desired exit code
1250 if not (opts.nics or opts.disks or opts.disk_template or
1251 opts.hvparams or opts.beparams or opts.os or opts.osparams):
1252 ToStderr("Please give at least one of the parameters.")
1255 for param in opts.beparams:
1256 if isinstance(opts.beparams[param], basestring):
1257 if opts.beparams[param].lower() == "default":
1258 opts.beparams[param] = constants.VALUE_DEFAULT
1260 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1261 allowed_values=[constants.VALUE_DEFAULT])
1263 for param in opts.hvparams:
1264 if isinstance(opts.hvparams[param], basestring):
1265 if opts.hvparams[param].lower() == "default":
1266 opts.hvparams[param] = constants.VALUE_DEFAULT
1268 utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1269 allowed_values=[constants.VALUE_DEFAULT])
1271 for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1273 nic_op = int(nic_op)
1274 opts.nics[idx] = (nic_op, nic_dict)
1275 except (TypeError, ValueError):
1278 for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1280 disk_op = int(disk_op)
1281 opts.disks[idx] = (disk_op, disk_dict)
1282 except (TypeError, ValueError):
1284 if disk_op == constants.DDM_ADD:
1285 if "size" not in disk_dict:
1286 raise errors.OpPrereqError("Missing required parameter 'size'",
1288 disk_dict["size"] = utils.ParseUnit(disk_dict["size"])
1290 if (opts.disk_template and
1291 opts.disk_template in constants.DTS_INT_MIRROR and
1293 ToStderr("Changing the disk template to a mirrored one requires"
1294 " specifying a secondary node")
1297 op = opcodes.OpInstanceSetParams(instance_name=args[0],
1300 disk_template=opts.disk_template,
1301 remote_node=opts.node,
1302 hvparams=opts.hvparams,
1303 beparams=opts.beparams,
1305 osparams=opts.osparams,
1306 force_variant=opts.force_variant,
1308 wait_for_sync=opts.wait_for_sync)
1310 # even if here we process the result, we allow submit only
1311 result = SubmitOrSend(op, opts)
1314 ToStdout("Modified instance %s", args[0])
1315 for param, data in result:
1316 ToStdout(" - %-5s -> %s", param, data)
1317 ToStdout("Please don't forget that most parameters take effect"
1318 " only at the next start of the instance.")
1322 def ChangeGroup(opts, args):
1323 """Moves an instance to another group.
1326 (instance_name, ) = args
1330 op = opcodes.OpInstanceChangeGroup(instance_name=instance_name,
1331 iallocator=opts.iallocator,
1332 target_groups=opts.to,
1333 early_release=opts.early_release)
1334 result = SubmitOpCode(op, cl=cl, opts=opts)
1336 # Keep track of submitted jobs
1337 jex = JobExecutor(cl=cl, opts=opts)
1339 for (status, job_id) in result[constants.JOB_IDS_KEY]:
1340 jex.AddJobId(None, status, job_id)
1342 results = jex.GetResults()
1343 bad_cnt = len([row for row in results if not row[0]])
1345 ToStdout("Instance '%s' changed group successfully.", instance_name)
1346 rcode = constants.EXIT_SUCCESS
1348 ToStdout("There were %s errors while changing group of instance '%s'.",
1349 bad_cnt, instance_name)
1350 rcode = constants.EXIT_FAILURE
1355 # multi-instance selection options
1356 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1357 help="Do not ask for confirmation when more than"
1358 " one instance is affected",
1359 action="store_true", default=False)
1361 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1362 help="Filter by nodes (primary only)",
1363 const=_EXPAND_NODES_PRI, action="store_const")
1365 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1366 help="Filter by nodes (secondary only)",
1367 const=_EXPAND_NODES_SEC, action="store_const")
1369 m_node_opt = cli_option("--node", dest="multi_mode",
1370 help="Filter by nodes (primary and secondary)",
1371 const=_EXPAND_NODES_BOTH, action="store_const")
1373 m_clust_opt = cli_option("--all", dest="multi_mode",
1374 help="Select all instances in the cluster",
1375 const=_EXPAND_CLUSTER, action="store_const")
1377 m_inst_opt = cli_option("--instance", dest="multi_mode",
1378 help="Filter by instance name [default]",
1379 const=_EXPAND_INSTANCES, action="store_const")
1381 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1382 help="Filter by node tag",
1383 const=_EXPAND_NODES_BOTH_BY_TAGS,
1384 action="store_const")
1386 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1387 help="Filter by primary node tag",
1388 const=_EXPAND_NODES_PRI_BY_TAGS,
1389 action="store_const")
1391 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1392 help="Filter by secondary node tag",
1393 const=_EXPAND_NODES_SEC_BY_TAGS,
1394 action="store_const")
1396 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1397 help="Filter by instance tag",
1398 const=_EXPAND_INSTANCES_BY_TAGS,
1399 action="store_const")
1401 # this is defined separately due to readability only
1411 AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1412 "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1413 "Creates and adds a new instance to the cluster"),
1415 BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1417 "Create a bunch of instances based on specs in the file."),
1419 ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1420 [SHOWCMD_OPT, PRIORITY_OPT],
1421 "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1423 FailoverInstance, ARGS_ONE_INSTANCE,
1424 [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1425 DRY_RUN_OPT, PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT],
1426 "[-f] <instance>", "Stops the instance, changes its primary node and"
1427 " (if it was originally running) starts it on the new node"
1428 " (the secondary for mirrored instances or any node"
1429 " for shared storage)."),
1431 MigrateInstance, ARGS_ONE_INSTANCE,
1432 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1433 PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT, ALLOW_FAILOVER_OPT],
1434 "[-f] <instance>", "Migrate instance to its secondary node"
1435 " (only for mirrored instances)"),
1437 MoveInstance, ARGS_ONE_INSTANCE,
1438 [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1439 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_CONSIST_OPT],
1440 "[-f] <instance>", "Move instance to an arbitrary node"
1441 " (only for instances of type file and lv)"),
1443 ShowInstanceConfig, ARGS_MANY_INSTANCES,
1444 [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1445 "[-s] {--all | <instance>...}",
1446 "Show information on the specified instance(s)"),
1448 ListInstances, ARGS_MANY_INSTANCES,
1449 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
1452 "Lists the instances and their status. The available fields can be shown"
1453 " using the \"list-fields\" command (see the man page for details)."
1454 " The default field list is (in order): %s." %
1455 utils.CommaJoin(_LIST_DEF_FIELDS),
1458 ListInstanceFields, [ArgUnknown()],
1459 [NOHDR_OPT, SEP_OPT],
1461 "Lists all available fields for instances"),
1463 ReinstallInstance, [ArgInstance()],
1464 [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1465 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1466 m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1467 SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1468 "[-f] <instance>", "Reinstall a stopped instance"),
1470 RemoveInstance, ARGS_ONE_INSTANCE,
1471 [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1472 DRY_RUN_OPT, PRIORITY_OPT],
1473 "[-f] <instance>", "Shuts down the instance and removes it"),
1476 [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1477 [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1478 "<instance> <new_name>", "Rename the instance"),
1480 ReplaceDisks, ARGS_ONE_INSTANCE,
1481 [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1482 NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1483 DRY_RUN_OPT, PRIORITY_OPT],
1484 "[-s|-p|-n NODE|-I NAME] <instance>",
1485 "Replaces all disks for the instance"),
1487 SetInstanceParams, ARGS_ONE_INSTANCE,
1488 [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1489 DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1490 OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT, NWSYNC_OPT],
1491 "<instance>", "Alters the parameters of an instance"),
1493 GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1494 [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1495 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1496 m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1497 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT, NO_REMEMBER_OPT],
1498 "<instance>", "Stops an instance"),
1500 GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1501 [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1502 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1503 m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1504 BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT,
1505 NO_REMEMBER_OPT, STARTUP_PAUSED_OPT],
1506 "<instance>", "Starts an instance"),
1508 GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1509 [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1510 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1511 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1512 m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1513 "<instance>", "Reboots an instance"),
1515 ActivateDisks, ARGS_ONE_INSTANCE,
1516 [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
1517 "<instance>", "Activate an instance's disks"),
1518 "deactivate-disks": (
1519 DeactivateDisks, ARGS_ONE_INSTANCE,
1520 [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1521 "[-f] <instance>", "Deactivate an instance's disks"),
1523 RecreateDisks, ARGS_ONE_INSTANCE,
1524 [SUBMIT_OPT, DISKIDX_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1525 "<instance>", "Recreate an instance's disks"),
1528 [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1529 ArgUnknown(min=1, max=1)],
1530 [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1531 "<instance> <disk> <size>", "Grow an instance's disk"),
1533 ChangeGroup, ARGS_ONE_INSTANCE,
1534 [TO_GROUP_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT],
1535 "[-I <iallocator>] [--to <group>]", "Change group of instance"),
1537 ListTags, ARGS_ONE_INSTANCE, [PRIORITY_OPT],
1538 "<instance_name>", "List the tags of the given instance"),
1540 AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1541 [TAG_SRC_OPT, PRIORITY_OPT],
1542 "<instance_name> tag...", "Add tags to the given instance"),
1544 RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1545 [TAG_SRC_OPT, PRIORITY_OPT],
1546 "<instance_name> tag...", "Remove tags from given instance"),
1549 #: dictionary with aliases for commands
1557 return GenericMain(commands, aliases=aliases,
1558 override={"tag_type": constants.TAG_INSTANCE},
1559 env_override=_ENV_OVERRIDE)