4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 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
32 from cStringIO import StringIO
34 from ganeti.cli import *
35 from ganeti import opcodes
36 from ganeti import constants
37 from ganeti import compat
38 from ganeti import utils
39 from ganeti import errors
40 from ganeti import netutils
41 from ganeti import ssh
42 from ganeti import objects
46 _EXPAND_CLUSTER = "cluster"
47 _EXPAND_NODES_BOTH = "nodes"
48 _EXPAND_NODES_PRI = "nodes-pri"
49 _EXPAND_NODES_SEC = "nodes-sec"
50 _EXPAND_NODES_BOTH_BY_TAGS = "nodes-by-tags"
51 _EXPAND_NODES_PRI_BY_TAGS = "nodes-pri-by-tags"
52 _EXPAND_NODES_SEC_BY_TAGS = "nodes-sec-by-tags"
53 _EXPAND_INSTANCES = "instances"
54 _EXPAND_INSTANCES_BY_TAGS = "instances-by-tags"
56 _EXPAND_NODES_TAGS_MODES = frozenset([
57 _EXPAND_NODES_BOTH_BY_TAGS,
58 _EXPAND_NODES_PRI_BY_TAGS,
59 _EXPAND_NODES_SEC_BY_TAGS,
63 #: default list of options for L{ListInstances}
65 "name", "hypervisor", "os", "pnode", "status", "oper_ram",
70 _ENV_OVERRIDE = frozenset(["list"])
73 def _ExpandMultiNames(mode, names, client=None):
74 """Expand the given names using the passed mode.
76 For _EXPAND_CLUSTER, all instances will be returned. For
77 _EXPAND_NODES_PRI/SEC, all instances having those nodes as
78 primary/secondary will be returned. For _EXPAND_NODES_BOTH, all
79 instances having those nodes as either primary or secondary will be
80 returned. For _EXPAND_INSTANCES, the given instances will be
83 @param mode: one of L{_EXPAND_CLUSTER}, L{_EXPAND_NODES_BOTH},
84 L{_EXPAND_NODES_PRI}, L{_EXPAND_NODES_SEC} or
86 @param names: a list of names; for cluster, it must be empty,
87 and for node and instance it must be a list of valid item
88 names (short names are valid as usual, e.g. node1 instead of
91 @return: the list of names after the expansion
92 @raise errors.ProgrammerError: for unknown selection type
93 @raise errors.OpPrereqError: for invalid input parameters
96 # pylint: disable=W0142
100 if mode == _EXPAND_CLUSTER:
102 raise errors.OpPrereqError("Cluster filter mode takes no arguments",
104 idata = client.QueryInstances([], ["name"], False)
105 inames = [row[0] for row in idata]
107 elif (mode in _EXPAND_NODES_TAGS_MODES or
108 mode in (_EXPAND_NODES_BOTH, _EXPAND_NODES_PRI, _EXPAND_NODES_SEC)):
109 if mode in _EXPAND_NODES_TAGS_MODES:
111 raise errors.OpPrereqError("No node tags passed", errors.ECODE_INVAL)
112 ndata = client.QueryNodes([], ["name", "pinst_list",
113 "sinst_list", "tags"], False)
114 ndata = [row for row in ndata if set(row[3]).intersection(names)]
117 raise errors.OpPrereqError("No node names passed", errors.ECODE_INVAL)
118 ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
121 ipri = [row[1] for row in ndata]
122 pri_names = list(itertools.chain(*ipri))
123 isec = [row[2] for row in ndata]
124 sec_names = list(itertools.chain(*isec))
125 if mode in (_EXPAND_NODES_BOTH, _EXPAND_NODES_BOTH_BY_TAGS):
126 inames = pri_names + sec_names
127 elif mode in (_EXPAND_NODES_PRI, _EXPAND_NODES_PRI_BY_TAGS):
129 elif mode in (_EXPAND_NODES_SEC, _EXPAND_NODES_SEC_BY_TAGS):
132 raise errors.ProgrammerError("Unhandled shutdown type")
133 elif mode == _EXPAND_INSTANCES:
135 raise errors.OpPrereqError("No instance names passed",
137 idata = client.QueryInstances(names, ["name"], False)
138 inames = [row[0] for row in idata]
139 elif mode == _EXPAND_INSTANCES_BY_TAGS:
141 raise errors.OpPrereqError("No instance tags passed",
143 idata = client.QueryInstances([], ["name", "tags"], False)
144 inames = [row[0] for row in idata if set(row[1]).intersection(names)]
146 raise errors.OpPrereqError("Unknown mode '%s'" % mode, errors.ECODE_INVAL)
151 def _EnsureInstancesExist(client, names):
152 """Check for and ensure the given instance names exist.
154 This function will raise an OpPrereqError in case they don't
155 exist. Otherwise it will exit cleanly.
157 @type client: L{ganeti.luxi.Client}
158 @param client: the client to use for the query
160 @param names: the list of instance names to query
161 @raise errors.OpPrereqError: in case any instance is missing
164 # TODO: change LUInstanceQuery to that it actually returns None
165 # instead of raising an exception, or devise a better mechanism
166 result = client.QueryInstances(names, ["name"], False)
167 for orig_name, row in zip(names, result):
169 raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name,
173 def GenericManyOps(operation, fn):
174 """Generic multi-instance operations.
176 The will return a wrapper that processes the options and arguments
177 given, and uses the passed function to build the opcode needed for
178 the specific operation. Thus all the generic loop/confirmation code
179 is abstracted into this function.
182 def realfn(opts, args):
183 if opts.multi_mode is None:
184 opts.multi_mode = _EXPAND_INSTANCES
186 inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
188 if opts.multi_mode == _EXPAND_CLUSTER:
189 ToStdout("Cluster is empty, no instances to shutdown")
191 raise errors.OpPrereqError("Selection filter does not match"
192 " any instances", errors.ECODE_INVAL)
193 multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1
194 if not (opts.force_multi or not multi_on
195 or ConfirmOperation(inames, "instances", operation)):
197 jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
200 jex.QueueJob(name, op)
201 results = jex.WaitOrShow(not opts.submit_only)
202 rcode = compat.all(row[0] for row in results)
203 return int(not rcode)
207 def ListInstances(opts, args):
208 """List instances and their properties.
210 @param opts: the command line options selected by the user
212 @param args: should be an empty list
214 @return: the desired exit code
217 selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
219 fmtoverride = dict.fromkeys(["tags", "disk.sizes", "nic.macs", "nic.ips",
220 "nic.modes", "nic.links", "nic.bridges",
221 "snodes", "snodes.group", "snodes.group.uuid"],
222 (lambda value: ",".join(str(item)
226 return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units,
227 opts.separator, not opts.no_headers,
228 format_override=fmtoverride, verbose=opts.verbose,
229 force_filter=opts.force_filter)
232 def ListInstanceFields(opts, args):
233 """List instance fields.
235 @param opts: the command line options selected by the user
237 @param args: fields to list, or empty for all
239 @return: the desired exit code
242 return GenericListFields(constants.QR_INSTANCE, args, opts.separator,
246 def AddInstance(opts, args):
247 """Add an instance to the cluster.
249 This is just a wrapper over GenericInstanceCreate.
252 return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
255 def BatchCreate(opts, args):
256 """Create instances using a definition file.
258 This function reads a json file with instances defined
262 "disk_size": [20480],
268 "primary_node": "firstnode",
269 "secondary_node": "secondnode",
270 "iallocator": "dumb"}
273 Note that I{primary_node} and I{secondary_node} have precedence over
276 @param opts: the command line options selected by the user
278 @param args: should contain one element, the json filename
280 @return: the desired exit code
283 _DEFAULT_SPECS = {"disk_size": [20 * 1024],
286 "primary_node": None,
287 "secondary_node": None,
294 "file_storage_dir": None,
295 "force_variant": False,
296 "file_driver": "loop"}
298 def _PopulateWithDefaults(spec):
299 """Returns a new hash combined with default values."""
300 mydict = _DEFAULT_SPECS.copy()
305 """Validate the instance specs."""
306 # Validate fields required under any circumstances
307 for required_field in ("os", "template"):
308 if required_field not in spec:
309 raise errors.OpPrereqError('Required field "%s" is missing.' %
310 required_field, errors.ECODE_INVAL)
311 # Validate special fields
312 if spec["primary_node"] is not None:
313 if (spec["template"] in constants.DTS_INT_MIRROR and
314 spec["secondary_node"] is None):
315 raise errors.OpPrereqError("Template requires secondary node, but"
316 " there was no secondary provided.",
318 elif spec["iallocator"] is None:
319 raise errors.OpPrereqError("You have to provide at least a primary_node"
320 " or an iallocator.",
323 if (spec["hvparams"] and
324 not isinstance(spec["hvparams"], dict)):
325 raise errors.OpPrereqError("Hypervisor parameters must be a dict.",
328 json_filename = args[0]
330 instance_data = simplejson.loads(utils.ReadFile(json_filename))
331 except Exception, err: # pylint: disable=W0703
332 ToStderr("Can't parse the instance definition file: %s" % str(err))
335 if not isinstance(instance_data, dict):
336 ToStderr("The instance definition file is not in dict format.")
339 jex = JobExecutor(opts=opts)
341 # Iterate over the instances and do:
342 # * Populate the specs with default value
343 # * Validate the instance specs
344 i_names = utils.NiceSort(instance_data.keys()) # pylint: disable=E1103
346 specs = instance_data[name]
347 specs = _PopulateWithDefaults(specs)
350 hypervisor = specs["hypervisor"]
351 hvparams = specs["hvparams"]
354 for elem in specs["disk_size"]:
356 size = utils.ParseUnit(elem)
357 except (TypeError, ValueError), err:
358 raise errors.OpPrereqError("Invalid disk size '%s' for"
360 (elem, name, err), errors.ECODE_INVAL)
361 disks.append({"size": size})
363 utils.ForceDictType(specs["backend"], constants.BES_PARAMETER_COMPAT)
364 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
367 for field in constants.INIC_PARAMS:
371 tmp_nics[0][field] = specs[field]
373 if specs["nics"] is not None and tmp_nics:
374 raise errors.OpPrereqError("'nics' list incompatible with using"
375 " individual nic fields as well",
377 elif specs["nics"] is not None:
378 tmp_nics = specs["nics"]
382 op = opcodes.OpInstanceCreate(instance_name=name,
384 disk_template=specs["template"],
385 mode=constants.INSTANCE_CREATE,
387 force_variant=specs["force_variant"],
388 pnode=specs["primary_node"],
389 snode=specs["secondary_node"],
391 start=specs["start"],
392 ip_check=specs["ip_check"],
393 name_check=specs["name_check"],
395 iallocator=specs["iallocator"],
396 hypervisor=hypervisor,
398 beparams=specs["backend"],
399 file_storage_dir=specs["file_storage_dir"],
400 file_driver=specs["file_driver"])
402 jex.QueueJob(name, op)
403 # we never want to wait, just show the submitted job IDs
404 jex.WaitOrShow(False)
409 def ReinstallInstance(opts, args):
410 """Reinstall an instance.
412 @param opts: the command line options selected by the user
414 @param args: should contain only one element, the name of the
415 instance to be reinstalled
417 @return: the desired exit code
420 # first, compute the desired name list
421 if opts.multi_mode is None:
422 opts.multi_mode = _EXPAND_INSTANCES
424 inames = _ExpandMultiNames(opts.multi_mode, args)
426 raise errors.OpPrereqError("Selection filter does not match any instances",
429 # second, if requested, ask for an OS
430 if opts.select_os is True:
431 op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[])
432 result = SubmitOpCode(op, opts=opts)
435 ToStdout("Can't get the OS list")
438 ToStdout("Available OS templates:")
441 for (name, variants) in result:
442 for entry in CalculateOSNames(name, variants):
443 ToStdout("%3s: %s", number, entry)
444 choices.append(("%s" % number, entry, entry))
447 choices.append(("x", "exit", "Exit gnt-instance reinstall"))
448 selected = AskUser("Enter OS template number (or x to abort):",
451 if selected == "exit":
452 ToStderr("User aborted reinstall, exiting")
456 os_msg = "change the OS to '%s'" % selected
459 if opts.os is not None:
460 os_msg = "change the OS to '%s'" % os_name
462 os_msg = "keep the same OS"
464 # third, get confirmation: multi-reinstall requires --force-multi,
465 # single-reinstall either --force or --force-multi (--force-multi is
466 # a stronger --force)
467 multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1
469 warn_msg = ("Note: this will remove *all* data for the"
470 " below instances! It will %s.\n" % os_msg)
471 if not (opts.force_multi or
472 ConfirmOperation(inames, "instances", "reinstall", extra=warn_msg)):
475 if not (opts.force or opts.force_multi):
476 usertext = ("This will reinstall the instance '%s' (and %s) which"
477 " removes all data. Continue?") % (inames[0], os_msg)
478 if not AskUser(usertext):
481 jex = JobExecutor(verbose=multi_on, opts=opts)
482 for instance_name in inames:
483 op = opcodes.OpInstanceReinstall(instance_name=instance_name,
485 force_variant=opts.force_variant,
486 osparams=opts.osparams)
487 jex.QueueJob(instance_name, op)
489 jex.WaitOrShow(not opts.submit_only)
493 def RemoveInstance(opts, args):
494 """Remove an instance.
496 @param opts: the command line options selected by the user
498 @param args: should contain only one element, the name of
499 the instance to be removed
501 @return: the desired exit code
504 instance_name = args[0]
509 _EnsureInstancesExist(cl, [instance_name])
511 usertext = ("This will remove the volumes of the instance %s"
512 " (including mirrors), thus removing all the data"
513 " of the instance. Continue?") % instance_name
514 if not AskUser(usertext):
517 op = opcodes.OpInstanceRemove(instance_name=instance_name,
518 ignore_failures=opts.ignore_failures,
519 shutdown_timeout=opts.shutdown_timeout)
520 SubmitOrSend(op, opts, cl=cl)
524 def RenameInstance(opts, args):
525 """Rename an instance.
527 @param opts: the command line options selected by the user
529 @param args: should contain two elements, the old and the
532 @return: the desired exit code
535 if not opts.name_check:
536 if not AskUser("As you disabled the check of the DNS entry, please verify"
537 " that '%s' is a FQDN. Continue?" % args[1]):
540 op = opcodes.OpInstanceRename(instance_name=args[0],
542 ip_check=opts.ip_check,
543 name_check=opts.name_check)
544 result = SubmitOrSend(op, opts)
547 ToStdout("Instance '%s' renamed to '%s'", args[0], result)
552 def ActivateDisks(opts, args):
553 """Activate an instance's disks.
555 This serves two purposes:
556 - it allows (as long as the instance is not running)
557 mounting the disks and modifying them from the node
558 - it repairs inactive secondary drbds
560 @param opts: the command line options selected by the user
562 @param args: should contain only one element, the instance name
564 @return: the desired exit code
567 instance_name = args[0]
568 op = opcodes.OpInstanceActivateDisks(instance_name=instance_name,
569 ignore_size=opts.ignore_size,
570 wait_for_sync=opts.wait_for_sync)
571 disks_info = SubmitOrSend(op, opts)
572 for host, iname, nname in disks_info:
573 ToStdout("%s:%s:%s", host, iname, nname)
577 def DeactivateDisks(opts, args):
578 """Deactivate an instance's disks.
580 This function takes the instance name, looks for its primary node
581 and the tries to shutdown its block devices on that node.
583 @param opts: the command line options selected by the user
585 @param args: should contain only one element, the instance name
587 @return: the desired exit code
590 instance_name = args[0]
591 op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name,
593 SubmitOrSend(op, opts)
597 def RecreateDisks(opts, args):
598 """Recreate an instance's disks.
600 @param opts: the command line options selected by the user
602 @param args: should contain only one element, the instance name
604 @return: the desired exit code
607 instance_name = args[0]
612 for didx, ddict in opts.disks:
615 if not ht.TDict(ddict):
616 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
617 raise errors.OpPrereqError(msg)
619 if constants.IDISK_SIZE in ddict:
621 ddict[constants.IDISK_SIZE] = \
622 utils.ParseUnit(ddict[constants.IDISK_SIZE])
623 except ValueError, err:
624 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
627 disks.append((didx, ddict))
629 # TODO: Verify modifyable parameters (already done in
630 # LUInstanceRecreateDisks, but it'd be nice to have in the client)
634 msg = "At most one of either --nodes or --iallocator can be passed"
635 raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
636 pnode, snode = SplitNodeOption(opts.node)
638 if snode is not None:
643 op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
644 disks=disks, nodes=nodes,
645 iallocator=opts.iallocator)
646 SubmitOrSend(op, opts)
651 def GrowDisk(opts, args):
652 """Grow an instance's disks.
654 @param opts: the command line options selected by the user
656 @param args: should contain three elements, the target instance name,
657 the target disk id, and the target growth
659 @return: the desired exit code
666 except (TypeError, ValueError), err:
667 raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
670 amount = utils.ParseUnit(args[2])
671 except errors.UnitParseError:
672 raise errors.OpPrereqError("Can't parse the given amount '%s'" % args[2],
674 op = opcodes.OpInstanceGrowDisk(instance_name=instance,
675 disk=disk, amount=amount,
676 wait_for_sync=opts.wait_for_sync,
677 absolute=opts.absolute)
678 SubmitOrSend(op, opts)
682 def _StartupInstance(name, opts):
683 """Startup instances.
685 This returns the opcode to start an instance, and its decorator will
686 wrap this into a loop starting all desired instances.
688 @param name: the name of the instance to act on
689 @param opts: the command line options selected by the user
690 @return: the opcode needed for the operation
693 op = opcodes.OpInstanceStartup(instance_name=name,
695 ignore_offline_nodes=opts.ignore_offline,
696 no_remember=opts.no_remember,
697 startup_paused=opts.startup_paused)
698 # do not add these parameters to the opcode unless they're defined
700 op.hvparams = opts.hvparams
702 op.beparams = opts.beparams
706 def _RebootInstance(name, opts):
707 """Reboot instance(s).
709 This returns the opcode to reboot an instance, and its decorator
710 will wrap this into a loop rebooting all desired instances.
712 @param name: the name of the instance to act on
713 @param opts: the command line options selected by the user
714 @return: the opcode needed for the operation
717 return opcodes.OpInstanceReboot(instance_name=name,
718 reboot_type=opts.reboot_type,
719 ignore_secondaries=opts.ignore_secondaries,
720 shutdown_timeout=opts.shutdown_timeout)
723 def _ShutdownInstance(name, opts):
724 """Shutdown an instance.
726 This returns the opcode to shutdown an instance, and its decorator
727 will wrap this into a loop shutting down all desired instances.
729 @param name: the name of the instance to act on
730 @param opts: the command line options selected by the user
731 @return: the opcode needed for the operation
734 return opcodes.OpInstanceShutdown(instance_name=name,
735 timeout=opts.timeout,
736 ignore_offline_nodes=opts.ignore_offline,
737 no_remember=opts.no_remember)
740 def ReplaceDisks(opts, args):
741 """Replace the disks of an instance
743 @param opts: the command line options selected by the user
745 @param args: should contain only one element, the instance name
747 @return: the desired exit code
750 new_2ndary = opts.dst_node
751 iallocator = opts.iallocator
752 if opts.disks is None:
756 disks = [int(i) for i in opts.disks.split(",")]
757 except (TypeError, ValueError), err:
758 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
760 cnt = [opts.on_primary, opts.on_secondary, opts.auto,
761 new_2ndary is not None, iallocator is not None].count(True)
763 raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -I"
764 " options must be passed", errors.ECODE_INVAL)
765 elif opts.on_primary:
766 mode = constants.REPLACE_DISK_PRI
767 elif opts.on_secondary:
768 mode = constants.REPLACE_DISK_SEC
770 mode = constants.REPLACE_DISK_AUTO
772 raise errors.OpPrereqError("Cannot specify disks when using automatic"
773 " mode", errors.ECODE_INVAL)
774 elif new_2ndary is not None or iallocator is not None:
776 mode = constants.REPLACE_DISK_CHG
778 op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
779 remote_node=new_2ndary, mode=mode,
780 iallocator=iallocator,
781 early_release=opts.early_release,
782 ignore_ipolicy=opts.ignore_ipolicy)
783 SubmitOrSend(op, opts)
787 def FailoverInstance(opts, args):
788 """Failover an instance.
790 The failover is done by shutting it down on its present node and
791 starting it on the secondary.
793 @param opts: the command line options selected by the user
795 @param args: should contain only one element, the instance name
797 @return: the desired exit code
801 instance_name = args[0]
803 iallocator = opts.iallocator
804 target_node = opts.dst_node
806 if iallocator and target_node:
807 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
808 " node (-n) but not both", errors.ECODE_INVAL)
811 _EnsureInstancesExist(cl, [instance_name])
813 usertext = ("Failover will happen to image %s."
814 " This requires a shutdown of the instance. Continue?" %
816 if not AskUser(usertext):
819 op = opcodes.OpInstanceFailover(instance_name=instance_name,
820 ignore_consistency=opts.ignore_consistency,
821 shutdown_timeout=opts.shutdown_timeout,
822 iallocator=iallocator,
823 target_node=target_node,
824 ignore_ipolicy=opts.ignore_ipolicy)
825 SubmitOrSend(op, opts, cl=cl)
829 def MigrateInstance(opts, args):
830 """Migrate an instance.
832 The migrate is done without shutdown.
834 @param opts: the command line options selected by the user
836 @param args: should contain only one element, the instance name
838 @return: the desired exit code
842 instance_name = args[0]
844 iallocator = opts.iallocator
845 target_node = opts.dst_node
847 if iallocator and target_node:
848 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
849 " node (-n) but not both", errors.ECODE_INVAL)
852 _EnsureInstancesExist(cl, [instance_name])
855 usertext = ("Instance %s will be recovered from a failed migration."
856 " Note that the migration procedure (including cleanup)" %
859 usertext = ("Instance %s will be migrated. Note that migration" %
861 usertext += (" might impact the instance if anything goes wrong"
862 " (e.g. due to bugs in the hypervisor). Continue?")
863 if not AskUser(usertext):
866 # this should be removed once --non-live is deprecated
867 if not opts.live and opts.migration_mode is not None:
868 raise errors.OpPrereqError("Only one of the --non-live and "
869 "--migration-mode options can be passed",
871 if not opts.live: # --non-live passed
872 mode = constants.HT_MIGRATION_NONLIVE
874 mode = opts.migration_mode
876 op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
877 cleanup=opts.cleanup, iallocator=iallocator,
878 target_node=target_node,
879 allow_failover=opts.allow_failover,
880 allow_runtime_changes=opts.allow_runtime_chgs,
881 ignore_ipolicy=opts.ignore_ipolicy)
882 SubmitOrSend(op, cl=cl, opts=opts)
886 def MoveInstance(opts, args):
889 @param opts: the command line options selected by the user
891 @param args: should contain only one element, the instance name
893 @return: the desired exit code
897 instance_name = args[0]
901 usertext = ("Instance %s will be moved."
902 " This requires a shutdown of the instance. Continue?" %
904 if not AskUser(usertext):
907 op = opcodes.OpInstanceMove(instance_name=instance_name,
908 target_node=opts.node,
909 shutdown_timeout=opts.shutdown_timeout,
910 ignore_consistency=opts.ignore_consistency,
911 ignore_ipolicy=opts.ignore_ipolicy)
912 SubmitOrSend(op, opts, cl=cl)
916 def ConnectToInstanceConsole(opts, args):
917 """Connect to the console of an instance.
919 @param opts: the command line options selected by the user
921 @param args: should contain only one element, the instance name
923 @return: the desired exit code
926 instance_name = args[0]
930 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
931 ((console_data, oper_state), ) = \
932 cl.QueryInstances([instance_name], ["console", "oper_state"], False)
934 # Ensure client connection is closed while external commands are run
941 # Instance is running
942 raise errors.OpExecError("Console information for instance %s is"
943 " unavailable" % instance_name)
945 raise errors.OpExecError("Instance %s is not running, can't get console" %
948 return _DoConsole(objects.InstanceConsole.FromDict(console_data),
949 opts.show_command, cluster_name)
952 def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
953 _runcmd_fn=utils.RunCmd):
954 """Acts based on the result of L{opcodes.OpInstanceConsole}.
956 @type console: L{objects.InstanceConsole}
957 @param console: Console object
958 @type show_command: bool
959 @param show_command: Whether to just display commands
960 @type cluster_name: string
961 @param cluster_name: Cluster name as retrieved from master daemon
964 assert console.Validate()
966 if console.kind == constants.CONS_MESSAGE:
967 feedback_fn(console.message)
968 elif console.kind == constants.CONS_VNC:
969 feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
970 " URL <vnc://%s:%s/>",
971 console.instance, console.host, console.port,
972 console.display, console.host, console.port)
973 elif console.kind == constants.CONS_SPICE:
974 feedback_fn("Instance %s has SPICE listening on %s:%s", console.instance,
975 console.host, console.port)
976 elif console.kind == constants.CONS_SSH:
977 # Convert to string if not already one
978 if isinstance(console.command, basestring):
979 cmd = console.command
981 cmd = utils.ShellQuoteArgs(console.command)
983 srun = ssh.SshRunner(cluster_name=cluster_name)
984 ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
985 batch=True, quiet=False, tty=True)
988 feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
990 result = _runcmd_fn(ssh_cmd, interactive=True)
992 logging.error("Console command \"%s\" failed with reason '%s' and"
993 " output %r", result.cmd, result.fail_reason,
995 raise errors.OpExecError("Connection to console of instance %s failed,"
996 " please check cluster configuration" %
999 raise errors.GenericError("Unknown console type '%s'" % console.kind)
1001 return constants.EXIT_SUCCESS
1004 def _FormatLogicalID(dev_type, logical_id, roman):
1005 """Formats the logical_id of a disk.
1008 if dev_type == constants.LD_DRBD8:
1009 node_a, node_b, port, minor_a, minor_b, key = logical_id
1011 ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
1013 ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
1015 ("port", compat.TryToRoman(port, convert=roman)),
1018 elif dev_type == constants.LD_LV:
1019 vg_name, lv_name = logical_id
1020 data = ["%s/%s" % (vg_name, lv_name)]
1022 data = [str(logical_id)]
1027 def _FormatBlockDevInfo(idx, top_level, dev, roman):
1028 """Show block device information.
1030 This is only used by L{ShowInstanceConfig}, but it's too big to be
1031 left for an inline definition.
1034 @param idx: the index of the current disk
1035 @type top_level: boolean
1036 @param top_level: if this a top-level disk?
1038 @param dev: dictionary with disk information
1039 @type roman: boolean
1040 @param roman: whether to try to use roman integers
1041 @return: a list of either strings, tuples or lists
1042 (which should be formatted at a higher indent level)
1045 def helper(dtype, status):
1046 """Format one line for physical device status.
1049 @param dtype: a constant from the L{constants.LDS_BLOCK} set
1051 @param status: a tuple as returned from L{backend.FindBlockDevice}
1052 @return: the string representing the status
1058 (path, major, minor, syncp, estt, degr, ldisk_status) = status
1060 major_string = "N/A"
1062 major_string = str(compat.TryToRoman(major, convert=roman))
1065 minor_string = "N/A"
1067 minor_string = str(compat.TryToRoman(minor, convert=roman))
1069 txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1070 if dtype in (constants.LD_DRBD8, ):
1071 if syncp is not None:
1072 sync_text = "*RECOVERING* %5.2f%%," % syncp
1074 sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1076 sync_text += " ETA unknown"
1078 sync_text = "in sync"
1080 degr_text = "*DEGRADED*"
1083 if ldisk_status == constants.LDS_FAULTY:
1084 ldisk_text = " *MISSING DISK*"
1085 elif ldisk_status == constants.LDS_UNKNOWN:
1086 ldisk_text = " *UNCERTAIN STATE*"
1089 txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1090 elif dtype == constants.LD_LV:
1091 if ldisk_status == constants.LDS_FAULTY:
1092 ldisk_text = " *FAILED* (failed drive?)"
1100 if dev["iv_name"] is not None:
1101 txt = dev["iv_name"]
1103 txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1105 txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1106 if isinstance(dev["size"], int):
1107 nice_size = utils.FormatUnit(dev["size"], "h")
1109 nice_size = dev["size"]
1110 d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1113 data.append(("access mode", dev["mode"]))
1114 if dev["logical_id"] is not None:
1116 l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1118 l_id = [str(dev["logical_id"])]
1120 data.append(("logical_id", l_id[0]))
1123 elif dev["physical_id"] is not None:
1124 data.append("physical_id:")
1125 data.append([dev["physical_id"]])
1128 data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1131 data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1134 data.append("child devices:")
1135 for c_idx, child in enumerate(dev["children"]):
1136 data.append(_FormatBlockDevInfo(c_idx, False, child, roman))
1141 def _FormatList(buf, data, indent_level):
1142 """Formats a list of data at a given indent level.
1144 If the element of the list is:
1145 - a string, it is simply formatted as is
1146 - a tuple, it will be split into key, value and the all the
1147 values in a list will be aligned all at the same start column
1148 - a list, will be recursively formatted
1151 @param buf: the buffer into which we write the output
1152 @param data: the list to format
1153 @type indent_level: int
1154 @param indent_level: the indent level to format at
1157 max_tlen = max([len(elem[0]) for elem in data
1158 if isinstance(elem, tuple)] or [0])
1160 if isinstance(elem, basestring):
1161 buf.write("%*s%s\n" % (2 * indent_level, "", elem))
1162 elif isinstance(elem, tuple):
1164 spacer = "%*s" % (max_tlen - len(key), "")
1165 buf.write("%*s%s:%s %s\n" % (2 * indent_level, "", key, spacer, value))
1166 elif isinstance(elem, list):
1167 _FormatList(buf, elem, indent_level + 1)
1170 def ShowInstanceConfig(opts, args):
1171 """Compute instance run-time status.
1173 @param opts: the command line options selected by the user
1175 @param args: either an empty list, and then we query all
1176 instances, or should contain a list of instance names
1178 @return: the desired exit code
1181 if not args and not opts.show_all:
1182 ToStderr("No instance selected."
1183 " Please pass in --all if you want to query all instances.\n"
1184 "Note that this can take a long time on a big cluster.")
1186 elif args and opts.show_all:
1187 ToStderr("Cannot use --all if you specify instance names.")
1191 op = opcodes.OpInstanceQueryData(instances=args, static=opts.static,
1192 use_locking=not opts.static)
1193 result = SubmitOpCode(op, opts=opts)
1195 ToStdout("No instances.")
1200 for instance_name in result:
1201 instance = result[instance_name]
1202 buf.write("Instance name: %s\n" % instance["name"])
1203 buf.write("UUID: %s\n" % instance["uuid"])
1204 buf.write("Serial number: %s\n" %
1205 compat.TryToRoman(instance["serial_no"],
1206 convert=opts.roman_integers))
1207 buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1208 buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1209 buf.write("State: configured to be %s" % instance["config_state"])
1210 if instance["run_state"]:
1211 buf.write(", actual state is %s" % instance["run_state"])
1213 ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1214 ## instance["auto_balance"])
1215 buf.write(" Nodes:\n")
1216 buf.write(" - primary: %s\n" % instance["pnode"])
1217 buf.write(" group: %s (UUID %s)\n" %
1218 (instance["pnode_group_name"], instance["pnode_group_uuid"]))
1219 buf.write(" - secondaries: %s\n" %
1220 utils.CommaJoin("%s (group %s, group UUID %s)" %
1221 (name, group_name, group_uuid)
1222 for (name, group_name, group_uuid) in
1223 zip(instance["snodes"],
1224 instance["snodes_group_names"],
1225 instance["snodes_group_uuids"])))
1226 buf.write(" Operating system: %s\n" % instance["os"])
1227 FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1229 if "network_port" in instance:
1230 buf.write(" Allocated network port: %s\n" %
1231 compat.TryToRoman(instance["network_port"],
1232 convert=opts.roman_integers))
1233 buf.write(" Hypervisor: %s\n" % instance["hypervisor"])
1235 # custom VNC console information
1236 vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1238 if vnc_bind_address:
1239 port = instance["network_port"]
1240 display = int(port) - constants.VNC_BASE_PORT
1241 if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1242 vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1245 elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1246 vnc_console_port = ("%s:%s (node %s) (display %s)" %
1247 (vnc_bind_address, port,
1248 instance["pnode"], display))
1250 # vnc bind address is a file
1251 vnc_console_port = "%s:%s" % (instance["pnode"],
1253 buf.write(" - console connection: vnc to %s\n" % vnc_console_port)
1255 FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1257 buf.write(" Hardware:\n")
1258 # deprecated "memory" value, kept for one version for compatibility
1259 # TODO(ganeti 2.7) remove.
1260 be_actual = copy.deepcopy(instance["be_actual"])
1261 be_actual["memory"] = be_actual[constants.BE_MAXMEM]
1262 FormatParameterDict(buf, instance["be_instance"], be_actual, level=2)
1263 # TODO(ganeti 2.7) rework the NICs as well
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)
1314 modify = params.pop(constants.DDM_MODIFY, _MISSING)
1316 if modify is _MISSING:
1317 if not (add is _MISSING or remove is _MISSING):
1318 raise errors.OpPrereqError("Cannot add and remove at the same time",
1320 elif add is not _MISSING:
1321 action = constants.DDM_ADD
1322 elif remove is not _MISSING:
1323 action = constants.DDM_REMOVE
1325 action = constants.DDM_MODIFY
1328 if add is _MISSING and remove is _MISSING:
1329 action = constants.DDM_MODIFY
1331 raise errors.OpPrereqError("Cannot modify and add/remove at the"
1332 " same time", errors.ECODE_INVAL)
1334 assert not (constants.DDMS_VALUES_WITH_MODIFY & set(params.keys()))
1336 if action == constants.DDM_REMOVE and params:
1337 raise errors.OpPrereqError("Not accepting parameters on removal",
1340 result.append((action, idxno, params))
1345 def _ParseDiskSizes(mods):
1346 """Parses disk sizes in parameters.
1349 for (action, _, params) in mods:
1350 if params and constants.IDISK_SIZE in params:
1351 params[constants.IDISK_SIZE] = \
1352 utils.ParseUnit(params[constants.IDISK_SIZE])
1353 elif action == constants.DDM_ADD:
1354 raise errors.OpPrereqError("Missing required parameter 'size'",
1360 def SetInstanceParams(opts, args):
1361 """Modifies an instance.
1363 All parameters take effect only at the next restart of the instance.
1365 @param opts: the command line options selected by the user
1367 @param args: should contain only one element, the instance name
1369 @return: the desired exit code
1372 if not (opts.nics or opts.disks or opts.disk_template or
1373 opts.hvparams or opts.beparams or opts.os or opts.osparams or
1374 opts.offline_inst or opts.online_inst or opts.runtime_mem):
1375 ToStderr("Please give at least one of the parameters.")
1378 for param in opts.beparams:
1379 if isinstance(opts.beparams[param], basestring):
1380 if opts.beparams[param].lower() == "default":
1381 opts.beparams[param] = constants.VALUE_DEFAULT
1383 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT,
1384 allowed_values=[constants.VALUE_DEFAULT])
1386 for param in opts.hvparams:
1387 if isinstance(opts.hvparams[param], basestring):
1388 if opts.hvparams[param].lower() == "default":
1389 opts.hvparams[param] = constants.VALUE_DEFAULT
1391 utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1392 allowed_values=[constants.VALUE_DEFAULT])
1394 nics = _ConvertNicDiskModifications(opts.nics)
1395 disks = _ParseDiskSizes(_ConvertNicDiskModifications(opts.disks))
1397 if (opts.disk_template and
1398 opts.disk_template in constants.DTS_INT_MIRROR and
1400 ToStderr("Changing the disk template to a mirrored one requires"
1401 " specifying a secondary node")
1404 if opts.offline_inst:
1406 elif opts.online_inst:
1411 op = opcodes.OpInstanceSetParams(instance_name=args[0],
1414 disk_template=opts.disk_template,
1415 remote_node=opts.node,
1416 hvparams=opts.hvparams,
1417 beparams=opts.beparams,
1418 runtime_mem=opts.runtime_mem,
1420 osparams=opts.osparams,
1421 force_variant=opts.force_variant,
1423 wait_for_sync=opts.wait_for_sync,
1425 ignore_ipolicy=opts.ignore_ipolicy)
1427 # even if here we process the result, we allow submit only
1428 result = SubmitOrSend(op, opts)
1431 ToStdout("Modified instance %s", args[0])
1432 for param, data in result:
1433 ToStdout(" - %-5s -> %s", param, data)
1434 ToStdout("Please don't forget that most parameters take effect"
1435 " only at the next start of the instance.")
1439 def ChangeGroup(opts, args):
1440 """Moves an instance to another group.
1443 (instance_name, ) = args
1447 op = opcodes.OpInstanceChangeGroup(instance_name=instance_name,
1448 iallocator=opts.iallocator,
1449 target_groups=opts.to,
1450 early_release=opts.early_release)
1451 result = SubmitOrSend(op, opts, cl=cl)
1453 # Keep track of submitted jobs
1454 jex = JobExecutor(cl=cl, opts=opts)
1456 for (status, job_id) in result[constants.JOB_IDS_KEY]:
1457 jex.AddJobId(None, status, job_id)
1459 results = jex.GetResults()
1460 bad_cnt = len([row for row in results if not row[0]])
1462 ToStdout("Instance '%s' changed group successfully.", instance_name)
1463 rcode = constants.EXIT_SUCCESS
1465 ToStdout("There were %s errors while changing group of instance '%s'.",
1466 bad_cnt, instance_name)
1467 rcode = constants.EXIT_FAILURE
1472 # multi-instance selection options
1473 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1474 help="Do not ask for confirmation when more than"
1475 " one instance is affected",
1476 action="store_true", default=False)
1478 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1479 help="Filter by nodes (primary only)",
1480 const=_EXPAND_NODES_PRI, action="store_const")
1482 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1483 help="Filter by nodes (secondary only)",
1484 const=_EXPAND_NODES_SEC, action="store_const")
1486 m_node_opt = cli_option("--node", dest="multi_mode",
1487 help="Filter by nodes (primary and secondary)",
1488 const=_EXPAND_NODES_BOTH, action="store_const")
1490 m_clust_opt = cli_option("--all", dest="multi_mode",
1491 help="Select all instances in the cluster",
1492 const=_EXPAND_CLUSTER, action="store_const")
1494 m_inst_opt = cli_option("--instance", dest="multi_mode",
1495 help="Filter by instance name [default]",
1496 const=_EXPAND_INSTANCES, action="store_const")
1498 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1499 help="Filter by node tag",
1500 const=_EXPAND_NODES_BOTH_BY_TAGS,
1501 action="store_const")
1503 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1504 help="Filter by primary node tag",
1505 const=_EXPAND_NODES_PRI_BY_TAGS,
1506 action="store_const")
1508 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1509 help="Filter by secondary node tag",
1510 const=_EXPAND_NODES_SEC_BY_TAGS,
1511 action="store_const")
1513 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1514 help="Filter by instance tag",
1515 const=_EXPAND_INSTANCES_BY_TAGS,
1516 action="store_const")
1518 # this is defined separately due to readability only
1529 AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1530 "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1531 "Creates and adds a new instance to the cluster"),
1533 BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1535 "Create a bunch of instances based on specs in the file."),
1537 ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1538 [SHOWCMD_OPT, PRIORITY_OPT],
1539 "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1541 FailoverInstance, ARGS_ONE_INSTANCE,
1542 [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1543 DRY_RUN_OPT, PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT,
1544 IGNORE_IPOLICY_OPT],
1545 "[-f] <instance>", "Stops the instance, changes its primary node and"
1546 " (if it was originally running) starts it on the new node"
1547 " (the secondary for mirrored instances or any node"
1548 " for shared storage)."),
1550 MigrateInstance, ARGS_ONE_INSTANCE,
1551 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1552 PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT, ALLOW_FAILOVER_OPT,
1553 IGNORE_IPOLICY_OPT, NORUNTIME_CHGS_OPT, SUBMIT_OPT],
1554 "[-f] <instance>", "Migrate instance to its secondary node"
1555 " (only for mirrored instances)"),
1557 MoveInstance, ARGS_ONE_INSTANCE,
1558 [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1559 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_CONSIST_OPT, IGNORE_IPOLICY_OPT],
1560 "[-f] <instance>", "Move instance to an arbitrary node"
1561 " (only for instances of type file and lv)"),
1563 ShowInstanceConfig, ARGS_MANY_INSTANCES,
1564 [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1565 "[-s] {--all | <instance>...}",
1566 "Show information on the specified instance(s)"),
1568 ListInstances, ARGS_MANY_INSTANCES,
1569 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
1572 "Lists the instances and their status. The available fields can be shown"
1573 " using the \"list-fields\" command (see the man page for details)."
1574 " The default field list is (in order): %s." %
1575 utils.CommaJoin(_LIST_DEF_FIELDS),
1578 ListInstanceFields, [ArgUnknown()],
1579 [NOHDR_OPT, SEP_OPT],
1581 "Lists all available fields for instances"),
1583 ReinstallInstance, [ArgInstance()],
1584 [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1585 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1586 m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1587 SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1588 "[-f] <instance>", "Reinstall a stopped instance"),
1590 RemoveInstance, ARGS_ONE_INSTANCE,
1591 [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1592 DRY_RUN_OPT, PRIORITY_OPT],
1593 "[-f] <instance>", "Shuts down the instance and removes it"),
1596 [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1597 [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1598 "<instance> <new_name>", "Rename the instance"),
1600 ReplaceDisks, ARGS_ONE_INSTANCE,
1601 [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1602 NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1603 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_IPOLICY_OPT],
1604 "[-s|-p|-n NODE|-I NAME] <instance>",
1605 "Replaces all disks for the instance"),
1607 SetInstanceParams, ARGS_ONE_INSTANCE,
1608 [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1609 DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1610 OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT, NWSYNC_OPT, OFFLINE_INST_OPT,
1611 ONLINE_INST_OPT, IGNORE_IPOLICY_OPT, RUNTIME_MEM_OPT],
1612 "<instance>", "Alters the parameters of an instance"),
1614 GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1615 [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1616 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1617 m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1618 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT, NO_REMEMBER_OPT],
1619 "<instance>", "Stops an instance"),
1621 GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1622 [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1623 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1624 m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1625 BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT,
1626 NO_REMEMBER_OPT, STARTUP_PAUSED_OPT],
1627 "<instance>", "Starts an instance"),
1629 GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1630 [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1631 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1632 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1633 m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1634 "<instance>", "Reboots an instance"),
1636 ActivateDisks, ARGS_ONE_INSTANCE,
1637 [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT, WFSYNC_OPT],
1638 "<instance>", "Activate an instance's disks"),
1639 "deactivate-disks": (
1640 DeactivateDisks, ARGS_ONE_INSTANCE,
1641 [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1642 "[-f] <instance>", "Deactivate an instance's disks"),
1644 RecreateDisks, ARGS_ONE_INSTANCE,
1645 [SUBMIT_OPT, DISK_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT,
1647 "<instance>", "Recreate an instance's disks"),
1650 [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1651 ArgUnknown(min=1, max=1)],
1652 [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT, ABSOLUTE_OPT],
1653 "<instance> <disk> <size>", "Grow an instance's disk"),
1655 ChangeGroup, ARGS_ONE_INSTANCE,
1656 [TO_GROUP_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT, PRIORITY_OPT, SUBMIT_OPT],
1657 "[-I <iallocator>] [--to <group>]", "Change group of instance"),
1659 ListTags, ARGS_ONE_INSTANCE, [],
1660 "<instance_name>", "List the tags of the given instance"),
1662 AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1663 [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
1664 "<instance_name> tag...", "Add tags to the given instance"),
1666 RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1667 [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
1668 "<instance_name> tag...", "Remove tags from given instance"),
1671 #: dictionary with aliases for commands
1680 return GenericMain(commands, aliases=aliases,
1681 override={"tag_type": constants.TAG_INSTANCE},
1682 env_override=_ENV_OVERRIDE)