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)
62 #: default list of options for L{ListInstances}
64 "name", "hypervisor", "os", "pnode", "status", "oper_ram",
68 def _ExpandMultiNames(mode, names, client=None):
69 """Expand the given names using the passed mode.
71 For _SHUTDOWN_CLUSTER, all instances will be returned. For
72 _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
73 primary/secondary will be returned. For _SHUTDOWN_NODES_BOTH, all
74 instances having those nodes as either primary or secondary will be
75 returned. For _SHUTDOWN_INSTANCES, the given instances will be
78 @param mode: one of L{_SHUTDOWN_CLUSTER}, L{_SHUTDOWN_NODES_BOTH},
79 L{_SHUTDOWN_NODES_PRI}, L{_SHUTDOWN_NODES_SEC} or
80 L{_SHUTDOWN_INSTANCES}
81 @param names: a list of names; for cluster, it must be empty,
82 and for node and instance it must be a list of valid item
83 names (short names are valid as usual, e.g. node1 instead of
86 @return: the list of names after the expansion
87 @raise errors.ProgrammerError: for unknown selection type
88 @raise errors.OpPrereqError: for invalid input parameters
91 # pylint: disable-msg=W0142
95 if mode == _SHUTDOWN_CLUSTER:
97 raise errors.OpPrereqError("Cluster filter mode takes no arguments",
99 idata = client.QueryInstances([], ["name"], False)
100 inames = [row[0] for row in idata]
102 elif mode in (_SHUTDOWN_NODES_BOTH,
104 _SHUTDOWN_NODES_SEC) + _SHUTDOWN_NODES_TAGS_MODES:
105 if mode in _SHUTDOWN_NODES_TAGS_MODES:
107 raise errors.OpPrereqError("No node tags passed", errors.ECODE_INVAL)
108 ndata = client.QueryNodes([], ["name", "pinst_list",
109 "sinst_list", "tags"], False)
110 ndata = [row for row in ndata if set(row[3]).intersection(names)]
113 raise errors.OpPrereqError("No node names passed", errors.ECODE_INVAL)
114 ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
117 ipri = [row[1] for row in ndata]
118 pri_names = list(itertools.chain(*ipri))
119 isec = [row[2] for row in ndata]
120 sec_names = list(itertools.chain(*isec))
121 if mode in (_SHUTDOWN_NODES_BOTH, _SHUTDOWN_NODES_BOTH_BY_TAGS):
122 inames = pri_names + sec_names
123 elif mode in (_SHUTDOWN_NODES_PRI, _SHUTDOWN_NODES_PRI_BY_TAGS):
125 elif mode in (_SHUTDOWN_NODES_SEC, _SHUTDOWN_NODES_SEC_BY_TAGS):
128 raise errors.ProgrammerError("Unhandled shutdown type")
129 elif mode == _SHUTDOWN_INSTANCES:
131 raise errors.OpPrereqError("No instance names passed",
133 idata = client.QueryInstances(names, ["name"], False)
134 inames = [row[0] for row in idata]
135 elif mode == _SHUTDOWN_INSTANCES_BY_TAGS:
137 raise errors.OpPrereqError("No instance tags passed",
139 idata = client.QueryInstances([], ["name", "tags"], False)
140 inames = [row[0] for row in idata if set(row[1]).intersection(names)]
142 raise errors.OpPrereqError("Unknown mode '%s'" % mode, errors.ECODE_INVAL)
147 def _ConfirmOperation(inames, text, extra=""):
148 """Ask the user to confirm an operation on a list of instances.
150 This function is used to request confirmation for doing an operation
151 on a given list of instances.
154 @param inames: the list of names that we display when
155 we ask for confirmation
157 @param text: the operation that the user should confirm
158 (e.g. I{shutdown} or I{startup})
160 @return: True or False depending on user's confirmation.
164 msg = ("The %s will operate on %d instances.\n%s"
165 "Do you want to continue?" % (text, count, extra))
166 affected = ("\nAffected instances:\n" +
167 "\n".join([" %s" % name for name in inames]))
169 choices = [('y', True, 'Yes, execute the %s' % text),
170 ('n', False, 'No, abort the %s' % text)]
173 choices.insert(1, ('v', 'v', 'View the list of affected instances'))
178 choice = AskUser(ask, choices)
181 choice = AskUser(msg + affected, choices)
185 def _EnsureInstancesExist(client, names):
186 """Check for and ensure the given instance names exist.
188 This function will raise an OpPrereqError in case they don't
189 exist. Otherwise it will exit cleanly.
191 @type client: L{ganeti.luxi.Client}
192 @param client: the client to use for the query
194 @param names: the list of instance names to query
195 @raise errors.OpPrereqError: in case any instance is missing
198 # TODO: change LUInstanceQuery to that it actually returns None
199 # instead of raising an exception, or devise a better mechanism
200 result = client.QueryInstances(names, ["name"], False)
201 for orig_name, row in zip(names, result):
203 raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name,
207 def GenericManyOps(operation, fn):
208 """Generic multi-instance operations.
210 The will return a wrapper that processes the options and arguments
211 given, and uses the passed function to build the opcode needed for
212 the specific operation. Thus all the generic loop/confirmation code
213 is abstracted into this function.
216 def realfn(opts, args):
217 if opts.multi_mode is None:
218 opts.multi_mode = _SHUTDOWN_INSTANCES
220 inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
222 raise errors.OpPrereqError("Selection filter does not match"
223 " any instances", errors.ECODE_INVAL)
224 multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
225 if not (opts.force_multi or not multi_on
226 or _ConfirmOperation(inames, operation)):
228 jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
231 jex.QueueJob(name, op)
232 results = jex.WaitOrShow(not opts.submit_only)
233 rcode = compat.all(row[0] for row in results)
234 return int(not rcode)
238 def ListInstances(opts, args):
239 """List instances and their properties.
241 @param opts: the command line options selected by the user
243 @param args: should be an empty list
245 @return: the desired exit code
248 selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
250 fmtoverride = dict.fromkeys(["tags", "disk.sizes", "nic.macs", "nic.ips",
251 "nic.modes", "nic.links", "nic.bridges",
253 (lambda value: ",".join(str(item)
257 return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units,
258 opts.separator, not opts.no_headers,
259 format_override=fmtoverride)
262 def ListInstanceFields(opts, args):
263 """List instance fields.
265 @param opts: the command line options selected by the user
267 @param args: fields to list, or empty for all
269 @return: the desired exit code
272 return GenericListFields(constants.QR_INSTANCE, args, opts.separator,
276 def AddInstance(opts, args):
277 """Add an instance to the cluster.
279 This is just a wrapper over GenericInstanceCreate.
282 return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
285 def BatchCreate(opts, args):
286 """Create instances using a definition file.
288 This function reads a json file with instances defined
292 "disk_size": [20480],
298 "primary_node": "firstnode",
299 "secondary_node": "secondnode",
300 "iallocator": "dumb"}
303 Note that I{primary_node} and I{secondary_node} have precedence over
306 @param opts: the command line options selected by the user
308 @param args: should contain one element, the json filename
310 @return: the desired exit code
313 _DEFAULT_SPECS = {"disk_size": [20 * 1024],
316 "primary_node": None,
317 "secondary_node": None,
324 "file_storage_dir": None,
325 "force_variant": False,
326 "file_driver": 'loop'}
328 def _PopulateWithDefaults(spec):
329 """Returns a new hash combined with default values."""
330 mydict = _DEFAULT_SPECS.copy()
335 """Validate the instance specs."""
336 # Validate fields required under any circumstances
337 for required_field in ('os', 'template'):
338 if required_field not in spec:
339 raise errors.OpPrereqError('Required field "%s" is missing.' %
340 required_field, errors.ECODE_INVAL)
341 # Validate special fields
342 if spec['primary_node'] is not None:
343 if (spec['template'] in constants.DTS_NET_MIRROR and
344 spec['secondary_node'] is None):
345 raise errors.OpPrereqError('Template requires secondary node, but'
346 ' there was no secondary provided.',
348 elif spec['iallocator'] is None:
349 raise errors.OpPrereqError('You have to provide at least a primary_node'
350 ' or an iallocator.',
353 if (spec['hvparams'] and
354 not isinstance(spec['hvparams'], dict)):
355 raise errors.OpPrereqError('Hypervisor parameters must be a dict.',
358 json_filename = args[0]
360 instance_data = simplejson.loads(utils.ReadFile(json_filename))
361 except Exception, err: # pylint: disable-msg=W0703
362 ToStderr("Can't parse the instance definition file: %s" % str(err))
365 if not isinstance(instance_data, dict):
366 ToStderr("The instance definition file is not in dict format.")
369 jex = JobExecutor(opts=opts)
371 # Iterate over the instances and do:
372 # * Populate the specs with default value
373 # * Validate the instance specs
374 i_names = utils.NiceSort(instance_data.keys()) # pylint: disable-msg=E1103
376 specs = instance_data[name]
377 specs = _PopulateWithDefaults(specs)
380 hypervisor = specs['hypervisor']
381 hvparams = specs['hvparams']
384 for elem in specs['disk_size']:
386 size = utils.ParseUnit(elem)
387 except (TypeError, ValueError), err:
388 raise errors.OpPrereqError("Invalid disk size '%s' for"
390 (elem, name, err), errors.ECODE_INVAL)
391 disks.append({"size": size})
393 utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
394 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
397 for field in ('ip', 'mac', 'mode', 'link', 'bridge'):
401 tmp_nics[0][field] = specs[field]
403 if specs['nics'] is not None and tmp_nics:
404 raise errors.OpPrereqError("'nics' list incompatible with using"
405 " individual nic fields as well",
407 elif specs['nics'] is not None:
408 tmp_nics = specs['nics']
412 op = opcodes.OpInstanceCreate(instance_name=name,
414 disk_template=specs['template'],
415 mode=constants.INSTANCE_CREATE,
417 force_variant=specs["force_variant"],
418 pnode=specs['primary_node'],
419 snode=specs['secondary_node'],
421 start=specs['start'],
422 ip_check=specs['ip_check'],
423 name_check=specs['name_check'],
425 iallocator=specs['iallocator'],
426 hypervisor=hypervisor,
428 beparams=specs['backend'],
429 file_storage_dir=specs['file_storage_dir'],
430 file_driver=specs['file_driver'])
432 jex.QueueJob(name, op)
433 # we never want to wait, just show the submitted job IDs
434 jex.WaitOrShow(False)
439 def ReinstallInstance(opts, args):
440 """Reinstall an instance.
442 @param opts: the command line options selected by the user
444 @param args: should contain only one element, the name of the
445 instance to be reinstalled
447 @return: the desired exit code
450 # first, compute the desired name list
451 if opts.multi_mode is None:
452 opts.multi_mode = _SHUTDOWN_INSTANCES
454 inames = _ExpandMultiNames(opts.multi_mode, args)
456 raise errors.OpPrereqError("Selection filter does not match any instances",
459 # second, if requested, ask for an OS
460 if opts.select_os is True:
461 op = opcodes.OpDiagnoseOS(output_fields=["name", "variants"], names=[])
462 result = SubmitOpCode(op, opts=opts)
465 ToStdout("Can't get the OS list")
468 ToStdout("Available OS templates:")
471 for (name, variants) in result:
472 for entry in CalculateOSNames(name, variants):
473 ToStdout("%3s: %s", number, entry)
474 choices.append(("%s" % number, entry, entry))
477 choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
478 selected = AskUser("Enter OS template number (or x to abort):",
481 if selected == 'exit':
482 ToStderr("User aborted reinstall, exiting")
489 # third, get confirmation: multi-reinstall requires --force-multi,
490 # single-reinstall either --force or --force-multi (--force-multi is
491 # a stronger --force)
492 multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
494 warn_msg = "Note: this will remove *all* data for the below instances!\n"
495 if not (opts.force_multi or
496 _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
499 if not (opts.force or opts.force_multi):
500 usertext = ("This will reinstall the instance %s and remove"
501 " all data. Continue?") % inames[0]
502 if not AskUser(usertext):
505 jex = JobExecutor(verbose=multi_on, opts=opts)
506 for instance_name in inames:
507 op = opcodes.OpReinstallInstance(instance_name=instance_name,
509 force_variant=opts.force_variant,
510 osparams=opts.osparams)
511 jex.QueueJob(instance_name, op)
513 jex.WaitOrShow(not opts.submit_only)
517 def RemoveInstance(opts, args):
518 """Remove an instance.
520 @param opts: the command line options selected by the user
522 @param args: should contain only one element, the name of
523 the instance to be removed
525 @return: the desired exit code
528 instance_name = args[0]
533 _EnsureInstancesExist(cl, [instance_name])
535 usertext = ("This will remove the volumes of the instance %s"
536 " (including mirrors), thus removing all the data"
537 " of the instance. Continue?") % instance_name
538 if not AskUser(usertext):
541 op = opcodes.OpRemoveInstance(instance_name=instance_name,
542 ignore_failures=opts.ignore_failures,
543 shutdown_timeout=opts.shutdown_timeout)
544 SubmitOrSend(op, opts, cl=cl)
548 def RenameInstance(opts, args):
549 """Rename an instance.
551 @param opts: the command line options selected by the user
553 @param args: should contain two elements, the old and the
556 @return: the desired exit code
559 if not opts.name_check:
560 if not AskUser("As you disabled the check of the DNS entry, please verify"
561 " that '%s' is a FQDN. Continue?" % args[1]):
564 op = opcodes.OpRenameInstance(instance_name=args[0],
566 ip_check=opts.ip_check,
567 name_check=opts.name_check)
568 result = SubmitOrSend(op, opts)
571 ToStdout("Instance '%s' renamed to '%s'", args[0], result)
576 def ActivateDisks(opts, args):
577 """Activate an instance's disks.
579 This serves two purposes:
580 - it allows (as long as the instance is not running)
581 mounting the disks and modifying them from the node
582 - it repairs inactive secondary drbds
584 @param opts: the command line options selected by the user
586 @param args: should contain only one element, the instance name
588 @return: the desired exit code
591 instance_name = args[0]
592 op = opcodes.OpInstanceActivateDisks(instance_name=instance_name,
593 ignore_size=opts.ignore_size)
594 disks_info = SubmitOrSend(op, opts)
595 for host, iname, nname in disks_info:
596 ToStdout("%s:%s:%s", host, iname, nname)
600 def DeactivateDisks(opts, args):
601 """Deactivate an instance's disks.
603 This function takes the instance name, looks for its primary node
604 and the tries to shutdown its block devices on that node.
606 @param opts: the command line options selected by the user
608 @param args: should contain only one element, the instance name
610 @return: the desired exit code
613 instance_name = args[0]
614 op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name)
615 SubmitOrSend(op, opts)
619 def RecreateDisks(opts, args):
620 """Recreate an instance's disks.
622 @param opts: the command line options selected by the user
624 @param args: should contain only one element, the instance name
626 @return: the desired exit code
629 instance_name = args[0]
632 opts.disks = [int(v) for v in opts.disks.split(",")]
633 except (ValueError, TypeError), err:
634 ToStderr("Invalid disks value: %s" % str(err))
639 op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
641 SubmitOrSend(op, opts)
645 def GrowDisk(opts, args):
646 """Grow an instance's disks.
648 @param opts: the command line options selected by the user
650 @param args: should contain two elements, the instance name
651 whose disks we grow and the disk name, e.g. I{sda}
653 @return: the desired exit code
660 except (TypeError, ValueError), err:
661 raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
663 amount = utils.ParseUnit(args[2])
664 op = opcodes.OpInstanceGrowDisk(instance_name=instance,
665 disk=disk, amount=amount,
666 wait_for_sync=opts.wait_for_sync)
667 SubmitOrSend(op, opts)
671 def _StartupInstance(name, opts):
672 """Startup instances.
674 This returns the opcode to start an instance, and its decorator will
675 wrap this into a loop starting 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 op = opcodes.OpStartupInstance(instance_name=name,
684 ignore_offline_nodes=opts.ignore_offline)
685 # do not add these parameters to the opcode unless they're defined
687 op.hvparams = opts.hvparams
689 op.beparams = opts.beparams
693 def _RebootInstance(name, opts):
694 """Reboot instance(s).
696 This returns the opcode to reboot an instance, and its decorator
697 will wrap this into a loop rebooting all desired instances.
699 @param name: the name of the instance to act on
700 @param opts: the command line options selected by the user
701 @return: the opcode needed for the operation
704 return opcodes.OpInstanceReboot(instance_name=name,
705 reboot_type=opts.reboot_type,
706 ignore_secondaries=opts.ignore_secondaries,
707 shutdown_timeout=opts.shutdown_timeout)
710 def _ShutdownInstance(name, opts):
711 """Shutdown an instance.
713 This returns the opcode to shutdown an instance, and its decorator
714 will wrap this into a loop shutting down all desired instances.
716 @param name: the name of the instance to act on
717 @param opts: the command line options selected by the user
718 @return: the opcode needed for the operation
721 return opcodes.OpShutdownInstance(instance_name=name,
722 timeout=opts.timeout,
723 ignore_offline_nodes=opts.ignore_offline)
726 def ReplaceDisks(opts, args):
727 """Replace the disks of an instance
729 @param opts: the command line options selected by the user
731 @param args: should contain only one element, the instance name
733 @return: the desired exit code
736 new_2ndary = opts.dst_node
737 iallocator = opts.iallocator
738 if opts.disks is None:
742 disks = [int(i) for i in opts.disks.split(",")]
743 except (TypeError, ValueError), err:
744 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
746 cnt = [opts.on_primary, opts.on_secondary, opts.auto,
747 new_2ndary is not None, iallocator is not None].count(True)
749 raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -i"
750 " options must be passed", errors.ECODE_INVAL)
751 elif opts.on_primary:
752 mode = constants.REPLACE_DISK_PRI
753 elif opts.on_secondary:
754 mode = constants.REPLACE_DISK_SEC
756 mode = constants.REPLACE_DISK_AUTO
758 raise errors.OpPrereqError("Cannot specify disks when using automatic"
759 " mode", errors.ECODE_INVAL)
760 elif new_2ndary is not None or iallocator is not None:
762 mode = constants.REPLACE_DISK_CHG
764 op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
765 remote_node=new_2ndary, mode=mode,
766 iallocator=iallocator,
767 early_release=opts.early_release)
768 SubmitOrSend(op, opts)
772 def FailoverInstance(opts, args):
773 """Failover an instance.
775 The failover is done by shutting it down on its present node and
776 starting it on the secondary.
778 @param opts: the command line options selected by the user
780 @param args: should contain only one element, the instance name
782 @return: the desired exit code
786 instance_name = args[0]
790 _EnsureInstancesExist(cl, [instance_name])
792 usertext = ("Failover will happen to image %s."
793 " This requires a shutdown of the instance. Continue?" %
795 if not AskUser(usertext):
798 op = opcodes.OpInstanceFailover(instance_name=instance_name,
799 ignore_consistency=opts.ignore_consistency,
800 shutdown_timeout=opts.shutdown_timeout)
801 SubmitOrSend(op, opts, cl=cl)
805 def MigrateInstance(opts, args):
806 """Migrate an instance.
808 The migrate is done without shutdown.
810 @param opts: the command line options selected by the user
812 @param args: should contain only one element, the instance name
814 @return: the desired exit code
818 instance_name = args[0]
822 _EnsureInstancesExist(cl, [instance_name])
825 usertext = ("Instance %s will be recovered from a failed migration."
826 " Note that the migration procedure (including cleanup)" %
829 usertext = ("Instance %s will be migrated. Note that migration" %
831 usertext += (" might impact the instance if anything goes wrong"
832 " (e.g. due to bugs in the hypervisor). Continue?")
833 if not AskUser(usertext):
836 # this should be removed once --non-live is deprecated
837 if not opts.live and opts.migration_mode is not None:
838 raise errors.OpPrereqError("Only one of the --non-live and "
839 "--migration-mode options can be passed",
841 if not opts.live: # --non-live passed
842 mode = constants.HT_MIGRATION_NONLIVE
844 mode = opts.migration_mode
846 op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
847 cleanup=opts.cleanup)
848 SubmitOpCode(op, cl=cl, opts=opts)
852 def MoveInstance(opts, args):
855 @param opts: the command line options selected by the user
857 @param args: should contain only one element, the instance name
859 @return: the desired exit code
863 instance_name = args[0]
867 usertext = ("Instance %s will be moved."
868 " This requires a shutdown of the instance. Continue?" %
870 if not AskUser(usertext):
873 op = opcodes.OpInstanceMove(instance_name=instance_name,
874 target_node=opts.node,
875 shutdown_timeout=opts.shutdown_timeout)
876 SubmitOrSend(op, opts, cl=cl)
880 def ConnectToInstanceConsole(opts, args):
881 """Connect to the console of an instance.
883 @param opts: the command line options selected by the user
885 @param args: should contain only one element, the instance name
887 @return: the desired exit code
890 instance_name = args[0]
892 op = opcodes.OpInstanceConsole(instance_name=instance_name)
896 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
897 console_data = SubmitOpCode(op, opts=opts, cl=cl)
899 # Ensure client connection is closed while external commands are run
904 return _DoConsole(objects.InstanceConsole.FromDict(console_data),
905 opts.show_command, cluster_name)
908 def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
909 _runcmd_fn=utils.RunCmd):
910 """Acts based on the result of L{opcodes.OpInstanceConsole}.
912 @type console: L{objects.InstanceConsole}
913 @param console: Console object
914 @type show_command: bool
915 @param show_command: Whether to just display commands
916 @type cluster_name: string
917 @param cluster_name: Cluster name as retrieved from master daemon
920 assert console.Validate()
922 if console.kind == constants.CONS_MESSAGE:
923 feedback_fn(console.message)
924 elif console.kind == constants.CONS_VNC:
925 feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
926 " URL <vnc://%s:%s/>",
927 console.instance, console.host, console.port,
928 console.display, console.host, console.port)
929 elif console.kind == constants.CONS_SSH:
930 # Convert to string if not already one
931 if isinstance(console.command, basestring):
932 cmd = console.command
934 cmd = utils.ShellQuoteArgs(console.command)
936 srun = ssh.SshRunner(cluster_name=cluster_name)
937 ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
938 batch=True, quiet=False, tty=True)
941 feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
943 result = _runcmd_fn(ssh_cmd, interactive=True)
945 logging.error("Console command \"%s\" failed with reason '%s' and"
946 " output %r", result.cmd, result.fail_reason,
948 raise errors.OpExecError("Connection to console of instance %s failed,"
949 " please check cluster configuration" %
952 raise errors.GenericError("Unknown console type '%s'" % console.kind)
954 return constants.EXIT_SUCCESS
957 def _FormatLogicalID(dev_type, logical_id, roman):
958 """Formats the logical_id of a disk.
961 if dev_type == constants.LD_DRBD8:
962 node_a, node_b, port, minor_a, minor_b, key = logical_id
964 ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
966 ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
968 ("port", compat.TryToRoman(port, convert=roman)),
971 elif dev_type == constants.LD_LV:
972 vg_name, lv_name = logical_id
973 data = ["%s/%s" % (vg_name, lv_name)]
975 data = [str(logical_id)]
980 def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
981 """Show block device information.
983 This is only used by L{ShowInstanceConfig}, but it's too big to be
984 left for an inline definition.
987 @param idx: the index of the current disk
988 @type top_level: boolean
989 @param top_level: if this a top-level disk?
991 @param dev: dictionary with disk information
992 @type static: boolean
993 @param static: wheter the device information doesn't contain
994 runtime information but only static data
996 @param roman: whether to try to use roman integers
997 @return: a list of either strings, tuples or lists
998 (which should be formatted at a higher indent level)
1001 def helper(dtype, status):
1002 """Format one line for physical device status.
1005 @param dtype: a constant from the L{constants.LDS_BLOCK} set
1007 @param status: a tuple as returned from L{backend.FindBlockDevice}
1008 @return: the string representing the status
1014 (path, major, minor, syncp, estt, degr, ldisk_status) = status
1016 major_string = "N/A"
1018 major_string = str(compat.TryToRoman(major, convert=roman))
1021 minor_string = "N/A"
1023 minor_string = str(compat.TryToRoman(minor, convert=roman))
1025 txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1026 if dtype in (constants.LD_DRBD8, ):
1027 if syncp is not None:
1028 sync_text = "*RECOVERING* %5.2f%%," % syncp
1030 sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1032 sync_text += " ETA unknown"
1034 sync_text = "in sync"
1036 degr_text = "*DEGRADED*"
1039 if ldisk_status == constants.LDS_FAULTY:
1040 ldisk_text = " *MISSING DISK*"
1041 elif ldisk_status == constants.LDS_UNKNOWN:
1042 ldisk_text = " *UNCERTAIN STATE*"
1045 txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1046 elif dtype == constants.LD_LV:
1047 if ldisk_status == constants.LDS_FAULTY:
1048 ldisk_text = " *FAILED* (failed drive?)"
1056 if dev["iv_name"] is not None:
1057 txt = dev["iv_name"]
1059 txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1061 txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1062 if isinstance(dev["size"], int):
1063 nice_size = utils.FormatUnit(dev["size"], "h")
1065 nice_size = dev["size"]
1066 d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1069 data.append(("access mode", dev["mode"]))
1070 if dev["logical_id"] is not None:
1072 l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1074 l_id = [str(dev["logical_id"])]
1076 data.append(("logical_id", l_id[0]))
1079 elif dev["physical_id"] is not None:
1080 data.append("physical_id:")
1081 data.append([dev["physical_id"]])
1083 data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1084 if dev["sstatus"] and not static:
1085 data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1088 data.append("child devices:")
1089 for c_idx, child in enumerate(dev["children"]):
1090 data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
1095 def _FormatList(buf, data, indent_level):
1096 """Formats a list of data at a given indent level.
1098 If the element of the list is:
1099 - a string, it is simply formatted as is
1100 - a tuple, it will be split into key, value and the all the
1101 values in a list will be aligned all at the same start column
1102 - a list, will be recursively formatted
1105 @param buf: the buffer into which we write the output
1106 @param data: the list to format
1107 @type indent_level: int
1108 @param indent_level: the indent level to format at
1111 max_tlen = max([len(elem[0]) for elem in data
1112 if isinstance(elem, tuple)] or [0])
1114 if isinstance(elem, basestring):
1115 buf.write("%*s%s\n" % (2*indent_level, "", elem))
1116 elif isinstance(elem, tuple):
1118 spacer = "%*s" % (max_tlen - len(key), "")
1119 buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1120 elif isinstance(elem, list):
1121 _FormatList(buf, elem, indent_level+1)
1124 def ShowInstanceConfig(opts, args):
1125 """Compute instance run-time status.
1127 @param opts: the command line options selected by the user
1129 @param args: either an empty list, and then we query all
1130 instances, or should contain a list of instance names
1132 @return: the desired exit code
1135 if not args and not opts.show_all:
1136 ToStderr("No instance selected."
1137 " Please pass in --all if you want to query all instances.\n"
1138 "Note that this can take a long time on a big cluster.")
1140 elif args and opts.show_all:
1141 ToStderr("Cannot use --all if you specify instance names.")
1145 op = opcodes.OpInstanceQueryData(instances=args, static=opts.static)
1146 result = SubmitOpCode(op, opts=opts)
1148 ToStdout("No instances.")
1153 for instance_name in result:
1154 instance = result[instance_name]
1155 buf.write("Instance name: %s\n" % instance["name"])
1156 buf.write("UUID: %s\n" % instance["uuid"])
1157 buf.write("Serial number: %s\n" %
1158 compat.TryToRoman(instance["serial_no"],
1159 convert=opts.roman_integers))
1160 buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1161 buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1162 buf.write("State: configured to be %s" % instance["config_state"])
1164 buf.write(", actual state is %s" % instance["run_state"])
1166 ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1167 ## instance["auto_balance"])
1168 buf.write(" Nodes:\n")
1169 buf.write(" - primary: %s\n" % instance["pnode"])
1170 buf.write(" - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1171 buf.write(" Operating system: %s\n" % instance["os"])
1172 FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1174 if instance.has_key("network_port"):
1175 buf.write(" Allocated network port: %s\n" %
1176 compat.TryToRoman(instance["network_port"],
1177 convert=opts.roman_integers))
1178 buf.write(" Hypervisor: %s\n" % instance["hypervisor"])
1180 # custom VNC console information
1181 vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1183 if vnc_bind_address:
1184 port = instance["network_port"]
1185 display = int(port) - constants.VNC_BASE_PORT
1186 if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1187 vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1190 elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1191 vnc_console_port = ("%s:%s (node %s) (display %s)" %
1192 (vnc_bind_address, port,
1193 instance["pnode"], display))
1195 # vnc bind address is a file
1196 vnc_console_port = "%s:%s" % (instance["pnode"],
1198 buf.write(" - console connection: vnc to %s\n" % vnc_console_port)
1200 FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1202 buf.write(" Hardware:\n")
1203 buf.write(" - VCPUs: %s\n" %
1204 compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1205 convert=opts.roman_integers))
1206 buf.write(" - memory: %sMiB\n" %
1207 compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
1208 convert=opts.roman_integers))
1209 buf.write(" - NICs:\n")
1210 for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1211 buf.write(" - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1212 (idx, mac, ip, mode, link))
1213 buf.write(" Disk template: %s\n" % instance["disk_template"])
1214 buf.write(" Disks:\n")
1216 for idx, device in enumerate(instance["disks"]):
1217 _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static,
1218 opts.roman_integers), 2)
1220 ToStdout(buf.getvalue().rstrip('\n'))
1224 def SetInstanceParams(opts, args):
1225 """Modifies an instance.
1227 All parameters take effect only at the next restart of the instance.
1229 @param opts: the command line options selected by the user
1231 @param args: should contain only one element, the instance name
1233 @return: the desired exit code
1236 if not (opts.nics or opts.disks or opts.disk_template or
1237 opts.hvparams or opts.beparams or opts.os or opts.osparams):
1238 ToStderr("Please give at least one of the parameters.")
1241 for param in opts.beparams:
1242 if isinstance(opts.beparams[param], basestring):
1243 if opts.beparams[param].lower() == "default":
1244 opts.beparams[param] = constants.VALUE_DEFAULT
1246 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1247 allowed_values=[constants.VALUE_DEFAULT])
1249 for param in opts.hvparams:
1250 if isinstance(opts.hvparams[param], basestring):
1251 if opts.hvparams[param].lower() == "default":
1252 opts.hvparams[param] = constants.VALUE_DEFAULT
1254 utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1255 allowed_values=[constants.VALUE_DEFAULT])
1257 for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1259 nic_op = int(nic_op)
1260 opts.nics[idx] = (nic_op, nic_dict)
1261 except (TypeError, ValueError):
1264 for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1266 disk_op = int(disk_op)
1267 opts.disks[idx] = (disk_op, disk_dict)
1268 except (TypeError, ValueError):
1270 if disk_op == constants.DDM_ADD:
1271 if 'size' not in disk_dict:
1272 raise errors.OpPrereqError("Missing required parameter 'size'",
1274 disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1276 if (opts.disk_template and
1277 opts.disk_template in constants.DTS_NET_MIRROR and
1279 ToStderr("Changing the disk template to a mirrored one requires"
1280 " specifying a secondary node")
1283 op = opcodes.OpSetInstanceParams(instance_name=args[0],
1286 disk_template=opts.disk_template,
1287 remote_node=opts.node,
1288 hvparams=opts.hvparams,
1289 beparams=opts.beparams,
1291 osparams=opts.osparams,
1292 force_variant=opts.force_variant,
1295 # even if here we process the result, we allow submit only
1296 result = SubmitOrSend(op, opts)
1299 ToStdout("Modified instance %s", args[0])
1300 for param, data in result:
1301 ToStdout(" - %-5s -> %s", param, data)
1302 ToStdout("Please don't forget that most parameters take effect"
1303 " only at the next start of the instance.")
1307 # multi-instance selection options
1308 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1309 help="Do not ask for confirmation when more than"
1310 " one instance is affected",
1311 action="store_true", default=False)
1313 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1314 help="Filter by nodes (primary only)",
1315 const=_SHUTDOWN_NODES_PRI, action="store_const")
1317 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1318 help="Filter by nodes (secondary only)",
1319 const=_SHUTDOWN_NODES_SEC, action="store_const")
1321 m_node_opt = cli_option("--node", dest="multi_mode",
1322 help="Filter by nodes (primary and secondary)",
1323 const=_SHUTDOWN_NODES_BOTH, action="store_const")
1325 m_clust_opt = cli_option("--all", dest="multi_mode",
1326 help="Select all instances in the cluster",
1327 const=_SHUTDOWN_CLUSTER, action="store_const")
1329 m_inst_opt = cli_option("--instance", dest="multi_mode",
1330 help="Filter by instance name [default]",
1331 const=_SHUTDOWN_INSTANCES, action="store_const")
1333 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1334 help="Filter by node tag",
1335 const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1336 action="store_const")
1338 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1339 help="Filter by primary node tag",
1340 const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1341 action="store_const")
1343 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1344 help="Filter by secondary node tag",
1345 const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1346 action="store_const")
1348 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1349 help="Filter by instance tag",
1350 const=_SHUTDOWN_INSTANCES_BY_TAGS,
1351 action="store_const")
1353 # this is defined separately due to readability only
1363 AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1364 "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1365 "Creates and adds a new instance to the cluster"),
1367 BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1369 "Create a bunch of instances based on specs in the file."),
1371 ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1372 [SHOWCMD_OPT, PRIORITY_OPT],
1373 "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1375 FailoverInstance, ARGS_ONE_INSTANCE,
1376 [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1377 DRY_RUN_OPT, PRIORITY_OPT],
1378 "[-f] <instance>", "Stops the instance and starts it on the backup node,"
1379 " using the remote mirror (only for instances of type drbd)"),
1381 MigrateInstance, ARGS_ONE_INSTANCE,
1382 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1384 "[-f] <instance>", "Migrate instance to its secondary node"
1385 " (only for instances of type drbd)"),
1387 MoveInstance, ARGS_ONE_INSTANCE,
1388 [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1389 DRY_RUN_OPT, PRIORITY_OPT],
1390 "[-f] <instance>", "Move instance to an arbitrary node"
1391 " (only for instances of type file and lv)"),
1393 ShowInstanceConfig, ARGS_MANY_INSTANCES,
1394 [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1395 "[-s] {--all | <instance>...}",
1396 "Show information on the specified instance(s)"),
1398 ListInstances, ARGS_MANY_INSTANCES,
1399 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
1401 "Lists the instances and their status. The available fields can be shown"
1402 " using the \"list-fields\" command (see the man page for details)."
1403 " The default field list is (in order): %s." %
1404 utils.CommaJoin(_LIST_DEF_FIELDS),
1407 ListInstanceFields, [ArgUnknown()],
1408 [NOHDR_OPT, SEP_OPT],
1410 "Lists all available fields for instances"),
1412 ReinstallInstance, [ArgInstance()],
1413 [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1414 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1415 m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1416 SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1417 "[-f] <instance>", "Reinstall a stopped instance"),
1419 RemoveInstance, ARGS_ONE_INSTANCE,
1420 [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1421 DRY_RUN_OPT, PRIORITY_OPT],
1422 "[-f] <instance>", "Shuts down the instance and removes it"),
1425 [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1426 [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1427 "<instance> <new_name>", "Rename the instance"),
1429 ReplaceDisks, ARGS_ONE_INSTANCE,
1430 [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1431 NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1432 DRY_RUN_OPT, PRIORITY_OPT],
1433 "[-s|-p|-n NODE|-I NAME] <instance>",
1434 "Replaces all disks for the instance"),
1436 SetInstanceParams, ARGS_ONE_INSTANCE,
1437 [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1438 DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1439 OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1440 "<instance>", "Alters the parameters of an instance"),
1442 GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1443 [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1444 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1445 m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1446 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1447 "<instance>", "Stops an instance"),
1449 GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1450 [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1451 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1452 m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1453 BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1454 "<instance>", "Starts an instance"),
1456 GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1457 [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1458 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1459 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1460 m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1461 "<instance>", "Reboots an instance"),
1463 ActivateDisks, ARGS_ONE_INSTANCE,
1464 [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
1465 "<instance>", "Activate an instance's disks"),
1466 'deactivate-disks': (
1467 DeactivateDisks, ARGS_ONE_INSTANCE,
1468 [SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1469 "<instance>", "Deactivate an instance's disks"),
1471 RecreateDisks, ARGS_ONE_INSTANCE,
1472 [SUBMIT_OPT, DISKIDX_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1473 "<instance>", "Recreate an instance's disks"),
1476 [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1477 ArgUnknown(min=1, max=1)],
1478 [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1479 "<instance> <disk> <size>", "Grow an instance's disk"),
1481 ListTags, ARGS_ONE_INSTANCE, [PRIORITY_OPT],
1482 "<instance_name>", "List the tags of the given instance"),
1484 AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1485 [TAG_SRC_OPT, PRIORITY_OPT],
1486 "<instance_name> tag...", "Add tags to the given instance"),
1488 RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1489 [TAG_SRC_OPT, PRIORITY_OPT],
1490 "<instance_name> tag...", "Remove tags from given instance"),
1493 #: dictionary with aliases for commands
1501 return GenericMain(commands, aliases=aliases,
1502 override={"tag_type": constants.TAG_INSTANCE})