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
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 = compat.UniqueFrozenset([
56 _EXPAND_NODES_BOTH_BY_TAGS,
57 _EXPAND_NODES_PRI_BY_TAGS,
58 _EXPAND_NODES_SEC_BY_TAGS,
61 #: default list of options for L{ListInstances}
63 "name", "hypervisor", "os", "pnode", "status", "oper_ram",
67 _ENV_OVERRIDE = compat.UniqueFrozenset(["list"])
69 _INST_DATA_VAL = ht.TListOf(ht.TDict)
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",
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 L{opcodes.OpInstanceCreate}
261 @param opts: the command line options selected by the user
263 @param args: should contain one element, the json filename
265 @return: the desired exit code
268 (json_filename,) = args
272 instance_data = simplejson.loads(utils.ReadFile(json_filename))
273 except Exception, err: # pylint: disable=W0703
274 ToStderr("Can't parse the instance definition file: %s" % str(err))
277 if not _INST_DATA_VAL(instance_data):
278 ToStderr("The instance definition file is not %s" % _INST_DATA_VAL)
282 possible_params = set(opcodes.OpInstanceCreate.GetAllSlots())
283 for (idx, inst) in enumerate(instance_data):
284 unknown = set(inst.keys()) - possible_params
287 # TODO: Suggest closest match for more user friendly experience
288 raise errors.OpPrereqError("Unknown fields in definition %s: %s" %
289 (idx, utils.CommaJoin(unknown)),
292 op = opcodes.OpInstanceCreate(**inst) # pylint: disable=W0142
296 op = opcodes.OpInstanceMultiAlloc(iallocator=opts.iallocator,
298 result = SubmitOrSend(op, opts, cl=cl)
300 # Keep track of submitted jobs
301 jex = JobExecutor(cl=cl, opts=opts)
303 for (status, job_id) in result[constants.JOB_IDS_KEY]:
304 jex.AddJobId(None, status, job_id)
306 results = jex.GetResults()
307 bad_cnt = len([row for row in results if not row[0]])
309 ToStdout("All instances created successfully.")
310 rcode = constants.EXIT_SUCCESS
312 ToStdout("There were %s errors during the creation.", bad_cnt)
313 rcode = constants.EXIT_FAILURE
318 def ReinstallInstance(opts, args):
319 """Reinstall an instance.
321 @param opts: the command line options selected by the user
323 @param args: should contain only one element, the name of the
324 instance to be reinstalled
326 @return: the desired exit code
329 # first, compute the desired name list
330 if opts.multi_mode is None:
331 opts.multi_mode = _EXPAND_INSTANCES
333 inames = _ExpandMultiNames(opts.multi_mode, args)
335 raise errors.OpPrereqError("Selection filter does not match any instances",
338 # second, if requested, ask for an OS
339 if opts.select_os is True:
340 op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[])
341 result = SubmitOpCode(op, opts=opts)
344 ToStdout("Can't get the OS list")
347 ToStdout("Available OS templates:")
350 for (name, variants) in result:
351 for entry in CalculateOSNames(name, variants):
352 ToStdout("%3s: %s", number, entry)
353 choices.append(("%s" % number, entry, entry))
356 choices.append(("x", "exit", "Exit gnt-instance reinstall"))
357 selected = AskUser("Enter OS template number (or x to abort):",
360 if selected == "exit":
361 ToStderr("User aborted reinstall, exiting")
365 os_msg = "change the OS to '%s'" % selected
368 if opts.os is not None:
369 os_msg = "change the OS to '%s'" % os_name
371 os_msg = "keep the same OS"
373 # third, get confirmation: multi-reinstall requires --force-multi,
374 # single-reinstall either --force or --force-multi (--force-multi is
375 # a stronger --force)
376 multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1
378 warn_msg = ("Note: this will remove *all* data for the"
379 " below instances! It will %s.\n" % os_msg)
380 if not (opts.force_multi or
381 ConfirmOperation(inames, "instances", "reinstall", extra=warn_msg)):
384 if not (opts.force or opts.force_multi):
385 usertext = ("This will reinstall the instance '%s' (and %s) which"
386 " removes all data. Continue?") % (inames[0], os_msg)
387 if not AskUser(usertext):
390 jex = JobExecutor(verbose=multi_on, opts=opts)
391 for instance_name in inames:
392 op = opcodes.OpInstanceReinstall(instance_name=instance_name,
394 force_variant=opts.force_variant,
395 osparams=opts.osparams)
396 jex.QueueJob(instance_name, op)
398 results = jex.WaitOrShow(not opts.submit_only)
400 if compat.all(map(compat.fst, results)):
401 return constants.EXIT_SUCCESS
403 return constants.EXIT_FAILURE
406 def SnapshotInstance(opts, args):
407 """Snapshot an instance.
409 @param opts: the command line options selected by the user
411 @param args: should contain only one element, the name of the
412 instance to be reinstalled
414 @return: the desired exit code
417 instance_name = args[0]
418 inames = _ExpandMultiNames(_EXPAND_INSTANCES, [instance_name])
420 raise errors.OpPrereqError("Selection filter does not match any instances",
422 multi_on = len(inames) > 1
423 jex = JobExecutor(verbose=multi_on, opts=opts)
424 for instance_name in inames:
425 op = opcodes.OpInstanceSnapshot(instance_name=instance_name,
427 jex.QueueJob(instance_name, op)
429 results = jex.WaitOrShow(not opts.submit_only)
431 if compat.all(map(compat.fst, results)):
432 return constants.EXIT_SUCCESS
434 return constants.EXIT_FAILURE
436 def RemoveInstance(opts, args):
437 """Remove an instance.
439 @param opts: the command line options selected by the user
441 @param args: should contain only one element, the name of
442 the instance to be removed
444 @return: the desired exit code
447 instance_name = args[0]
452 _EnsureInstancesExist(cl, [instance_name])
454 usertext = ("This will remove the volumes of the instance %s"
455 " (including mirrors), thus removing all the data"
456 " of the instance. Continue?") % instance_name
457 if not AskUser(usertext):
460 op = opcodes.OpInstanceRemove(instance_name=instance_name,
461 ignore_failures=opts.ignore_failures,
462 shutdown_timeout=opts.shutdown_timeout,
463 keep_disks=opts.keep_disks)
464 SubmitOrSend(op, opts, cl=cl)
468 def RenameInstance(opts, args):
469 """Rename an instance.
471 @param opts: the command line options selected by the user
473 @param args: should contain two elements, the old and the
476 @return: the desired exit code
479 if not opts.name_check:
480 if not AskUser("As you disabled the check of the DNS entry, please verify"
481 " that '%s' is a FQDN. Continue?" % args[1]):
484 op = opcodes.OpInstanceRename(instance_name=args[0],
486 ip_check=opts.ip_check,
487 name_check=opts.name_check)
488 result = SubmitOrSend(op, opts)
491 ToStdout("Instance '%s' renamed to '%s'", args[0], result)
496 def ActivateDisks(opts, args):
497 """Activate an instance's disks.
499 This serves two purposes:
500 - it allows (as long as the instance is not running)
501 mounting the disks and modifying them from the node
502 - it repairs inactive secondary drbds
504 @param opts: the command line options selected by the user
506 @param args: should contain only one element, the instance name
508 @return: the desired exit code
511 instance_name = args[0]
512 op = opcodes.OpInstanceActivateDisks(instance_name=instance_name,
513 ignore_size=opts.ignore_size,
514 wait_for_sync=opts.wait_for_sync)
515 disks_info = SubmitOrSend(op, opts)
516 for host, iname, nname in disks_info:
517 ToStdout("%s:%s:%s", host, iname, nname)
521 def DeactivateDisks(opts, args):
522 """Deactivate an instance's disks.
524 This function takes the instance name, looks for its primary node
525 and the tries to shutdown its block devices on that node.
527 @param opts: the command line options selected by the user
529 @param args: should contain only one element, the instance name
531 @return: the desired exit code
534 instance_name = args[0]
535 op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name,
537 SubmitOrSend(op, opts)
541 def RecreateDisks(opts, args):
542 """Recreate an instance's disks.
544 @param opts: the command line options selected by the user
546 @param args: should contain only one element, the instance name
548 @return: the desired exit code
551 instance_name = args[0]
556 for didx, ddict in opts.disks:
559 if not ht.TDict(ddict):
560 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
561 raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
563 if constants.IDISK_SIZE in ddict:
565 ddict[constants.IDISK_SIZE] = \
566 utils.ParseUnit(ddict[constants.IDISK_SIZE])
567 except ValueError, err:
568 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
569 (didx, err), errors.ECODE_INVAL)
571 disks.append((didx, ddict))
573 # TODO: Verify modifyable parameters (already done in
574 # LUInstanceRecreateDisks, but it'd be nice to have in the client)
578 msg = "At most one of either --nodes or --iallocator can be passed"
579 raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
580 pnode, snode = SplitNodeOption(opts.node)
582 if snode is not None:
587 op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
588 disks=disks, nodes=nodes,
589 iallocator=opts.iallocator)
590 SubmitOrSend(op, opts)
595 def GrowDisk(opts, args):
596 """Grow an instance's disks.
598 @param opts: the command line options selected by the user
600 @param args: should contain three elements, the target instance name,
601 the target disk id, and the target growth
603 @return: the desired exit code
610 except (TypeError, ValueError), err:
611 raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
614 amount = utils.ParseUnit(args[2])
615 except errors.UnitParseError:
616 raise errors.OpPrereqError("Can't parse the given amount '%s'" % args[2],
618 op = opcodes.OpInstanceGrowDisk(instance_name=instance,
619 disk=disk, amount=amount,
620 wait_for_sync=opts.wait_for_sync,
621 absolute=opts.absolute)
622 SubmitOrSend(op, opts)
626 def _StartupInstance(name, opts):
627 """Startup instances.
629 This returns the opcode to start an instance, and its decorator will
630 wrap this into a loop starting all desired instances.
632 @param name: the name of the instance to act on
633 @param opts: the command line options selected by the user
634 @return: the opcode needed for the operation
637 op = opcodes.OpInstanceStartup(instance_name=name,
639 ignore_offline_nodes=opts.ignore_offline,
640 no_remember=opts.no_remember,
641 startup_paused=opts.startup_paused)
642 # do not add these parameters to the opcode unless they're defined
644 op.hvparams = opts.hvparams
646 op.beparams = opts.beparams
650 def _RebootInstance(name, opts):
651 """Reboot instance(s).
653 This returns the opcode to reboot an instance, and its decorator
654 will wrap this into a loop rebooting all desired instances.
656 @param name: the name of the instance to act on
657 @param opts: the command line options selected by the user
658 @return: the opcode needed for the operation
661 return opcodes.OpInstanceReboot(instance_name=name,
662 reboot_type=opts.reboot_type,
663 ignore_secondaries=opts.ignore_secondaries,
664 shutdown_timeout=opts.shutdown_timeout)
667 def _ShutdownInstance(name, opts):
668 """Shutdown an instance.
670 This returns the opcode to shutdown an instance, and its decorator
671 will wrap this into a loop shutting down all desired instances.
673 @param name: the name of the instance to act on
674 @param opts: the command line options selected by the user
675 @return: the opcode needed for the operation
678 return opcodes.OpInstanceShutdown(instance_name=name,
680 timeout=opts.timeout,
681 ignore_offline_nodes=opts.ignore_offline,
682 no_remember=opts.no_remember)
685 def ReplaceDisks(opts, args):
686 """Replace the disks of an instance
688 @param opts: the command line options selected by the user
690 @param args: should contain only one element, the instance name
692 @return: the desired exit code
695 new_2ndary = opts.dst_node
696 iallocator = opts.iallocator
697 if opts.disks is None:
701 disks = [int(i) for i in opts.disks.split(",")]
702 except (TypeError, ValueError), err:
703 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
705 cnt = [opts.on_primary, opts.on_secondary, opts.auto,
706 new_2ndary is not None, iallocator is not None].count(True)
708 raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -I"
709 " options must be passed", errors.ECODE_INVAL)
710 elif opts.on_primary:
711 mode = constants.REPLACE_DISK_PRI
712 elif opts.on_secondary:
713 mode = constants.REPLACE_DISK_SEC
715 mode = constants.REPLACE_DISK_AUTO
717 raise errors.OpPrereqError("Cannot specify disks when using automatic"
718 " mode", errors.ECODE_INVAL)
719 elif new_2ndary is not None or iallocator is not None:
721 mode = constants.REPLACE_DISK_CHG
723 op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
724 remote_node=new_2ndary, mode=mode,
725 iallocator=iallocator,
726 early_release=opts.early_release,
727 ignore_ipolicy=opts.ignore_ipolicy)
728 SubmitOrSend(op, opts)
732 def FailoverInstance(opts, args):
733 """Failover an instance.
735 The failover is done by shutting it down on its present node and
736 starting it on the secondary.
738 @param opts: the command line options selected by the user
740 @param args: should contain only one element, the instance name
742 @return: the desired exit code
746 instance_name = args[0]
748 iallocator = opts.iallocator
749 target_node = opts.dst_node
751 if iallocator and target_node:
752 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
753 " node (-n) but not both", errors.ECODE_INVAL)
756 _EnsureInstancesExist(cl, [instance_name])
758 usertext = ("Failover will happen to image %s."
759 " This requires a shutdown of the instance. Continue?" %
761 if not AskUser(usertext):
764 op = opcodes.OpInstanceFailover(instance_name=instance_name,
765 ignore_consistency=opts.ignore_consistency,
766 shutdown_timeout=opts.shutdown_timeout,
767 iallocator=iallocator,
768 target_node=target_node,
769 ignore_ipolicy=opts.ignore_ipolicy)
770 SubmitOrSend(op, opts, cl=cl)
774 def MigrateInstance(opts, args):
775 """Migrate an instance.
777 The migrate is done without shutdown.
779 @param opts: the command line options selected by the user
781 @param args: should contain only one element, the instance name
783 @return: the desired exit code
787 instance_name = args[0]
789 iallocator = opts.iallocator
790 target_node = opts.dst_node
792 if iallocator and target_node:
793 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
794 " node (-n) but not both", errors.ECODE_INVAL)
797 _EnsureInstancesExist(cl, [instance_name])
800 usertext = ("Instance %s will be recovered from a failed migration."
801 " Note that the migration procedure (including cleanup)" %
804 usertext = ("Instance %s will be migrated. Note that migration" %
806 usertext += (" might impact the instance if anything goes wrong"
807 " (e.g. due to bugs in the hypervisor). Continue?")
808 if not AskUser(usertext):
811 # this should be removed once --non-live is deprecated
812 if not opts.live and opts.migration_mode is not None:
813 raise errors.OpPrereqError("Only one of the --non-live and "
814 "--migration-mode options can be passed",
816 if not opts.live: # --non-live passed
817 mode = constants.HT_MIGRATION_NONLIVE
819 mode = opts.migration_mode
821 op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
822 cleanup=opts.cleanup, iallocator=iallocator,
823 target_node=target_node,
824 allow_failover=opts.allow_failover,
825 allow_runtime_changes=opts.allow_runtime_chgs,
826 ignore_ipolicy=opts.ignore_ipolicy)
827 SubmitOrSend(op, cl=cl, opts=opts)
831 def MoveInstance(opts, args):
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]
846 usertext = ("Instance %s will be moved."
847 " This requires a shutdown of the instance. Continue?" %
849 if not AskUser(usertext):
852 op = opcodes.OpInstanceMove(instance_name=instance_name,
853 target_node=opts.node,
854 shutdown_timeout=opts.shutdown_timeout,
855 ignore_consistency=opts.ignore_consistency,
856 ignore_ipolicy=opts.ignore_ipolicy)
857 SubmitOrSend(op, opts, cl=cl)
861 def ConnectToInstanceConsole(opts, args):
862 """Connect to the console of an instance.
864 @param opts: the command line options selected by the user
866 @param args: should contain only one element, the instance name
868 @return: the desired exit code
871 instance_name = args[0]
875 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
876 ((console_data, oper_state), ) = \
877 cl.QueryInstances([instance_name], ["console", "oper_state"], False)
879 # Ensure client connection is closed while external commands are run
886 # Instance is running
887 raise errors.OpExecError("Console information for instance %s is"
888 " unavailable" % instance_name)
890 raise errors.OpExecError("Instance %s is not running, can't get console" %
893 return _DoConsole(objects.InstanceConsole.FromDict(console_data),
894 opts.show_command, cluster_name)
897 def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
898 _runcmd_fn=utils.RunCmd):
899 """Acts based on the result of L{opcodes.OpInstanceConsole}.
901 @type console: L{objects.InstanceConsole}
902 @param console: Console object
903 @type show_command: bool
904 @param show_command: Whether to just display commands
905 @type cluster_name: string
906 @param cluster_name: Cluster name as retrieved from master daemon
909 assert console.Validate()
911 if console.kind == constants.CONS_MESSAGE:
912 feedback_fn(console.message)
913 elif console.kind == constants.CONS_VNC:
914 feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
915 " URL <vnc://%s:%s/>",
916 console.instance, console.host, console.port,
917 console.display, console.host, console.port)
918 elif console.kind == constants.CONS_SPICE:
919 feedback_fn("Instance %s has SPICE listening on %s:%s", console.instance,
920 console.host, console.port)
921 elif console.kind == constants.CONS_SSH:
922 # Convert to string if not already one
923 if isinstance(console.command, basestring):
924 cmd = console.command
926 cmd = utils.ShellQuoteArgs(console.command)
928 srun = ssh.SshRunner(cluster_name=cluster_name)
929 ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
930 batch=True, quiet=False, tty=True)
933 feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
935 result = _runcmd_fn(ssh_cmd, interactive=True)
937 logging.error("Console command \"%s\" failed with reason '%s' and"
938 " output %r", result.cmd, result.fail_reason,
940 raise errors.OpExecError("Connection to console of instance %s failed,"
941 " please check cluster configuration" %
944 raise errors.GenericError("Unknown console type '%s'" % console.kind)
946 return constants.EXIT_SUCCESS
949 def _FormatLogicalID(dev_type, logical_id, roman):
950 """Formats the logical_id of a disk.
953 if dev_type == constants.LD_DRBD8:
954 node_a, node_b, port, minor_a, minor_b, key = logical_id
956 ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
958 ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
960 ("port", str(compat.TryToRoman(port, convert=roman))),
961 ("auth key", str(key)),
963 elif dev_type == constants.LD_LV:
964 vg_name, lv_name = logical_id
965 data = ["%s/%s" % (vg_name, lv_name)]
967 data = [str(logical_id)]
972 def _FormatListInfo(data):
973 return list(str(i) for i in data)
976 def _FormatBlockDevInfo(idx, top_level, dev, roman):
977 """Show block device information.
979 This is only used by L{ShowInstanceConfig}, but it's too big to be
980 left for an inline definition.
983 @param idx: the index of the current disk
984 @type top_level: boolean
985 @param top_level: if this a top-level disk?
987 @param dev: dictionary with disk information
989 @param roman: whether to try to use roman integers
990 @return: a list of either strings, tuples or lists
991 (which should be formatted at a higher indent level)
994 def helper(dtype, status):
995 """Format one line for physical device status.
998 @param dtype: a constant from the L{constants.LDS_BLOCK} set
1000 @param status: a tuple as returned from L{backend.FindBlockDevice}
1001 @return: the string representing the status
1007 (path, major, minor, syncp, estt, degr, ldisk_status) = status
1009 major_string = "N/A"
1011 major_string = str(compat.TryToRoman(major, convert=roman))
1014 minor_string = "N/A"
1016 minor_string = str(compat.TryToRoman(minor, convert=roman))
1018 txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1019 if dtype in (constants.LD_DRBD8, ):
1020 if syncp is not None:
1021 sync_text = "*RECOVERING* %5.2f%%," % syncp
1023 sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1025 sync_text += " ETA unknown"
1027 sync_text = "in sync"
1029 degr_text = "*DEGRADED*"
1032 if ldisk_status == constants.LDS_FAULTY:
1033 ldisk_text = " *MISSING DISK*"
1034 elif ldisk_status == constants.LDS_UNKNOWN:
1035 ldisk_text = " *UNCERTAIN STATE*"
1038 txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1039 elif dtype == constants.LD_LV:
1040 if ldisk_status == constants.LDS_FAULTY:
1041 ldisk_text = " *FAILED* (failed drive?)"
1049 if dev["iv_name"] is not None:
1050 txt = dev["iv_name"]
1052 txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1054 txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1055 if isinstance(dev["size"], int):
1056 nice_size = utils.FormatUnit(dev["size"], "h")
1058 nice_size = str(dev["size"])
1059 data = [(txt, "%s, size %s" % (dev["dev_type"], nice_size))]
1061 data.append(("access mode", dev["mode"]))
1062 if dev["logical_id"] is not None:
1064 l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1066 l_id = [str(dev["logical_id"])]
1068 data.append(("logical_id", l_id[0]))
1071 elif dev["physical_id"] is not None:
1072 data.append(("physical_id:", _FormatListInfo(dev["physical_id"])))
1075 data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1078 data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1080 data.append(("name", dev["name"]))
1081 data.append(("UUID", dev["uuid"]))
1084 data.append(("child devices", [
1085 _FormatBlockDevInfo(c_idx, False, child, roman)
1086 for c_idx, child in enumerate(dev["children"])
1091 def _FormatInstanceNicInfo(idx, nic):
1092 """Helper function for L{_FormatInstanceInfo()}"""
1093 (name, uuid, ip, mac, mode, link, _, netinfo) = nic
1096 network_name = netinfo["name"]
1098 ("nic/%d" % idx, ""),
1101 ("mode", str(mode)),
1102 ("link", str(link)),
1103 ("network", str(network_name)),
1104 ("UUID", str(uuid)),
1105 ("name", str(name)),
1109 def _FormatInstanceNodesInfo(instance):
1110 """Helper function for L{_FormatInstanceInfo()}"""
1111 pgroup = ("%s (UUID %s)" %
1112 (instance["pnode_group_name"], instance["pnode_group_uuid"]))
1113 secs = utils.CommaJoin(("%s (group %s, group UUID %s)" %
1114 (name, group_name, group_uuid))
1115 for (name, group_name, group_uuid) in
1116 zip(instance["snodes"],
1117 instance["snodes_group_names"],
1118 instance["snodes_group_uuids"]))
1121 ("primary", instance["pnode"]),
1124 [("secondaries", secs)],
1128 def _GetVncConsoleInfo(instance):
1129 """Helper function for L{_FormatInstanceInfo()}"""
1130 vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1132 if vnc_bind_address:
1133 port = instance["network_port"]
1134 display = int(port) - constants.VNC_BASE_PORT
1135 if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1136 vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1139 elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1140 vnc_console_port = ("%s:%s (node %s) (display %s)" %
1141 (vnc_bind_address, port,
1142 instance["pnode"], display))
1144 # vnc bind address is a file
1145 vnc_console_port = "%s:%s" % (instance["pnode"],
1147 ret = "vnc to %s" % vnc_console_port
1153 def _FormatInstanceInfo(instance, roman_integers):
1154 """Format instance information for L{cli.PrintGenericInfo()}"""
1155 istate = "configured to be %s" % instance["config_state"]
1156 if instance["run_state"]:
1157 istate += ", actual state is %s" % instance["run_state"]
1159 ("Instance name", instance["name"]),
1160 ("UUID", instance["uuid"]),
1162 str(compat.TryToRoman(instance["serial_no"], convert=roman_integers))),
1163 ("Creation time", utils.FormatTime(instance["ctime"])),
1164 ("Modification time", utils.FormatTime(instance["mtime"])),
1166 ("Nodes", _FormatInstanceNodesInfo(instance)),
1167 ("Operating system", instance["os"]),
1168 ("Operating system parameters",
1169 FormatParamsDictInfo(instance["os_instance"], instance["os_actual"])),
1172 if "network_port" in instance:
1173 info.append(("Allocated network port",
1174 str(compat.TryToRoman(instance["network_port"],
1175 convert=roman_integers))))
1176 info.append(("Hypervisor", instance["hypervisor"]))
1177 console = _GetVncConsoleInfo(instance)
1179 info.append(("console connection", console))
1180 # deprecated "memory" value, kept for one version for compatibility
1181 # TODO(ganeti 2.7) remove.
1182 be_actual = copy.deepcopy(instance["be_actual"])
1183 be_actual["memory"] = be_actual[constants.BE_MAXMEM]
1185 ("Hypervisor parameters",
1186 FormatParamsDictInfo(instance["hv_instance"], instance["hv_actual"])),
1187 ("Back-end parameters",
1188 FormatParamsDictInfo(instance["be_instance"], be_actual)),
1190 _FormatInstanceNicInfo(idx, nic)
1191 for (idx, nic) in enumerate(instance["nics"])
1193 ("Disk template", instance["disk_template"]),
1195 _FormatBlockDevInfo(idx, True, device, roman_integers)
1196 for (idx, device) in enumerate(instance["disks"])
1202 def ShowInstanceConfig(opts, args):
1203 """Compute instance run-time status.
1205 @param opts: the command line options selected by the user
1207 @param args: either an empty list, and then we query all
1208 instances, or should contain a list of instance names
1210 @return: the desired exit code
1213 if not args and not opts.show_all:
1214 ToStderr("No instance selected."
1215 " Please pass in --all if you want to query all instances.\n"
1216 "Note that this can take a long time on a big cluster.")
1218 elif args and opts.show_all:
1219 ToStderr("Cannot use --all if you specify instance names.")
1223 op = opcodes.OpInstanceQueryData(instances=args, static=opts.static,
1224 use_locking=not opts.static)
1225 result = SubmitOpCode(op, opts=opts)
1227 ToStdout("No instances.")
1231 _FormatInstanceInfo(instance, opts.roman_integers)
1232 for instance in result.values()
1237 def _ConvertNicDiskModifications(mods):
1238 """Converts NIC/disk modifications from CLI to opcode.
1240 When L{opcodes.OpInstanceSetParams} was changed to support adding/removing
1241 disks at arbitrary indices, its parameter format changed. This function
1242 converts legacy requests (e.g. "--net add" or "--disk add:size=4G") to the
1243 newer format and adds support for new-style requests (e.g. "--new 4:add").
1245 @type mods: list of tuples
1246 @param mods: Modifications as given by command line parser
1247 @rtype: list of tuples
1248 @return: Modifications as understood by L{opcodes.OpInstanceSetParams}
1253 for (identifier, params) in mods:
1254 if identifier == constants.DDM_ADD:
1255 # Add item as last item (legacy interface)
1256 action = constants.DDM_ADD
1258 elif identifier == constants.DDM_REMOVE:
1259 # Remove last item (legacy interface)
1260 action = constants.DDM_REMOVE
1263 # Modifications and adding/removing at arbitrary indices
1264 add = params.pop(constants.DDM_ADD, _MISSING)
1265 remove = params.pop(constants.DDM_REMOVE, _MISSING)
1266 modify = params.pop(constants.DDM_MODIFY, _MISSING)
1268 if modify is _MISSING:
1269 if not (add is _MISSING or remove is _MISSING):
1270 raise errors.OpPrereqError("Cannot add and remove at the same time",
1272 elif add is not _MISSING:
1273 action = constants.DDM_ADD
1274 elif remove is not _MISSING:
1275 action = constants.DDM_REMOVE
1277 action = constants.DDM_MODIFY
1279 elif add is _MISSING and remove is _MISSING:
1280 action = constants.DDM_MODIFY
1282 raise errors.OpPrereqError("Cannot modify and add/remove at the"
1283 " same time", errors.ECODE_INVAL)
1285 assert not (constants.DDMS_VALUES_WITH_MODIFY & set(params.keys()))
1287 if action == constants.DDM_REMOVE and params:
1288 raise errors.OpPrereqError("Not accepting parameters on removal",
1291 result.append((action, identifier, params))
1296 def _ParseDiskSizes(mods):
1297 """Parses disk sizes in parameters.
1300 for (action, _, params) in mods:
1301 if params and constants.IDISK_SIZE in params:
1302 params[constants.IDISK_SIZE] = \
1303 utils.ParseUnit(params[constants.IDISK_SIZE])
1304 elif action == constants.DDM_ADD:
1305 raise errors.OpPrereqError("Missing required parameter 'size'",
1311 def SetInstanceParams(opts, args):
1312 """Modifies an instance.
1314 All parameters take effect only at the next restart of the instance.
1316 @param opts: the command line options selected by the user
1318 @param args: should contain only one element, the instance name
1320 @return: the desired exit code
1323 if not (opts.nics or opts.disks or opts.disk_template or
1324 opts.hvparams or opts.beparams or opts.os or opts.osparams or
1325 opts.offline_inst or opts.online_inst or opts.runtime_mem or
1326 opts.new_primary_node):
1327 ToStderr("Please give at least one of the parameters.")
1330 for param in opts.beparams:
1331 if isinstance(opts.beparams[param], basestring):
1332 if opts.beparams[param].lower() == "default":
1333 opts.beparams[param] = constants.VALUE_DEFAULT
1335 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT,
1336 allowed_values=[constants.VALUE_DEFAULT])
1338 for param in opts.hvparams:
1339 if isinstance(opts.hvparams[param], basestring):
1340 if opts.hvparams[param].lower() == "default":
1341 opts.hvparams[param] = constants.VALUE_DEFAULT
1343 utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1344 allowed_values=[constants.VALUE_DEFAULT])
1346 nics = _ConvertNicDiskModifications(opts.nics)
1347 for action, _, __ in nics:
1348 if action == constants.DDM_MODIFY and opts.hotplug:
1349 usertext = ("You are about to hot-modify a NIC. This will be done"
1350 " by removing the exisiting and then adding a new one."
1351 " Network connection might be lost. Continue?")
1352 if not AskUser(usertext):
1355 disks = _ParseDiskSizes(_ConvertNicDiskModifications(opts.disks))
1357 if (opts.disk_template and
1358 opts.disk_template in constants.DTS_INT_MIRROR and
1360 ToStderr("Changing the disk template to a mirrored one requires"
1361 " specifying a secondary node")
1364 if opts.offline_inst:
1366 elif opts.online_inst:
1371 op = opcodes.OpInstanceSetParams(instance_name=args[0],
1374 hotplug=opts.hotplug,
1375 keep_disks=opts.keep_disks,
1376 disk_template=opts.disk_template,
1377 remote_node=opts.node,
1378 pnode=opts.new_primary_node,
1379 hvparams=opts.hvparams,
1380 beparams=opts.beparams,
1381 runtime_mem=opts.runtime_mem,
1383 osparams=opts.osparams,
1384 force_variant=opts.force_variant,
1386 wait_for_sync=opts.wait_for_sync,
1388 conflicts_check=opts.conflicts_check,
1389 ignore_ipolicy=opts.ignore_ipolicy)
1391 # even if here we process the result, we allow submit only
1392 result = SubmitOrSend(op, opts)
1395 ToStdout("Modified instance %s", args[0])
1396 for param, data in result:
1397 ToStdout(" - %-5s -> %s", param, data)
1398 if not opts.hotplug:
1399 ToStdout("Please don't forget that most parameters take effect"
1400 " only at the next (re)start of the instance initiated by"
1401 " ganeti; restarting from within the instance will"
1406 def ChangeGroup(opts, args):
1407 """Moves an instance to another group.
1410 (instance_name, ) = args
1414 op = opcodes.OpInstanceChangeGroup(instance_name=instance_name,
1415 iallocator=opts.iallocator,
1416 target_groups=opts.to,
1417 early_release=opts.early_release)
1418 result = SubmitOrSend(op, opts, cl=cl)
1420 # Keep track of submitted jobs
1421 jex = JobExecutor(cl=cl, opts=opts)
1423 for (status, job_id) in result[constants.JOB_IDS_KEY]:
1424 jex.AddJobId(None, status, job_id)
1426 results = jex.GetResults()
1427 bad_cnt = len([row for row in results if not row[0]])
1429 ToStdout("Instance '%s' changed group successfully.", instance_name)
1430 rcode = constants.EXIT_SUCCESS
1432 ToStdout("There were %s errors while changing group of instance '%s'.",
1433 bad_cnt, instance_name)
1434 rcode = constants.EXIT_FAILURE
1439 # multi-instance selection options
1440 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1441 help="Do not ask for confirmation when more than"
1442 " one instance is affected",
1443 action="store_true", default=False)
1445 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1446 help="Filter by nodes (primary only)",
1447 const=_EXPAND_NODES_PRI, action="store_const")
1449 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1450 help="Filter by nodes (secondary only)",
1451 const=_EXPAND_NODES_SEC, action="store_const")
1453 m_node_opt = cli_option("--node", dest="multi_mode",
1454 help="Filter by nodes (primary and secondary)",
1455 const=_EXPAND_NODES_BOTH, action="store_const")
1457 m_clust_opt = cli_option("--all", dest="multi_mode",
1458 help="Select all instances in the cluster",
1459 const=_EXPAND_CLUSTER, action="store_const")
1461 m_inst_opt = cli_option("--instance", dest="multi_mode",
1462 help="Filter by instance name [default]",
1463 const=_EXPAND_INSTANCES, action="store_const")
1465 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1466 help="Filter by node tag",
1467 const=_EXPAND_NODES_BOTH_BY_TAGS,
1468 action="store_const")
1470 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1471 help="Filter by primary node tag",
1472 const=_EXPAND_NODES_PRI_BY_TAGS,
1473 action="store_const")
1475 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1476 help="Filter by secondary node tag",
1477 const=_EXPAND_NODES_SEC_BY_TAGS,
1478 action="store_const")
1480 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1481 help="Filter by instance tag",
1482 const=_EXPAND_INSTANCES_BY_TAGS,
1483 action="store_const")
1485 # this is defined separately due to readability only
1496 AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1497 "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1498 "Creates and adds a new instance to the cluster"),
1500 BatchCreate, [ArgFile(min=1, max=1)],
1501 [DRY_RUN_OPT, PRIORITY_OPT, IALLOCATOR_OPT, SUBMIT_OPT],
1503 "Create a bunch of instances based on specs in the file."),
1505 ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1506 [SHOWCMD_OPT, PRIORITY_OPT],
1507 "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1509 FailoverInstance, ARGS_ONE_INSTANCE,
1510 [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1511 DRY_RUN_OPT, PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT,
1512 IGNORE_IPOLICY_OPT, CLEANUP_OPT],
1513 "[-f] <instance>", "Stops the instance, changes its primary node and"
1514 " (if it was originally running) starts it on the new node"
1515 " (the secondary for mirrored instances or any node"
1516 " for shared storage)."),
1518 MigrateInstance, ARGS_ONE_INSTANCE,
1519 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1520 PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT, ALLOW_FAILOVER_OPT,
1521 IGNORE_IPOLICY_OPT, NORUNTIME_CHGS_OPT, SUBMIT_OPT],
1522 "[-f] <instance>", "Migrate instance to its secondary node"
1523 " (only for mirrored instances)"),
1525 MoveInstance, ARGS_ONE_INSTANCE,
1526 [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1527 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_CONSIST_OPT, IGNORE_IPOLICY_OPT],
1528 "[-f] <instance>", "Move instance to an arbitrary node"
1529 " (only for instances of type file and lv)"),
1531 ShowInstanceConfig, ARGS_MANY_INSTANCES,
1532 [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1533 "[-s] {--all | <instance>...}",
1534 "Show information on the specified instance(s)"),
1536 ListInstances, ARGS_MANY_INSTANCES,
1537 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
1540 "Lists the instances and their status. The available fields can be shown"
1541 " using the \"list-fields\" command (see the man page for details)."
1542 " The default field list is (in order): %s." %
1543 utils.CommaJoin(_LIST_DEF_FIELDS),
1546 ListInstanceFields, [ArgUnknown()],
1547 [NOHDR_OPT, SEP_OPT],
1549 "Lists all available fields for instances"),
1551 ReinstallInstance, [ArgInstance()],
1552 [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1553 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1554 m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1555 SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1556 "[-f] <instance>", "Reinstall a stopped instance"),
1558 SnapshotInstance, [ArgInstance(min=1,max=1)],
1559 [DISK_OPT, SUBMIT_OPT, DRY_RUN_OPT],
1560 "<instance>", "Snapshot an instance's disk(s)"),
1562 RemoveInstance, ARGS_ONE_INSTANCE,
1563 [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1564 DRY_RUN_OPT, PRIORITY_OPT, KEEPDISKS_OPT],
1565 "[-f] <instance>", "Shuts down the instance and removes it"),
1568 [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1569 [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1570 "<instance> <new_name>", "Rename the instance"),
1572 ReplaceDisks, ARGS_ONE_INSTANCE,
1573 [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1574 NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1575 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_IPOLICY_OPT],
1576 "[-s|-p|-a|-n NODE|-I NAME] <instance>",
1577 "Replaces disks for the instance"),
1579 SetInstanceParams, ARGS_ONE_INSTANCE,
1580 [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1581 DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1582 OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT, NWSYNC_OPT, OFFLINE_INST_OPT,
1583 ONLINE_INST_OPT, IGNORE_IPOLICY_OPT, RUNTIME_MEM_OPT,
1584 NOCONFLICTSCHECK_OPT, NEW_PRIMARY_OPT, HOTPLUG_OPT, KEEPDISKS_OPT],
1585 "<instance>", "Alters the parameters of an instance"),
1587 GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1588 [FORCE_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1589 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1590 m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1591 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT, NO_REMEMBER_OPT],
1592 "<instance>", "Stops an instance"),
1594 GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1595 [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1596 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1597 m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1598 BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT,
1599 NO_REMEMBER_OPT, STARTUP_PAUSED_OPT],
1600 "<instance>", "Starts an instance"),
1602 GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1603 [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1604 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1605 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1606 m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1607 "<instance>", "Reboots an instance"),
1609 ActivateDisks, ARGS_ONE_INSTANCE,
1610 [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT, WFSYNC_OPT],
1611 "<instance>", "Activate an instance's disks"),
1612 "deactivate-disks": (
1613 DeactivateDisks, ARGS_ONE_INSTANCE,
1614 [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1615 "[-f] <instance>", "Deactivate an instance's disks"),
1617 RecreateDisks, ARGS_ONE_INSTANCE,
1618 [SUBMIT_OPT, DISK_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT,
1620 "<instance>", "Recreate an instance's disks"),
1623 [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1624 ArgUnknown(min=1, max=1)],
1625 [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT, ABSOLUTE_OPT],
1626 "<instance> <disk> <size>", "Grow an instance's disk"),
1628 ChangeGroup, ARGS_ONE_INSTANCE,
1629 [TO_GROUP_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT, PRIORITY_OPT, SUBMIT_OPT],
1630 "[-I <iallocator>] [--to <group>]", "Change group of instance"),
1632 ListTags, ARGS_ONE_INSTANCE, [],
1633 "<instance_name>", "List the tags of the given instance"),
1635 AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1636 [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
1637 "<instance_name> tag...", "Add tags to the given instance"),
1639 RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1640 [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
1641 "<instance_name> tag...", "Remove tags from given instance"),
1644 #: dictionary with aliases for commands
1653 return GenericMain(commands, aliases=aliases,
1654 override={"tag_type": constants.TAG_INSTANCE},
1655 env_override=_ENV_OVERRIDE)