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 raise errors.OpPrereqError("Selection filter does not match"
183 " any instances", errors.ECODE_INVAL)
184 multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
185 if not (opts.force_multi or not multi_on
186 or ConfirmOperation(inames, "instances", operation)):
188 jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
191 jex.QueueJob(name, op)
192 results = jex.WaitOrShow(not opts.submit_only)
193 rcode = compat.all(row[0] for row in results)
194 return int(not rcode)
198 def ListInstances(opts, args):
199 """List instances and their properties.
201 @param opts: the command line options selected by the user
203 @param args: should be an empty list
205 @return: the desired exit code
208 selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
210 fmtoverride = dict.fromkeys(["tags", "disk.sizes", "nic.macs", "nic.ips",
211 "nic.modes", "nic.links", "nic.bridges",
213 (lambda value: ",".join(str(item)
217 return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units,
218 opts.separator, not opts.no_headers,
219 format_override=fmtoverride)
222 def ListInstanceFields(opts, args):
223 """List instance fields.
225 @param opts: the command line options selected by the user
227 @param args: fields to list, or empty for all
229 @return: the desired exit code
232 return GenericListFields(constants.QR_INSTANCE, args, opts.separator,
236 def AddInstance(opts, args):
237 """Add an instance to the cluster.
239 This is just a wrapper over GenericInstanceCreate.
242 return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
245 def BatchCreate(opts, args):
246 """Create instances using a definition file.
248 This function reads a json file with instances defined
252 "disk_size": [20480],
258 "primary_node": "firstnode",
259 "secondary_node": "secondnode",
260 "iallocator": "dumb"}
263 Note that I{primary_node} and I{secondary_node} have precedence over
266 @param opts: the command line options selected by the user
268 @param args: should contain one element, the json filename
270 @return: the desired exit code
273 _DEFAULT_SPECS = {"disk_size": [20 * 1024],
276 "primary_node": None,
277 "secondary_node": None,
284 "file_storage_dir": None,
285 "force_variant": False,
286 "file_driver": 'loop'}
288 def _PopulateWithDefaults(spec):
289 """Returns a new hash combined with default values."""
290 mydict = _DEFAULT_SPECS.copy()
295 """Validate the instance specs."""
296 # Validate fields required under any circumstances
297 for required_field in ('os', 'template'):
298 if required_field not in spec:
299 raise errors.OpPrereqError('Required field "%s" is missing.' %
300 required_field, errors.ECODE_INVAL)
301 # Validate special fields
302 if spec['primary_node'] is not None:
303 if (spec['template'] in constants.DTS_NET_MIRROR and
304 spec['secondary_node'] is None):
305 raise errors.OpPrereqError('Template requires secondary node, but'
306 ' there was no secondary provided.',
308 elif spec['iallocator'] is None:
309 raise errors.OpPrereqError('You have to provide at least a primary_node'
310 ' or an iallocator.',
313 if (spec['hvparams'] and
314 not isinstance(spec['hvparams'], dict)):
315 raise errors.OpPrereqError('Hypervisor parameters must be a dict.',
318 json_filename = args[0]
320 instance_data = simplejson.loads(utils.ReadFile(json_filename))
321 except Exception, err: # pylint: disable-msg=W0703
322 ToStderr("Can't parse the instance definition file: %s" % str(err))
325 if not isinstance(instance_data, dict):
326 ToStderr("The instance definition file is not in dict format.")
329 jex = JobExecutor(opts=opts)
331 # Iterate over the instances and do:
332 # * Populate the specs with default value
333 # * Validate the instance specs
334 i_names = utils.NiceSort(instance_data.keys()) # pylint: disable-msg=E1103
336 specs = instance_data[name]
337 specs = _PopulateWithDefaults(specs)
340 hypervisor = specs['hypervisor']
341 hvparams = specs['hvparams']
344 for elem in specs['disk_size']:
346 size = utils.ParseUnit(elem)
347 except (TypeError, ValueError), err:
348 raise errors.OpPrereqError("Invalid disk size '%s' for"
350 (elem, name, err), errors.ECODE_INVAL)
351 disks.append({"size": size})
353 utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
354 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
357 for field in ('ip', 'mac', 'mode', 'link', 'bridge'):
361 tmp_nics[0][field] = specs[field]
363 if specs['nics'] is not None and tmp_nics:
364 raise errors.OpPrereqError("'nics' list incompatible with using"
365 " individual nic fields as well",
367 elif specs['nics'] is not None:
368 tmp_nics = specs['nics']
372 op = opcodes.OpInstanceCreate(instance_name=name,
374 disk_template=specs['template'],
375 mode=constants.INSTANCE_CREATE,
377 force_variant=specs["force_variant"],
378 pnode=specs['primary_node'],
379 snode=specs['secondary_node'],
381 start=specs['start'],
382 ip_check=specs['ip_check'],
383 name_check=specs['name_check'],
385 iallocator=specs['iallocator'],
386 hypervisor=hypervisor,
388 beparams=specs['backend'],
389 file_storage_dir=specs['file_storage_dir'],
390 file_driver=specs['file_driver'])
392 jex.QueueJob(name, op)
393 # we never want to wait, just show the submitted job IDs
394 jex.WaitOrShow(False)
399 def ReinstallInstance(opts, args):
400 """Reinstall an instance.
402 @param opts: the command line options selected by the user
404 @param args: should contain only one element, the name of the
405 instance to be reinstalled
407 @return: the desired exit code
410 # first, compute the desired name list
411 if opts.multi_mode is None:
412 opts.multi_mode = _SHUTDOWN_INSTANCES
414 inames = _ExpandMultiNames(opts.multi_mode, args)
416 raise errors.OpPrereqError("Selection filter does not match any instances",
419 # second, if requested, ask for an OS
420 if opts.select_os is True:
421 op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[])
422 result = SubmitOpCode(op, opts=opts)
425 ToStdout("Can't get the OS list")
428 ToStdout("Available OS templates:")
431 for (name, variants) in result:
432 for entry in CalculateOSNames(name, variants):
433 ToStdout("%3s: %s", number, entry)
434 choices.append(("%s" % number, entry, entry))
437 choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
438 selected = AskUser("Enter OS template number (or x to abort):",
441 if selected == 'exit':
442 ToStderr("User aborted reinstall, exiting")
449 # third, get confirmation: multi-reinstall requires --force-multi,
450 # single-reinstall either --force or --force-multi (--force-multi is
451 # a stronger --force)
452 multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
454 warn_msg = "Note: this will remove *all* data for the below instances!\n"
455 if not (opts.force_multi or
456 ConfirmOperation(inames, "instances", "reinstall", extra=warn_msg)):
459 if not (opts.force or opts.force_multi):
460 usertext = ("This will reinstall the instance %s and remove"
461 " all data. Continue?") % inames[0]
462 if not AskUser(usertext):
465 jex = JobExecutor(verbose=multi_on, opts=opts)
466 for instance_name in inames:
467 op = opcodes.OpInstanceReinstall(instance_name=instance_name,
469 force_variant=opts.force_variant,
470 osparams=opts.osparams)
471 jex.QueueJob(instance_name, op)
473 jex.WaitOrShow(not opts.submit_only)
477 def RemoveInstance(opts, args):
478 """Remove an instance.
480 @param opts: the command line options selected by the user
482 @param args: should contain only one element, the name of
483 the instance to be removed
485 @return: the desired exit code
488 instance_name = args[0]
493 _EnsureInstancesExist(cl, [instance_name])
495 usertext = ("This will remove the volumes of the instance %s"
496 " (including mirrors), thus removing all the data"
497 " of the instance. Continue?") % instance_name
498 if not AskUser(usertext):
501 op = opcodes.OpInstanceRemove(instance_name=instance_name,
502 ignore_failures=opts.ignore_failures,
503 shutdown_timeout=opts.shutdown_timeout)
504 SubmitOrSend(op, opts, cl=cl)
508 def RenameInstance(opts, args):
509 """Rename an instance.
511 @param opts: the command line options selected by the user
513 @param args: should contain two elements, the old and the
516 @return: the desired exit code
519 if not opts.name_check:
520 if not AskUser("As you disabled the check of the DNS entry, please verify"
521 " that '%s' is a FQDN. Continue?" % args[1]):
524 op = opcodes.OpInstanceRename(instance_name=args[0],
526 ip_check=opts.ip_check,
527 name_check=opts.name_check)
528 result = SubmitOrSend(op, opts)
531 ToStdout("Instance '%s' renamed to '%s'", args[0], result)
536 def ActivateDisks(opts, args):
537 """Activate an instance's disks.
539 This serves two purposes:
540 - it allows (as long as the instance is not running)
541 mounting the disks and modifying them from the node
542 - it repairs inactive secondary drbds
544 @param opts: the command line options selected by the user
546 @param args: should contain only one element, the instance name
548 @return: the desired exit code
551 instance_name = args[0]
552 op = opcodes.OpInstanceActivateDisks(instance_name=instance_name,
553 ignore_size=opts.ignore_size)
554 disks_info = SubmitOrSend(op, opts)
555 for host, iname, nname in disks_info:
556 ToStdout("%s:%s:%s", host, iname, nname)
560 def DeactivateDisks(opts, args):
561 """Deactivate an instance's disks.
563 This function takes the instance name, looks for its primary node
564 and the tries to shutdown its block devices on that node.
566 @param opts: the command line options selected by the user
568 @param args: should contain only one element, the instance name
570 @return: the desired exit code
573 instance_name = args[0]
574 op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name,
576 SubmitOrSend(op, opts)
580 def RecreateDisks(opts, args):
581 """Recreate an instance's disks.
583 @param opts: the command line options selected by the user
585 @param args: should contain only one element, the instance name
587 @return: the desired exit code
590 instance_name = args[0]
593 opts.disks = [int(v) for v in opts.disks.split(",")]
594 except (ValueError, TypeError), err:
595 ToStderr("Invalid disks value: %s" % str(err))
600 op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
602 SubmitOrSend(op, opts)
606 def GrowDisk(opts, args):
607 """Grow an instance's disks.
609 @param opts: the command line options selected by the user
611 @param args: should contain two elements, the instance name
612 whose disks we grow and the disk name, e.g. I{sda}
614 @return: the desired exit code
621 except (TypeError, ValueError), err:
622 raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
624 amount = utils.ParseUnit(args[2])
625 op = opcodes.OpInstanceGrowDisk(instance_name=instance,
626 disk=disk, amount=amount,
627 wait_for_sync=opts.wait_for_sync)
628 SubmitOrSend(op, opts)
632 def _StartupInstance(name, opts):
633 """Startup instances.
635 This returns the opcode to start an instance, and its decorator will
636 wrap this into a loop starting all desired instances.
638 @param name: the name of the instance to act on
639 @param opts: the command line options selected by the user
640 @return: the opcode needed for the operation
643 op = opcodes.OpInstanceStartup(instance_name=name,
645 ignore_offline_nodes=opts.ignore_offline)
646 # do not add these parameters to the opcode unless they're defined
648 op.hvparams = opts.hvparams
650 op.beparams = opts.beparams
654 def _RebootInstance(name, opts):
655 """Reboot instance(s).
657 This returns the opcode to reboot an instance, and its decorator
658 will wrap this into a loop rebooting all desired instances.
660 @param name: the name of the instance to act on
661 @param opts: the command line options selected by the user
662 @return: the opcode needed for the operation
665 return opcodes.OpInstanceReboot(instance_name=name,
666 reboot_type=opts.reboot_type,
667 ignore_secondaries=opts.ignore_secondaries,
668 shutdown_timeout=opts.shutdown_timeout)
671 def _ShutdownInstance(name, opts):
672 """Shutdown an instance.
674 This returns the opcode to shutdown an instance, and its decorator
675 will wrap this into a loop shutting down all desired instances.
677 @param name: the name of the instance to act on
678 @param opts: the command line options selected by the user
679 @return: the opcode needed for the operation
682 return opcodes.OpInstanceShutdown(instance_name=name,
683 timeout=opts.timeout,
684 ignore_offline_nodes=opts.ignore_offline)
687 def ReplaceDisks(opts, args):
688 """Replace the disks of an instance
690 @param opts: the command line options selected by the user
692 @param args: should contain only one element, the instance name
694 @return: the desired exit code
697 new_2ndary = opts.dst_node
698 iallocator = opts.iallocator
699 if opts.disks is None:
703 disks = [int(i) for i in opts.disks.split(",")]
704 except (TypeError, ValueError), err:
705 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
707 cnt = [opts.on_primary, opts.on_secondary, opts.auto,
708 new_2ndary is not None, iallocator is not None].count(True)
710 raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -i"
711 " options must be passed", errors.ECODE_INVAL)
712 elif opts.on_primary:
713 mode = constants.REPLACE_DISK_PRI
714 elif opts.on_secondary:
715 mode = constants.REPLACE_DISK_SEC
717 mode = constants.REPLACE_DISK_AUTO
719 raise errors.OpPrereqError("Cannot specify disks when using automatic"
720 " mode", errors.ECODE_INVAL)
721 elif new_2ndary is not None or iallocator is not None:
723 mode = constants.REPLACE_DISK_CHG
725 op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
726 remote_node=new_2ndary, mode=mode,
727 iallocator=iallocator,
728 early_release=opts.early_release)
729 SubmitOrSend(op, opts)
733 def FailoverInstance(opts, args):
734 """Failover an instance.
736 The failover is done by shutting it down on its present node and
737 starting it on the secondary.
739 @param opts: the command line options selected by the user
741 @param args: should contain only one element, the instance name
743 @return: the desired exit code
747 instance_name = args[0]
751 _EnsureInstancesExist(cl, [instance_name])
753 usertext = ("Failover will happen to image %s."
754 " This requires a shutdown of the instance. Continue?" %
756 if not AskUser(usertext):
759 op = opcodes.OpInstanceFailover(instance_name=instance_name,
760 ignore_consistency=opts.ignore_consistency,
761 shutdown_timeout=opts.shutdown_timeout)
762 SubmitOrSend(op, opts, cl=cl)
766 def MigrateInstance(opts, args):
767 """Migrate an instance.
769 The migrate is done without shutdown.
771 @param opts: the command line options selected by the user
773 @param args: should contain only one element, the instance name
775 @return: the desired exit code
779 instance_name = args[0]
783 _EnsureInstancesExist(cl, [instance_name])
786 usertext = ("Instance %s will be recovered from a failed migration."
787 " Note that the migration procedure (including cleanup)" %
790 usertext = ("Instance %s will be migrated. Note that migration" %
792 usertext += (" might impact the instance if anything goes wrong"
793 " (e.g. due to bugs in the hypervisor). Continue?")
794 if not AskUser(usertext):
797 # this should be removed once --non-live is deprecated
798 if not opts.live and opts.migration_mode is not None:
799 raise errors.OpPrereqError("Only one of the --non-live and "
800 "--migration-mode options can be passed",
802 if not opts.live: # --non-live passed
803 mode = constants.HT_MIGRATION_NONLIVE
805 mode = opts.migration_mode
807 op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
808 cleanup=opts.cleanup)
809 SubmitOpCode(op, cl=cl, opts=opts)
813 def MoveInstance(opts, args):
816 @param opts: the command line options selected by the user
818 @param args: should contain only one element, the instance name
820 @return: the desired exit code
824 instance_name = args[0]
828 usertext = ("Instance %s will be moved."
829 " This requires a shutdown of the instance. Continue?" %
831 if not AskUser(usertext):
834 op = opcodes.OpInstanceMove(instance_name=instance_name,
835 target_node=opts.node,
836 shutdown_timeout=opts.shutdown_timeout)
837 SubmitOrSend(op, opts, cl=cl)
841 def ConnectToInstanceConsole(opts, args):
842 """Connect to the console of an instance.
844 @param opts: the command line options selected by the user
846 @param args: should contain only one element, the instance name
848 @return: the desired exit code
851 instance_name = args[0]
853 op = opcodes.OpInstanceConsole(instance_name=instance_name)
857 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
858 console_data = SubmitOpCode(op, opts=opts, cl=cl)
860 # Ensure client connection is closed while external commands are run
865 return _DoConsole(objects.InstanceConsole.FromDict(console_data),
866 opts.show_command, cluster_name)
869 def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
870 _runcmd_fn=utils.RunCmd):
871 """Acts based on the result of L{opcodes.OpInstanceConsole}.
873 @type console: L{objects.InstanceConsole}
874 @param console: Console object
875 @type show_command: bool
876 @param show_command: Whether to just display commands
877 @type cluster_name: string
878 @param cluster_name: Cluster name as retrieved from master daemon
881 assert console.Validate()
883 if console.kind == constants.CONS_MESSAGE:
884 feedback_fn(console.message)
885 elif console.kind == constants.CONS_VNC:
886 feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
887 " URL <vnc://%s:%s/>",
888 console.instance, console.host, console.port,
889 console.display, console.host, console.port)
890 elif console.kind == constants.CONS_SSH:
891 # Convert to string if not already one
892 if isinstance(console.command, basestring):
893 cmd = console.command
895 cmd = utils.ShellQuoteArgs(console.command)
897 srun = ssh.SshRunner(cluster_name=cluster_name)
898 ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
899 batch=True, quiet=False, tty=True)
902 feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
904 result = _runcmd_fn(ssh_cmd, interactive=True)
906 logging.error("Console command \"%s\" failed with reason '%s' and"
907 " output %r", result.cmd, result.fail_reason,
909 raise errors.OpExecError("Connection to console of instance %s failed,"
910 " please check cluster configuration" %
913 raise errors.GenericError("Unknown console type '%s'" % console.kind)
915 return constants.EXIT_SUCCESS
918 def _FormatLogicalID(dev_type, logical_id, roman):
919 """Formats the logical_id of a disk.
922 if dev_type == constants.LD_DRBD8:
923 node_a, node_b, port, minor_a, minor_b, key = logical_id
925 ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
927 ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
929 ("port", compat.TryToRoman(port, convert=roman)),
932 elif dev_type == constants.LD_LV:
933 vg_name, lv_name = logical_id
934 data = ["%s/%s" % (vg_name, lv_name)]
936 data = [str(logical_id)]
941 def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
942 """Show block device information.
944 This is only used by L{ShowInstanceConfig}, but it's too big to be
945 left for an inline definition.
948 @param idx: the index of the current disk
949 @type top_level: boolean
950 @param top_level: if this a top-level disk?
952 @param dev: dictionary with disk information
953 @type static: boolean
954 @param static: wheter the device information doesn't contain
955 runtime information but only static data
957 @param roman: whether to try to use roman integers
958 @return: a list of either strings, tuples or lists
959 (which should be formatted at a higher indent level)
962 def helper(dtype, status):
963 """Format one line for physical device status.
966 @param dtype: a constant from the L{constants.LDS_BLOCK} set
968 @param status: a tuple as returned from L{backend.FindBlockDevice}
969 @return: the string representing the status
975 (path, major, minor, syncp, estt, degr, ldisk_status) = status
979 major_string = str(compat.TryToRoman(major, convert=roman))
984 minor_string = str(compat.TryToRoman(minor, convert=roman))
986 txt += ("%s (%s:%s)" % (path, major_string, minor_string))
987 if dtype in (constants.LD_DRBD8, ):
988 if syncp is not None:
989 sync_text = "*RECOVERING* %5.2f%%," % syncp
991 sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
993 sync_text += " ETA unknown"
995 sync_text = "in sync"
997 degr_text = "*DEGRADED*"
1000 if ldisk_status == constants.LDS_FAULTY:
1001 ldisk_text = " *MISSING DISK*"
1002 elif ldisk_status == constants.LDS_UNKNOWN:
1003 ldisk_text = " *UNCERTAIN STATE*"
1006 txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1007 elif dtype == constants.LD_LV:
1008 if ldisk_status == constants.LDS_FAULTY:
1009 ldisk_text = " *FAILED* (failed drive?)"
1017 if dev["iv_name"] is not None:
1018 txt = dev["iv_name"]
1020 txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1022 txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1023 if isinstance(dev["size"], int):
1024 nice_size = utils.FormatUnit(dev["size"], "h")
1026 nice_size = dev["size"]
1027 d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1030 data.append(("access mode", dev["mode"]))
1031 if dev["logical_id"] is not None:
1033 l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1035 l_id = [str(dev["logical_id"])]
1037 data.append(("logical_id", l_id[0]))
1040 elif dev["physical_id"] is not None:
1041 data.append("physical_id:")
1042 data.append([dev["physical_id"]])
1044 data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1045 if dev["sstatus"] and not static:
1046 data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1049 data.append("child devices:")
1050 for c_idx, child in enumerate(dev["children"]):
1051 data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
1056 def _FormatList(buf, data, indent_level):
1057 """Formats a list of data at a given indent level.
1059 If the element of the list is:
1060 - a string, it is simply formatted as is
1061 - a tuple, it will be split into key, value and the all the
1062 values in a list will be aligned all at the same start column
1063 - a list, will be recursively formatted
1066 @param buf: the buffer into which we write the output
1067 @param data: the list to format
1068 @type indent_level: int
1069 @param indent_level: the indent level to format at
1072 max_tlen = max([len(elem[0]) for elem in data
1073 if isinstance(elem, tuple)] or [0])
1075 if isinstance(elem, basestring):
1076 buf.write("%*s%s\n" % (2*indent_level, "", elem))
1077 elif isinstance(elem, tuple):
1079 spacer = "%*s" % (max_tlen - len(key), "")
1080 buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1081 elif isinstance(elem, list):
1082 _FormatList(buf, elem, indent_level+1)
1085 def ShowInstanceConfig(opts, args):
1086 """Compute instance run-time status.
1088 @param opts: the command line options selected by the user
1090 @param args: either an empty list, and then we query all
1091 instances, or should contain a list of instance names
1093 @return: the desired exit code
1096 if not args and not opts.show_all:
1097 ToStderr("No instance selected."
1098 " Please pass in --all if you want to query all instances.\n"
1099 "Note that this can take a long time on a big cluster.")
1101 elif args and opts.show_all:
1102 ToStderr("Cannot use --all if you specify instance names.")
1106 op = opcodes.OpInstanceQueryData(instances=args, static=opts.static)
1107 result = SubmitOpCode(op, opts=opts)
1109 ToStdout("No instances.")
1114 for instance_name in result:
1115 instance = result[instance_name]
1116 buf.write("Instance name: %s\n" % instance["name"])
1117 buf.write("UUID: %s\n" % instance["uuid"])
1118 buf.write("Serial number: %s\n" %
1119 compat.TryToRoman(instance["serial_no"],
1120 convert=opts.roman_integers))
1121 buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1122 buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1123 buf.write("State: configured to be %s" % instance["config_state"])
1125 buf.write(", actual state is %s" % instance["run_state"])
1127 ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1128 ## instance["auto_balance"])
1129 buf.write(" Nodes:\n")
1130 buf.write(" - primary: %s\n" % instance["pnode"])
1131 buf.write(" - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1132 buf.write(" Operating system: %s\n" % instance["os"])
1133 FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1135 if instance.has_key("network_port"):
1136 buf.write(" Allocated network port: %s\n" %
1137 compat.TryToRoman(instance["network_port"],
1138 convert=opts.roman_integers))
1139 buf.write(" Hypervisor: %s\n" % instance["hypervisor"])
1141 # custom VNC console information
1142 vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1144 if vnc_bind_address:
1145 port = instance["network_port"]
1146 display = int(port) - constants.VNC_BASE_PORT
1147 if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1148 vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1151 elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1152 vnc_console_port = ("%s:%s (node %s) (display %s)" %
1153 (vnc_bind_address, port,
1154 instance["pnode"], display))
1156 # vnc bind address is a file
1157 vnc_console_port = "%s:%s" % (instance["pnode"],
1159 buf.write(" - console connection: vnc to %s\n" % vnc_console_port)
1161 FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1163 buf.write(" Hardware:\n")
1164 buf.write(" - VCPUs: %s\n" %
1165 compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1166 convert=opts.roman_integers))
1167 buf.write(" - memory: %sMiB\n" %
1168 compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
1169 convert=opts.roman_integers))
1170 buf.write(" - NICs:\n")
1171 for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1172 buf.write(" - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1173 (idx, mac, ip, mode, link))
1174 buf.write(" Disk template: %s\n" % instance["disk_template"])
1175 buf.write(" Disks:\n")
1177 for idx, device in enumerate(instance["disks"]):
1178 _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static,
1179 opts.roman_integers), 2)
1181 ToStdout(buf.getvalue().rstrip('\n'))
1185 def SetInstanceParams(opts, args):
1186 """Modifies an instance.
1188 All parameters take effect only at the next restart of the instance.
1190 @param opts: the command line options selected by the user
1192 @param args: should contain only one element, the instance name
1194 @return: the desired exit code
1197 if not (opts.nics or opts.disks or opts.disk_template or
1198 opts.hvparams or opts.beparams or opts.os or opts.osparams):
1199 ToStderr("Please give at least one of the parameters.")
1202 for param in opts.beparams:
1203 if isinstance(opts.beparams[param], basestring):
1204 if opts.beparams[param].lower() == "default":
1205 opts.beparams[param] = constants.VALUE_DEFAULT
1207 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1208 allowed_values=[constants.VALUE_DEFAULT])
1210 for param in opts.hvparams:
1211 if isinstance(opts.hvparams[param], basestring):
1212 if opts.hvparams[param].lower() == "default":
1213 opts.hvparams[param] = constants.VALUE_DEFAULT
1215 utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1216 allowed_values=[constants.VALUE_DEFAULT])
1218 for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1220 nic_op = int(nic_op)
1221 opts.nics[idx] = (nic_op, nic_dict)
1222 except (TypeError, ValueError):
1225 for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1227 disk_op = int(disk_op)
1228 opts.disks[idx] = (disk_op, disk_dict)
1229 except (TypeError, ValueError):
1231 if disk_op == constants.DDM_ADD:
1232 if 'size' not in disk_dict:
1233 raise errors.OpPrereqError("Missing required parameter 'size'",
1235 disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1237 if (opts.disk_template and
1238 opts.disk_template in constants.DTS_NET_MIRROR and
1240 ToStderr("Changing the disk template to a mirrored one requires"
1241 " specifying a secondary node")
1244 op = opcodes.OpInstanceSetParams(instance_name=args[0],
1247 disk_template=opts.disk_template,
1248 remote_node=opts.node,
1249 hvparams=opts.hvparams,
1250 beparams=opts.beparams,
1252 osparams=opts.osparams,
1253 force_variant=opts.force_variant,
1256 # even if here we process the result, we allow submit only
1257 result = SubmitOrSend(op, opts)
1260 ToStdout("Modified instance %s", args[0])
1261 for param, data in result:
1262 ToStdout(" - %-5s -> %s", param, data)
1263 ToStdout("Please don't forget that most parameters take effect"
1264 " only at the next start of the instance.")
1268 # multi-instance selection options
1269 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1270 help="Do not ask for confirmation when more than"
1271 " one instance is affected",
1272 action="store_true", default=False)
1274 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1275 help="Filter by nodes (primary only)",
1276 const=_SHUTDOWN_NODES_PRI, action="store_const")
1278 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1279 help="Filter by nodes (secondary only)",
1280 const=_SHUTDOWN_NODES_SEC, action="store_const")
1282 m_node_opt = cli_option("--node", dest="multi_mode",
1283 help="Filter by nodes (primary and secondary)",
1284 const=_SHUTDOWN_NODES_BOTH, action="store_const")
1286 m_clust_opt = cli_option("--all", dest="multi_mode",
1287 help="Select all instances in the cluster",
1288 const=_SHUTDOWN_CLUSTER, action="store_const")
1290 m_inst_opt = cli_option("--instance", dest="multi_mode",
1291 help="Filter by instance name [default]",
1292 const=_SHUTDOWN_INSTANCES, action="store_const")
1294 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1295 help="Filter by node tag",
1296 const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1297 action="store_const")
1299 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1300 help="Filter by primary node tag",
1301 const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1302 action="store_const")
1304 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1305 help="Filter by secondary node tag",
1306 const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1307 action="store_const")
1309 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1310 help="Filter by instance tag",
1311 const=_SHUTDOWN_INSTANCES_BY_TAGS,
1312 action="store_const")
1314 # this is defined separately due to readability only
1324 AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1325 "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1326 "Creates and adds a new instance to the cluster"),
1328 BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1330 "Create a bunch of instances based on specs in the file."),
1332 ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1333 [SHOWCMD_OPT, PRIORITY_OPT],
1334 "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1336 FailoverInstance, ARGS_ONE_INSTANCE,
1337 [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1338 DRY_RUN_OPT, PRIORITY_OPT],
1339 "[-f] <instance>", "Stops the instance and starts it on the backup node,"
1340 " using the remote mirror (only for instances of type drbd)"),
1342 MigrateInstance, ARGS_ONE_INSTANCE,
1343 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1345 "[-f] <instance>", "Migrate instance to its secondary node"
1346 " (only for instances of type drbd)"),
1348 MoveInstance, ARGS_ONE_INSTANCE,
1349 [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1350 DRY_RUN_OPT, PRIORITY_OPT],
1351 "[-f] <instance>", "Move instance to an arbitrary node"
1352 " (only for instances of type file and lv)"),
1354 ShowInstanceConfig, ARGS_MANY_INSTANCES,
1355 [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1356 "[-s] {--all | <instance>...}",
1357 "Show information on the specified instance(s)"),
1359 ListInstances, ARGS_MANY_INSTANCES,
1360 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
1362 "Lists the instances and their status. The available fields can be shown"
1363 " using the \"list-fields\" command (see the man page for details)."
1364 " The default field list is (in order): %s." %
1365 utils.CommaJoin(_LIST_DEF_FIELDS),
1368 ListInstanceFields, [ArgUnknown()],
1369 [NOHDR_OPT, SEP_OPT],
1371 "Lists all available fields for instances"),
1373 ReinstallInstance, [ArgInstance()],
1374 [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1375 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1376 m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1377 SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1378 "[-f] <instance>", "Reinstall a stopped instance"),
1380 RemoveInstance, ARGS_ONE_INSTANCE,
1381 [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1382 DRY_RUN_OPT, PRIORITY_OPT],
1383 "[-f] <instance>", "Shuts down the instance and removes it"),
1386 [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1387 [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1388 "<instance> <new_name>", "Rename the instance"),
1390 ReplaceDisks, ARGS_ONE_INSTANCE,
1391 [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1392 NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1393 DRY_RUN_OPT, PRIORITY_OPT],
1394 "[-s|-p|-n NODE|-I NAME] <instance>",
1395 "Replaces all disks for the instance"),
1397 SetInstanceParams, ARGS_ONE_INSTANCE,
1398 [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1399 DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1400 OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1401 "<instance>", "Alters the parameters of an instance"),
1403 GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1404 [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1405 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1406 m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1407 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1408 "<instance>", "Stops an instance"),
1410 GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1411 [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1412 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1413 m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1414 BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1415 "<instance>", "Starts an instance"),
1417 GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1418 [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1419 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1420 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1421 m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1422 "<instance>", "Reboots an instance"),
1424 ActivateDisks, ARGS_ONE_INSTANCE,
1425 [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
1426 "<instance>", "Activate an instance's disks"),
1427 'deactivate-disks': (
1428 DeactivateDisks, ARGS_ONE_INSTANCE,
1429 [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1430 "[-f] <instance>", "Deactivate an instance's disks"),
1432 RecreateDisks, ARGS_ONE_INSTANCE,
1433 [SUBMIT_OPT, DISKIDX_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1434 "<instance>", "Recreate an instance's disks"),
1437 [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1438 ArgUnknown(min=1, max=1)],
1439 [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1440 "<instance> <disk> <size>", "Grow an instance's disk"),
1442 ListTags, ARGS_ONE_INSTANCE, [PRIORITY_OPT],
1443 "<instance_name>", "List the tags of the given instance"),
1445 AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1446 [TAG_SRC_OPT, PRIORITY_OPT],
1447 "<instance_name> tag...", "Add tags to the given instance"),
1449 RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1450 [TAG_SRC_OPT, PRIORITY_OPT],
1451 "<instance_name> tag...", "Remove tags from given instance"),
1454 #: dictionary with aliases for commands
1462 return GenericMain(commands, aliases=aliases,
1463 override={"tag_type": constants.TAG_INSTANCE})