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))
642 pnode, snode = SplitNodeOption(opts.node)
644 if snode is not None:
649 op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
652 SubmitOrSend(op, opts)
656 def GrowDisk(opts, args):
657 """Grow an instance's disks.
659 @param opts: the command line options selected by the user
661 @param args: should contain two elements, the instance name
662 whose disks we grow and the disk name, e.g. I{sda}
664 @return: the desired exit code
671 except (TypeError, ValueError), err:
672 raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
674 amount = utils.ParseUnit(args[2])
675 op = opcodes.OpInstanceGrowDisk(instance_name=instance,
676 disk=disk, amount=amount,
677 wait_for_sync=opts.wait_for_sync)
678 SubmitOrSend(op, opts)
682 def _StartupInstance(name, opts):
683 """Startup instances.
685 This returns the opcode to start an instance, and its decorator will
686 wrap this into a loop starting all desired instances.
688 @param name: the name of the instance to act on
689 @param opts: the command line options selected by the user
690 @return: the opcode needed for the operation
693 op = opcodes.OpInstanceStartup(instance_name=name,
695 ignore_offline_nodes=opts.ignore_offline)
696 # do not add these parameters to the opcode unless they're defined
698 op.hvparams = opts.hvparams
700 op.beparams = opts.beparams
704 def _RebootInstance(name, opts):
705 """Reboot instance(s).
707 This returns the opcode to reboot an instance, and its decorator
708 will wrap this into a loop rebooting all desired instances.
710 @param name: the name of the instance to act on
711 @param opts: the command line options selected by the user
712 @return: the opcode needed for the operation
715 return opcodes.OpInstanceReboot(instance_name=name,
716 reboot_type=opts.reboot_type,
717 ignore_secondaries=opts.ignore_secondaries,
718 shutdown_timeout=opts.shutdown_timeout)
721 def _ShutdownInstance(name, opts):
722 """Shutdown an instance.
724 This returns the opcode to shutdown an instance, and its decorator
725 will wrap this into a loop shutting down all desired instances.
727 @param name: the name of the instance to act on
728 @param opts: the command line options selected by the user
729 @return: the opcode needed for the operation
732 return opcodes.OpInstanceShutdown(instance_name=name,
733 timeout=opts.timeout,
734 ignore_offline_nodes=opts.ignore_offline)
737 def ReplaceDisks(opts, args):
738 """Replace the disks of an instance
740 @param opts: the command line options selected by the user
742 @param args: should contain only one element, the instance name
744 @return: the desired exit code
747 new_2ndary = opts.dst_node
748 iallocator = opts.iallocator
749 if opts.disks is None:
753 disks = [int(i) for i in opts.disks.split(",")]
754 except (TypeError, ValueError), err:
755 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
757 cnt = [opts.on_primary, opts.on_secondary, opts.auto,
758 new_2ndary is not None, iallocator is not None].count(True)
760 raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -I"
761 " options must be passed", errors.ECODE_INVAL)
762 elif opts.on_primary:
763 mode = constants.REPLACE_DISK_PRI
764 elif opts.on_secondary:
765 mode = constants.REPLACE_DISK_SEC
767 mode = constants.REPLACE_DISK_AUTO
769 raise errors.OpPrereqError("Cannot specify disks when using automatic"
770 " mode", errors.ECODE_INVAL)
771 elif new_2ndary is not None or iallocator is not None:
773 mode = constants.REPLACE_DISK_CHG
775 op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
776 remote_node=new_2ndary, mode=mode,
777 iallocator=iallocator,
778 early_release=opts.early_release)
779 SubmitOrSend(op, opts)
783 def FailoverInstance(opts, args):
784 """Failover an instance.
786 The failover is done by shutting it down on its present node and
787 starting it on the secondary.
789 @param opts: the command line options selected by the user
791 @param args: should contain only one element, the instance name
793 @return: the desired exit code
797 instance_name = args[0]
801 _EnsureInstancesExist(cl, [instance_name])
803 usertext = ("Failover will happen to image %s."
804 " This requires a shutdown of the instance. Continue?" %
806 if not AskUser(usertext):
809 op = opcodes.OpInstanceFailover(instance_name=instance_name,
810 ignore_consistency=opts.ignore_consistency,
811 shutdown_timeout=opts.shutdown_timeout)
812 SubmitOrSend(op, opts, cl=cl)
816 def MigrateInstance(opts, args):
817 """Migrate an instance.
819 The migrate is done without shutdown.
821 @param opts: the command line options selected by the user
823 @param args: should contain only one element, the instance name
825 @return: the desired exit code
829 instance_name = args[0]
833 _EnsureInstancesExist(cl, [instance_name])
836 usertext = ("Instance %s will be recovered from a failed migration."
837 " Note that the migration procedure (including cleanup)" %
840 usertext = ("Instance %s will be migrated. Note that migration" %
842 usertext += (" might impact the instance if anything goes wrong"
843 " (e.g. due to bugs in the hypervisor). Continue?")
844 if not AskUser(usertext):
847 # this should be removed once --non-live is deprecated
848 if not opts.live and opts.migration_mode is not None:
849 raise errors.OpPrereqError("Only one of the --non-live and "
850 "--migration-mode options can be passed",
852 if not opts.live: # --non-live passed
853 mode = constants.HT_MIGRATION_NONLIVE
855 mode = opts.migration_mode
857 op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
858 cleanup=opts.cleanup)
859 SubmitOpCode(op, cl=cl, opts=opts)
863 def MoveInstance(opts, args):
866 @param opts: the command line options selected by the user
868 @param args: should contain only one element, the instance name
870 @return: the desired exit code
874 instance_name = args[0]
878 usertext = ("Instance %s will be moved."
879 " This requires a shutdown of the instance. Continue?" %
881 if not AskUser(usertext):
884 op = opcodes.OpInstanceMove(instance_name=instance_name,
885 target_node=opts.node,
886 shutdown_timeout=opts.shutdown_timeout)
887 SubmitOrSend(op, opts, cl=cl)
891 def ConnectToInstanceConsole(opts, args):
892 """Connect to the console of an instance.
894 @param opts: the command line options selected by the user
896 @param args: should contain only one element, the instance name
898 @return: the desired exit code
901 instance_name = args[0]
903 op = opcodes.OpInstanceConsole(instance_name=instance_name)
907 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
908 console_data = SubmitOpCode(op, opts=opts, cl=cl)
910 # Ensure client connection is closed while external commands are run
915 return _DoConsole(objects.InstanceConsole.FromDict(console_data),
916 opts.show_command, cluster_name)
919 def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
920 _runcmd_fn=utils.RunCmd):
921 """Acts based on the result of L{opcodes.OpInstanceConsole}.
923 @type console: L{objects.InstanceConsole}
924 @param console: Console object
925 @type show_command: bool
926 @param show_command: Whether to just display commands
927 @type cluster_name: string
928 @param cluster_name: Cluster name as retrieved from master daemon
931 assert console.Validate()
933 if console.kind == constants.CONS_MESSAGE:
934 feedback_fn(console.message)
935 elif console.kind == constants.CONS_VNC:
936 feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
937 " URL <vnc://%s:%s/>",
938 console.instance, console.host, console.port,
939 console.display, console.host, console.port)
940 elif console.kind == constants.CONS_SSH:
941 # Convert to string if not already one
942 if isinstance(console.command, basestring):
943 cmd = console.command
945 cmd = utils.ShellQuoteArgs(console.command)
947 srun = ssh.SshRunner(cluster_name=cluster_name)
948 ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
949 batch=True, quiet=False, tty=True)
952 feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
954 result = _runcmd_fn(ssh_cmd, interactive=True)
956 logging.error("Console command \"%s\" failed with reason '%s' and"
957 " output %r", result.cmd, result.fail_reason,
959 raise errors.OpExecError("Connection to console of instance %s failed,"
960 " please check cluster configuration" %
963 raise errors.GenericError("Unknown console type '%s'" % console.kind)
965 return constants.EXIT_SUCCESS
968 def _FormatLogicalID(dev_type, logical_id, roman):
969 """Formats the logical_id of a disk.
972 if dev_type == constants.LD_DRBD8:
973 node_a, node_b, port, minor_a, minor_b, key = logical_id
975 ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
977 ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
979 ("port", compat.TryToRoman(port, convert=roman)),
982 elif dev_type == constants.LD_LV:
983 vg_name, lv_name = logical_id
984 data = ["%s/%s" % (vg_name, lv_name)]
986 data = [str(logical_id)]
991 def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
992 """Show block device information.
994 This is only used by L{ShowInstanceConfig}, but it's too big to be
995 left for an inline definition.
998 @param idx: the index of the current disk
999 @type top_level: boolean
1000 @param top_level: if this a top-level disk?
1002 @param dev: dictionary with disk information
1003 @type static: boolean
1004 @param static: wheter the device information doesn't contain
1005 runtime information but only static data
1006 @type roman: boolean
1007 @param roman: whether to try to use roman integers
1008 @return: a list of either strings, tuples or lists
1009 (which should be formatted at a higher indent level)
1012 def helper(dtype, status):
1013 """Format one line for physical device status.
1016 @param dtype: a constant from the L{constants.LDS_BLOCK} set
1018 @param status: a tuple as returned from L{backend.FindBlockDevice}
1019 @return: the string representing the status
1025 (path, major, minor, syncp, estt, degr, ldisk_status) = status
1027 major_string = "N/A"
1029 major_string = str(compat.TryToRoman(major, convert=roman))
1032 minor_string = "N/A"
1034 minor_string = str(compat.TryToRoman(minor, convert=roman))
1036 txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1037 if dtype in (constants.LD_DRBD8, ):
1038 if syncp is not None:
1039 sync_text = "*RECOVERING* %5.2f%%," % syncp
1041 sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1043 sync_text += " ETA unknown"
1045 sync_text = "in sync"
1047 degr_text = "*DEGRADED*"
1050 if ldisk_status == constants.LDS_FAULTY:
1051 ldisk_text = " *MISSING DISK*"
1052 elif ldisk_status == constants.LDS_UNKNOWN:
1053 ldisk_text = " *UNCERTAIN STATE*"
1056 txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1057 elif dtype == constants.LD_LV:
1058 if ldisk_status == constants.LDS_FAULTY:
1059 ldisk_text = " *FAILED* (failed drive?)"
1067 if dev["iv_name"] is not None:
1068 txt = dev["iv_name"]
1070 txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1072 txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1073 if isinstance(dev["size"], int):
1074 nice_size = utils.FormatUnit(dev["size"], "h")
1076 nice_size = dev["size"]
1077 d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1080 data.append(("access mode", dev["mode"]))
1081 if dev["logical_id"] is not None:
1083 l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1085 l_id = [str(dev["logical_id"])]
1087 data.append(("logical_id", l_id[0]))
1090 elif dev["physical_id"] is not None:
1091 data.append("physical_id:")
1092 data.append([dev["physical_id"]])
1094 data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1095 if dev["sstatus"] and not static:
1096 data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1099 data.append("child devices:")
1100 for c_idx, child in enumerate(dev["children"]):
1101 data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
1106 def _FormatList(buf, data, indent_level):
1107 """Formats a list of data at a given indent level.
1109 If the element of the list is:
1110 - a string, it is simply formatted as is
1111 - a tuple, it will be split into key, value and the all the
1112 values in a list will be aligned all at the same start column
1113 - a list, will be recursively formatted
1116 @param buf: the buffer into which we write the output
1117 @param data: the list to format
1118 @type indent_level: int
1119 @param indent_level: the indent level to format at
1122 max_tlen = max([len(elem[0]) for elem in data
1123 if isinstance(elem, tuple)] or [0])
1125 if isinstance(elem, basestring):
1126 buf.write("%*s%s\n" % (2*indent_level, "", elem))
1127 elif isinstance(elem, tuple):
1129 spacer = "%*s" % (max_tlen - len(key), "")
1130 buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1131 elif isinstance(elem, list):
1132 _FormatList(buf, elem, indent_level+1)
1135 def ShowInstanceConfig(opts, args):
1136 """Compute instance run-time status.
1138 @param opts: the command line options selected by the user
1140 @param args: either an empty list, and then we query all
1141 instances, or should contain a list of instance names
1143 @return: the desired exit code
1146 if not args and not opts.show_all:
1147 ToStderr("No instance selected."
1148 " Please pass in --all if you want to query all instances.\n"
1149 "Note that this can take a long time on a big cluster.")
1151 elif args and opts.show_all:
1152 ToStderr("Cannot use --all if you specify instance names.")
1156 op = opcodes.OpInstanceQueryData(instances=args, static=opts.static,
1157 use_locking=not opts.static)
1158 result = SubmitOpCode(op, opts=opts)
1160 ToStdout("No instances.")
1165 for instance_name in result:
1166 instance = result[instance_name]
1167 buf.write("Instance name: %s\n" % instance["name"])
1168 buf.write("UUID: %s\n" % instance["uuid"])
1169 buf.write("Serial number: %s\n" %
1170 compat.TryToRoman(instance["serial_no"],
1171 convert=opts.roman_integers))
1172 buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1173 buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1174 buf.write("State: configured to be %s" % instance["config_state"])
1176 buf.write(", actual state is %s" % instance["run_state"])
1178 ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1179 ## instance["auto_balance"])
1180 buf.write(" Nodes:\n")
1181 buf.write(" - primary: %s\n" % instance["pnode"])
1182 buf.write(" - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1183 buf.write(" Operating system: %s\n" % instance["os"])
1184 FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1186 if instance.has_key("network_port"):
1187 buf.write(" Allocated network port: %s\n" %
1188 compat.TryToRoman(instance["network_port"],
1189 convert=opts.roman_integers))
1190 buf.write(" Hypervisor: %s\n" % instance["hypervisor"])
1192 # custom VNC console information
1193 vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1195 if vnc_bind_address:
1196 port = instance["network_port"]
1197 display = int(port) - constants.VNC_BASE_PORT
1198 if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1199 vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1202 elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1203 vnc_console_port = ("%s:%s (node %s) (display %s)" %
1204 (vnc_bind_address, port,
1205 instance["pnode"], display))
1207 # vnc bind address is a file
1208 vnc_console_port = "%s:%s" % (instance["pnode"],
1210 buf.write(" - console connection: vnc to %s\n" % vnc_console_port)
1212 FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1214 buf.write(" Hardware:\n")
1215 buf.write(" - VCPUs: %s\n" %
1216 compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1217 convert=opts.roman_integers))
1218 buf.write(" - memory: %sMiB\n" %
1219 compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
1220 convert=opts.roman_integers))
1221 buf.write(" - NICs:\n")
1222 for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1223 buf.write(" - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1224 (idx, mac, ip, mode, link))
1225 buf.write(" Disk template: %s\n" % instance["disk_template"])
1226 buf.write(" Disks:\n")
1228 for idx, device in enumerate(instance["disks"]):
1229 _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static,
1230 opts.roman_integers), 2)
1232 ToStdout(buf.getvalue().rstrip('\n'))
1236 def SetInstanceParams(opts, args):
1237 """Modifies an instance.
1239 All parameters take effect only at the next restart of the instance.
1241 @param opts: the command line options selected by the user
1243 @param args: should contain only one element, the instance name
1245 @return: the desired exit code
1248 if not (opts.nics or opts.disks or opts.disk_template or
1249 opts.hvparams or opts.beparams or opts.os or opts.osparams):
1250 ToStderr("Please give at least one of the parameters.")
1253 for param in opts.beparams:
1254 if isinstance(opts.beparams[param], basestring):
1255 if opts.beparams[param].lower() == "default":
1256 opts.beparams[param] = constants.VALUE_DEFAULT
1258 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1259 allowed_values=[constants.VALUE_DEFAULT])
1261 for param in opts.hvparams:
1262 if isinstance(opts.hvparams[param], basestring):
1263 if opts.hvparams[param].lower() == "default":
1264 opts.hvparams[param] = constants.VALUE_DEFAULT
1266 utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1267 allowed_values=[constants.VALUE_DEFAULT])
1269 for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1271 nic_op = int(nic_op)
1272 opts.nics[idx] = (nic_op, nic_dict)
1273 except (TypeError, ValueError):
1276 for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1278 disk_op = int(disk_op)
1279 opts.disks[idx] = (disk_op, disk_dict)
1280 except (TypeError, ValueError):
1282 if disk_op == constants.DDM_ADD:
1283 if 'size' not in disk_dict:
1284 raise errors.OpPrereqError("Missing required parameter 'size'",
1286 disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1288 if (opts.disk_template and
1289 opts.disk_template in constants.DTS_NET_MIRROR and
1291 ToStderr("Changing the disk template to a mirrored one requires"
1292 " specifying a secondary node")
1295 op = opcodes.OpInstanceSetParams(instance_name=args[0],
1298 disk_template=opts.disk_template,
1299 remote_node=opts.node,
1300 hvparams=opts.hvparams,
1301 beparams=opts.beparams,
1303 osparams=opts.osparams,
1304 force_variant=opts.force_variant,
1307 # even if here we process the result, we allow submit only
1308 result = SubmitOrSend(op, opts)
1311 ToStdout("Modified instance %s", args[0])
1312 for param, data in result:
1313 ToStdout(" - %-5s -> %s", param, data)
1314 ToStdout("Please don't forget that most parameters take effect"
1315 " only at the next start of the instance.")
1319 # multi-instance selection options
1320 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1321 help="Do not ask for confirmation when more than"
1322 " one instance is affected",
1323 action="store_true", default=False)
1325 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1326 help="Filter by nodes (primary only)",
1327 const=_SHUTDOWN_NODES_PRI, action="store_const")
1329 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1330 help="Filter by nodes (secondary only)",
1331 const=_SHUTDOWN_NODES_SEC, action="store_const")
1333 m_node_opt = cli_option("--node", dest="multi_mode",
1334 help="Filter by nodes (primary and secondary)",
1335 const=_SHUTDOWN_NODES_BOTH, action="store_const")
1337 m_clust_opt = cli_option("--all", dest="multi_mode",
1338 help="Select all instances in the cluster",
1339 const=_SHUTDOWN_CLUSTER, action="store_const")
1341 m_inst_opt = cli_option("--instance", dest="multi_mode",
1342 help="Filter by instance name [default]",
1343 const=_SHUTDOWN_INSTANCES, action="store_const")
1345 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1346 help="Filter by node tag",
1347 const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1348 action="store_const")
1350 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1351 help="Filter by primary node tag",
1352 const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1353 action="store_const")
1355 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1356 help="Filter by secondary node tag",
1357 const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1358 action="store_const")
1360 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1361 help="Filter by instance tag",
1362 const=_SHUTDOWN_INSTANCES_BY_TAGS,
1363 action="store_const")
1365 # this is defined separately due to readability only
1375 AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1376 "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1377 "Creates and adds a new instance to the cluster"),
1379 BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1381 "Create a bunch of instances based on specs in the file."),
1383 ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1384 [SHOWCMD_OPT, PRIORITY_OPT],
1385 "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1387 FailoverInstance, ARGS_ONE_INSTANCE,
1388 [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1389 DRY_RUN_OPT, PRIORITY_OPT],
1390 "[-f] <instance>", "Stops the instance and starts it on the backup node,"
1391 " using the remote mirror (only for instances of type drbd)"),
1393 MigrateInstance, ARGS_ONE_INSTANCE,
1394 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1396 "[-f] <instance>", "Migrate instance to its secondary node"
1397 " (only for instances of type drbd)"),
1399 MoveInstance, ARGS_ONE_INSTANCE,
1400 [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1401 DRY_RUN_OPT, PRIORITY_OPT],
1402 "[-f] <instance>", "Move instance to an arbitrary node"
1403 " (only for instances of type file and lv)"),
1405 ShowInstanceConfig, ARGS_MANY_INSTANCES,
1406 [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1407 "[-s] {--all | <instance>...}",
1408 "Show information on the specified instance(s)"),
1410 ListInstances, ARGS_MANY_INSTANCES,
1411 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT],
1413 "Lists the instances and their status. The available fields can be shown"
1414 " using the \"list-fields\" command (see the man page for details)."
1415 " The default field list is (in order): %s." %
1416 utils.CommaJoin(_LIST_DEF_FIELDS),
1419 ListInstanceFields, [ArgUnknown()],
1420 [NOHDR_OPT, SEP_OPT],
1422 "Lists all available fields for instances"),
1424 ReinstallInstance, [ArgInstance()],
1425 [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1426 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1427 m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1428 SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1429 "[-f] <instance>", "Reinstall a stopped instance"),
1431 RemoveInstance, ARGS_ONE_INSTANCE,
1432 [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1433 DRY_RUN_OPT, PRIORITY_OPT],
1434 "[-f] <instance>", "Shuts down the instance and removes it"),
1437 [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1438 [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1439 "<instance> <new_name>", "Rename the instance"),
1441 ReplaceDisks, ARGS_ONE_INSTANCE,
1442 [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1443 NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1444 DRY_RUN_OPT, PRIORITY_OPT],
1445 "[-s|-p|-n NODE|-I NAME] <instance>",
1446 "Replaces all disks for the instance"),
1448 SetInstanceParams, ARGS_ONE_INSTANCE,
1449 [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1450 DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1451 OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1452 "<instance>", "Alters the parameters of an instance"),
1454 GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1455 [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1456 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1457 m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1458 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1459 "<instance>", "Stops an instance"),
1461 GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1462 [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1463 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1464 m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1465 BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1466 "<instance>", "Starts an instance"),
1468 GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1469 [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1470 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1471 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1472 m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1473 "<instance>", "Reboots an instance"),
1475 ActivateDisks, ARGS_ONE_INSTANCE,
1476 [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
1477 "<instance>", "Activate an instance's disks"),
1478 'deactivate-disks': (
1479 DeactivateDisks, ARGS_ONE_INSTANCE,
1480 [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1481 "[-f] <instance>", "Deactivate an instance's disks"),
1483 RecreateDisks, ARGS_ONE_INSTANCE,
1484 [SUBMIT_OPT, DISKIDX_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1485 "<instance>", "Recreate an instance's disks"),
1488 [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1489 ArgUnknown(min=1, max=1)],
1490 [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1491 "<instance> <disk> <size>", "Grow an instance's disk"),
1493 ListTags, ARGS_ONE_INSTANCE, [PRIORITY_OPT],
1494 "<instance_name>", "List the tags of the given instance"),
1496 AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1497 [TAG_SRC_OPT, PRIORITY_OPT],
1498 "<instance_name> tag...", "Add tags to the given instance"),
1500 RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1501 [TAG_SRC_OPT, PRIORITY_OPT],
1502 "<instance_name> tag...", "Remove tags from given instance"),
1505 #: dictionary with aliases for commands
1513 return GenericMain(commands, aliases=aliases,
1514 override={"tag_type": constants.TAG_INSTANCE})