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 def _ExpandMultiNames(mode, names, client=None):
68 """Expand the given names using the passed mode.
70 For _EXPAND_CLUSTER, all instances will be returned. For
71 _EXPAND_NODES_PRI/SEC, all instances having those nodes as
72 primary/secondary will be returned. For _EXPAND_NODES_BOTH, all
73 instances having those nodes as either primary or secondary will be
74 returned. For _EXPAND_INSTANCES, the given instances will be
77 @param mode: one of L{_EXPAND_CLUSTER}, L{_EXPAND_NODES_BOTH},
78 L{_EXPAND_NODES_PRI}, L{_EXPAND_NODES_SEC} or
80 @param names: a list of names; for cluster, it must be empty,
81 and for node and instance it must be a list of valid item
82 names (short names are valid as usual, e.g. node1 instead of
85 @return: the list of names after the expansion
86 @raise errors.ProgrammerError: for unknown selection type
87 @raise errors.OpPrereqError: for invalid input parameters
90 # pylint: disable=W0142
94 if mode == _EXPAND_CLUSTER:
96 raise errors.OpPrereqError("Cluster filter mode takes no arguments",
98 idata = client.QueryInstances([], ["name"], False)
99 inames = [row[0] for row in idata]
101 elif (mode in _EXPAND_NODES_TAGS_MODES or
102 mode in (_EXPAND_NODES_BOTH, _EXPAND_NODES_PRI, _EXPAND_NODES_SEC)):
103 if mode in _EXPAND_NODES_TAGS_MODES:
105 raise errors.OpPrereqError("No node tags passed", errors.ECODE_INVAL)
106 ndata = client.QueryNodes([], ["name", "pinst_list",
107 "sinst_list", "tags"], False)
108 ndata = [row for row in ndata if set(row[3]).intersection(names)]
111 raise errors.OpPrereqError("No node names passed", errors.ECODE_INVAL)
112 ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
115 ipri = [row[1] for row in ndata]
116 pri_names = list(itertools.chain(*ipri))
117 isec = [row[2] for row in ndata]
118 sec_names = list(itertools.chain(*isec))
119 if mode in (_EXPAND_NODES_BOTH, _EXPAND_NODES_BOTH_BY_TAGS):
120 inames = pri_names + sec_names
121 elif mode in (_EXPAND_NODES_PRI, _EXPAND_NODES_PRI_BY_TAGS):
123 elif mode in (_EXPAND_NODES_SEC, _EXPAND_NODES_SEC_BY_TAGS):
126 raise errors.ProgrammerError("Unhandled shutdown type")
127 elif mode == _EXPAND_INSTANCES:
129 raise errors.OpPrereqError("No instance names passed",
131 idata = client.QueryInstances(names, ["name"], False)
132 inames = [row[0] for row in idata]
133 elif mode == _EXPAND_INSTANCES_BY_TAGS:
135 raise errors.OpPrereqError("No instance tags passed",
137 idata = client.QueryInstances([], ["name", "tags"], False)
138 inames = [row[0] for row in idata if set(row[1]).intersection(names)]
140 raise errors.OpPrereqError("Unknown mode '%s'" % mode, errors.ECODE_INVAL)
145 def _EnsureInstancesExist(client, names):
146 """Check for and ensure the given instance names exist.
148 This function will raise an OpPrereqError in case they don't
149 exist. Otherwise it will exit cleanly.
151 @type client: L{ganeti.luxi.Client}
152 @param client: the client to use for the query
154 @param names: the list of instance names to query
155 @raise errors.OpPrereqError: in case any instance is missing
158 # TODO: change LUInstanceQuery to that it actually returns None
159 # instead of raising an exception, or devise a better mechanism
160 result = client.QueryInstances(names, ["name"], False)
161 for orig_name, row in zip(names, result):
163 raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name,
167 def GenericManyOps(operation, fn):
168 """Generic multi-instance operations.
170 The will return a wrapper that processes the options and arguments
171 given, and uses the passed function to build the opcode needed for
172 the specific operation. Thus all the generic loop/confirmation code
173 is abstracted into this function.
176 def realfn(opts, args):
177 if opts.multi_mode is None:
178 opts.multi_mode = _EXPAND_INSTANCES
180 inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
182 if opts.multi_mode == _EXPAND_CLUSTER:
183 ToStdout("Cluster is empty, no instances to shutdown")
185 raise errors.OpPrereqError("Selection filter does not match"
186 " any instances", errors.ECODE_INVAL)
187 multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1
188 if not (opts.force_multi or not multi_on
189 or ConfirmOperation(inames, "instances", operation)):
191 jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
194 jex.QueueJob(name, op)
195 results = jex.WaitOrShow(not opts.submit_only)
196 rcode = compat.all(row[0] for row in results)
197 return int(not rcode)
201 def ListInstances(opts, args):
202 """List instances and their properties.
204 @param opts: the command line options selected by the user
206 @param args: should be an empty list
208 @return: the desired exit code
211 selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
213 fmtoverride = dict.fromkeys(["tags", "disk.sizes", "nic.macs", "nic.ips",
214 "nic.modes", "nic.links", "nic.bridges",
215 "snodes", "snodes.group", "snodes.group.uuid"],
216 (lambda value: ",".join(str(item)
220 return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units,
221 opts.separator, not opts.no_headers,
222 format_override=fmtoverride, verbose=opts.verbose,
223 force_filter=opts.force_filter)
226 def ListInstanceFields(opts, args):
227 """List instance fields.
229 @param opts: the command line options selected by the user
231 @param args: fields to list, or empty for all
233 @return: the desired exit code
236 return GenericListFields(constants.QR_INSTANCE, args, opts.separator,
240 def AddInstance(opts, args):
241 """Add an instance to the cluster.
243 This is just a wrapper over GenericInstanceCreate.
246 return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
249 def BatchCreate(opts, args):
250 """Create instances using a definition file.
252 This function reads a json file with instances defined
256 "disk_size": [20480],
262 "primary_node": "firstnode",
263 "secondary_node": "secondnode",
264 "iallocator": "dumb"}
267 Note that I{primary_node} and I{secondary_node} have precedence over
270 @param opts: the command line options selected by the user
272 @param args: should contain one element, the json filename
274 @return: the desired exit code
277 _DEFAULT_SPECS = {"disk_size": [20 * 1024],
280 "primary_node": None,
281 "secondary_node": None,
288 "file_storage_dir": None,
289 "force_variant": False,
290 "file_driver": "loop"}
292 def _PopulateWithDefaults(spec):
293 """Returns a new hash combined with default values."""
294 mydict = _DEFAULT_SPECS.copy()
299 """Validate the instance specs."""
300 # Validate fields required under any circumstances
301 for required_field in ("os", "template"):
302 if required_field not in spec:
303 raise errors.OpPrereqError('Required field "%s" is missing.' %
304 required_field, errors.ECODE_INVAL)
305 # Validate special fields
306 if spec["primary_node"] is not None:
307 if (spec["template"] in constants.DTS_INT_MIRROR and
308 spec["secondary_node"] is None):
309 raise errors.OpPrereqError("Template requires secondary node, but"
310 " there was no secondary provided.",
312 elif spec["iallocator"] is None:
313 raise errors.OpPrereqError("You have to provide at least a primary_node"
314 " or an iallocator.",
317 if (spec["hvparams"] and
318 not isinstance(spec["hvparams"], dict)):
319 raise errors.OpPrereqError("Hypervisor parameters must be a dict.",
322 json_filename = args[0]
324 instance_data = simplejson.loads(utils.ReadFile(json_filename))
325 except Exception, err: # pylint: disable=W0703
326 ToStderr("Can't parse the instance definition file: %s" % str(err))
329 if not isinstance(instance_data, dict):
330 ToStderr("The instance definition file is not in dict format.")
333 jex = JobExecutor(opts=opts)
335 # Iterate over the instances and do:
336 # * Populate the specs with default value
337 # * Validate the instance specs
338 i_names = utils.NiceSort(instance_data.keys()) # pylint: disable=E1103
340 specs = instance_data[name]
341 specs = _PopulateWithDefaults(specs)
344 hypervisor = specs["hypervisor"]
345 hvparams = specs["hvparams"]
348 for elem in specs["disk_size"]:
350 size = utils.ParseUnit(elem)
351 except (TypeError, ValueError), err:
352 raise errors.OpPrereqError("Invalid disk size '%s' for"
354 (elem, name, err), errors.ECODE_INVAL)
355 disks.append({"size": size})
357 utils.ForceDictType(specs["backend"], constants.BES_PARAMETER_TYPES)
358 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
361 for field in constants.INIC_PARAMS:
365 tmp_nics[0][field] = specs[field]
367 if specs["nics"] is not None and tmp_nics:
368 raise errors.OpPrereqError("'nics' list incompatible with using"
369 " individual nic fields as well",
371 elif specs["nics"] is not None:
372 tmp_nics = specs["nics"]
376 op = opcodes.OpInstanceCreate(instance_name=name,
378 disk_template=specs["template"],
379 mode=constants.INSTANCE_CREATE,
381 force_variant=specs["force_variant"],
382 pnode=specs["primary_node"],
383 snode=specs["secondary_node"],
385 start=specs["start"],
386 ip_check=specs["ip_check"],
387 name_check=specs["name_check"],
389 iallocator=specs["iallocator"],
390 hypervisor=hypervisor,
392 beparams=specs["backend"],
393 file_storage_dir=specs["file_storage_dir"],
394 file_driver=specs["file_driver"])
396 jex.QueueJob(name, op)
397 # we never want to wait, just show the submitted job IDs
398 jex.WaitOrShow(False)
403 def ReinstallInstance(opts, args):
404 """Reinstall an instance.
406 @param opts: the command line options selected by the user
408 @param args: should contain only one element, the name of the
409 instance to be reinstalled
411 @return: the desired exit code
414 # first, compute the desired name list
415 if opts.multi_mode is None:
416 opts.multi_mode = _EXPAND_INSTANCES
418 inames = _ExpandMultiNames(opts.multi_mode, args)
420 raise errors.OpPrereqError("Selection filter does not match any instances",
423 # second, if requested, ask for an OS
424 if opts.select_os is True:
425 op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[])
426 result = SubmitOpCode(op, opts=opts)
429 ToStdout("Can't get the OS list")
432 ToStdout("Available OS templates:")
435 for (name, variants) in result:
436 for entry in CalculateOSNames(name, variants):
437 ToStdout("%3s: %s", number, entry)
438 choices.append(("%s" % number, entry, entry))
441 choices.append(("x", "exit", "Exit gnt-instance reinstall"))
442 selected = AskUser("Enter OS template number (or x to abort):",
445 if selected == "exit":
446 ToStderr("User aborted reinstall, exiting")
450 os_msg = "change the OS to '%s'" % selected
453 if opts.os is not None:
454 os_msg = "change the OS to '%s'" % os_name
456 os_msg = "keep the same OS"
458 # third, get confirmation: multi-reinstall requires --force-multi,
459 # single-reinstall either --force or --force-multi (--force-multi is
460 # a stronger --force)
461 multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1
463 warn_msg = ("Note: this will remove *all* data for the"
464 " below instances! It will %s.\n" % os_msg)
465 if not (opts.force_multi or
466 ConfirmOperation(inames, "instances", "reinstall", extra=warn_msg)):
469 if not (opts.force or opts.force_multi):
470 usertext = ("This will reinstall the instance '%s' (and %s) which"
471 " removes all data. Continue?") % (inames[0], os_msg)
472 if not AskUser(usertext):
475 jex = JobExecutor(verbose=multi_on, opts=opts)
476 for instance_name in inames:
477 op = opcodes.OpInstanceReinstall(instance_name=instance_name,
479 force_variant=opts.force_variant,
480 osparams=opts.osparams)
481 jex.QueueJob(instance_name, op)
483 jex.WaitOrShow(not opts.submit_only)
487 def RemoveInstance(opts, args):
488 """Remove an instance.
490 @param opts: the command line options selected by the user
492 @param args: should contain only one element, the name of
493 the instance to be removed
495 @return: the desired exit code
498 instance_name = args[0]
503 _EnsureInstancesExist(cl, [instance_name])
505 usertext = ("This will remove the volumes of the instance %s"
506 " (including mirrors), thus removing all the data"
507 " of the instance. Continue?") % instance_name
508 if not AskUser(usertext):
511 op = opcodes.OpInstanceRemove(instance_name=instance_name,
512 ignore_failures=opts.ignore_failures,
513 shutdown_timeout=opts.shutdown_timeout)
514 SubmitOrSend(op, opts, cl=cl)
518 def RenameInstance(opts, args):
519 """Rename an instance.
521 @param opts: the command line options selected by the user
523 @param args: should contain two elements, the old and the
526 @return: the desired exit code
529 if not opts.name_check:
530 if not AskUser("As you disabled the check of the DNS entry, please verify"
531 " that '%s' is a FQDN. Continue?" % args[1]):
534 op = opcodes.OpInstanceRename(instance_name=args[0],
536 ip_check=opts.ip_check,
537 name_check=opts.name_check)
538 result = SubmitOrSend(op, opts)
541 ToStdout("Instance '%s' renamed to '%s'", args[0], result)
546 def ActivateDisks(opts, args):
547 """Activate an instance's disks.
549 This serves two purposes:
550 - it allows (as long as the instance is not running)
551 mounting the disks and modifying them from the node
552 - it repairs inactive secondary drbds
554 @param opts: the command line options selected by the user
556 @param args: should contain only one element, the instance name
558 @return: the desired exit code
561 instance_name = args[0]
562 op = opcodes.OpInstanceActivateDisks(instance_name=instance_name,
563 ignore_size=opts.ignore_size)
564 disks_info = SubmitOrSend(op, opts)
565 for host, iname, nname in disks_info:
566 ToStdout("%s:%s:%s", host, iname, nname)
570 def DeactivateDisks(opts, args):
571 """Deactivate an instance's disks.
573 This function takes the instance name, looks for its primary node
574 and the tries to shutdown its block devices on that node.
576 @param opts: the command line options selected by the user
578 @param args: should contain only one element, the instance name
580 @return: the desired exit code
583 instance_name = args[0]
584 op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name,
586 SubmitOrSend(op, opts)
590 def RecreateDisks(opts, args):
591 """Recreate an instance's disks.
593 @param opts: the command line options selected by the user
595 @param args: should contain only one element, the instance name
597 @return: the desired exit code
600 instance_name = args[0]
603 opts.disks = [int(v) for v in opts.disks.split(",")]
604 except (ValueError, TypeError), err:
605 ToStderr("Invalid disks value: %s" % str(err))
611 pnode, snode = SplitNodeOption(opts.node)
613 if snode is not None:
618 op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
621 SubmitOrSend(op, opts)
625 def GrowDisk(opts, args):
626 """Grow an instance's disks.
628 @param opts: the command line options selected by the user
630 @param args: should contain two elements, the instance name
631 whose disks we grow and the disk name, e.g. I{sda}
633 @return: the desired exit code
640 except (TypeError, ValueError), err:
641 raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
643 amount = utils.ParseUnit(args[2])
644 op = opcodes.OpInstanceGrowDisk(instance_name=instance,
645 disk=disk, amount=amount,
646 wait_for_sync=opts.wait_for_sync)
647 SubmitOrSend(op, opts)
651 def _StartupInstance(name, opts):
652 """Startup instances.
654 This returns the opcode to start an instance, and its decorator will
655 wrap this into a loop starting all desired instances.
657 @param name: the name of the instance to act on
658 @param opts: the command line options selected by the user
659 @return: the opcode needed for the operation
662 op = opcodes.OpInstanceStartup(instance_name=name,
664 ignore_offline_nodes=opts.ignore_offline,
665 no_remember=opts.no_remember,
666 startup_paused=opts.startup_paused)
667 # do not add these parameters to the opcode unless they're defined
669 op.hvparams = opts.hvparams
671 op.beparams = opts.beparams
675 def _RebootInstance(name, opts):
676 """Reboot instance(s).
678 This returns the opcode to reboot an instance, and its decorator
679 will wrap this into a loop rebooting all desired instances.
681 @param name: the name of the instance to act on
682 @param opts: the command line options selected by the user
683 @return: the opcode needed for the operation
686 return opcodes.OpInstanceReboot(instance_name=name,
687 reboot_type=opts.reboot_type,
688 ignore_secondaries=opts.ignore_secondaries,
689 shutdown_timeout=opts.shutdown_timeout)
692 def _ShutdownInstance(name, opts):
693 """Shutdown an instance.
695 This returns the opcode to shutdown an instance, and its decorator
696 will wrap this into a loop shutting down all desired instances.
698 @param name: the name of the instance to act on
699 @param opts: the command line options selected by the user
700 @return: the opcode needed for the operation
703 return opcodes.OpInstanceShutdown(instance_name=name,
704 timeout=opts.timeout,
705 ignore_offline_nodes=opts.ignore_offline,
706 no_remember=opts.no_remember)
709 def ReplaceDisks(opts, args):
710 """Replace the disks of an instance
712 @param opts: the command line options selected by the user
714 @param args: should contain only one element, the instance name
716 @return: the desired exit code
719 new_2ndary = opts.dst_node
720 iallocator = opts.iallocator
721 if opts.disks is None:
725 disks = [int(i) for i in opts.disks.split(",")]
726 except (TypeError, ValueError), err:
727 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
729 cnt = [opts.on_primary, opts.on_secondary, opts.auto,
730 new_2ndary is not None, iallocator is not None].count(True)
732 raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -I"
733 " options must be passed", errors.ECODE_INVAL)
734 elif opts.on_primary:
735 mode = constants.REPLACE_DISK_PRI
736 elif opts.on_secondary:
737 mode = constants.REPLACE_DISK_SEC
739 mode = constants.REPLACE_DISK_AUTO
741 raise errors.OpPrereqError("Cannot specify disks when using automatic"
742 " mode", errors.ECODE_INVAL)
743 elif new_2ndary is not None or iallocator is not None:
745 mode = constants.REPLACE_DISK_CHG
747 op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
748 remote_node=new_2ndary, mode=mode,
749 iallocator=iallocator,
750 early_release=opts.early_release)
751 SubmitOrSend(op, opts)
755 def FailoverInstance(opts, args):
756 """Failover an instance.
758 The failover is done by shutting it down on its present node and
759 starting it on the secondary.
761 @param opts: the command line options selected by the user
763 @param args: should contain only one element, the instance name
765 @return: the desired exit code
769 instance_name = args[0]
771 iallocator = opts.iallocator
772 target_node = opts.dst_node
774 if iallocator and target_node:
775 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
776 " node (-n) but not both", errors.ECODE_INVAL)
779 _EnsureInstancesExist(cl, [instance_name])
781 usertext = ("Failover will happen to image %s."
782 " This requires a shutdown of the instance. Continue?" %
784 if not AskUser(usertext):
787 op = opcodes.OpInstanceFailover(instance_name=instance_name,
788 ignore_consistency=opts.ignore_consistency,
789 shutdown_timeout=opts.shutdown_timeout,
790 iallocator=iallocator,
791 target_node=target_node)
792 SubmitOrSend(op, opts, cl=cl)
796 def MigrateInstance(opts, args):
797 """Migrate an instance.
799 The migrate is done without shutdown.
801 @param opts: the command line options selected by the user
803 @param args: should contain only one element, the instance name
805 @return: the desired exit code
809 instance_name = args[0]
811 iallocator = opts.iallocator
812 target_node = opts.dst_node
814 if iallocator and target_node:
815 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
816 " node (-n) but not both", errors.ECODE_INVAL)
819 _EnsureInstancesExist(cl, [instance_name])
822 usertext = ("Instance %s will be recovered from a failed migration."
823 " Note that the migration procedure (including cleanup)" %
826 usertext = ("Instance %s will be migrated. Note that migration" %
828 usertext += (" might impact the instance if anything goes wrong"
829 " (e.g. due to bugs in the hypervisor). Continue?")
830 if not AskUser(usertext):
833 # this should be removed once --non-live is deprecated
834 if not opts.live and opts.migration_mode is not None:
835 raise errors.OpPrereqError("Only one of the --non-live and "
836 "--migration-mode options can be passed",
838 if not opts.live: # --non-live passed
839 mode = constants.HT_MIGRATION_NONLIVE
841 mode = opts.migration_mode
843 op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
844 cleanup=opts.cleanup, iallocator=iallocator,
845 target_node=target_node,
846 allow_failover=opts.allow_failover)
847 SubmitOpCode(op, cl=cl, opts=opts)
851 def MoveInstance(opts, args):
854 @param opts: the command line options selected by the user
856 @param args: should contain only one element, the instance name
858 @return: the desired exit code
862 instance_name = args[0]
866 usertext = ("Instance %s will be moved."
867 " This requires a shutdown of the instance. Continue?" %
869 if not AskUser(usertext):
872 op = opcodes.OpInstanceMove(instance_name=instance_name,
873 target_node=opts.node,
874 shutdown_timeout=opts.shutdown_timeout,
875 ignore_consistency=opts.ignore_consistency)
876 SubmitOrSend(op, opts, cl=cl)
880 def ConnectToInstanceConsole(opts, args):
881 """Connect to the console of an instance.
883 @param opts: the command line options selected by the user
885 @param args: should contain only one element, the instance name
887 @return: the desired exit code
890 instance_name = args[0]
894 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
895 ((console_data, oper_state), ) = \
896 cl.QueryInstances([instance_name], ["console", "oper_state"], False)
898 # Ensure client connection is closed while external commands are run
905 # Instance is running
906 raise errors.OpExecError("Console information for instance %s is"
907 " unavailable" % instance_name)
909 raise errors.OpExecError("Instance %s is not running, can't get console" %
912 return _DoConsole(objects.InstanceConsole.FromDict(console_data),
913 opts.show_command, cluster_name)
916 def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
917 _runcmd_fn=utils.RunCmd):
918 """Acts based on the result of L{opcodes.OpInstanceConsole}.
920 @type console: L{objects.InstanceConsole}
921 @param console: Console object
922 @type show_command: bool
923 @param show_command: Whether to just display commands
924 @type cluster_name: string
925 @param cluster_name: Cluster name as retrieved from master daemon
928 assert console.Validate()
930 if console.kind == constants.CONS_MESSAGE:
931 feedback_fn(console.message)
932 elif console.kind == constants.CONS_VNC:
933 feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
934 " URL <vnc://%s:%s/>",
935 console.instance, console.host, console.port,
936 console.display, console.host, console.port)
937 elif console.kind == constants.CONS_SPICE:
938 feedback_fn("Instance %s has SPICE listening on %s:%s", console.instance,
939 console.host, console.port)
940 elif console.kind == constants.CONS_SSH:
941 # Convert to string if not already one
942 if isinstance(console.command, basestring):
943 cmd = console.command
945 cmd = utils.ShellQuoteArgs(console.command)
947 srun = ssh.SshRunner(cluster_name=cluster_name)
948 ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
949 batch=True, quiet=False, tty=True)
952 feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
954 result = _runcmd_fn(ssh_cmd, interactive=True)
956 logging.error("Console command \"%s\" failed with reason '%s' and"
957 " output %r", result.cmd, result.fail_reason,
959 raise errors.OpExecError("Connection to console of instance %s failed,"
960 " please check cluster configuration" %
963 raise errors.GenericError("Unknown console type '%s'" % console.kind)
965 return constants.EXIT_SUCCESS
968 def _FormatLogicalID(dev_type, logical_id, roman):
969 """Formats the logical_id of a disk.
972 if dev_type == constants.LD_DRBD8:
973 node_a, node_b, port, minor_a, minor_b, key = logical_id
975 ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
977 ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
979 ("port", compat.TryToRoman(port, convert=roman)),
982 elif dev_type == constants.LD_LV:
983 vg_name, lv_name = logical_id
984 data = ["%s/%s" % (vg_name, lv_name)]
986 data = [str(logical_id)]
991 def _FormatBlockDevInfo(idx, top_level, dev, roman):
992 """Show block device information.
994 This is only used by L{ShowInstanceConfig}, but it's too big to be
995 left for an inline definition.
998 @param idx: the index of the current disk
999 @type top_level: boolean
1000 @param top_level: if this a top-level disk?
1002 @param dev: dictionary with disk information
1003 @type roman: boolean
1004 @param roman: whether to try to use roman integers
1005 @return: a list of either strings, tuples or lists
1006 (which should be formatted at a higher indent level)
1009 def helper(dtype, status):
1010 """Format one line for physical device status.
1013 @param dtype: a constant from the L{constants.LDS_BLOCK} set
1015 @param status: a tuple as returned from L{backend.FindBlockDevice}
1016 @return: the string representing the status
1022 (path, major, minor, syncp, estt, degr, ldisk_status) = status
1024 major_string = "N/A"
1026 major_string = str(compat.TryToRoman(major, convert=roman))
1029 minor_string = "N/A"
1031 minor_string = str(compat.TryToRoman(minor, convert=roman))
1033 txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1034 if dtype in (constants.LD_DRBD8, ):
1035 if syncp is not None:
1036 sync_text = "*RECOVERING* %5.2f%%," % syncp
1038 sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1040 sync_text += " ETA unknown"
1042 sync_text = "in sync"
1044 degr_text = "*DEGRADED*"
1047 if ldisk_status == constants.LDS_FAULTY:
1048 ldisk_text = " *MISSING DISK*"
1049 elif ldisk_status == constants.LDS_UNKNOWN:
1050 ldisk_text = " *UNCERTAIN STATE*"
1053 txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1054 elif dtype == constants.LD_LV:
1055 if ldisk_status == constants.LDS_FAULTY:
1056 ldisk_text = " *FAILED* (failed drive?)"
1064 if dev["iv_name"] is not None:
1065 txt = dev["iv_name"]
1067 txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1069 txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1070 if isinstance(dev["size"], int):
1071 nice_size = utils.FormatUnit(dev["size"], "h")
1073 nice_size = dev["size"]
1074 d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1077 data.append(("access mode", dev["mode"]))
1078 if dev["logical_id"] is not None:
1080 l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1082 l_id = [str(dev["logical_id"])]
1084 data.append(("logical_id", l_id[0]))
1087 elif dev["physical_id"] is not None:
1088 data.append("physical_id:")
1089 data.append([dev["physical_id"]])
1092 data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1095 data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1098 data.append("child devices:")
1099 for c_idx, child in enumerate(dev["children"]):
1100 data.append(_FormatBlockDevInfo(c_idx, False, child, roman))
1105 def _FormatList(buf, data, indent_level):
1106 """Formats a list of data at a given indent level.
1108 If the element of the list is:
1109 - a string, it is simply formatted as is
1110 - a tuple, it will be split into key, value and the all the
1111 values in a list will be aligned all at the same start column
1112 - a list, will be recursively formatted
1115 @param buf: the buffer into which we write the output
1116 @param data: the list to format
1117 @type indent_level: int
1118 @param indent_level: the indent level to format at
1121 max_tlen = max([len(elem[0]) for elem in data
1122 if isinstance(elem, tuple)] or [0])
1124 if isinstance(elem, basestring):
1125 buf.write("%*s%s\n" % (2 * indent_level, "", elem))
1126 elif isinstance(elem, tuple):
1128 spacer = "%*s" % (max_tlen - len(key), "")
1129 buf.write("%*s%s:%s %s\n" % (2 * indent_level, "", key, spacer, value))
1130 elif isinstance(elem, list):
1131 _FormatList(buf, elem, indent_level + 1)
1134 def ShowInstanceConfig(opts, args):
1135 """Compute instance run-time status.
1137 @param opts: the command line options selected by the user
1139 @param args: either an empty list, and then we query all
1140 instances, or should contain a list of instance names
1142 @return: the desired exit code
1145 if not args and not opts.show_all:
1146 ToStderr("No instance selected."
1147 " Please pass in --all if you want to query all instances.\n"
1148 "Note that this can take a long time on a big cluster.")
1150 elif args and opts.show_all:
1151 ToStderr("Cannot use --all if you specify instance names.")
1155 op = opcodes.OpInstanceQueryData(instances=args, static=opts.static,
1156 use_locking=not opts.static)
1157 result = SubmitOpCode(op, opts=opts)
1159 ToStdout("No instances.")
1164 for instance_name in result:
1165 instance = result[instance_name]
1166 buf.write("Instance name: %s\n" % instance["name"])
1167 buf.write("UUID: %s\n" % instance["uuid"])
1168 buf.write("Serial number: %s\n" %
1169 compat.TryToRoman(instance["serial_no"],
1170 convert=opts.roman_integers))
1171 buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1172 buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1173 buf.write("State: configured to be %s" % instance["config_state"])
1174 if instance["run_state"]:
1175 buf.write(", actual state is %s" % instance["run_state"])
1177 ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1178 ## instance["auto_balance"])
1179 buf.write(" Nodes:\n")
1180 buf.write(" - primary: %s\n" % instance["pnode"])
1181 buf.write(" group: %s (UUID %s)\n" %
1182 (instance["pnode_group_name"], instance["pnode_group_uuid"]))
1183 buf.write(" - secondaries: %s\n" %
1184 utils.CommaJoin("%s (group %s, group UUID %s)" %
1185 (name, group_name, group_uuid)
1186 for (name, group_name, group_uuid) in
1187 zip(instance["snodes"],
1188 instance["snodes_group_names"],
1189 instance["snodes_group_uuids"])))
1190 buf.write(" Operating system: %s\n" % instance["os"])
1191 FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1193 if "network_port" in instance:
1194 buf.write(" Allocated network port: %s\n" %
1195 compat.TryToRoman(instance["network_port"],
1196 convert=opts.roman_integers))
1197 buf.write(" Hypervisor: %s\n" % instance["hypervisor"])
1199 # custom VNC console information
1200 vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1202 if vnc_bind_address:
1203 port = instance["network_port"]
1204 display = int(port) - constants.VNC_BASE_PORT
1205 if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1206 vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1209 elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1210 vnc_console_port = ("%s:%s (node %s) (display %s)" %
1211 (vnc_bind_address, port,
1212 instance["pnode"], display))
1214 # vnc bind address is a file
1215 vnc_console_port = "%s:%s" % (instance["pnode"],
1217 buf.write(" - console connection: vnc to %s\n" % vnc_console_port)
1219 FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1221 buf.write(" Hardware:\n")
1222 buf.write(" - VCPUs: %s\n" %
1223 compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1224 convert=opts.roman_integers))
1225 buf.write(" - memory: %sMiB\n" %
1226 compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
1227 convert=opts.roman_integers))
1228 buf.write(" - NICs:\n")
1229 for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1230 buf.write(" - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1231 (idx, mac, ip, mode, link))
1232 buf.write(" Disk template: %s\n" % instance["disk_template"])
1233 buf.write(" Disks:\n")
1235 for idx, device in enumerate(instance["disks"]):
1236 _FormatList(buf, _FormatBlockDevInfo(idx, True, device,
1237 opts.roman_integers), 2)
1239 ToStdout(buf.getvalue().rstrip("\n"))
1243 def SetInstanceParams(opts, args):
1244 """Modifies an instance.
1246 All parameters take effect only at the next restart of the instance.
1248 @param opts: the command line options selected by the user
1250 @param args: should contain only one element, the instance name
1252 @return: the desired exit code
1255 if not (opts.nics or opts.disks or opts.disk_template or
1256 opts.hvparams or opts.beparams or opts.os or opts.osparams):
1257 ToStderr("Please give at least one of the parameters.")
1260 for param in opts.beparams:
1261 if isinstance(opts.beparams[param], basestring):
1262 if opts.beparams[param].lower() == "default":
1263 opts.beparams[param] = constants.VALUE_DEFAULT
1265 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1266 allowed_values=[constants.VALUE_DEFAULT])
1268 for param in opts.hvparams:
1269 if isinstance(opts.hvparams[param], basestring):
1270 if opts.hvparams[param].lower() == "default":
1271 opts.hvparams[param] = constants.VALUE_DEFAULT
1273 utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1274 allowed_values=[constants.VALUE_DEFAULT])
1276 for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1278 nic_op = int(nic_op)
1279 opts.nics[idx] = (nic_op, nic_dict)
1280 except (TypeError, ValueError):
1283 for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1285 disk_op = int(disk_op)
1286 opts.disks[idx] = (disk_op, disk_dict)
1287 except (TypeError, ValueError):
1289 if disk_op == constants.DDM_ADD:
1290 if "size" not in disk_dict:
1291 raise errors.OpPrereqError("Missing required parameter 'size'",
1293 disk_dict["size"] = utils.ParseUnit(disk_dict["size"])
1295 if (opts.disk_template and
1296 opts.disk_template in constants.DTS_INT_MIRROR and
1298 ToStderr("Changing the disk template to a mirrored one requires"
1299 " specifying a secondary node")
1302 op = opcodes.OpInstanceSetParams(instance_name=args[0],
1305 disk_template=opts.disk_template,
1306 remote_node=opts.node,
1307 hvparams=opts.hvparams,
1308 beparams=opts.beparams,
1310 osparams=opts.osparams,
1311 force_variant=opts.force_variant,
1313 wait_for_sync=opts.wait_for_sync)
1315 # even if here we process the result, we allow submit only
1316 result = SubmitOrSend(op, opts)
1319 ToStdout("Modified instance %s", args[0])
1320 for param, data in result:
1321 ToStdout(" - %-5s -> %s", param, data)
1322 ToStdout("Please don't forget that most parameters take effect"
1323 " only at the next start of the instance.")
1327 def ChangeGroup(opts, args):
1328 """Moves an instance to another group.
1331 (instance_name, ) = args
1335 op = opcodes.OpInstanceChangeGroup(instance_name=instance_name,
1336 iallocator=opts.iallocator,
1337 target_groups=opts.to,
1338 early_release=opts.early_release)
1339 result = SubmitOpCode(op, cl=cl, opts=opts)
1341 # Keep track of submitted jobs
1342 jex = JobExecutor(cl=cl, opts=opts)
1344 for (status, job_id) in result[constants.JOB_IDS_KEY]:
1345 jex.AddJobId(None, status, job_id)
1347 results = jex.GetResults()
1348 bad_cnt = len([row for row in results if not row[0]])
1350 ToStdout("Instance '%s' changed group successfully.", instance_name)
1351 rcode = constants.EXIT_SUCCESS
1353 ToStdout("There were %s errors while changing group of instance '%s'.",
1354 bad_cnt, instance_name)
1355 rcode = constants.EXIT_FAILURE
1360 # multi-instance selection options
1361 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1362 help="Do not ask for confirmation when more than"
1363 " one instance is affected",
1364 action="store_true", default=False)
1366 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1367 help="Filter by nodes (primary only)",
1368 const=_EXPAND_NODES_PRI, action="store_const")
1370 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1371 help="Filter by nodes (secondary only)",
1372 const=_EXPAND_NODES_SEC, action="store_const")
1374 m_node_opt = cli_option("--node", dest="multi_mode",
1375 help="Filter by nodes (primary and secondary)",
1376 const=_EXPAND_NODES_BOTH, action="store_const")
1378 m_clust_opt = cli_option("--all", dest="multi_mode",
1379 help="Select all instances in the cluster",
1380 const=_EXPAND_CLUSTER, action="store_const")
1382 m_inst_opt = cli_option("--instance", dest="multi_mode",
1383 help="Filter by instance name [default]",
1384 const=_EXPAND_INSTANCES, action="store_const")
1386 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1387 help="Filter by node tag",
1388 const=_EXPAND_NODES_BOTH_BY_TAGS,
1389 action="store_const")
1391 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1392 help="Filter by primary node tag",
1393 const=_EXPAND_NODES_PRI_BY_TAGS,
1394 action="store_const")
1396 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1397 help="Filter by secondary node tag",
1398 const=_EXPAND_NODES_SEC_BY_TAGS,
1399 action="store_const")
1401 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1402 help="Filter by instance tag",
1403 const=_EXPAND_INSTANCES_BY_TAGS,
1404 action="store_const")
1406 # this is defined separately due to readability only
1416 AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1417 "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1418 "Creates and adds a new instance to the cluster"),
1420 BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1422 "Create a bunch of instances based on specs in the file."),
1424 ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1425 [SHOWCMD_OPT, PRIORITY_OPT],
1426 "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1428 FailoverInstance, ARGS_ONE_INSTANCE,
1429 [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1430 DRY_RUN_OPT, PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT],
1431 "[-f] <instance>", "Stops the instance, changes its primary node and"
1432 " (if it was originally running) starts it on the new node"
1433 " (the secondary for mirrored instances or any node"
1434 " for shared storage)."),
1436 MigrateInstance, ARGS_ONE_INSTANCE,
1437 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1438 PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT, ALLOW_FAILOVER_OPT],
1439 "[-f] <instance>", "Migrate instance to its secondary node"
1440 " (only for mirrored instances)"),
1442 MoveInstance, ARGS_ONE_INSTANCE,
1443 [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1444 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_CONSIST_OPT],
1445 "[-f] <instance>", "Move instance to an arbitrary node"
1446 " (only for instances of type file and lv)"),
1448 ShowInstanceConfig, ARGS_MANY_INSTANCES,
1449 [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1450 "[-s] {--all | <instance>...}",
1451 "Show information on the specified instance(s)"),
1453 ListInstances, ARGS_MANY_INSTANCES,
1454 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
1457 "Lists the instances and their status. The available fields can be shown"
1458 " using the \"list-fields\" command (see the man page for details)."
1459 " The default field list is (in order): %s." %
1460 utils.CommaJoin(_LIST_DEF_FIELDS),
1463 ListInstanceFields, [ArgUnknown()],
1464 [NOHDR_OPT, SEP_OPT],
1466 "Lists all available fields for instances"),
1468 ReinstallInstance, [ArgInstance()],
1469 [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1470 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1471 m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1472 SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1473 "[-f] <instance>", "Reinstall a stopped instance"),
1475 RemoveInstance, ARGS_ONE_INSTANCE,
1476 [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1477 DRY_RUN_OPT, PRIORITY_OPT],
1478 "[-f] <instance>", "Shuts down the instance and removes it"),
1481 [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1482 [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1483 "<instance> <new_name>", "Rename the instance"),
1485 ReplaceDisks, ARGS_ONE_INSTANCE,
1486 [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1487 NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1488 DRY_RUN_OPT, PRIORITY_OPT],
1489 "[-s|-p|-n NODE|-I NAME] <instance>",
1490 "Replaces all disks for the instance"),
1492 SetInstanceParams, ARGS_ONE_INSTANCE,
1493 [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1494 DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1495 OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT, NWSYNC_OPT],
1496 "<instance>", "Alters the parameters of an instance"),
1498 GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1499 [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1500 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1501 m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1502 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT, NO_REMEMBER_OPT],
1503 "<instance>", "Stops an instance"),
1505 GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1506 [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1507 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1508 m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1509 BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT,
1510 NO_REMEMBER_OPT, STARTUP_PAUSED_OPT],
1511 "<instance>", "Starts an instance"),
1513 GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1514 [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1515 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1516 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1517 m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1518 "<instance>", "Reboots an instance"),
1520 ActivateDisks, ARGS_ONE_INSTANCE,
1521 [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
1522 "<instance>", "Activate an instance's disks"),
1523 "deactivate-disks": (
1524 DeactivateDisks, ARGS_ONE_INSTANCE,
1525 [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1526 "[-f] <instance>", "Deactivate an instance's disks"),
1528 RecreateDisks, ARGS_ONE_INSTANCE,
1529 [SUBMIT_OPT, DISKIDX_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1530 "<instance>", "Recreate an instance's disks"),
1533 [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1534 ArgUnknown(min=1, max=1)],
1535 [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1536 "<instance> <disk> <size>", "Grow an instance's disk"),
1538 ChangeGroup, ARGS_ONE_INSTANCE,
1539 [TO_GROUP_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT],
1540 "[-I <iallocator>] [--to <group>]", "Change group of instance"),
1542 ListTags, ARGS_ONE_INSTANCE, [],
1543 "<instance_name>", "List the tags of the given instance"),
1545 AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1546 [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
1547 "<instance_name> tag...", "Add tags to the given instance"),
1549 RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1550 [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
1551 "<instance_name> tag...", "Remove tags from given instance"),
1554 #: dictionary with aliases for commands
1562 return GenericMain(commands, aliases=aliases,
1563 override={"tag_type": constants.TAG_INSTANCE})