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_COMPAT)
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 three elements, the target instance name,
634 the target disk id, and the target growth
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 ignore_ipolicy=opts.ignore_ipolicy)
796 SubmitOrSend(op, opts, cl=cl)
800 def MigrateInstance(opts, args):
801 """Migrate an instance.
803 The migrate is done without shutdown.
805 @param opts: the command line options selected by the user
807 @param args: should contain only one element, the instance name
809 @return: the desired exit code
813 instance_name = args[0]
815 iallocator = opts.iallocator
816 target_node = opts.dst_node
818 if iallocator and target_node:
819 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
820 " node (-n) but not both", errors.ECODE_INVAL)
823 _EnsureInstancesExist(cl, [instance_name])
826 usertext = ("Instance %s will be recovered from a failed migration."
827 " Note that the migration procedure (including cleanup)" %
830 usertext = ("Instance %s will be migrated. Note that migration" %
832 usertext += (" might impact the instance if anything goes wrong"
833 " (e.g. due to bugs in the hypervisor). Continue?")
834 if not AskUser(usertext):
837 # this should be removed once --non-live is deprecated
838 if not opts.live and opts.migration_mode is not None:
839 raise errors.OpPrereqError("Only one of the --non-live and "
840 "--migration-mode options can be passed",
842 if not opts.live: # --non-live passed
843 mode = constants.HT_MIGRATION_NONLIVE
845 mode = opts.migration_mode
847 op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
848 cleanup=opts.cleanup, iallocator=iallocator,
849 target_node=target_node,
850 allow_failover=opts.allow_failover)
851 SubmitOpCode(op, cl=cl, opts=opts)
855 def MoveInstance(opts, args):
858 @param opts: the command line options selected by the user
860 @param args: should contain only one element, the instance name
862 @return: the desired exit code
866 instance_name = args[0]
870 usertext = ("Instance %s will be moved."
871 " This requires a shutdown of the instance. Continue?" %
873 if not AskUser(usertext):
876 op = opcodes.OpInstanceMove(instance_name=instance_name,
877 target_node=opts.node,
878 shutdown_timeout=opts.shutdown_timeout,
879 ignore_consistency=opts.ignore_consistency)
880 SubmitOrSend(op, opts, cl=cl)
884 def ConnectToInstanceConsole(opts, args):
885 """Connect to the console of an instance.
887 @param opts: the command line options selected by the user
889 @param args: should contain only one element, the instance name
891 @return: the desired exit code
894 instance_name = args[0]
898 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
899 ((console_data, oper_state), ) = \
900 cl.QueryInstances([instance_name], ["console", "oper_state"], False)
902 # Ensure client connection is closed while external commands are run
909 # Instance is running
910 raise errors.OpExecError("Console information for instance %s is"
911 " unavailable" % instance_name)
913 raise errors.OpExecError("Instance %s is not running, can't get console" %
916 return _DoConsole(objects.InstanceConsole.FromDict(console_data),
917 opts.show_command, cluster_name)
920 def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
921 _runcmd_fn=utils.RunCmd):
922 """Acts based on the result of L{opcodes.OpInstanceConsole}.
924 @type console: L{objects.InstanceConsole}
925 @param console: Console object
926 @type show_command: bool
927 @param show_command: Whether to just display commands
928 @type cluster_name: string
929 @param cluster_name: Cluster name as retrieved from master daemon
932 assert console.Validate()
934 if console.kind == constants.CONS_MESSAGE:
935 feedback_fn(console.message)
936 elif console.kind == constants.CONS_VNC:
937 feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
938 " URL <vnc://%s:%s/>",
939 console.instance, console.host, console.port,
940 console.display, console.host, console.port)
941 elif console.kind == constants.CONS_SPICE:
942 feedback_fn("Instance %s has SPICE listening on %s:%s", console.instance,
943 console.host, console.port)
944 elif console.kind == constants.CONS_SSH:
945 # Convert to string if not already one
946 if isinstance(console.command, basestring):
947 cmd = console.command
949 cmd = utils.ShellQuoteArgs(console.command)
951 srun = ssh.SshRunner(cluster_name=cluster_name)
952 ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
953 batch=True, quiet=False, tty=True)
956 feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
958 result = _runcmd_fn(ssh_cmd, interactive=True)
960 logging.error("Console command \"%s\" failed with reason '%s' and"
961 " output %r", result.cmd, result.fail_reason,
963 raise errors.OpExecError("Connection to console of instance %s failed,"
964 " please check cluster configuration" %
967 raise errors.GenericError("Unknown console type '%s'" % console.kind)
969 return constants.EXIT_SUCCESS
972 def _FormatLogicalID(dev_type, logical_id, roman):
973 """Formats the logical_id of a disk.
976 if dev_type == constants.LD_DRBD8:
977 node_a, node_b, port, minor_a, minor_b, key = logical_id
979 ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
981 ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
983 ("port", compat.TryToRoman(port, convert=roman)),
986 elif dev_type == constants.LD_LV:
987 vg_name, lv_name = logical_id
988 data = ["%s/%s" % (vg_name, lv_name)]
990 data = [str(logical_id)]
995 def _FormatBlockDevInfo(idx, top_level, dev, roman):
996 """Show block device information.
998 This is only used by L{ShowInstanceConfig}, but it's too big to be
999 left for an inline definition.
1002 @param idx: the index of the current disk
1003 @type top_level: boolean
1004 @param top_level: if this a top-level disk?
1006 @param dev: dictionary with disk information
1007 @type roman: boolean
1008 @param roman: whether to try to use roman integers
1009 @return: a list of either strings, tuples or lists
1010 (which should be formatted at a higher indent level)
1013 def helper(dtype, status):
1014 """Format one line for physical device status.
1017 @param dtype: a constant from the L{constants.LDS_BLOCK} set
1019 @param status: a tuple as returned from L{backend.FindBlockDevice}
1020 @return: the string representing the status
1026 (path, major, minor, syncp, estt, degr, ldisk_status) = status
1028 major_string = "N/A"
1030 major_string = str(compat.TryToRoman(major, convert=roman))
1033 minor_string = "N/A"
1035 minor_string = str(compat.TryToRoman(minor, convert=roman))
1037 txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1038 if dtype in (constants.LD_DRBD8, ):
1039 if syncp is not None:
1040 sync_text = "*RECOVERING* %5.2f%%," % syncp
1042 sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1044 sync_text += " ETA unknown"
1046 sync_text = "in sync"
1048 degr_text = "*DEGRADED*"
1051 if ldisk_status == constants.LDS_FAULTY:
1052 ldisk_text = " *MISSING DISK*"
1053 elif ldisk_status == constants.LDS_UNKNOWN:
1054 ldisk_text = " *UNCERTAIN STATE*"
1057 txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1058 elif dtype == constants.LD_LV:
1059 if ldisk_status == constants.LDS_FAULTY:
1060 ldisk_text = " *FAILED* (failed drive?)"
1068 if dev["iv_name"] is not None:
1069 txt = dev["iv_name"]
1071 txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1073 txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1074 if isinstance(dev["size"], int):
1075 nice_size = utils.FormatUnit(dev["size"], "h")
1077 nice_size = dev["size"]
1078 d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1081 data.append(("access mode", dev["mode"]))
1082 if dev["logical_id"] is not None:
1084 l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1086 l_id = [str(dev["logical_id"])]
1088 data.append(("logical_id", l_id[0]))
1091 elif dev["physical_id"] is not None:
1092 data.append("physical_id:")
1093 data.append([dev["physical_id"]])
1096 data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1099 data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1102 data.append("child devices:")
1103 for c_idx, child in enumerate(dev["children"]):
1104 data.append(_FormatBlockDevInfo(c_idx, False, child, roman))
1109 def _FormatList(buf, data, indent_level):
1110 """Formats a list of data at a given indent level.
1112 If the element of the list is:
1113 - a string, it is simply formatted as is
1114 - a tuple, it will be split into key, value and the all the
1115 values in a list will be aligned all at the same start column
1116 - a list, will be recursively formatted
1119 @param buf: the buffer into which we write the output
1120 @param data: the list to format
1121 @type indent_level: int
1122 @param indent_level: the indent level to format at
1125 max_tlen = max([len(elem[0]) for elem in data
1126 if isinstance(elem, tuple)] or [0])
1128 if isinstance(elem, basestring):
1129 buf.write("%*s%s\n" % (2 * indent_level, "", elem))
1130 elif isinstance(elem, tuple):
1132 spacer = "%*s" % (max_tlen - len(key), "")
1133 buf.write("%*s%s:%s %s\n" % (2 * indent_level, "", key, spacer, value))
1134 elif isinstance(elem, list):
1135 _FormatList(buf, elem, indent_level + 1)
1138 def ShowInstanceConfig(opts, args):
1139 """Compute instance run-time status.
1141 @param opts: the command line options selected by the user
1143 @param args: either an empty list, and then we query all
1144 instances, or should contain a list of instance names
1146 @return: the desired exit code
1149 if not args and not opts.show_all:
1150 ToStderr("No instance selected."
1151 " Please pass in --all if you want to query all instances.\n"
1152 "Note that this can take a long time on a big cluster.")
1154 elif args and opts.show_all:
1155 ToStderr("Cannot use --all if you specify instance names.")
1159 op = opcodes.OpInstanceQueryData(instances=args, static=opts.static,
1160 use_locking=not opts.static)
1161 result = SubmitOpCode(op, opts=opts)
1163 ToStdout("No instances.")
1168 for instance_name in result:
1169 instance = result[instance_name]
1170 buf.write("Instance name: %s\n" % instance["name"])
1171 buf.write("UUID: %s\n" % instance["uuid"])
1172 buf.write("Serial number: %s\n" %
1173 compat.TryToRoman(instance["serial_no"],
1174 convert=opts.roman_integers))
1175 buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1176 buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1177 buf.write("State: configured to be %s" % instance["config_state"])
1178 if instance["run_state"]:
1179 buf.write(", actual state is %s" % instance["run_state"])
1181 ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1182 ## instance["auto_balance"])
1183 buf.write(" Nodes:\n")
1184 buf.write(" - primary: %s\n" % instance["pnode"])
1185 buf.write(" - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1186 buf.write(" Operating system: %s\n" % instance["os"])
1187 FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1189 if "network_port" in instance:
1190 buf.write(" Allocated network port: %s\n" %
1191 compat.TryToRoman(instance["network_port"],
1192 convert=opts.roman_integers))
1193 buf.write(" Hypervisor: %s\n" % instance["hypervisor"])
1195 # custom VNC console information
1196 vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1198 if vnc_bind_address:
1199 port = instance["network_port"]
1200 display = int(port) - constants.VNC_BASE_PORT
1201 if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1202 vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1205 elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1206 vnc_console_port = ("%s:%s (node %s) (display %s)" %
1207 (vnc_bind_address, port,
1208 instance["pnode"], display))
1210 # vnc bind address is a file
1211 vnc_console_port = "%s:%s" % (instance["pnode"],
1213 buf.write(" - console connection: vnc to %s\n" % vnc_console_port)
1215 FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1217 buf.write(" Hardware:\n")
1218 buf.write(" - VCPUs: %s\n" %
1219 compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1220 convert=opts.roman_integers))
1221 buf.write(" - maxmem: %sMiB\n" %
1222 compat.TryToRoman(instance["be_actual"][constants.BE_MAXMEM],
1223 convert=opts.roman_integers))
1224 buf.write(" - minmem: %sMiB\n" %
1225 compat.TryToRoman(instance["be_actual"][constants.BE_MINMEM],
1226 convert=opts.roman_integers))
1227 # deprecated "memory" value, kept for one version for compatibility
1228 # TODO(ganeti 2.7) remove.
1229 buf.write(" - memory: %sMiB\n" %
1230 compat.TryToRoman(instance["be_actual"][constants.BE_MAXMEM],
1231 convert=opts.roman_integers))
1232 buf.write(" - %s: %s\n" %
1233 (constants.BE_ALWAYS_FAILOVER,
1234 instance["be_actual"][constants.BE_ALWAYS_FAILOVER]))
1235 buf.write(" - NICs:\n")
1236 for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1237 buf.write(" - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1238 (idx, mac, ip, mode, link))
1239 buf.write(" Disk template: %s\n" % instance["disk_template"])
1240 buf.write(" Disks:\n")
1242 for idx, device in enumerate(instance["disks"]):
1243 _FormatList(buf, _FormatBlockDevInfo(idx, True, device,
1244 opts.roman_integers), 2)
1246 ToStdout(buf.getvalue().rstrip("\n"))
1250 def SetInstanceParams(opts, args):
1251 """Modifies an instance.
1253 All parameters take effect only at the next restart of the instance.
1255 @param opts: the command line options selected by the user
1257 @param args: should contain only one element, the instance name
1259 @return: the desired exit code
1262 if not (opts.nics or opts.disks or opts.disk_template or
1263 opts.hvparams or opts.beparams or opts.os or opts.osparams or
1264 opts.offline_inst or opts.online_inst):
1265 ToStderr("Please give at least one of the parameters.")
1268 for param in opts.beparams:
1269 if isinstance(opts.beparams[param], basestring):
1270 if opts.beparams[param].lower() == "default":
1271 opts.beparams[param] = constants.VALUE_DEFAULT
1273 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT,
1274 allowed_values=[constants.VALUE_DEFAULT])
1276 for param in opts.hvparams:
1277 if isinstance(opts.hvparams[param], basestring):
1278 if opts.hvparams[param].lower() == "default":
1279 opts.hvparams[param] = constants.VALUE_DEFAULT
1281 utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1282 allowed_values=[constants.VALUE_DEFAULT])
1284 for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1286 nic_op = int(nic_op)
1287 opts.nics[idx] = (nic_op, nic_dict)
1288 except (TypeError, ValueError):
1291 for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1293 disk_op = int(disk_op)
1294 opts.disks[idx] = (disk_op, disk_dict)
1295 except (TypeError, ValueError):
1297 if disk_op == constants.DDM_ADD:
1298 if "size" not in disk_dict:
1299 raise errors.OpPrereqError("Missing required parameter 'size'",
1301 disk_dict["size"] = utils.ParseUnit(disk_dict["size"])
1303 if (opts.disk_template and
1304 opts.disk_template in constants.DTS_INT_MIRROR and
1306 ToStderr("Changing the disk template to a mirrored one requires"
1307 " specifying a secondary node")
1310 op = opcodes.OpInstanceSetParams(instance_name=args[0],
1313 disk_template=opts.disk_template,
1314 remote_node=opts.node,
1315 hvparams=opts.hvparams,
1316 beparams=opts.beparams,
1318 osparams=opts.osparams,
1319 force_variant=opts.force_variant,
1321 wait_for_sync=opts.wait_for_sync,
1322 offline_inst=opts.offline_inst,
1323 online_inst=opts.online_inst)
1325 # even if here we process the result, we allow submit only
1326 result = SubmitOrSend(op, opts)
1329 ToStdout("Modified instance %s", args[0])
1330 for param, data in result:
1331 ToStdout(" - %-5s -> %s", param, data)
1332 ToStdout("Please don't forget that most parameters take effect"
1333 " only at the next start of the instance.")
1337 def ChangeGroup(opts, args):
1338 """Moves an instance to another group.
1341 (instance_name, ) = args
1345 op = opcodes.OpInstanceChangeGroup(instance_name=instance_name,
1346 iallocator=opts.iallocator,
1347 target_groups=opts.to,
1348 early_release=opts.early_release)
1349 result = SubmitOpCode(op, cl=cl, opts=opts)
1351 # Keep track of submitted jobs
1352 jex = JobExecutor(cl=cl, opts=opts)
1354 for (status, job_id) in result[constants.JOB_IDS_KEY]:
1355 jex.AddJobId(None, status, job_id)
1357 results = jex.GetResults()
1358 bad_cnt = len([row for row in results if not row[0]])
1360 ToStdout("Instance '%s' changed group successfully.", instance_name)
1361 rcode = constants.EXIT_SUCCESS
1363 ToStdout("There were %s errors while changing group of instance '%s'.",
1364 bad_cnt, instance_name)
1365 rcode = constants.EXIT_FAILURE
1370 # multi-instance selection options
1371 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1372 help="Do not ask for confirmation when more than"
1373 " one instance is affected",
1374 action="store_true", default=False)
1376 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1377 help="Filter by nodes (primary only)",
1378 const=_EXPAND_NODES_PRI, action="store_const")
1380 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1381 help="Filter by nodes (secondary only)",
1382 const=_EXPAND_NODES_SEC, action="store_const")
1384 m_node_opt = cli_option("--node", dest="multi_mode",
1385 help="Filter by nodes (primary and secondary)",
1386 const=_EXPAND_NODES_BOTH, action="store_const")
1388 m_clust_opt = cli_option("--all", dest="multi_mode",
1389 help="Select all instances in the cluster",
1390 const=_EXPAND_CLUSTER, action="store_const")
1392 m_inst_opt = cli_option("--instance", dest="multi_mode",
1393 help="Filter by instance name [default]",
1394 const=_EXPAND_INSTANCES, action="store_const")
1396 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1397 help="Filter by node tag",
1398 const=_EXPAND_NODES_BOTH_BY_TAGS,
1399 action="store_const")
1401 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1402 help="Filter by primary node tag",
1403 const=_EXPAND_NODES_PRI_BY_TAGS,
1404 action="store_const")
1406 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1407 help="Filter by secondary node tag",
1408 const=_EXPAND_NODES_SEC_BY_TAGS,
1409 action="store_const")
1411 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1412 help="Filter by instance tag",
1413 const=_EXPAND_INSTANCES_BY_TAGS,
1414 action="store_const")
1416 # this is defined separately due to readability only
1426 AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1427 "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1428 "Creates and adds a new instance to the cluster"),
1430 BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1432 "Create a bunch of instances based on specs in the file."),
1434 ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1435 [SHOWCMD_OPT, PRIORITY_OPT],
1436 "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1438 FailoverInstance, ARGS_ONE_INSTANCE,
1439 [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1440 DRY_RUN_OPT, PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT,
1441 IGNORE_IPOLICY_OPT],
1442 "[-f] <instance>", "Stops the instance, changes its primary node and"
1443 " (if it was originally running) starts it on the new node"
1444 " (the secondary for mirrored instances or any node"
1445 " for shared storage)."),
1447 MigrateInstance, ARGS_ONE_INSTANCE,
1448 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1449 PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT, ALLOW_FAILOVER_OPT],
1450 "[-f] <instance>", "Migrate instance to its secondary node"
1451 " (only for mirrored instances)"),
1453 MoveInstance, ARGS_ONE_INSTANCE,
1454 [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1455 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_CONSIST_OPT],
1456 "[-f] <instance>", "Move instance to an arbitrary node"
1457 " (only for instances of type file and lv)"),
1459 ShowInstanceConfig, ARGS_MANY_INSTANCES,
1460 [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1461 "[-s] {--all | <instance>...}",
1462 "Show information on the specified instance(s)"),
1464 ListInstances, ARGS_MANY_INSTANCES,
1465 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
1468 "Lists the instances and their status. The available fields can be shown"
1469 " using the \"list-fields\" command (see the man page for details)."
1470 " The default field list is (in order): %s." %
1471 utils.CommaJoin(_LIST_DEF_FIELDS),
1474 ListInstanceFields, [ArgUnknown()],
1475 [NOHDR_OPT, SEP_OPT],
1477 "Lists all available fields for instances"),
1479 ReinstallInstance, [ArgInstance()],
1480 [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1481 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1482 m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1483 SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1484 "[-f] <instance>", "Reinstall a stopped instance"),
1486 RemoveInstance, ARGS_ONE_INSTANCE,
1487 [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1488 DRY_RUN_OPT, PRIORITY_OPT],
1489 "[-f] <instance>", "Shuts down the instance and removes it"),
1492 [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1493 [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1494 "<instance> <new_name>", "Rename the instance"),
1496 ReplaceDisks, ARGS_ONE_INSTANCE,
1497 [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1498 NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1499 DRY_RUN_OPT, PRIORITY_OPT],
1500 "[-s|-p|-n NODE|-I NAME] <instance>",
1501 "Replaces all disks for the instance"),
1503 SetInstanceParams, ARGS_ONE_INSTANCE,
1504 [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1505 DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1506 OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT, NWSYNC_OPT, OFFLINE_INST_OPT,
1508 "<instance>", "Alters the parameters of an instance"),
1510 GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1511 [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1512 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1513 m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1514 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT, NO_REMEMBER_OPT],
1515 "<instance>", "Stops an instance"),
1517 GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1518 [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1519 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1520 m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1521 BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT,
1522 NO_REMEMBER_OPT, STARTUP_PAUSED_OPT],
1523 "<instance>", "Starts an instance"),
1525 GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1526 [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1527 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1528 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1529 m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1530 "<instance>", "Reboots an instance"),
1532 ActivateDisks, ARGS_ONE_INSTANCE,
1533 [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
1534 "<instance>", "Activate an instance's disks"),
1535 "deactivate-disks": (
1536 DeactivateDisks, ARGS_ONE_INSTANCE,
1537 [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1538 "[-f] <instance>", "Deactivate an instance's disks"),
1540 RecreateDisks, ARGS_ONE_INSTANCE,
1541 [SUBMIT_OPT, DISKIDX_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1542 "<instance>", "Recreate an instance's disks"),
1545 [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1546 ArgUnknown(min=1, max=1)],
1547 [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1548 "<instance> <disk> <size>", "Grow an instance's disk"),
1550 ChangeGroup, ARGS_ONE_INSTANCE,
1551 [TO_GROUP_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT],
1552 "[-I <iallocator>] [--to <group>]", "Change group of instance"),
1554 ListTags, ARGS_ONE_INSTANCE, [PRIORITY_OPT],
1555 "<instance_name>", "List the tags of the given instance"),
1557 AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1558 [TAG_SRC_OPT, PRIORITY_OPT],
1559 "<instance_name> tag...", "Add tags to the given instance"),
1561 RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1562 [TAG_SRC_OPT, PRIORITY_OPT],
1563 "<instance_name> tag...", "Remove tags from given instance"),
1566 #: dictionary with aliases for commands
1574 return GenericMain(commands, aliases=aliases,
1575 override={"tag_type": constants.TAG_INSTANCE},
1576 env_override=_ENV_OVERRIDE)