4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 """Instance related commands"""
23 # pylint: disable=W0401,W0614,C0103
24 # W0401: Wildcard import ganeti.cli
25 # W0614: Unused import %s from wildcard import (since we need cli)
26 # C0103: Invalid name gnt-instance
32 from cStringIO import StringIO
34 from ganeti.cli import *
35 from ganeti import opcodes
36 from ganeti import constants
37 from ganeti import compat
38 from ganeti import utils
39 from ganeti import errors
40 from ganeti import netutils
41 from ganeti import ssh
42 from ganeti import objects
46 _EXPAND_CLUSTER = "cluster"
47 _EXPAND_NODES_BOTH = "nodes"
48 _EXPAND_NODES_PRI = "nodes-pri"
49 _EXPAND_NODES_SEC = "nodes-sec"
50 _EXPAND_NODES_BOTH_BY_TAGS = "nodes-by-tags"
51 _EXPAND_NODES_PRI_BY_TAGS = "nodes-pri-by-tags"
52 _EXPAND_NODES_SEC_BY_TAGS = "nodes-sec-by-tags"
53 _EXPAND_INSTANCES = "instances"
54 _EXPAND_INSTANCES_BY_TAGS = "instances-by-tags"
56 _EXPAND_NODES_TAGS_MODES = frozenset([
57 _EXPAND_NODES_BOTH_BY_TAGS,
58 _EXPAND_NODES_PRI_BY_TAGS,
59 _EXPAND_NODES_SEC_BY_TAGS,
63 #: default list of options for L{ListInstances}
65 "name", "hypervisor", "os", "pnode", "status", "oper_ram",
70 _ENV_OVERRIDE = frozenset(["list"])
72 _INST_DATA_VAL = ht.TListOf(ht.TDict)
75 def _ExpandMultiNames(mode, names, client=None):
76 """Expand the given names using the passed mode.
78 For _EXPAND_CLUSTER, all instances will be returned. For
79 _EXPAND_NODES_PRI/SEC, all instances having those nodes as
80 primary/secondary will be returned. For _EXPAND_NODES_BOTH, all
81 instances having those nodes as either primary or secondary will be
82 returned. For _EXPAND_INSTANCES, the given instances will be
85 @param mode: one of L{_EXPAND_CLUSTER}, L{_EXPAND_NODES_BOTH},
86 L{_EXPAND_NODES_PRI}, L{_EXPAND_NODES_SEC} or
88 @param names: a list of names; for cluster, it must be empty,
89 and for node and instance it must be a list of valid item
90 names (short names are valid as usual, e.g. node1 instead of
93 @return: the list of names after the expansion
94 @raise errors.ProgrammerError: for unknown selection type
95 @raise errors.OpPrereqError: for invalid input parameters
98 # pylint: disable=W0142
102 if mode == _EXPAND_CLUSTER:
104 raise errors.OpPrereqError("Cluster filter mode takes no arguments",
106 idata = client.QueryInstances([], ["name"], False)
107 inames = [row[0] for row in idata]
109 elif (mode in _EXPAND_NODES_TAGS_MODES or
110 mode in (_EXPAND_NODES_BOTH, _EXPAND_NODES_PRI, _EXPAND_NODES_SEC)):
111 if mode in _EXPAND_NODES_TAGS_MODES:
113 raise errors.OpPrereqError("No node tags passed", errors.ECODE_INVAL)
114 ndata = client.QueryNodes([], ["name", "pinst_list",
115 "sinst_list", "tags"], False)
116 ndata = [row for row in ndata if set(row[3]).intersection(names)]
119 raise errors.OpPrereqError("No node names passed", errors.ECODE_INVAL)
120 ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
123 ipri = [row[1] for row in ndata]
124 pri_names = list(itertools.chain(*ipri))
125 isec = [row[2] for row in ndata]
126 sec_names = list(itertools.chain(*isec))
127 if mode in (_EXPAND_NODES_BOTH, _EXPAND_NODES_BOTH_BY_TAGS):
128 inames = pri_names + sec_names
129 elif mode in (_EXPAND_NODES_PRI, _EXPAND_NODES_PRI_BY_TAGS):
131 elif mode in (_EXPAND_NODES_SEC, _EXPAND_NODES_SEC_BY_TAGS):
134 raise errors.ProgrammerError("Unhandled shutdown type")
135 elif mode == _EXPAND_INSTANCES:
137 raise errors.OpPrereqError("No instance names passed",
139 idata = client.QueryInstances(names, ["name"], False)
140 inames = [row[0] for row in idata]
141 elif mode == _EXPAND_INSTANCES_BY_TAGS:
143 raise errors.OpPrereqError("No instance tags passed",
145 idata = client.QueryInstances([], ["name", "tags"], False)
146 inames = [row[0] for row in idata if set(row[1]).intersection(names)]
148 raise errors.OpPrereqError("Unknown mode '%s'" % mode, errors.ECODE_INVAL)
153 def _EnsureInstancesExist(client, names):
154 """Check for and ensure the given instance names exist.
156 This function will raise an OpPrereqError in case they don't
157 exist. Otherwise it will exit cleanly.
159 @type client: L{ganeti.luxi.Client}
160 @param client: the client to use for the query
162 @param names: the list of instance names to query
163 @raise errors.OpPrereqError: in case any instance is missing
166 # TODO: change LUInstanceQuery to that it actually returns None
167 # instead of raising an exception, or devise a better mechanism
168 result = client.QueryInstances(names, ["name"], False)
169 for orig_name, row in zip(names, result):
171 raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name,
175 def GenericManyOps(operation, fn):
176 """Generic multi-instance operations.
178 The will return a wrapper that processes the options and arguments
179 given, and uses the passed function to build the opcode needed for
180 the specific operation. Thus all the generic loop/confirmation code
181 is abstracted into this function.
184 def realfn(opts, args):
185 if opts.multi_mode is None:
186 opts.multi_mode = _EXPAND_INSTANCES
188 inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
190 if opts.multi_mode == _EXPAND_CLUSTER:
191 ToStdout("Cluster is empty, no instances to shutdown")
193 raise errors.OpPrereqError("Selection filter does not match"
194 " any instances", errors.ECODE_INVAL)
195 multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1
196 if not (opts.force_multi or not multi_on
197 or ConfirmOperation(inames, "instances", operation)):
199 jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
202 jex.QueueJob(name, op)
203 results = jex.WaitOrShow(not opts.submit_only)
204 rcode = compat.all(row[0] for row in results)
205 return int(not rcode)
209 def ListInstances(opts, args):
210 """List instances and their properties.
212 @param opts: the command line options selected by the user
214 @param args: should be an empty list
216 @return: the desired exit code
219 selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
221 fmtoverride = dict.fromkeys(["tags", "disk.sizes", "nic.macs", "nic.ips",
222 "nic.modes", "nic.links", "nic.bridges",
224 "snodes", "snodes.group", "snodes.group.uuid"],
225 (lambda value: ",".join(str(item)
229 return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units,
230 opts.separator, not opts.no_headers,
231 format_override=fmtoverride, verbose=opts.verbose,
232 force_filter=opts.force_filter)
235 def ListInstanceFields(opts, args):
236 """List instance fields.
238 @param opts: the command line options selected by the user
240 @param args: fields to list, or empty for all
242 @return: the desired exit code
245 return GenericListFields(constants.QR_INSTANCE, args, opts.separator,
249 def AddInstance(opts, args):
250 """Add an instance to the cluster.
252 This is just a wrapper over GenericInstanceCreate.
255 return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
258 def BatchCreate(opts, args):
259 """Create instances using a definition file.
261 This function reads a json file with L{opcodes.OpInstanceCreate}
264 @param opts: the command line options selected by the user
266 @param args: should contain one element, the json filename
268 @return: the desired exit code
271 (json_filename,) = args
275 instance_data = simplejson.loads(utils.ReadFile(json_filename))
276 except Exception, err: # pylint: disable=W0703
277 ToStderr("Can't parse the instance definition file: %s" % str(err))
280 if not _INST_DATA_VAL(instance_data):
281 ToStderr("The instance definition file is not %s" % _INST_DATA_VAL)
285 possible_params = set(opcodes.OpInstanceCreate.GetAllSlots())
286 for (idx, inst) in enumerate(instance_data):
287 unknown = set(inst.keys()) - possible_params
290 # TODO: Suggest closest match for more user friendly experience
291 raise errors.OpPrereqError("Unknown fields in definition %s: %s" %
292 (idx, utils.CommaJoin(unknown)),
295 op = opcodes.OpInstanceCreate(**inst) # pylint: disable=W0142
299 op = opcodes.OpInstanceMultiAlloc(iallocator=opts.iallocator,
301 result = SubmitOrSend(op, opts, cl=cl)
303 # Keep track of submitted jobs
304 jex = JobExecutor(cl=cl, opts=opts)
306 for (status, job_id) in result[constants.JOB_IDS_KEY]:
307 jex.AddJobId(None, status, job_id)
309 results = jex.GetResults()
310 bad_cnt = len([row for row in results if not row[0]])
312 ToStdout("All instances created successfully.")
313 rcode = constants.EXIT_SUCCESS
315 ToStdout("There were %s errors during the creation.", bad_cnt)
316 rcode = constants.EXIT_FAILURE
321 def ReinstallInstance(opts, args):
322 """Reinstall an instance.
324 @param opts: the command line options selected by the user
326 @param args: should contain only one element, the name of the
327 instance to be reinstalled
329 @return: the desired exit code
332 # first, compute the desired name list
333 if opts.multi_mode is None:
334 opts.multi_mode = _EXPAND_INSTANCES
336 inames = _ExpandMultiNames(opts.multi_mode, args)
338 raise errors.OpPrereqError("Selection filter does not match any instances",
341 # second, if requested, ask for an OS
342 if opts.select_os is True:
343 op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[])
344 result = SubmitOpCode(op, opts=opts)
347 ToStdout("Can't get the OS list")
350 ToStdout("Available OS templates:")
353 for (name, variants) in result:
354 for entry in CalculateOSNames(name, variants):
355 ToStdout("%3s: %s", number, entry)
356 choices.append(("%s" % number, entry, entry))
359 choices.append(("x", "exit", "Exit gnt-instance reinstall"))
360 selected = AskUser("Enter OS template number (or x to abort):",
363 if selected == "exit":
364 ToStderr("User aborted reinstall, exiting")
368 os_msg = "change the OS to '%s'" % selected
371 if opts.os is not None:
372 os_msg = "change the OS to '%s'" % os_name
374 os_msg = "keep the same OS"
376 # third, get confirmation: multi-reinstall requires --force-multi,
377 # single-reinstall either --force or --force-multi (--force-multi is
378 # a stronger --force)
379 multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1
381 warn_msg = ("Note: this will remove *all* data for the"
382 " below instances! It will %s.\n" % os_msg)
383 if not (opts.force_multi or
384 ConfirmOperation(inames, "instances", "reinstall", extra=warn_msg)):
387 if not (opts.force or opts.force_multi):
388 usertext = ("This will reinstall the instance '%s' (and %s) which"
389 " removes all data. Continue?") % (inames[0], os_msg)
390 if not AskUser(usertext):
393 jex = JobExecutor(verbose=multi_on, opts=opts)
394 for instance_name in inames:
395 op = opcodes.OpInstanceReinstall(instance_name=instance_name,
397 force_variant=opts.force_variant,
398 osparams=opts.osparams)
399 jex.QueueJob(instance_name, op)
401 results = jex.WaitOrShow(not opts.submit_only)
403 if compat.all(map(compat.fst, results)):
404 return constants.EXIT_SUCCESS
406 return constants.EXIT_FAILURE
409 def RemoveInstance(opts, args):
410 """Remove an instance.
412 @param opts: the command line options selected by the user
414 @param args: should contain only one element, the name of
415 the instance to be removed
417 @return: the desired exit code
420 instance_name = args[0]
425 _EnsureInstancesExist(cl, [instance_name])
427 usertext = ("This will remove the volumes of the instance %s"
428 " (including mirrors), thus removing all the data"
429 " of the instance. Continue?") % instance_name
430 if not AskUser(usertext):
433 op = opcodes.OpInstanceRemove(instance_name=instance_name,
434 ignore_failures=opts.ignore_failures,
435 shutdown_timeout=opts.shutdown_timeout)
436 SubmitOrSend(op, opts, cl=cl)
440 def RenameInstance(opts, args):
441 """Rename an instance.
443 @param opts: the command line options selected by the user
445 @param args: should contain two elements, the old and the
448 @return: the desired exit code
451 if not opts.name_check:
452 if not AskUser("As you disabled the check of the DNS entry, please verify"
453 " that '%s' is a FQDN. Continue?" % args[1]):
456 op = opcodes.OpInstanceRename(instance_name=args[0],
458 ip_check=opts.ip_check,
459 name_check=opts.name_check)
460 result = SubmitOrSend(op, opts)
463 ToStdout("Instance '%s' renamed to '%s'", args[0], result)
468 def ActivateDisks(opts, args):
469 """Activate an instance's disks.
471 This serves two purposes:
472 - it allows (as long as the instance is not running)
473 mounting the disks and modifying them from the node
474 - it repairs inactive secondary drbds
476 @param opts: the command line options selected by the user
478 @param args: should contain only one element, the instance name
480 @return: the desired exit code
483 instance_name = args[0]
484 op = opcodes.OpInstanceActivateDisks(instance_name=instance_name,
485 ignore_size=opts.ignore_size,
486 wait_for_sync=opts.wait_for_sync)
487 disks_info = SubmitOrSend(op, opts)
488 for host, iname, nname in disks_info:
489 ToStdout("%s:%s:%s", host, iname, nname)
493 def DeactivateDisks(opts, args):
494 """Deactivate an instance's disks.
496 This function takes the instance name, looks for its primary node
497 and the tries to shutdown its block devices on that node.
499 @param opts: the command line options selected by the user
501 @param args: should contain only one element, the instance name
503 @return: the desired exit code
506 instance_name = args[0]
507 op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name,
509 SubmitOrSend(op, opts)
513 def RecreateDisks(opts, args):
514 """Recreate an instance's disks.
516 @param opts: the command line options selected by the user
518 @param args: should contain only one element, the instance name
520 @return: the desired exit code
523 instance_name = args[0]
528 for didx, ddict in opts.disks:
531 if not ht.TDict(ddict):
532 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
533 raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
535 if constants.IDISK_SIZE in ddict:
537 ddict[constants.IDISK_SIZE] = \
538 utils.ParseUnit(ddict[constants.IDISK_SIZE])
539 except ValueError, err:
540 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
541 (didx, err), errors.ECODE_INVAL)
543 disks.append((didx, ddict))
545 # TODO: Verify modifyable parameters (already done in
546 # LUInstanceRecreateDisks, but it'd be nice to have in the client)
550 msg = "At most one of either --nodes or --iallocator can be passed"
551 raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
552 pnode, snode = SplitNodeOption(opts.node)
554 if snode is not None:
559 op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
560 disks=disks, nodes=nodes,
561 iallocator=opts.iallocator)
562 SubmitOrSend(op, opts)
567 def GrowDisk(opts, args):
568 """Grow an instance's disks.
570 @param opts: the command line options selected by the user
572 @param args: should contain three elements, the target instance name,
573 the target disk id, and the target growth
575 @return: the desired exit code
582 except (TypeError, ValueError), err:
583 raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
586 amount = utils.ParseUnit(args[2])
587 except errors.UnitParseError:
588 raise errors.OpPrereqError("Can't parse the given amount '%s'" % args[2],
590 op = opcodes.OpInstanceGrowDisk(instance_name=instance,
591 disk=disk, amount=amount,
592 wait_for_sync=opts.wait_for_sync,
593 absolute=opts.absolute)
594 SubmitOrSend(op, opts)
598 def _StartupInstance(name, opts):
599 """Startup instances.
601 This returns the opcode to start an instance, and its decorator will
602 wrap this into a loop starting all desired instances.
604 @param name: the name of the instance to act on
605 @param opts: the command line options selected by the user
606 @return: the opcode needed for the operation
609 op = opcodes.OpInstanceStartup(instance_name=name,
611 ignore_offline_nodes=opts.ignore_offline,
612 no_remember=opts.no_remember,
613 startup_paused=opts.startup_paused)
614 # do not add these parameters to the opcode unless they're defined
616 op.hvparams = opts.hvparams
618 op.beparams = opts.beparams
622 def _RebootInstance(name, opts):
623 """Reboot instance(s).
625 This returns the opcode to reboot an instance, and its decorator
626 will wrap this into a loop rebooting all desired instances.
628 @param name: the name of the instance to act on
629 @param opts: the command line options selected by the user
630 @return: the opcode needed for the operation
633 return opcodes.OpInstanceReboot(instance_name=name,
634 reboot_type=opts.reboot_type,
635 ignore_secondaries=opts.ignore_secondaries,
636 shutdown_timeout=opts.shutdown_timeout)
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,
651 timeout=opts.timeout,
652 ignore_offline_nodes=opts.ignore_offline,
653 no_remember=opts.no_remember)
656 def ReplaceDisks(opts, args):
657 """Replace the disks of an instance
659 @param opts: the command line options selected by the user
661 @param args: should contain only one element, the instance name
663 @return: the desired exit code
666 new_2ndary = opts.dst_node
667 iallocator = opts.iallocator
668 if opts.disks is None:
672 disks = [int(i) for i in opts.disks.split(",")]
673 except (TypeError, ValueError), err:
674 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
676 cnt = [opts.on_primary, opts.on_secondary, opts.auto,
677 new_2ndary is not None, iallocator is not None].count(True)
679 raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -I"
680 " options must be passed", errors.ECODE_INVAL)
681 elif opts.on_primary:
682 mode = constants.REPLACE_DISK_PRI
683 elif opts.on_secondary:
684 mode = constants.REPLACE_DISK_SEC
686 mode = constants.REPLACE_DISK_AUTO
688 raise errors.OpPrereqError("Cannot specify disks when using automatic"
689 " mode", errors.ECODE_INVAL)
690 elif new_2ndary is not None or iallocator is not None:
692 mode = constants.REPLACE_DISK_CHG
694 op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
695 remote_node=new_2ndary, mode=mode,
696 iallocator=iallocator,
697 early_release=opts.early_release,
698 ignore_ipolicy=opts.ignore_ipolicy)
699 SubmitOrSend(op, opts)
703 def FailoverInstance(opts, args):
704 """Failover an instance.
706 The failover is done by shutting it down on its present node and
707 starting it on the secondary.
709 @param opts: the command line options selected by the user
711 @param args: should contain only one element, the instance name
713 @return: the desired exit code
717 instance_name = args[0]
719 iallocator = opts.iallocator
720 target_node = opts.dst_node
722 if iallocator and target_node:
723 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
724 " node (-n) but not both", errors.ECODE_INVAL)
727 _EnsureInstancesExist(cl, [instance_name])
729 usertext = ("Failover will happen to image %s."
730 " This requires a shutdown of the instance. Continue?" %
732 if not AskUser(usertext):
735 op = opcodes.OpInstanceFailover(instance_name=instance_name,
736 ignore_consistency=opts.ignore_consistency,
737 shutdown_timeout=opts.shutdown_timeout,
738 iallocator=iallocator,
739 target_node=target_node,
740 ignore_ipolicy=opts.ignore_ipolicy)
741 SubmitOrSend(op, opts, cl=cl)
745 def MigrateInstance(opts, args):
746 """Migrate an instance.
748 The migrate is done without shutdown.
750 @param opts: the command line options selected by the user
752 @param args: should contain only one element, the instance name
754 @return: the desired exit code
758 instance_name = args[0]
760 iallocator = opts.iallocator
761 target_node = opts.dst_node
763 if iallocator and target_node:
764 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
765 " node (-n) but not both", errors.ECODE_INVAL)
768 _EnsureInstancesExist(cl, [instance_name])
771 usertext = ("Instance %s will be recovered from a failed migration."
772 " Note that the migration procedure (including cleanup)" %
775 usertext = ("Instance %s will be migrated. Note that migration" %
777 usertext += (" might impact the instance if anything goes wrong"
778 " (e.g. due to bugs in the hypervisor). Continue?")
779 if not AskUser(usertext):
782 # this should be removed once --non-live is deprecated
783 if not opts.live and opts.migration_mode is not None:
784 raise errors.OpPrereqError("Only one of the --non-live and "
785 "--migration-mode options can be passed",
787 if not opts.live: # --non-live passed
788 mode = constants.HT_MIGRATION_NONLIVE
790 mode = opts.migration_mode
792 op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
793 cleanup=opts.cleanup, iallocator=iallocator,
794 target_node=target_node,
795 allow_failover=opts.allow_failover,
796 allow_runtime_changes=opts.allow_runtime_chgs,
797 ignore_ipolicy=opts.ignore_ipolicy)
798 SubmitOrSend(op, cl=cl, opts=opts)
802 def MoveInstance(opts, args):
805 @param opts: the command line options selected by the user
807 @param args: should contain only one element, the instance name
809 @return: the desired exit code
813 instance_name = args[0]
817 usertext = ("Instance %s will be moved."
818 " This requires a shutdown of the instance. Continue?" %
820 if not AskUser(usertext):
823 op = opcodes.OpInstanceMove(instance_name=instance_name,
824 target_node=opts.node,
825 shutdown_timeout=opts.shutdown_timeout,
826 ignore_consistency=opts.ignore_consistency,
827 ignore_ipolicy=opts.ignore_ipolicy)
828 SubmitOrSend(op, opts, cl=cl)
832 def ConnectToInstanceConsole(opts, args):
833 """Connect to the console of an instance.
835 @param opts: the command line options selected by the user
837 @param args: should contain only one element, the instance name
839 @return: the desired exit code
842 instance_name = args[0]
846 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
847 ((console_data, oper_state), ) = \
848 cl.QueryInstances([instance_name], ["console", "oper_state"], False)
850 # Ensure client connection is closed while external commands are run
857 # Instance is running
858 raise errors.OpExecError("Console information for instance %s is"
859 " unavailable" % instance_name)
861 raise errors.OpExecError("Instance %s is not running, can't get console" %
864 return _DoConsole(objects.InstanceConsole.FromDict(console_data),
865 opts.show_command, cluster_name)
868 def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
869 _runcmd_fn=utils.RunCmd):
870 """Acts based on the result of L{opcodes.OpInstanceConsole}.
872 @type console: L{objects.InstanceConsole}
873 @param console: Console object
874 @type show_command: bool
875 @param show_command: Whether to just display commands
876 @type cluster_name: string
877 @param cluster_name: Cluster name as retrieved from master daemon
880 assert console.Validate()
882 if console.kind == constants.CONS_MESSAGE:
883 feedback_fn(console.message)
884 elif console.kind == constants.CONS_VNC:
885 feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
886 " URL <vnc://%s:%s/>",
887 console.instance, console.host, console.port,
888 console.display, console.host, console.port)
889 elif console.kind == constants.CONS_SPICE:
890 feedback_fn("Instance %s has SPICE listening on %s:%s", console.instance,
891 console.host, console.port)
892 elif console.kind == constants.CONS_SSH:
893 # Convert to string if not already one
894 if isinstance(console.command, basestring):
895 cmd = console.command
897 cmd = utils.ShellQuoteArgs(console.command)
899 srun = ssh.SshRunner(cluster_name=cluster_name)
900 ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
901 batch=True, quiet=False, tty=True)
904 feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
906 result = _runcmd_fn(ssh_cmd, interactive=True)
908 logging.error("Console command \"%s\" failed with reason '%s' and"
909 " output %r", result.cmd, result.fail_reason,
911 raise errors.OpExecError("Connection to console of instance %s failed,"
912 " please check cluster configuration" %
915 raise errors.GenericError("Unknown console type '%s'" % console.kind)
917 return constants.EXIT_SUCCESS
920 def _FormatLogicalID(dev_type, logical_id, roman):
921 """Formats the logical_id of a disk.
924 if dev_type == constants.LD_DRBD8:
925 node_a, node_b, port, minor_a, minor_b, key = logical_id
927 ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
929 ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
931 ("port", compat.TryToRoman(port, convert=roman)),
934 elif dev_type == constants.LD_LV:
935 vg_name, lv_name = logical_id
936 data = ["%s/%s" % (vg_name, lv_name)]
938 data = [str(logical_id)]
943 def _FormatBlockDevInfo(idx, top_level, dev, roman):
944 """Show block device information.
946 This is only used by L{ShowInstanceConfig}, but it's too big to be
947 left for an inline definition.
950 @param idx: the index of the current disk
951 @type top_level: boolean
952 @param top_level: if this a top-level disk?
954 @param dev: dictionary with disk information
956 @param roman: whether to try to use roman integers
957 @return: a list of either strings, tuples or lists
958 (which should be formatted at a higher indent level)
961 def helper(dtype, status):
962 """Format one line for physical device status.
965 @param dtype: a constant from the L{constants.LDS_BLOCK} set
967 @param status: a tuple as returned from L{backend.FindBlockDevice}
968 @return: the string representing the status
974 (path, major, minor, syncp, estt, degr, ldisk_status) = status
978 major_string = str(compat.TryToRoman(major, convert=roman))
983 minor_string = str(compat.TryToRoman(minor, convert=roman))
985 txt += ("%s (%s:%s)" % (path, major_string, minor_string))
986 if dtype in (constants.LD_DRBD8, ):
987 if syncp is not None:
988 sync_text = "*RECOVERING* %5.2f%%," % syncp
990 sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
992 sync_text += " ETA unknown"
994 sync_text = "in sync"
996 degr_text = "*DEGRADED*"
999 if ldisk_status == constants.LDS_FAULTY:
1000 ldisk_text = " *MISSING DISK*"
1001 elif ldisk_status == constants.LDS_UNKNOWN:
1002 ldisk_text = " *UNCERTAIN STATE*"
1005 txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1006 elif dtype == constants.LD_LV:
1007 if ldisk_status == constants.LDS_FAULTY:
1008 ldisk_text = " *FAILED* (failed drive?)"
1016 if dev["iv_name"] is not None:
1017 txt = dev["iv_name"]
1019 txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1021 txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1022 if isinstance(dev["size"], int):
1023 nice_size = utils.FormatUnit(dev["size"], "h")
1025 nice_size = dev["size"]
1026 d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1029 data.append(("access mode", dev["mode"]))
1030 if dev["logical_id"] is not None:
1032 l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1034 l_id = [str(dev["logical_id"])]
1036 data.append(("logical_id", l_id[0]))
1039 elif dev["physical_id"] is not None:
1040 data.append("physical_id:")
1041 data.append([dev["physical_id"]])
1044 data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1047 data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1050 data.append("child devices:")
1051 for c_idx, child in enumerate(dev["children"]):
1052 data.append(_FormatBlockDevInfo(c_idx, False, child, roman))
1057 def _FormatList(buf, data, indent_level):
1058 """Formats a list of data at a given indent level.
1060 If the element of the list is:
1061 - a string, it is simply formatted as is
1062 - a tuple, it will be split into key, value and the all the
1063 values in a list will be aligned all at the same start column
1064 - a list, will be recursively formatted
1067 @param buf: the buffer into which we write the output
1068 @param data: the list to format
1069 @type indent_level: int
1070 @param indent_level: the indent level to format at
1073 max_tlen = max([len(elem[0]) for elem in data
1074 if isinstance(elem, tuple)] or [0])
1076 if isinstance(elem, basestring):
1077 buf.write("%*s%s\n" % (2 * indent_level, "", elem))
1078 elif isinstance(elem, tuple):
1080 spacer = "%*s" % (max_tlen - len(key), "")
1081 buf.write("%*s%s:%s %s\n" % (2 * indent_level, "", key, spacer, value))
1082 elif isinstance(elem, list):
1083 _FormatList(buf, elem, indent_level + 1)
1086 def ShowInstanceConfig(opts, args):
1087 """Compute instance run-time status.
1089 @param opts: the command line options selected by the user
1091 @param args: either an empty list, and then we query all
1092 instances, or should contain a list of instance names
1094 @return: the desired exit code
1097 if not args and not opts.show_all:
1098 ToStderr("No instance selected."
1099 " Please pass in --all if you want to query all instances.\n"
1100 "Note that this can take a long time on a big cluster.")
1102 elif args and opts.show_all:
1103 ToStderr("Cannot use --all if you specify instance names.")
1107 op = opcodes.OpInstanceQueryData(instances=args, static=opts.static,
1108 use_locking=not opts.static)
1109 result = SubmitOpCode(op, opts=opts)
1111 ToStdout("No instances.")
1116 for instance_name in result:
1117 instance = result[instance_name]
1118 buf.write("Instance name: %s\n" % instance["name"])
1119 buf.write("UUID: %s\n" % instance["uuid"])
1120 buf.write("Serial number: %s\n" %
1121 compat.TryToRoman(instance["serial_no"],
1122 convert=opts.roman_integers))
1123 buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1124 buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1125 buf.write("State: configured to be %s" % instance["config_state"])
1126 if instance["run_state"]:
1127 buf.write(", actual state is %s" % instance["run_state"])
1129 ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1130 ## instance["auto_balance"])
1131 buf.write(" Nodes:\n")
1132 buf.write(" - primary: %s\n" % instance["pnode"])
1133 buf.write(" group: %s (UUID %s)\n" %
1134 (instance["pnode_group_name"], instance["pnode_group_uuid"]))
1135 buf.write(" - secondaries: %s\n" %
1136 utils.CommaJoin("%s (group %s, group UUID %s)" %
1137 (name, group_name, group_uuid)
1138 for (name, group_name, group_uuid) in
1139 zip(instance["snodes"],
1140 instance["snodes_group_names"],
1141 instance["snodes_group_uuids"])))
1142 buf.write(" Operating system: %s\n" % instance["os"])
1143 FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1145 if "network_port" in instance:
1146 buf.write(" Allocated network port: %s\n" %
1147 compat.TryToRoman(instance["network_port"],
1148 convert=opts.roman_integers))
1149 buf.write(" Hypervisor: %s\n" % instance["hypervisor"])
1151 # custom VNC console information
1152 vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1154 if vnc_bind_address:
1155 port = instance["network_port"]
1156 display = int(port) - constants.VNC_BASE_PORT
1157 if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1158 vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1161 elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1162 vnc_console_port = ("%s:%s (node %s) (display %s)" %
1163 (vnc_bind_address, port,
1164 instance["pnode"], display))
1166 # vnc bind address is a file
1167 vnc_console_port = "%s:%s" % (instance["pnode"],
1169 buf.write(" - console connection: vnc to %s\n" % vnc_console_port)
1171 FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1173 buf.write(" Hardware:\n")
1174 # deprecated "memory" value, kept for one version for compatibility
1175 # TODO(ganeti 2.7) remove.
1176 be_actual = copy.deepcopy(instance["be_actual"])
1177 be_actual["memory"] = be_actual[constants.BE_MAXMEM]
1178 FormatParameterDict(buf, instance["be_instance"], be_actual, level=2)
1179 # TODO(ganeti 2.7) rework the NICs as well
1180 buf.write(" - NICs:\n")
1181 for idx, (ip, mac, mode, link, network) in enumerate(instance["nics"]):
1182 buf.write(" - nic/%d: MAC: %s, IP: %s,"
1183 " mode: %s, link: %s, network: %s\n" %
1184 (idx, mac, ip, mode, link, network))
1185 buf.write(" Disk template: %s\n" % instance["disk_template"])
1186 buf.write(" Disks:\n")
1188 for idx, device in enumerate(instance["disks"]):
1189 _FormatList(buf, _FormatBlockDevInfo(idx, True, device,
1190 opts.roman_integers), 2)
1192 ToStdout(buf.getvalue().rstrip("\n"))
1196 def _ConvertNicDiskModifications(mods):
1197 """Converts NIC/disk modifications from CLI to opcode.
1199 When L{opcodes.OpInstanceSetParams} was changed to support adding/removing
1200 disks at arbitrary indices, its parameter format changed. This function
1201 converts legacy requests (e.g. "--net add" or "--disk add:size=4G") to the
1202 newer format and adds support for new-style requests (e.g. "--new 4:add").
1204 @type mods: list of tuples
1205 @param mods: Modifications as given by command line parser
1206 @rtype: list of tuples
1207 @return: Modifications as understood by L{opcodes.OpInstanceSetParams}
1212 for (idx, params) in mods:
1213 if idx == constants.DDM_ADD:
1214 # Add item as last item (legacy interface)
1215 action = constants.DDM_ADD
1217 elif idx == constants.DDM_REMOVE:
1218 # Remove last item (legacy interface)
1219 action = constants.DDM_REMOVE
1222 # Modifications and adding/removing at arbitrary indices
1225 except (TypeError, ValueError):
1226 raise errors.OpPrereqError("Non-numeric index '%s'" % idx,
1229 add = params.pop(constants.DDM_ADD, _MISSING)
1230 remove = params.pop(constants.DDM_REMOVE, _MISSING)
1231 modify = params.pop(constants.DDM_MODIFY, _MISSING)
1233 if modify is _MISSING:
1234 if not (add is _MISSING or remove is _MISSING):
1235 raise errors.OpPrereqError("Cannot add and remove at the same time",
1237 elif add is not _MISSING:
1238 action = constants.DDM_ADD
1239 elif remove is not _MISSING:
1240 action = constants.DDM_REMOVE
1242 action = constants.DDM_MODIFY
1244 elif add is _MISSING and remove is _MISSING:
1245 action = constants.DDM_MODIFY
1247 raise errors.OpPrereqError("Cannot modify and add/remove at the"
1248 " same time", errors.ECODE_INVAL)
1250 assert not (constants.DDMS_VALUES_WITH_MODIFY & set(params.keys()))
1252 if action == constants.DDM_REMOVE and params:
1253 raise errors.OpPrereqError("Not accepting parameters on removal",
1256 result.append((action, idxno, params))
1261 def _ParseDiskSizes(mods):
1262 """Parses disk sizes in parameters.
1265 for (action, _, params) in mods:
1266 if params and constants.IDISK_SIZE in params:
1267 params[constants.IDISK_SIZE] = \
1268 utils.ParseUnit(params[constants.IDISK_SIZE])
1269 elif action == constants.DDM_ADD:
1270 raise errors.OpPrereqError("Missing required parameter 'size'",
1276 def SetInstanceParams(opts, args):
1277 """Modifies an instance.
1279 All parameters take effect only at the next restart of the instance.
1281 @param opts: the command line options selected by the user
1283 @param args: should contain only one element, the instance name
1285 @return: the desired exit code
1288 if not (opts.nics or opts.disks or opts.disk_template or
1289 opts.hvparams or opts.beparams or opts.os or opts.osparams or
1290 opts.offline_inst or opts.online_inst or opts.runtime_mem):
1291 ToStderr("Please give at least one of the parameters.")
1294 for param in opts.beparams:
1295 if isinstance(opts.beparams[param], basestring):
1296 if opts.beparams[param].lower() == "default":
1297 opts.beparams[param] = constants.VALUE_DEFAULT
1299 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT,
1300 allowed_values=[constants.VALUE_DEFAULT])
1302 for param in opts.hvparams:
1303 if isinstance(opts.hvparams[param], basestring):
1304 if opts.hvparams[param].lower() == "default":
1305 opts.hvparams[param] = constants.VALUE_DEFAULT
1307 utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1308 allowed_values=[constants.VALUE_DEFAULT])
1310 nics = _ConvertNicDiskModifications(opts.nics)
1311 disks = _ParseDiskSizes(_ConvertNicDiskModifications(opts.disks))
1313 if (opts.disk_template and
1314 opts.disk_template in constants.DTS_INT_MIRROR and
1316 ToStderr("Changing the disk template to a mirrored one requires"
1317 " specifying a secondary node")
1320 if opts.offline_inst:
1322 elif opts.online_inst:
1327 op = opcodes.OpInstanceSetParams(instance_name=args[0],
1330 disk_template=opts.disk_template,
1331 remote_node=opts.node,
1332 hvparams=opts.hvparams,
1333 beparams=opts.beparams,
1334 runtime_mem=opts.runtime_mem,
1336 osparams=opts.osparams,
1337 force_variant=opts.force_variant,
1339 wait_for_sync=opts.wait_for_sync,
1341 ignore_ipolicy=opts.ignore_ipolicy)
1343 # even if here we process the result, we allow submit only
1344 result = SubmitOrSend(op, opts)
1347 ToStdout("Modified instance %s", args[0])
1348 for param, data in result:
1349 ToStdout(" - %-5s -> %s", param, data)
1350 ToStdout("Please don't forget that most parameters take effect"
1351 " only at the next (re)start of the instance initiated by"
1352 " ganeti; restarting from within the instance will"
1357 def ChangeGroup(opts, args):
1358 """Moves an instance to another group.
1361 (instance_name, ) = args
1365 op = opcodes.OpInstanceChangeGroup(instance_name=instance_name,
1366 iallocator=opts.iallocator,
1367 target_groups=opts.to,
1368 early_release=opts.early_release)
1369 result = SubmitOrSend(op, opts, cl=cl)
1371 # Keep track of submitted jobs
1372 jex = JobExecutor(cl=cl, opts=opts)
1374 for (status, job_id) in result[constants.JOB_IDS_KEY]:
1375 jex.AddJobId(None, status, job_id)
1377 results = jex.GetResults()
1378 bad_cnt = len([row for row in results if not row[0]])
1380 ToStdout("Instance '%s' changed group successfully.", instance_name)
1381 rcode = constants.EXIT_SUCCESS
1383 ToStdout("There were %s errors while changing group of instance '%s'.",
1384 bad_cnt, instance_name)
1385 rcode = constants.EXIT_FAILURE
1390 # multi-instance selection options
1391 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1392 help="Do not ask for confirmation when more than"
1393 " one instance is affected",
1394 action="store_true", default=False)
1396 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1397 help="Filter by nodes (primary only)",
1398 const=_EXPAND_NODES_PRI, action="store_const")
1400 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1401 help="Filter by nodes (secondary only)",
1402 const=_EXPAND_NODES_SEC, action="store_const")
1404 m_node_opt = cli_option("--node", dest="multi_mode",
1405 help="Filter by nodes (primary and secondary)",
1406 const=_EXPAND_NODES_BOTH, action="store_const")
1408 m_clust_opt = cli_option("--all", dest="multi_mode",
1409 help="Select all instances in the cluster",
1410 const=_EXPAND_CLUSTER, action="store_const")
1412 m_inst_opt = cli_option("--instance", dest="multi_mode",
1413 help="Filter by instance name [default]",
1414 const=_EXPAND_INSTANCES, action="store_const")
1416 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1417 help="Filter by node tag",
1418 const=_EXPAND_NODES_BOTH_BY_TAGS,
1419 action="store_const")
1421 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1422 help="Filter by primary node tag",
1423 const=_EXPAND_NODES_PRI_BY_TAGS,
1424 action="store_const")
1426 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1427 help="Filter by secondary node tag",
1428 const=_EXPAND_NODES_SEC_BY_TAGS,
1429 action="store_const")
1431 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1432 help="Filter by instance tag",
1433 const=_EXPAND_INSTANCES_BY_TAGS,
1434 action="store_const")
1436 # this is defined separately due to readability only
1447 AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1448 "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1449 "Creates and adds a new instance to the cluster"),
1451 BatchCreate, [ArgFile(min=1, max=1)],
1452 [DRY_RUN_OPT, PRIORITY_OPT, IALLOCATOR_OPT, SUBMIT_OPT],
1454 "Create a bunch of instances based on specs in the file."),
1456 ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1457 [SHOWCMD_OPT, PRIORITY_OPT],
1458 "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1460 FailoverInstance, ARGS_ONE_INSTANCE,
1461 [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1462 DRY_RUN_OPT, PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT,
1463 IGNORE_IPOLICY_OPT],
1464 "[-f] <instance>", "Stops the instance, changes its primary node and"
1465 " (if it was originally running) starts it on the new node"
1466 " (the secondary for mirrored instances or any node"
1467 " for shared storage)."),
1469 MigrateInstance, ARGS_ONE_INSTANCE,
1470 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1471 PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT, ALLOW_FAILOVER_OPT,
1472 IGNORE_IPOLICY_OPT, NORUNTIME_CHGS_OPT, SUBMIT_OPT],
1473 "[-f] <instance>", "Migrate instance to its secondary node"
1474 " (only for mirrored instances)"),
1476 MoveInstance, ARGS_ONE_INSTANCE,
1477 [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1478 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_CONSIST_OPT, IGNORE_IPOLICY_OPT],
1479 "[-f] <instance>", "Move instance to an arbitrary node"
1480 " (only for instances of type file and lv)"),
1482 ShowInstanceConfig, ARGS_MANY_INSTANCES,
1483 [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1484 "[-s] {--all | <instance>...}",
1485 "Show information on the specified instance(s)"),
1487 ListInstances, ARGS_MANY_INSTANCES,
1488 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
1491 "Lists the instances and their status. The available fields can be shown"
1492 " using the \"list-fields\" command (see the man page for details)."
1493 " The default field list is (in order): %s." %
1494 utils.CommaJoin(_LIST_DEF_FIELDS),
1497 ListInstanceFields, [ArgUnknown()],
1498 [NOHDR_OPT, SEP_OPT],
1500 "Lists all available fields for instances"),
1502 ReinstallInstance, [ArgInstance()],
1503 [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1504 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1505 m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1506 SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1507 "[-f] <instance>", "Reinstall a stopped instance"),
1509 RemoveInstance, ARGS_ONE_INSTANCE,
1510 [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1511 DRY_RUN_OPT, PRIORITY_OPT],
1512 "[-f] <instance>", "Shuts down the instance and removes it"),
1515 [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1516 [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1517 "<instance> <new_name>", "Rename the instance"),
1519 ReplaceDisks, ARGS_ONE_INSTANCE,
1520 [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1521 NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1522 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_IPOLICY_OPT],
1523 "[-s|-p|-a|-n NODE|-I NAME] <instance>",
1524 "Replaces disks for the instance"),
1526 SetInstanceParams, ARGS_ONE_INSTANCE,
1527 [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1528 DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1529 OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT, NWSYNC_OPT, OFFLINE_INST_OPT,
1530 ONLINE_INST_OPT, IGNORE_IPOLICY_OPT, RUNTIME_MEM_OPT],
1531 "<instance>", "Alters the parameters of an instance"),
1533 GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1534 [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1535 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1536 m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1537 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT, NO_REMEMBER_OPT],
1538 "<instance>", "Stops an instance"),
1540 GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1541 [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1542 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1543 m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1544 BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT,
1545 NO_REMEMBER_OPT, STARTUP_PAUSED_OPT],
1546 "<instance>", "Starts an instance"),
1548 GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1549 [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1550 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1551 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1552 m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1553 "<instance>", "Reboots an instance"),
1555 ActivateDisks, ARGS_ONE_INSTANCE,
1556 [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT, WFSYNC_OPT],
1557 "<instance>", "Activate an instance's disks"),
1558 "deactivate-disks": (
1559 DeactivateDisks, ARGS_ONE_INSTANCE,
1560 [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1561 "[-f] <instance>", "Deactivate an instance's disks"),
1563 RecreateDisks, ARGS_ONE_INSTANCE,
1564 [SUBMIT_OPT, DISK_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT,
1566 "<instance>", "Recreate an instance's disks"),
1569 [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1570 ArgUnknown(min=1, max=1)],
1571 [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT, ABSOLUTE_OPT],
1572 "<instance> <disk> <size>", "Grow an instance's disk"),
1574 ChangeGroup, ARGS_ONE_INSTANCE,
1575 [TO_GROUP_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT, PRIORITY_OPT, SUBMIT_OPT],
1576 "[-I <iallocator>] [--to <group>]", "Change group of instance"),
1578 ListTags, ARGS_ONE_INSTANCE, [],
1579 "<instance_name>", "List the tags of the given instance"),
1581 AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1582 [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
1583 "<instance_name> tag...", "Add tags to the given instance"),
1585 RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1586 [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
1587 "<instance_name> tag...", "Remove tags from given instance"),
1590 #: dictionary with aliases for commands
1599 return GenericMain(commands, aliases=aliases,
1600 override={"tag_type": constants.TAG_INSTANCE},
1601 env_override=_ENV_OVERRIDE)