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 _EnsureInstancesExist(client, names):
146 """Check for and ensure the given instance names exist.
148 This function will raise an OpPrereqError in case they don't
149 exist. Otherwise it will exit cleanly.
151 @type client: L{ganeti.luxi.Client}
152 @param client: the client to use for the query
154 @param names: the list of instance names to query
155 @raise errors.OpPrereqError: in case any instance is missing
158 # TODO: change LUInstanceQuery to that it actually returns None
159 # instead of raising an exception, or devise a better mechanism
160 result = client.QueryInstances(names, ["name"], False)
161 for orig_name, row in zip(names, result):
163 raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name,
167 def GenericManyOps(operation, fn):
168 """Generic multi-instance operations.
170 The will return a wrapper that processes the options and arguments
171 given, and uses the passed function to build the opcode needed for
172 the specific operation. Thus all the generic loop/confirmation code
173 is abstracted into this function.
176 def realfn(opts, args):
177 if opts.multi_mode is None:
178 opts.multi_mode = _SHUTDOWN_INSTANCES
180 inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
182 if opts.multi_mode == _SHUTDOWN_CLUSTER:
183 ToStdout("Cluster is empty, no instances to shutdown")
185 raise errors.OpPrereqError("Selection filter does not match"
186 " any instances", errors.ECODE_INVAL)
187 multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
188 if not (opts.force_multi or not multi_on
189 or ConfirmOperation(inames, "instances", operation)):
191 jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
194 jex.QueueJob(name, op)
195 results = jex.WaitOrShow(not opts.submit_only)
196 rcode = compat.all(row[0] for row in results)
197 return int(not rcode)
201 def ListInstances(opts, args):
202 """List instances and their properties.
204 @param opts: the command line options selected by the user
206 @param args: should be an empty list
208 @return: the desired exit code
211 selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
213 fmtoverride = dict.fromkeys(["tags", "disk.sizes", "nic.macs", "nic.ips",
214 "nic.modes", "nic.links", "nic.bridges",
216 (lambda value: ",".join(str(item)
220 return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units,
221 opts.separator, not opts.no_headers,
222 format_override=fmtoverride, verbose=opts.verbose)
225 def ListInstanceFields(opts, args):
226 """List instance fields.
228 @param opts: the command line options selected by the user
230 @param args: fields to list, or empty for all
232 @return: the desired exit code
235 return GenericListFields(constants.QR_INSTANCE, args, opts.separator,
239 def AddInstance(opts, args):
240 """Add an instance to the cluster.
242 This is just a wrapper over GenericInstanceCreate.
245 return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
248 def BatchCreate(opts, args):
249 """Create instances using a definition file.
251 This function reads a json file with instances defined
255 "disk_size": [20480],
261 "primary_node": "firstnode",
262 "secondary_node": "secondnode",
263 "iallocator": "dumb"}
266 Note that I{primary_node} and I{secondary_node} have precedence over
269 @param opts: the command line options selected by the user
271 @param args: should contain one element, the json filename
273 @return: the desired exit code
276 _DEFAULT_SPECS = {"disk_size": [20 * 1024],
279 "primary_node": None,
280 "secondary_node": None,
287 "file_storage_dir": None,
288 "force_variant": False,
289 "file_driver": 'loop'}
291 def _PopulateWithDefaults(spec):
292 """Returns a new hash combined with default values."""
293 mydict = _DEFAULT_SPECS.copy()
298 """Validate the instance specs."""
299 # Validate fields required under any circumstances
300 for required_field in ('os', 'template'):
301 if required_field not in spec:
302 raise errors.OpPrereqError('Required field "%s" is missing.' %
303 required_field, errors.ECODE_INVAL)
304 # Validate special fields
305 if spec['primary_node'] is not None:
306 if (spec['template'] in constants.DTS_INT_MIRROR and
307 spec['secondary_node'] is None):
308 raise errors.OpPrereqError('Template requires secondary node, but'
309 ' there was no secondary provided.',
311 elif spec['iallocator'] is None:
312 raise errors.OpPrereqError('You have to provide at least a primary_node'
313 ' or an iallocator.',
316 if (spec['hvparams'] and
317 not isinstance(spec['hvparams'], dict)):
318 raise errors.OpPrereqError('Hypervisor parameters must be a dict.',
321 json_filename = args[0]
323 instance_data = simplejson.loads(utils.ReadFile(json_filename))
324 except Exception, err: # pylint: disable-msg=W0703
325 ToStderr("Can't parse the instance definition file: %s" % str(err))
328 if not isinstance(instance_data, dict):
329 ToStderr("The instance definition file is not in dict format.")
332 jex = JobExecutor(opts=opts)
334 # Iterate over the instances and do:
335 # * Populate the specs with default value
336 # * Validate the instance specs
337 i_names = utils.NiceSort(instance_data.keys()) # pylint: disable-msg=E1103
339 specs = instance_data[name]
340 specs = _PopulateWithDefaults(specs)
343 hypervisor = specs['hypervisor']
344 hvparams = specs['hvparams']
347 for elem in specs['disk_size']:
349 size = utils.ParseUnit(elem)
350 except (TypeError, ValueError), err:
351 raise errors.OpPrereqError("Invalid disk size '%s' for"
353 (elem, name, err), errors.ECODE_INVAL)
354 disks.append({"size": size})
356 utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
357 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
360 for field in constants.INIC_PARAMS:
364 tmp_nics[0][field] = specs[field]
366 if specs['nics'] is not None and tmp_nics:
367 raise errors.OpPrereqError("'nics' list incompatible with using"
368 " individual nic fields as well",
370 elif specs['nics'] is not None:
371 tmp_nics = specs['nics']
375 op = opcodes.OpInstanceCreate(instance_name=name,
377 disk_template=specs['template'],
378 mode=constants.INSTANCE_CREATE,
380 force_variant=specs["force_variant"],
381 pnode=specs['primary_node'],
382 snode=specs['secondary_node'],
384 start=specs['start'],
385 ip_check=specs['ip_check'],
386 name_check=specs['name_check'],
388 iallocator=specs['iallocator'],
389 hypervisor=hypervisor,
391 beparams=specs['backend'],
392 file_storage_dir=specs['file_storage_dir'],
393 file_driver=specs['file_driver'])
395 jex.QueueJob(name, op)
396 # we never want to wait, just show the submitted job IDs
397 jex.WaitOrShow(False)
402 def ReinstallInstance(opts, args):
403 """Reinstall an instance.
405 @param opts: the command line options selected by the user
407 @param args: should contain only one element, the name of the
408 instance to be reinstalled
410 @return: the desired exit code
413 # first, compute the desired name list
414 if opts.multi_mode is None:
415 opts.multi_mode = _SHUTDOWN_INSTANCES
417 inames = _ExpandMultiNames(opts.multi_mode, args)
419 raise errors.OpPrereqError("Selection filter does not match any instances",
422 # second, if requested, ask for an OS
423 if opts.select_os is True:
424 op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[])
425 result = SubmitOpCode(op, opts=opts)
428 ToStdout("Can't get the OS list")
431 ToStdout("Available OS templates:")
434 for (name, variants) in result:
435 for entry in CalculateOSNames(name, variants):
436 ToStdout("%3s: %s", number, entry)
437 choices.append(("%s" % number, entry, entry))
440 choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
441 selected = AskUser("Enter OS template number (or x to abort):",
444 if selected == 'exit':
445 ToStderr("User aborted reinstall, exiting")
449 os_msg = "change the OS to '%s'" % selected
452 if opts.os is not None:
453 os_msg = "change the OS to '%s'" % os_name
455 os_msg = "keep the same OS"
457 # third, get confirmation: multi-reinstall requires --force-multi,
458 # single-reinstall either --force or --force-multi (--force-multi is
459 # a stronger --force)
460 multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
462 warn_msg = ("Note: this will remove *all* data for the"
463 " below instances! It will %s.\n" % os_msg)
464 if not (opts.force_multi or
465 ConfirmOperation(inames, "instances", "reinstall", extra=warn_msg)):
468 if not (opts.force or opts.force_multi):
469 usertext = ("This will reinstall the instance '%s' (and %s) which"
470 " removes all data. Continue?") % (inames[0], os_msg)
471 if not AskUser(usertext):
474 jex = JobExecutor(verbose=multi_on, opts=opts)
475 for instance_name in inames:
476 op = opcodes.OpInstanceReinstall(instance_name=instance_name,
478 force_variant=opts.force_variant,
479 osparams=opts.osparams)
480 jex.QueueJob(instance_name, op)
482 jex.WaitOrShow(not opts.submit_only)
486 def RemoveInstance(opts, args):
487 """Remove an instance.
489 @param opts: the command line options selected by the user
491 @param args: should contain only one element, the name of
492 the instance to be removed
494 @return: the desired exit code
497 instance_name = args[0]
502 _EnsureInstancesExist(cl, [instance_name])
504 usertext = ("This will remove the volumes of the instance %s"
505 " (including mirrors), thus removing all the data"
506 " of the instance. Continue?") % instance_name
507 if not AskUser(usertext):
510 op = opcodes.OpInstanceRemove(instance_name=instance_name,
511 ignore_failures=opts.ignore_failures,
512 shutdown_timeout=opts.shutdown_timeout)
513 SubmitOrSend(op, opts, cl=cl)
517 def RenameInstance(opts, args):
518 """Rename an instance.
520 @param opts: the command line options selected by the user
522 @param args: should contain two elements, the old and the
525 @return: the desired exit code
528 if not opts.name_check:
529 if not AskUser("As you disabled the check of the DNS entry, please verify"
530 " that '%s' is a FQDN. Continue?" % args[1]):
533 op = opcodes.OpInstanceRename(instance_name=args[0],
535 ip_check=opts.ip_check,
536 name_check=opts.name_check)
537 result = SubmitOrSend(op, opts)
540 ToStdout("Instance '%s' renamed to '%s'", args[0], result)
545 def ActivateDisks(opts, args):
546 """Activate an instance's disks.
548 This serves two purposes:
549 - it allows (as long as the instance is not running)
550 mounting the disks and modifying them from the node
551 - it repairs inactive secondary drbds
553 @param opts: the command line options selected by the user
555 @param args: should contain only one element, the instance name
557 @return: the desired exit code
560 instance_name = args[0]
561 op = opcodes.OpInstanceActivateDisks(instance_name=instance_name,
562 ignore_size=opts.ignore_size)
563 disks_info = SubmitOrSend(op, opts)
564 for host, iname, nname in disks_info:
565 ToStdout("%s:%s:%s", host, iname, nname)
569 def DeactivateDisks(opts, args):
570 """Deactivate an instance's disks.
572 This function takes the instance name, looks for its primary node
573 and the tries to shutdown its block devices on that node.
575 @param opts: the command line options selected by the user
577 @param args: should contain only one element, the instance name
579 @return: the desired exit code
582 instance_name = args[0]
583 op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name,
585 SubmitOrSend(op, opts)
589 def RecreateDisks(opts, args):
590 """Recreate an instance's disks.
592 @param opts: the command line options selected by the user
594 @param args: should contain only one element, the instance name
596 @return: the desired exit code
599 instance_name = args[0]
602 opts.disks = [int(v) for v in opts.disks.split(",")]
603 except (ValueError, TypeError), err:
604 ToStderr("Invalid disks value: %s" % str(err))
609 op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
611 SubmitOrSend(op, opts)
615 def GrowDisk(opts, args):
616 """Grow an instance's disks.
618 @param opts: the command line options selected by the user
620 @param args: should contain two elements, the instance name
621 whose disks we grow and the disk name, e.g. I{sda}
623 @return: the desired exit code
630 except (TypeError, ValueError), err:
631 raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
633 amount = utils.ParseUnit(args[2])
634 op = opcodes.OpInstanceGrowDisk(instance_name=instance,
635 disk=disk, amount=amount,
636 wait_for_sync=opts.wait_for_sync)
637 SubmitOrSend(op, opts)
641 def _StartupInstance(name, opts):
642 """Startup instances.
644 This returns the opcode to start an instance, and its decorator will
645 wrap this into a loop starting all desired instances.
647 @param name: the name of the instance to act on
648 @param opts: the command line options selected by the user
649 @return: the opcode needed for the operation
652 op = opcodes.OpInstanceStartup(instance_name=name,
654 ignore_offline_nodes=opts.ignore_offline)
655 # do not add these parameters to the opcode unless they're defined
657 op.hvparams = opts.hvparams
659 op.beparams = opts.beparams
663 def _RebootInstance(name, opts):
664 """Reboot instance(s).
666 This returns the opcode to reboot an instance, and its decorator
667 will wrap this into a loop rebooting all desired instances.
669 @param name: the name of the instance to act on
670 @param opts: the command line options selected by the user
671 @return: the opcode needed for the operation
674 return opcodes.OpInstanceReboot(instance_name=name,
675 reboot_type=opts.reboot_type,
676 ignore_secondaries=opts.ignore_secondaries,
677 shutdown_timeout=opts.shutdown_timeout)
680 def _ShutdownInstance(name, opts):
681 """Shutdown an instance.
683 This returns the opcode to shutdown an instance, and its decorator
684 will wrap this into a loop shutting down all desired instances.
686 @param name: the name of the instance to act on
687 @param opts: the command line options selected by the user
688 @return: the opcode needed for the operation
691 return opcodes.OpInstanceShutdown(instance_name=name,
692 timeout=opts.timeout,
693 ignore_offline_nodes=opts.ignore_offline)
696 def ReplaceDisks(opts, args):
697 """Replace the disks of an instance
699 @param opts: the command line options selected by the user
701 @param args: should contain only one element, the instance name
703 @return: the desired exit code
706 new_2ndary = opts.dst_node
707 iallocator = opts.iallocator
708 if opts.disks is None:
712 disks = [int(i) for i in opts.disks.split(",")]
713 except (TypeError, ValueError), err:
714 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
716 cnt = [opts.on_primary, opts.on_secondary, opts.auto,
717 new_2ndary is not None, iallocator is not None].count(True)
719 raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -i"
720 " options must be passed", errors.ECODE_INVAL)
721 elif opts.on_primary:
722 mode = constants.REPLACE_DISK_PRI
723 elif opts.on_secondary:
724 mode = constants.REPLACE_DISK_SEC
726 mode = constants.REPLACE_DISK_AUTO
728 raise errors.OpPrereqError("Cannot specify disks when using automatic"
729 " mode", errors.ECODE_INVAL)
730 elif new_2ndary is not None or iallocator is not None:
732 mode = constants.REPLACE_DISK_CHG
734 op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
735 remote_node=new_2ndary, mode=mode,
736 iallocator=iallocator,
737 early_release=opts.early_release)
738 SubmitOrSend(op, opts)
742 def FailoverInstance(opts, args):
743 """Failover an instance.
745 The failover is done by shutting it down on its present node and
746 starting it on the secondary.
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])
768 usertext = ("Failover will happen to image %s."
769 " This requires a shutdown of the instance. Continue?" %
771 if not AskUser(usertext):
774 op = opcodes.OpInstanceFailover(instance_name=instance_name,
775 ignore_consistency=opts.ignore_consistency,
776 shutdown_timeout=opts.shutdown_timeout,
777 iallocator=iallocator,
778 target_node=target_node)
779 SubmitOrSend(op, opts, cl=cl)
783 def MigrateInstance(opts, args):
784 """Migrate an instance.
786 The migrate is done without shutdown.
788 @param opts: the command line options selected by the user
790 @param args: should contain only one element, the instance name
792 @return: the desired exit code
796 instance_name = args[0]
798 iallocator = opts.iallocator
799 target_node = opts.dst_node
801 if iallocator and target_node:
802 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
803 " node (-n) but not both", errors.ECODE_INVAL)
806 _EnsureInstancesExist(cl, [instance_name])
809 usertext = ("Instance %s will be recovered from a failed migration."
810 " Note that the migration procedure (including cleanup)" %
813 usertext = ("Instance %s will be migrated. Note that migration" %
815 usertext += (" might impact the instance if anything goes wrong"
816 " (e.g. due to bugs in the hypervisor). Continue?")
817 if not AskUser(usertext):
820 # this should be removed once --non-live is deprecated
821 if not opts.live and opts.migration_mode is not None:
822 raise errors.OpPrereqError("Only one of the --non-live and "
823 "--migration-mode options can be passed",
825 if not opts.live: # --non-live passed
826 mode = constants.HT_MIGRATION_NONLIVE
828 mode = opts.migration_mode
830 op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
831 cleanup=opts.cleanup, iallocator=iallocator,
832 target_node=target_node,
833 allow_failover=opts.allow_failover)
834 SubmitOpCode(op, cl=cl, opts=opts)
838 def MoveInstance(opts, args):
841 @param opts: the command line options selected by the user
843 @param args: should contain only one element, the instance name
845 @return: the desired exit code
849 instance_name = args[0]
853 usertext = ("Instance %s will be moved."
854 " This requires a shutdown of the instance. Continue?" %
856 if not AskUser(usertext):
859 op = opcodes.OpInstanceMove(instance_name=instance_name,
860 target_node=opts.node,
861 shutdown_timeout=opts.shutdown_timeout)
862 SubmitOrSend(op, opts, cl=cl)
866 def ConnectToInstanceConsole(opts, args):
867 """Connect to the console of an instance.
869 @param opts: the command line options selected by the user
871 @param args: should contain only one element, the instance name
873 @return: the desired exit code
876 instance_name = args[0]
878 op = opcodes.OpInstanceConsole(instance_name=instance_name)
882 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
883 console_data = SubmitOpCode(op, opts=opts, cl=cl)
885 # Ensure client connection is closed while external commands are run
890 return _DoConsole(objects.InstanceConsole.FromDict(console_data),
891 opts.show_command, cluster_name)
894 def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
895 _runcmd_fn=utils.RunCmd):
896 """Acts based on the result of L{opcodes.OpInstanceConsole}.
898 @type console: L{objects.InstanceConsole}
899 @param console: Console object
900 @type show_command: bool
901 @param show_command: Whether to just display commands
902 @type cluster_name: string
903 @param cluster_name: Cluster name as retrieved from master daemon
906 assert console.Validate()
908 if console.kind == constants.CONS_MESSAGE:
909 feedback_fn(console.message)
910 elif console.kind == constants.CONS_VNC:
911 feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
912 " URL <vnc://%s:%s/>",
913 console.instance, console.host, console.port,
914 console.display, console.host, console.port)
915 elif console.kind == constants.CONS_SSH:
916 # Convert to string if not already one
917 if isinstance(console.command, basestring):
918 cmd = console.command
920 cmd = utils.ShellQuoteArgs(console.command)
922 srun = ssh.SshRunner(cluster_name=cluster_name)
923 ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
924 batch=True, quiet=False, tty=True)
927 feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
929 result = _runcmd_fn(ssh_cmd, interactive=True)
931 logging.error("Console command \"%s\" failed with reason '%s' and"
932 " output %r", result.cmd, result.fail_reason,
934 raise errors.OpExecError("Connection to console of instance %s failed,"
935 " please check cluster configuration" %
938 raise errors.GenericError("Unknown console type '%s'" % console.kind)
940 return constants.EXIT_SUCCESS
943 def _FormatLogicalID(dev_type, logical_id, roman):
944 """Formats the logical_id of a disk.
947 if dev_type == constants.LD_DRBD8:
948 node_a, node_b, port, minor_a, minor_b, key = logical_id
950 ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
952 ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
954 ("port", compat.TryToRoman(port, convert=roman)),
957 elif dev_type == constants.LD_LV:
958 vg_name, lv_name = logical_id
959 data = ["%s/%s" % (vg_name, lv_name)]
961 data = [str(logical_id)]
966 def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
967 """Show block device information.
969 This is only used by L{ShowInstanceConfig}, but it's too big to be
970 left for an inline definition.
973 @param idx: the index of the current disk
974 @type top_level: boolean
975 @param top_level: if this a top-level disk?
977 @param dev: dictionary with disk information
978 @type static: boolean
979 @param static: wheter the device information doesn't contain
980 runtime information but only static data
982 @param roman: whether to try to use roman integers
983 @return: a list of either strings, tuples or lists
984 (which should be formatted at a higher indent level)
987 def helper(dtype, status):
988 """Format one line for physical device status.
991 @param dtype: a constant from the L{constants.LDS_BLOCK} set
993 @param status: a tuple as returned from L{backend.FindBlockDevice}
994 @return: the string representing the status
1000 (path, major, minor, syncp, estt, degr, ldisk_status) = status
1002 major_string = "N/A"
1004 major_string = str(compat.TryToRoman(major, convert=roman))
1007 minor_string = "N/A"
1009 minor_string = str(compat.TryToRoman(minor, convert=roman))
1011 txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1012 if dtype in (constants.LD_DRBD8, ):
1013 if syncp is not None:
1014 sync_text = "*RECOVERING* %5.2f%%," % syncp
1016 sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1018 sync_text += " ETA unknown"
1020 sync_text = "in sync"
1022 degr_text = "*DEGRADED*"
1025 if ldisk_status == constants.LDS_FAULTY:
1026 ldisk_text = " *MISSING DISK*"
1027 elif ldisk_status == constants.LDS_UNKNOWN:
1028 ldisk_text = " *UNCERTAIN STATE*"
1031 txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1032 elif dtype == constants.LD_LV:
1033 if ldisk_status == constants.LDS_FAULTY:
1034 ldisk_text = " *FAILED* (failed drive?)"
1042 if dev["iv_name"] is not None:
1043 txt = dev["iv_name"]
1045 txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1047 txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1048 if isinstance(dev["size"], int):
1049 nice_size = utils.FormatUnit(dev["size"], "h")
1051 nice_size = dev["size"]
1052 d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1055 data.append(("access mode", dev["mode"]))
1056 if dev["logical_id"] is not None:
1058 l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1060 l_id = [str(dev["logical_id"])]
1062 data.append(("logical_id", l_id[0]))
1065 elif dev["physical_id"] is not None:
1066 data.append("physical_id:")
1067 data.append([dev["physical_id"]])
1069 data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1070 if dev["sstatus"] and not static:
1071 data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1074 data.append("child devices:")
1075 for c_idx, child in enumerate(dev["children"]):
1076 data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
1081 def _FormatList(buf, data, indent_level):
1082 """Formats a list of data at a given indent level.
1084 If the element of the list is:
1085 - a string, it is simply formatted as is
1086 - a tuple, it will be split into key, value and the all the
1087 values in a list will be aligned all at the same start column
1088 - a list, will be recursively formatted
1091 @param buf: the buffer into which we write the output
1092 @param data: the list to format
1093 @type indent_level: int
1094 @param indent_level: the indent level to format at
1097 max_tlen = max([len(elem[0]) for elem in data
1098 if isinstance(elem, tuple)] or [0])
1100 if isinstance(elem, basestring):
1101 buf.write("%*s%s\n" % (2*indent_level, "", elem))
1102 elif isinstance(elem, tuple):
1104 spacer = "%*s" % (max_tlen - len(key), "")
1105 buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1106 elif isinstance(elem, list):
1107 _FormatList(buf, elem, indent_level+1)
1110 def ShowInstanceConfig(opts, args):
1111 """Compute instance run-time status.
1113 @param opts: the command line options selected by the user
1115 @param args: either an empty list, and then we query all
1116 instances, or should contain a list of instance names
1118 @return: the desired exit code
1121 if not args and not opts.show_all:
1122 ToStderr("No instance selected."
1123 " Please pass in --all if you want to query all instances.\n"
1124 "Note that this can take a long time on a big cluster.")
1126 elif args and opts.show_all:
1127 ToStderr("Cannot use --all if you specify instance names.")
1131 op = opcodes.OpInstanceQueryData(instances=args, static=opts.static)
1132 result = SubmitOpCode(op, opts=opts)
1134 ToStdout("No instances.")
1139 for instance_name in result:
1140 instance = result[instance_name]
1141 buf.write("Instance name: %s\n" % instance["name"])
1142 buf.write("UUID: %s\n" % instance["uuid"])
1143 buf.write("Serial number: %s\n" %
1144 compat.TryToRoman(instance["serial_no"],
1145 convert=opts.roman_integers))
1146 buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1147 buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1148 buf.write("State: configured to be %s" % instance["config_state"])
1150 buf.write(", actual state is %s" % instance["run_state"])
1152 ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1153 ## instance["auto_balance"])
1154 buf.write(" Nodes:\n")
1155 buf.write(" - primary: %s\n" % instance["pnode"])
1156 buf.write(" - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1157 buf.write(" Operating system: %s\n" % instance["os"])
1158 FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1160 if instance.has_key("network_port"):
1161 buf.write(" Allocated network port: %s\n" %
1162 compat.TryToRoman(instance["network_port"],
1163 convert=opts.roman_integers))
1164 buf.write(" Hypervisor: %s\n" % instance["hypervisor"])
1166 # custom VNC console information
1167 vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1169 if vnc_bind_address:
1170 port = instance["network_port"]
1171 display = int(port) - constants.VNC_BASE_PORT
1172 if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1173 vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1176 elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1177 vnc_console_port = ("%s:%s (node %s) (display %s)" %
1178 (vnc_bind_address, port,
1179 instance["pnode"], display))
1181 # vnc bind address is a file
1182 vnc_console_port = "%s:%s" % (instance["pnode"],
1184 buf.write(" - console connection: vnc to %s\n" % vnc_console_port)
1186 FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1188 buf.write(" Hardware:\n")
1189 buf.write(" - VCPUs: %s\n" %
1190 compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1191 convert=opts.roman_integers))
1192 buf.write(" - memory: %sMiB\n" %
1193 compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
1194 convert=opts.roman_integers))
1195 buf.write(" - NICs:\n")
1196 for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1197 buf.write(" - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1198 (idx, mac, ip, mode, link))
1199 buf.write(" Disk template: %s\n" % instance["disk_template"])
1200 buf.write(" Disks:\n")
1202 for idx, device in enumerate(instance["disks"]):
1203 _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static,
1204 opts.roman_integers), 2)
1206 ToStdout(buf.getvalue().rstrip('\n'))
1210 def SetInstanceParams(opts, args):
1211 """Modifies an instance.
1213 All parameters take effect only at the next restart of the instance.
1215 @param opts: the command line options selected by the user
1217 @param args: should contain only one element, the instance name
1219 @return: the desired exit code
1222 if not (opts.nics or opts.disks or opts.disk_template or
1223 opts.hvparams or opts.beparams or opts.os or opts.osparams):
1224 ToStderr("Please give at least one of the parameters.")
1227 for param in opts.beparams:
1228 if isinstance(opts.beparams[param], basestring):
1229 if opts.beparams[param].lower() == "default":
1230 opts.beparams[param] = constants.VALUE_DEFAULT
1232 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1233 allowed_values=[constants.VALUE_DEFAULT])
1235 for param in opts.hvparams:
1236 if isinstance(opts.hvparams[param], basestring):
1237 if opts.hvparams[param].lower() == "default":
1238 opts.hvparams[param] = constants.VALUE_DEFAULT
1240 utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1241 allowed_values=[constants.VALUE_DEFAULT])
1243 for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1245 nic_op = int(nic_op)
1246 opts.nics[idx] = (nic_op, nic_dict)
1247 except (TypeError, ValueError):
1250 for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1252 disk_op = int(disk_op)
1253 opts.disks[idx] = (disk_op, disk_dict)
1254 except (TypeError, ValueError):
1256 if disk_op == constants.DDM_ADD:
1257 if 'size' not in disk_dict:
1258 raise errors.OpPrereqError("Missing required parameter 'size'",
1260 disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1262 if (opts.disk_template and
1263 opts.disk_template in constants.DTS_INT_MIRROR and
1265 ToStderr("Changing the disk template to a mirrored one requires"
1266 " specifying a secondary node")
1269 op = opcodes.OpInstanceSetParams(instance_name=args[0],
1272 disk_template=opts.disk_template,
1273 remote_node=opts.node,
1274 hvparams=opts.hvparams,
1275 beparams=opts.beparams,
1277 osparams=opts.osparams,
1278 force_variant=opts.force_variant,
1281 # even if here we process the result, we allow submit only
1282 result = SubmitOrSend(op, opts)
1285 ToStdout("Modified instance %s", args[0])
1286 for param, data in result:
1287 ToStdout(" - %-5s -> %s", param, data)
1288 ToStdout("Please don't forget that most parameters take effect"
1289 " only at the next start of the instance.")
1293 # multi-instance selection options
1294 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1295 help="Do not ask for confirmation when more than"
1296 " one instance is affected",
1297 action="store_true", default=False)
1299 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1300 help="Filter by nodes (primary only)",
1301 const=_SHUTDOWN_NODES_PRI, action="store_const")
1303 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1304 help="Filter by nodes (secondary only)",
1305 const=_SHUTDOWN_NODES_SEC, action="store_const")
1307 m_node_opt = cli_option("--node", dest="multi_mode",
1308 help="Filter by nodes (primary and secondary)",
1309 const=_SHUTDOWN_NODES_BOTH, action="store_const")
1311 m_clust_opt = cli_option("--all", dest="multi_mode",
1312 help="Select all instances in the cluster",
1313 const=_SHUTDOWN_CLUSTER, action="store_const")
1315 m_inst_opt = cli_option("--instance", dest="multi_mode",
1316 help="Filter by instance name [default]",
1317 const=_SHUTDOWN_INSTANCES, action="store_const")
1319 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1320 help="Filter by node tag",
1321 const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1322 action="store_const")
1324 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1325 help="Filter by primary node tag",
1326 const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1327 action="store_const")
1329 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1330 help="Filter by secondary node tag",
1331 const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1332 action="store_const")
1334 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1335 help="Filter by instance tag",
1336 const=_SHUTDOWN_INSTANCES_BY_TAGS,
1337 action="store_const")
1339 # this is defined separately due to readability only
1349 AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1350 "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1351 "Creates and adds a new instance to the cluster"),
1353 BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1355 "Create a bunch of instances based on specs in the file."),
1357 ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1358 [SHOWCMD_OPT, PRIORITY_OPT],
1359 "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1361 FailoverInstance, ARGS_ONE_INSTANCE,
1362 [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1363 DRY_RUN_OPT, PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT],
1364 "[-f] <instance>", "Stops the instance and starts it on the backup node,"
1365 " using the remote mirror (only for mirrored instances)"),
1367 MigrateInstance, ARGS_ONE_INSTANCE,
1368 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1369 PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT, ALLOW_FAILOVER_OPT],
1370 "[-f] <instance>", "Migrate instance to its secondary node"
1371 " (only for mirrored instances)"),
1373 MoveInstance, ARGS_ONE_INSTANCE,
1374 [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1375 DRY_RUN_OPT, PRIORITY_OPT],
1376 "[-f] <instance>", "Move instance to an arbitrary node"
1377 " (only for instances of type file and lv)"),
1379 ShowInstanceConfig, ARGS_MANY_INSTANCES,
1380 [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1381 "[-s] {--all | <instance>...}",
1382 "Show information on the specified instance(s)"),
1384 ListInstances, ARGS_MANY_INSTANCES,
1385 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT],
1387 "Lists the instances and their status. The available fields can be shown"
1388 " using the \"list-fields\" command (see the man page for details)."
1389 " The default field list is (in order): %s." %
1390 utils.CommaJoin(_LIST_DEF_FIELDS),
1393 ListInstanceFields, [ArgUnknown()],
1394 [NOHDR_OPT, SEP_OPT],
1396 "Lists all available fields for instances"),
1398 ReinstallInstance, [ArgInstance()],
1399 [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1400 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1401 m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1402 SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1403 "[-f] <instance>", "Reinstall a stopped instance"),
1405 RemoveInstance, ARGS_ONE_INSTANCE,
1406 [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1407 DRY_RUN_OPT, PRIORITY_OPT],
1408 "[-f] <instance>", "Shuts down the instance and removes it"),
1411 [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1412 [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1413 "<instance> <new_name>", "Rename the instance"),
1415 ReplaceDisks, ARGS_ONE_INSTANCE,
1416 [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1417 NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1418 DRY_RUN_OPT, PRIORITY_OPT],
1419 "[-s|-p|-n NODE|-I NAME] <instance>",
1420 "Replaces all disks for the instance"),
1422 SetInstanceParams, ARGS_ONE_INSTANCE,
1423 [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1424 DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1425 OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1426 "<instance>", "Alters the parameters of an instance"),
1428 GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1429 [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1430 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1431 m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1432 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1433 "<instance>", "Stops an instance"),
1435 GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1436 [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1437 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1438 m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1439 BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1440 "<instance>", "Starts an instance"),
1442 GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1443 [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1444 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1445 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1446 m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1447 "<instance>", "Reboots an instance"),
1449 ActivateDisks, ARGS_ONE_INSTANCE,
1450 [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
1451 "<instance>", "Activate an instance's disks"),
1452 'deactivate-disks': (
1453 DeactivateDisks, ARGS_ONE_INSTANCE,
1454 [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1455 "[-f] <instance>", "Deactivate an instance's disks"),
1457 RecreateDisks, ARGS_ONE_INSTANCE,
1458 [SUBMIT_OPT, DISKIDX_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1459 "<instance>", "Recreate an instance's disks"),
1462 [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1463 ArgUnknown(min=1, max=1)],
1464 [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1465 "<instance> <disk> <size>", "Grow an instance's disk"),
1467 ListTags, ARGS_ONE_INSTANCE, [PRIORITY_OPT],
1468 "<instance_name>", "List the tags of the given instance"),
1470 AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1471 [TAG_SRC_OPT, PRIORITY_OPT],
1472 "<instance_name> tag...", "Add tags to the given instance"),
1474 RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1475 [TAG_SRC_OPT, PRIORITY_OPT],
1476 "<instance_name> tag...", "Remove tags from given instance"),
1479 #: dictionary with aliases for commands
1487 return GenericMain(commands, aliases=aliases,
1488 override={"tag_type": constants.TAG_INSTANCE})