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 allow_runtime_changes=opts.allow_runtime_chgs,
869 ignore_ipolicy=opts.ignore_ipolicy)
870 SubmitOpCode(op, cl=cl, opts=opts)
874 def MoveInstance(opts, args):
877 @param opts: the command line options selected by the user
879 @param args: should contain only one element, the instance name
881 @return: the desired exit code
885 instance_name = args[0]
889 usertext = ("Instance %s will be moved."
890 " This requires a shutdown of the instance. Continue?" %
892 if not AskUser(usertext):
895 op = opcodes.OpInstanceMove(instance_name=instance_name,
896 target_node=opts.node,
897 shutdown_timeout=opts.shutdown_timeout,
898 ignore_consistency=opts.ignore_consistency,
899 ignore_ipolicy=opts.ignore_ipolicy)
900 SubmitOrSend(op, opts, cl=cl)
904 def ConnectToInstanceConsole(opts, args):
905 """Connect to the console of an instance.
907 @param opts: the command line options selected by the user
909 @param args: should contain only one element, the instance name
911 @return: the desired exit code
914 instance_name = args[0]
918 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
919 ((console_data, oper_state), ) = \
920 cl.QueryInstances([instance_name], ["console", "oper_state"], False)
922 # Ensure client connection is closed while external commands are run
929 # Instance is running
930 raise errors.OpExecError("Console information for instance %s is"
931 " unavailable" % instance_name)
933 raise errors.OpExecError("Instance %s is not running, can't get console" %
936 return _DoConsole(objects.InstanceConsole.FromDict(console_data),
937 opts.show_command, cluster_name)
940 def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
941 _runcmd_fn=utils.RunCmd):
942 """Acts based on the result of L{opcodes.OpInstanceConsole}.
944 @type console: L{objects.InstanceConsole}
945 @param console: Console object
946 @type show_command: bool
947 @param show_command: Whether to just display commands
948 @type cluster_name: string
949 @param cluster_name: Cluster name as retrieved from master daemon
952 assert console.Validate()
954 if console.kind == constants.CONS_MESSAGE:
955 feedback_fn(console.message)
956 elif console.kind == constants.CONS_VNC:
957 feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
958 " URL <vnc://%s:%s/>",
959 console.instance, console.host, console.port,
960 console.display, console.host, console.port)
961 elif console.kind == constants.CONS_SPICE:
962 feedback_fn("Instance %s has SPICE listening on %s:%s", console.instance,
963 console.host, console.port)
964 elif console.kind == constants.CONS_SSH:
965 # Convert to string if not already one
966 if isinstance(console.command, basestring):
967 cmd = console.command
969 cmd = utils.ShellQuoteArgs(console.command)
971 srun = ssh.SshRunner(cluster_name=cluster_name)
972 ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
973 batch=True, quiet=False, tty=True)
976 feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
978 result = _runcmd_fn(ssh_cmd, interactive=True)
980 logging.error("Console command \"%s\" failed with reason '%s' and"
981 " output %r", result.cmd, result.fail_reason,
983 raise errors.OpExecError("Connection to console of instance %s failed,"
984 " please check cluster configuration" %
987 raise errors.GenericError("Unknown console type '%s'" % console.kind)
989 return constants.EXIT_SUCCESS
992 def _FormatLogicalID(dev_type, logical_id, roman):
993 """Formats the logical_id of a disk.
996 if dev_type == constants.LD_DRBD8:
997 node_a, node_b, port, minor_a, minor_b, key = logical_id
999 ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
1001 ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
1003 ("port", compat.TryToRoman(port, convert=roman)),
1006 elif dev_type == constants.LD_LV:
1007 vg_name, lv_name = logical_id
1008 data = ["%s/%s" % (vg_name, lv_name)]
1010 data = [str(logical_id)]
1015 def _FormatBlockDevInfo(idx, top_level, dev, roman):
1016 """Show block device information.
1018 This is only used by L{ShowInstanceConfig}, but it's too big to be
1019 left for an inline definition.
1022 @param idx: the index of the current disk
1023 @type top_level: boolean
1024 @param top_level: if this a top-level disk?
1026 @param dev: dictionary with disk information
1027 @type roman: boolean
1028 @param roman: whether to try to use roman integers
1029 @return: a list of either strings, tuples or lists
1030 (which should be formatted at a higher indent level)
1033 def helper(dtype, status):
1034 """Format one line for physical device status.
1037 @param dtype: a constant from the L{constants.LDS_BLOCK} set
1039 @param status: a tuple as returned from L{backend.FindBlockDevice}
1040 @return: the string representing the status
1046 (path, major, minor, syncp, estt, degr, ldisk_status) = status
1048 major_string = "N/A"
1050 major_string = str(compat.TryToRoman(major, convert=roman))
1053 minor_string = "N/A"
1055 minor_string = str(compat.TryToRoman(minor, convert=roman))
1057 txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1058 if dtype in (constants.LD_DRBD8, ):
1059 if syncp is not None:
1060 sync_text = "*RECOVERING* %5.2f%%," % syncp
1062 sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1064 sync_text += " ETA unknown"
1066 sync_text = "in sync"
1068 degr_text = "*DEGRADED*"
1071 if ldisk_status == constants.LDS_FAULTY:
1072 ldisk_text = " *MISSING DISK*"
1073 elif ldisk_status == constants.LDS_UNKNOWN:
1074 ldisk_text = " *UNCERTAIN STATE*"
1077 txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1078 elif dtype == constants.LD_LV:
1079 if ldisk_status == constants.LDS_FAULTY:
1080 ldisk_text = " *FAILED* (failed drive?)"
1088 if dev["iv_name"] is not None:
1089 txt = dev["iv_name"]
1091 txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1093 txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1094 if isinstance(dev["size"], int):
1095 nice_size = utils.FormatUnit(dev["size"], "h")
1097 nice_size = dev["size"]
1098 d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1101 data.append(("access mode", dev["mode"]))
1102 if dev["logical_id"] is not None:
1104 l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1106 l_id = [str(dev["logical_id"])]
1108 data.append(("logical_id", l_id[0]))
1111 elif dev["physical_id"] is not None:
1112 data.append("physical_id:")
1113 data.append([dev["physical_id"]])
1116 data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1119 data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1122 data.append("child devices:")
1123 for c_idx, child in enumerate(dev["children"]):
1124 data.append(_FormatBlockDevInfo(c_idx, False, child, roman))
1129 def _FormatList(buf, data, indent_level):
1130 """Formats a list of data at a given indent level.
1132 If the element of the list is:
1133 - a string, it is simply formatted as is
1134 - a tuple, it will be split into key, value and the all the
1135 values in a list will be aligned all at the same start column
1136 - a list, will be recursively formatted
1139 @param buf: the buffer into which we write the output
1140 @param data: the list to format
1141 @type indent_level: int
1142 @param indent_level: the indent level to format at
1145 max_tlen = max([len(elem[0]) for elem in data
1146 if isinstance(elem, tuple)] or [0])
1148 if isinstance(elem, basestring):
1149 buf.write("%*s%s\n" % (2 * indent_level, "", elem))
1150 elif isinstance(elem, tuple):
1152 spacer = "%*s" % (max_tlen - len(key), "")
1153 buf.write("%*s%s:%s %s\n" % (2 * indent_level, "", key, spacer, value))
1154 elif isinstance(elem, list):
1155 _FormatList(buf, elem, indent_level + 1)
1158 def ShowInstanceConfig(opts, args):
1159 """Compute instance run-time status.
1161 @param opts: the command line options selected by the user
1163 @param args: either an empty list, and then we query all
1164 instances, or should contain a list of instance names
1166 @return: the desired exit code
1169 if not args and not opts.show_all:
1170 ToStderr("No instance selected."
1171 " Please pass in --all if you want to query all instances.\n"
1172 "Note that this can take a long time on a big cluster.")
1174 elif args and opts.show_all:
1175 ToStderr("Cannot use --all if you specify instance names.")
1179 op = opcodes.OpInstanceQueryData(instances=args, static=opts.static,
1180 use_locking=not opts.static)
1181 result = SubmitOpCode(op, opts=opts)
1183 ToStdout("No instances.")
1188 for instance_name in result:
1189 instance = result[instance_name]
1190 buf.write("Instance name: %s\n" % instance["name"])
1191 buf.write("UUID: %s\n" % instance["uuid"])
1192 buf.write("Serial number: %s\n" %
1193 compat.TryToRoman(instance["serial_no"],
1194 convert=opts.roman_integers))
1195 buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1196 buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1197 buf.write("State: configured to be %s" % instance["config_state"])
1198 if instance["run_state"]:
1199 buf.write(", actual state is %s" % instance["run_state"])
1201 ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1202 ## instance["auto_balance"])
1203 buf.write(" Nodes:\n")
1204 buf.write(" - primary: %s\n" % instance["pnode"])
1205 buf.write(" - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1206 buf.write(" Operating system: %s\n" % instance["os"])
1207 FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1209 if "network_port" in instance:
1210 buf.write(" Allocated network port: %s\n" %
1211 compat.TryToRoman(instance["network_port"],
1212 convert=opts.roman_integers))
1213 buf.write(" Hypervisor: %s\n" % instance["hypervisor"])
1215 # custom VNC console information
1216 vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1218 if vnc_bind_address:
1219 port = instance["network_port"]
1220 display = int(port) - constants.VNC_BASE_PORT
1221 if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1222 vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1225 elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1226 vnc_console_port = ("%s:%s (node %s) (display %s)" %
1227 (vnc_bind_address, port,
1228 instance["pnode"], display))
1230 # vnc bind address is a file
1231 vnc_console_port = "%s:%s" % (instance["pnode"],
1233 buf.write(" - console connection: vnc to %s\n" % vnc_console_port)
1235 FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1237 buf.write(" Hardware:\n")
1238 buf.write(" - VCPUs: %s\n" %
1239 compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1240 convert=opts.roman_integers))
1241 buf.write(" - maxmem: %sMiB\n" %
1242 compat.TryToRoman(instance["be_actual"][constants.BE_MAXMEM],
1243 convert=opts.roman_integers))
1244 buf.write(" - minmem: %sMiB\n" %
1245 compat.TryToRoman(instance["be_actual"][constants.BE_MINMEM],
1246 convert=opts.roman_integers))
1247 # deprecated "memory" value, kept for one version for compatibility
1248 # TODO(ganeti 2.7) remove.
1249 buf.write(" - memory: %sMiB\n" %
1250 compat.TryToRoman(instance["be_actual"][constants.BE_MAXMEM],
1251 convert=opts.roman_integers))
1252 buf.write(" - %s: %s\n" %
1253 (constants.BE_ALWAYS_FAILOVER,
1254 instance["be_actual"][constants.BE_ALWAYS_FAILOVER]))
1255 buf.write(" - NICs:\n")
1256 for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1257 buf.write(" - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1258 (idx, mac, ip, mode, link))
1259 buf.write(" Disk template: %s\n" % instance["disk_template"])
1260 buf.write(" Disks:\n")
1262 for idx, device in enumerate(instance["disks"]):
1263 _FormatList(buf, _FormatBlockDevInfo(idx, True, device,
1264 opts.roman_integers), 2)
1266 ToStdout(buf.getvalue().rstrip("\n"))
1270 def SetInstanceParams(opts, args):
1271 """Modifies an instance.
1273 All parameters take effect only at the next restart of the instance.
1275 @param opts: the command line options selected by the user
1277 @param args: should contain only one element, the instance name
1279 @return: the desired exit code
1282 if not (opts.nics or opts.disks or opts.disk_template or
1283 opts.hvparams or opts.beparams or opts.os or opts.osparams or
1284 opts.offline_inst or opts.online_inst or opts.runtime_mem):
1285 ToStderr("Please give at least one of the parameters.")
1288 for param in opts.beparams:
1289 if isinstance(opts.beparams[param], basestring):
1290 if opts.beparams[param].lower() == "default":
1291 opts.beparams[param] = constants.VALUE_DEFAULT
1293 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT,
1294 allowed_values=[constants.VALUE_DEFAULT])
1296 for param in opts.hvparams:
1297 if isinstance(opts.hvparams[param], basestring):
1298 if opts.hvparams[param].lower() == "default":
1299 opts.hvparams[param] = constants.VALUE_DEFAULT
1301 utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1302 allowed_values=[constants.VALUE_DEFAULT])
1304 for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1306 nic_op = int(nic_op)
1307 opts.nics[idx] = (nic_op, nic_dict)
1308 except (TypeError, ValueError):
1311 for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1313 disk_op = int(disk_op)
1314 opts.disks[idx] = (disk_op, disk_dict)
1315 except (TypeError, ValueError):
1317 if disk_op == constants.DDM_ADD:
1318 if "size" not in disk_dict:
1319 raise errors.OpPrereqError("Missing required parameter 'size'",
1321 disk_dict["size"] = utils.ParseUnit(disk_dict["size"])
1323 if (opts.disk_template and
1324 opts.disk_template in constants.DTS_INT_MIRROR and
1326 ToStderr("Changing the disk template to a mirrored one requires"
1327 " specifying a secondary node")
1330 op = opcodes.OpInstanceSetParams(instance_name=args[0],
1333 disk_template=opts.disk_template,
1334 remote_node=opts.node,
1335 hvparams=opts.hvparams,
1336 beparams=opts.beparams,
1337 runtime_mem=opts.runtime_mem,
1339 osparams=opts.osparams,
1340 force_variant=opts.force_variant,
1342 wait_for_sync=opts.wait_for_sync,
1343 offline_inst=opts.offline_inst,
1344 online_inst=opts.online_inst,
1345 ignore_ipolicy=opts.ignore_ipolicy)
1347 # even if here we process the result, we allow submit only
1348 result = SubmitOrSend(op, opts)
1351 ToStdout("Modified instance %s", args[0])
1352 for param, data in result:
1353 ToStdout(" - %-5s -> %s", param, data)
1354 ToStdout("Please don't forget that most parameters take effect"
1355 " only at the next start of the instance.")
1359 def ChangeGroup(opts, args):
1360 """Moves an instance to another group.
1363 (instance_name, ) = args
1367 op = opcodes.OpInstanceChangeGroup(instance_name=instance_name,
1368 iallocator=opts.iallocator,
1369 target_groups=opts.to,
1370 early_release=opts.early_release)
1371 result = SubmitOpCode(op, cl=cl, opts=opts)
1373 # Keep track of submitted jobs
1374 jex = JobExecutor(cl=cl, opts=opts)
1376 for (status, job_id) in result[constants.JOB_IDS_KEY]:
1377 jex.AddJobId(None, status, job_id)
1379 results = jex.GetResults()
1380 bad_cnt = len([row for row in results if not row[0]])
1382 ToStdout("Instance '%s' changed group successfully.", instance_name)
1383 rcode = constants.EXIT_SUCCESS
1385 ToStdout("There were %s errors while changing group of instance '%s'.",
1386 bad_cnt, instance_name)
1387 rcode = constants.EXIT_FAILURE
1392 # multi-instance selection options
1393 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1394 help="Do not ask for confirmation when more than"
1395 " one instance is affected",
1396 action="store_true", default=False)
1398 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1399 help="Filter by nodes (primary only)",
1400 const=_EXPAND_NODES_PRI, action="store_const")
1402 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1403 help="Filter by nodes (secondary only)",
1404 const=_EXPAND_NODES_SEC, action="store_const")
1406 m_node_opt = cli_option("--node", dest="multi_mode",
1407 help="Filter by nodes (primary and secondary)",
1408 const=_EXPAND_NODES_BOTH, action="store_const")
1410 m_clust_opt = cli_option("--all", dest="multi_mode",
1411 help="Select all instances in the cluster",
1412 const=_EXPAND_CLUSTER, action="store_const")
1414 m_inst_opt = cli_option("--instance", dest="multi_mode",
1415 help="Filter by instance name [default]",
1416 const=_EXPAND_INSTANCES, action="store_const")
1418 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1419 help="Filter by node tag",
1420 const=_EXPAND_NODES_BOTH_BY_TAGS,
1421 action="store_const")
1423 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1424 help="Filter by primary node tag",
1425 const=_EXPAND_NODES_PRI_BY_TAGS,
1426 action="store_const")
1428 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1429 help="Filter by secondary node tag",
1430 const=_EXPAND_NODES_SEC_BY_TAGS,
1431 action="store_const")
1433 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1434 help="Filter by instance tag",
1435 const=_EXPAND_INSTANCES_BY_TAGS,
1436 action="store_const")
1438 # this is defined separately due to readability only
1449 AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1450 "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1451 "Creates and adds a new instance to the cluster"),
1453 BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1455 "Create a bunch of instances based on specs in the file."),
1457 ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1458 [SHOWCMD_OPT, PRIORITY_OPT],
1459 "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1461 FailoverInstance, ARGS_ONE_INSTANCE,
1462 [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1463 DRY_RUN_OPT, PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT,
1464 IGNORE_IPOLICY_OPT],
1465 "[-f] <instance>", "Stops the instance, changes its primary node and"
1466 " (if it was originally running) starts it on the new node"
1467 " (the secondary for mirrored instances or any node"
1468 " for shared storage)."),
1470 MigrateInstance, ARGS_ONE_INSTANCE,
1471 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1472 PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT, ALLOW_FAILOVER_OPT,
1473 IGNORE_IPOLICY_OPT, NORUNTIME_CHGS_OPT],
1474 "[-f] <instance>", "Migrate instance to its secondary node"
1475 " (only for mirrored instances)"),
1477 MoveInstance, ARGS_ONE_INSTANCE,
1478 [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1479 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_CONSIST_OPT, IGNORE_IPOLICY_OPT],
1480 "[-f] <instance>", "Move instance to an arbitrary node"
1481 " (only for instances of type file and lv)"),
1483 ShowInstanceConfig, ARGS_MANY_INSTANCES,
1484 [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1485 "[-s] {--all | <instance>...}",
1486 "Show information on the specified instance(s)"),
1488 ListInstances, ARGS_MANY_INSTANCES,
1489 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
1492 "Lists the instances and their status. The available fields can be shown"
1493 " using the \"list-fields\" command (see the man page for details)."
1494 " The default field list is (in order): %s." %
1495 utils.CommaJoin(_LIST_DEF_FIELDS),
1498 ListInstanceFields, [ArgUnknown()],
1499 [NOHDR_OPT, SEP_OPT],
1501 "Lists all available fields for instances"),
1503 ReinstallInstance, [ArgInstance()],
1504 [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1505 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1506 m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1507 SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1508 "[-f] <instance>", "Reinstall a stopped instance"),
1510 RemoveInstance, ARGS_ONE_INSTANCE,
1511 [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1512 DRY_RUN_OPT, PRIORITY_OPT],
1513 "[-f] <instance>", "Shuts down the instance and removes it"),
1516 [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1517 [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1518 "<instance> <new_name>", "Rename the instance"),
1520 ReplaceDisks, ARGS_ONE_INSTANCE,
1521 [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1522 NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1523 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_IPOLICY_OPT],
1524 "[-s|-p|-n NODE|-I NAME] <instance>",
1525 "Replaces all disks for the instance"),
1527 SetInstanceParams, ARGS_ONE_INSTANCE,
1528 [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1529 DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1530 OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT, NWSYNC_OPT, OFFLINE_INST_OPT,
1531 ONLINE_INST_OPT, IGNORE_IPOLICY_OPT, RUNTIME_MEM_OPT],
1532 "<instance>", "Alters the parameters of an instance"),
1534 GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1535 [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1536 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1537 m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1538 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT, NO_REMEMBER_OPT],
1539 "<instance>", "Stops an instance"),
1541 GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1542 [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1543 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1544 m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1545 BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT,
1546 NO_REMEMBER_OPT, STARTUP_PAUSED_OPT],
1547 "<instance>", "Starts an instance"),
1549 GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1550 [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1551 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1552 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1553 m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1554 "<instance>", "Reboots an instance"),
1556 ActivateDisks, ARGS_ONE_INSTANCE,
1557 [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
1558 "<instance>", "Activate an instance's disks"),
1559 "deactivate-disks": (
1560 DeactivateDisks, ARGS_ONE_INSTANCE,
1561 [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1562 "[-f] <instance>", "Deactivate an instance's disks"),
1564 RecreateDisks, ARGS_ONE_INSTANCE,
1565 [SUBMIT_OPT, DISK_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1566 "<instance>", "Recreate an instance's disks"),
1569 [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1570 ArgUnknown(min=1, max=1)],
1571 [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1572 "<instance> <disk> <size>", "Grow an instance's disk"),
1574 ChangeGroup, ARGS_ONE_INSTANCE,
1575 [TO_GROUP_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT],
1576 "[-I <iallocator>] [--to <group>]", "Change group of instance"),
1578 ListTags, ARGS_ONE_INSTANCE, [PRIORITY_OPT],
1579 "<instance_name>", "List the tags of the given instance"),
1581 AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1582 [TAG_SRC_OPT, PRIORITY_OPT],
1583 "<instance_name> tag...", "Add tags to the given instance"),
1585 RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1586 [TAG_SRC_OPT, PRIORITY_OPT],
1587 "<instance_name> tag...", "Remove tags from given instance"),
1590 #: dictionary with aliases for commands
1598 return GenericMain(commands, aliases=aliases,
1599 override={"tag_type": constants.TAG_INSTANCE},
1600 env_override=_ENV_OVERRIDE)