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
45 _EXPAND_CLUSTER = "cluster"
46 _EXPAND_NODES_BOTH = "nodes"
47 _EXPAND_NODES_PRI = "nodes-pri"
48 _EXPAND_NODES_SEC = "nodes-sec"
49 _EXPAND_NODES_BOTH_BY_TAGS = "nodes-by-tags"
50 _EXPAND_NODES_PRI_BY_TAGS = "nodes-pri-by-tags"
51 _EXPAND_NODES_SEC_BY_TAGS = "nodes-sec-by-tags"
52 _EXPAND_INSTANCES = "instances"
53 _EXPAND_INSTANCES_BY_TAGS = "instances-by-tags"
55 _EXPAND_NODES_TAGS_MODES = frozenset([
56 _EXPAND_NODES_BOTH_BY_TAGS,
57 _EXPAND_NODES_PRI_BY_TAGS,
58 _EXPAND_NODES_SEC_BY_TAGS,
62 #: default list of options for L{ListInstances}
64 "name", "hypervisor", "os", "pnode", "status", "oper_ram",
68 _ENV_OVERRIDE = frozenset(["list"])
71 def _ExpandMultiNames(mode, names, client=None):
72 """Expand the given names using the passed mode.
74 For _EXPAND_CLUSTER, all instances will be returned. For
75 _EXPAND_NODES_PRI/SEC, all instances having those nodes as
76 primary/secondary will be returned. For _EXPAND_NODES_BOTH, all
77 instances having those nodes as either primary or secondary will be
78 returned. For _EXPAND_INSTANCES, the given instances will be
81 @param mode: one of L{_EXPAND_CLUSTER}, L{_EXPAND_NODES_BOTH},
82 L{_EXPAND_NODES_PRI}, L{_EXPAND_NODES_SEC} or
84 @param names: a list of names; for cluster, it must be empty,
85 and for node and instance it must be a list of valid item
86 names (short names are valid as usual, e.g. node1 instead of
89 @return: the list of names after the expansion
90 @raise errors.ProgrammerError: for unknown selection type
91 @raise errors.OpPrereqError: for invalid input parameters
94 # pylint: disable=W0142
98 if mode == _EXPAND_CLUSTER:
100 raise errors.OpPrereqError("Cluster filter mode takes no arguments",
102 idata = client.QueryInstances([], ["name"], False)
103 inames = [row[0] for row in idata]
105 elif (mode in _EXPAND_NODES_TAGS_MODES or
106 mode in (_EXPAND_NODES_BOTH, _EXPAND_NODES_PRI, _EXPAND_NODES_SEC)):
107 if mode in _EXPAND_NODES_TAGS_MODES:
109 raise errors.OpPrereqError("No node tags passed", errors.ECODE_INVAL)
110 ndata = client.QueryNodes([], ["name", "pinst_list",
111 "sinst_list", "tags"], False)
112 ndata = [row for row in ndata if set(row[3]).intersection(names)]
115 raise errors.OpPrereqError("No node names passed", errors.ECODE_INVAL)
116 ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
119 ipri = [row[1] for row in ndata]
120 pri_names = list(itertools.chain(*ipri))
121 isec = [row[2] for row in ndata]
122 sec_names = list(itertools.chain(*isec))
123 if mode in (_EXPAND_NODES_BOTH, _EXPAND_NODES_BOTH_BY_TAGS):
124 inames = pri_names + sec_names
125 elif mode in (_EXPAND_NODES_PRI, _EXPAND_NODES_PRI_BY_TAGS):
127 elif mode in (_EXPAND_NODES_SEC, _EXPAND_NODES_SEC_BY_TAGS):
130 raise errors.ProgrammerError("Unhandled shutdown type")
131 elif mode == _EXPAND_INSTANCES:
133 raise errors.OpPrereqError("No instance names passed",
135 idata = client.QueryInstances(names, ["name"], False)
136 inames = [row[0] for row in idata]
137 elif mode == _EXPAND_INSTANCES_BY_TAGS:
139 raise errors.OpPrereqError("No instance tags passed",
141 idata = client.QueryInstances([], ["name", "tags"], False)
142 inames = [row[0] for row in idata if set(row[1]).intersection(names)]
144 raise errors.OpPrereqError("Unknown mode '%s'" % mode, errors.ECODE_INVAL)
149 def _EnsureInstancesExist(client, names):
150 """Check for and ensure the given instance names exist.
152 This function will raise an OpPrereqError in case they don't
153 exist. Otherwise it will exit cleanly.
155 @type client: L{ganeti.luxi.Client}
156 @param client: the client to use for the query
158 @param names: the list of instance names to query
159 @raise errors.OpPrereqError: in case any instance is missing
162 # TODO: change LUInstanceQuery to that it actually returns None
163 # instead of raising an exception, or devise a better mechanism
164 result = client.QueryInstances(names, ["name"], False)
165 for orig_name, row in zip(names, result):
167 raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name,
171 def GenericManyOps(operation, fn):
172 """Generic multi-instance operations.
174 The will return a wrapper that processes the options and arguments
175 given, and uses the passed function to build the opcode needed for
176 the specific operation. Thus all the generic loop/confirmation code
177 is abstracted into this function.
180 def realfn(opts, args):
181 if opts.multi_mode is None:
182 opts.multi_mode = _EXPAND_INSTANCES
184 inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
186 if opts.multi_mode == _EXPAND_CLUSTER:
187 ToStdout("Cluster is empty, no instances to shutdown")
189 raise errors.OpPrereqError("Selection filter does not match"
190 " any instances", errors.ECODE_INVAL)
191 multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1
192 if not (opts.force_multi or not multi_on
193 or ConfirmOperation(inames, "instances", operation)):
195 jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
198 jex.QueueJob(name, op)
199 results = jex.WaitOrShow(not opts.submit_only)
200 rcode = compat.all(row[0] for row in results)
201 return int(not rcode)
205 def ListInstances(opts, args):
206 """List instances and their properties.
208 @param opts: the command line options selected by the user
210 @param args: should be an empty list
212 @return: the desired exit code
215 selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
217 fmtoverride = dict.fromkeys(["tags", "disk.sizes", "nic.macs", "nic.ips",
218 "nic.modes", "nic.links", "nic.bridges",
219 "snodes", "snodes.group", "snodes.group.uuid"],
220 (lambda value: ",".join(str(item)
224 return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units,
225 opts.separator, not opts.no_headers,
226 format_override=fmtoverride, verbose=opts.verbose,
227 force_filter=opts.force_filter)
230 def ListInstanceFields(opts, args):
231 """List instance fields.
233 @param opts: the command line options selected by the user
235 @param args: fields to list, or empty for all
237 @return: the desired exit code
240 return GenericListFields(constants.QR_INSTANCE, args, opts.separator,
244 def AddInstance(opts, args):
245 """Add an instance to the cluster.
247 This is just a wrapper over GenericInstanceCreate.
250 return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
253 def BatchCreate(opts, args):
254 """Create instances using a definition file.
256 This function reads a json file with instances defined
260 "disk_size": [20480],
266 "primary_node": "firstnode",
267 "secondary_node": "secondnode",
268 "iallocator": "dumb"}
271 Note that I{primary_node} and I{secondary_node} have precedence over
274 @param opts: the command line options selected by the user
276 @param args: should contain one element, the json filename
278 @return: the desired exit code
281 _DEFAULT_SPECS = {"disk_size": [20 * 1024],
284 "primary_node": None,
285 "secondary_node": None,
292 "file_storage_dir": None,
293 "force_variant": False,
294 "file_driver": "loop"}
296 def _PopulateWithDefaults(spec):
297 """Returns a new hash combined with default values."""
298 mydict = _DEFAULT_SPECS.copy()
303 """Validate the instance specs."""
304 # Validate fields required under any circumstances
305 for required_field in ("os", "template"):
306 if required_field not in spec:
307 raise errors.OpPrereqError('Required field "%s" is missing.' %
308 required_field, errors.ECODE_INVAL)
309 # Validate special fields
310 if spec["primary_node"] is not None:
311 if (spec["template"] in constants.DTS_INT_MIRROR and
312 spec["secondary_node"] is None):
313 raise errors.OpPrereqError("Template requires secondary node, but"
314 " there was no secondary provided.",
316 elif spec["iallocator"] is None:
317 raise errors.OpPrereqError("You have to provide at least a primary_node"
318 " or an iallocator.",
321 if (spec["hvparams"] and
322 not isinstance(spec["hvparams"], dict)):
323 raise errors.OpPrereqError("Hypervisor parameters must be a dict.",
326 json_filename = args[0]
328 instance_data = simplejson.loads(utils.ReadFile(json_filename))
329 except Exception, err: # pylint: disable=W0703
330 ToStderr("Can't parse the instance definition file: %s" % str(err))
333 if not isinstance(instance_data, dict):
334 ToStderr("The instance definition file is not in dict format.")
337 jex = JobExecutor(opts=opts)
339 # Iterate over the instances and do:
340 # * Populate the specs with default value
341 # * Validate the instance specs
342 i_names = utils.NiceSort(instance_data.keys()) # pylint: disable=E1103
344 specs = instance_data[name]
345 specs = _PopulateWithDefaults(specs)
348 hypervisor = specs["hypervisor"]
349 hvparams = specs["hvparams"]
352 for elem in specs["disk_size"]:
354 size = utils.ParseUnit(elem)
355 except (TypeError, ValueError), err:
356 raise errors.OpPrereqError("Invalid disk size '%s' for"
358 (elem, name, err), errors.ECODE_INVAL)
359 disks.append({"size": size})
361 utils.ForceDictType(specs["backend"], constants.BES_PARAMETER_COMPAT)
362 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
365 for field in constants.INIC_PARAMS:
369 tmp_nics[0][field] = specs[field]
371 if specs["nics"] is not None and tmp_nics:
372 raise errors.OpPrereqError("'nics' list incompatible with using"
373 " individual nic fields as well",
375 elif specs["nics"] is not None:
376 tmp_nics = specs["nics"]
380 op = opcodes.OpInstanceCreate(instance_name=name,
382 disk_template=specs["template"],
383 mode=constants.INSTANCE_CREATE,
385 force_variant=specs["force_variant"],
386 pnode=specs["primary_node"],
387 snode=specs["secondary_node"],
389 start=specs["start"],
390 ip_check=specs["ip_check"],
391 name_check=specs["name_check"],
393 iallocator=specs["iallocator"],
394 hypervisor=hypervisor,
396 beparams=specs["backend"],
397 file_storage_dir=specs["file_storage_dir"],
398 file_driver=specs["file_driver"])
400 jex.QueueJob(name, op)
401 # we never want to wait, just show the submitted job IDs
402 jex.WaitOrShow(False)
407 def ReinstallInstance(opts, args):
408 """Reinstall an instance.
410 @param opts: the command line options selected by the user
412 @param args: should contain only one element, the name of the
413 instance to be reinstalled
415 @return: the desired exit code
418 # first, compute the desired name list
419 if opts.multi_mode is None:
420 opts.multi_mode = _EXPAND_INSTANCES
422 inames = _ExpandMultiNames(opts.multi_mode, args)
424 raise errors.OpPrereqError("Selection filter does not match any instances",
427 # second, if requested, ask for an OS
428 if opts.select_os is True:
429 op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[])
430 result = SubmitOpCode(op, opts=opts)
433 ToStdout("Can't get the OS list")
436 ToStdout("Available OS templates:")
439 for (name, variants) in result:
440 for entry in CalculateOSNames(name, variants):
441 ToStdout("%3s: %s", number, entry)
442 choices.append(("%s" % number, entry, entry))
445 choices.append(("x", "exit", "Exit gnt-instance reinstall"))
446 selected = AskUser("Enter OS template number (or x to abort):",
449 if selected == "exit":
450 ToStderr("User aborted reinstall, exiting")
454 os_msg = "change the OS to '%s'" % selected
457 if opts.os is not None:
458 os_msg = "change the OS to '%s'" % os_name
460 os_msg = "keep the same OS"
462 # third, get confirmation: multi-reinstall requires --force-multi,
463 # single-reinstall either --force or --force-multi (--force-multi is
464 # a stronger --force)
465 multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1
467 warn_msg = ("Note: this will remove *all* data for the"
468 " below instances! It will %s.\n" % os_msg)
469 if not (opts.force_multi or
470 ConfirmOperation(inames, "instances", "reinstall", extra=warn_msg)):
473 if not (opts.force or opts.force_multi):
474 usertext = ("This will reinstall the instance '%s' (and %s) which"
475 " removes all data. Continue?") % (inames[0], os_msg)
476 if not AskUser(usertext):
479 jex = JobExecutor(verbose=multi_on, opts=opts)
480 for instance_name in inames:
481 op = opcodes.OpInstanceReinstall(instance_name=instance_name,
483 force_variant=opts.force_variant,
484 osparams=opts.osparams)
485 jex.QueueJob(instance_name, op)
487 jex.WaitOrShow(not opts.submit_only)
491 def RemoveInstance(opts, args):
492 """Remove an instance.
494 @param opts: the command line options selected by the user
496 @param args: should contain only one element, the name of
497 the instance to be removed
499 @return: the desired exit code
502 instance_name = args[0]
507 _EnsureInstancesExist(cl, [instance_name])
509 usertext = ("This will remove the volumes of the instance %s"
510 " (including mirrors), thus removing all the data"
511 " of the instance. Continue?") % instance_name
512 if not AskUser(usertext):
515 op = opcodes.OpInstanceRemove(instance_name=instance_name,
516 ignore_failures=opts.ignore_failures,
517 shutdown_timeout=opts.shutdown_timeout)
518 SubmitOrSend(op, opts, cl=cl)
522 def RenameInstance(opts, args):
523 """Rename an instance.
525 @param opts: the command line options selected by the user
527 @param args: should contain two elements, the old and the
530 @return: the desired exit code
533 if not opts.name_check:
534 if not AskUser("As you disabled the check of the DNS entry, please verify"
535 " that '%s' is a FQDN. Continue?" % args[1]):
538 op = opcodes.OpInstanceRename(instance_name=args[0],
540 ip_check=opts.ip_check,
541 name_check=opts.name_check)
542 result = SubmitOrSend(op, opts)
545 ToStdout("Instance '%s' renamed to '%s'", args[0], result)
550 def ActivateDisks(opts, args):
551 """Activate an instance's disks.
553 This serves two purposes:
554 - it allows (as long as the instance is not running)
555 mounting the disks and modifying them from the node
556 - it repairs inactive secondary drbds
558 @param opts: the command line options selected by the user
560 @param args: should contain only one element, the instance name
562 @return: the desired exit code
565 instance_name = args[0]
566 op = opcodes.OpInstanceActivateDisks(instance_name=instance_name,
567 ignore_size=opts.ignore_size)
568 disks_info = SubmitOrSend(op, opts)
569 for host, iname, nname in disks_info:
570 ToStdout("%s:%s:%s", host, iname, nname)
574 def DeactivateDisks(opts, args):
575 """Deactivate an instance's disks.
577 This function takes the instance name, looks for its primary node
578 and the tries to shutdown its block devices on that node.
580 @param opts: the command line options selected by the user
582 @param args: should contain only one element, the instance name
584 @return: the desired exit code
587 instance_name = args[0]
588 op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name,
590 SubmitOrSend(op, opts)
594 def RecreateDisks(opts, args):
595 """Recreate an instance's disks.
597 @param opts: the command line options selected by the user
599 @param args: should contain only one element, the instance name
601 @return: the desired exit code
604 instance_name = args[0]
609 for didx, ddict in opts.disks:
612 if not ht.TDict(ddict):
613 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
614 raise errors.OpPrereqError(msg)
616 if constants.IDISK_SIZE in ddict:
618 ddict[constants.IDISK_SIZE] = \
619 utils.ParseUnit(ddict[constants.IDISK_SIZE])
620 except ValueError, err:
621 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
624 disks.append((didx, ddict))
626 # TODO: Verify modifyable parameters (already done in
627 # LUInstanceRecreateDisks, but it'd be nice to have in the client)
630 pnode, snode = SplitNodeOption(opts.node)
632 if snode is not None:
637 op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
638 disks=disks, nodes=nodes)
639 SubmitOrSend(op, opts)
644 def GrowDisk(opts, args):
645 """Grow an instance's disks.
647 @param opts: the command line options selected by the user
649 @param args: should contain three elements, the target instance name,
650 the target disk id, and the target growth
652 @return: the desired exit code
659 except (TypeError, ValueError), err:
660 raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
662 amount = utils.ParseUnit(args[2])
663 op = opcodes.OpInstanceGrowDisk(instance_name=instance,
664 disk=disk, amount=amount,
665 wait_for_sync=opts.wait_for_sync)
666 SubmitOrSend(op, opts)
670 def _StartupInstance(name, opts):
671 """Startup instances.
673 This returns the opcode to start an instance, and its decorator will
674 wrap this into a loop starting all desired instances.
676 @param name: the name of the instance to act on
677 @param opts: the command line options selected by the user
678 @return: the opcode needed for the operation
681 op = opcodes.OpInstanceStartup(instance_name=name,
683 ignore_offline_nodes=opts.ignore_offline,
684 no_remember=opts.no_remember,
685 startup_paused=opts.startup_paused)
686 # do not add these parameters to the opcode unless they're defined
688 op.hvparams = opts.hvparams
690 op.beparams = opts.beparams
694 def _RebootInstance(name, opts):
695 """Reboot instance(s).
697 This returns the opcode to reboot an instance, and its decorator
698 will wrap this into a loop rebooting all desired instances.
700 @param name: the name of the instance to act on
701 @param opts: the command line options selected by the user
702 @return: the opcode needed for the operation
705 return opcodes.OpInstanceReboot(instance_name=name,
706 reboot_type=opts.reboot_type,
707 ignore_secondaries=opts.ignore_secondaries,
708 shutdown_timeout=opts.shutdown_timeout)
711 def _ShutdownInstance(name, opts):
712 """Shutdown an instance.
714 This returns the opcode to shutdown an instance, and its decorator
715 will wrap this into a loop shutting down all desired instances.
717 @param name: the name of the instance to act on
718 @param opts: the command line options selected by the user
719 @return: the opcode needed for the operation
722 return opcodes.OpInstanceShutdown(instance_name=name,
723 timeout=opts.timeout,
724 ignore_offline_nodes=opts.ignore_offline,
725 no_remember=opts.no_remember)
728 def ReplaceDisks(opts, args):
729 """Replace the disks of an instance
731 @param opts: the command line options selected by the user
733 @param args: should contain only one element, the instance name
735 @return: the desired exit code
738 new_2ndary = opts.dst_node
739 iallocator = opts.iallocator
740 if opts.disks is None:
744 disks = [int(i) for i in opts.disks.split(",")]
745 except (TypeError, ValueError), err:
746 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
748 cnt = [opts.on_primary, opts.on_secondary, opts.auto,
749 new_2ndary is not None, iallocator is not None].count(True)
751 raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -I"
752 " options must be passed", errors.ECODE_INVAL)
753 elif opts.on_primary:
754 mode = constants.REPLACE_DISK_PRI
755 elif opts.on_secondary:
756 mode = constants.REPLACE_DISK_SEC
758 mode = constants.REPLACE_DISK_AUTO
760 raise errors.OpPrereqError("Cannot specify disks when using automatic"
761 " mode", errors.ECODE_INVAL)
762 elif new_2ndary is not None or iallocator is not None:
764 mode = constants.REPLACE_DISK_CHG
766 op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
767 remote_node=new_2ndary, mode=mode,
768 iallocator=iallocator,
769 early_release=opts.early_release,
770 ignore_ipolicy=opts.ignore_ipolicy)
771 SubmitOrSend(op, opts)
775 def FailoverInstance(opts, args):
776 """Failover an instance.
778 The failover is done by shutting it down on its present node and
779 starting it on the secondary.
781 @param opts: the command line options selected by the user
783 @param args: should contain only one element, the instance name
785 @return: the desired exit code
789 instance_name = args[0]
791 iallocator = opts.iallocator
792 target_node = opts.dst_node
794 if iallocator and target_node:
795 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
796 " node (-n) but not both", errors.ECODE_INVAL)
799 _EnsureInstancesExist(cl, [instance_name])
801 usertext = ("Failover will happen to image %s."
802 " This requires a shutdown of the instance. Continue?" %
804 if not AskUser(usertext):
807 op = opcodes.OpInstanceFailover(instance_name=instance_name,
808 ignore_consistency=opts.ignore_consistency,
809 shutdown_timeout=opts.shutdown_timeout,
810 iallocator=iallocator,
811 target_node=target_node,
812 ignore_ipolicy=opts.ignore_ipolicy)
813 SubmitOrSend(op, opts, cl=cl)
817 def MigrateInstance(opts, args):
818 """Migrate an instance.
820 The migrate is done without shutdown.
822 @param opts: the command line options selected by the user
824 @param args: should contain only one element, the instance name
826 @return: the desired exit code
830 instance_name = args[0]
832 iallocator = opts.iallocator
833 target_node = opts.dst_node
835 if iallocator and target_node:
836 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
837 " node (-n) but not both", errors.ECODE_INVAL)
840 _EnsureInstancesExist(cl, [instance_name])
843 usertext = ("Instance %s will be recovered from a failed migration."
844 " Note that the migration procedure (including cleanup)" %
847 usertext = ("Instance %s will be migrated. Note that migration" %
849 usertext += (" might impact the instance if anything goes wrong"
850 " (e.g. due to bugs in the hypervisor). Continue?")
851 if not AskUser(usertext):
854 # this should be removed once --non-live is deprecated
855 if not opts.live and opts.migration_mode is not None:
856 raise errors.OpPrereqError("Only one of the --non-live and "
857 "--migration-mode options can be passed",
859 if not opts.live: # --non-live passed
860 mode = constants.HT_MIGRATION_NONLIVE
862 mode = opts.migration_mode
864 op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
865 cleanup=opts.cleanup, iallocator=iallocator,
866 target_node=target_node,
867 allow_failover=opts.allow_failover,
868 ignore_ipolicy=opts.ignore_ipolicy)
869 SubmitOpCode(op, cl=cl, opts=opts)
873 def MoveInstance(opts, args):
876 @param opts: the command line options selected by the user
878 @param args: should contain only one element, the instance name
880 @return: the desired exit code
884 instance_name = args[0]
888 usertext = ("Instance %s will be moved."
889 " This requires a shutdown of the instance. Continue?" %
891 if not AskUser(usertext):
894 op = opcodes.OpInstanceMove(instance_name=instance_name,
895 target_node=opts.node,
896 shutdown_timeout=opts.shutdown_timeout,
897 ignore_consistency=opts.ignore_consistency,
898 ignore_ipolicy=opts.ignore_ipolicy)
899 SubmitOrSend(op, opts, cl=cl)
903 def ConnectToInstanceConsole(opts, args):
904 """Connect to the console of an instance.
906 @param opts: the command line options selected by the user
908 @param args: should contain only one element, the instance name
910 @return: the desired exit code
913 instance_name = args[0]
917 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
918 ((console_data, oper_state), ) = \
919 cl.QueryInstances([instance_name], ["console", "oper_state"], False)
921 # Ensure client connection is closed while external commands are run
928 # Instance is running
929 raise errors.OpExecError("Console information for instance %s is"
930 " unavailable" % instance_name)
932 raise errors.OpExecError("Instance %s is not running, can't get console" %
935 return _DoConsole(objects.InstanceConsole.FromDict(console_data),
936 opts.show_command, cluster_name)
939 def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
940 _runcmd_fn=utils.RunCmd):
941 """Acts based on the result of L{opcodes.OpInstanceConsole}.
943 @type console: L{objects.InstanceConsole}
944 @param console: Console object
945 @type show_command: bool
946 @param show_command: Whether to just display commands
947 @type cluster_name: string
948 @param cluster_name: Cluster name as retrieved from master daemon
951 assert console.Validate()
953 if console.kind == constants.CONS_MESSAGE:
954 feedback_fn(console.message)
955 elif console.kind == constants.CONS_VNC:
956 feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
957 " URL <vnc://%s:%s/>",
958 console.instance, console.host, console.port,
959 console.display, console.host, console.port)
960 elif console.kind == constants.CONS_SPICE:
961 feedback_fn("Instance %s has SPICE listening on %s:%s", console.instance,
962 console.host, console.port)
963 elif console.kind == constants.CONS_SSH:
964 # Convert to string if not already one
965 if isinstance(console.command, basestring):
966 cmd = console.command
968 cmd = utils.ShellQuoteArgs(console.command)
970 srun = ssh.SshRunner(cluster_name=cluster_name)
971 ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
972 batch=True, quiet=False, tty=True)
975 feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
977 result = _runcmd_fn(ssh_cmd, interactive=True)
979 logging.error("Console command \"%s\" failed with reason '%s' and"
980 " output %r", result.cmd, result.fail_reason,
982 raise errors.OpExecError("Connection to console of instance %s failed,"
983 " please check cluster configuration" %
986 raise errors.GenericError("Unknown console type '%s'" % console.kind)
988 return constants.EXIT_SUCCESS
991 def _FormatLogicalID(dev_type, logical_id, roman):
992 """Formats the logical_id of a disk.
995 if dev_type == constants.LD_DRBD8:
996 node_a, node_b, port, minor_a, minor_b, key = logical_id
998 ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
1000 ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
1002 ("port", compat.TryToRoman(port, convert=roman)),
1005 elif dev_type == constants.LD_LV:
1006 vg_name, lv_name = logical_id
1007 data = ["%s/%s" % (vg_name, lv_name)]
1009 data = [str(logical_id)]
1014 def _FormatBlockDevInfo(idx, top_level, dev, roman):
1015 """Show block device information.
1017 This is only used by L{ShowInstanceConfig}, but it's too big to be
1018 left for an inline definition.
1021 @param idx: the index of the current disk
1022 @type top_level: boolean
1023 @param top_level: if this a top-level disk?
1025 @param dev: dictionary with disk information
1026 @type roman: boolean
1027 @param roman: whether to try to use roman integers
1028 @return: a list of either strings, tuples or lists
1029 (which should be formatted at a higher indent level)
1032 def helper(dtype, status):
1033 """Format one line for physical device status.
1036 @param dtype: a constant from the L{constants.LDS_BLOCK} set
1038 @param status: a tuple as returned from L{backend.FindBlockDevice}
1039 @return: the string representing the status
1045 (path, major, minor, syncp, estt, degr, ldisk_status) = status
1047 major_string = "N/A"
1049 major_string = str(compat.TryToRoman(major, convert=roman))
1052 minor_string = "N/A"
1054 minor_string = str(compat.TryToRoman(minor, convert=roman))
1056 txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1057 if dtype in (constants.LD_DRBD8, ):
1058 if syncp is not None:
1059 sync_text = "*RECOVERING* %5.2f%%," % syncp
1061 sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1063 sync_text += " ETA unknown"
1065 sync_text = "in sync"
1067 degr_text = "*DEGRADED*"
1070 if ldisk_status == constants.LDS_FAULTY:
1071 ldisk_text = " *MISSING DISK*"
1072 elif ldisk_status == constants.LDS_UNKNOWN:
1073 ldisk_text = " *UNCERTAIN STATE*"
1076 txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1077 elif dtype == constants.LD_LV:
1078 if ldisk_status == constants.LDS_FAULTY:
1079 ldisk_text = " *FAILED* (failed drive?)"
1087 if dev["iv_name"] is not None:
1088 txt = dev["iv_name"]
1090 txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1092 txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1093 if isinstance(dev["size"], int):
1094 nice_size = utils.FormatUnit(dev["size"], "h")
1096 nice_size = dev["size"]
1097 d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1100 data.append(("access mode", dev["mode"]))
1101 if dev["logical_id"] is not None:
1103 l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1105 l_id = [str(dev["logical_id"])]
1107 data.append(("logical_id", l_id[0]))
1110 elif dev["physical_id"] is not None:
1111 data.append("physical_id:")
1112 data.append([dev["physical_id"]])
1115 data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1118 data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1121 data.append("child devices:")
1122 for c_idx, child in enumerate(dev["children"]):
1123 data.append(_FormatBlockDevInfo(c_idx, False, child, roman))
1128 def _FormatList(buf, data, indent_level):
1129 """Formats a list of data at a given indent level.
1131 If the element of the list is:
1132 - a string, it is simply formatted as is
1133 - a tuple, it will be split into key, value and the all the
1134 values in a list will be aligned all at the same start column
1135 - a list, will be recursively formatted
1138 @param buf: the buffer into which we write the output
1139 @param data: the list to format
1140 @type indent_level: int
1141 @param indent_level: the indent level to format at
1144 max_tlen = max([len(elem[0]) for elem in data
1145 if isinstance(elem, tuple)] or [0])
1147 if isinstance(elem, basestring):
1148 buf.write("%*s%s\n" % (2 * indent_level, "", elem))
1149 elif isinstance(elem, tuple):
1151 spacer = "%*s" % (max_tlen - len(key), "")
1152 buf.write("%*s%s:%s %s\n" % (2 * indent_level, "", key, spacer, value))
1153 elif isinstance(elem, list):
1154 _FormatList(buf, elem, indent_level + 1)
1157 def ShowInstanceConfig(opts, args):
1158 """Compute instance run-time status.
1160 @param opts: the command line options selected by the user
1162 @param args: either an empty list, and then we query all
1163 instances, or should contain a list of instance names
1165 @return: the desired exit code
1168 if not args and not opts.show_all:
1169 ToStderr("No instance selected."
1170 " Please pass in --all if you want to query all instances.\n"
1171 "Note that this can take a long time on a big cluster.")
1173 elif args and opts.show_all:
1174 ToStderr("Cannot use --all if you specify instance names.")
1178 op = opcodes.OpInstanceQueryData(instances=args, static=opts.static,
1179 use_locking=not opts.static)
1180 result = SubmitOpCode(op, opts=opts)
1182 ToStdout("No instances.")
1187 for instance_name in result:
1188 instance = result[instance_name]
1189 buf.write("Instance name: %s\n" % instance["name"])
1190 buf.write("UUID: %s\n" % instance["uuid"])
1191 buf.write("Serial number: %s\n" %
1192 compat.TryToRoman(instance["serial_no"],
1193 convert=opts.roman_integers))
1194 buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1195 buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1196 buf.write("State: configured to be %s" % instance["config_state"])
1197 if instance["run_state"]:
1198 buf.write(", actual state is %s" % instance["run_state"])
1200 ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1201 ## instance["auto_balance"])
1202 buf.write(" Nodes:\n")
1203 buf.write(" - primary: %s\n" % instance["pnode"])
1204 buf.write(" - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1205 buf.write(" Operating system: %s\n" % instance["os"])
1206 FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1208 if "network_port" in instance:
1209 buf.write(" Allocated network port: %s\n" %
1210 compat.TryToRoman(instance["network_port"],
1211 convert=opts.roman_integers))
1212 buf.write(" Hypervisor: %s\n" % instance["hypervisor"])
1214 # custom VNC console information
1215 vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1217 if vnc_bind_address:
1218 port = instance["network_port"]
1219 display = int(port) - constants.VNC_BASE_PORT
1220 if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1221 vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1224 elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1225 vnc_console_port = ("%s:%s (node %s) (display %s)" %
1226 (vnc_bind_address, port,
1227 instance["pnode"], display))
1229 # vnc bind address is a file
1230 vnc_console_port = "%s:%s" % (instance["pnode"],
1232 buf.write(" - console connection: vnc to %s\n" % vnc_console_port)
1234 FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1236 buf.write(" Hardware:\n")
1237 buf.write(" - VCPUs: %s\n" %
1238 compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1239 convert=opts.roman_integers))
1240 buf.write(" - maxmem: %sMiB\n" %
1241 compat.TryToRoman(instance["be_actual"][constants.BE_MAXMEM],
1242 convert=opts.roman_integers))
1243 buf.write(" - minmem: %sMiB\n" %
1244 compat.TryToRoman(instance["be_actual"][constants.BE_MINMEM],
1245 convert=opts.roman_integers))
1246 # deprecated "memory" value, kept for one version for compatibility
1247 # TODO(ganeti 2.7) remove.
1248 buf.write(" - memory: %sMiB\n" %
1249 compat.TryToRoman(instance["be_actual"][constants.BE_MAXMEM],
1250 convert=opts.roman_integers))
1251 buf.write(" - %s: %s\n" %
1252 (constants.BE_ALWAYS_FAILOVER,
1253 instance["be_actual"][constants.BE_ALWAYS_FAILOVER]))
1254 buf.write(" - NICs:\n")
1255 for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1256 buf.write(" - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1257 (idx, mac, ip, mode, link))
1258 buf.write(" Disk template: %s\n" % instance["disk_template"])
1259 buf.write(" Disks:\n")
1261 for idx, device in enumerate(instance["disks"]):
1262 _FormatList(buf, _FormatBlockDevInfo(idx, True, device,
1263 opts.roman_integers), 2)
1265 ToStdout(buf.getvalue().rstrip("\n"))
1269 def SetInstanceParams(opts, args):
1270 """Modifies an instance.
1272 All parameters take effect only at the next restart of the instance.
1274 @param opts: the command line options selected by the user
1276 @param args: should contain only one element, the instance name
1278 @return: the desired exit code
1281 if not (opts.nics or opts.disks or opts.disk_template or
1282 opts.hvparams or opts.beparams or opts.os or opts.osparams or
1283 opts.offline_inst or opts.online_inst or opts.runtime_mem):
1284 ToStderr("Please give at least one of the parameters.")
1287 for param in opts.beparams:
1288 if isinstance(opts.beparams[param], basestring):
1289 if opts.beparams[param].lower() == "default":
1290 opts.beparams[param] = constants.VALUE_DEFAULT
1292 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT,
1293 allowed_values=[constants.VALUE_DEFAULT])
1295 for param in opts.hvparams:
1296 if isinstance(opts.hvparams[param], basestring):
1297 if opts.hvparams[param].lower() == "default":
1298 opts.hvparams[param] = constants.VALUE_DEFAULT
1300 utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1301 allowed_values=[constants.VALUE_DEFAULT])
1303 for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1305 nic_op = int(nic_op)
1306 opts.nics[idx] = (nic_op, nic_dict)
1307 except (TypeError, ValueError):
1310 for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1312 disk_op = int(disk_op)
1313 opts.disks[idx] = (disk_op, disk_dict)
1314 except (TypeError, ValueError):
1316 if disk_op == constants.DDM_ADD:
1317 if "size" not in disk_dict:
1318 raise errors.OpPrereqError("Missing required parameter 'size'",
1320 disk_dict["size"] = utils.ParseUnit(disk_dict["size"])
1322 if (opts.disk_template and
1323 opts.disk_template in constants.DTS_INT_MIRROR and
1325 ToStderr("Changing the disk template to a mirrored one requires"
1326 " specifying a secondary node")
1329 op = opcodes.OpInstanceSetParams(instance_name=args[0],
1332 disk_template=opts.disk_template,
1333 remote_node=opts.node,
1334 hvparams=opts.hvparams,
1335 beparams=opts.beparams,
1336 runtime_mem=opts.runtime_mem,
1338 osparams=opts.osparams,
1339 force_variant=opts.force_variant,
1341 wait_for_sync=opts.wait_for_sync,
1342 offline_inst=opts.offline_inst,
1343 online_inst=opts.online_inst,
1344 ignore_ipolicy=opts.ignore_ipolicy)
1346 # even if here we process the result, we allow submit only
1347 result = SubmitOrSend(op, opts)
1350 ToStdout("Modified instance %s", args[0])
1351 for param, data in result:
1352 ToStdout(" - %-5s -> %s", param, data)
1353 ToStdout("Please don't forget that most parameters take effect"
1354 " only at the next start of the instance.")
1358 def ChangeGroup(opts, args):
1359 """Moves an instance to another group.
1362 (instance_name, ) = args
1366 op = opcodes.OpInstanceChangeGroup(instance_name=instance_name,
1367 iallocator=opts.iallocator,
1368 target_groups=opts.to,
1369 early_release=opts.early_release)
1370 result = SubmitOpCode(op, cl=cl, opts=opts)
1372 # Keep track of submitted jobs
1373 jex = JobExecutor(cl=cl, opts=opts)
1375 for (status, job_id) in result[constants.JOB_IDS_KEY]:
1376 jex.AddJobId(None, status, job_id)
1378 results = jex.GetResults()
1379 bad_cnt = len([row for row in results if not row[0]])
1381 ToStdout("Instance '%s' changed group successfully.", instance_name)
1382 rcode = constants.EXIT_SUCCESS
1384 ToStdout("There were %s errors while changing group of instance '%s'.",
1385 bad_cnt, instance_name)
1386 rcode = constants.EXIT_FAILURE
1391 # multi-instance selection options
1392 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1393 help="Do not ask for confirmation when more than"
1394 " one instance is affected",
1395 action="store_true", default=False)
1397 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1398 help="Filter by nodes (primary only)",
1399 const=_EXPAND_NODES_PRI, action="store_const")
1401 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1402 help="Filter by nodes (secondary only)",
1403 const=_EXPAND_NODES_SEC, action="store_const")
1405 m_node_opt = cli_option("--node", dest="multi_mode",
1406 help="Filter by nodes (primary and secondary)",
1407 const=_EXPAND_NODES_BOTH, action="store_const")
1409 m_clust_opt = cli_option("--all", dest="multi_mode",
1410 help="Select all instances in the cluster",
1411 const=_EXPAND_CLUSTER, action="store_const")
1413 m_inst_opt = cli_option("--instance", dest="multi_mode",
1414 help="Filter by instance name [default]",
1415 const=_EXPAND_INSTANCES, action="store_const")
1417 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1418 help="Filter by node tag",
1419 const=_EXPAND_NODES_BOTH_BY_TAGS,
1420 action="store_const")
1422 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1423 help="Filter by primary node tag",
1424 const=_EXPAND_NODES_PRI_BY_TAGS,
1425 action="store_const")
1427 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1428 help="Filter by secondary node tag",
1429 const=_EXPAND_NODES_SEC_BY_TAGS,
1430 action="store_const")
1432 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1433 help="Filter by instance tag",
1434 const=_EXPAND_INSTANCES_BY_TAGS,
1435 action="store_const")
1437 # this is defined separately due to readability only
1448 AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1449 "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1450 "Creates and adds a new instance to the cluster"),
1452 BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1454 "Create a bunch of instances based on specs in the file."),
1456 ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1457 [SHOWCMD_OPT, PRIORITY_OPT],
1458 "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1460 FailoverInstance, ARGS_ONE_INSTANCE,
1461 [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1462 DRY_RUN_OPT, PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT,
1463 IGNORE_IPOLICY_OPT],
1464 "[-f] <instance>", "Stops the instance, changes its primary node and"
1465 " (if it was originally running) starts it on the new node"
1466 " (the secondary for mirrored instances or any node"
1467 " for shared storage)."),
1469 MigrateInstance, ARGS_ONE_INSTANCE,
1470 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1471 PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT, ALLOW_FAILOVER_OPT,
1472 IGNORE_IPOLICY_OPT],
1473 "[-f] <instance>", "Migrate instance to its secondary node"
1474 " (only for mirrored instances)"),
1476 MoveInstance, ARGS_ONE_INSTANCE,
1477 [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1478 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_CONSIST_OPT, IGNORE_IPOLICY_OPT],
1479 "[-f] <instance>", "Move instance to an arbitrary node"
1480 " (only for instances of type file and lv)"),
1482 ShowInstanceConfig, ARGS_MANY_INSTANCES,
1483 [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1484 "[-s] {--all | <instance>...}",
1485 "Show information on the specified instance(s)"),
1487 ListInstances, ARGS_MANY_INSTANCES,
1488 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
1491 "Lists the instances and their status. The available fields can be shown"
1492 " using the \"list-fields\" command (see the man page for details)."
1493 " The default field list is (in order): %s." %
1494 utils.CommaJoin(_LIST_DEF_FIELDS),
1497 ListInstanceFields, [ArgUnknown()],
1498 [NOHDR_OPT, SEP_OPT],
1500 "Lists all available fields for instances"),
1502 ReinstallInstance, [ArgInstance()],
1503 [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1504 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1505 m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1506 SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1507 "[-f] <instance>", "Reinstall a stopped instance"),
1509 RemoveInstance, ARGS_ONE_INSTANCE,
1510 [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1511 DRY_RUN_OPT, PRIORITY_OPT],
1512 "[-f] <instance>", "Shuts down the instance and removes it"),
1515 [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1516 [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1517 "<instance> <new_name>", "Rename the instance"),
1519 ReplaceDisks, ARGS_ONE_INSTANCE,
1520 [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1521 NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1522 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_IPOLICY_OPT],
1523 "[-s|-p|-n NODE|-I NAME] <instance>",
1524 "Replaces all disks for the instance"),
1526 SetInstanceParams, ARGS_ONE_INSTANCE,
1527 [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1528 DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1529 OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT, NWSYNC_OPT, OFFLINE_INST_OPT,
1530 ONLINE_INST_OPT, IGNORE_IPOLICY_OPT, RUNTIME_MEM_OPT],
1531 "<instance>", "Alters the parameters of an instance"),
1533 GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1534 [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1535 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1536 m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1537 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT, NO_REMEMBER_OPT],
1538 "<instance>", "Stops an instance"),
1540 GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1541 [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1542 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1543 m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1544 BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT,
1545 NO_REMEMBER_OPT, STARTUP_PAUSED_OPT],
1546 "<instance>", "Starts an instance"),
1548 GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1549 [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1550 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1551 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1552 m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1553 "<instance>", "Reboots an instance"),
1555 ActivateDisks, ARGS_ONE_INSTANCE,
1556 [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
1557 "<instance>", "Activate an instance's disks"),
1558 "deactivate-disks": (
1559 DeactivateDisks, ARGS_ONE_INSTANCE,
1560 [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1561 "[-f] <instance>", "Deactivate an instance's disks"),
1563 RecreateDisks, ARGS_ONE_INSTANCE,
1564 [SUBMIT_OPT, DISK_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1565 "<instance>", "Recreate an instance's disks"),
1568 [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1569 ArgUnknown(min=1, max=1)],
1570 [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1571 "<instance> <disk> <size>", "Grow an instance's disk"),
1573 ChangeGroup, ARGS_ONE_INSTANCE,
1574 [TO_GROUP_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT],
1575 "[-I <iallocator>] [--to <group>]", "Change group of instance"),
1577 ListTags, ARGS_ONE_INSTANCE, [PRIORITY_OPT],
1578 "<instance_name>", "List the tags of the given instance"),
1580 AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1581 [TAG_SRC_OPT, PRIORITY_OPT],
1582 "<instance_name> tag...", "Add tags to the given instance"),
1584 RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1585 [TAG_SRC_OPT, PRIORITY_OPT],
1586 "<instance_name> tag...", "Remove tags from given instance"),
1589 #: dictionary with aliases for commands
1597 return GenericMain(commands, aliases=aliases,
1598 override={"tag_type": constants.TAG_INSTANCE},
1599 env_override=_ENV_OVERRIDE)