4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 """Instance related commands"""
23 # pylint: disable-msg=W0401,W0614,C0103
24 # W0401: Wildcard import ganeti.cli
25 # W0614: Unused import %s from wildcard import (since we need cli)
26 # C0103: Invalid name gnt-instance
31 from cStringIO import StringIO
33 from ganeti.cli import *
34 from ganeti import opcodes
35 from ganeti import constants
36 from ganeti import compat
37 from ganeti import utils
38 from ganeti import errors
39 from ganeti import netutils
40 from ganeti import ssh
41 from ganeti import objects
44 _SHUTDOWN_CLUSTER = "cluster"
45 _SHUTDOWN_NODES_BOTH = "nodes"
46 _SHUTDOWN_NODES_PRI = "nodes-pri"
47 _SHUTDOWN_NODES_SEC = "nodes-sec"
48 _SHUTDOWN_NODES_BOTH_BY_TAGS = "nodes-by-tags"
49 _SHUTDOWN_NODES_PRI_BY_TAGS = "nodes-pri-by-tags"
50 _SHUTDOWN_NODES_SEC_BY_TAGS = "nodes-sec-by-tags"
51 _SHUTDOWN_INSTANCES = "instances"
52 _SHUTDOWN_INSTANCES_BY_TAGS = "instances-by-tags"
54 _SHUTDOWN_NODES_TAGS_MODES = (
55 _SHUTDOWN_NODES_BOTH_BY_TAGS,
56 _SHUTDOWN_NODES_PRI_BY_TAGS,
57 _SHUTDOWN_NODES_SEC_BY_TAGS)
60 #: default list of options for L{ListInstances}
62 "name", "hypervisor", "os", "pnode", "status", "oper_ram",
66 def _ExpandMultiNames(mode, names, client=None):
67 """Expand the given names using the passed mode.
69 For _SHUTDOWN_CLUSTER, all instances will be returned. For
70 _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
71 primary/secondary will be returned. For _SHUTDOWN_NODES_BOTH, all
72 instances having those nodes as either primary or secondary will be
73 returned. For _SHUTDOWN_INSTANCES, the given instances will be
76 @param mode: one of L{_SHUTDOWN_CLUSTER}, L{_SHUTDOWN_NODES_BOTH},
77 L{_SHUTDOWN_NODES_PRI}, L{_SHUTDOWN_NODES_SEC} or
78 L{_SHUTDOWN_INSTANCES}
79 @param names: a list of names; for cluster, it must be empty,
80 and for node and instance it must be a list of valid item
81 names (short names are valid as usual, e.g. node1 instead of
84 @return: the list of names after the expansion
85 @raise errors.ProgrammerError: for unknown selection type
86 @raise errors.OpPrereqError: for invalid input parameters
89 # pylint: disable-msg=W0142
93 if mode == _SHUTDOWN_CLUSTER:
95 raise errors.OpPrereqError("Cluster filter mode takes no arguments",
97 idata = client.QueryInstances([], ["name"], False)
98 inames = [row[0] for row in idata]
100 elif mode in (_SHUTDOWN_NODES_BOTH,
102 _SHUTDOWN_NODES_SEC) + _SHUTDOWN_NODES_TAGS_MODES:
103 if mode in _SHUTDOWN_NODES_TAGS_MODES:
105 raise errors.OpPrereqError("No node tags passed", errors.ECODE_INVAL)
106 ndata = client.QueryNodes([], ["name", "pinst_list",
107 "sinst_list", "tags"], False)
108 ndata = [row for row in ndata if set(row[3]).intersection(names)]
111 raise errors.OpPrereqError("No node names passed", errors.ECODE_INVAL)
112 ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
115 ipri = [row[1] for row in ndata]
116 pri_names = list(itertools.chain(*ipri))
117 isec = [row[2] for row in ndata]
118 sec_names = list(itertools.chain(*isec))
119 if mode in (_SHUTDOWN_NODES_BOTH, _SHUTDOWN_NODES_BOTH_BY_TAGS):
120 inames = pri_names + sec_names
121 elif mode in (_SHUTDOWN_NODES_PRI, _SHUTDOWN_NODES_PRI_BY_TAGS):
123 elif mode in (_SHUTDOWN_NODES_SEC, _SHUTDOWN_NODES_SEC_BY_TAGS):
126 raise errors.ProgrammerError("Unhandled shutdown type")
127 elif mode == _SHUTDOWN_INSTANCES:
129 raise errors.OpPrereqError("No instance names passed",
131 idata = client.QueryInstances(names, ["name"], False)
132 inames = [row[0] for row in idata]
133 elif mode == _SHUTDOWN_INSTANCES_BY_TAGS:
135 raise errors.OpPrereqError("No instance tags passed",
137 idata = client.QueryInstances([], ["name", "tags"], False)
138 inames = [row[0] for row in idata if set(row[1]).intersection(names)]
140 raise errors.OpPrereqError("Unknown mode '%s'" % mode, errors.ECODE_INVAL)
145 def _EnsureInstancesExist(client, names):
146 """Check for and ensure the given instance names exist.
148 This function will raise an OpPrereqError in case they don't
149 exist. Otherwise it will exit cleanly.
151 @type client: L{ganeti.luxi.Client}
152 @param client: the client to use for the query
154 @param names: the list of instance names to query
155 @raise errors.OpPrereqError: in case any instance is missing
158 # TODO: change LUInstanceQuery to that it actually returns None
159 # instead of raising an exception, or devise a better mechanism
160 result = client.QueryInstances(names, ["name"], False)
161 for orig_name, row in zip(names, result):
163 raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name,
167 def GenericManyOps(operation, fn):
168 """Generic multi-instance operations.
170 The will return a wrapper that processes the options and arguments
171 given, and uses the passed function to build the opcode needed for
172 the specific operation. Thus all the generic loop/confirmation code
173 is abstracted into this function.
176 def realfn(opts, args):
177 if opts.multi_mode is None:
178 opts.multi_mode = _SHUTDOWN_INSTANCES
180 inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
182 if opts.multi_mode == _SHUTDOWN_CLUSTER:
183 ToStdout("Cluster is empty, no instances to shutdown")
185 raise errors.OpPrereqError("Selection filter does not match"
186 " any instances", errors.ECODE_INVAL)
187 multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
188 if not (opts.force_multi or not multi_on
189 or ConfirmOperation(inames, "instances", operation)):
191 jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
194 jex.QueueJob(name, op)
195 results = jex.WaitOrShow(not opts.submit_only)
196 rcode = compat.all(row[0] for row in results)
197 return int(not rcode)
201 def ListInstances(opts, args):
202 """List instances and their properties.
204 @param opts: the command line options selected by the user
206 @param args: should be an empty list
208 @return: the desired exit code
211 selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
213 fmtoverride = dict.fromkeys(["tags", "disk.sizes", "nic.macs", "nic.ips",
214 "nic.modes", "nic.links", "nic.bridges",
216 (lambda value: ",".join(str(item)
220 return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units,
221 opts.separator, not opts.no_headers,
222 format_override=fmtoverride, verbose=opts.verbose,
223 force_filter=opts.force_filter)
226 def ListInstanceFields(opts, args):
227 """List instance fields.
229 @param opts: the command line options selected by the user
231 @param args: fields to list, or empty for all
233 @return: the desired exit code
236 return GenericListFields(constants.QR_INSTANCE, args, opts.separator,
240 def AddInstance(opts, args):
241 """Add an instance to the cluster.
243 This is just a wrapper over GenericInstanceCreate.
246 return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
249 def BatchCreate(opts, args):
250 """Create instances using a definition file.
252 This function reads a json file with instances defined
256 "disk_size": [20480],
262 "primary_node": "firstnode",
263 "secondary_node": "secondnode",
264 "iallocator": "dumb"}
267 Note that I{primary_node} and I{secondary_node} have precedence over
270 @param opts: the command line options selected by the user
272 @param args: should contain one element, the json filename
274 @return: the desired exit code
277 _DEFAULT_SPECS = {"disk_size": [20 * 1024],
280 "primary_node": None,
281 "secondary_node": None,
288 "file_storage_dir": None,
289 "force_variant": False,
290 "file_driver": 'loop'}
292 def _PopulateWithDefaults(spec):
293 """Returns a new hash combined with default values."""
294 mydict = _DEFAULT_SPECS.copy()
299 """Validate the instance specs."""
300 # Validate fields required under any circumstances
301 for required_field in ('os', 'template'):
302 if required_field not in spec:
303 raise errors.OpPrereqError('Required field "%s" is missing.' %
304 required_field, errors.ECODE_INVAL)
305 # Validate special fields
306 if spec['primary_node'] is not None:
307 if (spec['template'] in constants.DTS_INT_MIRROR and
308 spec['secondary_node'] is None):
309 raise errors.OpPrereqError('Template requires secondary node, but'
310 ' there was no secondary provided.',
312 elif spec['iallocator'] is None:
313 raise errors.OpPrereqError('You have to provide at least a primary_node'
314 ' or an iallocator.',
317 if (spec['hvparams'] and
318 not isinstance(spec['hvparams'], dict)):
319 raise errors.OpPrereqError('Hypervisor parameters must be a dict.',
322 json_filename = args[0]
324 instance_data = simplejson.loads(utils.ReadFile(json_filename))
325 except Exception, err: # pylint: disable-msg=W0703
326 ToStderr("Can't parse the instance definition file: %s" % str(err))
329 if not isinstance(instance_data, dict):
330 ToStderr("The instance definition file is not in dict format.")
333 jex = JobExecutor(opts=opts)
335 # Iterate over the instances and do:
336 # * Populate the specs with default value
337 # * Validate the instance specs
338 i_names = utils.NiceSort(instance_data.keys()) # pylint: disable-msg=E1103
340 specs = instance_data[name]
341 specs = _PopulateWithDefaults(specs)
344 hypervisor = specs['hypervisor']
345 hvparams = specs['hvparams']
348 for elem in specs['disk_size']:
350 size = utils.ParseUnit(elem)
351 except (TypeError, ValueError), err:
352 raise errors.OpPrereqError("Invalid disk size '%s' for"
354 (elem, name, err), errors.ECODE_INVAL)
355 disks.append({"size": size})
357 utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
358 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
361 for field in constants.INIC_PARAMS:
365 tmp_nics[0][field] = specs[field]
367 if specs['nics'] is not None and tmp_nics:
368 raise errors.OpPrereqError("'nics' list incompatible with using"
369 " individual nic fields as well",
371 elif specs['nics'] is not None:
372 tmp_nics = specs['nics']
376 op = opcodes.OpInstanceCreate(instance_name=name,
378 disk_template=specs['template'],
379 mode=constants.INSTANCE_CREATE,
381 force_variant=specs["force_variant"],
382 pnode=specs['primary_node'],
383 snode=specs['secondary_node'],
385 start=specs['start'],
386 ip_check=specs['ip_check'],
387 name_check=specs['name_check'],
389 iallocator=specs['iallocator'],
390 hypervisor=hypervisor,
392 beparams=specs['backend'],
393 file_storage_dir=specs['file_storage_dir'],
394 file_driver=specs['file_driver'])
396 jex.QueueJob(name, op)
397 # we never want to wait, just show the submitted job IDs
398 jex.WaitOrShow(False)
403 def ReinstallInstance(opts, args):
404 """Reinstall an instance.
406 @param opts: the command line options selected by the user
408 @param args: should contain only one element, the name of the
409 instance to be reinstalled
411 @return: the desired exit code
414 # first, compute the desired name list
415 if opts.multi_mode is None:
416 opts.multi_mode = _SHUTDOWN_INSTANCES
418 inames = _ExpandMultiNames(opts.multi_mode, args)
420 raise errors.OpPrereqError("Selection filter does not match any instances",
423 # second, if requested, ask for an OS
424 if opts.select_os is True:
425 op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[])
426 result = SubmitOpCode(op, opts=opts)
429 ToStdout("Can't get the OS list")
432 ToStdout("Available OS templates:")
435 for (name, variants) in result:
436 for entry in CalculateOSNames(name, variants):
437 ToStdout("%3s: %s", number, entry)
438 choices.append(("%s" % number, entry, entry))
441 choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
442 selected = AskUser("Enter OS template number (or x to abort):",
445 if selected == 'exit':
446 ToStderr("User aborted reinstall, exiting")
450 os_msg = "change the OS to '%s'" % selected
453 if opts.os is not None:
454 os_msg = "change the OS to '%s'" % os_name
456 os_msg = "keep the same OS"
458 # third, get confirmation: multi-reinstall requires --force-multi,
459 # single-reinstall either --force or --force-multi (--force-multi is
460 # a stronger --force)
461 multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
463 warn_msg = ("Note: this will remove *all* data for the"
464 " below instances! It will %s.\n" % os_msg)
465 if not (opts.force_multi or
466 ConfirmOperation(inames, "instances", "reinstall", extra=warn_msg)):
469 if not (opts.force or opts.force_multi):
470 usertext = ("This will reinstall the instance '%s' (and %s) which"
471 " removes all data. Continue?") % (inames[0], os_msg)
472 if not AskUser(usertext):
475 jex = JobExecutor(verbose=multi_on, opts=opts)
476 for instance_name in inames:
477 op = opcodes.OpInstanceReinstall(instance_name=instance_name,
479 force_variant=opts.force_variant,
480 osparams=opts.osparams)
481 jex.QueueJob(instance_name, op)
483 jex.WaitOrShow(not opts.submit_only)
487 def RemoveInstance(opts, args):
488 """Remove an instance.
490 @param opts: the command line options selected by the user
492 @param args: should contain only one element, the name of
493 the instance to be removed
495 @return: the desired exit code
498 instance_name = args[0]
503 _EnsureInstancesExist(cl, [instance_name])
505 usertext = ("This will remove the volumes of the instance %s"
506 " (including mirrors), thus removing all the data"
507 " of the instance. Continue?") % instance_name
508 if not AskUser(usertext):
511 op = opcodes.OpInstanceRemove(instance_name=instance_name,
512 ignore_failures=opts.ignore_failures,
513 shutdown_timeout=opts.shutdown_timeout)
514 SubmitOrSend(op, opts, cl=cl)
518 def RenameInstance(opts, args):
519 """Rename an instance.
521 @param opts: the command line options selected by the user
523 @param args: should contain two elements, the old and the
526 @return: the desired exit code
529 if not opts.name_check:
530 if not AskUser("As you disabled the check of the DNS entry, please verify"
531 " that '%s' is a FQDN. Continue?" % args[1]):
534 op = opcodes.OpInstanceRename(instance_name=args[0],
536 ip_check=opts.ip_check,
537 name_check=opts.name_check)
538 result = SubmitOrSend(op, opts)
541 ToStdout("Instance '%s' renamed to '%s'", args[0], result)
546 def ActivateDisks(opts, args):
547 """Activate an instance's disks.
549 This serves two purposes:
550 - it allows (as long as the instance is not running)
551 mounting the disks and modifying them from the node
552 - it repairs inactive secondary drbds
554 @param opts: the command line options selected by the user
556 @param args: should contain only one element, the instance name
558 @return: the desired exit code
561 instance_name = args[0]
562 op = opcodes.OpInstanceActivateDisks(instance_name=instance_name,
563 ignore_size=opts.ignore_size)
564 disks_info = SubmitOrSend(op, opts)
565 for host, iname, nname in disks_info:
566 ToStdout("%s:%s:%s", host, iname, nname)
570 def DeactivateDisks(opts, args):
571 """Deactivate an instance's disks.
573 This function takes the instance name, looks for its primary node
574 and the tries to shutdown its block devices on that node.
576 @param opts: the command line options selected by the user
578 @param args: should contain only one element, the instance name
580 @return: the desired exit code
583 instance_name = args[0]
584 op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name,
586 SubmitOrSend(op, opts)
590 def RecreateDisks(opts, args):
591 """Recreate an instance's disks.
593 @param opts: the command line options selected by the user
595 @param args: should contain only one element, the instance name
597 @return: the desired exit code
600 instance_name = args[0]
603 opts.disks = [int(v) for v in opts.disks.split(",")]
604 except (ValueError, TypeError), err:
605 ToStderr("Invalid disks value: %s" % str(err))
610 op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
612 SubmitOrSend(op, opts)
616 def GrowDisk(opts, args):
617 """Grow an instance's disks.
619 @param opts: the command line options selected by the user
621 @param args: should contain two elements, the instance name
622 whose disks we grow and the disk name, e.g. I{sda}
624 @return: the desired exit code
631 except (TypeError, ValueError), err:
632 raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
634 amount = utils.ParseUnit(args[2])
635 op = opcodes.OpInstanceGrowDisk(instance_name=instance,
636 disk=disk, amount=amount,
637 wait_for_sync=opts.wait_for_sync)
638 SubmitOrSend(op, opts)
642 def _StartupInstance(name, opts):
643 """Startup instances.
645 This returns the opcode to start an instance, and its decorator will
646 wrap this into a loop starting all desired instances.
648 @param name: the name of the instance to act on
649 @param opts: the command line options selected by the user
650 @return: the opcode needed for the operation
653 op = opcodes.OpInstanceStartup(instance_name=name,
655 ignore_offline_nodes=opts.ignore_offline)
656 # do not add these parameters to the opcode unless they're defined
658 op.hvparams = opts.hvparams
660 op.beparams = opts.beparams
664 def _RebootInstance(name, opts):
665 """Reboot instance(s).
667 This returns the opcode to reboot an instance, and its decorator
668 will wrap this into a loop rebooting all desired instances.
670 @param name: the name of the instance to act on
671 @param opts: the command line options selected by the user
672 @return: the opcode needed for the operation
675 return opcodes.OpInstanceReboot(instance_name=name,
676 reboot_type=opts.reboot_type,
677 ignore_secondaries=opts.ignore_secondaries,
678 shutdown_timeout=opts.shutdown_timeout)
681 def _ShutdownInstance(name, opts):
682 """Shutdown an instance.
684 This returns the opcode to shutdown an instance, and its decorator
685 will wrap this into a loop shutting down all desired instances.
687 @param name: the name of the instance to act on
688 @param opts: the command line options selected by the user
689 @return: the opcode needed for the operation
692 return opcodes.OpInstanceShutdown(instance_name=name,
693 timeout=opts.timeout,
694 ignore_offline_nodes=opts.ignore_offline)
697 def ReplaceDisks(opts, args):
698 """Replace the disks of an instance
700 @param opts: the command line options selected by the user
702 @param args: should contain only one element, the instance name
704 @return: the desired exit code
707 new_2ndary = opts.dst_node
708 iallocator = opts.iallocator
709 if opts.disks is None:
713 disks = [int(i) for i in opts.disks.split(",")]
714 except (TypeError, ValueError), err:
715 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
717 cnt = [opts.on_primary, opts.on_secondary, opts.auto,
718 new_2ndary is not None, iallocator is not None].count(True)
720 raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -i"
721 " options must be passed", errors.ECODE_INVAL)
722 elif opts.on_primary:
723 mode = constants.REPLACE_DISK_PRI
724 elif opts.on_secondary:
725 mode = constants.REPLACE_DISK_SEC
727 mode = constants.REPLACE_DISK_AUTO
729 raise errors.OpPrereqError("Cannot specify disks when using automatic"
730 " mode", errors.ECODE_INVAL)
731 elif new_2ndary is not None or iallocator is not None:
733 mode = constants.REPLACE_DISK_CHG
735 op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
736 remote_node=new_2ndary, mode=mode,
737 iallocator=iallocator,
738 early_release=opts.early_release)
739 SubmitOrSend(op, opts)
743 def FailoverInstance(opts, args):
744 """Failover an instance.
746 The failover is done by shutting it down on its present node and
747 starting it on the secondary.
749 @param opts: the command line options selected by the user
751 @param args: should contain only one element, the instance name
753 @return: the desired exit code
757 instance_name = args[0]
759 iallocator = opts.iallocator
760 target_node = opts.dst_node
762 if iallocator and target_node:
763 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
764 " node (-n) but not both", errors.ECODE_INVAL)
767 _EnsureInstancesExist(cl, [instance_name])
769 usertext = ("Failover will happen to image %s."
770 " This requires a shutdown of the instance. Continue?" %
772 if not AskUser(usertext):
775 op = opcodes.OpInstanceFailover(instance_name=instance_name,
776 ignore_consistency=opts.ignore_consistency,
777 shutdown_timeout=opts.shutdown_timeout,
778 iallocator=iallocator,
779 target_node=target_node)
780 SubmitOrSend(op, opts, cl=cl)
784 def MigrateInstance(opts, args):
785 """Migrate an instance.
787 The migrate is done without shutdown.
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]
799 iallocator = opts.iallocator
800 target_node = opts.dst_node
802 if iallocator and target_node:
803 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
804 " node (-n) but not both", errors.ECODE_INVAL)
807 _EnsureInstancesExist(cl, [instance_name])
810 usertext = ("Instance %s will be recovered from a failed migration."
811 " Note that the migration procedure (including cleanup)" %
814 usertext = ("Instance %s will be migrated. Note that migration" %
816 usertext += (" might impact the instance if anything goes wrong"
817 " (e.g. due to bugs in the hypervisor). Continue?")
818 if not AskUser(usertext):
821 # this should be removed once --non-live is deprecated
822 if not opts.live and opts.migration_mode is not None:
823 raise errors.OpPrereqError("Only one of the --non-live and "
824 "--migration-mode options can be passed",
826 if not opts.live: # --non-live passed
827 mode = constants.HT_MIGRATION_NONLIVE
829 mode = opts.migration_mode
831 op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
832 cleanup=opts.cleanup, iallocator=iallocator,
833 target_node=target_node,
834 allow_failover=opts.allow_failover)
835 SubmitOpCode(op, cl=cl, opts=opts)
839 def MoveInstance(opts, args):
842 @param opts: the command line options selected by the user
844 @param args: should contain only one element, the instance name
846 @return: the desired exit code
850 instance_name = args[0]
854 usertext = ("Instance %s will be moved."
855 " This requires a shutdown of the instance. Continue?" %
857 if not AskUser(usertext):
860 op = opcodes.OpInstanceMove(instance_name=instance_name,
861 target_node=opts.node,
862 shutdown_timeout=opts.shutdown_timeout)
863 SubmitOrSend(op, opts, cl=cl)
867 def ConnectToInstanceConsole(opts, args):
868 """Connect to the console of an instance.
870 @param opts: the command line options selected by the user
872 @param args: should contain only one element, the instance name
874 @return: the desired exit code
877 instance_name = args[0]
879 op = opcodes.OpInstanceConsole(instance_name=instance_name)
883 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
884 console_data = SubmitOpCode(op, opts=opts, cl=cl)
886 # Ensure client connection is closed while external commands are run
891 return _DoConsole(objects.InstanceConsole.FromDict(console_data),
892 opts.show_command, cluster_name)
895 def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
896 _runcmd_fn=utils.RunCmd):
897 """Acts based on the result of L{opcodes.OpInstanceConsole}.
899 @type console: L{objects.InstanceConsole}
900 @param console: Console object
901 @type show_command: bool
902 @param show_command: Whether to just display commands
903 @type cluster_name: string
904 @param cluster_name: Cluster name as retrieved from master daemon
907 assert console.Validate()
909 if console.kind == constants.CONS_MESSAGE:
910 feedback_fn(console.message)
911 elif console.kind == constants.CONS_VNC:
912 feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
913 " URL <vnc://%s:%s/>",
914 console.instance, console.host, console.port,
915 console.display, console.host, console.port)
916 elif console.kind == constants.CONS_SSH:
917 # Convert to string if not already one
918 if isinstance(console.command, basestring):
919 cmd = console.command
921 cmd = utils.ShellQuoteArgs(console.command)
923 srun = ssh.SshRunner(cluster_name=cluster_name)
924 ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
925 batch=True, quiet=False, tty=True)
928 feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
930 result = _runcmd_fn(ssh_cmd, interactive=True)
932 logging.error("Console command \"%s\" failed with reason '%s' and"
933 " output %r", result.cmd, result.fail_reason,
935 raise errors.OpExecError("Connection to console of instance %s failed,"
936 " please check cluster configuration" %
939 raise errors.GenericError("Unknown console type '%s'" % console.kind)
941 return constants.EXIT_SUCCESS
944 def _FormatLogicalID(dev_type, logical_id, roman):
945 """Formats the logical_id of a disk.
948 if dev_type == constants.LD_DRBD8:
949 node_a, node_b, port, minor_a, minor_b, key = logical_id
951 ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
953 ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
955 ("port", compat.TryToRoman(port, convert=roman)),
958 elif dev_type == constants.LD_LV:
959 vg_name, lv_name = logical_id
960 data = ["%s/%s" % (vg_name, lv_name)]
962 data = [str(logical_id)]
967 def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
968 """Show block device information.
970 This is only used by L{ShowInstanceConfig}, but it's too big to be
971 left for an inline definition.
974 @param idx: the index of the current disk
975 @type top_level: boolean
976 @param top_level: if this a top-level disk?
978 @param dev: dictionary with disk information
979 @type static: boolean
980 @param static: wheter the device information doesn't contain
981 runtime information but only static data
983 @param roman: whether to try to use roman integers
984 @return: a list of either strings, tuples or lists
985 (which should be formatted at a higher indent level)
988 def helper(dtype, status):
989 """Format one line for physical device status.
992 @param dtype: a constant from the L{constants.LDS_BLOCK} set
994 @param status: a tuple as returned from L{backend.FindBlockDevice}
995 @return: the string representing the status
1001 (path, major, minor, syncp, estt, degr, ldisk_status) = status
1003 major_string = "N/A"
1005 major_string = str(compat.TryToRoman(major, convert=roman))
1008 minor_string = "N/A"
1010 minor_string = str(compat.TryToRoman(minor, convert=roman))
1012 txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1013 if dtype in (constants.LD_DRBD8, ):
1014 if syncp is not None:
1015 sync_text = "*RECOVERING* %5.2f%%," % syncp
1017 sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1019 sync_text += " ETA unknown"
1021 sync_text = "in sync"
1023 degr_text = "*DEGRADED*"
1026 if ldisk_status == constants.LDS_FAULTY:
1027 ldisk_text = " *MISSING DISK*"
1028 elif ldisk_status == constants.LDS_UNKNOWN:
1029 ldisk_text = " *UNCERTAIN STATE*"
1032 txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1033 elif dtype == constants.LD_LV:
1034 if ldisk_status == constants.LDS_FAULTY:
1035 ldisk_text = " *FAILED* (failed drive?)"
1043 if dev["iv_name"] is not None:
1044 txt = dev["iv_name"]
1046 txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1048 txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1049 if isinstance(dev["size"], int):
1050 nice_size = utils.FormatUnit(dev["size"], "h")
1052 nice_size = dev["size"]
1053 d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1056 data.append(("access mode", dev["mode"]))
1057 if dev["logical_id"] is not None:
1059 l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1061 l_id = [str(dev["logical_id"])]
1063 data.append(("logical_id", l_id[0]))
1066 elif dev["physical_id"] is not None:
1067 data.append("physical_id:")
1068 data.append([dev["physical_id"]])
1070 data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1071 if dev["sstatus"] and not static:
1072 data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1075 data.append("child devices:")
1076 for c_idx, child in enumerate(dev["children"]):
1077 data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
1082 def _FormatList(buf, data, indent_level):
1083 """Formats a list of data at a given indent level.
1085 If the element of the list is:
1086 - a string, it is simply formatted as is
1087 - a tuple, it will be split into key, value and the all the
1088 values in a list will be aligned all at the same start column
1089 - a list, will be recursively formatted
1092 @param buf: the buffer into which we write the output
1093 @param data: the list to format
1094 @type indent_level: int
1095 @param indent_level: the indent level to format at
1098 max_tlen = max([len(elem[0]) for elem in data
1099 if isinstance(elem, tuple)] or [0])
1101 if isinstance(elem, basestring):
1102 buf.write("%*s%s\n" % (2*indent_level, "", elem))
1103 elif isinstance(elem, tuple):
1105 spacer = "%*s" % (max_tlen - len(key), "")
1106 buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1107 elif isinstance(elem, list):
1108 _FormatList(buf, elem, indent_level+1)
1111 def ShowInstanceConfig(opts, args):
1112 """Compute instance run-time status.
1114 @param opts: the command line options selected by the user
1116 @param args: either an empty list, and then we query all
1117 instances, or should contain a list of instance names
1119 @return: the desired exit code
1122 if not args and not opts.show_all:
1123 ToStderr("No instance selected."
1124 " Please pass in --all if you want to query all instances.\n"
1125 "Note that this can take a long time on a big cluster.")
1127 elif args and opts.show_all:
1128 ToStderr("Cannot use --all if you specify instance names.")
1132 op = opcodes.OpInstanceQueryData(instances=args, static=opts.static)
1133 result = SubmitOpCode(op, opts=opts)
1135 ToStdout("No instances.")
1140 for instance_name in result:
1141 instance = result[instance_name]
1142 buf.write("Instance name: %s\n" % instance["name"])
1143 buf.write("UUID: %s\n" % instance["uuid"])
1144 buf.write("Serial number: %s\n" %
1145 compat.TryToRoman(instance["serial_no"],
1146 convert=opts.roman_integers))
1147 buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1148 buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1149 buf.write("State: configured to be %s" % instance["config_state"])
1151 buf.write(", actual state is %s" % instance["run_state"])
1153 ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1154 ## instance["auto_balance"])
1155 buf.write(" Nodes:\n")
1156 buf.write(" - primary: %s\n" % instance["pnode"])
1157 buf.write(" - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1158 buf.write(" Operating system: %s\n" % instance["os"])
1159 FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1161 if instance.has_key("network_port"):
1162 buf.write(" Allocated network port: %s\n" %
1163 compat.TryToRoman(instance["network_port"],
1164 convert=opts.roman_integers))
1165 buf.write(" Hypervisor: %s\n" % instance["hypervisor"])
1167 # custom VNC console information
1168 vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1170 if vnc_bind_address:
1171 port = instance["network_port"]
1172 display = int(port) - constants.VNC_BASE_PORT
1173 if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1174 vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1177 elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1178 vnc_console_port = ("%s:%s (node %s) (display %s)" %
1179 (vnc_bind_address, port,
1180 instance["pnode"], display))
1182 # vnc bind address is a file
1183 vnc_console_port = "%s:%s" % (instance["pnode"],
1185 buf.write(" - console connection: vnc to %s\n" % vnc_console_port)
1187 FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1189 buf.write(" Hardware:\n")
1190 buf.write(" - VCPUs: %s\n" %
1191 compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1192 convert=opts.roman_integers))
1193 buf.write(" - memory: %sMiB\n" %
1194 compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
1195 convert=opts.roman_integers))
1196 buf.write(" - NICs:\n")
1197 for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1198 buf.write(" - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1199 (idx, mac, ip, mode, link))
1200 buf.write(" Disk template: %s\n" % instance["disk_template"])
1201 buf.write(" Disks:\n")
1203 for idx, device in enumerate(instance["disks"]):
1204 _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static,
1205 opts.roman_integers), 2)
1207 ToStdout(buf.getvalue().rstrip('\n'))
1211 def SetInstanceParams(opts, args):
1212 """Modifies an instance.
1214 All parameters take effect only at the next restart of the instance.
1216 @param opts: the command line options selected by the user
1218 @param args: should contain only one element, the instance name
1220 @return: the desired exit code
1223 if not (opts.nics or opts.disks or opts.disk_template or
1224 opts.hvparams or opts.beparams or opts.os or opts.osparams):
1225 ToStderr("Please give at least one of the parameters.")
1228 for param in opts.beparams:
1229 if isinstance(opts.beparams[param], basestring):
1230 if opts.beparams[param].lower() == "default":
1231 opts.beparams[param] = constants.VALUE_DEFAULT
1233 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1234 allowed_values=[constants.VALUE_DEFAULT])
1236 for param in opts.hvparams:
1237 if isinstance(opts.hvparams[param], basestring):
1238 if opts.hvparams[param].lower() == "default":
1239 opts.hvparams[param] = constants.VALUE_DEFAULT
1241 utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1242 allowed_values=[constants.VALUE_DEFAULT])
1244 for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1246 nic_op = int(nic_op)
1247 opts.nics[idx] = (nic_op, nic_dict)
1248 except (TypeError, ValueError):
1251 for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1253 disk_op = int(disk_op)
1254 opts.disks[idx] = (disk_op, disk_dict)
1255 except (TypeError, ValueError):
1257 if disk_op == constants.DDM_ADD:
1258 if 'size' not in disk_dict:
1259 raise errors.OpPrereqError("Missing required parameter 'size'",
1261 disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1263 if (opts.disk_template and
1264 opts.disk_template in constants.DTS_INT_MIRROR and
1266 ToStderr("Changing the disk template to a mirrored one requires"
1267 " specifying a secondary node")
1270 op = opcodes.OpInstanceSetParams(instance_name=args[0],
1273 disk_template=opts.disk_template,
1274 remote_node=opts.node,
1275 hvparams=opts.hvparams,
1276 beparams=opts.beparams,
1278 osparams=opts.osparams,
1279 force_variant=opts.force_variant,
1282 # even if here we process the result, we allow submit only
1283 result = SubmitOrSend(op, opts)
1286 ToStdout("Modified instance %s", args[0])
1287 for param, data in result:
1288 ToStdout(" - %-5s -> %s", param, data)
1289 ToStdout("Please don't forget that most parameters take effect"
1290 " only at the next start of the instance.")
1294 # multi-instance selection options
1295 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1296 help="Do not ask for confirmation when more than"
1297 " one instance is affected",
1298 action="store_true", default=False)
1300 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1301 help="Filter by nodes (primary only)",
1302 const=_SHUTDOWN_NODES_PRI, action="store_const")
1304 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1305 help="Filter by nodes (secondary only)",
1306 const=_SHUTDOWN_NODES_SEC, action="store_const")
1308 m_node_opt = cli_option("--node", dest="multi_mode",
1309 help="Filter by nodes (primary and secondary)",
1310 const=_SHUTDOWN_NODES_BOTH, action="store_const")
1312 m_clust_opt = cli_option("--all", dest="multi_mode",
1313 help="Select all instances in the cluster",
1314 const=_SHUTDOWN_CLUSTER, action="store_const")
1316 m_inst_opt = cli_option("--instance", dest="multi_mode",
1317 help="Filter by instance name [default]",
1318 const=_SHUTDOWN_INSTANCES, action="store_const")
1320 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1321 help="Filter by node tag",
1322 const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1323 action="store_const")
1325 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1326 help="Filter by primary node tag",
1327 const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1328 action="store_const")
1330 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1331 help="Filter by secondary node tag",
1332 const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1333 action="store_const")
1335 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1336 help="Filter by instance tag",
1337 const=_SHUTDOWN_INSTANCES_BY_TAGS,
1338 action="store_const")
1340 # this is defined separately due to readability only
1350 AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1351 "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1352 "Creates and adds a new instance to the cluster"),
1354 BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1356 "Create a bunch of instances based on specs in the file."),
1358 ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1359 [SHOWCMD_OPT, PRIORITY_OPT],
1360 "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1362 FailoverInstance, ARGS_ONE_INSTANCE,
1363 [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1364 DRY_RUN_OPT, PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT],
1365 "[-f] <instance>", "Stops the instance and starts it on the backup node,"
1366 " using the remote mirror (only for mirrored instances)"),
1368 MigrateInstance, ARGS_ONE_INSTANCE,
1369 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1370 PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT, ALLOW_FAILOVER_OPT],
1371 "[-f] <instance>", "Migrate instance to its secondary node"
1372 " (only for mirrored instances)"),
1374 MoveInstance, ARGS_ONE_INSTANCE,
1375 [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1376 DRY_RUN_OPT, PRIORITY_OPT],
1377 "[-f] <instance>", "Move instance to an arbitrary node"
1378 " (only for instances of type file and lv)"),
1380 ShowInstanceConfig, ARGS_MANY_INSTANCES,
1381 [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1382 "[-s] {--all | <instance>...}",
1383 "Show information on the specified instance(s)"),
1385 ListInstances, ARGS_MANY_INSTANCES,
1386 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
1389 "Lists the instances and their status. The available fields can be shown"
1390 " using the \"list-fields\" command (see the man page for details)."
1391 " The default field list is (in order): %s." %
1392 utils.CommaJoin(_LIST_DEF_FIELDS),
1395 ListInstanceFields, [ArgUnknown()],
1396 [NOHDR_OPT, SEP_OPT],
1398 "Lists all available fields for instances"),
1400 ReinstallInstance, [ArgInstance()],
1401 [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1402 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1403 m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1404 SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1405 "[-f] <instance>", "Reinstall a stopped instance"),
1407 RemoveInstance, ARGS_ONE_INSTANCE,
1408 [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1409 DRY_RUN_OPT, PRIORITY_OPT],
1410 "[-f] <instance>", "Shuts down the instance and removes it"),
1413 [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1414 [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1415 "<instance> <new_name>", "Rename the instance"),
1417 ReplaceDisks, ARGS_ONE_INSTANCE,
1418 [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1419 NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1420 DRY_RUN_OPT, PRIORITY_OPT],
1421 "[-s|-p|-n NODE|-I NAME] <instance>",
1422 "Replaces all disks for the instance"),
1424 SetInstanceParams, ARGS_ONE_INSTANCE,
1425 [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1426 DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1427 OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1428 "<instance>", "Alters the parameters of an instance"),
1430 GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1431 [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1432 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1433 m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1434 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1435 "<instance>", "Stops an instance"),
1437 GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1438 [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1439 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1440 m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1441 BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1442 "<instance>", "Starts an instance"),
1444 GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1445 [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1446 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1447 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1448 m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1449 "<instance>", "Reboots an instance"),
1451 ActivateDisks, ARGS_ONE_INSTANCE,
1452 [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
1453 "<instance>", "Activate an instance's disks"),
1454 'deactivate-disks': (
1455 DeactivateDisks, ARGS_ONE_INSTANCE,
1456 [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1457 "[-f] <instance>", "Deactivate an instance's disks"),
1459 RecreateDisks, ARGS_ONE_INSTANCE,
1460 [SUBMIT_OPT, DISKIDX_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1461 "<instance>", "Recreate an instance's disks"),
1464 [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1465 ArgUnknown(min=1, max=1)],
1466 [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1467 "<instance> <disk> <size>", "Grow an instance's disk"),
1469 ListTags, ARGS_ONE_INSTANCE, [PRIORITY_OPT],
1470 "<instance_name>", "List the tags of the given instance"),
1472 AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1473 [TAG_SRC_OPT, PRIORITY_OPT],
1474 "<instance_name> tag...", "Add tags to the given instance"),
1476 RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1477 [TAG_SRC_OPT, PRIORITY_OPT],
1478 "<instance_name> tag...", "Remove tags from given instance"),
1481 #: dictionary with aliases for commands
1489 return GenericMain(commands, aliases=aliases,
1490 override={"tag_type": constants.TAG_INSTANCE})