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 = compat.UniqueFrozenset([
57 _EXPAND_NODES_BOTH_BY_TAGS,
58 _EXPAND_NODES_PRI_BY_TAGS,
59 _EXPAND_NODES_SEC_BY_TAGS,
62 #: default list of options for L{ListInstances}
64 "name", "hypervisor", "os", "pnode", "status", "oper_ram",
68 _ENV_OVERRIDE = compat.UniqueFrozenset(["list"])
70 _INST_DATA_VAL = ht.TListOf(ht.TDict)
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",
222 "snodes", "snodes.group", "snodes.group.uuid"],
223 (lambda value: ",".join(str(item)
227 return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units,
228 opts.separator, not opts.no_headers,
229 format_override=fmtoverride, verbose=opts.verbose,
230 force_filter=opts.force_filter)
233 def ListInstanceFields(opts, args):
234 """List instance fields.
236 @param opts: the command line options selected by the user
238 @param args: fields to list, or empty for all
240 @return: the desired exit code
243 return GenericListFields(constants.QR_INSTANCE, args, opts.separator,
247 def AddInstance(opts, args):
248 """Add an instance to the cluster.
250 This is just a wrapper over GenericInstanceCreate.
253 return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
256 def BatchCreate(opts, args):
257 """Create instances using a definition file.
259 This function reads a json file with L{opcodes.OpInstanceCreate}
262 @param opts: the command line options selected by the user
264 @param args: should contain one element, the json filename
266 @return: the desired exit code
269 (json_filename,) = args
273 instance_data = simplejson.loads(utils.ReadFile(json_filename))
274 except Exception, err: # pylint: disable=W0703
275 ToStderr("Can't parse the instance definition file: %s" % str(err))
278 if not _INST_DATA_VAL(instance_data):
279 ToStderr("The instance definition file is not %s" % _INST_DATA_VAL)
283 possible_params = set(opcodes.OpInstanceCreate.GetAllSlots())
284 for (idx, inst) in enumerate(instance_data):
285 unknown = set(inst.keys()) - possible_params
288 # TODO: Suggest closest match for more user friendly experience
289 raise errors.OpPrereqError("Unknown fields in definition %s: %s" %
290 (idx, utils.CommaJoin(unknown)),
293 op = opcodes.OpInstanceCreate(**inst) # pylint: disable=W0142
297 op = opcodes.OpInstanceMultiAlloc(iallocator=opts.iallocator,
299 result = SubmitOrSend(op, opts, cl=cl)
301 # Keep track of submitted jobs
302 jex = JobExecutor(cl=cl, opts=opts)
304 for (status, job_id) in result[constants.JOB_IDS_KEY]:
305 jex.AddJobId(None, status, job_id)
307 results = jex.GetResults()
308 bad_cnt = len([row for row in results if not row[0]])
310 ToStdout("All instances created successfully.")
311 rcode = constants.EXIT_SUCCESS
313 ToStdout("There were %s errors during the creation.", bad_cnt)
314 rcode = constants.EXIT_FAILURE
319 def ReinstallInstance(opts, args):
320 """Reinstall an instance.
322 @param opts: the command line options selected by the user
324 @param args: should contain only one element, the name of the
325 instance to be reinstalled
327 @return: the desired exit code
330 # first, compute the desired name list
331 if opts.multi_mode is None:
332 opts.multi_mode = _EXPAND_INSTANCES
334 inames = _ExpandMultiNames(opts.multi_mode, args)
336 raise errors.OpPrereqError("Selection filter does not match any instances",
339 # second, if requested, ask for an OS
340 if opts.select_os is True:
341 op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[])
342 result = SubmitOpCode(op, opts=opts)
345 ToStdout("Can't get the OS list")
348 ToStdout("Available OS templates:")
351 for (name, variants) in result:
352 for entry in CalculateOSNames(name, variants):
353 ToStdout("%3s: %s", number, entry)
354 choices.append(("%s" % number, entry, entry))
357 choices.append(("x", "exit", "Exit gnt-instance reinstall"))
358 selected = AskUser("Enter OS template number (or x to abort):",
361 if selected == "exit":
362 ToStderr("User aborted reinstall, exiting")
366 os_msg = "change the OS to '%s'" % selected
369 if opts.os is not None:
370 os_msg = "change the OS to '%s'" % os_name
372 os_msg = "keep the same OS"
374 # third, get confirmation: multi-reinstall requires --force-multi,
375 # single-reinstall either --force or --force-multi (--force-multi is
376 # a stronger --force)
377 multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1
379 warn_msg = ("Note: this will remove *all* data for the"
380 " below instances! It will %s.\n" % os_msg)
381 if not (opts.force_multi or
382 ConfirmOperation(inames, "instances", "reinstall", extra=warn_msg)):
385 if not (opts.force or opts.force_multi):
386 usertext = ("This will reinstall the instance '%s' (and %s) which"
387 " removes all data. Continue?") % (inames[0], os_msg)
388 if not AskUser(usertext):
391 jex = JobExecutor(verbose=multi_on, opts=opts)
392 for instance_name in inames:
393 op = opcodes.OpInstanceReinstall(instance_name=instance_name,
395 force_variant=opts.force_variant,
396 osparams=opts.osparams)
397 jex.QueueJob(instance_name, op)
399 results = jex.WaitOrShow(not opts.submit_only)
401 if compat.all(map(compat.fst, results)):
402 return constants.EXIT_SUCCESS
404 return constants.EXIT_FAILURE
407 def RemoveInstance(opts, args):
408 """Remove an instance.
410 @param opts: the command line options selected by the user
412 @param args: should contain only one element, the name of
413 the instance to be removed
415 @return: the desired exit code
418 instance_name = args[0]
423 _EnsureInstancesExist(cl, [instance_name])
425 usertext = ("This will remove the volumes of the instance %s"
426 " (including mirrors), thus removing all the data"
427 " of the instance. Continue?") % instance_name
428 if not AskUser(usertext):
431 op = opcodes.OpInstanceRemove(instance_name=instance_name,
432 ignore_failures=opts.ignore_failures,
433 shutdown_timeout=opts.shutdown_timeout)
434 SubmitOrSend(op, opts, cl=cl)
438 def RenameInstance(opts, args):
439 """Rename an instance.
441 @param opts: the command line options selected by the user
443 @param args: should contain two elements, the old and the
446 @return: the desired exit code
449 if not opts.name_check:
450 if not AskUser("As you disabled the check of the DNS entry, please verify"
451 " that '%s' is a FQDN. Continue?" % args[1]):
454 op = opcodes.OpInstanceRename(instance_name=args[0],
456 ip_check=opts.ip_check,
457 name_check=opts.name_check)
458 result = SubmitOrSend(op, opts)
461 ToStdout("Instance '%s' renamed to '%s'", args[0], result)
466 def ActivateDisks(opts, args):
467 """Activate an instance's disks.
469 This serves two purposes:
470 - it allows (as long as the instance is not running)
471 mounting the disks and modifying them from the node
472 - it repairs inactive secondary drbds
474 @param opts: the command line options selected by the user
476 @param args: should contain only one element, the instance name
478 @return: the desired exit code
481 instance_name = args[0]
482 op = opcodes.OpInstanceActivateDisks(instance_name=instance_name,
483 ignore_size=opts.ignore_size,
484 wait_for_sync=opts.wait_for_sync)
485 disks_info = SubmitOrSend(op, opts)
486 for host, iname, nname in disks_info:
487 ToStdout("%s:%s:%s", host, iname, nname)
491 def DeactivateDisks(opts, args):
492 """Deactivate an instance's disks.
494 This function takes the instance name, looks for its primary node
495 and the tries to shutdown its block devices on that node.
497 @param opts: the command line options selected by the user
499 @param args: should contain only one element, the instance name
501 @return: the desired exit code
504 instance_name = args[0]
505 op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name,
507 SubmitOrSend(op, opts)
511 def RecreateDisks(opts, args):
512 """Recreate an instance's disks.
514 @param opts: the command line options selected by the user
516 @param args: should contain only one element, the instance name
518 @return: the desired exit code
521 instance_name = args[0]
526 for didx, ddict in opts.disks:
529 if not ht.TDict(ddict):
530 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
531 raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
533 if constants.IDISK_SIZE in ddict:
535 ddict[constants.IDISK_SIZE] = \
536 utils.ParseUnit(ddict[constants.IDISK_SIZE])
537 except ValueError, err:
538 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
539 (didx, err), errors.ECODE_INVAL)
541 disks.append((didx, ddict))
543 # TODO: Verify modifyable parameters (already done in
544 # LUInstanceRecreateDisks, but it'd be nice to have in the client)
548 msg = "At most one of either --nodes or --iallocator can be passed"
549 raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
550 pnode, snode = SplitNodeOption(opts.node)
552 if snode is not None:
557 op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
558 disks=disks, nodes=nodes,
559 iallocator=opts.iallocator)
560 SubmitOrSend(op, opts)
565 def GrowDisk(opts, args):
566 """Grow an instance's disks.
568 @param opts: the command line options selected by the user
570 @param args: should contain three elements, the target instance name,
571 the target disk id, and the target growth
573 @return: the desired exit code
580 except (TypeError, ValueError), err:
581 raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
584 amount = utils.ParseUnit(args[2])
585 except errors.UnitParseError:
586 raise errors.OpPrereqError("Can't parse the given amount '%s'" % args[2],
588 op = opcodes.OpInstanceGrowDisk(instance_name=instance,
589 disk=disk, amount=amount,
590 wait_for_sync=opts.wait_for_sync,
591 absolute=opts.absolute)
592 SubmitOrSend(op, opts)
596 def _StartupInstance(name, opts):
597 """Startup instances.
599 This returns the opcode to start an instance, and its decorator will
600 wrap this into a loop starting all desired instances.
602 @param name: the name of the instance to act on
603 @param opts: the command line options selected by the user
604 @return: the opcode needed for the operation
607 op = opcodes.OpInstanceStartup(instance_name=name,
609 ignore_offline_nodes=opts.ignore_offline,
610 no_remember=opts.no_remember,
611 startup_paused=opts.startup_paused)
612 # do not add these parameters to the opcode unless they're defined
614 op.hvparams = opts.hvparams
616 op.beparams = opts.beparams
620 def _RebootInstance(name, opts):
621 """Reboot instance(s).
623 This returns the opcode to reboot an instance, and its decorator
624 will wrap this into a loop rebooting all desired instances.
626 @param name: the name of the instance to act on
627 @param opts: the command line options selected by the user
628 @return: the opcode needed for the operation
631 return opcodes.OpInstanceReboot(instance_name=name,
632 reboot_type=opts.reboot_type,
633 ignore_secondaries=opts.ignore_secondaries,
634 shutdown_timeout=opts.shutdown_timeout,
635 reason=(constants.INSTANCE_REASON_SOURCE_CLI,
639 def _ShutdownInstance(name, opts):
640 """Shutdown an instance.
642 This returns the opcode to shutdown an instance, and its decorator
643 will wrap this into a loop shutting down all desired instances.
645 @param name: the name of the instance to act on
646 @param opts: the command line options selected by the user
647 @return: the opcode needed for the operation
650 return opcodes.OpInstanceShutdown(instance_name=name,
652 timeout=opts.timeout,
653 ignore_offline_nodes=opts.ignore_offline,
654 no_remember=opts.no_remember)
657 def ReplaceDisks(opts, args):
658 """Replace the disks of an instance
660 @param opts: the command line options selected by the user
662 @param args: should contain only one element, the instance name
664 @return: the desired exit code
667 new_2ndary = opts.dst_node
668 iallocator = opts.iallocator
669 if opts.disks is None:
673 disks = [int(i) for i in opts.disks.split(",")]
674 except (TypeError, ValueError), err:
675 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
677 cnt = [opts.on_primary, opts.on_secondary, opts.auto,
678 new_2ndary is not None, iallocator is not None].count(True)
680 raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -I"
681 " options must be passed", errors.ECODE_INVAL)
682 elif opts.on_primary:
683 mode = constants.REPLACE_DISK_PRI
684 elif opts.on_secondary:
685 mode = constants.REPLACE_DISK_SEC
687 mode = constants.REPLACE_DISK_AUTO
689 raise errors.OpPrereqError("Cannot specify disks when using automatic"
690 " mode", errors.ECODE_INVAL)
691 elif new_2ndary is not None or iallocator is not None:
693 mode = constants.REPLACE_DISK_CHG
695 op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
696 remote_node=new_2ndary, mode=mode,
697 iallocator=iallocator,
698 early_release=opts.early_release,
699 ignore_ipolicy=opts.ignore_ipolicy)
700 SubmitOrSend(op, opts)
704 def FailoverInstance(opts, args):
705 """Failover an instance.
707 The failover is done by shutting it down on its present node and
708 starting it on the secondary.
710 @param opts: the command line options selected by the user
712 @param args: should contain only one element, the instance name
714 @return: the desired exit code
718 instance_name = args[0]
720 iallocator = opts.iallocator
721 target_node = opts.dst_node
723 if iallocator and target_node:
724 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
725 " node (-n) but not both", errors.ECODE_INVAL)
728 _EnsureInstancesExist(cl, [instance_name])
730 usertext = ("Failover will happen to image %s."
731 " This requires a shutdown of the instance. Continue?" %
733 if not AskUser(usertext):
736 op = opcodes.OpInstanceFailover(instance_name=instance_name,
737 ignore_consistency=opts.ignore_consistency,
738 shutdown_timeout=opts.shutdown_timeout,
739 iallocator=iallocator,
740 target_node=target_node,
741 ignore_ipolicy=opts.ignore_ipolicy)
742 SubmitOrSend(op, opts, cl=cl)
746 def MigrateInstance(opts, args):
747 """Migrate an instance.
749 The migrate is done without shutdown.
751 @param opts: the command line options selected by the user
753 @param args: should contain only one element, the instance name
755 @return: the desired exit code
759 instance_name = args[0]
761 iallocator = opts.iallocator
762 target_node = opts.dst_node
764 if iallocator and target_node:
765 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
766 " node (-n) but not both", errors.ECODE_INVAL)
769 _EnsureInstancesExist(cl, [instance_name])
772 usertext = ("Instance %s will be recovered from a failed migration."
773 " Note that the migration procedure (including cleanup)" %
776 usertext = ("Instance %s will be migrated. Note that migration" %
778 usertext += (" might impact the instance if anything goes wrong"
779 " (e.g. due to bugs in the hypervisor). Continue?")
780 if not AskUser(usertext):
783 # this should be removed once --non-live is deprecated
784 if not opts.live and opts.migration_mode is not None:
785 raise errors.OpPrereqError("Only one of the --non-live and "
786 "--migration-mode options can be passed",
788 if not opts.live: # --non-live passed
789 mode = constants.HT_MIGRATION_NONLIVE
791 mode = opts.migration_mode
793 op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
794 cleanup=opts.cleanup, iallocator=iallocator,
795 target_node=target_node,
796 allow_failover=opts.allow_failover,
797 allow_runtime_changes=opts.allow_runtime_chgs,
798 ignore_ipolicy=opts.ignore_ipolicy)
799 SubmitOrSend(op, cl=cl, opts=opts)
803 def MoveInstance(opts, args):
806 @param opts: the command line options selected by the user
808 @param args: should contain only one element, the instance name
810 @return: the desired exit code
814 instance_name = args[0]
818 usertext = ("Instance %s will be moved."
819 " This requires a shutdown of the instance. Continue?" %
821 if not AskUser(usertext):
824 op = opcodes.OpInstanceMove(instance_name=instance_name,
825 target_node=opts.node,
826 shutdown_timeout=opts.shutdown_timeout,
827 ignore_consistency=opts.ignore_consistency,
828 ignore_ipolicy=opts.ignore_ipolicy)
829 SubmitOrSend(op, opts, cl=cl)
833 def ConnectToInstanceConsole(opts, args):
834 """Connect to the console of an instance.
836 @param opts: the command line options selected by the user
838 @param args: should contain only one element, the instance name
840 @return: the desired exit code
843 instance_name = args[0]
847 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
848 ((console_data, oper_state), ) = \
849 cl.QueryInstances([instance_name], ["console", "oper_state"], False)
851 # Ensure client connection is closed while external commands are run
858 # Instance is running
859 raise errors.OpExecError("Console information for instance %s is"
860 " unavailable" % instance_name)
862 raise errors.OpExecError("Instance %s is not running, can't get console" %
865 return _DoConsole(objects.InstanceConsole.FromDict(console_data),
866 opts.show_command, cluster_name)
869 def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
870 _runcmd_fn=utils.RunCmd):
871 """Acts based on the result of L{opcodes.OpInstanceConsole}.
873 @type console: L{objects.InstanceConsole}
874 @param console: Console object
875 @type show_command: bool
876 @param show_command: Whether to just display commands
877 @type cluster_name: string
878 @param cluster_name: Cluster name as retrieved from master daemon
881 assert console.Validate()
883 if console.kind == constants.CONS_MESSAGE:
884 feedback_fn(console.message)
885 elif console.kind == constants.CONS_VNC:
886 feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
887 " URL <vnc://%s:%s/>",
888 console.instance, console.host, console.port,
889 console.display, console.host, console.port)
890 elif console.kind == constants.CONS_SPICE:
891 feedback_fn("Instance %s has SPICE listening on %s:%s", console.instance,
892 console.host, console.port)
893 elif console.kind == constants.CONS_SSH:
894 # Convert to string if not already one
895 if isinstance(console.command, basestring):
896 cmd = console.command
898 cmd = utils.ShellQuoteArgs(console.command)
900 srun = ssh.SshRunner(cluster_name=cluster_name)
901 ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
902 batch=True, quiet=False, tty=True)
905 feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
907 result = _runcmd_fn(ssh_cmd, interactive=True)
909 logging.error("Console command \"%s\" failed with reason '%s' and"
910 " output %r", result.cmd, result.fail_reason,
912 raise errors.OpExecError("Connection to console of instance %s failed,"
913 " please check cluster configuration" %
916 raise errors.GenericError("Unknown console type '%s'" % console.kind)
918 return constants.EXIT_SUCCESS
921 def _FormatLogicalID(dev_type, logical_id, roman):
922 """Formats the logical_id of a disk.
925 if dev_type == constants.LD_DRBD8:
926 node_a, node_b, port, minor_a, minor_b, key = logical_id
928 ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
930 ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
932 ("port", compat.TryToRoman(port, convert=roman)),
935 elif dev_type == constants.LD_LV:
936 vg_name, lv_name = logical_id
937 data = ["%s/%s" % (vg_name, lv_name)]
939 data = [str(logical_id)]
944 def _FormatBlockDevInfo(idx, top_level, dev, roman):
945 """Show block device information.
947 This is only used by L{ShowInstanceConfig}, but it's too big to be
948 left for an inline definition.
951 @param idx: the index of the current disk
952 @type top_level: boolean
953 @param top_level: if this a top-level disk?
955 @param dev: dictionary with disk information
957 @param roman: whether to try to use roman integers
958 @return: a list of either strings, tuples or lists
959 (which should be formatted at a higher indent level)
962 def helper(dtype, status):
963 """Format one line for physical device status.
966 @param dtype: a constant from the L{constants.LDS_BLOCK} set
968 @param status: a tuple as returned from L{backend.FindBlockDevice}
969 @return: the string representing the status
975 (path, major, minor, syncp, estt, degr, ldisk_status) = status
979 major_string = str(compat.TryToRoman(major, convert=roman))
984 minor_string = str(compat.TryToRoman(minor, convert=roman))
986 txt += ("%s (%s:%s)" % (path, major_string, minor_string))
987 if dtype in (constants.LD_DRBD8, ):
988 if syncp is not None:
989 sync_text = "*RECOVERING* %5.2f%%," % syncp
991 sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
993 sync_text += " ETA unknown"
995 sync_text = "in sync"
997 degr_text = "*DEGRADED*"
1000 if ldisk_status == constants.LDS_FAULTY:
1001 ldisk_text = " *MISSING DISK*"
1002 elif ldisk_status == constants.LDS_UNKNOWN:
1003 ldisk_text = " *UNCERTAIN STATE*"
1006 txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1007 elif dtype == constants.LD_LV:
1008 if ldisk_status == constants.LDS_FAULTY:
1009 ldisk_text = " *FAILED* (failed drive?)"
1017 if dev["iv_name"] is not None:
1018 txt = dev["iv_name"]
1020 txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1022 txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1023 if isinstance(dev["size"], int):
1024 nice_size = utils.FormatUnit(dev["size"], "h")
1026 nice_size = dev["size"]
1027 d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1030 data.append(("access mode", dev["mode"]))
1031 if dev["logical_id"] is not None:
1033 l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1035 l_id = [str(dev["logical_id"])]
1037 data.append(("logical_id", l_id[0]))
1040 elif dev["physical_id"] is not None:
1041 data.append("physical_id:")
1042 data.append([dev["physical_id"]])
1045 data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1048 data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1051 data.append("child devices:")
1052 for c_idx, child in enumerate(dev["children"]):
1053 data.append(_FormatBlockDevInfo(c_idx, False, child, roman))
1058 def _FormatList(buf, data, indent_level):
1059 """Formats a list of data at a given indent level.
1061 If the element of the list is:
1062 - a string, it is simply formatted as is
1063 - a tuple, it will be split into key, value and the all the
1064 values in a list will be aligned all at the same start column
1065 - a list, will be recursively formatted
1068 @param buf: the buffer into which we write the output
1069 @param data: the list to format
1070 @type indent_level: int
1071 @param indent_level: the indent level to format at
1074 max_tlen = max([len(elem[0]) for elem in data
1075 if isinstance(elem, tuple)] or [0])
1077 if isinstance(elem, basestring):
1078 buf.write("%*s%s\n" % (2 * indent_level, "", elem))
1079 elif isinstance(elem, tuple):
1081 spacer = "%*s" % (max_tlen - len(key), "")
1082 buf.write("%*s%s:%s %s\n" % (2 * indent_level, "", key, spacer, value))
1083 elif isinstance(elem, list):
1084 _FormatList(buf, elem, indent_level + 1)
1087 def ShowInstanceConfig(opts, args):
1088 """Compute instance run-time status.
1090 @param opts: the command line options selected by the user
1092 @param args: either an empty list, and then we query all
1093 instances, or should contain a list of instance names
1095 @return: the desired exit code
1098 if not args and not opts.show_all:
1099 ToStderr("No instance selected."
1100 " Please pass in --all if you want to query all instances.\n"
1101 "Note that this can take a long time on a big cluster.")
1103 elif args and opts.show_all:
1104 ToStderr("Cannot use --all if you specify instance names.")
1108 op = opcodes.OpInstanceQueryData(instances=args, static=opts.static,
1109 use_locking=not opts.static)
1110 result = SubmitOpCode(op, opts=opts)
1112 ToStdout("No instances.")
1117 for instance_name in result:
1118 instance = result[instance_name]
1119 buf.write("Instance name: %s\n" % instance["name"])
1120 buf.write("UUID: %s\n" % instance["uuid"])
1121 buf.write("Serial number: %s\n" %
1122 compat.TryToRoman(instance["serial_no"],
1123 convert=opts.roman_integers))
1124 buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1125 buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1126 buf.write("State: configured to be %s" % instance["config_state"])
1127 if instance["run_state"]:
1128 buf.write(", actual state is %s" % instance["run_state"])
1130 ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1131 ## instance["auto_balance"])
1132 buf.write(" Nodes:\n")
1133 buf.write(" - primary: %s\n" % instance["pnode"])
1134 buf.write(" group: %s (UUID %s)\n" %
1135 (instance["pnode_group_name"], instance["pnode_group_uuid"]))
1136 buf.write(" - secondaries: %s\n" %
1137 utils.CommaJoin("%s (group %s, group UUID %s)" %
1138 (name, group_name, group_uuid)
1139 for (name, group_name, group_uuid) in
1140 zip(instance["snodes"],
1141 instance["snodes_group_names"],
1142 instance["snodes_group_uuids"])))
1143 buf.write(" Operating system: %s\n" % instance["os"])
1144 FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1146 if "network_port" in instance:
1147 buf.write(" Allocated network port: %s\n" %
1148 compat.TryToRoman(instance["network_port"],
1149 convert=opts.roman_integers))
1150 buf.write(" Hypervisor: %s\n" % instance["hypervisor"])
1152 # custom VNC console information
1153 vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1155 if vnc_bind_address:
1156 port = instance["network_port"]
1157 display = int(port) - constants.VNC_BASE_PORT
1158 if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1159 vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1162 elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1163 vnc_console_port = ("%s:%s (node %s) (display %s)" %
1164 (vnc_bind_address, port,
1165 instance["pnode"], display))
1167 # vnc bind address is a file
1168 vnc_console_port = "%s:%s" % (instance["pnode"],
1170 buf.write(" - console connection: vnc to %s\n" % vnc_console_port)
1172 FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1174 buf.write(" Hardware:\n")
1175 # deprecated "memory" value, kept for one version for compatibility
1176 # TODO(ganeti 2.7) remove.
1177 be_actual = copy.deepcopy(instance["be_actual"])
1178 be_actual["memory"] = be_actual[constants.BE_MAXMEM]
1179 FormatParameterDict(buf, instance["be_instance"], be_actual, level=2)
1180 # TODO(ganeti 2.7) rework the NICs as well
1181 buf.write(" - NICs:\n")
1182 for idx, (ip, mac, mode, link, _, netinfo) in enumerate(instance["nics"]):
1185 network_name = netinfo["name"]
1186 buf.write(" - nic/%d: MAC: %s, IP: %s,"
1187 " mode: %s, link: %s, network: %s\n" %
1188 (idx, mac, ip, mode, link, network_name))
1189 buf.write(" Disk template: %s\n" % instance["disk_template"])
1190 buf.write(" Disks:\n")
1192 for idx, device in enumerate(instance["disks"]):
1193 _FormatList(buf, _FormatBlockDevInfo(idx, True, device,
1194 opts.roman_integers), 2)
1196 ToStdout(buf.getvalue().rstrip("\n"))
1200 def _ConvertNicDiskModifications(mods):
1201 """Converts NIC/disk modifications from CLI to opcode.
1203 When L{opcodes.OpInstanceSetParams} was changed to support adding/removing
1204 disks at arbitrary indices, its parameter format changed. This function
1205 converts legacy requests (e.g. "--net add" or "--disk add:size=4G") to the
1206 newer format and adds support for new-style requests (e.g. "--new 4:add").
1208 @type mods: list of tuples
1209 @param mods: Modifications as given by command line parser
1210 @rtype: list of tuples
1211 @return: Modifications as understood by L{opcodes.OpInstanceSetParams}
1216 for (idx, params) in mods:
1217 if idx == constants.DDM_ADD:
1218 # Add item as last item (legacy interface)
1219 action = constants.DDM_ADD
1221 elif idx == constants.DDM_REMOVE:
1222 # Remove last item (legacy interface)
1223 action = constants.DDM_REMOVE
1226 # Modifications and adding/removing at arbitrary indices
1229 except (TypeError, ValueError):
1230 raise errors.OpPrereqError("Non-numeric index '%s'" % idx,
1233 add = params.pop(constants.DDM_ADD, _MISSING)
1234 remove = params.pop(constants.DDM_REMOVE, _MISSING)
1235 modify = params.pop(constants.DDM_MODIFY, _MISSING)
1237 if modify is _MISSING:
1238 if not (add is _MISSING or remove is _MISSING):
1239 raise errors.OpPrereqError("Cannot add and remove at the same time",
1241 elif add is not _MISSING:
1242 action = constants.DDM_ADD
1243 elif remove is not _MISSING:
1244 action = constants.DDM_REMOVE
1246 action = constants.DDM_MODIFY
1248 elif add is _MISSING and remove is _MISSING:
1249 action = constants.DDM_MODIFY
1251 raise errors.OpPrereqError("Cannot modify and add/remove at the"
1252 " same time", errors.ECODE_INVAL)
1254 assert not (constants.DDMS_VALUES_WITH_MODIFY & set(params.keys()))
1256 if action == constants.DDM_REMOVE and params:
1257 raise errors.OpPrereqError("Not accepting parameters on removal",
1260 result.append((action, idxno, params))
1265 def _ParseDiskSizes(mods):
1266 """Parses disk sizes in parameters.
1269 for (action, _, params) in mods:
1270 if params and constants.IDISK_SIZE in params:
1271 params[constants.IDISK_SIZE] = \
1272 utils.ParseUnit(params[constants.IDISK_SIZE])
1273 elif action == constants.DDM_ADD:
1274 raise errors.OpPrereqError("Missing required parameter 'size'",
1280 def SetInstanceParams(opts, args):
1281 """Modifies an instance.
1283 All parameters take effect only at the next restart of the instance.
1285 @param opts: the command line options selected by the user
1287 @param args: should contain only one element, the instance name
1289 @return: the desired exit code
1292 if not (opts.nics or opts.disks or opts.disk_template or
1293 opts.hvparams or opts.beparams or opts.os or opts.osparams or
1294 opts.offline_inst or opts.online_inst or opts.runtime_mem):
1295 ToStderr("Please give at least one of the parameters.")
1298 for param in opts.beparams:
1299 if isinstance(opts.beparams[param], basestring):
1300 if opts.beparams[param].lower() == "default":
1301 opts.beparams[param] = constants.VALUE_DEFAULT
1303 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT,
1304 allowed_values=[constants.VALUE_DEFAULT])
1306 for param in opts.hvparams:
1307 if isinstance(opts.hvparams[param], basestring):
1308 if opts.hvparams[param].lower() == "default":
1309 opts.hvparams[param] = constants.VALUE_DEFAULT
1311 utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1312 allowed_values=[constants.VALUE_DEFAULT])
1314 nics = _ConvertNicDiskModifications(opts.nics)
1315 disks = _ParseDiskSizes(_ConvertNicDiskModifications(opts.disks))
1317 if (opts.disk_template and
1318 opts.disk_template in constants.DTS_INT_MIRROR and
1320 ToStderr("Changing the disk template to a mirrored one requires"
1321 " specifying a secondary node")
1324 if opts.offline_inst:
1326 elif opts.online_inst:
1331 op = opcodes.OpInstanceSetParams(instance_name=args[0],
1334 disk_template=opts.disk_template,
1335 remote_node=opts.node,
1336 hvparams=opts.hvparams,
1337 beparams=opts.beparams,
1338 runtime_mem=opts.runtime_mem,
1340 osparams=opts.osparams,
1341 force_variant=opts.force_variant,
1343 wait_for_sync=opts.wait_for_sync,
1345 conflicts_check=opts.conflicts_check,
1346 ignore_ipolicy=opts.ignore_ipolicy)
1348 # even if here we process the result, we allow submit only
1349 result = SubmitOrSend(op, opts)
1352 ToStdout("Modified instance %s", args[0])
1353 for param, data in result:
1354 ToStdout(" - %-5s -> %s", param, data)
1355 ToStdout("Please don't forget that most parameters take effect"
1356 " only at the next (re)start of the instance initiated by"
1357 " ganeti; restarting from within the instance will"
1362 def ChangeGroup(opts, args):
1363 """Moves an instance to another group.
1366 (instance_name, ) = args
1370 op = opcodes.OpInstanceChangeGroup(instance_name=instance_name,
1371 iallocator=opts.iallocator,
1372 target_groups=opts.to,
1373 early_release=opts.early_release)
1374 result = SubmitOrSend(op, opts, cl=cl)
1376 # Keep track of submitted jobs
1377 jex = JobExecutor(cl=cl, opts=opts)
1379 for (status, job_id) in result[constants.JOB_IDS_KEY]:
1380 jex.AddJobId(None, status, job_id)
1382 results = jex.GetResults()
1383 bad_cnt = len([row for row in results if not row[0]])
1385 ToStdout("Instance '%s' changed group successfully.", instance_name)
1386 rcode = constants.EXIT_SUCCESS
1388 ToStdout("There were %s errors while changing group of instance '%s'.",
1389 bad_cnt, instance_name)
1390 rcode = constants.EXIT_FAILURE
1395 # multi-instance selection options
1396 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1397 help="Do not ask for confirmation when more than"
1398 " one instance is affected",
1399 action="store_true", default=False)
1401 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1402 help="Filter by nodes (primary only)",
1403 const=_EXPAND_NODES_PRI, action="store_const")
1405 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1406 help="Filter by nodes (secondary only)",
1407 const=_EXPAND_NODES_SEC, action="store_const")
1409 m_node_opt = cli_option("--node", dest="multi_mode",
1410 help="Filter by nodes (primary and secondary)",
1411 const=_EXPAND_NODES_BOTH, action="store_const")
1413 m_clust_opt = cli_option("--all", dest="multi_mode",
1414 help="Select all instances in the cluster",
1415 const=_EXPAND_CLUSTER, action="store_const")
1417 m_inst_opt = cli_option("--instance", dest="multi_mode",
1418 help="Filter by instance name [default]",
1419 const=_EXPAND_INSTANCES, action="store_const")
1421 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1422 help="Filter by node tag",
1423 const=_EXPAND_NODES_BOTH_BY_TAGS,
1424 action="store_const")
1426 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1427 help="Filter by primary node tag",
1428 const=_EXPAND_NODES_PRI_BY_TAGS,
1429 action="store_const")
1431 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1432 help="Filter by secondary node tag",
1433 const=_EXPAND_NODES_SEC_BY_TAGS,
1434 action="store_const")
1436 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1437 help="Filter by instance tag",
1438 const=_EXPAND_INSTANCES_BY_TAGS,
1439 action="store_const")
1441 # this is defined separately due to readability only
1452 AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1453 "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1454 "Creates and adds a new instance to the cluster"),
1456 BatchCreate, [ArgFile(min=1, max=1)],
1457 [DRY_RUN_OPT, PRIORITY_OPT, IALLOCATOR_OPT, SUBMIT_OPT],
1459 "Create a bunch of instances based on specs in the file."),
1461 ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1462 [SHOWCMD_OPT, PRIORITY_OPT],
1463 "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1465 FailoverInstance, ARGS_ONE_INSTANCE,
1466 [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1467 DRY_RUN_OPT, PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT,
1468 IGNORE_IPOLICY_OPT],
1469 "[-f] <instance>", "Stops the instance, changes its primary node and"
1470 " (if it was originally running) starts it on the new node"
1471 " (the secondary for mirrored instances or any node"
1472 " for shared storage)."),
1474 MigrateInstance, ARGS_ONE_INSTANCE,
1475 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1476 PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT, ALLOW_FAILOVER_OPT,
1477 IGNORE_IPOLICY_OPT, NORUNTIME_CHGS_OPT, SUBMIT_OPT],
1478 "[-f] <instance>", "Migrate instance to its secondary node"
1479 " (only for mirrored instances)"),
1481 MoveInstance, ARGS_ONE_INSTANCE,
1482 [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1483 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_CONSIST_OPT, IGNORE_IPOLICY_OPT],
1484 "[-f] <instance>", "Move instance to an arbitrary node"
1485 " (only for instances of type file and lv)"),
1487 ShowInstanceConfig, ARGS_MANY_INSTANCES,
1488 [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1489 "[-s] {--all | <instance>...}",
1490 "Show information on the specified instance(s)"),
1492 ListInstances, ARGS_MANY_INSTANCES,
1493 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
1496 "Lists the instances and their status. The available fields can be shown"
1497 " using the \"list-fields\" command (see the man page for details)."
1498 " The default field list is (in order): %s." %
1499 utils.CommaJoin(_LIST_DEF_FIELDS),
1502 ListInstanceFields, [ArgUnknown()],
1503 [NOHDR_OPT, SEP_OPT],
1505 "Lists all available fields for instances"),
1507 ReinstallInstance, [ArgInstance()],
1508 [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1509 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1510 m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1511 SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1512 "[-f] <instance>", "Reinstall a stopped instance"),
1514 RemoveInstance, ARGS_ONE_INSTANCE,
1515 [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1516 DRY_RUN_OPT, PRIORITY_OPT],
1517 "[-f] <instance>", "Shuts down the instance and removes it"),
1520 [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1521 [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1522 "<instance> <new_name>", "Rename the instance"),
1524 ReplaceDisks, ARGS_ONE_INSTANCE,
1525 [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1526 NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1527 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_IPOLICY_OPT],
1528 "[-s|-p|-a|-n NODE|-I NAME] <instance>",
1529 "Replaces disks for the instance"),
1531 SetInstanceParams, ARGS_ONE_INSTANCE,
1532 [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1533 DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1534 OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT, NWSYNC_OPT, OFFLINE_INST_OPT,
1535 ONLINE_INST_OPT, IGNORE_IPOLICY_OPT, RUNTIME_MEM_OPT,
1536 NOCONFLICTSCHECK_OPT],
1537 "<instance>", "Alters the parameters of an instance"),
1539 GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1540 [FORCE_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1541 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1542 m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1543 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT, NO_REMEMBER_OPT],
1544 "<instance>", "Stops an instance"),
1546 GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1547 [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1548 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1549 m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1550 BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT,
1551 NO_REMEMBER_OPT, STARTUP_PAUSED_OPT],
1552 "<instance>", "Starts an instance"),
1554 GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1555 [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1556 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1557 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1558 m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT,
1560 "<instance>", "Reboots an instance"),
1562 ActivateDisks, ARGS_ONE_INSTANCE,
1563 [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT, WFSYNC_OPT],
1564 "<instance>", "Activate an instance's disks"),
1565 "deactivate-disks": (
1566 DeactivateDisks, ARGS_ONE_INSTANCE,
1567 [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1568 "[-f] <instance>", "Deactivate an instance's disks"),
1570 RecreateDisks, ARGS_ONE_INSTANCE,
1571 [SUBMIT_OPT, DISK_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT,
1573 "<instance>", "Recreate an instance's disks"),
1576 [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1577 ArgUnknown(min=1, max=1)],
1578 [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT, ABSOLUTE_OPT],
1579 "<instance> <disk> <size>", "Grow an instance's disk"),
1581 ChangeGroup, ARGS_ONE_INSTANCE,
1582 [TO_GROUP_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT, PRIORITY_OPT, SUBMIT_OPT],
1583 "[-I <iallocator>] [--to <group>]", "Change group of instance"),
1585 ListTags, ARGS_ONE_INSTANCE, [],
1586 "<instance_name>", "List the tags of the given instance"),
1588 AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1589 [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
1590 "<instance_name> tag...", "Add tags to the given instance"),
1592 RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1593 [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
1594 "<instance_name> tag...", "Remove tags from given instance"),
1597 #: dictionary with aliases for commands
1606 return GenericMain(commands, aliases=aliases,
1607 override={"tag_type": constants.TAG_INSTANCE},
1608 env_override=_ENV_OVERRIDE)