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_NET_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 SubmitOpCode(op, cl=cl, opts=opts)
837 def MoveInstance(opts, args):
840 @param opts: the command line options selected by the user
842 @param args: should contain only one element, the instance name
844 @return: the desired exit code
848 instance_name = args[0]
852 usertext = ("Instance %s will be moved."
853 " This requires a shutdown of the instance. Continue?" %
855 if not AskUser(usertext):
858 op = opcodes.OpInstanceMove(instance_name=instance_name,
859 target_node=opts.node,
860 shutdown_timeout=opts.shutdown_timeout)
861 SubmitOrSend(op, opts, cl=cl)
865 def ConnectToInstanceConsole(opts, args):
866 """Connect to the console of an instance.
868 @param opts: the command line options selected by the user
870 @param args: should contain only one element, the instance name
872 @return: the desired exit code
875 instance_name = args[0]
877 op = opcodes.OpInstanceConsole(instance_name=instance_name)
881 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
882 console_data = SubmitOpCode(op, opts=opts, cl=cl)
884 # Ensure client connection is closed while external commands are run
889 return _DoConsole(objects.InstanceConsole.FromDict(console_data),
890 opts.show_command, cluster_name)
893 def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
894 _runcmd_fn=utils.RunCmd):
895 """Acts based on the result of L{opcodes.OpInstanceConsole}.
897 @type console: L{objects.InstanceConsole}
898 @param console: Console object
899 @type show_command: bool
900 @param show_command: Whether to just display commands
901 @type cluster_name: string
902 @param cluster_name: Cluster name as retrieved from master daemon
905 assert console.Validate()
907 if console.kind == constants.CONS_MESSAGE:
908 feedback_fn(console.message)
909 elif console.kind == constants.CONS_VNC:
910 feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
911 " URL <vnc://%s:%s/>",
912 console.instance, console.host, console.port,
913 console.display, console.host, console.port)
914 elif console.kind == constants.CONS_SSH:
915 # Convert to string if not already one
916 if isinstance(console.command, basestring):
917 cmd = console.command
919 cmd = utils.ShellQuoteArgs(console.command)
921 srun = ssh.SshRunner(cluster_name=cluster_name)
922 ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
923 batch=True, quiet=False, tty=True)
926 feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
928 result = _runcmd_fn(ssh_cmd, interactive=True)
930 logging.error("Console command \"%s\" failed with reason '%s' and"
931 " output %r", result.cmd, result.fail_reason,
933 raise errors.OpExecError("Connection to console of instance %s failed,"
934 " please check cluster configuration" %
937 raise errors.GenericError("Unknown console type '%s'" % console.kind)
939 return constants.EXIT_SUCCESS
942 def _FormatLogicalID(dev_type, logical_id, roman):
943 """Formats the logical_id of a disk.
946 if dev_type == constants.LD_DRBD8:
947 node_a, node_b, port, minor_a, minor_b, key = logical_id
949 ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
951 ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
953 ("port", compat.TryToRoman(port, convert=roman)),
956 elif dev_type == constants.LD_LV:
957 vg_name, lv_name = logical_id
958 data = ["%s/%s" % (vg_name, lv_name)]
960 data = [str(logical_id)]
965 def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
966 """Show block device information.
968 This is only used by L{ShowInstanceConfig}, but it's too big to be
969 left for an inline definition.
972 @param idx: the index of the current disk
973 @type top_level: boolean
974 @param top_level: if this a top-level disk?
976 @param dev: dictionary with disk information
977 @type static: boolean
978 @param static: wheter the device information doesn't contain
979 runtime information but only static data
981 @param roman: whether to try to use roman integers
982 @return: a list of either strings, tuples or lists
983 (which should be formatted at a higher indent level)
986 def helper(dtype, status):
987 """Format one line for physical device status.
990 @param dtype: a constant from the L{constants.LDS_BLOCK} set
992 @param status: a tuple as returned from L{backend.FindBlockDevice}
993 @return: the string representing the status
999 (path, major, minor, syncp, estt, degr, ldisk_status) = status
1001 major_string = "N/A"
1003 major_string = str(compat.TryToRoman(major, convert=roman))
1006 minor_string = "N/A"
1008 minor_string = str(compat.TryToRoman(minor, convert=roman))
1010 txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1011 if dtype in (constants.LD_DRBD8, ):
1012 if syncp is not None:
1013 sync_text = "*RECOVERING* %5.2f%%," % syncp
1015 sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1017 sync_text += " ETA unknown"
1019 sync_text = "in sync"
1021 degr_text = "*DEGRADED*"
1024 if ldisk_status == constants.LDS_FAULTY:
1025 ldisk_text = " *MISSING DISK*"
1026 elif ldisk_status == constants.LDS_UNKNOWN:
1027 ldisk_text = " *UNCERTAIN STATE*"
1030 txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1031 elif dtype == constants.LD_LV:
1032 if ldisk_status == constants.LDS_FAULTY:
1033 ldisk_text = " *FAILED* (failed drive?)"
1041 if dev["iv_name"] is not None:
1042 txt = dev["iv_name"]
1044 txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1046 txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1047 if isinstance(dev["size"], int):
1048 nice_size = utils.FormatUnit(dev["size"], "h")
1050 nice_size = dev["size"]
1051 d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1054 data.append(("access mode", dev["mode"]))
1055 if dev["logical_id"] is not None:
1057 l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1059 l_id = [str(dev["logical_id"])]
1061 data.append(("logical_id", l_id[0]))
1064 elif dev["physical_id"] is not None:
1065 data.append("physical_id:")
1066 data.append([dev["physical_id"]])
1068 data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1069 if dev["sstatus"] and not static:
1070 data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1073 data.append("child devices:")
1074 for c_idx, child in enumerate(dev["children"]):
1075 data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
1080 def _FormatList(buf, data, indent_level):
1081 """Formats a list of data at a given indent level.
1083 If the element of the list is:
1084 - a string, it is simply formatted as is
1085 - a tuple, it will be split into key, value and the all the
1086 values in a list will be aligned all at the same start column
1087 - a list, will be recursively formatted
1090 @param buf: the buffer into which we write the output
1091 @param data: the list to format
1092 @type indent_level: int
1093 @param indent_level: the indent level to format at
1096 max_tlen = max([len(elem[0]) for elem in data
1097 if isinstance(elem, tuple)] or [0])
1099 if isinstance(elem, basestring):
1100 buf.write("%*s%s\n" % (2*indent_level, "", elem))
1101 elif isinstance(elem, tuple):
1103 spacer = "%*s" % (max_tlen - len(key), "")
1104 buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1105 elif isinstance(elem, list):
1106 _FormatList(buf, elem, indent_level+1)
1109 def ShowInstanceConfig(opts, args):
1110 """Compute instance run-time status.
1112 @param opts: the command line options selected by the user
1114 @param args: either an empty list, and then we query all
1115 instances, or should contain a list of instance names
1117 @return: the desired exit code
1120 if not args and not opts.show_all:
1121 ToStderr("No instance selected."
1122 " Please pass in --all if you want to query all instances.\n"
1123 "Note that this can take a long time on a big cluster.")
1125 elif args and opts.show_all:
1126 ToStderr("Cannot use --all if you specify instance names.")
1130 op = opcodes.OpInstanceQueryData(instances=args, static=opts.static)
1131 result = SubmitOpCode(op, opts=opts)
1133 ToStdout("No instances.")
1138 for instance_name in result:
1139 instance = result[instance_name]
1140 buf.write("Instance name: %s\n" % instance["name"])
1141 buf.write("UUID: %s\n" % instance["uuid"])
1142 buf.write("Serial number: %s\n" %
1143 compat.TryToRoman(instance["serial_no"],
1144 convert=opts.roman_integers))
1145 buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1146 buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1147 buf.write("State: configured to be %s" % instance["config_state"])
1149 buf.write(", actual state is %s" % instance["run_state"])
1151 ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1152 ## instance["auto_balance"])
1153 buf.write(" Nodes:\n")
1154 buf.write(" - primary: %s\n" % instance["pnode"])
1155 buf.write(" - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1156 buf.write(" Operating system: %s\n" % instance["os"])
1157 FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1159 if instance.has_key("network_port"):
1160 buf.write(" Allocated network port: %s\n" %
1161 compat.TryToRoman(instance["network_port"],
1162 convert=opts.roman_integers))
1163 buf.write(" Hypervisor: %s\n" % instance["hypervisor"])
1165 # custom VNC console information
1166 vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1168 if vnc_bind_address:
1169 port = instance["network_port"]
1170 display = int(port) - constants.VNC_BASE_PORT
1171 if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1172 vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1175 elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1176 vnc_console_port = ("%s:%s (node %s) (display %s)" %
1177 (vnc_bind_address, port,
1178 instance["pnode"], display))
1180 # vnc bind address is a file
1181 vnc_console_port = "%s:%s" % (instance["pnode"],
1183 buf.write(" - console connection: vnc to %s\n" % vnc_console_port)
1185 FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1187 buf.write(" Hardware:\n")
1188 buf.write(" - VCPUs: %s\n" %
1189 compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1190 convert=opts.roman_integers))
1191 buf.write(" - memory: %sMiB\n" %
1192 compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
1193 convert=opts.roman_integers))
1194 buf.write(" - NICs:\n")
1195 for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1196 buf.write(" - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1197 (idx, mac, ip, mode, link))
1198 buf.write(" Disk template: %s\n" % instance["disk_template"])
1199 buf.write(" Disks:\n")
1201 for idx, device in enumerate(instance["disks"]):
1202 _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static,
1203 opts.roman_integers), 2)
1205 ToStdout(buf.getvalue().rstrip('\n'))
1209 def SetInstanceParams(opts, args):
1210 """Modifies an instance.
1212 All parameters take effect only at the next restart of the instance.
1214 @param opts: the command line options selected by the user
1216 @param args: should contain only one element, the instance name
1218 @return: the desired exit code
1221 if not (opts.nics or opts.disks or opts.disk_template or
1222 opts.hvparams or opts.beparams or opts.os or opts.osparams):
1223 ToStderr("Please give at least one of the parameters.")
1226 for param in opts.beparams:
1227 if isinstance(opts.beparams[param], basestring):
1228 if opts.beparams[param].lower() == "default":
1229 opts.beparams[param] = constants.VALUE_DEFAULT
1231 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1232 allowed_values=[constants.VALUE_DEFAULT])
1234 for param in opts.hvparams:
1235 if isinstance(opts.hvparams[param], basestring):
1236 if opts.hvparams[param].lower() == "default":
1237 opts.hvparams[param] = constants.VALUE_DEFAULT
1239 utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1240 allowed_values=[constants.VALUE_DEFAULT])
1242 for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1244 nic_op = int(nic_op)
1245 opts.nics[idx] = (nic_op, nic_dict)
1246 except (TypeError, ValueError):
1249 for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1251 disk_op = int(disk_op)
1252 opts.disks[idx] = (disk_op, disk_dict)
1253 except (TypeError, ValueError):
1255 if disk_op == constants.DDM_ADD:
1256 if 'size' not in disk_dict:
1257 raise errors.OpPrereqError("Missing required parameter 'size'",
1259 disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1261 if (opts.disk_template and
1262 opts.disk_template in constants.DTS_NET_MIRROR and
1264 ToStderr("Changing the disk template to a mirrored one requires"
1265 " specifying a secondary node")
1268 op = opcodes.OpInstanceSetParams(instance_name=args[0],
1271 disk_template=opts.disk_template,
1272 remote_node=opts.node,
1273 hvparams=opts.hvparams,
1274 beparams=opts.beparams,
1276 osparams=opts.osparams,
1277 force_variant=opts.force_variant,
1280 # even if here we process the result, we allow submit only
1281 result = SubmitOrSend(op, opts)
1284 ToStdout("Modified instance %s", args[0])
1285 for param, data in result:
1286 ToStdout(" - %-5s -> %s", param, data)
1287 ToStdout("Please don't forget that most parameters take effect"
1288 " only at the next start of the instance.")
1292 # multi-instance selection options
1293 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1294 help="Do not ask for confirmation when more than"
1295 " one instance is affected",
1296 action="store_true", default=False)
1298 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1299 help="Filter by nodes (primary only)",
1300 const=_SHUTDOWN_NODES_PRI, action="store_const")
1302 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1303 help="Filter by nodes (secondary only)",
1304 const=_SHUTDOWN_NODES_SEC, action="store_const")
1306 m_node_opt = cli_option("--node", dest="multi_mode",
1307 help="Filter by nodes (primary and secondary)",
1308 const=_SHUTDOWN_NODES_BOTH, action="store_const")
1310 m_clust_opt = cli_option("--all", dest="multi_mode",
1311 help="Select all instances in the cluster",
1312 const=_SHUTDOWN_CLUSTER, action="store_const")
1314 m_inst_opt = cli_option("--instance", dest="multi_mode",
1315 help="Filter by instance name [default]",
1316 const=_SHUTDOWN_INSTANCES, action="store_const")
1318 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1319 help="Filter by node tag",
1320 const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1321 action="store_const")
1323 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1324 help="Filter by primary node tag",
1325 const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1326 action="store_const")
1328 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1329 help="Filter by secondary node tag",
1330 const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1331 action="store_const")
1333 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1334 help="Filter by instance tag",
1335 const=_SHUTDOWN_INSTANCES_BY_TAGS,
1336 action="store_const")
1338 # this is defined separately due to readability only
1348 AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1349 "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1350 "Creates and adds a new instance to the cluster"),
1352 BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1354 "Create a bunch of instances based on specs in the file."),
1356 ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1357 [SHOWCMD_OPT, PRIORITY_OPT],
1358 "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1360 FailoverInstance, ARGS_ONE_INSTANCE,
1361 [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1362 DRY_RUN_OPT, PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT],
1363 "[-f] <instance>", "Stops the instance and starts it on the backup node,"
1364 " using the remote mirror (only for mirrored instances)"),
1366 MigrateInstance, ARGS_ONE_INSTANCE,
1367 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1368 PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT],
1369 "[-f] <instance>", "Migrate instance to its secondary node"
1370 " (only for mirrored instances)"),
1372 MoveInstance, ARGS_ONE_INSTANCE,
1373 [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1374 DRY_RUN_OPT, PRIORITY_OPT],
1375 "[-f] <instance>", "Move instance to an arbitrary node"
1376 " (only for instances of type file and lv)"),
1378 ShowInstanceConfig, ARGS_MANY_INSTANCES,
1379 [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1380 "[-s] {--all | <instance>...}",
1381 "Show information on the specified instance(s)"),
1383 ListInstances, ARGS_MANY_INSTANCES,
1384 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT],
1386 "Lists the instances and their status. The available fields can be shown"
1387 " using the \"list-fields\" command (see the man page for details)."
1388 " The default field list is (in order): %s." %
1389 utils.CommaJoin(_LIST_DEF_FIELDS),
1392 ListInstanceFields, [ArgUnknown()],
1393 [NOHDR_OPT, SEP_OPT],
1395 "Lists all available fields for instances"),
1397 ReinstallInstance, [ArgInstance()],
1398 [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1399 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1400 m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1401 SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1402 "[-f] <instance>", "Reinstall a stopped instance"),
1404 RemoveInstance, ARGS_ONE_INSTANCE,
1405 [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1406 DRY_RUN_OPT, PRIORITY_OPT],
1407 "[-f] <instance>", "Shuts down the instance and removes it"),
1410 [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1411 [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1412 "<instance> <new_name>", "Rename the instance"),
1414 ReplaceDisks, ARGS_ONE_INSTANCE,
1415 [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1416 NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1417 DRY_RUN_OPT, PRIORITY_OPT],
1418 "[-s|-p|-n NODE|-I NAME] <instance>",
1419 "Replaces all disks for the instance"),
1421 SetInstanceParams, ARGS_ONE_INSTANCE,
1422 [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1423 DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1424 OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1425 "<instance>", "Alters the parameters of an instance"),
1427 GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1428 [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1429 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1430 m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1431 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1432 "<instance>", "Stops an instance"),
1434 GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1435 [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1436 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1437 m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1438 BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1439 "<instance>", "Starts an instance"),
1441 GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1442 [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1443 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1444 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1445 m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1446 "<instance>", "Reboots an instance"),
1448 ActivateDisks, ARGS_ONE_INSTANCE,
1449 [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
1450 "<instance>", "Activate an instance's disks"),
1451 'deactivate-disks': (
1452 DeactivateDisks, ARGS_ONE_INSTANCE,
1453 [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1454 "[-f] <instance>", "Deactivate an instance's disks"),
1456 RecreateDisks, ARGS_ONE_INSTANCE,
1457 [SUBMIT_OPT, DISKIDX_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1458 "<instance>", "Recreate an instance's disks"),
1461 [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1462 ArgUnknown(min=1, max=1)],
1463 [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1464 "<instance> <disk> <size>", "Grow an instance's disk"),
1466 ListTags, ARGS_ONE_INSTANCE, [PRIORITY_OPT],
1467 "<instance_name>", "List the tags of the given instance"),
1469 AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1470 [TAG_SRC_OPT, PRIORITY_OPT],
1471 "<instance_name> tag...", "Add tags to the given instance"),
1473 RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1474 [TAG_SRC_OPT, PRIORITY_OPT],
1475 "<instance_name> tag...", "Remove tags from given instance"),
1478 #: dictionary with aliases for commands
1486 return GenericMain(commands, aliases=aliases,
1487 override={"tag_type": constants.TAG_INSTANCE})