4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 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-msg=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
31 from cStringIO import StringIO
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
44 _SHUTDOWN_CLUSTER = "cluster"
45 _SHUTDOWN_NODES_BOTH = "nodes"
46 _SHUTDOWN_NODES_PRI = "nodes-pri"
47 _SHUTDOWN_NODES_SEC = "nodes-sec"
48 _SHUTDOWN_NODES_BOTH_BY_TAGS = "nodes-by-tags"
49 _SHUTDOWN_NODES_PRI_BY_TAGS = "nodes-pri-by-tags"
50 _SHUTDOWN_NODES_SEC_BY_TAGS = "nodes-sec-by-tags"
51 _SHUTDOWN_INSTANCES = "instances"
52 _SHUTDOWN_INSTANCES_BY_TAGS = "instances-by-tags"
54 _SHUTDOWN_NODES_TAGS_MODES = (
55 _SHUTDOWN_NODES_BOTH_BY_TAGS,
56 _SHUTDOWN_NODES_PRI_BY_TAGS,
57 _SHUTDOWN_NODES_SEC_BY_TAGS)
60 #: default list of options for L{ListInstances}
62 "name", "hypervisor", "os", "pnode", "status", "oper_ram",
66 def _ExpandMultiNames(mode, names, client=None):
67 """Expand the given names using the passed mode.
69 For _SHUTDOWN_CLUSTER, all instances will be returned. For
70 _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
71 primary/secondary will be returned. For _SHUTDOWN_NODES_BOTH, all
72 instances having those nodes as either primary or secondary will be
73 returned. For _SHUTDOWN_INSTANCES, the given instances will be
76 @param mode: one of L{_SHUTDOWN_CLUSTER}, L{_SHUTDOWN_NODES_BOTH},
77 L{_SHUTDOWN_NODES_PRI}, L{_SHUTDOWN_NODES_SEC} or
78 L{_SHUTDOWN_INSTANCES}
79 @param names: a list of names; for cluster, it must be empty,
80 and for node and instance it must be a list of valid item
81 names (short names are valid as usual, e.g. node1 instead of
84 @return: the list of names after the expansion
85 @raise errors.ProgrammerError: for unknown selection type
86 @raise errors.OpPrereqError: for invalid input parameters
89 # pylint: disable-msg=W0142
93 if mode == _SHUTDOWN_CLUSTER:
95 raise errors.OpPrereqError("Cluster filter mode takes no arguments",
97 idata = client.QueryInstances([], ["name"], False)
98 inames = [row[0] for row in idata]
100 elif mode in (_SHUTDOWN_NODES_BOTH,
102 _SHUTDOWN_NODES_SEC) + _SHUTDOWN_NODES_TAGS_MODES:
103 if mode in _SHUTDOWN_NODES_TAGS_MODES:
105 raise errors.OpPrereqError("No node tags passed", errors.ECODE_INVAL)
106 ndata = client.QueryNodes([], ["name", "pinst_list",
107 "sinst_list", "tags"], False)
108 ndata = [row for row in ndata if set(row[3]).intersection(names)]
111 raise errors.OpPrereqError("No node names passed", errors.ECODE_INVAL)
112 ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
115 ipri = [row[1] for row in ndata]
116 pri_names = list(itertools.chain(*ipri))
117 isec = [row[2] for row in ndata]
118 sec_names = list(itertools.chain(*isec))
119 if mode in (_SHUTDOWN_NODES_BOTH, _SHUTDOWN_NODES_BOTH_BY_TAGS):
120 inames = pri_names + sec_names
121 elif mode in (_SHUTDOWN_NODES_PRI, _SHUTDOWN_NODES_PRI_BY_TAGS):
123 elif mode in (_SHUTDOWN_NODES_SEC, _SHUTDOWN_NODES_SEC_BY_TAGS):
126 raise errors.ProgrammerError("Unhandled shutdown type")
127 elif mode == _SHUTDOWN_INSTANCES:
129 raise errors.OpPrereqError("No instance names passed",
131 idata = client.QueryInstances(names, ["name"], False)
132 inames = [row[0] for row in idata]
133 elif mode == _SHUTDOWN_INSTANCES_BY_TAGS:
135 raise errors.OpPrereqError("No instance tags passed",
137 idata = client.QueryInstances([], ["name", "tags"], False)
138 inames = [row[0] for row in idata if set(row[1]).intersection(names)]
140 raise errors.OpPrereqError("Unknown mode '%s'" % mode, errors.ECODE_INVAL)
145 def _ConfirmOperation(inames, text, extra=""):
146 """Ask the user to confirm an operation on a list of instances.
148 This function is used to request confirmation for doing an operation
149 on a given list of instances.
152 @param inames: the list of names that we display when
153 we ask for confirmation
155 @param text: the operation that the user should confirm
156 (e.g. I{shutdown} or I{startup})
158 @return: True or False depending on user's confirmation.
162 msg = ("The %s will operate on %d instances.\n%s"
163 "Do you want to continue?" % (text, count, extra))
164 affected = ("\nAffected instances:\n" +
165 "\n".join([" %s" % name for name in inames]))
167 choices = [('y', True, 'Yes, execute the %s' % text),
168 ('n', False, 'No, abort the %s' % text)]
171 choices.insert(1, ('v', 'v', 'View the list of affected instances'))
176 choice = AskUser(ask, choices)
179 choice = AskUser(msg + affected, choices)
183 def _EnsureInstancesExist(client, names):
184 """Check for and ensure the given instance names exist.
186 This function will raise an OpPrereqError in case they don't
187 exist. Otherwise it will exit cleanly.
189 @type client: L{ganeti.luxi.Client}
190 @param client: the client to use for the query
192 @param names: the list of instance names to query
193 @raise errors.OpPrereqError: in case any instance is missing
196 # TODO: change LUInstanceQuery to that it actually returns None
197 # instead of raising an exception, or devise a better mechanism
198 result = client.QueryInstances(names, ["name"], False)
199 for orig_name, row in zip(names, result):
201 raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name,
205 def GenericManyOps(operation, fn):
206 """Generic multi-instance operations.
208 The will return a wrapper that processes the options and arguments
209 given, and uses the passed function to build the opcode needed for
210 the specific operation. Thus all the generic loop/confirmation code
211 is abstracted into this function.
214 def realfn(opts, args):
215 if opts.multi_mode is None:
216 opts.multi_mode = _SHUTDOWN_INSTANCES
218 inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
220 raise errors.OpPrereqError("Selection filter does not match"
221 " any instances", errors.ECODE_INVAL)
222 multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
223 if not (opts.force_multi or not multi_on
224 or _ConfirmOperation(inames, operation)):
226 jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
229 jex.QueueJob(name, op)
230 results = jex.WaitOrShow(not opts.submit_only)
231 rcode = compat.all(row[0] for row in results)
232 return int(not rcode)
236 def ListInstances(opts, args):
237 """List instances and their properties.
239 @param opts: the command line options selected by the user
241 @param args: should be an empty list
243 @return: the desired exit code
246 selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
248 fmtoverride = dict.fromkeys(["tags", "disk.sizes", "nic.macs", "nic.ips",
249 "nic.modes", "nic.links", "nic.bridges",
251 (lambda value: ",".join(str(item)
255 return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units,
256 opts.separator, not opts.no_headers,
257 format_override=fmtoverride)
260 def ListInstanceFields(opts, args):
261 """List instance fields.
263 @param opts: the command line options selected by the user
265 @param args: fields to list, or empty for all
267 @return: the desired exit code
270 return GenericListFields(constants.QR_INSTANCE, args, opts.separator,
274 def AddInstance(opts, args):
275 """Add an instance to the cluster.
277 This is just a wrapper over GenericInstanceCreate.
280 return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
283 def BatchCreate(opts, args):
284 """Create instances using a definition file.
286 This function reads a json file with instances defined
290 "disk_size": [20480],
296 "primary_node": "firstnode",
297 "secondary_node": "secondnode",
298 "iallocator": "dumb"}
301 Note that I{primary_node} and I{secondary_node} have precedence over
304 @param opts: the command line options selected by the user
306 @param args: should contain one element, the json filename
308 @return: the desired exit code
311 _DEFAULT_SPECS = {"disk_size": [20 * 1024],
314 "primary_node": None,
315 "secondary_node": None,
322 "file_storage_dir": None,
323 "force_variant": False,
324 "file_driver": 'loop'}
326 def _PopulateWithDefaults(spec):
327 """Returns a new hash combined with default values."""
328 mydict = _DEFAULT_SPECS.copy()
333 """Validate the instance specs."""
334 # Validate fields required under any circumstances
335 for required_field in ('os', 'template'):
336 if required_field not in spec:
337 raise errors.OpPrereqError('Required field "%s" is missing.' %
338 required_field, errors.ECODE_INVAL)
339 # Validate special fields
340 if spec['primary_node'] is not None:
341 if (spec['template'] in constants.DTS_NET_MIRROR and
342 spec['secondary_node'] is None):
343 raise errors.OpPrereqError('Template requires secondary node, but'
344 ' there was no secondary provided.',
346 elif spec['iallocator'] is None:
347 raise errors.OpPrereqError('You have to provide at least a primary_node'
348 ' or an iallocator.',
351 if (spec['hvparams'] and
352 not isinstance(spec['hvparams'], dict)):
353 raise errors.OpPrereqError('Hypervisor parameters must be a dict.',
356 json_filename = args[0]
358 instance_data = simplejson.loads(utils.ReadFile(json_filename))
359 except Exception, err: # pylint: disable-msg=W0703
360 ToStderr("Can't parse the instance definition file: %s" % str(err))
363 if not isinstance(instance_data, dict):
364 ToStderr("The instance definition file is not in dict format.")
367 jex = JobExecutor(opts=opts)
369 # Iterate over the instances and do:
370 # * Populate the specs with default value
371 # * Validate the instance specs
372 i_names = utils.NiceSort(instance_data.keys()) # pylint: disable-msg=E1103
374 specs = instance_data[name]
375 specs = _PopulateWithDefaults(specs)
378 hypervisor = specs['hypervisor']
379 hvparams = specs['hvparams']
382 for elem in specs['disk_size']:
384 size = utils.ParseUnit(elem)
385 except (TypeError, ValueError), err:
386 raise errors.OpPrereqError("Invalid disk size '%s' for"
388 (elem, name, err), errors.ECODE_INVAL)
389 disks.append({"size": size})
391 utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
392 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
395 for field in ('ip', 'mac', 'mode', 'link', 'bridge'):
399 tmp_nics[0][field] = specs[field]
401 if specs['nics'] is not None and tmp_nics:
402 raise errors.OpPrereqError("'nics' list incompatible with using"
403 " individual nic fields as well",
405 elif specs['nics'] is not None:
406 tmp_nics = specs['nics']
410 op = opcodes.OpInstanceCreate(instance_name=name,
412 disk_template=specs['template'],
413 mode=constants.INSTANCE_CREATE,
415 force_variant=specs["force_variant"],
416 pnode=specs['primary_node'],
417 snode=specs['secondary_node'],
419 start=specs['start'],
420 ip_check=specs['ip_check'],
421 name_check=specs['name_check'],
423 iallocator=specs['iallocator'],
424 hypervisor=hypervisor,
426 beparams=specs['backend'],
427 file_storage_dir=specs['file_storage_dir'],
428 file_driver=specs['file_driver'])
430 jex.QueueJob(name, op)
431 # we never want to wait, just show the submitted job IDs
432 jex.WaitOrShow(False)
437 def ReinstallInstance(opts, args):
438 """Reinstall an instance.
440 @param opts: the command line options selected by the user
442 @param args: should contain only one element, the name of the
443 instance to be reinstalled
445 @return: the desired exit code
448 # first, compute the desired name list
449 if opts.multi_mode is None:
450 opts.multi_mode = _SHUTDOWN_INSTANCES
452 inames = _ExpandMultiNames(opts.multi_mode, args)
454 raise errors.OpPrereqError("Selection filter does not match any instances",
457 # second, if requested, ask for an OS
458 if opts.select_os is True:
459 op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[])
460 result = SubmitOpCode(op, opts=opts)
463 ToStdout("Can't get the OS list")
466 ToStdout("Available OS templates:")
469 for (name, variants) in result:
470 for entry in CalculateOSNames(name, variants):
471 ToStdout("%3s: %s", number, entry)
472 choices.append(("%s" % number, entry, entry))
475 choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
476 selected = AskUser("Enter OS template number (or x to abort):",
479 if selected == 'exit':
480 ToStderr("User aborted reinstall, exiting")
487 # third, get confirmation: multi-reinstall requires --force-multi,
488 # single-reinstall either --force or --force-multi (--force-multi is
489 # a stronger --force)
490 multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
492 warn_msg = "Note: this will remove *all* data for the below instances!\n"
493 if not (opts.force_multi or
494 _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
497 if not (opts.force or opts.force_multi):
498 usertext = ("This will reinstall the instance %s and remove"
499 " all data. Continue?") % inames[0]
500 if not AskUser(usertext):
503 jex = JobExecutor(verbose=multi_on, opts=opts)
504 for instance_name in inames:
505 op = opcodes.OpInstanceReinstall(instance_name=instance_name,
507 force_variant=opts.force_variant,
508 osparams=opts.osparams)
509 jex.QueueJob(instance_name, op)
511 jex.WaitOrShow(not opts.submit_only)
515 def RemoveInstance(opts, args):
516 """Remove an instance.
518 @param opts: the command line options selected by the user
520 @param args: should contain only one element, the name of
521 the instance to be removed
523 @return: the desired exit code
526 instance_name = args[0]
531 _EnsureInstancesExist(cl, [instance_name])
533 usertext = ("This will remove the volumes of the instance %s"
534 " (including mirrors), thus removing all the data"
535 " of the instance. Continue?") % instance_name
536 if not AskUser(usertext):
539 op = opcodes.OpInstanceRemove(instance_name=instance_name,
540 ignore_failures=opts.ignore_failures,
541 shutdown_timeout=opts.shutdown_timeout)
542 SubmitOrSend(op, opts, cl=cl)
546 def RenameInstance(opts, args):
547 """Rename an instance.
549 @param opts: the command line options selected by the user
551 @param args: should contain two elements, the old and the
554 @return: the desired exit code
557 if not opts.name_check:
558 if not AskUser("As you disabled the check of the DNS entry, please verify"
559 " that '%s' is a FQDN. Continue?" % args[1]):
562 op = opcodes.OpInstanceRename(instance_name=args[0],
564 ip_check=opts.ip_check,
565 name_check=opts.name_check)
566 result = SubmitOrSend(op, opts)
569 ToStdout("Instance '%s' renamed to '%s'", args[0], result)
574 def ActivateDisks(opts, args):
575 """Activate an instance's disks.
577 This serves two purposes:
578 - it allows (as long as the instance is not running)
579 mounting the disks and modifying them from the node
580 - it repairs inactive secondary drbds
582 @param opts: the command line options selected by the user
584 @param args: should contain only one element, the instance name
586 @return: the desired exit code
589 instance_name = args[0]
590 op = opcodes.OpInstanceActivateDisks(instance_name=instance_name,
591 ignore_size=opts.ignore_size)
592 disks_info = SubmitOrSend(op, opts)
593 for host, iname, nname in disks_info:
594 ToStdout("%s:%s:%s", host, iname, nname)
598 def DeactivateDisks(opts, args):
599 """Deactivate an instance's disks.
601 This function takes the instance name, looks for its primary node
602 and the tries to shutdown its block devices on that node.
604 @param opts: the command line options selected by the user
606 @param args: should contain only one element, the instance name
608 @return: the desired exit code
611 instance_name = args[0]
612 op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name,
614 SubmitOrSend(op, opts)
618 def RecreateDisks(opts, args):
619 """Recreate an instance's disks.
621 @param opts: the command line options selected by the user
623 @param args: should contain only one element, the instance name
625 @return: the desired exit code
628 instance_name = args[0]
631 opts.disks = [int(v) for v in opts.disks.split(",")]
632 except (ValueError, TypeError), err:
633 ToStderr("Invalid disks value: %s" % str(err))
638 op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
640 SubmitOrSend(op, opts)
644 def GrowDisk(opts, args):
645 """Grow an instance's disks.
647 @param opts: the command line options selected by the user
649 @param args: should contain two elements, the instance name
650 whose disks we grow and the disk name, e.g. I{sda}
652 @return: the desired exit code
659 except (TypeError, ValueError), err:
660 raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
662 amount = utils.ParseUnit(args[2])
663 op = opcodes.OpInstanceGrowDisk(instance_name=instance,
664 disk=disk, amount=amount,
665 wait_for_sync=opts.wait_for_sync)
666 SubmitOrSend(op, opts)
670 def _StartupInstance(name, opts):
671 """Startup instances.
673 This returns the opcode to start an instance, and its decorator will
674 wrap this into a loop starting all desired instances.
676 @param name: the name of the instance to act on
677 @param opts: the command line options selected by the user
678 @return: the opcode needed for the operation
681 op = opcodes.OpInstanceStartup(instance_name=name,
683 ignore_offline_nodes=opts.ignore_offline)
684 # do not add these parameters to the opcode unless they're defined
686 op.hvparams = opts.hvparams
688 op.beparams = opts.beparams
692 def _RebootInstance(name, opts):
693 """Reboot instance(s).
695 This returns the opcode to reboot an instance, and its decorator
696 will wrap this into a loop rebooting all desired instances.
698 @param name: the name of the instance to act on
699 @param opts: the command line options selected by the user
700 @return: the opcode needed for the operation
703 return opcodes.OpInstanceReboot(instance_name=name,
704 reboot_type=opts.reboot_type,
705 ignore_secondaries=opts.ignore_secondaries,
706 shutdown_timeout=opts.shutdown_timeout)
709 def _ShutdownInstance(name, opts):
710 """Shutdown an instance.
712 This returns the opcode to shutdown an instance, and its decorator
713 will wrap this into a loop shutting down all desired instances.
715 @param name: the name of the instance to act on
716 @param opts: the command line options selected by the user
717 @return: the opcode needed for the operation
720 return opcodes.OpInstanceShutdown(instance_name=name,
721 timeout=opts.timeout,
722 ignore_offline_nodes=opts.ignore_offline)
725 def ReplaceDisks(opts, args):
726 """Replace the disks of an instance
728 @param opts: the command line options selected by the user
730 @param args: should contain only one element, the instance name
732 @return: the desired exit code
735 new_2ndary = opts.dst_node
736 iallocator = opts.iallocator
737 if opts.disks is None:
741 disks = [int(i) for i in opts.disks.split(",")]
742 except (TypeError, ValueError), err:
743 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
745 cnt = [opts.on_primary, opts.on_secondary, opts.auto,
746 new_2ndary is not None, iallocator is not None].count(True)
748 raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -i"
749 " options must be passed", errors.ECODE_INVAL)
750 elif opts.on_primary:
751 mode = constants.REPLACE_DISK_PRI
752 elif opts.on_secondary:
753 mode = constants.REPLACE_DISK_SEC
755 mode = constants.REPLACE_DISK_AUTO
757 raise errors.OpPrereqError("Cannot specify disks when using automatic"
758 " mode", errors.ECODE_INVAL)
759 elif new_2ndary is not None or iallocator is not None:
761 mode = constants.REPLACE_DISK_CHG
763 op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
764 remote_node=new_2ndary, mode=mode,
765 iallocator=iallocator,
766 early_release=opts.early_release)
767 SubmitOrSend(op, opts)
771 def FailoverInstance(opts, args):
772 """Failover an instance.
774 The failover is done by shutting it down on its present node and
775 starting it on the secondary.
777 @param opts: the command line options selected by the user
779 @param args: should contain only one element, the instance name
781 @return: the desired exit code
785 instance_name = args[0]
789 _EnsureInstancesExist(cl, [instance_name])
791 usertext = ("Failover will happen to image %s."
792 " This requires a shutdown of the instance. Continue?" %
794 if not AskUser(usertext):
797 op = opcodes.OpInstanceFailover(instance_name=instance_name,
798 ignore_consistency=opts.ignore_consistency,
799 shutdown_timeout=opts.shutdown_timeout)
800 SubmitOrSend(op, opts, cl=cl)
804 def MigrateInstance(opts, args):
805 """Migrate an instance.
807 The migrate is done without shutdown.
809 @param opts: the command line options selected by the user
811 @param args: should contain only one element, the instance name
813 @return: the desired exit code
817 instance_name = args[0]
821 _EnsureInstancesExist(cl, [instance_name])
824 usertext = ("Instance %s will be recovered from a failed migration."
825 " Note that the migration procedure (including cleanup)" %
828 usertext = ("Instance %s will be migrated. Note that migration" %
830 usertext += (" might impact the instance if anything goes wrong"
831 " (e.g. due to bugs in the hypervisor). Continue?")
832 if not AskUser(usertext):
835 # this should be removed once --non-live is deprecated
836 if not opts.live and opts.migration_mode is not None:
837 raise errors.OpPrereqError("Only one of the --non-live and "
838 "--migration-mode options can be passed",
840 if not opts.live: # --non-live passed
841 mode = constants.HT_MIGRATION_NONLIVE
843 mode = opts.migration_mode
845 op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
846 cleanup=opts.cleanup)
847 SubmitOpCode(op, cl=cl, opts=opts)
851 def MoveInstance(opts, args):
854 @param opts: the command line options selected by the user
856 @param args: should contain only one element, the instance name
858 @return: the desired exit code
862 instance_name = args[0]
866 usertext = ("Instance %s will be moved."
867 " This requires a shutdown of the instance. Continue?" %
869 if not AskUser(usertext):
872 op = opcodes.OpInstanceMove(instance_name=instance_name,
873 target_node=opts.node,
874 shutdown_timeout=opts.shutdown_timeout)
875 SubmitOrSend(op, opts, cl=cl)
879 def ConnectToInstanceConsole(opts, args):
880 """Connect to the console of an instance.
882 @param opts: the command line options selected by the user
884 @param args: should contain only one element, the instance name
886 @return: the desired exit code
889 instance_name = args[0]
891 op = opcodes.OpInstanceConsole(instance_name=instance_name)
895 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
896 console_data = SubmitOpCode(op, opts=opts, cl=cl)
898 # Ensure client connection is closed while external commands are run
903 return _DoConsole(objects.InstanceConsole.FromDict(console_data),
904 opts.show_command, cluster_name)
907 def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
908 _runcmd_fn=utils.RunCmd):
909 """Acts based on the result of L{opcodes.OpInstanceConsole}.
911 @type console: L{objects.InstanceConsole}
912 @param console: Console object
913 @type show_command: bool
914 @param show_command: Whether to just display commands
915 @type cluster_name: string
916 @param cluster_name: Cluster name as retrieved from master daemon
919 assert console.Validate()
921 if console.kind == constants.CONS_MESSAGE:
922 feedback_fn(console.message)
923 elif console.kind == constants.CONS_VNC:
924 feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
925 " URL <vnc://%s:%s/>",
926 console.instance, console.host, console.port,
927 console.display, console.host, console.port)
928 elif console.kind == constants.CONS_SSH:
929 # Convert to string if not already one
930 if isinstance(console.command, basestring):
931 cmd = console.command
933 cmd = utils.ShellQuoteArgs(console.command)
935 srun = ssh.SshRunner(cluster_name=cluster_name)
936 ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
937 batch=True, quiet=False, tty=True)
940 feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
942 result = _runcmd_fn(ssh_cmd, interactive=True)
944 logging.error("Console command \"%s\" failed with reason '%s' and"
945 " output %r", result.cmd, result.fail_reason,
947 raise errors.OpExecError("Connection to console of instance %s failed,"
948 " please check cluster configuration" %
951 raise errors.GenericError("Unknown console type '%s'" % console.kind)
953 return constants.EXIT_SUCCESS
956 def _FormatLogicalID(dev_type, logical_id, roman):
957 """Formats the logical_id of a disk.
960 if dev_type == constants.LD_DRBD8:
961 node_a, node_b, port, minor_a, minor_b, key = logical_id
963 ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
965 ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
967 ("port", compat.TryToRoman(port, convert=roman)),
970 elif dev_type == constants.LD_LV:
971 vg_name, lv_name = logical_id
972 data = ["%s/%s" % (vg_name, lv_name)]
974 data = [str(logical_id)]
979 def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
980 """Show block device information.
982 This is only used by L{ShowInstanceConfig}, but it's too big to be
983 left for an inline definition.
986 @param idx: the index of the current disk
987 @type top_level: boolean
988 @param top_level: if this a top-level disk?
990 @param dev: dictionary with disk information
991 @type static: boolean
992 @param static: wheter the device information doesn't contain
993 runtime information but only static data
995 @param roman: whether to try to use roman integers
996 @return: a list of either strings, tuples or lists
997 (which should be formatted at a higher indent level)
1000 def helper(dtype, status):
1001 """Format one line for physical device status.
1004 @param dtype: a constant from the L{constants.LDS_BLOCK} set
1006 @param status: a tuple as returned from L{backend.FindBlockDevice}
1007 @return: the string representing the status
1013 (path, major, minor, syncp, estt, degr, ldisk_status) = status
1015 major_string = "N/A"
1017 major_string = str(compat.TryToRoman(major, convert=roman))
1020 minor_string = "N/A"
1022 minor_string = str(compat.TryToRoman(minor, convert=roman))
1024 txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1025 if dtype in (constants.LD_DRBD8, ):
1026 if syncp is not None:
1027 sync_text = "*RECOVERING* %5.2f%%," % syncp
1029 sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1031 sync_text += " ETA unknown"
1033 sync_text = "in sync"
1035 degr_text = "*DEGRADED*"
1038 if ldisk_status == constants.LDS_FAULTY:
1039 ldisk_text = " *MISSING DISK*"
1040 elif ldisk_status == constants.LDS_UNKNOWN:
1041 ldisk_text = " *UNCERTAIN STATE*"
1044 txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1045 elif dtype == constants.LD_LV:
1046 if ldisk_status == constants.LDS_FAULTY:
1047 ldisk_text = " *FAILED* (failed drive?)"
1055 if dev["iv_name"] is not None:
1056 txt = dev["iv_name"]
1058 txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1060 txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1061 if isinstance(dev["size"], int):
1062 nice_size = utils.FormatUnit(dev["size"], "h")
1064 nice_size = dev["size"]
1065 d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1068 data.append(("access mode", dev["mode"]))
1069 if dev["logical_id"] is not None:
1071 l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1073 l_id = [str(dev["logical_id"])]
1075 data.append(("logical_id", l_id[0]))
1078 elif dev["physical_id"] is not None:
1079 data.append("physical_id:")
1080 data.append([dev["physical_id"]])
1082 data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1083 if dev["sstatus"] and not static:
1084 data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1087 data.append("child devices:")
1088 for c_idx, child in enumerate(dev["children"]):
1089 data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
1094 def _FormatList(buf, data, indent_level):
1095 """Formats a list of data at a given indent level.
1097 If the element of the list is:
1098 - a string, it is simply formatted as is
1099 - a tuple, it will be split into key, value and the all the
1100 values in a list will be aligned all at the same start column
1101 - a list, will be recursively formatted
1104 @param buf: the buffer into which we write the output
1105 @param data: the list to format
1106 @type indent_level: int
1107 @param indent_level: the indent level to format at
1110 max_tlen = max([len(elem[0]) for elem in data
1111 if isinstance(elem, tuple)] or [0])
1113 if isinstance(elem, basestring):
1114 buf.write("%*s%s\n" % (2*indent_level, "", elem))
1115 elif isinstance(elem, tuple):
1117 spacer = "%*s" % (max_tlen - len(key), "")
1118 buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1119 elif isinstance(elem, list):
1120 _FormatList(buf, elem, indent_level+1)
1123 def ShowInstanceConfig(opts, args):
1124 """Compute instance run-time status.
1126 @param opts: the command line options selected by the user
1128 @param args: either an empty list, and then we query all
1129 instances, or should contain a list of instance names
1131 @return: the desired exit code
1134 if not args and not opts.show_all:
1135 ToStderr("No instance selected."
1136 " Please pass in --all if you want to query all instances.\n"
1137 "Note that this can take a long time on a big cluster.")
1139 elif args and opts.show_all:
1140 ToStderr("Cannot use --all if you specify instance names.")
1144 op = opcodes.OpInstanceQueryData(instances=args, static=opts.static)
1145 result = SubmitOpCode(op, opts=opts)
1147 ToStdout("No instances.")
1152 for instance_name in result:
1153 instance = result[instance_name]
1154 buf.write("Instance name: %s\n" % instance["name"])
1155 buf.write("UUID: %s\n" % instance["uuid"])
1156 buf.write("Serial number: %s\n" %
1157 compat.TryToRoman(instance["serial_no"],
1158 convert=opts.roman_integers))
1159 buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1160 buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1161 buf.write("State: configured to be %s" % instance["config_state"])
1163 buf.write(", actual state is %s" % instance["run_state"])
1165 ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1166 ## instance["auto_balance"])
1167 buf.write(" Nodes:\n")
1168 buf.write(" - primary: %s\n" % instance["pnode"])
1169 buf.write(" - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1170 buf.write(" Operating system: %s\n" % instance["os"])
1171 FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1173 if instance.has_key("network_port"):
1174 buf.write(" Allocated network port: %s\n" %
1175 compat.TryToRoman(instance["network_port"],
1176 convert=opts.roman_integers))
1177 buf.write(" Hypervisor: %s\n" % instance["hypervisor"])
1179 # custom VNC console information
1180 vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1182 if vnc_bind_address:
1183 port = instance["network_port"]
1184 display = int(port) - constants.VNC_BASE_PORT
1185 if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1186 vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1189 elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1190 vnc_console_port = ("%s:%s (node %s) (display %s)" %
1191 (vnc_bind_address, port,
1192 instance["pnode"], display))
1194 # vnc bind address is a file
1195 vnc_console_port = "%s:%s" % (instance["pnode"],
1197 buf.write(" - console connection: vnc to %s\n" % vnc_console_port)
1199 FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1201 buf.write(" Hardware:\n")
1202 buf.write(" - VCPUs: %s\n" %
1203 compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1204 convert=opts.roman_integers))
1205 buf.write(" - memory: %sMiB\n" %
1206 compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
1207 convert=opts.roman_integers))
1208 buf.write(" - NICs:\n")
1209 for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1210 buf.write(" - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1211 (idx, mac, ip, mode, link))
1212 buf.write(" Disk template: %s\n" % instance["disk_template"])
1213 buf.write(" Disks:\n")
1215 for idx, device in enumerate(instance["disks"]):
1216 _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static,
1217 opts.roman_integers), 2)
1219 ToStdout(buf.getvalue().rstrip('\n'))
1223 def SetInstanceParams(opts, args):
1224 """Modifies an instance.
1226 All parameters take effect only at the next restart of the instance.
1228 @param opts: the command line options selected by the user
1230 @param args: should contain only one element, the instance name
1232 @return: the desired exit code
1235 if not (opts.nics or opts.disks or opts.disk_template or
1236 opts.hvparams or opts.beparams or opts.os or opts.osparams):
1237 ToStderr("Please give at least one of the parameters.")
1240 for param in opts.beparams:
1241 if isinstance(opts.beparams[param], basestring):
1242 if opts.beparams[param].lower() == "default":
1243 opts.beparams[param] = constants.VALUE_DEFAULT
1245 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1246 allowed_values=[constants.VALUE_DEFAULT])
1248 for param in opts.hvparams:
1249 if isinstance(opts.hvparams[param], basestring):
1250 if opts.hvparams[param].lower() == "default":
1251 opts.hvparams[param] = constants.VALUE_DEFAULT
1253 utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1254 allowed_values=[constants.VALUE_DEFAULT])
1256 for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1258 nic_op = int(nic_op)
1259 opts.nics[idx] = (nic_op, nic_dict)
1260 except (TypeError, ValueError):
1263 for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1265 disk_op = int(disk_op)
1266 opts.disks[idx] = (disk_op, disk_dict)
1267 except (TypeError, ValueError):
1269 if disk_op == constants.DDM_ADD:
1270 if 'size' not in disk_dict:
1271 raise errors.OpPrereqError("Missing required parameter 'size'",
1273 disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1275 if (opts.disk_template and
1276 opts.disk_template in constants.DTS_NET_MIRROR and
1278 ToStderr("Changing the disk template to a mirrored one requires"
1279 " specifying a secondary node")
1282 op = opcodes.OpInstanceSetParams(instance_name=args[0],
1285 disk_template=opts.disk_template,
1286 remote_node=opts.node,
1287 hvparams=opts.hvparams,
1288 beparams=opts.beparams,
1290 osparams=opts.osparams,
1291 force_variant=opts.force_variant,
1294 # even if here we process the result, we allow submit only
1295 result = SubmitOrSend(op, opts)
1298 ToStdout("Modified instance %s", args[0])
1299 for param, data in result:
1300 ToStdout(" - %-5s -> %s", param, data)
1301 ToStdout("Please don't forget that most parameters take effect"
1302 " only at the next start of the instance.")
1306 # multi-instance selection options
1307 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1308 help="Do not ask for confirmation when more than"
1309 " one instance is affected",
1310 action="store_true", default=False)
1312 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1313 help="Filter by nodes (primary only)",
1314 const=_SHUTDOWN_NODES_PRI, action="store_const")
1316 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1317 help="Filter by nodes (secondary only)",
1318 const=_SHUTDOWN_NODES_SEC, action="store_const")
1320 m_node_opt = cli_option("--node", dest="multi_mode",
1321 help="Filter by nodes (primary and secondary)",
1322 const=_SHUTDOWN_NODES_BOTH, action="store_const")
1324 m_clust_opt = cli_option("--all", dest="multi_mode",
1325 help="Select all instances in the cluster",
1326 const=_SHUTDOWN_CLUSTER, action="store_const")
1328 m_inst_opt = cli_option("--instance", dest="multi_mode",
1329 help="Filter by instance name [default]",
1330 const=_SHUTDOWN_INSTANCES, action="store_const")
1332 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1333 help="Filter by node tag",
1334 const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1335 action="store_const")
1337 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1338 help="Filter by primary node tag",
1339 const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1340 action="store_const")
1342 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1343 help="Filter by secondary node tag",
1344 const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1345 action="store_const")
1347 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1348 help="Filter by instance tag",
1349 const=_SHUTDOWN_INSTANCES_BY_TAGS,
1350 action="store_const")
1352 # this is defined separately due to readability only
1362 AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1363 "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1364 "Creates and adds a new instance to the cluster"),
1366 BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1368 "Create a bunch of instances based on specs in the file."),
1370 ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1371 [SHOWCMD_OPT, PRIORITY_OPT],
1372 "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1374 FailoverInstance, ARGS_ONE_INSTANCE,
1375 [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1376 DRY_RUN_OPT, PRIORITY_OPT],
1377 "[-f] <instance>", "Stops the instance and starts it on the backup node,"
1378 " using the remote mirror (only for instances of type drbd)"),
1380 MigrateInstance, ARGS_ONE_INSTANCE,
1381 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1383 "[-f] <instance>", "Migrate instance to its secondary node"
1384 " (only for instances of type drbd)"),
1386 MoveInstance, ARGS_ONE_INSTANCE,
1387 [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1388 DRY_RUN_OPT, PRIORITY_OPT],
1389 "[-f] <instance>", "Move instance to an arbitrary node"
1390 " (only for instances of type file and lv)"),
1392 ShowInstanceConfig, ARGS_MANY_INSTANCES,
1393 [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1394 "[-s] {--all | <instance>...}",
1395 "Show information on the specified instance(s)"),
1397 ListInstances, ARGS_MANY_INSTANCES,
1398 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
1400 "Lists the instances and their status. The available fields can be shown"
1401 " using the \"list-fields\" command (see the man page for details)."
1402 " The default field list is (in order): %s." %
1403 utils.CommaJoin(_LIST_DEF_FIELDS),
1406 ListInstanceFields, [ArgUnknown()],
1407 [NOHDR_OPT, SEP_OPT],
1409 "Lists all available fields for instances"),
1411 ReinstallInstance, [ArgInstance()],
1412 [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1413 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1414 m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1415 SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1416 "[-f] <instance>", "Reinstall a stopped instance"),
1418 RemoveInstance, ARGS_ONE_INSTANCE,
1419 [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1420 DRY_RUN_OPT, PRIORITY_OPT],
1421 "[-f] <instance>", "Shuts down the instance and removes it"),
1424 [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1425 [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1426 "<instance> <new_name>", "Rename the instance"),
1428 ReplaceDisks, ARGS_ONE_INSTANCE,
1429 [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1430 NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1431 DRY_RUN_OPT, PRIORITY_OPT],
1432 "[-s|-p|-n NODE|-I NAME] <instance>",
1433 "Replaces all disks for the instance"),
1435 SetInstanceParams, ARGS_ONE_INSTANCE,
1436 [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1437 DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1438 OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1439 "<instance>", "Alters the parameters of an instance"),
1441 GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1442 [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1443 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1444 m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1445 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1446 "<instance>", "Stops an instance"),
1448 GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1449 [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1450 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1451 m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1452 BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1453 "<instance>", "Starts an instance"),
1455 GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1456 [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1457 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1458 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1459 m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1460 "<instance>", "Reboots an instance"),
1462 ActivateDisks, ARGS_ONE_INSTANCE,
1463 [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
1464 "<instance>", "Activate an instance's disks"),
1465 'deactivate-disks': (
1466 DeactivateDisks, ARGS_ONE_INSTANCE,
1467 [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1468 "[-f] <instance>", "Deactivate an instance's disks"),
1470 RecreateDisks, ARGS_ONE_INSTANCE,
1471 [SUBMIT_OPT, DISKIDX_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1472 "<instance>", "Recreate an instance's disks"),
1475 [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1476 ArgUnknown(min=1, max=1)],
1477 [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1478 "<instance> <disk> <size>", "Grow an instance's disk"),
1480 ListTags, ARGS_ONE_INSTANCE, [PRIORITY_OPT],
1481 "<instance_name>", "List the tags of the given instance"),
1483 AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1484 [TAG_SRC_OPT, PRIORITY_OPT],
1485 "<instance_name> tag...", "Add tags to the given instance"),
1487 RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1488 [TAG_SRC_OPT, PRIORITY_OPT],
1489 "<instance_name> tag...", "Remove tags from given instance"),
1492 #: dictionary with aliases for commands
1500 return GenericMain(commands, aliases=aliases,
1501 override={"tag_type": constants.TAG_INSTANCE})