4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 """Instance related commands"""
23 # pylint: disable-msg=W0401,W0614,C0103
24 # W0401: Wildcard import ganeti.cli
25 # W0614: Unused import %s from wildcard import (since we need cli)
26 # C0103: Invalid name gnt-instance
31 from cStringIO import StringIO
33 from ganeti.cli import *
34 from ganeti import opcodes
35 from ganeti import constants
36 from ganeti import compat
37 from ganeti import utils
38 from ganeti import errors
39 from ganeti import netutils
40 from ganeti import ssh
41 from ganeti import objects
44 _SHUTDOWN_CLUSTER = "cluster"
45 _SHUTDOWN_NODES_BOTH = "nodes"
46 _SHUTDOWN_NODES_PRI = "nodes-pri"
47 _SHUTDOWN_NODES_SEC = "nodes-sec"
48 _SHUTDOWN_NODES_BOTH_BY_TAGS = "nodes-by-tags"
49 _SHUTDOWN_NODES_PRI_BY_TAGS = "nodes-pri-by-tags"
50 _SHUTDOWN_NODES_SEC_BY_TAGS = "nodes-sec-by-tags"
51 _SHUTDOWN_INSTANCES = "instances"
52 _SHUTDOWN_INSTANCES_BY_TAGS = "instances-by-tags"
54 _SHUTDOWN_NODES_TAGS_MODES = (
55 _SHUTDOWN_NODES_BOTH_BY_TAGS,
56 _SHUTDOWN_NODES_PRI_BY_TAGS,
57 _SHUTDOWN_NODES_SEC_BY_TAGS)
60 #: default list of options for L{ListInstances}
62 "name", "hypervisor", "os", "pnode", "status", "oper_ram",
66 def _ExpandMultiNames(mode, names, client=None):
67 """Expand the given names using the passed mode.
69 For _SHUTDOWN_CLUSTER, all instances will be returned. For
70 _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
71 primary/secondary will be returned. For _SHUTDOWN_NODES_BOTH, all
72 instances having those nodes as either primary or secondary will be
73 returned. For _SHUTDOWN_INSTANCES, the given instances will be
76 @param mode: one of L{_SHUTDOWN_CLUSTER}, L{_SHUTDOWN_NODES_BOTH},
77 L{_SHUTDOWN_NODES_PRI}, L{_SHUTDOWN_NODES_SEC} or
78 L{_SHUTDOWN_INSTANCES}
79 @param names: a list of names; for cluster, it must be empty,
80 and for node and instance it must be a list of valid item
81 names (short names are valid as usual, e.g. node1 instead of
84 @return: the list of names after the expansion
85 @raise errors.ProgrammerError: for unknown selection type
86 @raise errors.OpPrereqError: for invalid input parameters
89 # pylint: disable-msg=W0142
93 if mode == _SHUTDOWN_CLUSTER:
95 raise errors.OpPrereqError("Cluster filter mode takes no arguments",
97 idata = client.QueryInstances([], ["name"], False)
98 inames = [row[0] for row in idata]
100 elif mode in (_SHUTDOWN_NODES_BOTH,
102 _SHUTDOWN_NODES_SEC) + _SHUTDOWN_NODES_TAGS_MODES:
103 if mode in _SHUTDOWN_NODES_TAGS_MODES:
105 raise errors.OpPrereqError("No node tags passed", errors.ECODE_INVAL)
106 ndata = client.QueryNodes([], ["name", "pinst_list",
107 "sinst_list", "tags"], False)
108 ndata = [row for row in ndata if set(row[3]).intersection(names)]
111 raise errors.OpPrereqError("No node names passed", errors.ECODE_INVAL)
112 ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
115 ipri = [row[1] for row in ndata]
116 pri_names = list(itertools.chain(*ipri))
117 isec = [row[2] for row in ndata]
118 sec_names = list(itertools.chain(*isec))
119 if mode in (_SHUTDOWN_NODES_BOTH, _SHUTDOWN_NODES_BOTH_BY_TAGS):
120 inames = pri_names + sec_names
121 elif mode in (_SHUTDOWN_NODES_PRI, _SHUTDOWN_NODES_PRI_BY_TAGS):
123 elif mode in (_SHUTDOWN_NODES_SEC, _SHUTDOWN_NODES_SEC_BY_TAGS):
126 raise errors.ProgrammerError("Unhandled shutdown type")
127 elif mode == _SHUTDOWN_INSTANCES:
129 raise errors.OpPrereqError("No instance names passed",
131 idata = client.QueryInstances(names, ["name"], False)
132 inames = [row[0] for row in idata]
133 elif mode == _SHUTDOWN_INSTANCES_BY_TAGS:
135 raise errors.OpPrereqError("No instance tags passed",
137 idata = client.QueryInstances([], ["name", "tags"], False)
138 inames = [row[0] for row in idata if set(row[1]).intersection(names)]
140 raise errors.OpPrereqError("Unknown mode '%s'" % mode, errors.ECODE_INVAL)
145 def _ConfirmOperation(inames, text, extra=""):
146 """Ask the user to confirm an operation on a list of instances.
148 This function is used to request confirmation for doing an operation
149 on a given list of instances.
152 @param inames: the list of names that we display when
153 we ask for confirmation
155 @param text: the operation that the user should confirm
156 (e.g. I{shutdown} or I{startup})
158 @return: True or False depending on user's confirmation.
162 msg = ("The %s will operate on %d instances.\n%s"
163 "Do you want to continue?" % (text, count, extra))
164 affected = ("\nAffected instances:\n" +
165 "\n".join([" %s" % name for name in inames]))
167 choices = [('y', True, 'Yes, execute the %s' % text),
168 ('n', False, 'No, abort the %s' % text)]
171 choices.insert(1, ('v', 'v', 'View the list of affected instances'))
176 choice = AskUser(ask, choices)
179 choice = AskUser(msg + affected, choices)
183 def _EnsureInstancesExist(client, names):
184 """Check for and ensure the given instance names exist.
186 This function will raise an OpPrereqError in case they don't
187 exist. Otherwise it will exit cleanly.
189 @type client: L{ganeti.luxi.Client}
190 @param client: the client to use for the query
192 @param names: the list of instance names to query
193 @raise errors.OpPrereqError: in case any instance is missing
196 # TODO: change LUInstanceQuery to that it actually returns None
197 # instead of raising an exception, or devise a better mechanism
198 result = client.QueryInstances(names, ["name"], False)
199 for orig_name, row in zip(names, result):
201 raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name,
205 def GenericManyOps(operation, fn):
206 """Generic multi-instance operations.
208 The will return a wrapper that processes the options and arguments
209 given, and uses the passed function to build the opcode needed for
210 the specific operation. Thus all the generic loop/confirmation code
211 is abstracted into this function.
214 def realfn(opts, args):
215 if opts.multi_mode is None:
216 opts.multi_mode = _SHUTDOWN_INSTANCES
218 inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
220 if opts.multi_mode == _SHUTDOWN_CLUSTER:
221 ToStdout("Cluster is empty, no instances to shutdown")
223 raise errors.OpPrereqError("Selection filter does not match"
224 " any instances", errors.ECODE_INVAL)
225 multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
226 if not (opts.force_multi or not multi_on
227 or _ConfirmOperation(inames, operation)):
229 jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
232 jex.QueueJob(name, op)
233 results = jex.WaitOrShow(not opts.submit_only)
234 rcode = compat.all(row[0] for row in results)
235 return int(not rcode)
239 def ListInstances(opts, args):
240 """List instances and their properties.
242 @param opts: the command line options selected by the user
244 @param args: should be an empty list
246 @return: the desired exit code
249 selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
251 fmtoverride = dict.fromkeys(["tags", "disk.sizes", "nic.macs", "nic.ips",
252 "nic.modes", "nic.links", "nic.bridges",
254 (lambda value: ",".join(str(item)
258 return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units,
259 opts.separator, not opts.no_headers,
260 format_override=fmtoverride, verbose=opts.verbose)
263 def ListInstanceFields(opts, args):
264 """List instance fields.
266 @param opts: the command line options selected by the user
268 @param args: fields to list, or empty for all
270 @return: the desired exit code
273 return GenericListFields(constants.QR_INSTANCE, args, opts.separator,
277 def AddInstance(opts, args):
278 """Add an instance to the cluster.
280 This is just a wrapper over GenericInstanceCreate.
283 return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
286 def BatchCreate(opts, args):
287 """Create instances using a definition file.
289 This function reads a json file with instances defined
293 "disk_size": [20480],
299 "primary_node": "firstnode",
300 "secondary_node": "secondnode",
301 "iallocator": "dumb"}
304 Note that I{primary_node} and I{secondary_node} have precedence over
307 @param opts: the command line options selected by the user
309 @param args: should contain one element, the json filename
311 @return: the desired exit code
314 _DEFAULT_SPECS = {"disk_size": [20 * 1024],
317 "primary_node": None,
318 "secondary_node": None,
325 "file_storage_dir": None,
326 "force_variant": False,
327 "file_driver": 'loop'}
329 def _PopulateWithDefaults(spec):
330 """Returns a new hash combined with default values."""
331 mydict = _DEFAULT_SPECS.copy()
336 """Validate the instance specs."""
337 # Validate fields required under any circumstances
338 for required_field in ('os', 'template'):
339 if required_field not in spec:
340 raise errors.OpPrereqError('Required field "%s" is missing.' %
341 required_field, errors.ECODE_INVAL)
342 # Validate special fields
343 if spec['primary_node'] is not None:
344 if (spec['template'] in constants.DTS_NET_MIRROR and
345 spec['secondary_node'] is None):
346 raise errors.OpPrereqError('Template requires secondary node, but'
347 ' there was no secondary provided.',
349 elif spec['iallocator'] is None:
350 raise errors.OpPrereqError('You have to provide at least a primary_node'
351 ' or an iallocator.',
354 if (spec['hvparams'] and
355 not isinstance(spec['hvparams'], dict)):
356 raise errors.OpPrereqError('Hypervisor parameters must be a dict.',
359 json_filename = args[0]
361 instance_data = simplejson.loads(utils.ReadFile(json_filename))
362 except Exception, err: # pylint: disable-msg=W0703
363 ToStderr("Can't parse the instance definition file: %s" % str(err))
366 if not isinstance(instance_data, dict):
367 ToStderr("The instance definition file is not in dict format.")
370 jex = JobExecutor(opts=opts)
372 # Iterate over the instances and do:
373 # * Populate the specs with default value
374 # * Validate the instance specs
375 i_names = utils.NiceSort(instance_data.keys()) # pylint: disable-msg=E1103
377 specs = instance_data[name]
378 specs = _PopulateWithDefaults(specs)
381 hypervisor = specs['hypervisor']
382 hvparams = specs['hvparams']
385 for elem in specs['disk_size']:
387 size = utils.ParseUnit(elem)
388 except (TypeError, ValueError), err:
389 raise errors.OpPrereqError("Invalid disk size '%s' for"
391 (elem, name, err), errors.ECODE_INVAL)
392 disks.append({"size": size})
394 utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
395 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
398 for field in ('ip', 'mac', 'mode', 'link', 'bridge'):
402 tmp_nics[0][field] = specs[field]
404 if specs['nics'] is not None and tmp_nics:
405 raise errors.OpPrereqError("'nics' list incompatible with using"
406 " individual nic fields as well",
408 elif specs['nics'] is not None:
409 tmp_nics = specs['nics']
413 op = opcodes.OpInstanceCreate(instance_name=name,
415 disk_template=specs['template'],
416 mode=constants.INSTANCE_CREATE,
418 force_variant=specs["force_variant"],
419 pnode=specs['primary_node'],
420 snode=specs['secondary_node'],
422 start=specs['start'],
423 ip_check=specs['ip_check'],
424 name_check=specs['name_check'],
426 iallocator=specs['iallocator'],
427 hypervisor=hypervisor,
429 beparams=specs['backend'],
430 file_storage_dir=specs['file_storage_dir'],
431 file_driver=specs['file_driver'])
433 jex.QueueJob(name, op)
434 # we never want to wait, just show the submitted job IDs
435 jex.WaitOrShow(False)
440 def ReinstallInstance(opts, args):
441 """Reinstall an instance.
443 @param opts: the command line options selected by the user
445 @param args: should contain only one element, the name of the
446 instance to be reinstalled
448 @return: the desired exit code
451 # first, compute the desired name list
452 if opts.multi_mode is None:
453 opts.multi_mode = _SHUTDOWN_INSTANCES
455 inames = _ExpandMultiNames(opts.multi_mode, args)
457 raise errors.OpPrereqError("Selection filter does not match any instances",
460 # second, if requested, ask for an OS
461 if opts.select_os is True:
462 op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[])
463 result = SubmitOpCode(op, opts=opts)
466 ToStdout("Can't get the OS list")
469 ToStdout("Available OS templates:")
472 for (name, variants) in result:
473 for entry in CalculateOSNames(name, variants):
474 ToStdout("%3s: %s", number, entry)
475 choices.append(("%s" % number, entry, entry))
478 choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
479 selected = AskUser("Enter OS template number (or x to abort):",
482 if selected == 'exit':
483 ToStderr("User aborted reinstall, exiting")
490 # third, get confirmation: multi-reinstall requires --force-multi,
491 # single-reinstall either --force or --force-multi (--force-multi is
492 # a stronger --force)
493 multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
495 warn_msg = "Note: this will remove *all* data for the below instances!\n"
496 if not (opts.force_multi or
497 _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
500 if not (opts.force or opts.force_multi):
501 usertext = ("This will reinstall the instance %s and remove"
502 " all data. Continue?") % inames[0]
503 if not AskUser(usertext):
506 jex = JobExecutor(verbose=multi_on, opts=opts)
507 for instance_name in inames:
508 op = opcodes.OpInstanceReinstall(instance_name=instance_name,
510 force_variant=opts.force_variant,
511 osparams=opts.osparams)
512 jex.QueueJob(instance_name, op)
514 jex.WaitOrShow(not opts.submit_only)
518 def RemoveInstance(opts, args):
519 """Remove an instance.
521 @param opts: the command line options selected by the user
523 @param args: should contain only one element, the name of
524 the instance to be removed
526 @return: the desired exit code
529 instance_name = args[0]
534 _EnsureInstancesExist(cl, [instance_name])
536 usertext = ("This will remove the volumes of the instance %s"
537 " (including mirrors), thus removing all the data"
538 " of the instance. Continue?") % instance_name
539 if not AskUser(usertext):
542 op = opcodes.OpInstanceRemove(instance_name=instance_name,
543 ignore_failures=opts.ignore_failures,
544 shutdown_timeout=opts.shutdown_timeout)
545 SubmitOrSend(op, opts, cl=cl)
549 def RenameInstance(opts, args):
550 """Rename an instance.
552 @param opts: the command line options selected by the user
554 @param args: should contain two elements, the old and the
557 @return: the desired exit code
560 if not opts.name_check:
561 if not AskUser("As you disabled the check of the DNS entry, please verify"
562 " that '%s' is a FQDN. Continue?" % args[1]):
565 op = opcodes.OpInstanceRename(instance_name=args[0],
567 ip_check=opts.ip_check,
568 name_check=opts.name_check)
569 result = SubmitOrSend(op, opts)
572 ToStdout("Instance '%s' renamed to '%s'", args[0], result)
577 def ActivateDisks(opts, args):
578 """Activate an instance's disks.
580 This serves two purposes:
581 - it allows (as long as the instance is not running)
582 mounting the disks and modifying them from the node
583 - it repairs inactive secondary drbds
585 @param opts: the command line options selected by the user
587 @param args: should contain only one element, the instance name
589 @return: the desired exit code
592 instance_name = args[0]
593 op = opcodes.OpInstanceActivateDisks(instance_name=instance_name,
594 ignore_size=opts.ignore_size)
595 disks_info = SubmitOrSend(op, opts)
596 for host, iname, nname in disks_info:
597 ToStdout("%s:%s:%s", host, iname, nname)
601 def DeactivateDisks(opts, args):
602 """Deactivate an instance's disks.
604 This function takes the instance name, looks for its primary node
605 and the tries to shutdown its block devices on that node.
607 @param opts: the command line options selected by the user
609 @param args: should contain only one element, the instance name
611 @return: the desired exit code
614 instance_name = args[0]
615 op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name,
617 SubmitOrSend(op, opts)
621 def RecreateDisks(opts, args):
622 """Recreate an instance's disks.
624 @param opts: the command line options selected by the user
626 @param args: should contain only one element, the instance name
628 @return: the desired exit code
631 instance_name = args[0]
634 opts.disks = [int(v) for v in opts.disks.split(",")]
635 except (ValueError, TypeError), err:
636 ToStderr("Invalid disks value: %s" % str(err))
641 op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
643 SubmitOrSend(op, opts)
647 def GrowDisk(opts, args):
648 """Grow an instance's disks.
650 @param opts: the command line options selected by the user
652 @param args: should contain two elements, the instance name
653 whose disks we grow and the disk name, e.g. I{sda}
655 @return: the desired exit code
662 except (TypeError, ValueError), err:
663 raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
665 amount = utils.ParseUnit(args[2])
666 op = opcodes.OpInstanceGrowDisk(instance_name=instance,
667 disk=disk, amount=amount,
668 wait_for_sync=opts.wait_for_sync)
669 SubmitOrSend(op, opts)
673 def _StartupInstance(name, opts):
674 """Startup instances.
676 This returns the opcode to start an instance, and its decorator will
677 wrap this into a loop starting all desired instances.
679 @param name: the name of the instance to act on
680 @param opts: the command line options selected by the user
681 @return: the opcode needed for the operation
684 op = opcodes.OpInstanceStartup(instance_name=name,
686 ignore_offline_nodes=opts.ignore_offline)
687 # do not add these parameters to the opcode unless they're defined
689 op.hvparams = opts.hvparams
691 op.beparams = opts.beparams
695 def _RebootInstance(name, opts):
696 """Reboot instance(s).
698 This returns the opcode to reboot an instance, and its decorator
699 will wrap this into a loop rebooting all desired instances.
701 @param name: the name of the instance to act on
702 @param opts: the command line options selected by the user
703 @return: the opcode needed for the operation
706 return opcodes.OpInstanceReboot(instance_name=name,
707 reboot_type=opts.reboot_type,
708 ignore_secondaries=opts.ignore_secondaries,
709 shutdown_timeout=opts.shutdown_timeout)
712 def _ShutdownInstance(name, opts):
713 """Shutdown an instance.
715 This returns the opcode to shutdown an instance, and its decorator
716 will wrap this into a loop shutting down all desired instances.
718 @param name: the name of the instance to act on
719 @param opts: the command line options selected by the user
720 @return: the opcode needed for the operation
723 return opcodes.OpInstanceShutdown(instance_name=name,
724 timeout=opts.timeout,
725 ignore_offline_nodes=opts.ignore_offline)
728 def ReplaceDisks(opts, args):
729 """Replace the disks of an instance
731 @param opts: the command line options selected by the user
733 @param args: should contain only one element, the instance name
735 @return: the desired exit code
738 new_2ndary = opts.dst_node
739 iallocator = opts.iallocator
740 if opts.disks is None:
744 disks = [int(i) for i in opts.disks.split(",")]
745 except (TypeError, ValueError), err:
746 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
748 cnt = [opts.on_primary, opts.on_secondary, opts.auto,
749 new_2ndary is not None, iallocator is not None].count(True)
751 raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -I"
752 " options must be passed", errors.ECODE_INVAL)
753 elif opts.on_primary:
754 mode = constants.REPLACE_DISK_PRI
755 elif opts.on_secondary:
756 mode = constants.REPLACE_DISK_SEC
758 mode = constants.REPLACE_DISK_AUTO
760 raise errors.OpPrereqError("Cannot specify disks when using automatic"
761 " mode", errors.ECODE_INVAL)
762 elif new_2ndary is not None or iallocator is not None:
764 mode = constants.REPLACE_DISK_CHG
766 op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
767 remote_node=new_2ndary, mode=mode,
768 iallocator=iallocator,
769 early_release=opts.early_release)
770 SubmitOrSend(op, opts)
774 def FailoverInstance(opts, args):
775 """Failover an instance.
777 The failover is done by shutting it down on its present node and
778 starting it on the secondary.
780 @param opts: the command line options selected by the user
782 @param args: should contain only one element, the instance name
784 @return: the desired exit code
788 instance_name = args[0]
792 _EnsureInstancesExist(cl, [instance_name])
794 usertext = ("Failover will happen to image %s."
795 " This requires a shutdown of the instance. Continue?" %
797 if not AskUser(usertext):
800 op = opcodes.OpInstanceFailover(instance_name=instance_name,
801 ignore_consistency=opts.ignore_consistency,
802 shutdown_timeout=opts.shutdown_timeout)
803 SubmitOrSend(op, opts, cl=cl)
807 def MigrateInstance(opts, args):
808 """Migrate an instance.
810 The migrate is done without shutdown.
812 @param opts: the command line options selected by the user
814 @param args: should contain only one element, the instance name
816 @return: the desired exit code
820 instance_name = args[0]
824 _EnsureInstancesExist(cl, [instance_name])
827 usertext = ("Instance %s will be recovered from a failed migration."
828 " Note that the migration procedure (including cleanup)" %
831 usertext = ("Instance %s will be migrated. Note that migration" %
833 usertext += (" might impact the instance if anything goes wrong"
834 " (e.g. due to bugs in the hypervisor). Continue?")
835 if not AskUser(usertext):
838 # this should be removed once --non-live is deprecated
839 if not opts.live and opts.migration_mode is not None:
840 raise errors.OpPrereqError("Only one of the --non-live and "
841 "--migration-mode options can be passed",
843 if not opts.live: # --non-live passed
844 mode = constants.HT_MIGRATION_NONLIVE
846 mode = opts.migration_mode
848 op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
849 cleanup=opts.cleanup)
850 SubmitOpCode(op, cl=cl, opts=opts)
854 def MoveInstance(opts, args):
857 @param opts: the command line options selected by the user
859 @param args: should contain only one element, the instance name
861 @return: the desired exit code
865 instance_name = args[0]
869 usertext = ("Instance %s will be moved."
870 " This requires a shutdown of the instance. Continue?" %
872 if not AskUser(usertext):
875 op = opcodes.OpInstanceMove(instance_name=instance_name,
876 target_node=opts.node,
877 shutdown_timeout=opts.shutdown_timeout)
878 SubmitOrSend(op, opts, cl=cl)
882 def ConnectToInstanceConsole(opts, args):
883 """Connect to the console of an instance.
885 @param opts: the command line options selected by the user
887 @param args: should contain only one element, the instance name
889 @return: the desired exit code
892 instance_name = args[0]
894 op = opcodes.OpInstanceConsole(instance_name=instance_name)
898 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
899 console_data = SubmitOpCode(op, opts=opts, cl=cl)
901 # Ensure client connection is closed while external commands are run
906 return _DoConsole(objects.InstanceConsole.FromDict(console_data),
907 opts.show_command, cluster_name)
910 def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
911 _runcmd_fn=utils.RunCmd):
912 """Acts based on the result of L{opcodes.OpInstanceConsole}.
914 @type console: L{objects.InstanceConsole}
915 @param console: Console object
916 @type show_command: bool
917 @param show_command: Whether to just display commands
918 @type cluster_name: string
919 @param cluster_name: Cluster name as retrieved from master daemon
922 assert console.Validate()
924 if console.kind == constants.CONS_MESSAGE:
925 feedback_fn(console.message)
926 elif console.kind == constants.CONS_VNC:
927 feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
928 " URL <vnc://%s:%s/>",
929 console.instance, console.host, console.port,
930 console.display, console.host, console.port)
931 elif console.kind == constants.CONS_SSH:
932 # Convert to string if not already one
933 if isinstance(console.command, basestring):
934 cmd = console.command
936 cmd = utils.ShellQuoteArgs(console.command)
938 srun = ssh.SshRunner(cluster_name=cluster_name)
939 ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
940 batch=True, quiet=False, tty=True)
943 feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
945 result = _runcmd_fn(ssh_cmd, interactive=True)
947 logging.error("Console command \"%s\" failed with reason '%s' and"
948 " output %r", result.cmd, result.fail_reason,
950 raise errors.OpExecError("Connection to console of instance %s failed,"
951 " please check cluster configuration" %
954 raise errors.GenericError("Unknown console type '%s'" % console.kind)
956 return constants.EXIT_SUCCESS
959 def _FormatLogicalID(dev_type, logical_id, roman):
960 """Formats the logical_id of a disk.
963 if dev_type == constants.LD_DRBD8:
964 node_a, node_b, port, minor_a, minor_b, key = logical_id
966 ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
968 ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
970 ("port", compat.TryToRoman(port, convert=roman)),
973 elif dev_type == constants.LD_LV:
974 vg_name, lv_name = logical_id
975 data = ["%s/%s" % (vg_name, lv_name)]
977 data = [str(logical_id)]
982 def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
983 """Show block device information.
985 This is only used by L{ShowInstanceConfig}, but it's too big to be
986 left for an inline definition.
989 @param idx: the index of the current disk
990 @type top_level: boolean
991 @param top_level: if this a top-level disk?
993 @param dev: dictionary with disk information
994 @type static: boolean
995 @param static: wheter the device information doesn't contain
996 runtime information but only static data
998 @param roman: whether to try to use roman integers
999 @return: a list of either strings, tuples or lists
1000 (which should be formatted at a higher indent level)
1003 def helper(dtype, status):
1004 """Format one line for physical device status.
1007 @param dtype: a constant from the L{constants.LDS_BLOCK} set
1009 @param status: a tuple as returned from L{backend.FindBlockDevice}
1010 @return: the string representing the status
1016 (path, major, minor, syncp, estt, degr, ldisk_status) = status
1018 major_string = "N/A"
1020 major_string = str(compat.TryToRoman(major, convert=roman))
1023 minor_string = "N/A"
1025 minor_string = str(compat.TryToRoman(minor, convert=roman))
1027 txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1028 if dtype in (constants.LD_DRBD8, ):
1029 if syncp is not None:
1030 sync_text = "*RECOVERING* %5.2f%%," % syncp
1032 sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1034 sync_text += " ETA unknown"
1036 sync_text = "in sync"
1038 degr_text = "*DEGRADED*"
1041 if ldisk_status == constants.LDS_FAULTY:
1042 ldisk_text = " *MISSING DISK*"
1043 elif ldisk_status == constants.LDS_UNKNOWN:
1044 ldisk_text = " *UNCERTAIN STATE*"
1047 txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1048 elif dtype == constants.LD_LV:
1049 if ldisk_status == constants.LDS_FAULTY:
1050 ldisk_text = " *FAILED* (failed drive?)"
1058 if dev["iv_name"] is not None:
1059 txt = dev["iv_name"]
1061 txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1063 txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1064 if isinstance(dev["size"], int):
1065 nice_size = utils.FormatUnit(dev["size"], "h")
1067 nice_size = dev["size"]
1068 d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1071 data.append(("access mode", dev["mode"]))
1072 if dev["logical_id"] is not None:
1074 l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1076 l_id = [str(dev["logical_id"])]
1078 data.append(("logical_id", l_id[0]))
1081 elif dev["physical_id"] is not None:
1082 data.append("physical_id:")
1083 data.append([dev["physical_id"]])
1085 data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1086 if dev["sstatus"] and not static:
1087 data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1090 data.append("child devices:")
1091 for c_idx, child in enumerate(dev["children"]):
1092 data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
1097 def _FormatList(buf, data, indent_level):
1098 """Formats a list of data at a given indent level.
1100 If the element of the list is:
1101 - a string, it is simply formatted as is
1102 - a tuple, it will be split into key, value and the all the
1103 values in a list will be aligned all at the same start column
1104 - a list, will be recursively formatted
1107 @param buf: the buffer into which we write the output
1108 @param data: the list to format
1109 @type indent_level: int
1110 @param indent_level: the indent level to format at
1113 max_tlen = max([len(elem[0]) for elem in data
1114 if isinstance(elem, tuple)] or [0])
1116 if isinstance(elem, basestring):
1117 buf.write("%*s%s\n" % (2*indent_level, "", elem))
1118 elif isinstance(elem, tuple):
1120 spacer = "%*s" % (max_tlen - len(key), "")
1121 buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1122 elif isinstance(elem, list):
1123 _FormatList(buf, elem, indent_level+1)
1126 def ShowInstanceConfig(opts, args):
1127 """Compute instance run-time status.
1129 @param opts: the command line options selected by the user
1131 @param args: either an empty list, and then we query all
1132 instances, or should contain a list of instance names
1134 @return: the desired exit code
1137 if not args and not opts.show_all:
1138 ToStderr("No instance selected."
1139 " Please pass in --all if you want to query all instances.\n"
1140 "Note that this can take a long time on a big cluster.")
1142 elif args and opts.show_all:
1143 ToStderr("Cannot use --all if you specify instance names.")
1147 op = opcodes.OpInstanceQueryData(instances=args, static=opts.static,
1148 use_locking=not opts.static)
1149 result = SubmitOpCode(op, opts=opts)
1151 ToStdout("No instances.")
1156 for instance_name in result:
1157 instance = result[instance_name]
1158 buf.write("Instance name: %s\n" % instance["name"])
1159 buf.write("UUID: %s\n" % instance["uuid"])
1160 buf.write("Serial number: %s\n" %
1161 compat.TryToRoman(instance["serial_no"],
1162 convert=opts.roman_integers))
1163 buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1164 buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1165 buf.write("State: configured to be %s" % instance["config_state"])
1167 buf.write(", actual state is %s" % instance["run_state"])
1169 ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1170 ## instance["auto_balance"])
1171 buf.write(" Nodes:\n")
1172 buf.write(" - primary: %s\n" % instance["pnode"])
1173 buf.write(" - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1174 buf.write(" Operating system: %s\n" % instance["os"])
1175 FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1177 if instance.has_key("network_port"):
1178 buf.write(" Allocated network port: %s\n" %
1179 compat.TryToRoman(instance["network_port"],
1180 convert=opts.roman_integers))
1181 buf.write(" Hypervisor: %s\n" % instance["hypervisor"])
1183 # custom VNC console information
1184 vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1186 if vnc_bind_address:
1187 port = instance["network_port"]
1188 display = int(port) - constants.VNC_BASE_PORT
1189 if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1190 vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1193 elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1194 vnc_console_port = ("%s:%s (node %s) (display %s)" %
1195 (vnc_bind_address, port,
1196 instance["pnode"], display))
1198 # vnc bind address is a file
1199 vnc_console_port = "%s:%s" % (instance["pnode"],
1201 buf.write(" - console connection: vnc to %s\n" % vnc_console_port)
1203 FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1205 buf.write(" Hardware:\n")
1206 buf.write(" - VCPUs: %s\n" %
1207 compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1208 convert=opts.roman_integers))
1209 buf.write(" - memory: %sMiB\n" %
1210 compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
1211 convert=opts.roman_integers))
1212 buf.write(" - NICs:\n")
1213 for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1214 buf.write(" - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1215 (idx, mac, ip, mode, link))
1216 buf.write(" Disk template: %s\n" % instance["disk_template"])
1217 buf.write(" Disks:\n")
1219 for idx, device in enumerate(instance["disks"]):
1220 _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static,
1221 opts.roman_integers), 2)
1223 ToStdout(buf.getvalue().rstrip('\n'))
1227 def SetInstanceParams(opts, args):
1228 """Modifies an instance.
1230 All parameters take effect only at the next restart of the instance.
1232 @param opts: the command line options selected by the user
1234 @param args: should contain only one element, the instance name
1236 @return: the desired exit code
1239 if not (opts.nics or opts.disks or opts.disk_template or
1240 opts.hvparams or opts.beparams or opts.os or opts.osparams):
1241 ToStderr("Please give at least one of the parameters.")
1244 for param in opts.beparams:
1245 if isinstance(opts.beparams[param], basestring):
1246 if opts.beparams[param].lower() == "default":
1247 opts.beparams[param] = constants.VALUE_DEFAULT
1249 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1250 allowed_values=[constants.VALUE_DEFAULT])
1252 for param in opts.hvparams:
1253 if isinstance(opts.hvparams[param], basestring):
1254 if opts.hvparams[param].lower() == "default":
1255 opts.hvparams[param] = constants.VALUE_DEFAULT
1257 utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1258 allowed_values=[constants.VALUE_DEFAULT])
1260 for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1262 nic_op = int(nic_op)
1263 opts.nics[idx] = (nic_op, nic_dict)
1264 except (TypeError, ValueError):
1267 for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1269 disk_op = int(disk_op)
1270 opts.disks[idx] = (disk_op, disk_dict)
1271 except (TypeError, ValueError):
1273 if disk_op == constants.DDM_ADD:
1274 if 'size' not in disk_dict:
1275 raise errors.OpPrereqError("Missing required parameter 'size'",
1277 disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1279 if (opts.disk_template and
1280 opts.disk_template in constants.DTS_NET_MIRROR and
1282 ToStderr("Changing the disk template to a mirrored one requires"
1283 " specifying a secondary node")
1286 op = opcodes.OpInstanceSetParams(instance_name=args[0],
1289 disk_template=opts.disk_template,
1290 remote_node=opts.node,
1291 hvparams=opts.hvparams,
1292 beparams=opts.beparams,
1294 osparams=opts.osparams,
1295 force_variant=opts.force_variant,
1298 # even if here we process the result, we allow submit only
1299 result = SubmitOrSend(op, opts)
1302 ToStdout("Modified instance %s", args[0])
1303 for param, data in result:
1304 ToStdout(" - %-5s -> %s", param, data)
1305 ToStdout("Please don't forget that most parameters take effect"
1306 " only at the next start of the instance.")
1310 # multi-instance selection options
1311 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1312 help="Do not ask for confirmation when more than"
1313 " one instance is affected",
1314 action="store_true", default=False)
1316 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1317 help="Filter by nodes (primary only)",
1318 const=_SHUTDOWN_NODES_PRI, action="store_const")
1320 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1321 help="Filter by nodes (secondary only)",
1322 const=_SHUTDOWN_NODES_SEC, action="store_const")
1324 m_node_opt = cli_option("--node", dest="multi_mode",
1325 help="Filter by nodes (primary and secondary)",
1326 const=_SHUTDOWN_NODES_BOTH, action="store_const")
1328 m_clust_opt = cli_option("--all", dest="multi_mode",
1329 help="Select all instances in the cluster",
1330 const=_SHUTDOWN_CLUSTER, action="store_const")
1332 m_inst_opt = cli_option("--instance", dest="multi_mode",
1333 help="Filter by instance name [default]",
1334 const=_SHUTDOWN_INSTANCES, action="store_const")
1336 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1337 help="Filter by node tag",
1338 const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1339 action="store_const")
1341 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1342 help="Filter by primary node tag",
1343 const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1344 action="store_const")
1346 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1347 help="Filter by secondary node tag",
1348 const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1349 action="store_const")
1351 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1352 help="Filter by instance tag",
1353 const=_SHUTDOWN_INSTANCES_BY_TAGS,
1354 action="store_const")
1356 # this is defined separately due to readability only
1366 AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1367 "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1368 "Creates and adds a new instance to the cluster"),
1370 BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1372 "Create a bunch of instances based on specs in the file."),
1374 ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1375 [SHOWCMD_OPT, PRIORITY_OPT],
1376 "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1378 FailoverInstance, ARGS_ONE_INSTANCE,
1379 [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1380 DRY_RUN_OPT, PRIORITY_OPT],
1381 "[-f] <instance>", "Stops the instance and starts it on the backup node,"
1382 " using the remote mirror (only for instances of type drbd)"),
1384 MigrateInstance, ARGS_ONE_INSTANCE,
1385 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1387 "[-f] <instance>", "Migrate instance to its secondary node"
1388 " (only for instances of type drbd)"),
1390 MoveInstance, ARGS_ONE_INSTANCE,
1391 [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1392 DRY_RUN_OPT, PRIORITY_OPT],
1393 "[-f] <instance>", "Move instance to an arbitrary node"
1394 " (only for instances of type file and lv)"),
1396 ShowInstanceConfig, ARGS_MANY_INSTANCES,
1397 [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1398 "[-s] {--all | <instance>...}",
1399 "Show information on the specified instance(s)"),
1401 ListInstances, ARGS_MANY_INSTANCES,
1402 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT],
1404 "Lists the instances and their status. The available fields can be shown"
1405 " using the \"list-fields\" command (see the man page for details)."
1406 " The default field list is (in order): %s." %
1407 utils.CommaJoin(_LIST_DEF_FIELDS),
1410 ListInstanceFields, [ArgUnknown()],
1411 [NOHDR_OPT, SEP_OPT],
1413 "Lists all available fields for instances"),
1415 ReinstallInstance, [ArgInstance()],
1416 [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1417 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1418 m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1419 SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1420 "[-f] <instance>", "Reinstall a stopped instance"),
1422 RemoveInstance, ARGS_ONE_INSTANCE,
1423 [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1424 DRY_RUN_OPT, PRIORITY_OPT],
1425 "[-f] <instance>", "Shuts down the instance and removes it"),
1428 [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1429 [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1430 "<instance> <new_name>", "Rename the instance"),
1432 ReplaceDisks, ARGS_ONE_INSTANCE,
1433 [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1434 NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1435 DRY_RUN_OPT, PRIORITY_OPT],
1436 "[-s|-p|-n NODE|-I NAME] <instance>",
1437 "Replaces all disks for the instance"),
1439 SetInstanceParams, ARGS_ONE_INSTANCE,
1440 [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1441 DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1442 OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1443 "<instance>", "Alters the parameters of an instance"),
1445 GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1446 [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1447 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1448 m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1449 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1450 "<instance>", "Stops an instance"),
1452 GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1453 [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1454 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1455 m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1456 BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1457 "<instance>", "Starts an instance"),
1459 GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1460 [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1461 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1462 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1463 m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1464 "<instance>", "Reboots an instance"),
1466 ActivateDisks, ARGS_ONE_INSTANCE,
1467 [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
1468 "<instance>", "Activate an instance's disks"),
1469 'deactivate-disks': (
1470 DeactivateDisks, ARGS_ONE_INSTANCE,
1471 [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1472 "[-f] <instance>", "Deactivate an instance's disks"),
1474 RecreateDisks, ARGS_ONE_INSTANCE,
1475 [SUBMIT_OPT, DISKIDX_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1476 "<instance>", "Recreate an instance's disks"),
1479 [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1480 ArgUnknown(min=1, max=1)],
1481 [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1482 "<instance> <disk> <size>", "Grow an instance's disk"),
1484 ListTags, ARGS_ONE_INSTANCE, [PRIORITY_OPT],
1485 "<instance_name>", "List the tags of the given instance"),
1487 AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1488 [TAG_SRC_OPT, PRIORITY_OPT],
1489 "<instance_name> tag...", "Add tags to the given instance"),
1491 RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1492 [TAG_SRC_OPT, PRIORITY_OPT],
1493 "<instance_name> tag...", "Remove tags from given instance"),
1496 #: dictionary with aliases for commands
1504 return GenericMain(commands, aliases=aliases,
1505 override={"tag_type": constants.TAG_INSTANCE})