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",
69 _ENV_OVERRIDE = frozenset(["list"])
72 def _ExpandMultiNames(mode, names, client=None):
73 """Expand the given names using the passed mode.
75 For _EXPAND_CLUSTER, all instances will be returned. For
76 _EXPAND_NODES_PRI/SEC, all instances having those nodes as
77 primary/secondary will be returned. For _EXPAND_NODES_BOTH, all
78 instances having those nodes as either primary or secondary will be
79 returned. For _EXPAND_INSTANCES, the given instances will be
82 @param mode: one of L{_EXPAND_CLUSTER}, L{_EXPAND_NODES_BOTH},
83 L{_EXPAND_NODES_PRI}, L{_EXPAND_NODES_SEC} or
85 @param names: a list of names; for cluster, it must be empty,
86 and for node and instance it must be a list of valid item
87 names (short names are valid as usual, e.g. node1 instead of
90 @return: the list of names after the expansion
91 @raise errors.ProgrammerError: for unknown selection type
92 @raise errors.OpPrereqError: for invalid input parameters
95 # pylint: disable=W0142
99 if mode == _EXPAND_CLUSTER:
101 raise errors.OpPrereqError("Cluster filter mode takes no arguments",
103 idata = client.QueryInstances([], ["name"], False)
104 inames = [row[0] for row in idata]
106 elif (mode in _EXPAND_NODES_TAGS_MODES or
107 mode in (_EXPAND_NODES_BOTH, _EXPAND_NODES_PRI, _EXPAND_NODES_SEC)):
108 if mode in _EXPAND_NODES_TAGS_MODES:
110 raise errors.OpPrereqError("No node tags passed", errors.ECODE_INVAL)
111 ndata = client.QueryNodes([], ["name", "pinst_list",
112 "sinst_list", "tags"], False)
113 ndata = [row for row in ndata if set(row[3]).intersection(names)]
116 raise errors.OpPrereqError("No node names passed", errors.ECODE_INVAL)
117 ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
120 ipri = [row[1] for row in ndata]
121 pri_names = list(itertools.chain(*ipri))
122 isec = [row[2] for row in ndata]
123 sec_names = list(itertools.chain(*isec))
124 if mode in (_EXPAND_NODES_BOTH, _EXPAND_NODES_BOTH_BY_TAGS):
125 inames = pri_names + sec_names
126 elif mode in (_EXPAND_NODES_PRI, _EXPAND_NODES_PRI_BY_TAGS):
128 elif mode in (_EXPAND_NODES_SEC, _EXPAND_NODES_SEC_BY_TAGS):
131 raise errors.ProgrammerError("Unhandled shutdown type")
132 elif mode == _EXPAND_INSTANCES:
134 raise errors.OpPrereqError("No instance names passed",
136 idata = client.QueryInstances(names, ["name"], False)
137 inames = [row[0] for row in idata]
138 elif mode == _EXPAND_INSTANCES_BY_TAGS:
140 raise errors.OpPrereqError("No instance tags passed",
142 idata = client.QueryInstances([], ["name", "tags"], False)
143 inames = [row[0] for row in idata if set(row[1]).intersection(names)]
145 raise errors.OpPrereqError("Unknown mode '%s'" % mode, errors.ECODE_INVAL)
150 def _EnsureInstancesExist(client, names):
151 """Check for and ensure the given instance names exist.
153 This function will raise an OpPrereqError in case they don't
154 exist. Otherwise it will exit cleanly.
156 @type client: L{ganeti.luxi.Client}
157 @param client: the client to use for the query
159 @param names: the list of instance names to query
160 @raise errors.OpPrereqError: in case any instance is missing
163 # TODO: change LUInstanceQuery to that it actually returns None
164 # instead of raising an exception, or devise a better mechanism
165 result = client.QueryInstances(names, ["name"], False)
166 for orig_name, row in zip(names, result):
168 raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name,
172 def GenericManyOps(operation, fn):
173 """Generic multi-instance operations.
175 The will return a wrapper that processes the options and arguments
176 given, and uses the passed function to build the opcode needed for
177 the specific operation. Thus all the generic loop/confirmation code
178 is abstracted into this function.
181 def realfn(opts, args):
182 if opts.multi_mode is None:
183 opts.multi_mode = _EXPAND_INSTANCES
185 inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
187 if opts.multi_mode == _EXPAND_CLUSTER:
188 ToStdout("Cluster is empty, no instances to shutdown")
190 raise errors.OpPrereqError("Selection filter does not match"
191 " any instances", errors.ECODE_INVAL)
192 multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1
193 if not (opts.force_multi or not multi_on
194 or ConfirmOperation(inames, "instances", operation)):
196 jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
199 jex.QueueJob(name, op)
200 results = jex.WaitOrShow(not opts.submit_only)
201 rcode = compat.all(row[0] for row in results)
202 return int(not rcode)
206 def ListInstances(opts, args):
207 """List instances and their properties.
209 @param opts: the command line options selected by the user
211 @param args: should be an empty list
213 @return: the desired exit code
216 selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
218 fmtoverride = dict.fromkeys(["tags", "disk.sizes", "nic.macs", "nic.ips",
219 "nic.modes", "nic.links", "nic.bridges",
220 "snodes", "snodes.group", "snodes.group.uuid"],
221 (lambda value: ",".join(str(item)
225 return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units,
226 opts.separator, not opts.no_headers,
227 format_override=fmtoverride, verbose=opts.verbose,
228 force_filter=opts.force_filter)
231 def ListInstanceFields(opts, args):
232 """List instance fields.
234 @param opts: the command line options selected by the user
236 @param args: fields to list, or empty for all
238 @return: the desired exit code
241 return GenericListFields(constants.QR_INSTANCE, args, opts.separator,
245 def AddInstance(opts, args):
246 """Add an instance to the cluster.
248 This is just a wrapper over GenericInstanceCreate.
251 return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
254 def BatchCreate(opts, args):
255 """Create instances using a definition file.
257 This function reads a json file with instances defined
261 "disk_size": [20480],
267 "primary_node": "firstnode",
268 "secondary_node": "secondnode",
269 "iallocator": "dumb"}
272 Note that I{primary_node} and I{secondary_node} have precedence over
275 @param opts: the command line options selected by the user
277 @param args: should contain one element, the json filename
279 @return: the desired exit code
282 _DEFAULT_SPECS = {"disk_size": [20 * 1024],
285 "primary_node": None,
286 "secondary_node": None,
293 "file_storage_dir": None,
294 "force_variant": False,
295 "file_driver": "loop"}
297 def _PopulateWithDefaults(spec):
298 """Returns a new hash combined with default values."""
299 mydict = _DEFAULT_SPECS.copy()
304 """Validate the instance specs."""
305 # Validate fields required under any circumstances
306 for required_field in ("os", "template"):
307 if required_field not in spec:
308 raise errors.OpPrereqError('Required field "%s" is missing.' %
309 required_field, errors.ECODE_INVAL)
310 # Validate special fields
311 if spec["primary_node"] is not None:
312 if (spec["template"] in constants.DTS_INT_MIRROR and
313 spec["secondary_node"] is None):
314 raise errors.OpPrereqError("Template requires secondary node, but"
315 " there was no secondary provided.",
317 elif spec["iallocator"] is None:
318 raise errors.OpPrereqError("You have to provide at least a primary_node"
319 " or an iallocator.",
322 if (spec["hvparams"] and
323 not isinstance(spec["hvparams"], dict)):
324 raise errors.OpPrereqError("Hypervisor parameters must be a dict.",
327 json_filename = args[0]
329 instance_data = simplejson.loads(utils.ReadFile(json_filename))
330 except Exception, err: # pylint: disable=W0703
331 ToStderr("Can't parse the instance definition file: %s" % str(err))
334 if not isinstance(instance_data, dict):
335 ToStderr("The instance definition file is not in dict format.")
338 jex = JobExecutor(opts=opts)
340 # Iterate over the instances and do:
341 # * Populate the specs with default value
342 # * Validate the instance specs
343 i_names = utils.NiceSort(instance_data.keys()) # pylint: disable=E1103
345 specs = instance_data[name]
346 specs = _PopulateWithDefaults(specs)
349 hypervisor = specs["hypervisor"]
350 hvparams = specs["hvparams"]
353 for elem in specs["disk_size"]:
355 size = utils.ParseUnit(elem)
356 except (TypeError, ValueError), err:
357 raise errors.OpPrereqError("Invalid disk size '%s' for"
359 (elem, name, err), errors.ECODE_INVAL)
360 disks.append({"size": size})
362 utils.ForceDictType(specs["backend"], constants.BES_PARAMETER_COMPAT)
363 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
366 for field in constants.INIC_PARAMS:
370 tmp_nics[0][field] = specs[field]
372 if specs["nics"] is not None and tmp_nics:
373 raise errors.OpPrereqError("'nics' list incompatible with using"
374 " individual nic fields as well",
376 elif specs["nics"] is not None:
377 tmp_nics = specs["nics"]
381 op = opcodes.OpInstanceCreate(instance_name=name,
383 disk_template=specs["template"],
384 mode=constants.INSTANCE_CREATE,
386 force_variant=specs["force_variant"],
387 pnode=specs["primary_node"],
388 snode=specs["secondary_node"],
390 start=specs["start"],
391 ip_check=specs["ip_check"],
392 name_check=specs["name_check"],
394 iallocator=specs["iallocator"],
395 hypervisor=hypervisor,
397 beparams=specs["backend"],
398 file_storage_dir=specs["file_storage_dir"],
399 file_driver=specs["file_driver"])
401 jex.QueueJob(name, op)
402 # we never want to wait, just show the submitted job IDs
403 jex.WaitOrShow(False)
408 def ReinstallInstance(opts, args):
409 """Reinstall an instance.
411 @param opts: the command line options selected by the user
413 @param args: should contain only one element, the name of the
414 instance to be reinstalled
416 @return: the desired exit code
419 # first, compute the desired name list
420 if opts.multi_mode is None:
421 opts.multi_mode = _EXPAND_INSTANCES
423 inames = _ExpandMultiNames(opts.multi_mode, args)
425 raise errors.OpPrereqError("Selection filter does not match any instances",
428 # second, if requested, ask for an OS
429 if opts.select_os is True:
430 op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[])
431 result = SubmitOpCode(op, opts=opts)
434 ToStdout("Can't get the OS list")
437 ToStdout("Available OS templates:")
440 for (name, variants) in result:
441 for entry in CalculateOSNames(name, variants):
442 ToStdout("%3s: %s", number, entry)
443 choices.append(("%s" % number, entry, entry))
446 choices.append(("x", "exit", "Exit gnt-instance reinstall"))
447 selected = AskUser("Enter OS template number (or x to abort):",
450 if selected == "exit":
451 ToStderr("User aborted reinstall, exiting")
455 os_msg = "change the OS to '%s'" % selected
458 if opts.os is not None:
459 os_msg = "change the OS to '%s'" % os_name
461 os_msg = "keep the same OS"
463 # third, get confirmation: multi-reinstall requires --force-multi,
464 # single-reinstall either --force or --force-multi (--force-multi is
465 # a stronger --force)
466 multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1
468 warn_msg = ("Note: this will remove *all* data for the"
469 " below instances! It will %s.\n" % os_msg)
470 if not (opts.force_multi or
471 ConfirmOperation(inames, "instances", "reinstall", extra=warn_msg)):
474 if not (opts.force or opts.force_multi):
475 usertext = ("This will reinstall the instance '%s' (and %s) which"
476 " removes all data. Continue?") % (inames[0], os_msg)
477 if not AskUser(usertext):
480 jex = JobExecutor(verbose=multi_on, opts=opts)
481 for instance_name in inames:
482 op = opcodes.OpInstanceReinstall(instance_name=instance_name,
484 force_variant=opts.force_variant,
485 osparams=opts.osparams)
486 jex.QueueJob(instance_name, op)
488 jex.WaitOrShow(not opts.submit_only)
492 def RemoveInstance(opts, args):
493 """Remove an instance.
495 @param opts: the command line options selected by the user
497 @param args: should contain only one element, the name of
498 the instance to be removed
500 @return: the desired exit code
503 instance_name = args[0]
508 _EnsureInstancesExist(cl, [instance_name])
510 usertext = ("This will remove the volumes of the instance %s"
511 " (including mirrors), thus removing all the data"
512 " of the instance. Continue?") % instance_name
513 if not AskUser(usertext):
516 op = opcodes.OpInstanceRemove(instance_name=instance_name,
517 ignore_failures=opts.ignore_failures,
518 shutdown_timeout=opts.shutdown_timeout)
519 SubmitOrSend(op, opts, cl=cl)
523 def RenameInstance(opts, args):
524 """Rename an instance.
526 @param opts: the command line options selected by the user
528 @param args: should contain two elements, the old and the
531 @return: the desired exit code
534 if not opts.name_check:
535 if not AskUser("As you disabled the check of the DNS entry, please verify"
536 " that '%s' is a FQDN. Continue?" % args[1]):
539 op = opcodes.OpInstanceRename(instance_name=args[0],
541 ip_check=opts.ip_check,
542 name_check=opts.name_check)
543 result = SubmitOrSend(op, opts)
546 ToStdout("Instance '%s' renamed to '%s'", args[0], result)
551 def ActivateDisks(opts, args):
552 """Activate an instance's disks.
554 This serves two purposes:
555 - it allows (as long as the instance is not running)
556 mounting the disks and modifying them from the node
557 - it repairs inactive secondary drbds
559 @param opts: the command line options selected by the user
561 @param args: should contain only one element, the instance name
563 @return: the desired exit code
566 instance_name = args[0]
567 op = opcodes.OpInstanceActivateDisks(instance_name=instance_name,
568 ignore_size=opts.ignore_size)
569 disks_info = SubmitOrSend(op, opts)
570 for host, iname, nname in disks_info:
571 ToStdout("%s:%s:%s", host, iname, nname)
575 def DeactivateDisks(opts, args):
576 """Deactivate an instance's disks.
578 This function takes the instance name, looks for its primary node
579 and the tries to shutdown its block devices on that node.
581 @param opts: the command line options selected by the user
583 @param args: should contain only one element, the instance name
585 @return: the desired exit code
588 instance_name = args[0]
589 op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name,
591 SubmitOrSend(op, opts)
595 def RecreateDisks(opts, args):
596 """Recreate an instance's disks.
598 @param opts: the command line options selected by the user
600 @param args: should contain only one element, the instance name
602 @return: the desired exit code
605 instance_name = args[0]
610 for didx, ddict in opts.disks:
613 if not ht.TDict(ddict):
614 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
615 raise errors.OpPrereqError(msg)
617 if constants.IDISK_SIZE in ddict:
619 ddict[constants.IDISK_SIZE] = \
620 utils.ParseUnit(ddict[constants.IDISK_SIZE])
621 except ValueError, err:
622 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
625 disks.append((didx, ddict))
627 # TODO: Verify modifyable parameters (already done in
628 # LUInstanceRecreateDisks, but it'd be nice to have in the client)
631 pnode, snode = SplitNodeOption(opts.node)
633 if snode is not None:
638 op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
639 disks=disks, nodes=nodes)
640 SubmitOrSend(op, opts)
645 def GrowDisk(opts, args):
646 """Grow an instance's disks.
648 @param opts: the command line options selected by the user
650 @param args: should contain three elements, the target instance name,
651 the target disk id, and the target growth
653 @return: the desired exit code
660 except (TypeError, ValueError), err:
661 raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
663 amount = utils.ParseUnit(args[2])
664 op = opcodes.OpInstanceGrowDisk(instance_name=instance,
665 disk=disk, amount=amount,
666 wait_for_sync=opts.wait_for_sync)
667 SubmitOrSend(op, opts)
671 def _StartupInstance(name, opts):
672 """Startup instances.
674 This returns the opcode to start an instance, and its decorator will
675 wrap this into a loop starting all desired instances.
677 @param name: the name of the instance to act on
678 @param opts: the command line options selected by the user
679 @return: the opcode needed for the operation
682 op = opcodes.OpInstanceStartup(instance_name=name,
684 ignore_offline_nodes=opts.ignore_offline,
685 no_remember=opts.no_remember,
686 startup_paused=opts.startup_paused)
687 # do not add these parameters to the opcode unless they're defined
689 op.hvparams = opts.hvparams
691 op.beparams = opts.beparams
695 def _RebootInstance(name, opts):
696 """Reboot instance(s).
698 This returns the opcode to reboot an instance, and its decorator
699 will wrap this into a loop rebooting all desired instances.
701 @param name: the name of the instance to act on
702 @param opts: the command line options selected by the user
703 @return: the opcode needed for the operation
706 return opcodes.OpInstanceReboot(instance_name=name,
707 reboot_type=opts.reboot_type,
708 ignore_secondaries=opts.ignore_secondaries,
709 shutdown_timeout=opts.shutdown_timeout)
712 def _ShutdownInstance(name, opts):
713 """Shutdown an instance.
715 This returns the opcode to shutdown an instance, and its decorator
716 will wrap this into a loop shutting down all desired instances.
718 @param name: the name of the instance to act on
719 @param opts: the command line options selected by the user
720 @return: the opcode needed for the operation
723 return opcodes.OpInstanceShutdown(instance_name=name,
724 timeout=opts.timeout,
725 ignore_offline_nodes=opts.ignore_offline,
726 no_remember=opts.no_remember)
729 def ReplaceDisks(opts, args):
730 """Replace the disks of an instance
732 @param opts: the command line options selected by the user
734 @param args: should contain only one element, the instance name
736 @return: the desired exit code
739 new_2ndary = opts.dst_node
740 iallocator = opts.iallocator
741 if opts.disks is None:
745 disks = [int(i) for i in opts.disks.split(",")]
746 except (TypeError, ValueError), err:
747 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
749 cnt = [opts.on_primary, opts.on_secondary, opts.auto,
750 new_2ndary is not None, iallocator is not None].count(True)
752 raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -I"
753 " options must be passed", errors.ECODE_INVAL)
754 elif opts.on_primary:
755 mode = constants.REPLACE_DISK_PRI
756 elif opts.on_secondary:
757 mode = constants.REPLACE_DISK_SEC
759 mode = constants.REPLACE_DISK_AUTO
761 raise errors.OpPrereqError("Cannot specify disks when using automatic"
762 " mode", errors.ECODE_INVAL)
763 elif new_2ndary is not None or iallocator is not None:
765 mode = constants.REPLACE_DISK_CHG
767 op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
768 remote_node=new_2ndary, mode=mode,
769 iallocator=iallocator,
770 early_release=opts.early_release,
771 ignore_ipolicy=opts.ignore_ipolicy)
772 SubmitOrSend(op, opts)
776 def FailoverInstance(opts, args):
777 """Failover an instance.
779 The failover is done by shutting it down on its present node and
780 starting it on the secondary.
782 @param opts: the command line options selected by the user
784 @param args: should contain only one element, the instance name
786 @return: the desired exit code
790 instance_name = args[0]
792 iallocator = opts.iallocator
793 target_node = opts.dst_node
795 if iallocator and target_node:
796 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
797 " node (-n) but not both", errors.ECODE_INVAL)
800 _EnsureInstancesExist(cl, [instance_name])
802 usertext = ("Failover will happen to image %s."
803 " This requires a shutdown of the instance. Continue?" %
805 if not AskUser(usertext):
808 op = opcodes.OpInstanceFailover(instance_name=instance_name,
809 ignore_consistency=opts.ignore_consistency,
810 shutdown_timeout=opts.shutdown_timeout,
811 iallocator=iallocator,
812 target_node=target_node,
813 ignore_ipolicy=opts.ignore_ipolicy)
814 SubmitOrSend(op, opts, cl=cl)
818 def MigrateInstance(opts, args):
819 """Migrate an instance.
821 The migrate is done without shutdown.
823 @param opts: the command line options selected by the user
825 @param args: should contain only one element, the instance name
827 @return: the desired exit code
831 instance_name = args[0]
833 iallocator = opts.iallocator
834 target_node = opts.dst_node
836 if iallocator and target_node:
837 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
838 " node (-n) but not both", errors.ECODE_INVAL)
841 _EnsureInstancesExist(cl, [instance_name])
844 usertext = ("Instance %s will be recovered from a failed migration."
845 " Note that the migration procedure (including cleanup)" %
848 usertext = ("Instance %s will be migrated. Note that migration" %
850 usertext += (" might impact the instance if anything goes wrong"
851 " (e.g. due to bugs in the hypervisor). Continue?")
852 if not AskUser(usertext):
855 # this should be removed once --non-live is deprecated
856 if not opts.live and opts.migration_mode is not None:
857 raise errors.OpPrereqError("Only one of the --non-live and "
858 "--migration-mode options can be passed",
860 if not opts.live: # --non-live passed
861 mode = constants.HT_MIGRATION_NONLIVE
863 mode = opts.migration_mode
865 op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
866 cleanup=opts.cleanup, iallocator=iallocator,
867 target_node=target_node,
868 allow_failover=opts.allow_failover,
869 allow_runtime_changes=opts.allow_runtime_chgs,
870 ignore_ipolicy=opts.ignore_ipolicy)
871 SubmitOrSend(op, cl=cl, opts=opts)
875 def MoveInstance(opts, args):
878 @param opts: the command line options selected by the user
880 @param args: should contain only one element, the instance name
882 @return: the desired exit code
886 instance_name = args[0]
890 usertext = ("Instance %s will be moved."
891 " This requires a shutdown of the instance. Continue?" %
893 if not AskUser(usertext):
896 op = opcodes.OpInstanceMove(instance_name=instance_name,
897 target_node=opts.node,
898 shutdown_timeout=opts.shutdown_timeout,
899 ignore_consistency=opts.ignore_consistency,
900 ignore_ipolicy=opts.ignore_ipolicy)
901 SubmitOrSend(op, opts, cl=cl)
905 def ConnectToInstanceConsole(opts, args):
906 """Connect to the console of an instance.
908 @param opts: the command line options selected by the user
910 @param args: should contain only one element, the instance name
912 @return: the desired exit code
915 instance_name = args[0]
919 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
920 ((console_data, oper_state), ) = \
921 cl.QueryInstances([instance_name], ["console", "oper_state"], False)
923 # Ensure client connection is closed while external commands are run
930 # Instance is running
931 raise errors.OpExecError("Console information for instance %s is"
932 " unavailable" % instance_name)
934 raise errors.OpExecError("Instance %s is not running, can't get console" %
937 return _DoConsole(objects.InstanceConsole.FromDict(console_data),
938 opts.show_command, cluster_name)
941 def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
942 _runcmd_fn=utils.RunCmd):
943 """Acts based on the result of L{opcodes.OpInstanceConsole}.
945 @type console: L{objects.InstanceConsole}
946 @param console: Console object
947 @type show_command: bool
948 @param show_command: Whether to just display commands
949 @type cluster_name: string
950 @param cluster_name: Cluster name as retrieved from master daemon
953 assert console.Validate()
955 if console.kind == constants.CONS_MESSAGE:
956 feedback_fn(console.message)
957 elif console.kind == constants.CONS_VNC:
958 feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
959 " URL <vnc://%s:%s/>",
960 console.instance, console.host, console.port,
961 console.display, console.host, console.port)
962 elif console.kind == constants.CONS_SPICE:
963 feedback_fn("Instance %s has SPICE listening on %s:%s", console.instance,
964 console.host, console.port)
965 elif console.kind == constants.CONS_SSH:
966 # Convert to string if not already one
967 if isinstance(console.command, basestring):
968 cmd = console.command
970 cmd = utils.ShellQuoteArgs(console.command)
972 srun = ssh.SshRunner(cluster_name=cluster_name)
973 ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
974 batch=True, quiet=False, tty=True)
977 feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
979 result = _runcmd_fn(ssh_cmd, interactive=True)
981 logging.error("Console command \"%s\" failed with reason '%s' and"
982 " output %r", result.cmd, result.fail_reason,
984 raise errors.OpExecError("Connection to console of instance %s failed,"
985 " please check cluster configuration" %
988 raise errors.GenericError("Unknown console type '%s'" % console.kind)
990 return constants.EXIT_SUCCESS
993 def _FormatLogicalID(dev_type, logical_id, roman):
994 """Formats the logical_id of a disk.
997 if dev_type == constants.LD_DRBD8:
998 node_a, node_b, port, minor_a, minor_b, key = logical_id
1000 ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
1002 ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
1004 ("port", compat.TryToRoman(port, convert=roman)),
1007 elif dev_type == constants.LD_LV:
1008 vg_name, lv_name = logical_id
1009 data = ["%s/%s" % (vg_name, lv_name)]
1011 data = [str(logical_id)]
1016 def _FormatBlockDevInfo(idx, top_level, dev, roman):
1017 """Show block device information.
1019 This is only used by L{ShowInstanceConfig}, but it's too big to be
1020 left for an inline definition.
1023 @param idx: the index of the current disk
1024 @type top_level: boolean
1025 @param top_level: if this a top-level disk?
1027 @param dev: dictionary with disk information
1028 @type roman: boolean
1029 @param roman: whether to try to use roman integers
1030 @return: a list of either strings, tuples or lists
1031 (which should be formatted at a higher indent level)
1034 def helper(dtype, status):
1035 """Format one line for physical device status.
1038 @param dtype: a constant from the L{constants.LDS_BLOCK} set
1040 @param status: a tuple as returned from L{backend.FindBlockDevice}
1041 @return: the string representing the status
1047 (path, major, minor, syncp, estt, degr, ldisk_status) = status
1049 major_string = "N/A"
1051 major_string = str(compat.TryToRoman(major, convert=roman))
1054 minor_string = "N/A"
1056 minor_string = str(compat.TryToRoman(minor, convert=roman))
1058 txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1059 if dtype in (constants.LD_DRBD8, ):
1060 if syncp is not None:
1061 sync_text = "*RECOVERING* %5.2f%%," % syncp
1063 sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1065 sync_text += " ETA unknown"
1067 sync_text = "in sync"
1069 degr_text = "*DEGRADED*"
1072 if ldisk_status == constants.LDS_FAULTY:
1073 ldisk_text = " *MISSING DISK*"
1074 elif ldisk_status == constants.LDS_UNKNOWN:
1075 ldisk_text = " *UNCERTAIN STATE*"
1078 txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1079 elif dtype == constants.LD_LV:
1080 if ldisk_status == constants.LDS_FAULTY:
1081 ldisk_text = " *FAILED* (failed drive?)"
1089 if dev["iv_name"] is not None:
1090 txt = dev["iv_name"]
1092 txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1094 txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1095 if isinstance(dev["size"], int):
1096 nice_size = utils.FormatUnit(dev["size"], "h")
1098 nice_size = dev["size"]
1099 d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1102 data.append(("access mode", dev["mode"]))
1103 if dev["logical_id"] is not None:
1105 l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1107 l_id = [str(dev["logical_id"])]
1109 data.append(("logical_id", l_id[0]))
1112 elif dev["physical_id"] is not None:
1113 data.append("physical_id:")
1114 data.append([dev["physical_id"]])
1117 data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1120 data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1123 data.append("child devices:")
1124 for c_idx, child in enumerate(dev["children"]):
1125 data.append(_FormatBlockDevInfo(c_idx, False, child, roman))
1130 def _FormatList(buf, data, indent_level):
1131 """Formats a list of data at a given indent level.
1133 If the element of the list is:
1134 - a string, it is simply formatted as is
1135 - a tuple, it will be split into key, value and the all the
1136 values in a list will be aligned all at the same start column
1137 - a list, will be recursively formatted
1140 @param buf: the buffer into which we write the output
1141 @param data: the list to format
1142 @type indent_level: int
1143 @param indent_level: the indent level to format at
1146 max_tlen = max([len(elem[0]) for elem in data
1147 if isinstance(elem, tuple)] or [0])
1149 if isinstance(elem, basestring):
1150 buf.write("%*s%s\n" % (2 * indent_level, "", elem))
1151 elif isinstance(elem, tuple):
1153 spacer = "%*s" % (max_tlen - len(key), "")
1154 buf.write("%*s%s:%s %s\n" % (2 * indent_level, "", key, spacer, value))
1155 elif isinstance(elem, list):
1156 _FormatList(buf, elem, indent_level + 1)
1159 def ShowInstanceConfig(opts, args):
1160 """Compute instance run-time status.
1162 @param opts: the command line options selected by the user
1164 @param args: either an empty list, and then we query all
1165 instances, or should contain a list of instance names
1167 @return: the desired exit code
1170 if not args and not opts.show_all:
1171 ToStderr("No instance selected."
1172 " Please pass in --all if you want to query all instances.\n"
1173 "Note that this can take a long time on a big cluster.")
1175 elif args and opts.show_all:
1176 ToStderr("Cannot use --all if you specify instance names.")
1180 op = opcodes.OpInstanceQueryData(instances=args, static=opts.static,
1181 use_locking=not opts.static)
1182 result = SubmitOpCode(op, opts=opts)
1184 ToStdout("No instances.")
1189 for instance_name in result:
1190 instance = result[instance_name]
1191 buf.write("Instance name: %s\n" % instance["name"])
1192 buf.write("UUID: %s\n" % instance["uuid"])
1193 buf.write("Serial number: %s\n" %
1194 compat.TryToRoman(instance["serial_no"],
1195 convert=opts.roman_integers))
1196 buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1197 buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1198 buf.write("State: configured to be %s" % instance["config_state"])
1199 if instance["run_state"]:
1200 buf.write(", actual state is %s" % instance["run_state"])
1202 ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1203 ## instance["auto_balance"])
1204 buf.write(" Nodes:\n")
1205 buf.write(" - primary: %s\n" % instance["pnode"])
1206 buf.write(" group: %s (UUID %s)\n" %
1207 (instance["pnode_group_name"], instance["pnode_group_uuid"]))
1208 buf.write(" - secondaries: %s\n" %
1209 utils.CommaJoin("%s (group %s, group UUID %s)" %
1210 (name, group_name, group_uuid)
1211 for (name, group_name, group_uuid) in
1212 zip(instance["snodes"],
1213 instance["snodes_group_names"],
1214 instance["snodes_group_uuids"])))
1215 buf.write(" Operating system: %s\n" % instance["os"])
1216 FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1218 if "network_port" in instance:
1219 buf.write(" Allocated network port: %s\n" %
1220 compat.TryToRoman(instance["network_port"],
1221 convert=opts.roman_integers))
1222 buf.write(" Hypervisor: %s\n" % instance["hypervisor"])
1224 # custom VNC console information
1225 vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1227 if vnc_bind_address:
1228 port = instance["network_port"]
1229 display = int(port) - constants.VNC_BASE_PORT
1230 if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1231 vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1234 elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1235 vnc_console_port = ("%s:%s (node %s) (display %s)" %
1236 (vnc_bind_address, port,
1237 instance["pnode"], display))
1239 # vnc bind address is a file
1240 vnc_console_port = "%s:%s" % (instance["pnode"],
1242 buf.write(" - console connection: vnc to %s\n" % vnc_console_port)
1244 FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1246 buf.write(" Hardware:\n")
1247 buf.write(" - VCPUs: %s\n" %
1248 compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1249 convert=opts.roman_integers))
1250 buf.write(" - maxmem: %sMiB\n" %
1251 compat.TryToRoman(instance["be_actual"][constants.BE_MAXMEM],
1252 convert=opts.roman_integers))
1253 buf.write(" - minmem: %sMiB\n" %
1254 compat.TryToRoman(instance["be_actual"][constants.BE_MINMEM],
1255 convert=opts.roman_integers))
1256 # deprecated "memory" value, kept for one version for compatibility
1257 # TODO(ganeti 2.7) remove.
1258 buf.write(" - memory: %sMiB\n" %
1259 compat.TryToRoman(instance["be_actual"][constants.BE_MAXMEM],
1260 convert=opts.roman_integers))
1261 buf.write(" - %s: %s\n" %
1262 (constants.BE_ALWAYS_FAILOVER,
1263 instance["be_actual"][constants.BE_ALWAYS_FAILOVER]))
1264 buf.write(" - NICs:\n")
1265 for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1266 buf.write(" - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1267 (idx, mac, ip, mode, link))
1268 buf.write(" Disk template: %s\n" % instance["disk_template"])
1269 buf.write(" Disks:\n")
1271 for idx, device in enumerate(instance["disks"]):
1272 _FormatList(buf, _FormatBlockDevInfo(idx, True, device,
1273 opts.roman_integers), 2)
1275 ToStdout(buf.getvalue().rstrip("\n"))
1279 def _ConvertNicDiskModifications(mods):
1280 """Converts NIC/disk modifications from CLI to opcode.
1282 When L{opcodes.OpInstanceSetParams} was changed to support adding/removing
1283 disks at arbitrary indices, its parameter format changed. This function
1284 converts legacy requests (e.g. "--net add" or "--disk add:size=4G") to the
1285 newer format and adds support for new-style requests (e.g. "--new 4:add").
1287 @type mods: list of tuples
1288 @param mods: Modifications as given by command line parser
1289 @rtype: list of tuples
1290 @return: Modifications as understood by L{opcodes.OpInstanceSetParams}
1295 for (idx, params) in mods:
1296 if idx == constants.DDM_ADD:
1297 # Add item as last item (legacy interface)
1298 action = constants.DDM_ADD
1300 elif idx == constants.DDM_REMOVE:
1301 # Remove last item (legacy interface)
1302 action = constants.DDM_REMOVE
1305 # Modifications and adding/removing at arbitrary indices
1308 except (TypeError, ValueError):
1309 raise errors.OpPrereqError("Non-numeric index '%s'" % idx,
1312 add = params.pop(constants.DDM_ADD, _MISSING)
1313 remove = params.pop(constants.DDM_REMOVE, _MISSING)
1315 if not (add is _MISSING or remove is _MISSING):
1316 raise errors.OpPrereqError("Cannot add and remove at the same time",
1318 elif add is not _MISSING:
1319 action = constants.DDM_ADD
1320 elif remove is not _MISSING:
1321 action = constants.DDM_REMOVE
1323 action = constants.DDM_MODIFY
1325 assert not (constants.DDMS_VALUES_WITH_MODIFY & set(params.keys()))
1327 if action == constants.DDM_REMOVE and params:
1328 raise errors.OpPrereqError("Not accepting parameters on removal",
1331 result.append((action, idxno, params))
1336 def _ParseDiskSizes(mods):
1337 """Parses disk sizes in parameters.
1340 for (action, _, params) in mods:
1341 if params and constants.IDISK_SIZE in params:
1342 params[constants.IDISK_SIZE] = \
1343 utils.ParseUnit(params[constants.IDISK_SIZE])
1344 elif action == constants.DDM_ADD:
1345 raise errors.OpPrereqError("Missing required parameter 'size'",
1351 def SetInstanceParams(opts, args):
1352 """Modifies an instance.
1354 All parameters take effect only at the next restart of the instance.
1356 @param opts: the command line options selected by the user
1358 @param args: should contain only one element, the instance name
1360 @return: the desired exit code
1363 if not (opts.nics or opts.disks or opts.disk_template or
1364 opts.hvparams or opts.beparams or opts.os or opts.osparams or
1365 opts.offline_inst or opts.online_inst or opts.runtime_mem):
1366 ToStderr("Please give at least one of the parameters.")
1369 for param in opts.beparams:
1370 if isinstance(opts.beparams[param], basestring):
1371 if opts.beparams[param].lower() == "default":
1372 opts.beparams[param] = constants.VALUE_DEFAULT
1374 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT,
1375 allowed_values=[constants.VALUE_DEFAULT])
1377 for param in opts.hvparams:
1378 if isinstance(opts.hvparams[param], basestring):
1379 if opts.hvparams[param].lower() == "default":
1380 opts.hvparams[param] = constants.VALUE_DEFAULT
1382 utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1383 allowed_values=[constants.VALUE_DEFAULT])
1385 nics = _ConvertNicDiskModifications(opts.nics)
1386 disks = _ParseDiskSizes(_ConvertNicDiskModifications(opts.disks))
1388 if (opts.disk_template and
1389 opts.disk_template in constants.DTS_INT_MIRROR and
1391 ToStderr("Changing the disk template to a mirrored one requires"
1392 " specifying a secondary node")
1395 if opts.offline_inst:
1397 elif opts.online_inst:
1402 op = opcodes.OpInstanceSetParams(instance_name=args[0],
1405 disk_template=opts.disk_template,
1406 remote_node=opts.node,
1407 hvparams=opts.hvparams,
1408 beparams=opts.beparams,
1409 runtime_mem=opts.runtime_mem,
1411 osparams=opts.osparams,
1412 force_variant=opts.force_variant,
1414 wait_for_sync=opts.wait_for_sync,
1416 ignore_ipolicy=opts.ignore_ipolicy)
1418 # even if here we process the result, we allow submit only
1419 result = SubmitOrSend(op, opts)
1422 ToStdout("Modified instance %s", args[0])
1423 for param, data in result:
1424 ToStdout(" - %-5s -> %s", param, data)
1425 ToStdout("Please don't forget that most parameters take effect"
1426 " only at the next start of the instance.")
1430 def ChangeGroup(opts, args):
1431 """Moves an instance to another group.
1434 (instance_name, ) = args
1438 op = opcodes.OpInstanceChangeGroup(instance_name=instance_name,
1439 iallocator=opts.iallocator,
1440 target_groups=opts.to,
1441 early_release=opts.early_release)
1442 result = SubmitOrSend(op, opts, cl=cl)
1444 # Keep track of submitted jobs
1445 jex = JobExecutor(cl=cl, opts=opts)
1447 for (status, job_id) in result[constants.JOB_IDS_KEY]:
1448 jex.AddJobId(None, status, job_id)
1450 results = jex.GetResults()
1451 bad_cnt = len([row for row in results if not row[0]])
1453 ToStdout("Instance '%s' changed group successfully.", instance_name)
1454 rcode = constants.EXIT_SUCCESS
1456 ToStdout("There were %s errors while changing group of instance '%s'.",
1457 bad_cnt, instance_name)
1458 rcode = constants.EXIT_FAILURE
1463 # multi-instance selection options
1464 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1465 help="Do not ask for confirmation when more than"
1466 " one instance is affected",
1467 action="store_true", default=False)
1469 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1470 help="Filter by nodes (primary only)",
1471 const=_EXPAND_NODES_PRI, action="store_const")
1473 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1474 help="Filter by nodes (secondary only)",
1475 const=_EXPAND_NODES_SEC, action="store_const")
1477 m_node_opt = cli_option("--node", dest="multi_mode",
1478 help="Filter by nodes (primary and secondary)",
1479 const=_EXPAND_NODES_BOTH, action="store_const")
1481 m_clust_opt = cli_option("--all", dest="multi_mode",
1482 help="Select all instances in the cluster",
1483 const=_EXPAND_CLUSTER, action="store_const")
1485 m_inst_opt = cli_option("--instance", dest="multi_mode",
1486 help="Filter by instance name [default]",
1487 const=_EXPAND_INSTANCES, action="store_const")
1489 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1490 help="Filter by node tag",
1491 const=_EXPAND_NODES_BOTH_BY_TAGS,
1492 action="store_const")
1494 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1495 help="Filter by primary node tag",
1496 const=_EXPAND_NODES_PRI_BY_TAGS,
1497 action="store_const")
1499 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1500 help="Filter by secondary node tag",
1501 const=_EXPAND_NODES_SEC_BY_TAGS,
1502 action="store_const")
1504 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1505 help="Filter by instance tag",
1506 const=_EXPAND_INSTANCES_BY_TAGS,
1507 action="store_const")
1509 # this is defined separately due to readability only
1520 AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1521 "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1522 "Creates and adds a new instance to the cluster"),
1524 BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1526 "Create a bunch of instances based on specs in the file."),
1528 ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1529 [SHOWCMD_OPT, PRIORITY_OPT],
1530 "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1532 FailoverInstance, ARGS_ONE_INSTANCE,
1533 [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1534 DRY_RUN_OPT, PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT,
1535 IGNORE_IPOLICY_OPT],
1536 "[-f] <instance>", "Stops the instance, changes its primary node and"
1537 " (if it was originally running) starts it on the new node"
1538 " (the secondary for mirrored instances or any node"
1539 " for shared storage)."),
1541 MigrateInstance, ARGS_ONE_INSTANCE,
1542 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1543 PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT, ALLOW_FAILOVER_OPT,
1544 IGNORE_IPOLICY_OPT, NORUNTIME_CHGS_OPT, SUBMIT_OPT],
1545 "[-f] <instance>", "Migrate instance to its secondary node"
1546 " (only for mirrored instances)"),
1548 MoveInstance, ARGS_ONE_INSTANCE,
1549 [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1550 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_CONSIST_OPT, IGNORE_IPOLICY_OPT],
1551 "[-f] <instance>", "Move instance to an arbitrary node"
1552 " (only for instances of type file and lv)"),
1554 ShowInstanceConfig, ARGS_MANY_INSTANCES,
1555 [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1556 "[-s] {--all | <instance>...}",
1557 "Show information on the specified instance(s)"),
1559 ListInstances, ARGS_MANY_INSTANCES,
1560 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
1563 "Lists the instances and their status. The available fields can be shown"
1564 " using the \"list-fields\" command (see the man page for details)."
1565 " The default field list is (in order): %s." %
1566 utils.CommaJoin(_LIST_DEF_FIELDS),
1569 ListInstanceFields, [ArgUnknown()],
1570 [NOHDR_OPT, SEP_OPT],
1572 "Lists all available fields for instances"),
1574 ReinstallInstance, [ArgInstance()],
1575 [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1576 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1577 m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1578 SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1579 "[-f] <instance>", "Reinstall a stopped instance"),
1581 RemoveInstance, ARGS_ONE_INSTANCE,
1582 [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1583 DRY_RUN_OPT, PRIORITY_OPT],
1584 "[-f] <instance>", "Shuts down the instance and removes it"),
1587 [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1588 [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1589 "<instance> <new_name>", "Rename the instance"),
1591 ReplaceDisks, ARGS_ONE_INSTANCE,
1592 [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1593 NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1594 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_IPOLICY_OPT],
1595 "[-s|-p|-n NODE|-I NAME] <instance>",
1596 "Replaces all disks for the instance"),
1598 SetInstanceParams, ARGS_ONE_INSTANCE,
1599 [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1600 DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1601 OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT, NWSYNC_OPT, OFFLINE_INST_OPT,
1602 ONLINE_INST_OPT, IGNORE_IPOLICY_OPT, RUNTIME_MEM_OPT],
1603 "<instance>", "Alters the parameters of an instance"),
1605 GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1606 [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1607 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1608 m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1609 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT, NO_REMEMBER_OPT],
1610 "<instance>", "Stops an instance"),
1612 GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1613 [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1614 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1615 m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1616 BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT,
1617 NO_REMEMBER_OPT, STARTUP_PAUSED_OPT],
1618 "<instance>", "Starts an instance"),
1620 GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1621 [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1622 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1623 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1624 m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1625 "<instance>", "Reboots an instance"),
1627 ActivateDisks, ARGS_ONE_INSTANCE,
1628 [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
1629 "<instance>", "Activate an instance's disks"),
1630 "deactivate-disks": (
1631 DeactivateDisks, ARGS_ONE_INSTANCE,
1632 [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1633 "[-f] <instance>", "Deactivate an instance's disks"),
1635 RecreateDisks, ARGS_ONE_INSTANCE,
1636 [SUBMIT_OPT, DISK_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1637 "<instance>", "Recreate an instance's disks"),
1640 [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1641 ArgUnknown(min=1, max=1)],
1642 [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1643 "<instance> <disk> <size>", "Grow an instance's disk"),
1645 ChangeGroup, ARGS_ONE_INSTANCE,
1646 [TO_GROUP_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT, PRIORITY_OPT, SUBMIT_OPT],
1647 "[-I <iallocator>] [--to <group>]", "Change group of instance"),
1649 ListTags, ARGS_ONE_INSTANCE, [],
1650 "<instance_name>", "List the tags of the given instance"),
1652 AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1653 [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
1654 "<instance_name> tag...", "Add tags to the given instance"),
1656 RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1657 [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
1658 "<instance_name> tag...", "Remove tags from given instance"),
1661 #: dictionary with aliases for commands
1670 return GenericMain(commands, aliases=aliases,
1671 override={"tag_type": constants.TAG_INSTANCE},
1672 env_override=_ENV_OVERRIDE)