Slighly improve multi-nic in gnt-instance info
[ganeti-local] / scripts / gnt-instance
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2006, 2007 Google Inc.
5 #
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.
10 #
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.
15 #
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
19 # 02110-1301, USA.
20
21
22 # pylint: disable-msg=W0401,W0614
23 # W0401: Wildcard import ganeti.cli
24 # W0614: Unused import %s from wildcard import (since we need cli)
25
26 import sys
27 import os
28 import itertools
29 import simplejson
30 from optparse import make_option
31 from cStringIO import StringIO
32
33 from ganeti.cli import *
34 from ganeti import cli
35 from ganeti import opcodes
36 from ganeti import constants
37 from ganeti import utils
38 from ganeti import errors
39
40
41 _SHUTDOWN_CLUSTER = "cluster"
42 _SHUTDOWN_NODES_BOTH = "nodes"
43 _SHUTDOWN_NODES_PRI = "nodes-pri"
44 _SHUTDOWN_NODES_SEC = "nodes-sec"
45 _SHUTDOWN_INSTANCES = "instances"
46
47
48 _VALUE_TRUE = "true"
49
50 #: default list of options for L{ListInstances}
51 _LIST_DEF_FIELDS = [
52   "name", "hypervisor", "os", "pnode", "status", "oper_ram",
53   ]
54
55
56 def _ExpandMultiNames(mode, names):
57   """Expand the given names using the passed mode.
58
59   For _SHUTDOWN_CLUSTER, all instances will be returned. For
60   _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
61   primary/secondary will be returned. For _SHUTDOWN_NODES_BOTH, all
62   instances having those nodes as either primary or secondary will be
63   returned. For _SHUTDOWN_INSTANCES, the given instances will be
64   returned.
65
66   @param mode: one of L{_SHUTDOWN_CLUSTER}, L{_SHUTDOWN_NODES_BOTH},
67       L{_SHUTDOWN_NODES_PRI}, L{_SHUTDOWN_NODES_SEC} or
68       L{_SHUTDOWN_INSTANCES}
69   @param names: a list of names; for cluster, it must be empty,
70       and for node and instance it must be a list of valid item
71       names (short names are valid as usual, e.g. node1 instead of
72       node1.example.com)
73   @rtype: list
74   @return: the list of names after the expansion
75   @raise errors.ProgrammerError: for unknown selection type
76   @raise errors.OpPrereqError: for invalid input parameters
77
78   """
79   if mode == _SHUTDOWN_CLUSTER:
80     if names:
81       raise errors.OpPrereqError("Cluster filter mode takes no arguments")
82     client = GetClient()
83     idata = client.QueryInstances([], ["name"])
84     inames = [row[0] for row in idata]
85
86   elif mode in (_SHUTDOWN_NODES_BOTH,
87                 _SHUTDOWN_NODES_PRI,
88                 _SHUTDOWN_NODES_SEC):
89     if not names:
90       raise errors.OpPrereqError("No node names passed")
91     client = GetClient()
92     ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"])
93     ipri = [row[1] for row in ndata]
94     pri_names = list(itertools.chain(*ipri))
95     isec = [row[2] for row in ndata]
96     sec_names = list(itertools.chain(*isec))
97     if mode == _SHUTDOWN_NODES_BOTH:
98       inames = pri_names + sec_names
99     elif mode == _SHUTDOWN_NODES_PRI:
100       inames = pri_names
101     elif mode == _SHUTDOWN_NODES_SEC:
102       inames = sec_names
103     else:
104       raise errors.ProgrammerError("Unhandled shutdown type")
105
106   elif mode == _SHUTDOWN_INSTANCES:
107     if not names:
108       raise errors.OpPrereqError("No instance names passed")
109     client = GetClient()
110     idata = client.QueryInstances(names, ["name"])
111     inames = [row[0] for row in idata]
112
113   else:
114     raise errors.OpPrereqError("Unknown mode '%s'" % mode)
115
116   return inames
117
118
119 def _ConfirmOperation(inames, text):
120   """Ask the user to confirm an operation on a list of instances.
121
122   This function is used to request confirmation for doing an operation
123   on a given list of instances.
124
125   @type inames: list
126   @param inames: the list of names that we display when
127       we ask for confirmation
128   @type text: str
129   @param text: the operation that the user should confirm
130       (e.g. I{shutdown} or I{startup})
131   @rtype: boolean
132   @return: True or False depending on user's confirmation.
133
134   """
135   count = len(inames)
136   msg = ("The %s will operate on %d instances.\n"
137          "Do you want to continue?" % (text, count))
138   affected = ("\nAffected instances:\n" +
139               "\n".join(["  %s" % name for name in inames]))
140
141   choices = [('y', True, 'Yes, execute the %s' % text),
142              ('n', False, 'No, abort the %s' % text)]
143
144   if count > 20:
145     choices.insert(1, ('v', 'v', 'View the list of affected instances'))
146     ask = msg
147   else:
148     ask = msg + affected
149
150   choice = AskUser(ask, choices)
151   if choice == 'v':
152     choices.pop(1)
153     choice = AskUser(msg + affected, choices)
154   return choice
155
156
157 def _TransformPath(user_input):
158   """Transform a user path into a canonical value.
159
160   This function transforms the a path passed as textual information
161   into the constants that the LU code expects.
162
163   """
164   if user_input:
165     if user_input.lower() == "default":
166       result_path = constants.VALUE_DEFAULT
167     elif user_input.lower() == "none":
168       result_path = constants.VALUE_NONE
169     else:
170       if not os.path.isabs(user_input):
171         raise errors.OpPrereqError("Path '%s' is not an absolute filename" %
172                                    user_input)
173       result_path = user_input
174   else:
175     result_path = constants.VALUE_DEFAULT
176
177   return result_path
178
179
180 def ListInstances(opts, args):
181   """List instances and their properties.
182
183   @param opts: the command line options selected by the user
184   @type args: list
185   @param args: should be an empty list
186   @rtype: int
187   @return: the desired exit code
188
189   """
190   if opts.output is None:
191     selected_fields = _LIST_DEF_FIELDS
192   elif opts.output.startswith("+"):
193     selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
194   else:
195     selected_fields = opts.output.split(",")
196
197   output = GetClient().QueryInstances([], selected_fields)
198
199   if not opts.no_headers:
200     headers = {
201       "name": "Instance", "os": "OS", "pnode": "Primary_node",
202       "snodes": "Secondary_Nodes", "admin_state": "Autostart",
203       "oper_state": "Running",
204       "oper_ram": "Memory", "disk_template": "Disk_template",
205       "ip": "IP_address", "mac": "MAC_address",
206       "bridge": "Bridge",
207       "sda_size": "Disk/0", "sdb_size": "Disk/1",
208       "status": "Status", "tags": "Tags",
209       "network_port": "Network_port",
210       "hv/kernel_path": "Kernel_path",
211       "hv/initrd_path": "Initrd_path",
212       "hv/boot_order": "HVM_boot_order",
213       "hv/acpi": "HVM_ACPI",
214       "hv/pae": "HVM_PAE",
215       "hv/cdrom_image_path": "HVM_CDROM_image_path",
216       "hv/nic_type": "HVM_NIC_type",
217       "hv/disk_type": "HVM_Disk_type",
218       "hv/vnc_bind_address": "VNC_bind_address",
219       "serial_no": "SerialNo", "hypervisor": "Hypervisor",
220       "hvparams": "Hypervisor_parameters",
221       "be/memory": "Configured_memory",
222       "be/vcpus": "VCPUs",
223       "be/auto_balance": "Auto_balance",
224       "disk.count": "Disks", "disk.sizes": "Disk_sizes",
225       "nic.count": "NICs", "nic.ips": "NIC_IPs",
226       "nic.bridges": "NIC_bridges", "nic.macs": "NIC_MACs",
227       }
228   else:
229     headers = None
230
231   unitfields = ["be/memory", "oper_ram", "sd(a|b)_size", "disk\.size/.*"]
232   numfields = ["be/memory", "oper_ram", "sd(a|b)_size", "be/vcpus",
233                "serial_no", "(disk|nic)\.count", "disk\.size/.*"]
234
235   list_type_fields = ("tags", "disk.sizes",
236                       "nic.macs", "nic.ips", "nic.bridges")
237   # change raw values to nicer strings
238   for row in output:
239     for idx, field in enumerate(selected_fields):
240       val = row[idx]
241       if field == "snodes":
242         val = ",".join(val) or "-"
243       elif field == "admin_state":
244         if val:
245           val = "yes"
246         else:
247           val = "no"
248       elif field == "oper_state":
249         if val is None:
250           val = "(node down)"
251         elif val: # True
252           val = "running"
253         else:
254           val = "stopped"
255       elif field == "oper_ram":
256         if val is None:
257           val = "(node down)"
258       elif field == "sda_size" or field == "sdb_size":
259         if val is None:
260           val = "N/A"
261       elif field in list_type_fields:
262         val = ",".join(str(item) for item in val)
263       elif val is None:
264         val = "-"
265       row[idx] = str(val)
266
267   data = GenerateTable(separator=opts.separator, headers=headers,
268                        fields=selected_fields, unitfields=unitfields,
269                        numfields=numfields, data=output, units=opts.units)
270
271   for line in data:
272     ToStdout(line)
273
274   return 0
275
276
277 def AddInstance(opts, args):
278   """Add an instance to the cluster.
279
280   @param opts: the command line options selected by the user
281   @type args: list
282   @param args: should contain only one element, the new instance name
283   @rtype: int
284   @return: the desired exit code
285
286   """
287   instance = args[0]
288
289   (pnode, snode) = SplitNodeOption(opts.node)
290
291   hypervisor = None
292   hvparams = {}
293   if opts.hypervisor:
294     hypervisor, hvparams = opts.hypervisor
295
296   if opts.nics:
297     try:
298       nic_max = max(int(nidx[0])+1 for nidx in opts.nics)
299     except ValueError, err:
300       raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
301     nics = [{}] * nic_max
302     for nidx, ndict in opts.nics.items():
303       nidx = int(nidx)
304       nics[nidx] = ndict
305   else:
306     # default of one nic, all auto
307     nics = [{}]
308
309   if not opts.disks and opts.disk_template != constants.DT_DISKLESS:
310     raise errors.OpPrereqError("No disk information specified")
311   elif opts.disks and opts.disk_template == constants.DT_DISKLESS:
312     raise errors.OpPrereqError("Diskless instance but disk information passeD")
313   else:
314     try:
315       disk_max = max(int(didx[0])+1 for didx in opts.disks)
316     except ValueError, err:
317       raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
318     disks = [{}] * disk_max
319     for didx, ddict in opts.disks:
320       didx = int(didx)
321       if "size" not in ddict:
322         raise errors.OpPrereqError("Missing size for disk %d" % didx)
323       try:
324         ddict["size"] = utils.ParseUnit(ddict["size"])
325       except ValueError, err:
326         raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
327                                    (didx, err))
328       disks[didx] = ddict
329
330   ValidateBeParams(opts.beparams)
331
332 ##  kernel_path = _TransformPath(opts.kernel_path)
333 ##  initrd_path = _TransformPath(opts.initrd_path)
334
335 ##  hvm_acpi = opts.hvm_acpi == _VALUE_TRUE
336 ##  hvm_pae = opts.hvm_pae == _VALUE_TRUE
337
338 ##  if ((opts.hvm_cdrom_image_path is not None) and
339 ##      (opts.hvm_cdrom_image_path.lower() == constants.VALUE_NONE)):
340 ##    hvm_cdrom_image_path = None
341 ##  else:
342 ##    hvm_cdrom_image_path = opts.hvm_cdrom_image_path
343
344   op = opcodes.OpCreateInstance(instance_name=instance,
345                                 disks=disks,
346                                 disk_template=opts.disk_template,
347                                 nics=nics,
348                                 mode=constants.INSTANCE_CREATE,
349                                 os_type=opts.os, pnode=pnode,
350                                 snode=snode,
351                                 start=opts.start, ip_check=opts.ip_check,
352                                 wait_for_sync=opts.wait_for_sync,
353                                 hypervisor=hypervisor,
354                                 hvparams=hvparams,
355                                 beparams=opts.beparams,
356                                 iallocator=opts.iallocator,
357                                 file_storage_dir=opts.file_storage_dir,
358                                 file_driver=opts.file_driver,
359                                 )
360
361   SubmitOrSend(op, opts)
362   return 0
363
364
365 def BatchCreate(opts, args):
366   """Create instances using a definition file.
367
368   This function reads a json file with instances defined
369   in the form::
370
371     {"instance-name":{
372       "disk_size": 25,
373       "swap_size": 1024,
374       "template": "drbd",
375       "backend": {
376         "memory": 512,
377         "vcpus": 1 },
378       "os": "etch-image",
379       "primary_node": "firstnode",
380       "secondary_node": "secondnode",
381       "iallocator": "dumb"}
382     }
383
384   Note that I{primary_node} and I{secondary_node} have precedence over
385   I{iallocator}.
386
387   @param opts: the command line options selected by the user
388   @type args: list
389   @param args: should contain one element, the json filename
390   @rtype: int
391   @return: the desired exit code
392
393   """
394   _DEFAULT_SPECS = {"disk_size": 20 * 1024,
395                     "swap_size": 4 * 1024,
396                     "backend": {},
397                     "iallocator": None,
398                     "primary_node": None,
399                     "secondary_node": None,
400                     "ip": 'none',
401                     "mac": 'auto',
402                     "bridge": None,
403                     "start": True,
404                     "ip_check": True,
405                     "hypervisor": None,
406                     "file_storage_dir": None,
407                     "file_driver": 'loop'}
408
409   def _PopulateWithDefaults(spec):
410     """Returns a new hash combined with default values."""
411     mydict = _DEFAULT_SPECS.copy()
412     mydict.update(spec)
413     return mydict
414
415   def _Validate(spec):
416     """Validate the instance specs."""
417     # Validate fields required under any circumstances
418     for required_field in ('os', 'template'):
419       if required_field not in spec:
420         raise errors.OpPrereqError('Required field "%s" is missing.' %
421                                    required_field)
422     # Validate special fields
423     if spec['primary_node'] is not None:
424       if (spec['template'] in constants.DTS_NET_MIRROR and
425           spec['secondary_node'] is None):
426         raise errors.OpPrereqError('Template requires secondary node, but'
427                                    ' there was no secondary provided.')
428     elif spec['iallocator'] is None:
429       raise errors.OpPrereqError('You have to provide at least a primary_node'
430                                  ' or an iallocator.')
431
432     if (spec['hypervisor'] and
433         not isinstance(spec['hypervisor'], dict)):
434       raise errors.OpPrereqError('Hypervisor parameters must be a dict.')
435
436   json_filename = args[0]
437   fd = open(json_filename, 'r')
438   try:
439     instance_data = simplejson.load(fd)
440   finally:
441     fd.close()
442
443   # Iterate over the instances and do:
444   #  * Populate the specs with default value
445   #  * Validate the instance specs
446   for (name, specs) in instance_data.iteritems():
447     specs = _PopulateWithDefaults(specs)
448     _Validate(specs)
449
450     hypervisor = None
451     hvparams = {}
452     if specs['hypervisor']:
453       hypervisor, hvparams = specs['hypervisor'].iteritems()
454
455     op = opcodes.OpCreateInstance(instance_name=name,
456                                   disk_size=specs['disk_size'],
457                                   swap_size=specs['swap_size'],
458                                   disk_template=specs['template'],
459                                   mode=constants.INSTANCE_CREATE,
460                                   os_type=specs['os'],
461                                   pnode=specs['primary_node'],
462                                   snode=specs['secondary_node'],
463                                   ip=specs['ip'], bridge=specs['bridge'],
464                                   start=specs['start'],
465                                   ip_check=specs['ip_check'],
466                                   wait_for_sync=True,
467                                   mac=specs['mac'],
468                                   iallocator=specs['iallocator'],
469                                   hypervisor=hypervisor,
470                                   hvparams=hvparams,
471                                   beparams=specs['backend'],
472                                   file_storage_dir=specs['file_storage_dir'],
473                                   file_driver=specs['file_driver'])
474
475     ToStdout("%s: %s", name, cli.SendJob([op]))
476
477   return 0
478
479
480 def ReinstallInstance(opts, args):
481   """Reinstall an instance.
482
483   @param opts: the command line options selected by the user
484   @type args: list
485   @param args: should contain only one element, the name of the
486       instance to be reinstalled
487   @rtype: int
488   @return: the desired exit code
489
490   """
491   instance_name = args[0]
492
493   if opts.select_os is True:
494     op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
495     result = SubmitOpCode(op)
496
497     if not result:
498       ToStdout("Can't get the OS list")
499       return 1
500
501     ToStdout("Available OS templates:")
502     number = 0
503     choices = []
504     for entry in result:
505       ToStdout("%3s: %s", number, entry[0])
506       choices.append(("%s" % number, entry[0], entry[0]))
507       number = number + 1
508
509     choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
510     selected = AskUser("Enter OS template name or number (or x to abort):",
511                        choices)
512
513     if selected == 'exit':
514       ToStdout("User aborted reinstall, exiting")
515       return 1
516
517     os_name = selected
518   else:
519     os_name = opts.os
520
521   if not opts.force:
522     usertext = ("This will reinstall the instance %s and remove"
523                 " all data. Continue?") % instance_name
524     if not AskUser(usertext):
525       return 1
526
527   op = opcodes.OpReinstallInstance(instance_name=instance_name,
528                                    os_type=os_name)
529   SubmitOrSend(op, opts)
530
531   return 0
532
533
534 def RemoveInstance(opts, args):
535   """Remove an instance.
536
537   @param opts: the command line options selected by the user
538   @type args: list
539   @param args: should contain only one element, the name of
540       the instance to be removed
541   @rtype: int
542   @return: the desired exit code
543
544   """
545   instance_name = args[0]
546   force = opts.force
547
548   if not force:
549     usertext = ("This will remove the volumes of the instance %s"
550                 " (including mirrors), thus removing all the data"
551                 " of the instance. Continue?") % instance_name
552     if not AskUser(usertext):
553       return 1
554
555   op = opcodes.OpRemoveInstance(instance_name=instance_name,
556                                 ignore_failures=opts.ignore_failures)
557   SubmitOrSend(op, opts)
558   return 0
559
560
561 def RenameInstance(opts, args):
562   """Rename an instance.
563
564   @param opts: the command line options selected by the user
565   @type args: list
566   @param args: should contain two elements, the old and the
567       new instance names
568   @rtype: int
569   @return: the desired exit code
570
571   """
572   op = opcodes.OpRenameInstance(instance_name=args[0],
573                                 new_name=args[1],
574                                 ignore_ip=opts.ignore_ip)
575   SubmitOrSend(op, opts)
576   return 0
577
578
579 def ActivateDisks(opts, args):
580   """Activate an instance's disks.
581
582   This serves two purposes:
583     - it allows (as long as the instance is not running)
584       mounting the disks and modifying them from the node
585     - it repairs inactive secondary drbds
586
587   @param opts: the command line options selected by the user
588   @type args: list
589   @param args: should contain only one element, the instance name
590   @rtype: int
591   @return: the desired exit code
592
593   """
594   instance_name = args[0]
595   op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
596   disks_info = SubmitOrSend(op, opts)
597   for host, iname, nname in disks_info:
598     ToStdout("%s:%s:%s", host, iname, nname)
599   return 0
600
601
602 def DeactivateDisks(opts, args):
603   """Deactivate an instance's disks..
604
605   This function takes the instance name, looks for its primary node
606   and the tries to shutdown its block devices on that node.
607
608   @param opts: the command line options selected by the user
609   @type args: list
610   @param args: should contain only one element, the instance name
611   @rtype: int
612   @return: the desired exit code
613
614   """
615   instance_name = args[0]
616   op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
617   SubmitOrSend(op, opts)
618   return 0
619
620
621 def GrowDisk(opts, args):
622   """Grow an instance's disks.
623
624   @param opts: the command line options selected by the user
625   @type args: list
626   @param args: should contain two elements, the instance name
627       whose disks we grow and the disk name, e.g. I{sda}
628   @rtype: int
629   @return: the desired exit code
630
631   """
632   instance = args[0]
633   disk = args[1]
634   try:
635     disk = int(disk)
636   except ValueError, err:
637     raise errors.OpPrereqError("Invalid disk index: %s" % str(err))
638   amount = utils.ParseUnit(args[2])
639   op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
640                           wait_for_sync=opts.wait_for_sync)
641   SubmitOrSend(op, opts)
642   return 0
643
644
645 def StartupInstance(opts, args):
646   """Startup instances.
647
648   Depending on the options given, this will start one or more
649   instances.
650
651   @param opts: the command line options selected by the user
652   @type args: list
653   @param args: the instance or node names based on which we
654       create the final selection (in conjunction with the
655       opts argument)
656   @rtype: int
657   @return: the desired exit code
658
659   """
660   if opts.multi_mode is None:
661     opts.multi_mode = _SHUTDOWN_INSTANCES
662   inames = _ExpandMultiNames(opts.multi_mode, args)
663   if not inames:
664     raise errors.OpPrereqError("Selection filter does not match any instances")
665   multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
666   if not (opts.force_multi or not multi_on
667           or _ConfirmOperation(inames, "startup")):
668     return 1
669   for name in inames:
670     op = opcodes.OpStartupInstance(instance_name=name,
671                                    force=opts.force,
672                                    extra_args=opts.extra_args)
673     if multi_on:
674       ToStdout("Starting up %s", name)
675     try:
676       SubmitOrSend(op, opts)
677     except JobSubmittedException, err:
678       _, txt = FormatError(err)
679       ToStdout("%s", txt)
680   return 0
681
682
683 def RebootInstance(opts, args):
684   """Reboot instance(s).
685
686   Depending on the parameters given, this will reboot one or more
687   instances.
688
689   @param opts: the command line options selected by the user
690   @type args: list
691   @param args: the instance or node names based on which we
692       create the final selection (in conjunction with the
693       opts argument)
694   @rtype: int
695   @return: the desired exit code
696
697   """
698   if opts.multi_mode is None:
699     opts.multi_mode = _SHUTDOWN_INSTANCES
700   inames = _ExpandMultiNames(opts.multi_mode, args)
701   if not inames:
702     raise errors.OpPrereqError("Selection filter does not match any instances")
703   multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
704   if not (opts.force_multi or not multi_on
705           or _ConfirmOperation(inames, "reboot")):
706     return 1
707   for name in inames:
708     op = opcodes.OpRebootInstance(instance_name=name,
709                                   reboot_type=opts.reboot_type,
710                                   ignore_secondaries=opts.ignore_secondaries)
711
712     SubmitOrSend(op, opts)
713   return 0
714
715
716 def ShutdownInstance(opts, args):
717   """Shutdown an instance.
718
719   @param opts: the command line options selected by the user
720   @type args: list
721   @param args: the instance or node names based on which we
722       create the final selection (in conjunction with the
723       opts argument)
724   @rtype: int
725   @return: the desired exit code
726
727   """
728   if opts.multi_mode is None:
729     opts.multi_mode = _SHUTDOWN_INSTANCES
730   inames = _ExpandMultiNames(opts.multi_mode, args)
731   if not inames:
732     raise errors.OpPrereqError("Selection filter does not match any instances")
733   multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
734   if not (opts.force_multi or not multi_on
735           or _ConfirmOperation(inames, "shutdown")):
736     return 1
737   for name in inames:
738     op = opcodes.OpShutdownInstance(instance_name=name)
739     if multi_on:
740       ToStdout("Shutting down %s", name)
741     try:
742       SubmitOrSend(op, opts)
743     except JobSubmittedException, err:
744       _, txt = FormatError(err)
745       ToStdout("%s", txt)
746   return 0
747
748
749 def ReplaceDisks(opts, args):
750   """Replace the disks of an instance
751
752   @param opts: the command line options selected by the user
753   @type args: list
754   @param args: should contain only one element, the instance name
755   @rtype: int
756   @return: the desired exit code
757
758   """
759   instance_name = args[0]
760   new_2ndary = opts.new_secondary
761   iallocator = opts.iallocator
762   if opts.disks is None:
763     disks = []
764   else:
765     try:
766       disks = [int(i) for i in opts.disks.split(",")]
767     except ValueError, err:
768       raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
769   if opts.on_primary == opts.on_secondary: # no -p or -s passed, or both passed
770     mode = constants.REPLACE_DISK_ALL
771   elif opts.on_primary: # only on primary:
772     mode = constants.REPLACE_DISK_PRI
773     if new_2ndary is not None or iallocator is not None:
774       raise errors.OpPrereqError("Can't change secondary node on primary disk"
775                                  " replacement")
776   elif opts.on_secondary is not None or iallocator is not None:
777     # only on secondary
778     mode = constants.REPLACE_DISK_SEC
779
780   op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
781                               remote_node=new_2ndary, mode=mode,
782                               iallocator=iallocator)
783   SubmitOrSend(op, opts)
784   return 0
785
786
787 def FailoverInstance(opts, args):
788   """Failover an instance.
789
790   The failover is done by shutting it down on its present node and
791   starting it on the secondary.
792
793   @param opts: the command line options selected by the user
794   @type args: list
795   @param args: should contain only one element, the instance name
796   @rtype: int
797   @return: the desired exit code
798
799   """
800   instance_name = args[0]
801   force = opts.force
802
803   if not force:
804     usertext = ("Failover will happen to image %s."
805                 " This requires a shutdown of the instance. Continue?" %
806                 (instance_name,))
807     if not AskUser(usertext):
808       return 1
809
810   op = opcodes.OpFailoverInstance(instance_name=instance_name,
811                                   ignore_consistency=opts.ignore_consistency)
812   SubmitOrSend(op, opts)
813   return 0
814
815
816 def ConnectToInstanceConsole(opts, args):
817   """Connect to the console of an instance.
818
819   @param opts: the command line options selected by the user
820   @type args: list
821   @param args: should contain only one element, the instance name
822   @rtype: int
823   @return: the desired exit code
824
825   """
826   instance_name = args[0]
827
828   op = opcodes.OpConnectConsole(instance_name=instance_name)
829   cmd = SubmitOpCode(op)
830
831   if opts.show_command:
832     ToStdout("%s", utils.ShellQuoteArgs(cmd))
833   else:
834     try:
835       os.execvp(cmd[0], cmd)
836     finally:
837       ToStderr("Can't run console command %s with arguments:\n'%s'",
838                cmd[0], " ".join(cmd))
839       os._exit(1)
840
841
842 def _FormatBlockDevInfo(buf, dev, indent_level, static):
843   """Show block device information.
844
845   This is only used by L{ShowInstanceConfig}, but it's too big to be
846   left for an inline definition.
847
848   @type buf: StringIO
849   @param buf: buffer that will accumulate the output
850   @type dev: dict
851   @param dev: dictionary with disk information
852   @type indent_level: int
853   @param indent_level: the indendation level we are at, used for
854       the layout of the device tree
855   @type static: boolean
856   @param static: wheter the device information doesn't contain
857       runtime information but only static data
858
859   """
860   def helper(buf, dtype, status):
861     """Format one line for physical device status.
862
863     @type buf: StringIO
864     @param buf: buffer that will accumulate the output
865     @type dtype: str
866     @param dtype: a constant from the L{constants.LDS_BLOCK} set
867     @type status: tuple
868     @param status: a tuple as returned from L{backend.FindBlockDevice}
869
870     """
871     if not status:
872       buf.write("not active\n")
873     else:
874       (path, major, minor, syncp, estt, degr, ldisk) = status
875       if major is None:
876         major_string = "N/A"
877       else:
878         major_string = str(major)
879
880       if minor is None:
881         minor_string = "N/A"
882       else:
883         minor_string = str(minor)
884
885       buf.write("%s (%s:%s)" % (path, major_string, minor_string))
886       if dtype in (constants.LD_DRBD8, ):
887         if syncp is not None:
888           sync_text = "*RECOVERING* %5.2f%%," % syncp
889           if estt:
890             sync_text += " ETA %ds" % estt
891           else:
892             sync_text += " ETA unknown"
893         else:
894           sync_text = "in sync"
895         if degr:
896           degr_text = "*DEGRADED*"
897         else:
898           degr_text = "ok"
899         if ldisk:
900           ldisk_text = " *MISSING DISK*"
901         else:
902           ldisk_text = ""
903         buf.write(" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
904       elif dtype == constants.LD_LV:
905         if ldisk:
906           ldisk_text = " *FAILED* (failed drive?)"
907         else:
908           ldisk_text = ""
909         buf.write(ldisk_text)
910       buf.write("\n")
911
912   if dev["iv_name"] is not None:
913     data = "  - %s, " % dev["iv_name"]
914   else:
915     data = "  - "
916   data += "type: %s" % dev["dev_type"]
917   if dev["logical_id"] is not None:
918     data += ", logical_id: %s" % (dev["logical_id"],)
919   elif dev["physical_id"] is not None:
920     data += ", physical_id: %s" % (dev["physical_id"],)
921   buf.write("%*s%s\n" % (2*indent_level, "", data))
922   if not static:
923     buf.write("%*s    primary:   " % (2*indent_level, ""))
924     helper(buf, dev["dev_type"], dev["pstatus"])
925
926   if dev["sstatus"] and not static:
927     buf.write("%*s    secondary: " % (2*indent_level, ""))
928     helper(buf, dev["dev_type"], dev["sstatus"])
929
930   if dev["children"]:
931     for child in dev["children"]:
932       _FormatBlockDevInfo(buf, child, indent_level+1, static)
933
934
935 def ShowInstanceConfig(opts, args):
936   """Compute instance run-time status.
937
938   @param opts: the command line options selected by the user
939   @type args: list
940   @param args: either an empty list, and then we query all
941       instances, or should contain a list of instance names
942   @rtype: int
943   @return: the desired exit code
944
945   """
946   retcode = 0
947   op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
948   result = SubmitOpCode(op)
949   if not result:
950     ToStdout("No instances.")
951     return 1
952
953   buf = StringIO()
954   retcode = 0
955   for instance_name in result:
956     instance = result[instance_name]
957     buf.write("Instance name: %s\n" % instance["name"])
958     buf.write("State: configured to be %s" % instance["config_state"])
959     if not opts.static:
960       buf.write(", actual state is %s" % instance["run_state"])
961     buf.write("\n")
962     ##buf.write("Considered for memory checks in cluster verify: %s\n" %
963     ##          instance["auto_balance"])
964     buf.write("  Nodes:\n")
965     buf.write("    - primary: %s\n" % instance["pnode"])
966     buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
967     buf.write("  Operating system: %s\n" % instance["os"])
968     if instance.has_key("network_port"):
969       buf.write("  Allocated network port: %s\n" % instance["network_port"])
970     buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
971     if instance["hypervisor"] == constants.HT_XEN_PVM:
972       hvattrs = ((constants.HV_KERNEL_PATH, "kernel path"),
973                  (constants.HV_INITRD_PATH, "initrd path"))
974     elif instance["hypervisor"] == constants.HT_XEN_HVM:
975       hvattrs = ((constants.HV_BOOT_ORDER, "boot order"),
976                  (constants.HV_ACPI, "ACPI"),
977                  (constants.HV_PAE, "PAE"),
978                  (constants.HV_CDROM_IMAGE_PATH, "virtual CDROM"),
979                  (constants.HV_NIC_TYPE, "NIC type"),
980                  (constants.HV_DISK_TYPE, "Disk type"),
981                  (constants.HV_VNC_BIND_ADDRESS, "VNC bind address"),
982                  )
983       # custom console information for HVM
984       vnc_bind_address = instance["hv_actual"][constants.HV_VNC_BIND_ADDRESS]
985       if vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
986         vnc_console_port = "%s:%s" % (instance["pnode"],
987                                       instance["network_port"])
988       elif vnc_bind_address == constants.LOCALHOST_IP_ADDRESS:
989         vnc_console_port = "%s:%s on node %s" % (vnc_bind_address,
990                                                  instance["network_port"],
991                                                  instance["pnode"])
992       else:
993         vnc_console_port = "%s:%s" % (vnc_bind_address,
994                                       instance["network_port"])
995       buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
996
997     else:
998       # auto-handle other hypervisor types
999       hvattrs = [(key, key) for key in instance["hv_actual"]]
1000
1001     for key, desc in hvattrs:
1002       if key in instance["hv_instance"]:
1003         val = instance["hv_instance"][key]
1004       else:
1005         val = "default (%s)" % instance["hv_actual"][key]
1006       buf.write("    - %s: %s\n" % (desc, val))
1007     buf.write("  Hardware:\n")
1008     buf.write("    - VCPUs: %d\n" %
1009               instance["be_actual"][constants.BE_VCPUS])
1010     buf.write("    - memory: %dMiB\n" %
1011               instance["be_actual"][constants.BE_MEMORY])
1012     buf.write("    - NICs:\n")
1013     for idx, (mac, ip, bridge) in enumerate(instance["nics"]):
1014       buf.write("      - nic/%d: MAC: %s, IP: %s, bridge: %s\n" %
1015                 (idx, mac, ip, bridge))
1016     buf.write("  Block devices:\n")
1017
1018     for device in instance["disks"]:
1019       _FormatBlockDevInfo(buf, device, 1, opts.static)
1020
1021   ToStdout(buf.getvalue().rstrip('\n'))
1022   return retcode
1023
1024
1025 def SetInstanceParams(opts, args):
1026   """Modifies an instance.
1027
1028   All parameters take effect only at the next restart of the instance.
1029
1030   @param opts: the command line options selected by the user
1031   @type args: list
1032   @param args: should contain only one element, the instance name
1033   @rtype: int
1034   @return: the desired exit code
1035
1036   """
1037   if not (opts.ip or opts.bridge or opts.mac or
1038           opts.hypervisor or opts.beparams):
1039     ToStderr("Please give at least one of the parameters.")
1040     return 1
1041
1042   if constants.BE_MEMORY in opts.beparams:
1043     opts.beparams[constants.BE_MEMORY] = utils.ParseUnit(
1044       opts.beparams[constants.BE_MEMORY])
1045
1046   op = opcodes.OpSetInstanceParams(instance_name=args[0],
1047                                    ip=opts.ip,
1048                                    bridge=opts.bridge, mac=opts.mac,
1049                                    hvparams=opts.hypervisor,
1050                                    beparams=opts.beparams,
1051                                    force=opts.force)
1052
1053   # even if here we process the result, we allow submit only
1054   result = SubmitOrSend(op, opts)
1055
1056   if result:
1057     ToStdout("Modified instance %s", args[0])
1058     for param, data in result:
1059       ToStdout(" - %-5s -> %s", param, data)
1060     ToStdout("Please don't forget that these parameters take effect"
1061              " only at the next start of the instance.")
1062   return 0
1063
1064
1065 # options used in more than one cmd
1066 node_opt = make_option("-n", "--node", dest="node", help="Target node",
1067                        metavar="<node>")
1068
1069 os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1070                     metavar="<os>")
1071
1072 # multi-instance selection options
1073 m_force_multi = make_option("--force-multiple", dest="force_multi",
1074                             help="Do not ask for confirmation when more than"
1075                             " one instance is affected",
1076                             action="store_true", default=False)
1077
1078 m_pri_node_opt = make_option("--primary", dest="multi_mode",
1079                              help="Filter by nodes (primary only)",
1080                              const=_SHUTDOWN_NODES_PRI, action="store_const")
1081
1082 m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1083                              help="Filter by nodes (secondary only)",
1084                              const=_SHUTDOWN_NODES_SEC, action="store_const")
1085
1086 m_node_opt = make_option("--node", dest="multi_mode",
1087                          help="Filter by nodes (primary and secondary)",
1088                          const=_SHUTDOWN_NODES_BOTH, action="store_const")
1089
1090 m_clust_opt = make_option("--all", dest="multi_mode",
1091                           help="Select all instances in the cluster",
1092                           const=_SHUTDOWN_CLUSTER, action="store_const")
1093
1094 m_inst_opt = make_option("--instance", dest="multi_mode",
1095                          help="Filter by instance name [default]",
1096                          const=_SHUTDOWN_INSTANCES, action="store_const")
1097
1098
1099 # this is defined separately due to readability only
1100 add_opts = [
1101   DEBUG_OPT,
1102   make_option("-n", "--node", dest="node",
1103               help="Target node and optional secondary node",
1104               metavar="<pnode>[:<snode>]"),
1105   cli_option("-s", "--os-size", dest="size", help="Disk size, in MiB unless"
1106              " a suffix is used",
1107              default=20 * 1024, type="unit", metavar="<size>"),
1108   cli_option("--swap-size", dest="swap", help="Swap size, in MiB unless a"
1109              " suffix is used",
1110              default=4 * 1024, type="unit", metavar="<size>"),
1111   os_opt,
1112   keyval_option("-B", "--backend", dest="beparams",
1113                 type="keyval", default={},
1114                 help="Backend parameters"),
1115   make_option("-t", "--disk-template", dest="disk_template",
1116               help="Custom disk setup (diskless, file, plain or drbd)",
1117               default=None, metavar="TEMPL"),
1118   ikv_option("--disk", help="Disk information",
1119              default=[], dest="disks",
1120              action="append",
1121              type="identkeyval"),
1122   ikv_option("--net", help="NIC information",
1123              default=[], dest="nics",
1124              action="append",
1125              type="identkeyval"),
1126   make_option("--no-wait-for-sync", dest="wait_for_sync", default=True,
1127               action="store_false", help="Don't wait for sync (DANGEROUS!)"),
1128   make_option("--no-start", dest="start", default=True,
1129               action="store_false", help="Don't start the instance after"
1130               " creation"),
1131   make_option("--no-ip-check", dest="ip_check", default=True,
1132               action="store_false", help="Don't check that the instance's IP"
1133               " is alive (only valid with --no-start)"),
1134   make_option("--file-storage-dir", dest="file_storage_dir",
1135               help="Relative path under default cluster-wide file storage dir"
1136               " to store file-based disks", default=None,
1137               metavar="<DIR>"),
1138   make_option("--file-driver", dest="file_driver", help="Driver to use"
1139               " for image files", default="loop", metavar="<DRIVER>"),
1140   make_option("--iallocator", metavar="<NAME>",
1141               help="Select nodes for the instance automatically using the"
1142               " <NAME> iallocator plugin", default=None, type="string"),
1143   ikv_option("-H", "--hypervisor", dest="hypervisor",
1144               help="Hypervisor and hypervisor options, in the format"
1145               " hypervisor:option=value,option=value,...", default=None,
1146               type="identkeyval"),
1147   SUBMIT_OPT,
1148   ]
1149
1150 commands = {
1151   'add': (AddInstance, ARGS_ONE, add_opts,
1152           "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1153           "Creates and adds a new instance to the cluster"),
1154   'batch-create': (BatchCreate, ARGS_ONE,
1155                    [DEBUG_OPT],
1156                    "<instances_file.json>",
1157                    "Create a bunch of instances based on specs in the file."),
1158   'console': (ConnectToInstanceConsole, ARGS_ONE,
1159               [DEBUG_OPT,
1160                make_option("--show-cmd", dest="show_command",
1161                            action="store_true", default=False,
1162                            help=("Show command instead of executing it"))],
1163               "[--show-cmd] <instance>",
1164               "Opens a console on the specified instance"),
1165   'failover': (FailoverInstance, ARGS_ONE,
1166                [DEBUG_OPT, FORCE_OPT,
1167                 make_option("--ignore-consistency", dest="ignore_consistency",
1168                             action="store_true", default=False,
1169                             help="Ignore the consistency of the disks on"
1170                             " the secondary"),
1171                 SUBMIT_OPT,
1172                 ],
1173                "[-f] <instance>",
1174                "Stops the instance and starts it on the backup node, using"
1175                " the remote mirror (only for instances of type drbd)"),
1176   'info': (ShowInstanceConfig, ARGS_ANY,
1177            [DEBUG_OPT,
1178             make_option("-s", "--static", dest="static",
1179                         action="store_true", default=False,
1180                         help="Only show configuration data, not runtime data"),
1181             ], "[-s] [<instance>...]",
1182            "Show information on the specified instance(s)"),
1183   'list': (ListInstances, ARGS_NONE,
1184            [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT], "",
1185            "Lists the instances and their status. The available fields are"
1186            " (see the man page for details): status, oper_state, oper_ram,"
1187            " name, os, pnode, snodes, admin_state, admin_ram, disk_template,"
1188            " ip, mac, bridge, sda_size, sdb_size, vcpus, serial_no,"
1189            " hypervisor."
1190            " The default field"
1191            " list is (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
1192            ),
1193   'reinstall': (ReinstallInstance, ARGS_ONE,
1194                 [DEBUG_OPT, FORCE_OPT, os_opt,
1195                  make_option("--select-os", dest="select_os",
1196                              action="store_true", default=False,
1197                              help="Interactive OS reinstall, lists available"
1198                              " OS templates for selection"),
1199                  SUBMIT_OPT,
1200                  ],
1201                 "[-f] <instance>", "Reinstall a stopped instance"),
1202   'remove': (RemoveInstance, ARGS_ONE,
1203              [DEBUG_OPT, FORCE_OPT,
1204               make_option("--ignore-failures", dest="ignore_failures",
1205                           action="store_true", default=False,
1206                           help=("Remove the instance from the cluster even"
1207                                 " if there are failures during the removal"
1208                                 " process (shutdown, disk removal, etc.)")),
1209               SUBMIT_OPT,
1210               ],
1211              "[-f] <instance>", "Shuts down the instance and removes it"),
1212   'rename': (RenameInstance, ARGS_FIXED(2),
1213              [DEBUG_OPT,
1214               make_option("--no-ip-check", dest="ignore_ip",
1215                           help="Do not check that the IP of the new name"
1216                           " is alive",
1217                           default=False, action="store_true"),
1218               SUBMIT_OPT,
1219               ],
1220              "<instance> <new_name>", "Rename the instance"),
1221   'replace-disks': (ReplaceDisks, ARGS_ONE,
1222                     [DEBUG_OPT,
1223                      make_option("-n", "--new-secondary", dest="new_secondary",
1224                                  help=("New secondary node (for secondary"
1225                                        " node change)"), metavar="NODE"),
1226                      make_option("-p", "--on-primary", dest="on_primary",
1227                                  default=False, action="store_true",
1228                                  help=("Replace the disk(s) on the primary"
1229                                        " node (only for the drbd template)")),
1230                      make_option("-s", "--on-secondary", dest="on_secondary",
1231                                  default=False, action="store_true",
1232                                  help=("Replace the disk(s) on the secondary"
1233                                        " node (only for the drbd template)")),
1234                      make_option("--disks", dest="disks", default=None,
1235                                  help=("Comma-separated list of disks"
1236                                        " to replace (e.g. sda) (optional,"
1237                                        " defaults to all disks")),
1238                      make_option("--iallocator", metavar="<NAME>",
1239                                  help="Select new secondary for the instance"
1240                                  " automatically using the"
1241                                  " <NAME> iallocator plugin (enables"
1242                                  " secondary node replacement)",
1243                                  default=None, type="string"),
1244                      SUBMIT_OPT,
1245                      ],
1246                     "[-s|-p|-n NODE] <instance>",
1247                     "Replaces all disks for the instance"),
1248   'modify': (SetInstanceParams, ARGS_ONE,
1249              [DEBUG_OPT, FORCE_OPT,
1250               make_option("-i", "--ip", dest="ip",
1251                           help="IP address ('none' or numeric IP)",
1252                           default=None, type="string", metavar="<ADDRESS>"),
1253               make_option("-b", "--bridge", dest="bridge",
1254                           help="Bridge to connect this instance to",
1255                           default=None, type="string", metavar="<bridge>"),
1256               make_option("--mac", dest="mac",
1257                           help="MAC address", default=None,
1258                           type="string", metavar="<MACADDRESS>"),
1259               keyval_option("-H", "--hypervisor", type="keyval",
1260                             default={}, dest="hypervisor",
1261                             help="Change hypervisor parameters"),
1262               keyval_option("-B", "--backend", type="keyval",
1263                             default={}, dest="beparams",
1264                             help="Change backend parameters"),
1265               SUBMIT_OPT,
1266               ],
1267              "<instance>", "Alters the parameters of an instance"),
1268   'shutdown': (ShutdownInstance, ARGS_ANY,
1269                [DEBUG_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1270                 m_clust_opt, m_inst_opt, m_force_multi,
1271                 SUBMIT_OPT,
1272                 ],
1273                "<instance>", "Stops an instance"),
1274   'startup': (StartupInstance, ARGS_ANY,
1275               [DEBUG_OPT, FORCE_OPT, m_force_multi,
1276                make_option("-e", "--extra", dest="extra_args",
1277                            help="Extra arguments for the instance's kernel",
1278                            default=None, type="string", metavar="<PARAMS>"),
1279                m_node_opt, m_pri_node_opt, m_sec_node_opt,
1280                m_clust_opt, m_inst_opt,
1281                SUBMIT_OPT,
1282                ],
1283             "<instance>", "Starts an instance"),
1284
1285   'reboot': (RebootInstance, ARGS_ANY,
1286               [DEBUG_OPT, m_force_multi,
1287                make_option("-e", "--extra", dest="extra_args",
1288                            help="Extra arguments for the instance's kernel",
1289                            default=None, type="string", metavar="<PARAMS>"),
1290                make_option("-t", "--type", dest="reboot_type",
1291                            help="Type of reboot: soft/hard/full",
1292                            default=constants.INSTANCE_REBOOT_HARD,
1293                            type="string", metavar="<REBOOT>"),
1294                make_option("--ignore-secondaries", dest="ignore_secondaries",
1295                            default=False, action="store_true",
1296                            help="Ignore errors from secondaries"),
1297                m_node_opt, m_pri_node_opt, m_sec_node_opt,
1298                m_clust_opt, m_inst_opt,
1299                SUBMIT_OPT,
1300                ],
1301             "<instance>", "Reboots an instance"),
1302   'activate-disks': (ActivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1303                      "<instance>",
1304                      "Activate an instance's disks"),
1305   'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1306                        "<instance>",
1307                        "Deactivate an instance's disks"),
1308   'grow-disk': (GrowDisk, ARGS_FIXED(3),
1309                 [DEBUG_OPT, SUBMIT_OPT,
1310                  make_option("--no-wait-for-sync",
1311                              dest="wait_for_sync", default=True,
1312                              action="store_false",
1313                              help="Don't wait for sync (DANGEROUS!)"),
1314                  ],
1315                 "<instance> <disk> <size>", "Grow an instance's disk"),
1316   'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
1317                 "<instance_name>", "List the tags of the given instance"),
1318   'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1319                "<instance_name> tag...", "Add tags to the given instance"),
1320   'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1321                   "<instance_name> tag...", "Remove tags from given instance"),
1322   }
1323
1324 #: dictionary with aliases for commands
1325 aliases = {
1326   'activate_block_devs': 'activate-disks',
1327   'replace_disks': 'replace-disks',
1328   'start': 'startup',
1329   'stop': 'shutdown',
1330   }
1331
1332 if __name__ == '__main__':
1333   sys.exit(GenericMain(commands, aliases=aliases,
1334                        override={"tag_type": constants.TAG_INSTANCE}))