ganeti.http: Remove _HttpClientError
[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   elif opts.no_nics:
306     # no nics
307     nics = []
308   else:
309     # default of one nic, all auto
310     nics = [{}]
311
312   if opts.disk_template == constants.DT_DISKLESS:
313     if opts.disks:
314       raise errors.OpPrereqError("Diskless instance but disk"
315                                  " information passed")
316     disks = []
317   else:
318     if not opts.disks:
319       raise errors.OpPrereqError("No disk information specified")
320     try:
321       disk_max = max(int(didx[0])+1 for didx in opts.disks)
322     except ValueError, err:
323       raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
324     disks = [{}] * disk_max
325     for didx, ddict in opts.disks:
326       didx = int(didx)
327       if "size" not in ddict:
328         raise errors.OpPrereqError("Missing size for disk %d" % didx)
329       try:
330         ddict["size"] = utils.ParseUnit(ddict["size"])
331       except ValueError, err:
332         raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
333                                    (didx, err))
334       disks[didx] = ddict
335
336   ValidateBeParams(opts.beparams)
337
338 ##  kernel_path = _TransformPath(opts.kernel_path)
339 ##  initrd_path = _TransformPath(opts.initrd_path)
340
341 ##  hvm_acpi = opts.hvm_acpi == _VALUE_TRUE
342 ##  hvm_pae = opts.hvm_pae == _VALUE_TRUE
343
344 ##  if ((opts.hvm_cdrom_image_path is not None) and
345 ##      (opts.hvm_cdrom_image_path.lower() == constants.VALUE_NONE)):
346 ##    hvm_cdrom_image_path = None
347 ##  else:
348 ##    hvm_cdrom_image_path = opts.hvm_cdrom_image_path
349
350   op = opcodes.OpCreateInstance(instance_name=instance,
351                                 disks=disks,
352                                 disk_template=opts.disk_template,
353                                 nics=nics,
354                                 mode=constants.INSTANCE_CREATE,
355                                 os_type=opts.os, pnode=pnode,
356                                 snode=snode,
357                                 start=opts.start, ip_check=opts.ip_check,
358                                 wait_for_sync=opts.wait_for_sync,
359                                 hypervisor=hypervisor,
360                                 hvparams=hvparams,
361                                 beparams=opts.beparams,
362                                 iallocator=opts.iallocator,
363                                 file_storage_dir=opts.file_storage_dir,
364                                 file_driver=opts.file_driver,
365                                 )
366
367   SubmitOrSend(op, opts)
368   return 0
369
370
371 def BatchCreate(opts, args):
372   """Create instances using a definition file.
373
374   This function reads a json file with instances defined
375   in the form::
376
377     {"instance-name":{
378       "disk_size": 25,
379       "swap_size": 1024,
380       "template": "drbd",
381       "backend": {
382         "memory": 512,
383         "vcpus": 1 },
384       "os": "etch-image",
385       "primary_node": "firstnode",
386       "secondary_node": "secondnode",
387       "iallocator": "dumb"}
388     }
389
390   Note that I{primary_node} and I{secondary_node} have precedence over
391   I{iallocator}.
392
393   @param opts: the command line options selected by the user
394   @type args: list
395   @param args: should contain one element, the json filename
396   @rtype: int
397   @return: the desired exit code
398
399   """
400   _DEFAULT_SPECS = {"disk_size": 20 * 1024,
401                     "swap_size": 4 * 1024,
402                     "backend": {},
403                     "iallocator": None,
404                     "primary_node": None,
405                     "secondary_node": None,
406                     "ip": 'none',
407                     "mac": 'auto',
408                     "bridge": None,
409                     "start": True,
410                     "ip_check": True,
411                     "hypervisor": None,
412                     "file_storage_dir": None,
413                     "file_driver": 'loop'}
414
415   def _PopulateWithDefaults(spec):
416     """Returns a new hash combined with default values."""
417     mydict = _DEFAULT_SPECS.copy()
418     mydict.update(spec)
419     return mydict
420
421   def _Validate(spec):
422     """Validate the instance specs."""
423     # Validate fields required under any circumstances
424     for required_field in ('os', 'template'):
425       if required_field not in spec:
426         raise errors.OpPrereqError('Required field "%s" is missing.' %
427                                    required_field)
428     # Validate special fields
429     if spec['primary_node'] is not None:
430       if (spec['template'] in constants.DTS_NET_MIRROR and
431           spec['secondary_node'] is None):
432         raise errors.OpPrereqError('Template requires secondary node, but'
433                                    ' there was no secondary provided.')
434     elif spec['iallocator'] is None:
435       raise errors.OpPrereqError('You have to provide at least a primary_node'
436                                  ' or an iallocator.')
437
438     if (spec['hypervisor'] and
439         not isinstance(spec['hypervisor'], dict)):
440       raise errors.OpPrereqError('Hypervisor parameters must be a dict.')
441
442   json_filename = args[0]
443   fd = open(json_filename, 'r')
444   try:
445     instance_data = simplejson.load(fd)
446   finally:
447     fd.close()
448
449   # Iterate over the instances and do:
450   #  * Populate the specs with default value
451   #  * Validate the instance specs
452   for (name, specs) in instance_data.iteritems():
453     specs = _PopulateWithDefaults(specs)
454     _Validate(specs)
455
456     hypervisor = None
457     hvparams = {}
458     if specs['hypervisor']:
459       hypervisor, hvparams = specs['hypervisor'].iteritems()
460
461     op = opcodes.OpCreateInstance(instance_name=name,
462                                   disk_size=specs['disk_size'],
463                                   swap_size=specs['swap_size'],
464                                   disk_template=specs['template'],
465                                   mode=constants.INSTANCE_CREATE,
466                                   os_type=specs['os'],
467                                   pnode=specs['primary_node'],
468                                   snode=specs['secondary_node'],
469                                   ip=specs['ip'], bridge=specs['bridge'],
470                                   start=specs['start'],
471                                   ip_check=specs['ip_check'],
472                                   wait_for_sync=True,
473                                   mac=specs['mac'],
474                                   iallocator=specs['iallocator'],
475                                   hypervisor=hypervisor,
476                                   hvparams=hvparams,
477                                   beparams=specs['backend'],
478                                   file_storage_dir=specs['file_storage_dir'],
479                                   file_driver=specs['file_driver'])
480
481     ToStdout("%s: %s", name, cli.SendJob([op]))
482
483   return 0
484
485
486 def ReinstallInstance(opts, args):
487   """Reinstall an instance.
488
489   @param opts: the command line options selected by the user
490   @type args: list
491   @param args: should contain only one element, the name of the
492       instance to be reinstalled
493   @rtype: int
494   @return: the desired exit code
495
496   """
497   instance_name = args[0]
498
499   if opts.select_os is True:
500     op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
501     result = SubmitOpCode(op)
502
503     if not result:
504       ToStdout("Can't get the OS list")
505       return 1
506
507     ToStdout("Available OS templates:")
508     number = 0
509     choices = []
510     for entry in result:
511       ToStdout("%3s: %s", number, entry[0])
512       choices.append(("%s" % number, entry[0], entry[0]))
513       number = number + 1
514
515     choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
516     selected = AskUser("Enter OS template name or number (or x to abort):",
517                        choices)
518
519     if selected == 'exit':
520       ToStdout("User aborted reinstall, exiting")
521       return 1
522
523     os_name = selected
524   else:
525     os_name = opts.os
526
527   if not opts.force:
528     usertext = ("This will reinstall the instance %s and remove"
529                 " all data. Continue?") % instance_name
530     if not AskUser(usertext):
531       return 1
532
533   op = opcodes.OpReinstallInstance(instance_name=instance_name,
534                                    os_type=os_name)
535   SubmitOrSend(op, opts)
536
537   return 0
538
539
540 def RemoveInstance(opts, args):
541   """Remove an instance.
542
543   @param opts: the command line options selected by the user
544   @type args: list
545   @param args: should contain only one element, the name of
546       the instance to be removed
547   @rtype: int
548   @return: the desired exit code
549
550   """
551   instance_name = args[0]
552   force = opts.force
553
554   if not force:
555     usertext = ("This will remove the volumes of the instance %s"
556                 " (including mirrors), thus removing all the data"
557                 " of the instance. Continue?") % instance_name
558     if not AskUser(usertext):
559       return 1
560
561   op = opcodes.OpRemoveInstance(instance_name=instance_name,
562                                 ignore_failures=opts.ignore_failures)
563   SubmitOrSend(op, opts)
564   return 0
565
566
567 def RenameInstance(opts, args):
568   """Rename an instance.
569
570   @param opts: the command line options selected by the user
571   @type args: list
572   @param args: should contain two elements, the old and the
573       new instance names
574   @rtype: int
575   @return: the desired exit code
576
577   """
578   op = opcodes.OpRenameInstance(instance_name=args[0],
579                                 new_name=args[1],
580                                 ignore_ip=opts.ignore_ip)
581   SubmitOrSend(op, opts)
582   return 0
583
584
585 def ActivateDisks(opts, args):
586   """Activate an instance's disks.
587
588   This serves two purposes:
589     - it allows (as long as the instance is not running)
590       mounting the disks and modifying them from the node
591     - it repairs inactive secondary drbds
592
593   @param opts: the command line options selected by the user
594   @type args: list
595   @param args: should contain only one element, the instance name
596   @rtype: int
597   @return: the desired exit code
598
599   """
600   instance_name = args[0]
601   op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
602   disks_info = SubmitOrSend(op, opts)
603   for host, iname, nname in disks_info:
604     ToStdout("%s:%s:%s", host, iname, nname)
605   return 0
606
607
608 def DeactivateDisks(opts, args):
609   """Deactivate an instance's disks..
610
611   This function takes the instance name, looks for its primary node
612   and the tries to shutdown its block devices on that node.
613
614   @param opts: the command line options selected by the user
615   @type args: list
616   @param args: should contain only one element, the instance name
617   @rtype: int
618   @return: the desired exit code
619
620   """
621   instance_name = args[0]
622   op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
623   SubmitOrSend(op, opts)
624   return 0
625
626
627 def GrowDisk(opts, args):
628   """Grow an instance's disks.
629
630   @param opts: the command line options selected by the user
631   @type args: list
632   @param args: should contain two elements, the instance name
633       whose disks we grow and the disk name, e.g. I{sda}
634   @rtype: int
635   @return: the desired exit code
636
637   """
638   instance = args[0]
639   disk = args[1]
640   try:
641     disk = int(disk)
642   except ValueError, err:
643     raise errors.OpPrereqError("Invalid disk index: %s" % str(err))
644   amount = utils.ParseUnit(args[2])
645   op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
646                           wait_for_sync=opts.wait_for_sync)
647   SubmitOrSend(op, opts)
648   return 0
649
650
651 def StartupInstance(opts, args):
652   """Startup instances.
653
654   Depending on the options given, this will start one or more
655   instances.
656
657   @param opts: the command line options selected by the user
658   @type args: list
659   @param args: the instance or node names based on which we
660       create the final selection (in conjunction with the
661       opts argument)
662   @rtype: int
663   @return: the desired exit code
664
665   """
666   if opts.multi_mode is None:
667     opts.multi_mode = _SHUTDOWN_INSTANCES
668   inames = _ExpandMultiNames(opts.multi_mode, args)
669   if not inames:
670     raise errors.OpPrereqError("Selection filter does not match any instances")
671   multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
672   if not (opts.force_multi or not multi_on
673           or _ConfirmOperation(inames, "startup")):
674     return 1
675   for name in inames:
676     op = opcodes.OpStartupInstance(instance_name=name,
677                                    force=opts.force,
678                                    extra_args=opts.extra_args)
679     if multi_on:
680       ToStdout("Starting up %s", name)
681     try:
682       SubmitOrSend(op, opts)
683     except JobSubmittedException, err:
684       _, txt = FormatError(err)
685       ToStdout("%s", txt)
686   return 0
687
688
689 def RebootInstance(opts, args):
690   """Reboot instance(s).
691
692   Depending on the parameters given, this will reboot one or more
693   instances.
694
695   @param opts: the command line options selected by the user
696   @type args: list
697   @param args: the instance or node names based on which we
698       create the final selection (in conjunction with the
699       opts argument)
700   @rtype: int
701   @return: the desired exit code
702
703   """
704   if opts.multi_mode is None:
705     opts.multi_mode = _SHUTDOWN_INSTANCES
706   inames = _ExpandMultiNames(opts.multi_mode, args)
707   if not inames:
708     raise errors.OpPrereqError("Selection filter does not match any instances")
709   multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
710   if not (opts.force_multi or not multi_on
711           or _ConfirmOperation(inames, "reboot")):
712     return 1
713   for name in inames:
714     op = opcodes.OpRebootInstance(instance_name=name,
715                                   reboot_type=opts.reboot_type,
716                                   ignore_secondaries=opts.ignore_secondaries)
717
718     SubmitOrSend(op, opts)
719   return 0
720
721
722 def ShutdownInstance(opts, args):
723   """Shutdown an instance.
724
725   @param opts: the command line options selected by the user
726   @type args: list
727   @param args: the instance or node names based on which we
728       create the final selection (in conjunction with the
729       opts argument)
730   @rtype: int
731   @return: the desired exit code
732
733   """
734   if opts.multi_mode is None:
735     opts.multi_mode = _SHUTDOWN_INSTANCES
736   inames = _ExpandMultiNames(opts.multi_mode, args)
737   if not inames:
738     raise errors.OpPrereqError("Selection filter does not match any instances")
739   multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
740   if not (opts.force_multi or not multi_on
741           or _ConfirmOperation(inames, "shutdown")):
742     return 1
743   for name in inames:
744     op = opcodes.OpShutdownInstance(instance_name=name)
745     if multi_on:
746       ToStdout("Shutting down %s", name)
747     try:
748       SubmitOrSend(op, opts)
749     except JobSubmittedException, err:
750       _, txt = FormatError(err)
751       ToStdout("%s", txt)
752   return 0
753
754
755 def ReplaceDisks(opts, args):
756   """Replace the disks of an instance
757
758   @param opts: the command line options selected by the user
759   @type args: list
760   @param args: should contain only one element, the instance name
761   @rtype: int
762   @return: the desired exit code
763
764   """
765   instance_name = args[0]
766   new_2ndary = opts.new_secondary
767   iallocator = opts.iallocator
768   if opts.disks is None:
769     disks = []
770   else:
771     try:
772       disks = [int(i) for i in opts.disks.split(",")]
773     except ValueError, err:
774       raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
775   if opts.on_primary == opts.on_secondary: # no -p or -s passed, or both passed
776     mode = constants.REPLACE_DISK_ALL
777   elif opts.on_primary: # only on primary:
778     mode = constants.REPLACE_DISK_PRI
779     if new_2ndary is not None or iallocator is not None:
780       raise errors.OpPrereqError("Can't change secondary node on primary disk"
781                                  " replacement")
782   elif opts.on_secondary is not None or iallocator is not None:
783     # only on secondary
784     mode = constants.REPLACE_DISK_SEC
785
786   op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
787                               remote_node=new_2ndary, mode=mode,
788                               iallocator=iallocator)
789   SubmitOrSend(op, opts)
790   return 0
791
792
793 def FailoverInstance(opts, args):
794   """Failover an instance.
795
796   The failover is done by shutting it down on its present node and
797   starting it on the secondary.
798
799   @param opts: the command line options selected by the user
800   @type args: list
801   @param args: should contain only one element, the instance name
802   @rtype: int
803   @return: the desired exit code
804
805   """
806   instance_name = args[0]
807   force = opts.force
808
809   if not force:
810     usertext = ("Failover will happen to image %s."
811                 " This requires a shutdown of the instance. Continue?" %
812                 (instance_name,))
813     if not AskUser(usertext):
814       return 1
815
816   op = opcodes.OpFailoverInstance(instance_name=instance_name,
817                                   ignore_consistency=opts.ignore_consistency)
818   SubmitOrSend(op, opts)
819   return 0
820
821
822 def ConnectToInstanceConsole(opts, args):
823   """Connect to the console of an instance.
824
825   @param opts: the command line options selected by the user
826   @type args: list
827   @param args: should contain only one element, the instance name
828   @rtype: int
829   @return: the desired exit code
830
831   """
832   instance_name = args[0]
833
834   op = opcodes.OpConnectConsole(instance_name=instance_name)
835   cmd = SubmitOpCode(op)
836
837   if opts.show_command:
838     ToStdout("%s", utils.ShellQuoteArgs(cmd))
839   else:
840     try:
841       os.execvp(cmd[0], cmd)
842     finally:
843       ToStderr("Can't run console command %s with arguments:\n'%s'",
844                cmd[0], " ".join(cmd))
845       os._exit(1)
846
847
848 def _FormatBlockDevInfo(buf, dev, indent_level, static):
849   """Show block device information.
850
851   This is only used by L{ShowInstanceConfig}, but it's too big to be
852   left for an inline definition.
853
854   @type buf: StringIO
855   @param buf: buffer that will accumulate the output
856   @type dev: dict
857   @param dev: dictionary with disk information
858   @type indent_level: int
859   @param indent_level: the indendation level we are at, used for
860       the layout of the device tree
861   @type static: boolean
862   @param static: wheter the device information doesn't contain
863       runtime information but only static data
864
865   """
866   def helper(buf, dtype, status):
867     """Format one line for physical device status.
868
869     @type buf: StringIO
870     @param buf: buffer that will accumulate the output
871     @type dtype: str
872     @param dtype: a constant from the L{constants.LDS_BLOCK} set
873     @type status: tuple
874     @param status: a tuple as returned from L{backend.FindBlockDevice}
875
876     """
877     if not status:
878       buf.write("not active\n")
879     else:
880       (path, major, minor, syncp, estt, degr, ldisk) = status
881       if major is None:
882         major_string = "N/A"
883       else:
884         major_string = str(major)
885
886       if minor is None:
887         minor_string = "N/A"
888       else:
889         minor_string = str(minor)
890
891       buf.write("%s (%s:%s)" % (path, major_string, minor_string))
892       if dtype in (constants.LD_DRBD8, ):
893         if syncp is not None:
894           sync_text = "*RECOVERING* %5.2f%%," % syncp
895           if estt:
896             sync_text += " ETA %ds" % estt
897           else:
898             sync_text += " ETA unknown"
899         else:
900           sync_text = "in sync"
901         if degr:
902           degr_text = "*DEGRADED*"
903         else:
904           degr_text = "ok"
905         if ldisk:
906           ldisk_text = " *MISSING DISK*"
907         else:
908           ldisk_text = ""
909         buf.write(" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
910       elif dtype == constants.LD_LV:
911         if ldisk:
912           ldisk_text = " *FAILED* (failed drive?)"
913         else:
914           ldisk_text = ""
915         buf.write(ldisk_text)
916       buf.write("\n")
917
918   if dev["iv_name"] is not None:
919     data = "  - %s, " % dev["iv_name"]
920   else:
921     data = "  - "
922   data += "access mode: %s, " % dev["mode"]
923   data += "type: %s" % dev["dev_type"]
924   if dev["logical_id"] is not None:
925     data += ", logical_id: %s" % (dev["logical_id"],)
926   elif dev["physical_id"] is not None:
927     data += ", physical_id: %s" % (dev["physical_id"],)
928   buf.write("%*s%s\n" % (2*indent_level, "", data))
929   if not static:
930     buf.write("%*s    primary:   " % (2*indent_level, ""))
931     helper(buf, dev["dev_type"], dev["pstatus"])
932
933   if dev["sstatus"] and not static:
934     buf.write("%*s    secondary: " % (2*indent_level, ""))
935     helper(buf, dev["dev_type"], dev["sstatus"])
936
937   if dev["children"]:
938     for child in dev["children"]:
939       _FormatBlockDevInfo(buf, child, indent_level+1, static)
940
941
942 def ShowInstanceConfig(opts, args):
943   """Compute instance run-time status.
944
945   @param opts: the command line options selected by the user
946   @type args: list
947   @param args: either an empty list, and then we query all
948       instances, or should contain a list of instance names
949   @rtype: int
950   @return: the desired exit code
951
952   """
953   retcode = 0
954   op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
955   result = SubmitOpCode(op)
956   if not result:
957     ToStdout("No instances.")
958     return 1
959
960   buf = StringIO()
961   retcode = 0
962   for instance_name in result:
963     instance = result[instance_name]
964     buf.write("Instance name: %s\n" % instance["name"])
965     buf.write("State: configured to be %s" % instance["config_state"])
966     if not opts.static:
967       buf.write(", actual state is %s" % instance["run_state"])
968     buf.write("\n")
969     ##buf.write("Considered for memory checks in cluster verify: %s\n" %
970     ##          instance["auto_balance"])
971     buf.write("  Nodes:\n")
972     buf.write("    - primary: %s\n" % instance["pnode"])
973     buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
974     buf.write("  Operating system: %s\n" % instance["os"])
975     if instance.has_key("network_port"):
976       buf.write("  Allocated network port: %s\n" % instance["network_port"])
977     buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
978     if instance["hypervisor"] == constants.HT_XEN_PVM:
979       hvattrs = ((constants.HV_KERNEL_PATH, "kernel path"),
980                  (constants.HV_INITRD_PATH, "initrd path"))
981     elif instance["hypervisor"] == constants.HT_XEN_HVM:
982       hvattrs = ((constants.HV_BOOT_ORDER, "boot order"),
983                  (constants.HV_ACPI, "ACPI"),
984                  (constants.HV_PAE, "PAE"),
985                  (constants.HV_CDROM_IMAGE_PATH, "virtual CDROM"),
986                  (constants.HV_NIC_TYPE, "NIC type"),
987                  (constants.HV_DISK_TYPE, "Disk type"),
988                  (constants.HV_VNC_BIND_ADDRESS, "VNC bind address"),
989                  )
990       # custom console information for HVM
991       vnc_bind_address = instance["hv_actual"][constants.HV_VNC_BIND_ADDRESS]
992       if vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
993         vnc_console_port = "%s:%s" % (instance["pnode"],
994                                       instance["network_port"])
995       elif vnc_bind_address == constants.LOCALHOST_IP_ADDRESS:
996         vnc_console_port = "%s:%s on node %s" % (vnc_bind_address,
997                                                  instance["network_port"],
998                                                  instance["pnode"])
999       else:
1000         vnc_console_port = "%s:%s" % (vnc_bind_address,
1001                                       instance["network_port"])
1002       buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1003
1004     else:
1005       # auto-handle other hypervisor types
1006       hvattrs = [(key, key) for key in instance["hv_actual"]]
1007
1008     for key, desc in hvattrs:
1009       if key in instance["hv_instance"]:
1010         val = instance["hv_instance"][key]
1011       else:
1012         val = "default (%s)" % instance["hv_actual"][key]
1013       buf.write("    - %s: %s\n" % (desc, val))
1014     buf.write("  Hardware:\n")
1015     buf.write("    - VCPUs: %d\n" %
1016               instance["be_actual"][constants.BE_VCPUS])
1017     buf.write("    - memory: %dMiB\n" %
1018               instance["be_actual"][constants.BE_MEMORY])
1019     buf.write("    - NICs:\n")
1020     for idx, (mac, ip, bridge) in enumerate(instance["nics"]):
1021       buf.write("      - nic/%d: MAC: %s, IP: %s, bridge: %s\n" %
1022                 (idx, mac, ip, bridge))
1023     buf.write("  Block devices:\n")
1024
1025     for device in instance["disks"]:
1026       _FormatBlockDevInfo(buf, device, 1, opts.static)
1027
1028   ToStdout(buf.getvalue().rstrip('\n'))
1029   return retcode
1030
1031
1032 def SetInstanceParams(opts, args):
1033   """Modifies an instance.
1034
1035   All parameters take effect only at the next restart of the instance.
1036
1037   @param opts: the command line options selected by the user
1038   @type args: list
1039   @param args: should contain only one element, the instance name
1040   @rtype: int
1041   @return: the desired exit code
1042
1043   """
1044   if not (opts.nics or opts.disks or
1045           opts.hypervisor or opts.beparams):
1046     ToStderr("Please give at least one of the parameters.")
1047     return 1
1048
1049   for param in opts.beparams:
1050     if opts.beparams[param].lower() == "default":
1051       opts.beparams[param] = constants.VALUE_DEFAULT
1052     elif opts.beparams[param].lower() == "none":
1053       opts.beparams[param] = constants.VALUE_NONE
1054     elif param == constants.BE_MEMORY:
1055       opts.beparams[constants.BE_MEMORY] = \
1056         utils.ParseUnit(opts.beparams[constants.BE_MEMORY])
1057
1058   for param in opts.hypervisor:
1059     if opts.hypervisor[param].lower() == "default":
1060       opts.hypervisor[param] = constants.VALUE_DEFAULT
1061     elif opts.hypervisor[param].lower() == "none":
1062       opts.hypervisor[param] = constants.VALUE_NONE
1063
1064   for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1065     try:
1066       nic_op = int(nic_op)
1067       opts.nics[idx] = (nic_op, nic_dict)
1068     except ValueError:
1069       pass
1070
1071   for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1072     try:
1073       disk_op = int(disk_op)
1074       opts.disks[idx] = (disk_op, disk_dict)
1075     except ValueError:
1076       pass
1077     if disk_op == constants.DDM_ADD:
1078       if 'size' not in disk_dict:
1079         raise errors.OpPrereqError("Missing required parameter 'size'")
1080       disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1081
1082   op = opcodes.OpSetInstanceParams(instance_name=args[0],
1083                                    nics=opts.nics,
1084                                    disks=opts.disks,
1085                                    hvparams=opts.hypervisor,
1086                                    beparams=opts.beparams,
1087                                    force=opts.force)
1088
1089   # even if here we process the result, we allow submit only
1090   result = SubmitOrSend(op, opts)
1091
1092   if result:
1093     ToStdout("Modified instance %s", args[0])
1094     for param, data in result:
1095       ToStdout(" - %-5s -> %s", param, data)
1096     ToStdout("Please don't forget that these parameters take effect"
1097              " only at the next start of the instance.")
1098   return 0
1099
1100
1101 # options used in more than one cmd
1102 node_opt = make_option("-n", "--node", dest="node", help="Target node",
1103                        metavar="<node>")
1104
1105 os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1106                     metavar="<os>")
1107
1108 # multi-instance selection options
1109 m_force_multi = make_option("--force-multiple", dest="force_multi",
1110                             help="Do not ask for confirmation when more than"
1111                             " one instance is affected",
1112                             action="store_true", default=False)
1113
1114 m_pri_node_opt = make_option("--primary", dest="multi_mode",
1115                              help="Filter by nodes (primary only)",
1116                              const=_SHUTDOWN_NODES_PRI, action="store_const")
1117
1118 m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1119                              help="Filter by nodes (secondary only)",
1120                              const=_SHUTDOWN_NODES_SEC, action="store_const")
1121
1122 m_node_opt = make_option("--node", dest="multi_mode",
1123                          help="Filter by nodes (primary and secondary)",
1124                          const=_SHUTDOWN_NODES_BOTH, action="store_const")
1125
1126 m_clust_opt = make_option("--all", dest="multi_mode",
1127                           help="Select all instances in the cluster",
1128                           const=_SHUTDOWN_CLUSTER, action="store_const")
1129
1130 m_inst_opt = make_option("--instance", dest="multi_mode",
1131                          help="Filter by instance name [default]",
1132                          const=_SHUTDOWN_INSTANCES, action="store_const")
1133
1134
1135 # this is defined separately due to readability only
1136 add_opts = [
1137   DEBUG_OPT,
1138   make_option("-n", "--node", dest="node",
1139               help="Target node and optional secondary node",
1140               metavar="<pnode>[:<snode>]"),
1141   os_opt,
1142   keyval_option("-B", "--backend", dest="beparams",
1143                 type="keyval", default={},
1144                 help="Backend parameters"),
1145   make_option("-t", "--disk-template", dest="disk_template",
1146               help="Custom disk setup (diskless, file, plain or drbd)",
1147               default=None, metavar="TEMPL"),
1148   ikv_option("--disk", help="Disk information",
1149              default=[], dest="disks",
1150              action="append",
1151              type="identkeyval"),
1152   ikv_option("--net", help="NIC information",
1153              default=[], dest="nics",
1154              action="append",
1155              type="identkeyval"),
1156   make_option("--no-nics", default=False, action="store_true",
1157               help="Do not create any network cards for the instance"),
1158   make_option("--no-wait-for-sync", dest="wait_for_sync", default=True,
1159               action="store_false", help="Don't wait for sync (DANGEROUS!)"),
1160   make_option("--no-start", dest="start", default=True,
1161               action="store_false", help="Don't start the instance after"
1162               " creation"),
1163   make_option("--no-ip-check", dest="ip_check", default=True,
1164               action="store_false", help="Don't check that the instance's IP"
1165               " is alive (only valid with --no-start)"),
1166   make_option("--file-storage-dir", dest="file_storage_dir",
1167               help="Relative path under default cluster-wide file storage dir"
1168               " to store file-based disks", default=None,
1169               metavar="<DIR>"),
1170   make_option("--file-driver", dest="file_driver", help="Driver to use"
1171               " for image files", default="loop", metavar="<DRIVER>"),
1172   make_option("--iallocator", metavar="<NAME>",
1173               help="Select nodes for the instance automatically using the"
1174               " <NAME> iallocator plugin", default=None, type="string"),
1175   ikv_option("-H", "--hypervisor", dest="hypervisor",
1176               help="Hypervisor and hypervisor options, in the format"
1177               " hypervisor:option=value,option=value,...", default=None,
1178               type="identkeyval"),
1179   SUBMIT_OPT,
1180   ]
1181
1182 commands = {
1183   'add': (AddInstance, ARGS_ONE, add_opts,
1184           "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1185           "Creates and adds a new instance to the cluster"),
1186   'batch-create': (BatchCreate, ARGS_ONE,
1187                    [DEBUG_OPT],
1188                    "<instances_file.json>",
1189                    "Create a bunch of instances based on specs in the file."),
1190   'console': (ConnectToInstanceConsole, ARGS_ONE,
1191               [DEBUG_OPT,
1192                make_option("--show-cmd", dest="show_command",
1193                            action="store_true", default=False,
1194                            help=("Show command instead of executing it"))],
1195               "[--show-cmd] <instance>",
1196               "Opens a console on the specified instance"),
1197   'failover': (FailoverInstance, ARGS_ONE,
1198                [DEBUG_OPT, FORCE_OPT,
1199                 make_option("--ignore-consistency", dest="ignore_consistency",
1200                             action="store_true", default=False,
1201                             help="Ignore the consistency of the disks on"
1202                             " the secondary"),
1203                 SUBMIT_OPT,
1204                 ],
1205                "[-f] <instance>",
1206                "Stops the instance and starts it on the backup node, using"
1207                " the remote mirror (only for instances of type drbd)"),
1208   'info': (ShowInstanceConfig, ARGS_ANY,
1209            [DEBUG_OPT,
1210             make_option("-s", "--static", dest="static",
1211                         action="store_true", default=False,
1212                         help="Only show configuration data, not runtime data"),
1213             ], "[-s] [<instance>...]",
1214            "Show information on the specified instance(s)"),
1215   'list': (ListInstances, ARGS_NONE,
1216            [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT], "",
1217            "Lists the instances and their status. The available fields are"
1218            " (see the man page for details): status, oper_state, oper_ram,"
1219            " name, os, pnode, snodes, admin_state, admin_ram, disk_template,"
1220            " ip, mac, bridge, sda_size, sdb_size, vcpus, serial_no,"
1221            " hypervisor."
1222            " The default field"
1223            " list is (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
1224            ),
1225   'reinstall': (ReinstallInstance, ARGS_ONE,
1226                 [DEBUG_OPT, FORCE_OPT, os_opt,
1227                  make_option("--select-os", dest="select_os",
1228                              action="store_true", default=False,
1229                              help="Interactive OS reinstall, lists available"
1230                              " OS templates for selection"),
1231                  SUBMIT_OPT,
1232                  ],
1233                 "[-f] <instance>", "Reinstall a stopped instance"),
1234   'remove': (RemoveInstance, ARGS_ONE,
1235              [DEBUG_OPT, FORCE_OPT,
1236               make_option("--ignore-failures", dest="ignore_failures",
1237                           action="store_true", default=False,
1238                           help=("Remove the instance from the cluster even"
1239                                 " if there are failures during the removal"
1240                                 " process (shutdown, disk removal, etc.)")),
1241               SUBMIT_OPT,
1242               ],
1243              "[-f] <instance>", "Shuts down the instance and removes it"),
1244   'rename': (RenameInstance, ARGS_FIXED(2),
1245              [DEBUG_OPT,
1246               make_option("--no-ip-check", dest="ignore_ip",
1247                           help="Do not check that the IP of the new name"
1248                           " is alive",
1249                           default=False, action="store_true"),
1250               SUBMIT_OPT,
1251               ],
1252              "<instance> <new_name>", "Rename the instance"),
1253   'replace-disks': (ReplaceDisks, ARGS_ONE,
1254                     [DEBUG_OPT,
1255                      make_option("-n", "--new-secondary", dest="new_secondary",
1256                                  help=("New secondary node (for secondary"
1257                                        " node change)"), metavar="NODE"),
1258                      make_option("-p", "--on-primary", dest="on_primary",
1259                                  default=False, action="store_true",
1260                                  help=("Replace the disk(s) on the primary"
1261                                        " node (only for the drbd template)")),
1262                      make_option("-s", "--on-secondary", dest="on_secondary",
1263                                  default=False, action="store_true",
1264                                  help=("Replace the disk(s) on the secondary"
1265                                        " node (only for the drbd template)")),
1266                      make_option("--disks", dest="disks", default=None,
1267                                  help=("Comma-separated list of disks"
1268                                        " to replace (e.g. sda) (optional,"
1269                                        " defaults to all disks")),
1270                      make_option("--iallocator", metavar="<NAME>",
1271                                  help="Select new secondary for the instance"
1272                                  " automatically using the"
1273                                  " <NAME> iallocator plugin (enables"
1274                                  " secondary node replacement)",
1275                                  default=None, type="string"),
1276                      SUBMIT_OPT,
1277                      ],
1278                     "[-s|-p|-n NODE] <instance>",
1279                     "Replaces all disks for the instance"),
1280   'modify': (SetInstanceParams, ARGS_ONE,
1281              [DEBUG_OPT, FORCE_OPT,
1282               keyval_option("-H", "--hypervisor", type="keyval",
1283                             default={}, dest="hypervisor",
1284                             help="Change hypervisor parameters"),
1285               keyval_option("-B", "--backend", type="keyval",
1286                             default={}, dest="beparams",
1287                             help="Change backend parameters"),
1288               ikv_option("--disk", help="Disk changes",
1289                          default=[], dest="disks",
1290                          action="append",
1291                          type="identkeyval"),
1292               ikv_option("--net", help="NIC changes",
1293                          default=[], dest="nics",
1294                          action="append",
1295                          type="identkeyval"),
1296               SUBMIT_OPT,
1297               ],
1298              "<instance>", "Alters the parameters of an instance"),
1299   'shutdown': (ShutdownInstance, ARGS_ANY,
1300                [DEBUG_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1301                 m_clust_opt, m_inst_opt, m_force_multi,
1302                 SUBMIT_OPT,
1303                 ],
1304                "<instance>", "Stops an instance"),
1305   'startup': (StartupInstance, ARGS_ANY,
1306               [DEBUG_OPT, FORCE_OPT, m_force_multi,
1307                make_option("-e", "--extra", dest="extra_args",
1308                            help="Extra arguments for the instance's kernel",
1309                            default=None, type="string", metavar="<PARAMS>"),
1310                m_node_opt, m_pri_node_opt, m_sec_node_opt,
1311                m_clust_opt, m_inst_opt,
1312                SUBMIT_OPT,
1313                ],
1314             "<instance>", "Starts an instance"),
1315
1316   'reboot': (RebootInstance, ARGS_ANY,
1317               [DEBUG_OPT, m_force_multi,
1318                make_option("-e", "--extra", dest="extra_args",
1319                            help="Extra arguments for the instance's kernel",
1320                            default=None, type="string", metavar="<PARAMS>"),
1321                make_option("-t", "--type", dest="reboot_type",
1322                            help="Type of reboot: soft/hard/full",
1323                            default=constants.INSTANCE_REBOOT_HARD,
1324                            type="string", metavar="<REBOOT>"),
1325                make_option("--ignore-secondaries", dest="ignore_secondaries",
1326                            default=False, action="store_true",
1327                            help="Ignore errors from secondaries"),
1328                m_node_opt, m_pri_node_opt, m_sec_node_opt,
1329                m_clust_opt, m_inst_opt,
1330                SUBMIT_OPT,
1331                ],
1332             "<instance>", "Reboots an instance"),
1333   'activate-disks': (ActivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1334                      "<instance>",
1335                      "Activate an instance's disks"),
1336   'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1337                        "<instance>",
1338                        "Deactivate an instance's disks"),
1339   'grow-disk': (GrowDisk, ARGS_FIXED(3),
1340                 [DEBUG_OPT, SUBMIT_OPT,
1341                  make_option("--no-wait-for-sync",
1342                              dest="wait_for_sync", default=True,
1343                              action="store_false",
1344                              help="Don't wait for sync (DANGEROUS!)"),
1345                  ],
1346                 "<instance> <disk> <size>", "Grow an instance's disk"),
1347   'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
1348                 "<instance_name>", "List the tags of the given instance"),
1349   'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1350                "<instance_name> tag...", "Add tags to the given instance"),
1351   'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1352                   "<instance_name> tag...", "Remove tags from given instance"),
1353   }
1354
1355 #: dictionary with aliases for commands
1356 aliases = {
1357   'activate_block_devs': 'activate-disks',
1358   'replace_disks': 'replace-disks',
1359   'start': 'startup',
1360   'stop': 'shutdown',
1361   }
1362
1363 if __name__ == '__main__':
1364   sys.exit(GenericMain(commands, aliases=aliases,
1365                        override={"tag_type": constants.TAG_INSTANCE}))