Heavy redo of gnt-instance info output
[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   cnt = [opts.on_primary, opts.on_secondary,
776          new_2ndary is not None, iallocator is not None].count(True)
777   if cnt != 1:
778     raise errors.OpPrereqError("One and only one of the -p, -s, -n and -i"
779                                " options must be passed")
780   elif opts.on_primary:
781     mode = constants.REPLACE_DISK_PRI
782   elif opts.on_secondary:
783     mode = constants.REPLACE_DISK_SEC
784   elif new_2ndary is not None or iallocator is not None:
785     # replace secondary
786     mode = constants.REPLACE_DISK_CHG
787
788   op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
789                               remote_node=new_2ndary, mode=mode,
790                               iallocator=iallocator)
791   SubmitOrSend(op, opts)
792   return 0
793
794
795 def FailoverInstance(opts, args):
796   """Failover an instance.
797
798   The failover is done by shutting it down on its present node and
799   starting it on the secondary.
800
801   @param opts: the command line options selected by the user
802   @type args: list
803   @param args: should contain only one element, the instance name
804   @rtype: int
805   @return: the desired exit code
806
807   """
808   instance_name = args[0]
809   force = opts.force
810
811   if not force:
812     usertext = ("Failover will happen to image %s."
813                 " This requires a shutdown of the instance. Continue?" %
814                 (instance_name,))
815     if not AskUser(usertext):
816       return 1
817
818   op = opcodes.OpFailoverInstance(instance_name=instance_name,
819                                   ignore_consistency=opts.ignore_consistency)
820   SubmitOrSend(op, opts)
821   return 0
822
823
824 def ConnectToInstanceConsole(opts, args):
825   """Connect to the console of an instance.
826
827   @param opts: the command line options selected by the user
828   @type args: list
829   @param args: should contain only one element, the instance name
830   @rtype: int
831   @return: the desired exit code
832
833   """
834   instance_name = args[0]
835
836   op = opcodes.OpConnectConsole(instance_name=instance_name)
837   cmd = SubmitOpCode(op)
838
839   if opts.show_command:
840     ToStdout("%s", utils.ShellQuoteArgs(cmd))
841   else:
842     try:
843       os.execvp(cmd[0], cmd)
844     finally:
845       ToStderr("Can't run console command %s with arguments:\n'%s'",
846                cmd[0], " ".join(cmd))
847       os._exit(1)
848
849
850 def _FormatLogicalID(dev_type, logical_id):
851   """Formats the logical_id of a disk.
852
853   """
854   if dev_type == constants.LD_DRBD8:
855     node_a, node_b, port, minor_a, minor_b, key = logical_id
856     data = [
857       ("nodeA", "%s, minor=%s" % (node_a, minor_a)),
858       ("nodeB", "%s, minor=%s" % (node_b, minor_b)),
859       ("port", port),
860       ("auth key", key),
861       ]
862   elif dev_type == constants.LD_LV:
863     vg_name, lv_name = logical_id
864     data = ["%s/%s" % (vg_name, lv_name)]
865   else:
866     data = [str(logical_id)]
867
868   return data
869
870
871 def _FormatBlockDevInfo(idx, top_level, dev, static):
872   """Show block device information.
873
874   This is only used by L{ShowInstanceConfig}, but it's too big to be
875   left for an inline definition.
876
877   @type idx: int
878   @param idx: the index of the current disk
879   @type top_level: boolean
880   @param top_level: if this a top-level disk?
881   @type dev: dict
882   @param dev: dictionary with disk information
883   @type static: boolean
884   @param static: wheter the device information doesn't contain
885       runtime information but only static data
886   @return: a list of either strings, tuples or lists
887       (which should be formatted at a higher indent level)
888
889   """
890   def helper(dtype, status):
891     """Format one line for physical device status.
892
893     @type dtype: str
894     @param dtype: a constant from the L{constants.LDS_BLOCK} set
895     @type status: tuple
896     @param status: a tuple as returned from L{backend.FindBlockDevice}
897     @return: the string representing the status
898
899     """
900     if not status:
901       return "not active"
902     txt = ""
903     (path, major, minor, syncp, estt, degr, ldisk) = status
904     if major is None:
905       major_string = "N/A"
906     else:
907       major_string = str(major)
908
909     if minor is None:
910       minor_string = "N/A"
911     else:
912       minor_string = str(minor)
913
914     txt += ("%s (%s:%s)" % (path, major_string, minor_string))
915     if dtype in (constants.LD_DRBD8, ):
916       if syncp is not None:
917         sync_text = "*RECOVERING* %5.2f%%," % syncp
918         if estt:
919           sync_text += " ETA %ds" % estt
920         else:
921           sync_text += " ETA unknown"
922       else:
923         sync_text = "in sync"
924       if degr:
925         degr_text = "*DEGRADED*"
926       else:
927         degr_text = "ok"
928       if ldisk:
929         ldisk_text = " *MISSING DISK*"
930       else:
931         ldisk_text = ""
932       txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
933     elif dtype == constants.LD_LV:
934       if ldisk:
935         ldisk_text = " *FAILED* (failed drive?)"
936       else:
937         ldisk_text = ""
938       txt += ldisk_text
939     return txt
940
941   # the header
942   if top_level:
943     if dev["iv_name"] is not None:
944       txt = dev["iv_name"]
945     else:
946       txt = "disk %d" % idx
947   else:
948     txt = "child %d" % idx
949   d1 = ["- %s: %s" % (txt, dev["dev_type"])]
950   data = []
951   if top_level:
952     data.append(("access mode", dev["mode"]))
953   if dev["logical_id"] is not None:
954     try:
955       l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"])
956     except ValueError:
957       l_id = [str(dev["logical_id"])]
958     if len(l_id) == 1:
959       data.append(("logical_id", l_id[0]))
960     else:
961       data.extend(l_id)
962   elif dev["physical_id"] is not None:
963     data.append("physical_id:")
964     data.append([dev["physical_id"]])
965   if not static:
966     data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
967   if dev["sstatus"] and not static:
968     data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
969
970   if dev["children"]:
971     data.append("child devices:")
972     for c_idx, child in enumerate(dev["children"]):
973       data.append(_FormatBlockDevInfo(c_idx, False, child, static))
974   d1.append(data)
975   return d1
976
977
978 def _FormatList(buf, data, indent_level):
979   """Formats a list of data at a given indent level.
980
981   If the element of the list is:
982     - a string, it is simply formatted as is
983     - a tuple, it will be split into key, value and the all the
984       values in a list will be aligned all at the same start column
985     - a list, will be recursively formatted
986
987   @type buf: StringIO
988   @param buf: the buffer into which we write the output
989   @param data: the list to format
990   @type indent_level: int
991   @param indent_level: the indent level to format at
992
993   """
994   max_tlen = max([len(elem[0]) for elem in data
995                  if isinstance(elem, tuple)] or [0])
996   for elem in data:
997     if isinstance(elem, basestring):
998       buf.write("%*s%s\n" % (2*indent_level, "", elem))
999     elif isinstance(elem, tuple):
1000       key, value = elem
1001       spacer = "%*s" % (max_tlen - len(key), "")
1002       buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1003     elif isinstance(elem, list):
1004       _FormatList(buf, elem, indent_level+1)
1005
1006 def ShowInstanceConfig(opts, args):
1007   """Compute instance run-time status.
1008
1009   @param opts: the command line options selected by the user
1010   @type args: list
1011   @param args: either an empty list, and then we query all
1012       instances, or should contain a list of instance names
1013   @rtype: int
1014   @return: the desired exit code
1015
1016   """
1017   retcode = 0
1018   op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1019   result = SubmitOpCode(op)
1020   if not result:
1021     ToStdout("No instances.")
1022     return 1
1023
1024   buf = StringIO()
1025   retcode = 0
1026   for instance_name in result:
1027     instance = result[instance_name]
1028     buf.write("Instance name: %s\n" % instance["name"])
1029     buf.write("State: configured to be %s" % instance["config_state"])
1030     if not opts.static:
1031       buf.write(", actual state is %s" % instance["run_state"])
1032     buf.write("\n")
1033     ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1034     ##          instance["auto_balance"])
1035     buf.write("  Nodes:\n")
1036     buf.write("    - primary: %s\n" % instance["pnode"])
1037     buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
1038     buf.write("  Operating system: %s\n" % instance["os"])
1039     if instance.has_key("network_port"):
1040       buf.write("  Allocated network port: %s\n" % instance["network_port"])
1041     buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1042     if instance["hypervisor"] == constants.HT_XEN_PVM:
1043       hvattrs = ((constants.HV_KERNEL_PATH, "kernel path"),
1044                  (constants.HV_INITRD_PATH, "initrd path"))
1045     elif instance["hypervisor"] == constants.HT_XEN_HVM:
1046       hvattrs = ((constants.HV_BOOT_ORDER, "boot order"),
1047                  (constants.HV_ACPI, "ACPI"),
1048                  (constants.HV_PAE, "PAE"),
1049                  (constants.HV_CDROM_IMAGE_PATH, "virtual CDROM"),
1050                  (constants.HV_NIC_TYPE, "NIC type"),
1051                  (constants.HV_DISK_TYPE, "Disk type"),
1052                  (constants.HV_VNC_BIND_ADDRESS, "VNC bind address"),
1053                  )
1054       # custom console information for HVM
1055       vnc_bind_address = instance["hv_actual"][constants.HV_VNC_BIND_ADDRESS]
1056       if vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
1057         vnc_console_port = "%s:%s" % (instance["pnode"],
1058                                       instance["network_port"])
1059       elif vnc_bind_address == constants.LOCALHOST_IP_ADDRESS:
1060         vnc_console_port = "%s:%s on node %s" % (vnc_bind_address,
1061                                                  instance["network_port"],
1062                                                  instance["pnode"])
1063       else:
1064         vnc_console_port = "%s:%s" % (vnc_bind_address,
1065                                       instance["network_port"])
1066       buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1067
1068     else:
1069       # auto-handle other hypervisor types
1070       hvattrs = [(key, key) for key in instance["hv_actual"]]
1071
1072     for key, desc in hvattrs:
1073       if key in instance["hv_instance"]:
1074         val = instance["hv_instance"][key]
1075       else:
1076         val = "default (%s)" % instance["hv_actual"][key]
1077       buf.write("    - %s: %s\n" % (desc, val))
1078     buf.write("  Hardware:\n")
1079     buf.write("    - VCPUs: %d\n" %
1080               instance["be_actual"][constants.BE_VCPUS])
1081     buf.write("    - memory: %dMiB\n" %
1082               instance["be_actual"][constants.BE_MEMORY])
1083     buf.write("    - NICs:\n")
1084     for idx, (mac, ip, bridge) in enumerate(instance["nics"]):
1085       buf.write("      - nic/%d: MAC: %s, IP: %s, bridge: %s\n" %
1086                 (idx, mac, ip, bridge))
1087     buf.write("  Disks:\n")
1088
1089     for idx, device in enumerate(instance["disks"]):
1090       _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1091
1092   ToStdout(buf.getvalue().rstrip('\n'))
1093   return retcode
1094
1095
1096 def SetInstanceParams(opts, args):
1097   """Modifies an instance.
1098
1099   All parameters take effect only at the next restart of the instance.
1100
1101   @param opts: the command line options selected by the user
1102   @type args: list
1103   @param args: should contain only one element, the instance name
1104   @rtype: int
1105   @return: the desired exit code
1106
1107   """
1108   if not (opts.nics or opts.disks or
1109           opts.hypervisor or opts.beparams):
1110     ToStderr("Please give at least one of the parameters.")
1111     return 1
1112
1113   for param in opts.beparams:
1114     if opts.beparams[param].lower() == "default":
1115       opts.beparams[param] = constants.VALUE_DEFAULT
1116     elif opts.beparams[param].lower() == "none":
1117       opts.beparams[param] = constants.VALUE_NONE
1118     elif param == constants.BE_MEMORY:
1119       opts.beparams[constants.BE_MEMORY] = \
1120         utils.ParseUnit(opts.beparams[constants.BE_MEMORY])
1121
1122   for param in opts.hypervisor:
1123     if opts.hypervisor[param].lower() == "default":
1124       opts.hypervisor[param] = constants.VALUE_DEFAULT
1125     elif opts.hypervisor[param].lower() == "none":
1126       opts.hypervisor[param] = constants.VALUE_NONE
1127
1128   for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1129     try:
1130       nic_op = int(nic_op)
1131       opts.nics[idx] = (nic_op, nic_dict)
1132     except ValueError:
1133       pass
1134
1135   for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1136     try:
1137       disk_op = int(disk_op)
1138       opts.disks[idx] = (disk_op, disk_dict)
1139     except ValueError:
1140       pass
1141     if disk_op == constants.DDM_ADD:
1142       if 'size' not in disk_dict:
1143         raise errors.OpPrereqError("Missing required parameter 'size'")
1144       disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1145
1146   op = opcodes.OpSetInstanceParams(instance_name=args[0],
1147                                    nics=opts.nics,
1148                                    disks=opts.disks,
1149                                    hvparams=opts.hypervisor,
1150                                    beparams=opts.beparams,
1151                                    force=opts.force)
1152
1153   # even if here we process the result, we allow submit only
1154   result = SubmitOrSend(op, opts)
1155
1156   if result:
1157     ToStdout("Modified instance %s", args[0])
1158     for param, data in result:
1159       ToStdout(" - %-5s -> %s", param, data)
1160     ToStdout("Please don't forget that these parameters take effect"
1161              " only at the next start of the instance.")
1162   return 0
1163
1164
1165 # options used in more than one cmd
1166 node_opt = make_option("-n", "--node", dest="node", help="Target node",
1167                        metavar="<node>")
1168
1169 os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1170                     metavar="<os>")
1171
1172 # multi-instance selection options
1173 m_force_multi = make_option("--force-multiple", dest="force_multi",
1174                             help="Do not ask for confirmation when more than"
1175                             " one instance is affected",
1176                             action="store_true", default=False)
1177
1178 m_pri_node_opt = make_option("--primary", dest="multi_mode",
1179                              help="Filter by nodes (primary only)",
1180                              const=_SHUTDOWN_NODES_PRI, action="store_const")
1181
1182 m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1183                              help="Filter by nodes (secondary only)",
1184                              const=_SHUTDOWN_NODES_SEC, action="store_const")
1185
1186 m_node_opt = make_option("--node", dest="multi_mode",
1187                          help="Filter by nodes (primary and secondary)",
1188                          const=_SHUTDOWN_NODES_BOTH, action="store_const")
1189
1190 m_clust_opt = make_option("--all", dest="multi_mode",
1191                           help="Select all instances in the cluster",
1192                           const=_SHUTDOWN_CLUSTER, action="store_const")
1193
1194 m_inst_opt = make_option("--instance", dest="multi_mode",
1195                          help="Filter by instance name [default]",
1196                          const=_SHUTDOWN_INSTANCES, action="store_const")
1197
1198
1199 # this is defined separately due to readability only
1200 add_opts = [
1201   DEBUG_OPT,
1202   make_option("-n", "--node", dest="node",
1203               help="Target node and optional secondary node",
1204               metavar="<pnode>[:<snode>]"),
1205   os_opt,
1206   keyval_option("-B", "--backend", dest="beparams",
1207                 type="keyval", default={},
1208                 help="Backend parameters"),
1209   make_option("-t", "--disk-template", dest="disk_template",
1210               help="Custom disk setup (diskless, file, plain or drbd)",
1211               default=None, metavar="TEMPL"),
1212   ikv_option("--disk", help="Disk information",
1213              default=[], dest="disks",
1214              action="append",
1215              type="identkeyval"),
1216   ikv_option("--net", help="NIC information",
1217              default=[], dest="nics",
1218              action="append",
1219              type="identkeyval"),
1220   make_option("--no-nics", default=False, action="store_true",
1221               help="Do not create any network cards for the instance"),
1222   make_option("--no-wait-for-sync", dest="wait_for_sync", default=True,
1223               action="store_false", help="Don't wait for sync (DANGEROUS!)"),
1224   make_option("--no-start", dest="start", default=True,
1225               action="store_false", help="Don't start the instance after"
1226               " creation"),
1227   make_option("--no-ip-check", dest="ip_check", default=True,
1228               action="store_false", help="Don't check that the instance's IP"
1229               " is alive (only valid with --no-start)"),
1230   make_option("--file-storage-dir", dest="file_storage_dir",
1231               help="Relative path under default cluster-wide file storage dir"
1232               " to store file-based disks", default=None,
1233               metavar="<DIR>"),
1234   make_option("--file-driver", dest="file_driver", help="Driver to use"
1235               " for image files", default="loop", metavar="<DRIVER>"),
1236   make_option("--iallocator", metavar="<NAME>",
1237               help="Select nodes for the instance automatically using the"
1238               " <NAME> iallocator plugin", default=None, type="string"),
1239   ikv_option("-H", "--hypervisor", dest="hypervisor",
1240               help="Hypervisor and hypervisor options, in the format"
1241               " hypervisor:option=value,option=value,...", default=None,
1242               type="identkeyval"),
1243   SUBMIT_OPT,
1244   ]
1245
1246 commands = {
1247   'add': (AddInstance, ARGS_ONE, add_opts,
1248           "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1249           "Creates and adds a new instance to the cluster"),
1250   'batch-create': (BatchCreate, ARGS_ONE,
1251                    [DEBUG_OPT],
1252                    "<instances_file.json>",
1253                    "Create a bunch of instances based on specs in the file."),
1254   'console': (ConnectToInstanceConsole, ARGS_ONE,
1255               [DEBUG_OPT,
1256                make_option("--show-cmd", dest="show_command",
1257                            action="store_true", default=False,
1258                            help=("Show command instead of executing it"))],
1259               "[--show-cmd] <instance>",
1260               "Opens a console on the specified instance"),
1261   'failover': (FailoverInstance, ARGS_ONE,
1262                [DEBUG_OPT, FORCE_OPT,
1263                 make_option("--ignore-consistency", dest="ignore_consistency",
1264                             action="store_true", default=False,
1265                             help="Ignore the consistency of the disks on"
1266                             " the secondary"),
1267                 SUBMIT_OPT,
1268                 ],
1269                "[-f] <instance>",
1270                "Stops the instance and starts it on the backup node, using"
1271                " the remote mirror (only for instances of type drbd)"),
1272   'info': (ShowInstanceConfig, ARGS_ANY,
1273            [DEBUG_OPT,
1274             make_option("-s", "--static", dest="static",
1275                         action="store_true", default=False,
1276                         help="Only show configuration data, not runtime data"),
1277             ], "[-s] [<instance>...]",
1278            "Show information on the specified instance(s)"),
1279   'list': (ListInstances, ARGS_NONE,
1280            [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT], "",
1281            "Lists the instances and their status. The available fields are"
1282            " (see the man page for details): status, oper_state, oper_ram,"
1283            " name, os, pnode, snodes, admin_state, admin_ram, disk_template,"
1284            " ip, mac, bridge, sda_size, sdb_size, vcpus, serial_no,"
1285            " hypervisor."
1286            " The default field"
1287            " list is (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
1288            ),
1289   'reinstall': (ReinstallInstance, ARGS_ONE,
1290                 [DEBUG_OPT, FORCE_OPT, os_opt,
1291                  make_option("--select-os", dest="select_os",
1292                              action="store_true", default=False,
1293                              help="Interactive OS reinstall, lists available"
1294                              " OS templates for selection"),
1295                  SUBMIT_OPT,
1296                  ],
1297                 "[-f] <instance>", "Reinstall a stopped instance"),
1298   'remove': (RemoveInstance, ARGS_ONE,
1299              [DEBUG_OPT, FORCE_OPT,
1300               make_option("--ignore-failures", dest="ignore_failures",
1301                           action="store_true", default=False,
1302                           help=("Remove the instance from the cluster even"
1303                                 " if there are failures during the removal"
1304                                 " process (shutdown, disk removal, etc.)")),
1305               SUBMIT_OPT,
1306               ],
1307              "[-f] <instance>", "Shuts down the instance and removes it"),
1308   'rename': (RenameInstance, ARGS_FIXED(2),
1309              [DEBUG_OPT,
1310               make_option("--no-ip-check", dest="ignore_ip",
1311                           help="Do not check that the IP of the new name"
1312                           " is alive",
1313                           default=False, action="store_true"),
1314               SUBMIT_OPT,
1315               ],
1316              "<instance> <new_name>", "Rename the instance"),
1317   'replace-disks': (ReplaceDisks, ARGS_ONE,
1318                     [DEBUG_OPT,
1319                      make_option("-n", "--new-secondary", dest="new_secondary",
1320                                  help=("New secondary node (for secondary"
1321                                        " node change)"), metavar="NODE",
1322                                  default=None),
1323                      make_option("-p", "--on-primary", dest="on_primary",
1324                                  default=False, action="store_true",
1325                                  help=("Replace the disk(s) on the primary"
1326                                        " node (only for the drbd template)")),
1327                      make_option("-s", "--on-secondary", dest="on_secondary",
1328                                  default=False, action="store_true",
1329                                  help=("Replace the disk(s) on the secondary"
1330                                        " node (only for the drbd template)")),
1331                      make_option("--disks", dest="disks", default=None,
1332                                  help=("Comma-separated list of disks"
1333                                        " to replace (e.g. sda) (optional,"
1334                                        " defaults to all disks")),
1335                      make_option("-i", "--iallocator", metavar="<NAME>",
1336                                  help="Select new secondary for the instance"
1337                                  " automatically using the"
1338                                  " <NAME> iallocator plugin (enables"
1339                                  " secondary node replacement)",
1340                                  default=None, type="string"),
1341                      SUBMIT_OPT,
1342                      ],
1343                     "[-s|-p|-n NODE] <instance>",
1344                     "Replaces all disks for the instance"),
1345   'modify': (SetInstanceParams, ARGS_ONE,
1346              [DEBUG_OPT, FORCE_OPT,
1347               keyval_option("-H", "--hypervisor", type="keyval",
1348                             default={}, dest="hypervisor",
1349                             help="Change hypervisor parameters"),
1350               keyval_option("-B", "--backend", type="keyval",
1351                             default={}, dest="beparams",
1352                             help="Change backend parameters"),
1353               ikv_option("--disk", help="Disk changes",
1354                          default=[], dest="disks",
1355                          action="append",
1356                          type="identkeyval"),
1357               ikv_option("--net", help="NIC changes",
1358                          default=[], dest="nics",
1359                          action="append",
1360                          type="identkeyval"),
1361               SUBMIT_OPT,
1362               ],
1363              "<instance>", "Alters the parameters of an instance"),
1364   'shutdown': (ShutdownInstance, ARGS_ANY,
1365                [DEBUG_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1366                 m_clust_opt, m_inst_opt, m_force_multi,
1367                 SUBMIT_OPT,
1368                 ],
1369                "<instance>", "Stops an instance"),
1370   'startup': (StartupInstance, ARGS_ANY,
1371               [DEBUG_OPT, FORCE_OPT, m_force_multi,
1372                make_option("-e", "--extra", dest="extra_args",
1373                            help="Extra arguments for the instance's kernel",
1374                            default=None, type="string", metavar="<PARAMS>"),
1375                m_node_opt, m_pri_node_opt, m_sec_node_opt,
1376                m_clust_opt, m_inst_opt,
1377                SUBMIT_OPT,
1378                ],
1379             "<instance>", "Starts an instance"),
1380
1381   'reboot': (RebootInstance, ARGS_ANY,
1382               [DEBUG_OPT, m_force_multi,
1383                make_option("-e", "--extra", dest="extra_args",
1384                            help="Extra arguments for the instance's kernel",
1385                            default=None, type="string", metavar="<PARAMS>"),
1386                make_option("-t", "--type", dest="reboot_type",
1387                            help="Type of reboot: soft/hard/full",
1388                            default=constants.INSTANCE_REBOOT_HARD,
1389                            type="string", metavar="<REBOOT>"),
1390                make_option("--ignore-secondaries", dest="ignore_secondaries",
1391                            default=False, action="store_true",
1392                            help="Ignore errors from secondaries"),
1393                m_node_opt, m_pri_node_opt, m_sec_node_opt,
1394                m_clust_opt, m_inst_opt,
1395                SUBMIT_OPT,
1396                ],
1397             "<instance>", "Reboots an instance"),
1398   'activate-disks': (ActivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1399                      "<instance>",
1400                      "Activate an instance's disks"),
1401   'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1402                        "<instance>",
1403                        "Deactivate an instance's disks"),
1404   'grow-disk': (GrowDisk, ARGS_FIXED(3),
1405                 [DEBUG_OPT, SUBMIT_OPT,
1406                  make_option("--no-wait-for-sync",
1407                              dest="wait_for_sync", default=True,
1408                              action="store_false",
1409                              help="Don't wait for sync (DANGEROUS!)"),
1410                  ],
1411                 "<instance> <disk> <size>", "Grow an instance's disk"),
1412   'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
1413                 "<instance_name>", "List the tags of the given instance"),
1414   'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1415                "<instance_name> tag...", "Add tags to the given instance"),
1416   'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1417                   "<instance_name> tag...", "Remove tags from given instance"),
1418   }
1419
1420 #: dictionary with aliases for commands
1421 aliases = {
1422   'activate_block_devs': 'activate-disks',
1423   'replace_disks': 'replace-disks',
1424   'start': 'startup',
1425   'stop': 'shutdown',
1426   }
1427
1428 if __name__ == '__main__':
1429   sys.exit(GenericMain(commands, aliases=aliases,
1430                        override={"tag_type": constants.TAG_INSTANCE}))