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 ignore_ipolicy=opts.ignore_ipolicy)
852 SubmitOpCode(op, cl=cl, opts=opts)
856 def MoveInstance(opts, args):
859 @param opts: the command line options selected by the user
861 @param args: should contain only one element, the instance name
863 @return: the desired exit code
867 instance_name = args[0]
871 usertext = ("Instance %s will be moved."
872 " This requires a shutdown of the instance. Continue?" %
874 if not AskUser(usertext):
877 op = opcodes.OpInstanceMove(instance_name=instance_name,
878 target_node=opts.node,
879 shutdown_timeout=opts.shutdown_timeout,
880 ignore_consistency=opts.ignore_consistency,
881 ignore_ipolicy=opts.ignore_ipolicy)
882 SubmitOrSend(op, opts, cl=cl)
886 def ConnectToInstanceConsole(opts, args):
887 """Connect to the console of an instance.
889 @param opts: the command line options selected by the user
891 @param args: should contain only one element, the instance name
893 @return: the desired exit code
896 instance_name = args[0]
900 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
901 ((console_data, oper_state), ) = \
902 cl.QueryInstances([instance_name], ["console", "oper_state"], False)
904 # Ensure client connection is closed while external commands are run
911 # Instance is running
912 raise errors.OpExecError("Console information for instance %s is"
913 " unavailable" % instance_name)
915 raise errors.OpExecError("Instance %s is not running, can't get console" %
918 return _DoConsole(objects.InstanceConsole.FromDict(console_data),
919 opts.show_command, cluster_name)
922 def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
923 _runcmd_fn=utils.RunCmd):
924 """Acts based on the result of L{opcodes.OpInstanceConsole}.
926 @type console: L{objects.InstanceConsole}
927 @param console: Console object
928 @type show_command: bool
929 @param show_command: Whether to just display commands
930 @type cluster_name: string
931 @param cluster_name: Cluster name as retrieved from master daemon
934 assert console.Validate()
936 if console.kind == constants.CONS_MESSAGE:
937 feedback_fn(console.message)
938 elif console.kind == constants.CONS_VNC:
939 feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
940 " URL <vnc://%s:%s/>",
941 console.instance, console.host, console.port,
942 console.display, console.host, console.port)
943 elif console.kind == constants.CONS_SPICE:
944 feedback_fn("Instance %s has SPICE listening on %s:%s", console.instance,
945 console.host, console.port)
946 elif console.kind == constants.CONS_SSH:
947 # Convert to string if not already one
948 if isinstance(console.command, basestring):
949 cmd = console.command
951 cmd = utils.ShellQuoteArgs(console.command)
953 srun = ssh.SshRunner(cluster_name=cluster_name)
954 ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
955 batch=True, quiet=False, tty=True)
958 feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
960 result = _runcmd_fn(ssh_cmd, interactive=True)
962 logging.error("Console command \"%s\" failed with reason '%s' and"
963 " output %r", result.cmd, result.fail_reason,
965 raise errors.OpExecError("Connection to console of instance %s failed,"
966 " please check cluster configuration" %
969 raise errors.GenericError("Unknown console type '%s'" % console.kind)
971 return constants.EXIT_SUCCESS
974 def _FormatLogicalID(dev_type, logical_id, roman):
975 """Formats the logical_id of a disk.
978 if dev_type == constants.LD_DRBD8:
979 node_a, node_b, port, minor_a, minor_b, key = logical_id
981 ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
983 ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
985 ("port", compat.TryToRoman(port, convert=roman)),
988 elif dev_type == constants.LD_LV:
989 vg_name, lv_name = logical_id
990 data = ["%s/%s" % (vg_name, lv_name)]
992 data = [str(logical_id)]
997 def _FormatBlockDevInfo(idx, top_level, dev, roman):
998 """Show block device information.
1000 This is only used by L{ShowInstanceConfig}, but it's too big to be
1001 left for an inline definition.
1004 @param idx: the index of the current disk
1005 @type top_level: boolean
1006 @param top_level: if this a top-level disk?
1008 @param dev: dictionary with disk information
1009 @type roman: boolean
1010 @param roman: whether to try to use roman integers
1011 @return: a list of either strings, tuples or lists
1012 (which should be formatted at a higher indent level)
1015 def helper(dtype, status):
1016 """Format one line for physical device status.
1019 @param dtype: a constant from the L{constants.LDS_BLOCK} set
1021 @param status: a tuple as returned from L{backend.FindBlockDevice}
1022 @return: the string representing the status
1028 (path, major, minor, syncp, estt, degr, ldisk_status) = status
1030 major_string = "N/A"
1032 major_string = str(compat.TryToRoman(major, convert=roman))
1035 minor_string = "N/A"
1037 minor_string = str(compat.TryToRoman(minor, convert=roman))
1039 txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1040 if dtype in (constants.LD_DRBD8, ):
1041 if syncp is not None:
1042 sync_text = "*RECOVERING* %5.2f%%," % syncp
1044 sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1046 sync_text += " ETA unknown"
1048 sync_text = "in sync"
1050 degr_text = "*DEGRADED*"
1053 if ldisk_status == constants.LDS_FAULTY:
1054 ldisk_text = " *MISSING DISK*"
1055 elif ldisk_status == constants.LDS_UNKNOWN:
1056 ldisk_text = " *UNCERTAIN STATE*"
1059 txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1060 elif dtype == constants.LD_LV:
1061 if ldisk_status == constants.LDS_FAULTY:
1062 ldisk_text = " *FAILED* (failed drive?)"
1070 if dev["iv_name"] is not None:
1071 txt = dev["iv_name"]
1073 txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1075 txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1076 if isinstance(dev["size"], int):
1077 nice_size = utils.FormatUnit(dev["size"], "h")
1079 nice_size = dev["size"]
1080 d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1083 data.append(("access mode", dev["mode"]))
1084 if dev["logical_id"] is not None:
1086 l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1088 l_id = [str(dev["logical_id"])]
1090 data.append(("logical_id", l_id[0]))
1093 elif dev["physical_id"] is not None:
1094 data.append("physical_id:")
1095 data.append([dev["physical_id"]])
1098 data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1101 data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1104 data.append("child devices:")
1105 for c_idx, child in enumerate(dev["children"]):
1106 data.append(_FormatBlockDevInfo(c_idx, False, child, roman))
1111 def _FormatList(buf, data, indent_level):
1112 """Formats a list of data at a given indent level.
1114 If the element of the list is:
1115 - a string, it is simply formatted as is
1116 - a tuple, it will be split into key, value and the all the
1117 values in a list will be aligned all at the same start column
1118 - a list, will be recursively formatted
1121 @param buf: the buffer into which we write the output
1122 @param data: the list to format
1123 @type indent_level: int
1124 @param indent_level: the indent level to format at
1127 max_tlen = max([len(elem[0]) for elem in data
1128 if isinstance(elem, tuple)] or [0])
1130 if isinstance(elem, basestring):
1131 buf.write("%*s%s\n" % (2 * indent_level, "", elem))
1132 elif isinstance(elem, tuple):
1134 spacer = "%*s" % (max_tlen - len(key), "")
1135 buf.write("%*s%s:%s %s\n" % (2 * indent_level, "", key, spacer, value))
1136 elif isinstance(elem, list):
1137 _FormatList(buf, elem, indent_level + 1)
1140 def ShowInstanceConfig(opts, args):
1141 """Compute instance run-time status.
1143 @param opts: the command line options selected by the user
1145 @param args: either an empty list, and then we query all
1146 instances, or should contain a list of instance names
1148 @return: the desired exit code
1151 if not args and not opts.show_all:
1152 ToStderr("No instance selected."
1153 " Please pass in --all if you want to query all instances.\n"
1154 "Note that this can take a long time on a big cluster.")
1156 elif args and opts.show_all:
1157 ToStderr("Cannot use --all if you specify instance names.")
1161 op = opcodes.OpInstanceQueryData(instances=args, static=opts.static,
1162 use_locking=not opts.static)
1163 result = SubmitOpCode(op, opts=opts)
1165 ToStdout("No instances.")
1170 for instance_name in result:
1171 instance = result[instance_name]
1172 buf.write("Instance name: %s\n" % instance["name"])
1173 buf.write("UUID: %s\n" % instance["uuid"])
1174 buf.write("Serial number: %s\n" %
1175 compat.TryToRoman(instance["serial_no"],
1176 convert=opts.roman_integers))
1177 buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1178 buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1179 buf.write("State: configured to be %s" % instance["config_state"])
1180 if instance["run_state"]:
1181 buf.write(", actual state is %s" % instance["run_state"])
1183 ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1184 ## instance["auto_balance"])
1185 buf.write(" Nodes:\n")
1186 buf.write(" - primary: %s\n" % instance["pnode"])
1187 buf.write(" - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1188 buf.write(" Operating system: %s\n" % instance["os"])
1189 FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1191 if "network_port" in instance:
1192 buf.write(" Allocated network port: %s\n" %
1193 compat.TryToRoman(instance["network_port"],
1194 convert=opts.roman_integers))
1195 buf.write(" Hypervisor: %s\n" % instance["hypervisor"])
1197 # custom VNC console information
1198 vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1200 if vnc_bind_address:
1201 port = instance["network_port"]
1202 display = int(port) - constants.VNC_BASE_PORT
1203 if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1204 vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1207 elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1208 vnc_console_port = ("%s:%s (node %s) (display %s)" %
1209 (vnc_bind_address, port,
1210 instance["pnode"], display))
1212 # vnc bind address is a file
1213 vnc_console_port = "%s:%s" % (instance["pnode"],
1215 buf.write(" - console connection: vnc to %s\n" % vnc_console_port)
1217 FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1219 buf.write(" Hardware:\n")
1220 buf.write(" - VCPUs: %s\n" %
1221 compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1222 convert=opts.roman_integers))
1223 buf.write(" - maxmem: %sMiB\n" %
1224 compat.TryToRoman(instance["be_actual"][constants.BE_MAXMEM],
1225 convert=opts.roman_integers))
1226 buf.write(" - minmem: %sMiB\n" %
1227 compat.TryToRoman(instance["be_actual"][constants.BE_MINMEM],
1228 convert=opts.roman_integers))
1229 # deprecated "memory" value, kept for one version for compatibility
1230 # TODO(ganeti 2.7) remove.
1231 buf.write(" - memory: %sMiB\n" %
1232 compat.TryToRoman(instance["be_actual"][constants.BE_MAXMEM],
1233 convert=opts.roman_integers))
1234 buf.write(" - %s: %s\n" %
1235 (constants.BE_ALWAYS_FAILOVER,
1236 instance["be_actual"][constants.BE_ALWAYS_FAILOVER]))
1237 buf.write(" - NICs:\n")
1238 for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1239 buf.write(" - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1240 (idx, mac, ip, mode, link))
1241 buf.write(" Disk template: %s\n" % instance["disk_template"])
1242 buf.write(" Disks:\n")
1244 for idx, device in enumerate(instance["disks"]):
1245 _FormatList(buf, _FormatBlockDevInfo(idx, True, device,
1246 opts.roman_integers), 2)
1248 ToStdout(buf.getvalue().rstrip("\n"))
1252 def SetInstanceParams(opts, args):
1253 """Modifies an instance.
1255 All parameters take effect only at the next restart of the instance.
1257 @param opts: the command line options selected by the user
1259 @param args: should contain only one element, the instance name
1261 @return: the desired exit code
1264 if not (opts.nics or opts.disks or opts.disk_template or
1265 opts.hvparams or opts.beparams or opts.os or opts.osparams or
1266 opts.offline_inst or opts.online_inst):
1267 ToStderr("Please give at least one of the parameters.")
1270 for param in opts.beparams:
1271 if isinstance(opts.beparams[param], basestring):
1272 if opts.beparams[param].lower() == "default":
1273 opts.beparams[param] = constants.VALUE_DEFAULT
1275 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT,
1276 allowed_values=[constants.VALUE_DEFAULT])
1278 for param in opts.hvparams:
1279 if isinstance(opts.hvparams[param], basestring):
1280 if opts.hvparams[param].lower() == "default":
1281 opts.hvparams[param] = constants.VALUE_DEFAULT
1283 utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1284 allowed_values=[constants.VALUE_DEFAULT])
1286 for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1288 nic_op = int(nic_op)
1289 opts.nics[idx] = (nic_op, nic_dict)
1290 except (TypeError, ValueError):
1293 for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1295 disk_op = int(disk_op)
1296 opts.disks[idx] = (disk_op, disk_dict)
1297 except (TypeError, ValueError):
1299 if disk_op == constants.DDM_ADD:
1300 if "size" not in disk_dict:
1301 raise errors.OpPrereqError("Missing required parameter 'size'",
1303 disk_dict["size"] = utils.ParseUnit(disk_dict["size"])
1305 if (opts.disk_template and
1306 opts.disk_template in constants.DTS_INT_MIRROR and
1308 ToStderr("Changing the disk template to a mirrored one requires"
1309 " specifying a secondary node")
1312 op = opcodes.OpInstanceSetParams(instance_name=args[0],
1315 disk_template=opts.disk_template,
1316 remote_node=opts.node,
1317 hvparams=opts.hvparams,
1318 beparams=opts.beparams,
1320 osparams=opts.osparams,
1321 force_variant=opts.force_variant,
1323 wait_for_sync=opts.wait_for_sync,
1324 offline_inst=opts.offline_inst,
1325 online_inst=opts.online_inst,
1326 ignore_ipolicy=opts.ignore_ipolicy)
1328 # even if here we process the result, we allow submit only
1329 result = SubmitOrSend(op, opts)
1332 ToStdout("Modified instance %s", args[0])
1333 for param, data in result:
1334 ToStdout(" - %-5s -> %s", param, data)
1335 ToStdout("Please don't forget that most parameters take effect"
1336 " only at the next start of the instance.")
1340 def ChangeGroup(opts, args):
1341 """Moves an instance to another group.
1344 (instance_name, ) = args
1348 op = opcodes.OpInstanceChangeGroup(instance_name=instance_name,
1349 iallocator=opts.iallocator,
1350 target_groups=opts.to,
1351 early_release=opts.early_release)
1352 result = SubmitOpCode(op, cl=cl, opts=opts)
1354 # Keep track of submitted jobs
1355 jex = JobExecutor(cl=cl, opts=opts)
1357 for (status, job_id) in result[constants.JOB_IDS_KEY]:
1358 jex.AddJobId(None, status, job_id)
1360 results = jex.GetResults()
1361 bad_cnt = len([row for row in results if not row[0]])
1363 ToStdout("Instance '%s' changed group successfully.", instance_name)
1364 rcode = constants.EXIT_SUCCESS
1366 ToStdout("There were %s errors while changing group of instance '%s'.",
1367 bad_cnt, instance_name)
1368 rcode = constants.EXIT_FAILURE
1373 # multi-instance selection options
1374 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1375 help="Do not ask for confirmation when more than"
1376 " one instance is affected",
1377 action="store_true", default=False)
1379 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1380 help="Filter by nodes (primary only)",
1381 const=_EXPAND_NODES_PRI, action="store_const")
1383 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1384 help="Filter by nodes (secondary only)",
1385 const=_EXPAND_NODES_SEC, action="store_const")
1387 m_node_opt = cli_option("--node", dest="multi_mode",
1388 help="Filter by nodes (primary and secondary)",
1389 const=_EXPAND_NODES_BOTH, action="store_const")
1391 m_clust_opt = cli_option("--all", dest="multi_mode",
1392 help="Select all instances in the cluster",
1393 const=_EXPAND_CLUSTER, action="store_const")
1395 m_inst_opt = cli_option("--instance", dest="multi_mode",
1396 help="Filter by instance name [default]",
1397 const=_EXPAND_INSTANCES, action="store_const")
1399 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1400 help="Filter by node tag",
1401 const=_EXPAND_NODES_BOTH_BY_TAGS,
1402 action="store_const")
1404 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1405 help="Filter by primary node tag",
1406 const=_EXPAND_NODES_PRI_BY_TAGS,
1407 action="store_const")
1409 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1410 help="Filter by secondary node tag",
1411 const=_EXPAND_NODES_SEC_BY_TAGS,
1412 action="store_const")
1414 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1415 help="Filter by instance tag",
1416 const=_EXPAND_INSTANCES_BY_TAGS,
1417 action="store_const")
1419 # this is defined separately due to readability only
1430 AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1431 "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1432 "Creates and adds a new instance to the cluster"),
1434 BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1436 "Create a bunch of instances based on specs in the file."),
1438 ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1439 [SHOWCMD_OPT, PRIORITY_OPT],
1440 "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1442 FailoverInstance, ARGS_ONE_INSTANCE,
1443 [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1444 DRY_RUN_OPT, PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT,
1445 IGNORE_IPOLICY_OPT],
1446 "[-f] <instance>", "Stops the instance, changes its primary node and"
1447 " (if it was originally running) starts it on the new node"
1448 " (the secondary for mirrored instances or any node"
1449 " for shared storage)."),
1451 MigrateInstance, ARGS_ONE_INSTANCE,
1452 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1453 PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT, ALLOW_FAILOVER_OPT,
1454 IGNORE_IPOLICY_OPT],
1455 "[-f] <instance>", "Migrate instance to its secondary node"
1456 " (only for mirrored instances)"),
1458 MoveInstance, ARGS_ONE_INSTANCE,
1459 [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1460 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_CONSIST_OPT, IGNORE_IPOLICY_OPT],
1461 "[-f] <instance>", "Move instance to an arbitrary node"
1462 " (only for instances of type file and lv)"),
1464 ShowInstanceConfig, ARGS_MANY_INSTANCES,
1465 [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1466 "[-s] {--all | <instance>...}",
1467 "Show information on the specified instance(s)"),
1469 ListInstances, ARGS_MANY_INSTANCES,
1470 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
1473 "Lists the instances and their status. The available fields can be shown"
1474 " using the \"list-fields\" command (see the man page for details)."
1475 " The default field list is (in order): %s." %
1476 utils.CommaJoin(_LIST_DEF_FIELDS),
1479 ListInstanceFields, [ArgUnknown()],
1480 [NOHDR_OPT, SEP_OPT],
1482 "Lists all available fields for instances"),
1484 ReinstallInstance, [ArgInstance()],
1485 [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1486 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1487 m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1488 SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1489 "[-f] <instance>", "Reinstall a stopped instance"),
1491 RemoveInstance, ARGS_ONE_INSTANCE,
1492 [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1493 DRY_RUN_OPT, PRIORITY_OPT],
1494 "[-f] <instance>", "Shuts down the instance and removes it"),
1497 [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1498 [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1499 "<instance> <new_name>", "Rename the instance"),
1501 ReplaceDisks, ARGS_ONE_INSTANCE,
1502 [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1503 NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1504 DRY_RUN_OPT, PRIORITY_OPT],
1505 "[-s|-p|-n NODE|-I NAME] <instance>",
1506 "Replaces all disks for the instance"),
1508 SetInstanceParams, ARGS_ONE_INSTANCE,
1509 [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1510 DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1511 OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT, NWSYNC_OPT, OFFLINE_INST_OPT,
1512 ONLINE_INST_OPT, IGNORE_IPOLICY_OPT],
1513 "<instance>", "Alters the parameters of an instance"),
1515 GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1516 [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1517 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1518 m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1519 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT, NO_REMEMBER_OPT],
1520 "<instance>", "Stops an instance"),
1522 GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1523 [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1524 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1525 m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1526 BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT,
1527 NO_REMEMBER_OPT, STARTUP_PAUSED_OPT],
1528 "<instance>", "Starts an instance"),
1530 GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1531 [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1532 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1533 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1534 m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1535 "<instance>", "Reboots an instance"),
1537 ActivateDisks, ARGS_ONE_INSTANCE,
1538 [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
1539 "<instance>", "Activate an instance's disks"),
1540 "deactivate-disks": (
1541 DeactivateDisks, ARGS_ONE_INSTANCE,
1542 [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1543 "[-f] <instance>", "Deactivate an instance's disks"),
1545 RecreateDisks, ARGS_ONE_INSTANCE,
1546 [SUBMIT_OPT, DISKIDX_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1547 "<instance>", "Recreate an instance's disks"),
1550 [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1551 ArgUnknown(min=1, max=1)],
1552 [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1553 "<instance> <disk> <size>", "Grow an instance's disk"),
1555 ChangeGroup, ARGS_ONE_INSTANCE,
1556 [TO_GROUP_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT],
1557 "[-I <iallocator>] [--to <group>]", "Change group of instance"),
1559 ListTags, ARGS_ONE_INSTANCE, [PRIORITY_OPT],
1560 "<instance_name>", "List the tags of the given instance"),
1562 AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1563 [TAG_SRC_OPT, PRIORITY_OPT],
1564 "<instance_name> tag...", "Add tags to the given instance"),
1566 RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1567 [TAG_SRC_OPT, PRIORITY_OPT],
1568 "<instance_name> tag...", "Remove tags from given instance"),
1571 #: dictionary with aliases for commands
1579 return GenericMain(commands, aliases=aliases,
1580 override={"tag_type": constants.TAG_INSTANCE},
1581 env_override=_ENV_OVERRIDE)