4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 """Instance related commands"""
23 # pylint: disable=W0401,W0614,C0103
24 # W0401: Wildcard import ganeti.cli
25 # W0614: Unused import %s from wildcard import (since we need cli)
26 # C0103: Invalid name gnt-instance
33 from ganeti.cli import *
34 from ganeti import opcodes
35 from ganeti import constants
36 from ganeti import compat
37 from ganeti import utils
38 from ganeti import errors
39 from ganeti import netutils
40 from ganeti import ssh
41 from ganeti import objects
45 _EXPAND_CLUSTER = "cluster"
46 _EXPAND_NODES_BOTH = "nodes"
47 _EXPAND_NODES_PRI = "nodes-pri"
48 _EXPAND_NODES_SEC = "nodes-sec"
49 _EXPAND_NODES_BOTH_BY_TAGS = "nodes-by-tags"
50 _EXPAND_NODES_PRI_BY_TAGS = "nodes-pri-by-tags"
51 _EXPAND_NODES_SEC_BY_TAGS = "nodes-sec-by-tags"
52 _EXPAND_INSTANCES = "instances"
53 _EXPAND_INSTANCES_BY_TAGS = "instances-by-tags"
55 _EXPAND_NODES_TAGS_MODES = compat.UniqueFrozenset([
56 _EXPAND_NODES_BOTH_BY_TAGS,
57 _EXPAND_NODES_PRI_BY_TAGS,
58 _EXPAND_NODES_SEC_BY_TAGS,
61 #: default list of options for L{ListInstances}
63 "name", "hypervisor", "os", "pnode", "status", "oper_ram",
67 _ENV_OVERRIDE = compat.UniqueFrozenset(["list"])
69 _INST_DATA_VAL = ht.TListOf(ht.TDict)
72 def _ExpandMultiNames(mode, names, client=None):
73 """Expand the given names using the passed mode.
75 For _EXPAND_CLUSTER, all instances will be returned. For
76 _EXPAND_NODES_PRI/SEC, all instances having those nodes as
77 primary/secondary will be returned. For _EXPAND_NODES_BOTH, all
78 instances having those nodes as either primary or secondary will be
79 returned. For _EXPAND_INSTANCES, the given instances will be
82 @param mode: one of L{_EXPAND_CLUSTER}, L{_EXPAND_NODES_BOTH},
83 L{_EXPAND_NODES_PRI}, L{_EXPAND_NODES_SEC} or
85 @param names: a list of names; for cluster, it must be empty,
86 and for node and instance it must be a list of valid item
87 names (short names are valid as usual, e.g. node1 instead of
90 @return: the list of names after the expansion
91 @raise errors.ProgrammerError: for unknown selection type
92 @raise errors.OpPrereqError: for invalid input parameters
95 # pylint: disable=W0142
99 if mode == _EXPAND_CLUSTER:
101 raise errors.OpPrereqError("Cluster filter mode takes no arguments",
103 idata = client.QueryInstances([], ["name"], False)
104 inames = [row[0] for row in idata]
106 elif (mode in _EXPAND_NODES_TAGS_MODES or
107 mode in (_EXPAND_NODES_BOTH, _EXPAND_NODES_PRI, _EXPAND_NODES_SEC)):
108 if mode in _EXPAND_NODES_TAGS_MODES:
110 raise errors.OpPrereqError("No node tags passed", errors.ECODE_INVAL)
111 ndata = client.QueryNodes([], ["name", "pinst_list",
112 "sinst_list", "tags"], False)
113 ndata = [row for row in ndata if set(row[3]).intersection(names)]
116 raise errors.OpPrereqError("No node names passed", errors.ECODE_INVAL)
117 ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
120 ipri = [row[1] for row in ndata]
121 pri_names = list(itertools.chain(*ipri))
122 isec = [row[2] for row in ndata]
123 sec_names = list(itertools.chain(*isec))
124 if mode in (_EXPAND_NODES_BOTH, _EXPAND_NODES_BOTH_BY_TAGS):
125 inames = pri_names + sec_names
126 elif mode in (_EXPAND_NODES_PRI, _EXPAND_NODES_PRI_BY_TAGS):
128 elif mode in (_EXPAND_NODES_SEC, _EXPAND_NODES_SEC_BY_TAGS):
131 raise errors.ProgrammerError("Unhandled shutdown type")
132 elif mode == _EXPAND_INSTANCES:
134 raise errors.OpPrereqError("No instance names passed",
136 idata = client.QueryInstances(names, ["name"], False)
137 inames = [row[0] for row in idata]
138 elif mode == _EXPAND_INSTANCES_BY_TAGS:
140 raise errors.OpPrereqError("No instance tags passed",
142 idata = client.QueryInstances([], ["name", "tags"], False)
143 inames = [row[0] for row in idata if set(row[1]).intersection(names)]
145 raise errors.OpPrereqError("Unknown mode '%s'" % mode, errors.ECODE_INVAL)
150 def _EnsureInstancesExist(client, names):
151 """Check for and ensure the given instance names exist.
153 This function will raise an OpPrereqError in case they don't
154 exist. Otherwise it will exit cleanly.
156 @type client: L{ganeti.luxi.Client}
157 @param client: the client to use for the query
159 @param names: the list of instance names to query
160 @raise errors.OpPrereqError: in case any instance is missing
163 # TODO: change LUInstanceQuery to that it actually returns None
164 # instead of raising an exception, or devise a better mechanism
165 result = client.QueryInstances(names, ["name"], False)
166 for orig_name, row in zip(names, result):
168 raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name,
172 def GenericManyOps(operation, fn):
173 """Generic multi-instance operations.
175 The will return a wrapper that processes the options and arguments
176 given, and uses the passed function to build the opcode needed for
177 the specific operation. Thus all the generic loop/confirmation code
178 is abstracted into this function.
181 def realfn(opts, args):
182 if opts.multi_mode is None:
183 opts.multi_mode = _EXPAND_INSTANCES
185 inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
187 if opts.multi_mode == _EXPAND_CLUSTER:
188 ToStdout("Cluster is empty, no instances to shutdown")
190 raise errors.OpPrereqError("Selection filter does not match"
191 " any instances", errors.ECODE_INVAL)
192 multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1
193 if not (opts.force_multi or not multi_on
194 or ConfirmOperation(inames, "instances", operation)):
196 jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
199 jex.QueueJob(name, op)
200 results = jex.WaitOrShow(not opts.submit_only)
201 rcode = compat.all(row[0] for row in results)
202 return int(not rcode)
206 def ListInstances(opts, args):
207 """List instances and their properties.
209 @param opts: the command line options selected by the user
211 @param args: should be an empty list
213 @return: the desired exit code
216 selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
218 fmtoverride = dict.fromkeys(["tags", "disk.sizes", "nic.macs", "nic.ips",
219 "nic.modes", "nic.links", "nic.bridges",
221 "snodes", "snodes.group", "snodes.group.uuid"],
222 (lambda value: ",".join(str(item)
226 return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units,
227 opts.separator, not opts.no_headers,
228 format_override=fmtoverride, verbose=opts.verbose,
229 force_filter=opts.force_filter)
232 def ListInstanceFields(opts, args):
233 """List instance fields.
235 @param opts: the command line options selected by the user
237 @param args: fields to list, or empty for all
239 @return: the desired exit code
242 return GenericListFields(constants.QR_INSTANCE, args, opts.separator,
246 def AddInstance(opts, args):
247 """Add an instance to the cluster.
249 This is just a wrapper over GenericInstanceCreate.
252 return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
255 def BatchCreate(opts, args):
256 """Create instances using a definition file.
258 This function reads a json file with L{opcodes.OpInstanceCreate}
261 @param opts: the command line options selected by the user
263 @param args: should contain one element, the json filename
265 @return: the desired exit code
268 (json_filename,) = args
272 instance_data = simplejson.loads(utils.ReadFile(json_filename))
273 except Exception, err: # pylint: disable=W0703
274 ToStderr("Can't parse the instance definition file: %s" % str(err))
277 if not _INST_DATA_VAL(instance_data):
278 ToStderr("The instance definition file is not %s" % _INST_DATA_VAL)
282 possible_params = set(opcodes.OpInstanceCreate.GetAllSlots())
283 for (idx, inst) in enumerate(instance_data):
284 unknown = set(inst.keys()) - possible_params
287 # TODO: Suggest closest match for more user friendly experience
288 raise errors.OpPrereqError("Unknown fields in definition %s: %s" %
289 (idx, utils.CommaJoin(unknown)),
292 op = opcodes.OpInstanceCreate(**inst) # pylint: disable=W0142
296 op = opcodes.OpInstanceMultiAlloc(iallocator=opts.iallocator,
298 result = SubmitOrSend(op, opts, cl=cl)
300 # Keep track of submitted jobs
301 jex = JobExecutor(cl=cl, opts=opts)
303 for (status, job_id) in result[constants.JOB_IDS_KEY]:
304 jex.AddJobId(None, status, job_id)
306 results = jex.GetResults()
307 bad_cnt = len([row for row in results if not row[0]])
309 ToStdout("All instances created successfully.")
310 rcode = constants.EXIT_SUCCESS
312 ToStdout("There were %s errors during the creation.", bad_cnt)
313 rcode = constants.EXIT_FAILURE
318 def ReinstallInstance(opts, args):
319 """Reinstall an instance.
321 @param opts: the command line options selected by the user
323 @param args: should contain only one element, the name of the
324 instance to be reinstalled
326 @return: the desired exit code
329 # first, compute the desired name list
330 if opts.multi_mode is None:
331 opts.multi_mode = _EXPAND_INSTANCES
333 inames = _ExpandMultiNames(opts.multi_mode, args)
335 raise errors.OpPrereqError("Selection filter does not match any instances",
338 # second, if requested, ask for an OS
339 if opts.select_os is True:
340 op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[])
341 result = SubmitOpCode(op, opts=opts)
344 ToStdout("Can't get the OS list")
347 ToStdout("Available OS templates:")
350 for (name, variants) in result:
351 for entry in CalculateOSNames(name, variants):
352 ToStdout("%3s: %s", number, entry)
353 choices.append(("%s" % number, entry, entry))
356 choices.append(("x", "exit", "Exit gnt-instance reinstall"))
357 selected = AskUser("Enter OS template number (or x to abort):",
360 if selected == "exit":
361 ToStderr("User aborted reinstall, exiting")
365 os_msg = "change the OS to '%s'" % selected
368 if opts.os is not None:
369 os_msg = "change the OS to '%s'" % os_name
371 os_msg = "keep the same OS"
373 # third, get confirmation: multi-reinstall requires --force-multi,
374 # single-reinstall either --force or --force-multi (--force-multi is
375 # a stronger --force)
376 multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1
378 warn_msg = ("Note: this will remove *all* data for the"
379 " below instances! It will %s.\n" % os_msg)
380 if not (opts.force_multi or
381 ConfirmOperation(inames, "instances", "reinstall", extra=warn_msg)):
384 if not (opts.force or opts.force_multi):
385 usertext = ("This will reinstall the instance '%s' (and %s) which"
386 " removes all data. Continue?") % (inames[0], os_msg)
387 if not AskUser(usertext):
390 jex = JobExecutor(verbose=multi_on, opts=opts)
391 for instance_name in inames:
392 op = opcodes.OpInstanceReinstall(instance_name=instance_name,
394 force_variant=opts.force_variant,
395 osparams=opts.osparams)
396 jex.QueueJob(instance_name, op)
398 results = jex.WaitOrShow(not opts.submit_only)
400 if compat.all(map(compat.fst, results)):
401 return constants.EXIT_SUCCESS
403 return constants.EXIT_FAILURE
406 def RemoveInstance(opts, args):
407 """Remove an instance.
409 @param opts: the command line options selected by the user
411 @param args: should contain only one element, the name of
412 the instance to be removed
414 @return: the desired exit code
417 instance_name = args[0]
422 _EnsureInstancesExist(cl, [instance_name])
424 usertext = ("This will remove the volumes of the instance %s"
425 " (including mirrors), thus removing all the data"
426 " of the instance. Continue?") % instance_name
427 if not AskUser(usertext):
430 op = opcodes.OpInstanceRemove(instance_name=instance_name,
431 ignore_failures=opts.ignore_failures,
432 shutdown_timeout=opts.shutdown_timeout)
433 SubmitOrSend(op, opts, cl=cl)
437 def RenameInstance(opts, args):
438 """Rename an instance.
440 @param opts: the command line options selected by the user
442 @param args: should contain two elements, the old and the
445 @return: the desired exit code
448 if not opts.name_check:
449 if not AskUser("As you disabled the check of the DNS entry, please verify"
450 " that '%s' is a FQDN. Continue?" % args[1]):
453 op = opcodes.OpInstanceRename(instance_name=args[0],
455 ip_check=opts.ip_check,
456 name_check=opts.name_check)
457 result = SubmitOrSend(op, opts)
460 ToStdout("Instance '%s' renamed to '%s'", args[0], result)
465 def ActivateDisks(opts, args):
466 """Activate an instance's disks.
468 This serves two purposes:
469 - it allows (as long as the instance is not running)
470 mounting the disks and modifying them from the node
471 - it repairs inactive secondary drbds
473 @param opts: the command line options selected by the user
475 @param args: should contain only one element, the instance name
477 @return: the desired exit code
480 instance_name = args[0]
481 op = opcodes.OpInstanceActivateDisks(instance_name=instance_name,
482 ignore_size=opts.ignore_size,
483 wait_for_sync=opts.wait_for_sync)
484 disks_info = SubmitOrSend(op, opts)
485 for host, iname, nname in disks_info:
486 ToStdout("%s:%s:%s", host, iname, nname)
490 def DeactivateDisks(opts, args):
491 """Deactivate an instance's disks.
493 This function takes the instance name, looks for its primary node
494 and the tries to shutdown its block devices on that node.
496 @param opts: the command line options selected by the user
498 @param args: should contain only one element, the instance name
500 @return: the desired exit code
503 instance_name = args[0]
504 op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name,
506 SubmitOrSend(op, opts)
510 def RecreateDisks(opts, args):
511 """Recreate an instance's disks.
513 @param opts: the command line options selected by the user
515 @param args: should contain only one element, the instance name
517 @return: the desired exit code
520 instance_name = args[0]
525 for didx, ddict in opts.disks:
528 if not ht.TDict(ddict):
529 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
530 raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
532 if constants.IDISK_SIZE in ddict:
534 ddict[constants.IDISK_SIZE] = \
535 utils.ParseUnit(ddict[constants.IDISK_SIZE])
536 except ValueError, err:
537 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
538 (didx, err), errors.ECODE_INVAL)
540 disks.append((didx, ddict))
542 # TODO: Verify modifyable parameters (already done in
543 # LUInstanceRecreateDisks, but it'd be nice to have in the client)
547 msg = "At most one of either --nodes or --iallocator can be passed"
548 raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
549 pnode, snode = SplitNodeOption(opts.node)
551 if snode is not None:
556 op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
557 disks=disks, nodes=nodes,
558 iallocator=opts.iallocator)
559 SubmitOrSend(op, opts)
564 def GrowDisk(opts, args):
565 """Grow an instance's disks.
567 @param opts: the command line options selected by the user
569 @param args: should contain three elements, the target instance name,
570 the target disk id, and the target growth
572 @return: the desired exit code
579 except (TypeError, ValueError), err:
580 raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
583 amount = utils.ParseUnit(args[2])
584 except errors.UnitParseError:
585 raise errors.OpPrereqError("Can't parse the given amount '%s'" % args[2],
587 op = opcodes.OpInstanceGrowDisk(instance_name=instance,
588 disk=disk, amount=amount,
589 wait_for_sync=opts.wait_for_sync,
590 absolute=opts.absolute)
591 SubmitOrSend(op, opts)
595 def _StartupInstance(name, opts):
596 """Startup instances.
598 This returns the opcode to start an instance, and its decorator will
599 wrap this into a loop starting all desired instances.
601 @param name: the name of the instance to act on
602 @param opts: the command line options selected by the user
603 @return: the opcode needed for the operation
606 op = opcodes.OpInstanceStartup(instance_name=name,
608 ignore_offline_nodes=opts.ignore_offline,
609 no_remember=opts.no_remember,
610 startup_paused=opts.startup_paused)
611 # do not add these parameters to the opcode unless they're defined
613 op.hvparams = opts.hvparams
615 op.beparams = opts.beparams
619 def _RebootInstance(name, opts):
620 """Reboot instance(s).
622 This returns the opcode to reboot an instance, and its decorator
623 will wrap this into a loop rebooting all desired instances.
625 @param name: the name of the instance to act on
626 @param opts: the command line options selected by the user
627 @return: the opcode needed for the operation
630 return opcodes.OpInstanceReboot(instance_name=name,
631 reboot_type=opts.reboot_type,
632 ignore_secondaries=opts.ignore_secondaries,
633 shutdown_timeout=opts.shutdown_timeout)
636 def _ShutdownInstance(name, opts):
637 """Shutdown an instance.
639 This returns the opcode to shutdown an instance, and its decorator
640 will wrap this into a loop shutting down all desired instances.
642 @param name: the name of the instance to act on
643 @param opts: the command line options selected by the user
644 @return: the opcode needed for the operation
647 return opcodes.OpInstanceShutdown(instance_name=name,
649 timeout=opts.timeout,
650 ignore_offline_nodes=opts.ignore_offline,
651 no_remember=opts.no_remember)
654 def ReplaceDisks(opts, args):
655 """Replace the disks of an instance
657 @param opts: the command line options selected by the user
659 @param args: should contain only one element, the instance name
661 @return: the desired exit code
664 new_2ndary = opts.dst_node
665 iallocator = opts.iallocator
666 if opts.disks is None:
670 disks = [int(i) for i in opts.disks.split(",")]
671 except (TypeError, ValueError), err:
672 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
674 cnt = [opts.on_primary, opts.on_secondary, opts.auto,
675 new_2ndary is not None, iallocator is not None].count(True)
677 raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -I"
678 " options must be passed", errors.ECODE_INVAL)
679 elif opts.on_primary:
680 mode = constants.REPLACE_DISK_PRI
681 elif opts.on_secondary:
682 mode = constants.REPLACE_DISK_SEC
684 mode = constants.REPLACE_DISK_AUTO
686 raise errors.OpPrereqError("Cannot specify disks when using automatic"
687 " mode", errors.ECODE_INVAL)
688 elif new_2ndary is not None or iallocator is not None:
690 mode = constants.REPLACE_DISK_CHG
692 op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
693 remote_node=new_2ndary, mode=mode,
694 iallocator=iallocator,
695 early_release=opts.early_release,
696 ignore_ipolicy=opts.ignore_ipolicy)
697 SubmitOrSend(op, opts)
701 def FailoverInstance(opts, args):
702 """Failover an instance.
704 The failover is done by shutting it down on its present node and
705 starting it on the secondary.
707 @param opts: the command line options selected by the user
709 @param args: should contain only one element, the instance name
711 @return: the desired exit code
715 instance_name = args[0]
717 iallocator = opts.iallocator
718 target_node = opts.dst_node
720 if iallocator and target_node:
721 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
722 " node (-n) but not both", errors.ECODE_INVAL)
725 _EnsureInstancesExist(cl, [instance_name])
727 usertext = ("Failover will happen to image %s."
728 " This requires a shutdown of the instance. Continue?" %
730 if not AskUser(usertext):
733 op = opcodes.OpInstanceFailover(instance_name=instance_name,
734 ignore_consistency=opts.ignore_consistency,
735 shutdown_timeout=opts.shutdown_timeout,
736 iallocator=iallocator,
737 target_node=target_node,
738 ignore_ipolicy=opts.ignore_ipolicy)
739 SubmitOrSend(op, opts, cl=cl)
743 def MigrateInstance(opts, args):
744 """Migrate an instance.
746 The migrate is done without shutdown.
748 @param opts: the command line options selected by the user
750 @param args: should contain only one element, the instance name
752 @return: the desired exit code
756 instance_name = args[0]
758 iallocator = opts.iallocator
759 target_node = opts.dst_node
761 if iallocator and target_node:
762 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
763 " node (-n) but not both", errors.ECODE_INVAL)
766 _EnsureInstancesExist(cl, [instance_name])
769 usertext = ("Instance %s will be recovered from a failed migration."
770 " Note that the migration procedure (including cleanup)" %
773 usertext = ("Instance %s will be migrated. Note that migration" %
775 usertext += (" might impact the instance if anything goes wrong"
776 " (e.g. due to bugs in the hypervisor). Continue?")
777 if not AskUser(usertext):
780 # this should be removed once --non-live is deprecated
781 if not opts.live and opts.migration_mode is not None:
782 raise errors.OpPrereqError("Only one of the --non-live and "
783 "--migration-mode options can be passed",
785 if not opts.live: # --non-live passed
786 mode = constants.HT_MIGRATION_NONLIVE
788 mode = opts.migration_mode
790 op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
791 cleanup=opts.cleanup, iallocator=iallocator,
792 target_node=target_node,
793 allow_failover=opts.allow_failover,
794 allow_runtime_changes=opts.allow_runtime_chgs,
795 ignore_ipolicy=opts.ignore_ipolicy)
796 SubmitOrSend(op, cl=cl, opts=opts)
800 def MoveInstance(opts, args):
803 @param opts: the command line options selected by the user
805 @param args: should contain only one element, the instance name
807 @return: the desired exit code
811 instance_name = args[0]
815 usertext = ("Instance %s will be moved."
816 " This requires a shutdown of the instance. Continue?" %
818 if not AskUser(usertext):
821 op = opcodes.OpInstanceMove(instance_name=instance_name,
822 target_node=opts.node,
823 shutdown_timeout=opts.shutdown_timeout,
824 ignore_consistency=opts.ignore_consistency,
825 ignore_ipolicy=opts.ignore_ipolicy)
826 SubmitOrSend(op, opts, cl=cl)
830 def ConnectToInstanceConsole(opts, args):
831 """Connect to the console of an instance.
833 @param opts: the command line options selected by the user
835 @param args: should contain only one element, the instance name
837 @return: the desired exit code
840 instance_name = args[0]
844 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
845 ((console_data, oper_state), ) = \
846 cl.QueryInstances([instance_name], ["console", "oper_state"], False)
848 # Ensure client connection is closed while external commands are run
855 # Instance is running
856 raise errors.OpExecError("Console information for instance %s is"
857 " unavailable" % instance_name)
859 raise errors.OpExecError("Instance %s is not running, can't get console" %
862 return _DoConsole(objects.InstanceConsole.FromDict(console_data),
863 opts.show_command, cluster_name)
866 def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
867 _runcmd_fn=utils.RunCmd):
868 """Acts based on the result of L{opcodes.OpInstanceConsole}.
870 @type console: L{objects.InstanceConsole}
871 @param console: Console object
872 @type show_command: bool
873 @param show_command: Whether to just display commands
874 @type cluster_name: string
875 @param cluster_name: Cluster name as retrieved from master daemon
878 assert console.Validate()
880 if console.kind == constants.CONS_MESSAGE:
881 feedback_fn(console.message)
882 elif console.kind == constants.CONS_VNC:
883 feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
884 " URL <vnc://%s:%s/>",
885 console.instance, console.host, console.port,
886 console.display, console.host, console.port)
887 elif console.kind == constants.CONS_SPICE:
888 feedback_fn("Instance %s has SPICE listening on %s:%s", console.instance,
889 console.host, console.port)
890 elif console.kind == constants.CONS_SSH:
891 # Convert to string if not already one
892 if isinstance(console.command, basestring):
893 cmd = console.command
895 cmd = utils.ShellQuoteArgs(console.command)
897 srun = ssh.SshRunner(cluster_name=cluster_name)
898 ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
899 batch=True, quiet=False, tty=True)
902 feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
904 result = _runcmd_fn(ssh_cmd, interactive=True)
906 logging.error("Console command \"%s\" failed with reason '%s' and"
907 " output %r", result.cmd, result.fail_reason,
909 raise errors.OpExecError("Connection to console of instance %s failed,"
910 " please check cluster configuration" %
913 raise errors.GenericError("Unknown console type '%s'" % console.kind)
915 return constants.EXIT_SUCCESS
918 def _FormatDiskDetails(dev_type, dev, roman):
919 """Formats the logical_id of a disk.
922 if dev_type == constants.DT_DRBD8:
923 drbd_info = dev["drbd_info"]
925 ("nodeA", "%s, minor=%s" %
926 (drbd_info["primary_node"],
927 compat.TryToRoman(drbd_info["primary_minor"],
929 ("nodeB", "%s, minor=%s" %
930 (drbd_info["secondary_node"],
931 compat.TryToRoman(drbd_info["secondary_minor"],
933 ("port", str(compat.TryToRoman(drbd_info["port"], convert=roman))),
934 ("auth key", str(drbd_info["secret"])),
936 elif dev_type == constants.DT_PLAIN:
937 vg_name, lv_name = dev["logical_id"]
938 data = ["%s/%s" % (vg_name, lv_name)]
940 data = [str(dev["logical_id"])]
945 def _FormatBlockDevInfo(idx, top_level, dev, roman):
946 """Show block device information.
948 This is only used by L{ShowInstanceConfig}, but it's too big to be
949 left for an inline definition.
952 @param idx: the index of the current disk
953 @type top_level: boolean
954 @param top_level: if this a top-level disk?
956 @param dev: dictionary with disk information
958 @param roman: whether to try to use roman integers
959 @return: a list of either strings, tuples or lists
960 (which should be formatted at a higher indent level)
963 def helper(dtype, status):
964 """Format one line for physical device status.
967 @param dtype: a constant from the L{constants.DTS_BLOCK} set
969 @param status: a tuple as returned from L{backend.FindBlockDevice}
970 @return: the string representing the status
976 (path, major, minor, syncp, estt, degr, ldisk_status) = status
980 major_string = str(compat.TryToRoman(major, convert=roman))
985 minor_string = str(compat.TryToRoman(minor, convert=roman))
987 txt += ("%s (%s:%s)" % (path, major_string, minor_string))
988 if dtype in (constants.DT_DRBD8, ):
989 if syncp is not None:
990 sync_text = "*RECOVERING* %5.2f%%," % syncp
992 sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
994 sync_text += " ETA unknown"
996 sync_text = "in sync"
998 degr_text = "*DEGRADED*"
1001 if ldisk_status == constants.LDS_FAULTY:
1002 ldisk_text = " *MISSING DISK*"
1003 elif ldisk_status == constants.LDS_UNKNOWN:
1004 ldisk_text = " *UNCERTAIN STATE*"
1007 txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1008 elif dtype == constants.DT_PLAIN:
1009 if ldisk_status == constants.LDS_FAULTY:
1010 ldisk_text = " *FAILED* (failed drive?)"
1018 if dev["iv_name"] is not None:
1019 txt = dev["iv_name"]
1021 txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1023 txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1024 if isinstance(dev["size"], int):
1025 nice_size = utils.FormatUnit(dev["size"], "h")
1027 nice_size = str(dev["size"])
1028 data = [(txt, "%s, size %s" % (dev["dev_type"], nice_size))]
1030 if dev["spindles"] is not None:
1031 data.append(("spindles", dev["spindles"]))
1032 data.append(("access mode", dev["mode"]))
1033 if dev["logical_id"] is not None:
1035 l_id = _FormatDiskDetails(dev["dev_type"], dev, roman)
1037 l_id = [str(dev["logical_id"])]
1039 data.append(("logical_id", l_id[0]))
1044 data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1047 data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1049 data.append(("name", dev["name"]))
1050 data.append(("UUID", dev["uuid"]))
1053 data.append(("child devices", [
1054 _FormatBlockDevInfo(c_idx, False, child, roman)
1055 for c_idx, child in enumerate(dev["children"])
1060 def _FormatInstanceNicInfo(idx, nic):
1061 """Helper function for L{_FormatInstanceInfo()}"""
1062 (name, uuid, ip, mac, mode, link, vlan, _, netinfo) = nic
1065 network_name = netinfo["name"]
1067 ("nic/%d" % idx, ""),
1070 ("mode", str(mode)),
1071 ("link", str(link)),
1072 ("vlan", str(vlan)),
1073 ("network", str(network_name)),
1074 ("UUID", str(uuid)),
1075 ("name", str(name)),
1079 def _FormatInstanceNodesInfo(instance):
1080 """Helper function for L{_FormatInstanceInfo()}"""
1081 pgroup = ("%s (UUID %s)" %
1082 (instance["pnode_group_name"], instance["pnode_group_uuid"]))
1083 secs = utils.CommaJoin(("%s (group %s, group UUID %s)" %
1084 (name, group_name, group_uuid))
1085 for (name, group_name, group_uuid) in
1086 zip(instance["snodes"],
1087 instance["snodes_group_names"],
1088 instance["snodes_group_uuids"]))
1091 ("primary", instance["pnode"]),
1094 [("secondaries", secs)],
1098 def _GetVncConsoleInfo(instance):
1099 """Helper function for L{_FormatInstanceInfo()}"""
1100 vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1102 if vnc_bind_address:
1103 port = instance["network_port"]
1104 display = int(port) - constants.VNC_BASE_PORT
1105 if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1106 vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1109 elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1110 vnc_console_port = ("%s:%s (node %s) (display %s)" %
1111 (vnc_bind_address, port,
1112 instance["pnode"], display))
1114 # vnc bind address is a file
1115 vnc_console_port = "%s:%s" % (instance["pnode"],
1117 ret = "vnc to %s" % vnc_console_port
1123 def _FormatInstanceInfo(instance, roman_integers):
1124 """Format instance information for L{cli.PrintGenericInfo()}"""
1125 istate = "configured to be %s" % instance["config_state"]
1126 if instance["run_state"]:
1127 istate += ", actual state is %s" % instance["run_state"]
1129 ("Instance name", instance["name"]),
1130 ("UUID", instance["uuid"]),
1132 str(compat.TryToRoman(instance["serial_no"], convert=roman_integers))),
1133 ("Creation time", utils.FormatTime(instance["ctime"])),
1134 ("Modification time", utils.FormatTime(instance["mtime"])),
1136 ("Nodes", _FormatInstanceNodesInfo(instance)),
1137 ("Operating system", instance["os"]),
1138 ("Operating system parameters",
1139 FormatParamsDictInfo(instance["os_instance"], instance["os_actual"])),
1142 if "network_port" in instance:
1143 info.append(("Allocated network port",
1144 str(compat.TryToRoman(instance["network_port"],
1145 convert=roman_integers))))
1146 info.append(("Hypervisor", instance["hypervisor"]))
1147 console = _GetVncConsoleInfo(instance)
1149 info.append(("console connection", console))
1150 # deprecated "memory" value, kept for one version for compatibility
1151 # TODO(ganeti 2.7) remove.
1152 be_actual = copy.deepcopy(instance["be_actual"])
1153 be_actual["memory"] = be_actual[constants.BE_MAXMEM]
1155 ("Hypervisor parameters",
1156 FormatParamsDictInfo(instance["hv_instance"], instance["hv_actual"])),
1157 ("Back-end parameters",
1158 FormatParamsDictInfo(instance["be_instance"], be_actual)),
1160 _FormatInstanceNicInfo(idx, nic)
1161 for (idx, nic) in enumerate(instance["nics"])
1163 ("Disk template", instance["disk_template"]),
1165 _FormatBlockDevInfo(idx, True, device, roman_integers)
1166 for (idx, device) in enumerate(instance["disks"])
1172 def ShowInstanceConfig(opts, args):
1173 """Compute instance run-time status.
1175 @param opts: the command line options selected by the user
1177 @param args: either an empty list, and then we query all
1178 instances, or should contain a list of instance names
1180 @return: the desired exit code
1183 if not args and not opts.show_all:
1184 ToStderr("No instance selected."
1185 " Please pass in --all if you want to query all instances.\n"
1186 "Note that this can take a long time on a big cluster.")
1188 elif args and opts.show_all:
1189 ToStderr("Cannot use --all if you specify instance names.")
1193 op = opcodes.OpInstanceQueryData(instances=args, static=opts.static,
1194 use_locking=not opts.static)
1195 result = SubmitOpCode(op, opts=opts)
1197 ToStdout("No instances.")
1201 _FormatInstanceInfo(instance, opts.roman_integers)
1202 for instance in result.values()
1207 def _ConvertNicDiskModifications(mods):
1208 """Converts NIC/disk modifications from CLI to opcode.
1210 When L{opcodes.OpInstanceSetParams} was changed to support adding/removing
1211 disks at arbitrary indices, its parameter format changed. This function
1212 converts legacy requests (e.g. "--net add" or "--disk add:size=4G") to the
1213 newer format and adds support for new-style requests (e.g. "--new 4:add").
1215 @type mods: list of tuples
1216 @param mods: Modifications as given by command line parser
1217 @rtype: list of tuples
1218 @return: Modifications as understood by L{opcodes.OpInstanceSetParams}
1223 for (identifier, params) in mods:
1224 if identifier == constants.DDM_ADD:
1225 # Add item as last item (legacy interface)
1226 action = constants.DDM_ADD
1228 elif identifier == constants.DDM_REMOVE:
1229 # Remove last item (legacy interface)
1230 action = constants.DDM_REMOVE
1233 # Modifications and adding/removing at arbitrary indices
1234 add = params.pop(constants.DDM_ADD, _MISSING)
1235 remove = params.pop(constants.DDM_REMOVE, _MISSING)
1236 modify = params.pop(constants.DDM_MODIFY, _MISSING)
1238 if modify is _MISSING:
1239 if not (add is _MISSING or remove is _MISSING):
1240 raise errors.OpPrereqError("Cannot add and remove at the same time",
1242 elif add is not _MISSING:
1243 action = constants.DDM_ADD
1244 elif remove is not _MISSING:
1245 action = constants.DDM_REMOVE
1247 action = constants.DDM_MODIFY
1249 elif add is _MISSING and remove is _MISSING:
1250 action = constants.DDM_MODIFY
1252 raise errors.OpPrereqError("Cannot modify and add/remove at the"
1253 " same time", errors.ECODE_INVAL)
1255 assert not (constants.DDMS_VALUES_WITH_MODIFY & set(params.keys()))
1257 if action == constants.DDM_REMOVE and params:
1258 raise errors.OpPrereqError("Not accepting parameters on removal",
1261 result.append((action, identifier, params))
1266 def _ParseDiskSizes(mods):
1267 """Parses disk sizes in parameters.
1270 for (action, _, params) in mods:
1271 if params and constants.IDISK_SIZE in params:
1272 params[constants.IDISK_SIZE] = \
1273 utils.ParseUnit(params[constants.IDISK_SIZE])
1274 elif action == constants.DDM_ADD:
1275 raise errors.OpPrereqError("Missing required parameter 'size'",
1281 def SetInstanceParams(opts, args):
1282 """Modifies an instance.
1284 All parameters take effect only at the next restart of the instance.
1286 @param opts: the command line options selected by the user
1288 @param args: should contain only one element, the instance name
1290 @return: the desired exit code
1293 if not (opts.nics or opts.disks or opts.disk_template or
1294 opts.hvparams or opts.beparams or opts.os or opts.osparams or
1295 opts.offline_inst or opts.online_inst or opts.runtime_mem or
1296 opts.new_primary_node):
1297 ToStderr("Please give at least one of the parameters.")
1300 for param in opts.beparams:
1301 if isinstance(opts.beparams[param], basestring):
1302 if opts.beparams[param].lower() == "default":
1303 opts.beparams[param] = constants.VALUE_DEFAULT
1305 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT,
1306 allowed_values=[constants.VALUE_DEFAULT])
1308 for param in opts.hvparams:
1309 if isinstance(opts.hvparams[param], basestring):
1310 if opts.hvparams[param].lower() == "default":
1311 opts.hvparams[param] = constants.VALUE_DEFAULT
1313 utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1314 allowed_values=[constants.VALUE_DEFAULT])
1315 FixHvParams(opts.hvparams)
1317 nics = _ConvertNicDiskModifications(opts.nics)
1318 for action, _, __ in nics:
1319 if action == constants.DDM_MODIFY and opts.hotplug and not opts.force:
1320 usertext = ("You are about to hot-modify a NIC. This will be done"
1321 " by removing the exisiting and then adding a new one."
1322 " Network connection might be lost. Continue?")
1323 if not AskUser(usertext):
1326 disks = _ParseDiskSizes(_ConvertNicDiskModifications(opts.disks))
1328 if (opts.disk_template and
1329 opts.disk_template in constants.DTS_INT_MIRROR and
1331 ToStderr("Changing the disk template to a mirrored one requires"
1332 " specifying a secondary node")
1335 if opts.offline_inst:
1337 elif opts.online_inst:
1342 op = opcodes.OpInstanceSetParams(instance_name=args[0],
1345 hotplug=opts.hotplug,
1346 hotplug_if_possible=opts.hotplug_if_possible,
1347 disk_template=opts.disk_template,
1348 remote_node=opts.node,
1349 pnode=opts.new_primary_node,
1350 hvparams=opts.hvparams,
1351 beparams=opts.beparams,
1352 runtime_mem=opts.runtime_mem,
1354 osparams=opts.osparams,
1355 force_variant=opts.force_variant,
1357 wait_for_sync=opts.wait_for_sync,
1359 conflicts_check=opts.conflicts_check,
1360 ignore_ipolicy=opts.ignore_ipolicy)
1362 # even if here we process the result, we allow submit only
1363 result = SubmitOrSend(op, opts)
1366 ToStdout("Modified instance %s", args[0])
1367 for param, data in result:
1368 ToStdout(" - %-5s -> %s", param, data)
1369 ToStdout("Please don't forget that most parameters take effect"
1370 " only at the next (re)start of the instance initiated by"
1371 " ganeti; restarting from within the instance will"
1376 def ChangeGroup(opts, args):
1377 """Moves an instance to another group.
1380 (instance_name, ) = args
1384 op = opcodes.OpInstanceChangeGroup(instance_name=instance_name,
1385 iallocator=opts.iallocator,
1386 target_groups=opts.to,
1387 early_release=opts.early_release)
1388 result = SubmitOrSend(op, opts, cl=cl)
1390 # Keep track of submitted jobs
1391 jex = JobExecutor(cl=cl, opts=opts)
1393 for (status, job_id) in result[constants.JOB_IDS_KEY]:
1394 jex.AddJobId(None, status, job_id)
1396 results = jex.GetResults()
1397 bad_cnt = len([row for row in results if not row[0]])
1399 ToStdout("Instance '%s' changed group successfully.", instance_name)
1400 rcode = constants.EXIT_SUCCESS
1402 ToStdout("There were %s errors while changing group of instance '%s'.",
1403 bad_cnt, instance_name)
1404 rcode = constants.EXIT_FAILURE
1409 # multi-instance selection options
1410 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1411 help="Do not ask for confirmation when more than"
1412 " one instance is affected",
1413 action="store_true", default=False)
1415 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1416 help="Filter by nodes (primary only)",
1417 const=_EXPAND_NODES_PRI, action="store_const")
1419 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1420 help="Filter by nodes (secondary only)",
1421 const=_EXPAND_NODES_SEC, action="store_const")
1423 m_node_opt = cli_option("--node", dest="multi_mode",
1424 help="Filter by nodes (primary and secondary)",
1425 const=_EXPAND_NODES_BOTH, action="store_const")
1427 m_clust_opt = cli_option("--all", dest="multi_mode",
1428 help="Select all instances in the cluster",
1429 const=_EXPAND_CLUSTER, action="store_const")
1431 m_inst_opt = cli_option("--instance", dest="multi_mode",
1432 help="Filter by instance name [default]",
1433 const=_EXPAND_INSTANCES, action="store_const")
1435 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1436 help="Filter by node tag",
1437 const=_EXPAND_NODES_BOTH_BY_TAGS,
1438 action="store_const")
1440 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1441 help="Filter by primary node tag",
1442 const=_EXPAND_NODES_PRI_BY_TAGS,
1443 action="store_const")
1445 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1446 help="Filter by secondary node tag",
1447 const=_EXPAND_NODES_SEC_BY_TAGS,
1448 action="store_const")
1450 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1451 help="Filter by instance tag",
1452 const=_EXPAND_INSTANCES_BY_TAGS,
1453 action="store_const")
1455 # this is defined separately due to readability only
1466 AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1467 "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1468 "Creates and adds a new instance to the cluster"),
1470 BatchCreate, [ArgFile(min=1, max=1)],
1471 [DRY_RUN_OPT, PRIORITY_OPT, IALLOCATOR_OPT] + SUBMIT_OPTS,
1473 "Create a bunch of instances based on specs in the file."),
1475 ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1476 [SHOWCMD_OPT, PRIORITY_OPT],
1477 "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1479 FailoverInstance, ARGS_ONE_INSTANCE,
1480 [FORCE_OPT, IGNORE_CONSIST_OPT] + SUBMIT_OPTS +
1481 [SHUTDOWN_TIMEOUT_OPT,
1482 DRY_RUN_OPT, PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT,
1483 IGNORE_IPOLICY_OPT, CLEANUP_OPT],
1484 "[-f] <instance>", "Stops the instance, changes its primary node and"
1485 " (if it was originally running) starts it on the new node"
1486 " (the secondary for mirrored instances or any node"
1487 " for shared storage)."),
1489 MigrateInstance, ARGS_ONE_INSTANCE,
1490 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1491 PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT, ALLOW_FAILOVER_OPT,
1492 IGNORE_IPOLICY_OPT, NORUNTIME_CHGS_OPT] + SUBMIT_OPTS,
1493 "[-f] <instance>", "Migrate instance to its secondary node"
1494 " (only for mirrored instances)"),
1496 MoveInstance, ARGS_ONE_INSTANCE,
1497 [FORCE_OPT] + SUBMIT_OPTS +
1499 SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_CONSIST_OPT,
1500 IGNORE_IPOLICY_OPT],
1501 "[-f] <instance>", "Move instance to an arbitrary node"
1502 " (only for instances of type file and lv)"),
1504 ShowInstanceConfig, ARGS_MANY_INSTANCES,
1505 [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1506 "[-s] {--all | <instance>...}",
1507 "Show information on the specified instance(s)"),
1509 ListInstances, ARGS_MANY_INSTANCES,
1510 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
1513 "Lists the instances and their status. The available fields can be shown"
1514 " using the \"list-fields\" command (see the man page for details)."
1515 " The default field list is (in order): %s." %
1516 utils.CommaJoin(_LIST_DEF_FIELDS),
1519 ListInstanceFields, [ArgUnknown()],
1520 [NOHDR_OPT, SEP_OPT],
1522 "Lists all available fields for instances"),
1524 ReinstallInstance, [ArgInstance()],
1525 [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1526 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1527 m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT]
1528 + SUBMIT_OPTS + [DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1529 "[-f] <instance>", "Reinstall a stopped instance"),
1531 RemoveInstance, ARGS_ONE_INSTANCE,
1532 [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT] + SUBMIT_OPTS
1533 + [DRY_RUN_OPT, PRIORITY_OPT],
1534 "[-f] <instance>", "Shuts down the instance and removes it"),
1537 [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1538 [NOIPCHECK_OPT, NONAMECHECK_OPT] + SUBMIT_OPTS
1539 + [DRY_RUN_OPT, PRIORITY_OPT],
1540 "<instance> <new_name>", "Rename the instance"),
1542 ReplaceDisks, ARGS_ONE_INSTANCE,
1543 [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1544 NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT] + SUBMIT_OPTS
1545 + [DRY_RUN_OPT, PRIORITY_OPT, IGNORE_IPOLICY_OPT],
1546 "[-s|-p|-a|-n NODE|-I NAME] <instance>",
1547 "Replaces disks for the instance"),
1549 SetInstanceParams, ARGS_ONE_INSTANCE,
1550 [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT] + SUBMIT_OPTS +
1551 [DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1552 OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT, NWSYNC_OPT, OFFLINE_INST_OPT,
1553 ONLINE_INST_OPT, IGNORE_IPOLICY_OPT, RUNTIME_MEM_OPT,
1554 NOCONFLICTSCHECK_OPT, NEW_PRIMARY_OPT, HOTPLUG_OPT,
1555 HOTPLUG_IF_POSSIBLE_OPT],
1556 "<instance>", "Alters the parameters of an instance"),
1558 GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1559 [FORCE_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1560 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1561 m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT] + SUBMIT_OPTS
1562 + [DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT, NO_REMEMBER_OPT],
1563 "<instance>", "Stops an instance"),
1565 GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1566 [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1567 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1568 m_inst_tags_opt, m_clust_opt, m_inst_opt] + SUBMIT_OPTS +
1570 BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT,
1571 NO_REMEMBER_OPT, STARTUP_PAUSED_OPT],
1572 "<instance>", "Starts an instance"),
1574 GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1575 [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1576 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt] + SUBMIT_OPTS +
1577 [m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1578 m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1579 "<instance>", "Reboots an instance"),
1581 ActivateDisks, ARGS_ONE_INSTANCE,
1582 SUBMIT_OPTS + [IGNORE_SIZE_OPT, PRIORITY_OPT, WFSYNC_OPT],
1583 "<instance>", "Activate an instance's disks"),
1584 "deactivate-disks": (
1585 DeactivateDisks, ARGS_ONE_INSTANCE,
1586 [FORCE_OPT] + SUBMIT_OPTS + [DRY_RUN_OPT, PRIORITY_OPT],
1587 "[-f] <instance>", "Deactivate an instance's disks"),
1589 RecreateDisks, ARGS_ONE_INSTANCE,
1591 [DISK_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT,
1593 "<instance>", "Recreate an instance's disks"),
1596 [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1597 ArgUnknown(min=1, max=1)],
1598 SUBMIT_OPTS + [NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT, ABSOLUTE_OPT],
1599 "<instance> <disk> <size>", "Grow an instance's disk"),
1601 ChangeGroup, ARGS_ONE_INSTANCE,
1602 [TO_GROUP_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT, PRIORITY_OPT]
1604 "[-I <iallocator>] [--to <group>]", "Change group of instance"),
1606 ListTags, ARGS_ONE_INSTANCE, [],
1607 "<instance_name>", "List the tags of the given instance"),
1609 AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1610 [TAG_SRC_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
1611 "<instance_name> tag...", "Add tags to the given instance"),
1613 RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1614 [TAG_SRC_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
1615 "<instance_name> tag...", "Remove tags from given instance"),
1618 #: dictionary with aliases for commands
1627 return GenericMain(commands, aliases=aliases,
1628 override={"tag_type": constants.TAG_INSTANCE},
1629 env_override=_ENV_OVERRIDE)