Add a new instance query flag ‘disk_usage’
[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, client=None):
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 client is None:
80     client = GetClient()
81   if mode == _SHUTDOWN_CLUSTER:
82     if names:
83       raise errors.OpPrereqError("Cluster filter mode takes no arguments")
84     idata = client.QueryInstances([], ["name"], False)
85     inames = [row[0] for row in idata]
86
87   elif mode in (_SHUTDOWN_NODES_BOTH,
88                 _SHUTDOWN_NODES_PRI,
89                 _SHUTDOWN_NODES_SEC):
90     if not names:
91       raise errors.OpPrereqError("No node names passed")
92     ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
93                               True)
94     ipri = [row[1] for row in ndata]
95     pri_names = list(itertools.chain(*ipri))
96     isec = [row[2] for row in ndata]
97     sec_names = list(itertools.chain(*isec))
98     if mode == _SHUTDOWN_NODES_BOTH:
99       inames = pri_names + sec_names
100     elif mode == _SHUTDOWN_NODES_PRI:
101       inames = pri_names
102     elif mode == _SHUTDOWN_NODES_SEC:
103       inames = sec_names
104     else:
105       raise errors.ProgrammerError("Unhandled shutdown type")
106
107   elif mode == _SHUTDOWN_INSTANCES:
108     if not names:
109       raise errors.OpPrereqError("No instance names passed")
110     idata = client.QueryInstances(names, ["name"], False)
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 _EnsureInstancesExist(client, names):
181   """Check for and ensure the given instance names exist.
182
183   This function will raise an OpPrereqError in case they don't
184   exist. Otherwise it will exit cleanly.
185
186   @type client: L{luxi.Client}
187   @param client: the client to use for the query
188   @type names: list
189   @param names: the list of instance names to query
190   @raise errors.OpPrereqError: in case any instance is missing
191
192   """
193   # TODO: change LUQueryInstances to that it actually returns None
194   # instead of raising an exception, or devise a better mechanism
195   result = client.QueryInstances(names, ["name"], False)
196   for orig_name, row in zip(names, result):
197     if row[0] is None:
198       raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name)
199
200
201 def ListInstances(opts, args):
202   """List instances and their properties.
203
204   @param opts: the command line options selected by the user
205   @type args: list
206   @param args: should be an empty list
207   @rtype: int
208   @return: the desired exit code
209
210   """
211   if opts.output is None:
212     selected_fields = _LIST_DEF_FIELDS
213   elif opts.output.startswith("+"):
214     selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
215   else:
216     selected_fields = opts.output.split(",")
217
218   output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
219
220   if not opts.no_headers:
221     headers = {
222       "name": "Instance", "os": "OS", "pnode": "Primary_node",
223       "snodes": "Secondary_Nodes", "admin_state": "Autostart",
224       "oper_state": "Running",
225       "oper_ram": "Memory", "disk_template": "Disk_template",
226       "ip": "IP_address", "mac": "MAC_address",
227       "bridge": "Bridge",
228       "sda_size": "Disk/0", "sdb_size": "Disk/1",
229       "disk_usage": "DiskUsage",
230       "status": "Status", "tags": "Tags",
231       "network_port": "Network_port",
232       "hv/kernel_path": "Kernel_path",
233       "hv/initrd_path": "Initrd_path",
234       "hv/boot_order": "HVM_boot_order",
235       "hv/acpi": "HVM_ACPI",
236       "hv/pae": "HVM_PAE",
237       "hv/cdrom_image_path": "HVM_CDROM_image_path",
238       "hv/nic_type": "HVM_NIC_type",
239       "hv/disk_type": "HVM_Disk_type",
240       "hv/vnc_bind_address": "VNC_bind_address",
241       "serial_no": "SerialNo", "hypervisor": "Hypervisor",
242       "hvparams": "Hypervisor_parameters",
243       "be/memory": "Configured_memory",
244       "be/vcpus": "VCPUs",
245       "be/auto_balance": "Auto_balance",
246       "disk.count": "Disks", "disk.sizes": "Disk_sizes",
247       "nic.count": "NICs", "nic.ips": "NIC_IPs",
248       "nic.bridges": "NIC_bridges", "nic.macs": "NIC_MACs",
249       }
250   else:
251     headers = None
252
253   unitfields = ["be/memory", "oper_ram", "sd(a|b)_size", "disk\.size/.*"]
254   numfields = ["be/memory", "oper_ram", "sd(a|b)_size", "be/vcpus",
255                "serial_no", "(disk|nic)\.count", "disk\.size/.*"]
256
257   list_type_fields = ("tags", "disk.sizes",
258                       "nic.macs", "nic.ips", "nic.bridges")
259   # change raw values to nicer strings
260   for row in output:
261     for idx, field in enumerate(selected_fields):
262       val = row[idx]
263       if field == "snodes":
264         val = ",".join(val) or "-"
265       elif field == "admin_state":
266         if val:
267           val = "yes"
268         else:
269           val = "no"
270       elif field == "oper_state":
271         if val is None:
272           val = "(node down)"
273         elif val: # True
274           val = "running"
275         else:
276           val = "stopped"
277       elif field == "oper_ram":
278         if val is None:
279           val = "(node down)"
280       elif field == "sda_size" or field == "sdb_size":
281         if val is None:
282           val = "N/A"
283       elif field in list_type_fields:
284         val = ",".join(str(item) for item in val)
285       elif val is None:
286         val = "-"
287       row[idx] = str(val)
288
289   data = GenerateTable(separator=opts.separator, headers=headers,
290                        fields=selected_fields, unitfields=unitfields,
291                        numfields=numfields, data=output, units=opts.units)
292
293   for line in data:
294     ToStdout(line)
295
296   return 0
297
298
299 def AddInstance(opts, args):
300   """Add an instance to the cluster.
301
302   @param opts: the command line options selected by the user
303   @type args: list
304   @param args: should contain only one element, the new instance name
305   @rtype: int
306   @return: the desired exit code
307
308   """
309   instance = args[0]
310
311   (pnode, snode) = SplitNodeOption(opts.node)
312
313   hypervisor = None
314   hvparams = {}
315   if opts.hypervisor:
316     hypervisor, hvparams = opts.hypervisor
317
318   if opts.nics:
319     try:
320       nic_max = max(int(nidx[0])+1 for nidx in opts.nics)
321     except ValueError, err:
322       raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
323     nics = [{}] * nic_max
324     for nidx, ndict in opts.nics.items():
325       nidx = int(nidx)
326       nics[nidx] = ndict
327   elif opts.no_nics:
328     # no nics
329     nics = []
330   else:
331     # default of one nic, all auto
332     nics = [{}]
333
334   if opts.disk_template == constants.DT_DISKLESS:
335     if opts.disks:
336       raise errors.OpPrereqError("Diskless instance but disk"
337                                  " information passed")
338     disks = []
339   else:
340     if not opts.disks:
341       raise errors.OpPrereqError("No disk information specified")
342     try:
343       disk_max = max(int(didx[0])+1 for didx in opts.disks)
344     except ValueError, err:
345       raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
346     disks = [{}] * disk_max
347     for didx, ddict in opts.disks:
348       didx = int(didx)
349       if "size" not in ddict:
350         raise errors.OpPrereqError("Missing size for disk %d" % didx)
351       try:
352         ddict["size"] = utils.ParseUnit(ddict["size"])
353       except ValueError, err:
354         raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
355                                    (didx, err))
356       disks[didx] = ddict
357
358   ValidateBeParams(opts.beparams)
359
360 ##  kernel_path = _TransformPath(opts.kernel_path)
361 ##  initrd_path = _TransformPath(opts.initrd_path)
362
363 ##  hvm_acpi = opts.hvm_acpi == _VALUE_TRUE
364 ##  hvm_pae = opts.hvm_pae == _VALUE_TRUE
365
366 ##  if ((opts.hvm_cdrom_image_path is not None) and
367 ##      (opts.hvm_cdrom_image_path.lower() == constants.VALUE_NONE)):
368 ##    hvm_cdrom_image_path = None
369 ##  else:
370 ##    hvm_cdrom_image_path = opts.hvm_cdrom_image_path
371
372   op = opcodes.OpCreateInstance(instance_name=instance,
373                                 disks=disks,
374                                 disk_template=opts.disk_template,
375                                 nics=nics,
376                                 mode=constants.INSTANCE_CREATE,
377                                 os_type=opts.os, pnode=pnode,
378                                 snode=snode,
379                                 start=opts.start, ip_check=opts.ip_check,
380                                 wait_for_sync=opts.wait_for_sync,
381                                 hypervisor=hypervisor,
382                                 hvparams=hvparams,
383                                 beparams=opts.beparams,
384                                 iallocator=opts.iallocator,
385                                 file_storage_dir=opts.file_storage_dir,
386                                 file_driver=opts.file_driver,
387                                 )
388
389   SubmitOrSend(op, opts)
390   return 0
391
392
393 def BatchCreate(opts, args):
394   """Create instances using a definition file.
395
396   This function reads a json file with instances defined
397   in the form::
398
399     {"instance-name":{
400       "disk_size": [20480],
401       "template": "drbd",
402       "backend": {
403         "memory": 512,
404         "vcpus": 1 },
405       "os": "debootstrap",
406       "primary_node": "firstnode",
407       "secondary_node": "secondnode",
408       "iallocator": "dumb"}
409     }
410
411   Note that I{primary_node} and I{secondary_node} have precedence over
412   I{iallocator}.
413
414   @param opts: the command line options selected by the user
415   @type args: list
416   @param args: should contain one element, the json filename
417   @rtype: int
418   @return: the desired exit code
419
420   """
421   _DEFAULT_SPECS = {"disk_size": [20 * 1024],
422                     "backend": {},
423                     "iallocator": None,
424                     "primary_node": None,
425                     "secondary_node": None,
426                     "ip": 'none',
427                     "mac": 'auto',
428                     "bridge": None,
429                     "start": True,
430                     "ip_check": True,
431                     "hypervisor": None,
432                     "file_storage_dir": None,
433                     "file_driver": 'loop'}
434
435   def _PopulateWithDefaults(spec):
436     """Returns a new hash combined with default values."""
437     mydict = _DEFAULT_SPECS.copy()
438     mydict.update(spec)
439     return mydict
440
441   def _Validate(spec):
442     """Validate the instance specs."""
443     # Validate fields required under any circumstances
444     for required_field in ('os', 'template'):
445       if required_field not in spec:
446         raise errors.OpPrereqError('Required field "%s" is missing.' %
447                                    required_field)
448     # Validate special fields
449     if spec['primary_node'] is not None:
450       if (spec['template'] in constants.DTS_NET_MIRROR and
451           spec['secondary_node'] is None):
452         raise errors.OpPrereqError('Template requires secondary node, but'
453                                    ' there was no secondary provided.')
454     elif spec['iallocator'] is None:
455       raise errors.OpPrereqError('You have to provide at least a primary_node'
456                                  ' or an iallocator.')
457
458     if (spec['hypervisor'] and
459         not isinstance(spec['hypervisor'], dict)):
460       raise errors.OpPrereqError('Hypervisor parameters must be a dict.')
461
462   json_filename = args[0]
463   fd = open(json_filename, 'r')
464   try:
465     instance_data = simplejson.load(fd)
466   finally:
467     fd.close()
468
469   # Iterate over the instances and do:
470   #  * Populate the specs with default value
471   #  * Validate the instance specs
472   i_names = utils.NiceSort(instance_data.keys())
473   for name in i_names:
474     specs = instance_data[name]
475     specs = _PopulateWithDefaults(specs)
476     _Validate(specs)
477
478     hypervisor = None
479     hvparams = {}
480     if specs['hypervisor']:
481       hypervisor, hvparams = specs['hypervisor'].iteritems()
482
483     disks = []
484     for elem in specs['disk_size']:
485       try:
486         size = utils.ParseUnit(elem)
487       except ValueError, err:
488         raise errors.OpPrereqError("Invalid disk size '%s' for"
489                                    " instance %s: %s" %
490                                    (elem, name, err))
491       disks.append({"size": size})
492
493     nic0 = {'ip': specs['ip'], 'bridge': specs['bridge'], 'mac': specs['mac']}
494
495     op = opcodes.OpCreateInstance(instance_name=name,
496                                   disks=disks,
497                                   disk_template=specs['template'],
498                                   mode=constants.INSTANCE_CREATE,
499                                   os_type=specs['os'],
500                                   pnode=specs['primary_node'],
501                                   snode=specs['secondary_node'],
502                                   nics=[nic0],
503                                   start=specs['start'],
504                                   ip_check=specs['ip_check'],
505                                   wait_for_sync=True,
506                                   iallocator=specs['iallocator'],
507                                   hypervisor=hypervisor,
508                                   hvparams=hvparams,
509                                   beparams=specs['backend'],
510                                   file_storage_dir=specs['file_storage_dir'],
511                                   file_driver=specs['file_driver'])
512
513     ToStdout("%s: %s", name, cli.SendJob([op]))
514
515   return 0
516
517
518 def ReinstallInstance(opts, args):
519   """Reinstall an instance.
520
521   @param opts: the command line options selected by the user
522   @type args: list
523   @param args: should contain only one element, the name of the
524       instance to be reinstalled
525   @rtype: int
526   @return: the desired exit code
527
528   """
529   instance_name = args[0]
530
531   if opts.select_os is True:
532     op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
533     result = SubmitOpCode(op)
534
535     if not result:
536       ToStdout("Can't get the OS list")
537       return 1
538
539     ToStdout("Available OS templates:")
540     number = 0
541     choices = []
542     for entry in result:
543       ToStdout("%3s: %s", number, entry[0])
544       choices.append(("%s" % number, entry[0], entry[0]))
545       number = number + 1
546
547     choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
548     selected = AskUser("Enter OS template name or number (or x to abort):",
549                        choices)
550
551     if selected == 'exit':
552       ToStdout("User aborted reinstall, exiting")
553       return 1
554
555     os_name = selected
556   else:
557     os_name = opts.os
558
559   if not opts.force:
560     usertext = ("This will reinstall the instance %s and remove"
561                 " all data. Continue?") % instance_name
562     if not AskUser(usertext):
563       return 1
564
565   op = opcodes.OpReinstallInstance(instance_name=instance_name,
566                                    os_type=os_name)
567   SubmitOrSend(op, opts)
568
569   return 0
570
571
572 def RemoveInstance(opts, args):
573   """Remove an instance.
574
575   @param opts: the command line options selected by the user
576   @type args: list
577   @param args: should contain only one element, the name of
578       the instance to be removed
579   @rtype: int
580   @return: the desired exit code
581
582   """
583   instance_name = args[0]
584   force = opts.force
585   cl = GetClient()
586
587   if not force:
588     _EnsureInstancesExist(cl, [instance_name])
589
590     usertext = ("This will remove the volumes of the instance %s"
591                 " (including mirrors), thus removing all the data"
592                 " of the instance. Continue?") % instance_name
593     if not AskUser(usertext):
594       return 1
595
596   op = opcodes.OpRemoveInstance(instance_name=instance_name,
597                                 ignore_failures=opts.ignore_failures)
598   SubmitOrSend(op, opts, cl=cl)
599   return 0
600
601
602 def RenameInstance(opts, args):
603   """Rename an instance.
604
605   @param opts: the command line options selected by the user
606   @type args: list
607   @param args: should contain two elements, the old and the
608       new instance names
609   @rtype: int
610   @return: the desired exit code
611
612   """
613   op = opcodes.OpRenameInstance(instance_name=args[0],
614                                 new_name=args[1],
615                                 ignore_ip=opts.ignore_ip)
616   SubmitOrSend(op, opts)
617   return 0
618
619
620 def ActivateDisks(opts, args):
621   """Activate an instance's disks.
622
623   This serves two purposes:
624     - it allows (as long as the instance is not running)
625       mounting the disks and modifying them from the node
626     - it repairs inactive secondary drbds
627
628   @param opts: the command line options selected by the user
629   @type args: list
630   @param args: should contain only one element, the instance name
631   @rtype: int
632   @return: the desired exit code
633
634   """
635   instance_name = args[0]
636   op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
637   disks_info = SubmitOrSend(op, opts)
638   for host, iname, nname in disks_info:
639     ToStdout("%s:%s:%s", host, iname, nname)
640   return 0
641
642
643 def DeactivateDisks(opts, args):
644   """Deactivate an instance's disks..
645
646   This function takes the instance name, looks for its primary node
647   and the tries to shutdown its block devices on that node.
648
649   @param opts: the command line options selected by the user
650   @type args: list
651   @param args: should contain only one element, the instance name
652   @rtype: int
653   @return: the desired exit code
654
655   """
656   instance_name = args[0]
657   op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
658   SubmitOrSend(op, opts)
659   return 0
660
661
662 def GrowDisk(opts, args):
663   """Grow an instance's disks.
664
665   @param opts: the command line options selected by the user
666   @type args: list
667   @param args: should contain two elements, the instance name
668       whose disks we grow and the disk name, e.g. I{sda}
669   @rtype: int
670   @return: the desired exit code
671
672   """
673   instance = args[0]
674   disk = args[1]
675   try:
676     disk = int(disk)
677   except ValueError, err:
678     raise errors.OpPrereqError("Invalid disk index: %s" % str(err))
679   amount = utils.ParseUnit(args[2])
680   op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
681                           wait_for_sync=opts.wait_for_sync)
682   SubmitOrSend(op, opts)
683   return 0
684
685
686 def StartupInstance(opts, args):
687   """Startup instances.
688
689   Depending on the options given, this will start one or more
690   instances.
691
692   @param opts: the command line options selected by the user
693   @type args: list
694   @param args: the instance or node names based on which we
695       create the final selection (in conjunction with the
696       opts argument)
697   @rtype: int
698   @return: the desired exit code
699
700   """
701   cl = GetClient()
702   if opts.multi_mode is None:
703     opts.multi_mode = _SHUTDOWN_INSTANCES
704   inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
705   if not inames:
706     raise errors.OpPrereqError("Selection filter does not match any instances")
707   multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
708   if not (opts.force_multi or not multi_on
709           or _ConfirmOperation(inames, "startup")):
710     return 1
711   jex = cli.JobExecutor(verbose=multi_on, cl=cl)
712   for name in inames:
713     op = opcodes.OpStartupInstance(instance_name=name,
714                                    force=opts.force,
715                                    extra_args=opts.extra_args)
716     jex.QueueJob(name, op)
717   jex.WaitOrShow(not opts.submit_only)
718   return 0
719
720
721 def RebootInstance(opts, args):
722   """Reboot instance(s).
723
724   Depending on the parameters given, this will reboot one or more
725   instances.
726
727   @param opts: the command line options selected by the user
728   @type args: list
729   @param args: the instance or node names based on which we
730       create the final selection (in conjunction with the
731       opts argument)
732   @rtype: int
733   @return: the desired exit code
734
735   """
736   cl = GetClient()
737   if opts.multi_mode is None:
738     opts.multi_mode = _SHUTDOWN_INSTANCES
739   inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
740   if not inames:
741     raise errors.OpPrereqError("Selection filter does not match any instances")
742   multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
743   if not (opts.force_multi or not multi_on
744           or _ConfirmOperation(inames, "reboot")):
745     return 1
746   jex = JobExecutor(verbose=multi_on, cl=cl)
747   for name in inames:
748     op = opcodes.OpRebootInstance(instance_name=name,
749                                   reboot_type=opts.reboot_type,
750                                   ignore_secondaries=opts.ignore_secondaries)
751     jex.QueueJob(name, op)
752   jex.WaitOrShow(not opts.submit_only)
753   return 0
754
755
756 def ShutdownInstance(opts, args):
757   """Shutdown an instance.
758
759   @param opts: the command line options selected by the user
760   @type args: list
761   @param args: the instance or node names based on which we
762       create the final selection (in conjunction with the
763       opts argument)
764   @rtype: int
765   @return: the desired exit code
766
767   """
768   cl = GetClient()
769   if opts.multi_mode is None:
770     opts.multi_mode = _SHUTDOWN_INSTANCES
771   inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
772   if not inames:
773     raise errors.OpPrereqError("Selection filter does not match any instances")
774   multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
775   if not (opts.force_multi or not multi_on
776           or _ConfirmOperation(inames, "shutdown")):
777     return 1
778
779   jex = cli.JobExecutor(verbose=multi_on, cl=cl)
780   for name in inames:
781     op = opcodes.OpShutdownInstance(instance_name=name)
782     jex.QueueJob(name, op)
783   jex.WaitOrShow(not opts.submit_only)
784   return 0
785
786
787 def ReplaceDisks(opts, args):
788   """Replace the disks of an instance
789
790   @param opts: the command line options selected by the user
791   @type args: list
792   @param args: should contain only one element, the instance name
793   @rtype: int
794   @return: the desired exit code
795
796   """
797   instance_name = args[0]
798   new_2ndary = opts.new_secondary
799   iallocator = opts.iallocator
800   if opts.disks is None:
801     disks = []
802   else:
803     try:
804       disks = [int(i) for i in opts.disks.split(",")]
805     except ValueError, err:
806       raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
807   cnt = [opts.on_primary, opts.on_secondary,
808          new_2ndary is not None, iallocator is not None].count(True)
809   if cnt != 1:
810     raise errors.OpPrereqError("One and only one of the -p, -s, -n and -i"
811                                " options must be passed")
812   elif opts.on_primary:
813     mode = constants.REPLACE_DISK_PRI
814   elif opts.on_secondary:
815     mode = constants.REPLACE_DISK_SEC
816   elif new_2ndary is not None or iallocator is not None:
817     # replace secondary
818     mode = constants.REPLACE_DISK_CHG
819
820   op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
821                               remote_node=new_2ndary, mode=mode,
822                               iallocator=iallocator)
823   SubmitOrSend(op, opts)
824   return 0
825
826
827 def FailoverInstance(opts, args):
828   """Failover an instance.
829
830   The failover is done by shutting it down on its present node and
831   starting it on the secondary.
832
833   @param opts: the command line options selected by the user
834   @type args: list
835   @param args: should contain only one element, the instance name
836   @rtype: int
837   @return: the desired exit code
838
839   """
840   cl = GetClient()
841   instance_name = args[0]
842   force = opts.force
843
844   if not force:
845     _EnsureInstancesExist(cl, [instance_name])
846
847     usertext = ("Failover will happen to image %s."
848                 " This requires a shutdown of the instance. Continue?" %
849                 (instance_name,))
850     if not AskUser(usertext):
851       return 1
852
853   op = opcodes.OpFailoverInstance(instance_name=instance_name,
854                                   ignore_consistency=opts.ignore_consistency)
855   SubmitOrSend(op, opts, cl=cl)
856   return 0
857
858
859 def MigrateInstance(opts, args):
860   """Migrate an instance.
861
862   The migrate is done without shutdown.
863
864   @param opts: the command line options selected by the user
865   @type args: list
866   @param args: should contain only one element, the instance name
867   @rtype: int
868   @return: the desired exit code
869
870   """
871   cl = GetClient()
872   instance_name = args[0]
873   force = opts.force
874
875   if not force:
876     _EnsureInstancesExist(cl, [instance_name])
877
878     if opts.cleanup:
879       usertext = ("Instance %s will be recovered from a failed migration."
880                   " Note that the migration procedure (including cleanup)" %
881                   (instance_name,))
882     else:
883       usertext = ("Instance %s will be migrated. Note that migration" %
884                   (instance_name,))
885     usertext += (" is **experimental** in this version."
886                 " This might impact the instance if anything goes wrong."
887                 " Continue?")
888     if not AskUser(usertext):
889       return 1
890
891   op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
892                                  cleanup=opts.cleanup)
893   SubmitOpCode(op, cl=cl)
894   return 0
895
896
897 def ConnectToInstanceConsole(opts, args):
898   """Connect to the console of an instance.
899
900   @param opts: the command line options selected by the user
901   @type args: list
902   @param args: should contain only one element, the instance name
903   @rtype: int
904   @return: the desired exit code
905
906   """
907   instance_name = args[0]
908
909   op = opcodes.OpConnectConsole(instance_name=instance_name)
910   cmd = SubmitOpCode(op)
911
912   if opts.show_command:
913     ToStdout("%s", utils.ShellQuoteArgs(cmd))
914   else:
915     try:
916       os.execvp(cmd[0], cmd)
917     finally:
918       ToStderr("Can't run console command %s with arguments:\n'%s'",
919                cmd[0], " ".join(cmd))
920       os._exit(1)
921
922
923 def _FormatLogicalID(dev_type, logical_id):
924   """Formats the logical_id of a disk.
925
926   """
927   if dev_type == constants.LD_DRBD8:
928     node_a, node_b, port, minor_a, minor_b, key = logical_id
929     data = [
930       ("nodeA", "%s, minor=%s" % (node_a, minor_a)),
931       ("nodeB", "%s, minor=%s" % (node_b, minor_b)),
932       ("port", port),
933       ("auth key", key),
934       ]
935   elif dev_type == constants.LD_LV:
936     vg_name, lv_name = logical_id
937     data = ["%s/%s" % (vg_name, lv_name)]
938   else:
939     data = [str(logical_id)]
940
941   return data
942
943
944 def _FormatBlockDevInfo(idx, top_level, dev, static):
945   """Show block device information.
946
947   This is only used by L{ShowInstanceConfig}, but it's too big to be
948   left for an inline definition.
949
950   @type idx: int
951   @param idx: the index of the current disk
952   @type top_level: boolean
953   @param top_level: if this a top-level disk?
954   @type dev: dict
955   @param dev: dictionary with disk information
956   @type static: boolean
957   @param static: wheter the device information doesn't contain
958       runtime information but only static data
959   @return: a list of either strings, tuples or lists
960       (which should be formatted at a higher indent level)
961
962   """
963   def helper(dtype, status):
964     """Format one line for physical device status.
965
966     @type dtype: str
967     @param dtype: a constant from the L{constants.LDS_BLOCK} set
968     @type status: tuple
969     @param status: a tuple as returned from L{backend.FindBlockDevice}
970     @return: the string representing the status
971
972     """
973     if not status:
974       return "not active"
975     txt = ""
976     (path, major, minor, syncp, estt, degr, ldisk) = status
977     if major is None:
978       major_string = "N/A"
979     else:
980       major_string = str(major)
981
982     if minor is None:
983       minor_string = "N/A"
984     else:
985       minor_string = str(minor)
986
987     txt += ("%s (%s:%s)" % (path, major_string, minor_string))
988     if dtype in (constants.LD_DRBD8, ):
989       if syncp is not None:
990         sync_text = "*RECOVERING* %5.2f%%," % syncp
991         if estt:
992           sync_text += " ETA %ds" % estt
993         else:
994           sync_text += " ETA unknown"
995       else:
996         sync_text = "in sync"
997       if degr:
998         degr_text = "*DEGRADED*"
999       else:
1000         degr_text = "ok"
1001       if ldisk:
1002         ldisk_text = " *MISSING DISK*"
1003       else:
1004         ldisk_text = ""
1005       txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1006     elif dtype == constants.LD_LV:
1007       if ldisk:
1008         ldisk_text = " *FAILED* (failed drive?)"
1009       else:
1010         ldisk_text = ""
1011       txt += ldisk_text
1012     return txt
1013
1014   # the header
1015   if top_level:
1016     if dev["iv_name"] is not None:
1017       txt = dev["iv_name"]
1018     else:
1019       txt = "disk %d" % idx
1020   else:
1021     txt = "child %d" % idx
1022   d1 = ["- %s: %s" % (txt, dev["dev_type"])]
1023   data = []
1024   if top_level:
1025     data.append(("access mode", dev["mode"]))
1026   if dev["logical_id"] is not None:
1027     try:
1028       l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"])
1029     except ValueError:
1030       l_id = [str(dev["logical_id"])]
1031     if len(l_id) == 1:
1032       data.append(("logical_id", l_id[0]))
1033     else:
1034       data.extend(l_id)
1035   elif dev["physical_id"] is not None:
1036     data.append("physical_id:")
1037     data.append([dev["physical_id"]])
1038   if not static:
1039     data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1040   if dev["sstatus"] and not static:
1041     data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1042
1043   if dev["children"]:
1044     data.append("child devices:")
1045     for c_idx, child in enumerate(dev["children"]):
1046       data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1047   d1.append(data)
1048   return d1
1049
1050
1051 def _FormatList(buf, data, indent_level):
1052   """Formats a list of data at a given indent level.
1053
1054   If the element of the list is:
1055     - a string, it is simply formatted as is
1056     - a tuple, it will be split into key, value and the all the
1057       values in a list will be aligned all at the same start column
1058     - a list, will be recursively formatted
1059
1060   @type buf: StringIO
1061   @param buf: the buffer into which we write the output
1062   @param data: the list to format
1063   @type indent_level: int
1064   @param indent_level: the indent level to format at
1065
1066   """
1067   max_tlen = max([len(elem[0]) for elem in data
1068                  if isinstance(elem, tuple)] or [0])
1069   for elem in data:
1070     if isinstance(elem, basestring):
1071       buf.write("%*s%s\n" % (2*indent_level, "", elem))
1072     elif isinstance(elem, tuple):
1073       key, value = elem
1074       spacer = "%*s" % (max_tlen - len(key), "")
1075       buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1076     elif isinstance(elem, list):
1077       _FormatList(buf, elem, indent_level+1)
1078
1079 def ShowInstanceConfig(opts, args):
1080   """Compute instance run-time status.
1081
1082   @param opts: the command line options selected by the user
1083   @type args: list
1084   @param args: either an empty list, and then we query all
1085       instances, or should contain a list of instance names
1086   @rtype: int
1087   @return: the desired exit code
1088
1089   """
1090   retcode = 0
1091   op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1092   result = SubmitOpCode(op)
1093   if not result:
1094     ToStdout("No instances.")
1095     return 1
1096
1097   buf = StringIO()
1098   retcode = 0
1099   for instance_name in result:
1100     instance = result[instance_name]
1101     buf.write("Instance name: %s\n" % instance["name"])
1102     buf.write("State: configured to be %s" % instance["config_state"])
1103     if not opts.static:
1104       buf.write(", actual state is %s" % instance["run_state"])
1105     buf.write("\n")
1106     ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1107     ##          instance["auto_balance"])
1108     buf.write("  Nodes:\n")
1109     buf.write("    - primary: %s\n" % instance["pnode"])
1110     buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
1111     buf.write("  Operating system: %s\n" % instance["os"])
1112     if instance.has_key("network_port"):
1113       buf.write("  Allocated network port: %s\n" % instance["network_port"])
1114     buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1115     if instance["hypervisor"] == constants.HT_XEN_PVM:
1116       hvattrs = ((constants.HV_KERNEL_PATH, "kernel path"),
1117                  (constants.HV_INITRD_PATH, "initrd path"))
1118     elif instance["hypervisor"] == constants.HT_XEN_HVM:
1119       hvattrs = ((constants.HV_BOOT_ORDER, "boot order"),
1120                  (constants.HV_ACPI, "ACPI"),
1121                  (constants.HV_PAE, "PAE"),
1122                  (constants.HV_CDROM_IMAGE_PATH, "virtual CDROM"),
1123                  (constants.HV_NIC_TYPE, "NIC type"),
1124                  (constants.HV_DISK_TYPE, "Disk type"),
1125                  (constants.HV_VNC_BIND_ADDRESS, "VNC bind address"),
1126                  )
1127       # custom console information for HVM
1128       vnc_bind_address = instance["hv_actual"][constants.HV_VNC_BIND_ADDRESS]
1129       if vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
1130         vnc_console_port = "%s:%s" % (instance["pnode"],
1131                                       instance["network_port"])
1132       elif vnc_bind_address == constants.LOCALHOST_IP_ADDRESS:
1133         vnc_console_port = "%s:%s on node %s" % (vnc_bind_address,
1134                                                  instance["network_port"],
1135                                                  instance["pnode"])
1136       else:
1137         vnc_console_port = "%s:%s" % (vnc_bind_address,
1138                                       instance["network_port"])
1139       buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1140
1141     else:
1142       # auto-handle other hypervisor types
1143       hvattrs = [(key, key) for key in instance["hv_actual"]]
1144
1145     for key, desc in hvattrs:
1146       if key in instance["hv_instance"]:
1147         val = instance["hv_instance"][key]
1148       else:
1149         val = "default (%s)" % instance["hv_actual"][key]
1150       buf.write("    - %s: %s\n" % (desc, val))
1151     buf.write("  Hardware:\n")
1152     buf.write("    - VCPUs: %d\n" %
1153               instance["be_actual"][constants.BE_VCPUS])
1154     buf.write("    - memory: %dMiB\n" %
1155               instance["be_actual"][constants.BE_MEMORY])
1156     buf.write("    - NICs:\n")
1157     for idx, (mac, ip, bridge) in enumerate(instance["nics"]):
1158       buf.write("      - nic/%d: MAC: %s, IP: %s, bridge: %s\n" %
1159                 (idx, mac, ip, bridge))
1160     buf.write("  Disks:\n")
1161
1162     for idx, device in enumerate(instance["disks"]):
1163       _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1164
1165   ToStdout(buf.getvalue().rstrip('\n'))
1166   return retcode
1167
1168
1169 def SetInstanceParams(opts, args):
1170   """Modifies an instance.
1171
1172   All parameters take effect only at the next restart of the instance.
1173
1174   @param opts: the command line options selected by the user
1175   @type args: list
1176   @param args: should contain only one element, the instance name
1177   @rtype: int
1178   @return: the desired exit code
1179
1180   """
1181   if not (opts.nics or opts.disks or
1182           opts.hypervisor or opts.beparams):
1183     ToStderr("Please give at least one of the parameters.")
1184     return 1
1185
1186   for param in opts.beparams:
1187     if isinstance(opts.beparams[param], basestring):
1188       if opts.beparams[param].lower() == "default":
1189         opts.beparams[param] = constants.VALUE_DEFAULT
1190       elif opts.beparams[param].lower() == "none":
1191         opts.beparams[param] = constants.VALUE_NONE
1192       elif param == constants.BE_MEMORY:
1193         opts.beparams[constants.BE_MEMORY] = \
1194           utils.ParseUnit(opts.beparams[constants.BE_MEMORY])
1195
1196   for param in opts.hypervisor:
1197     if isinstance(opts.hypervisor[param], basestring):
1198       if opts.hypervisor[param].lower() == "default":
1199         opts.hypervisor[param] = constants.VALUE_DEFAULT
1200       elif opts.hypervisor[param].lower() == "none":
1201         opts.hypervisor[param] = constants.VALUE_NONE
1202
1203   for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1204     try:
1205       nic_op = int(nic_op)
1206       opts.nics[idx] = (nic_op, nic_dict)
1207     except ValueError:
1208       pass
1209
1210   for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1211     try:
1212       disk_op = int(disk_op)
1213       opts.disks[idx] = (disk_op, disk_dict)
1214     except ValueError:
1215       pass
1216     if disk_op == constants.DDM_ADD:
1217       if 'size' not in disk_dict:
1218         raise errors.OpPrereqError("Missing required parameter 'size'")
1219       disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1220
1221   op = opcodes.OpSetInstanceParams(instance_name=args[0],
1222                                    nics=opts.nics,
1223                                    disks=opts.disks,
1224                                    hvparams=opts.hypervisor,
1225                                    beparams=opts.beparams,
1226                                    force=opts.force)
1227
1228   # even if here we process the result, we allow submit only
1229   result = SubmitOrSend(op, opts)
1230
1231   if result:
1232     ToStdout("Modified instance %s", args[0])
1233     for param, data in result:
1234       ToStdout(" - %-5s -> %s", param, data)
1235     ToStdout("Please don't forget that these parameters take effect"
1236              " only at the next start of the instance.")
1237   return 0
1238
1239
1240 # options used in more than one cmd
1241 node_opt = make_option("-n", "--node", dest="node", help="Target node",
1242                        metavar="<node>")
1243
1244 os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1245                     metavar="<os>")
1246
1247 # multi-instance selection options
1248 m_force_multi = make_option("--force-multiple", dest="force_multi",
1249                             help="Do not ask for confirmation when more than"
1250                             " one instance is affected",
1251                             action="store_true", default=False)
1252
1253 m_pri_node_opt = make_option("--primary", dest="multi_mode",
1254                              help="Filter by nodes (primary only)",
1255                              const=_SHUTDOWN_NODES_PRI, action="store_const")
1256
1257 m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1258                              help="Filter by nodes (secondary only)",
1259                              const=_SHUTDOWN_NODES_SEC, action="store_const")
1260
1261 m_node_opt = make_option("--node", dest="multi_mode",
1262                          help="Filter by nodes (primary and secondary)",
1263                          const=_SHUTDOWN_NODES_BOTH, action="store_const")
1264
1265 m_clust_opt = make_option("--all", dest="multi_mode",
1266                           help="Select all instances in the cluster",
1267                           const=_SHUTDOWN_CLUSTER, action="store_const")
1268
1269 m_inst_opt = make_option("--instance", dest="multi_mode",
1270                          help="Filter by instance name [default]",
1271                          const=_SHUTDOWN_INSTANCES, action="store_const")
1272
1273
1274 # this is defined separately due to readability only
1275 add_opts = [
1276   DEBUG_OPT,
1277   make_option("-n", "--node", dest="node",
1278               help="Target node and optional secondary node",
1279               metavar="<pnode>[:<snode>]"),
1280   os_opt,
1281   keyval_option("-B", "--backend", dest="beparams",
1282                 type="keyval", default={},
1283                 help="Backend parameters"),
1284   make_option("-t", "--disk-template", dest="disk_template",
1285               help="Custom disk setup (diskless, file, plain or drbd)",
1286               default=None, metavar="TEMPL"),
1287   ikv_option("--disk", help="Disk information",
1288              default=[], dest="disks",
1289              action="append",
1290              type="identkeyval"),
1291   ikv_option("--net", help="NIC information",
1292              default=[], dest="nics",
1293              action="append",
1294              type="identkeyval"),
1295   make_option("--no-nics", default=False, action="store_true",
1296               help="Do not create any network cards for the instance"),
1297   make_option("--no-wait-for-sync", dest="wait_for_sync", default=True,
1298               action="store_false", help="Don't wait for sync (DANGEROUS!)"),
1299   make_option("--no-start", dest="start", default=True,
1300               action="store_false", help="Don't start the instance after"
1301               " creation"),
1302   make_option("--no-ip-check", dest="ip_check", default=True,
1303               action="store_false", help="Don't check that the instance's IP"
1304               " is alive (only valid with --no-start)"),
1305   make_option("--file-storage-dir", dest="file_storage_dir",
1306               help="Relative path under default cluster-wide file storage dir"
1307               " to store file-based disks", default=None,
1308               metavar="<DIR>"),
1309   make_option("--file-driver", dest="file_driver", help="Driver to use"
1310               " for image files", default="loop", metavar="<DRIVER>"),
1311   make_option("-I", "--iallocator", metavar="<NAME>",
1312               help="Select nodes for the instance automatically using the"
1313               " <NAME> iallocator plugin", default=None, type="string"),
1314   ikv_option("-H", "--hypervisor", dest="hypervisor",
1315               help="Hypervisor and hypervisor options, in the format"
1316               " hypervisor:option=value,option=value,...", default=None,
1317               type="identkeyval"),
1318   SUBMIT_OPT,
1319   ]
1320
1321 commands = {
1322   'add': (AddInstance, ARGS_ONE, add_opts,
1323           "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1324           "Creates and adds a new instance to the cluster"),
1325   'batch-create': (BatchCreate, ARGS_ONE,
1326                    [DEBUG_OPT],
1327                    "<instances_file.json>",
1328                    "Create a bunch of instances based on specs in the file."),
1329   'console': (ConnectToInstanceConsole, ARGS_ONE,
1330               [DEBUG_OPT,
1331                make_option("--show-cmd", dest="show_command",
1332                            action="store_true", default=False,
1333                            help=("Show command instead of executing it"))],
1334               "[--show-cmd] <instance>",
1335               "Opens a console on the specified instance"),
1336   'failover': (FailoverInstance, ARGS_ONE,
1337                [DEBUG_OPT, FORCE_OPT,
1338                 make_option("--ignore-consistency", dest="ignore_consistency",
1339                             action="store_true", default=False,
1340                             help="Ignore the consistency of the disks on"
1341                             " the secondary"),
1342                 SUBMIT_OPT,
1343                 ],
1344                "[-f] <instance>",
1345                "Stops the instance and starts it on the backup node, using"
1346                " the remote mirror (only for instances of type drbd)"),
1347   'migrate': (MigrateInstance, ARGS_ONE,
1348                [DEBUG_OPT, FORCE_OPT,
1349                 make_option("--non-live", dest="live",
1350                             default=True, action="store_false",
1351                             help="Do a non-live migration (this usually means"
1352                             " freeze the instance, save the state,"
1353                             " transfer and only then resume running on the"
1354                             " secondary node)"),
1355                 make_option("--cleanup", dest="cleanup",
1356                             default=False, action="store_true",
1357                             help="Instead of performing the migration, try to"
1358                             " recover from a failed cleanup. This is safe"
1359                             " to run even if the instance is healthy, but it"
1360                             " will create extra replication traffic and "
1361                             " disrupt briefly the replication (like during the"
1362                             " migration"),
1363                 ],
1364                "[-f] <instance>",
1365                "Migrate instance to its secondary node"
1366                " (only for instances of type drbd)"),
1367   'info': (ShowInstanceConfig, ARGS_ANY,
1368            [DEBUG_OPT,
1369             make_option("-s", "--static", dest="static",
1370                         action="store_true", default=False,
1371                         help="Only show configuration data, not runtime data"),
1372             ], "[-s] [<instance>...]",
1373            "Show information on the specified instance(s)"),
1374   'list': (ListInstances, ARGS_ANY,
1375            [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT],
1376            "[<instance>...]",
1377            "Lists the instances and their status. The available fields are"
1378            " (see the man page for details): status, oper_state, oper_ram,"
1379            " name, os, pnode, snodes, admin_state, admin_ram, disk_template,"
1380            " ip, mac, bridge, sda_size, sdb_size, vcpus, serial_no,"
1381            " hypervisor."
1382            " The default field"
1383            " list is (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
1384            ),
1385   'reinstall': (ReinstallInstance, ARGS_ONE,
1386                 [DEBUG_OPT, FORCE_OPT, os_opt,
1387                  make_option("--select-os", dest="select_os",
1388                              action="store_true", default=False,
1389                              help="Interactive OS reinstall, lists available"
1390                              " OS templates for selection"),
1391                  SUBMIT_OPT,
1392                  ],
1393                 "[-f] <instance>", "Reinstall a stopped instance"),
1394   'remove': (RemoveInstance, ARGS_ONE,
1395              [DEBUG_OPT, FORCE_OPT,
1396               make_option("--ignore-failures", dest="ignore_failures",
1397                           action="store_true", default=False,
1398                           help=("Remove the instance from the cluster even"
1399                                 " if there are failures during the removal"
1400                                 " process (shutdown, disk removal, etc.)")),
1401               SUBMIT_OPT,
1402               ],
1403              "[-f] <instance>", "Shuts down the instance and removes it"),
1404   'rename': (RenameInstance, ARGS_FIXED(2),
1405              [DEBUG_OPT,
1406               make_option("--no-ip-check", dest="ignore_ip",
1407                           help="Do not check that the IP of the new name"
1408                           " is alive",
1409                           default=False, action="store_true"),
1410               SUBMIT_OPT,
1411               ],
1412              "<instance> <new_name>", "Rename the instance"),
1413   'replace-disks': (ReplaceDisks, ARGS_ONE,
1414                     [DEBUG_OPT,
1415                      make_option("-n", "--new-secondary", dest="new_secondary",
1416                                  help=("New secondary node (for secondary"
1417                                        " node change)"), metavar="NODE",
1418                                  default=None),
1419                      make_option("-p", "--on-primary", dest="on_primary",
1420                                  default=False, action="store_true",
1421                                  help=("Replace the disk(s) on the primary"
1422                                        " node (only for the drbd template)")),
1423                      make_option("-s", "--on-secondary", dest="on_secondary",
1424                                  default=False, action="store_true",
1425                                  help=("Replace the disk(s) on the secondary"
1426                                        " node (only for the drbd template)")),
1427                      make_option("--disks", dest="disks", default=None,
1428                                  help=("Comma-separated list of disks"
1429                                        " to replace (e.g. sda) (optional,"
1430                                        " defaults to all disks")),
1431                      make_option("-i", "--iallocator", metavar="<NAME>",
1432                                  help="Select new secondary for the instance"
1433                                  " automatically using the"
1434                                  " <NAME> iallocator plugin (enables"
1435                                  " secondary node replacement)",
1436                                  default=None, type="string"),
1437                      SUBMIT_OPT,
1438                      ],
1439                     "[-s|-p|-n NODE] <instance>",
1440                     "Replaces all disks for the instance"),
1441   'modify': (SetInstanceParams, ARGS_ONE,
1442              [DEBUG_OPT, FORCE_OPT,
1443               keyval_option("-H", "--hypervisor", type="keyval",
1444                             default={}, dest="hypervisor",
1445                             help="Change hypervisor parameters"),
1446               keyval_option("-B", "--backend", type="keyval",
1447                             default={}, dest="beparams",
1448                             help="Change backend parameters"),
1449               ikv_option("--disk", help="Disk changes",
1450                          default=[], dest="disks",
1451                          action="append",
1452                          type="identkeyval"),
1453               ikv_option("--net", help="NIC changes",
1454                          default=[], dest="nics",
1455                          action="append",
1456                          type="identkeyval"),
1457               SUBMIT_OPT,
1458               ],
1459              "<instance>", "Alters the parameters of an instance"),
1460   'shutdown': (ShutdownInstance, ARGS_ANY,
1461                [DEBUG_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1462                 m_clust_opt, m_inst_opt, m_force_multi,
1463                 SUBMIT_OPT,
1464                 ],
1465                "<instance>", "Stops an instance"),
1466   'startup': (StartupInstance, ARGS_ANY,
1467               [DEBUG_OPT, FORCE_OPT, m_force_multi,
1468                make_option("-e", "--extra", dest="extra_args",
1469                            help="Extra arguments for the instance's kernel",
1470                            default=None, type="string", metavar="<PARAMS>"),
1471                m_node_opt, m_pri_node_opt, m_sec_node_opt,
1472                m_clust_opt, m_inst_opt,
1473                SUBMIT_OPT,
1474                ],
1475             "<instance>", "Starts an instance"),
1476
1477   'reboot': (RebootInstance, ARGS_ANY,
1478               [DEBUG_OPT, m_force_multi,
1479                make_option("-e", "--extra", dest="extra_args",
1480                            help="Extra arguments for the instance's kernel",
1481                            default=None, type="string", metavar="<PARAMS>"),
1482                make_option("-t", "--type", dest="reboot_type",
1483                            help="Type of reboot: soft/hard/full",
1484                            default=constants.INSTANCE_REBOOT_HARD,
1485                            type="string", metavar="<REBOOT>"),
1486                make_option("--ignore-secondaries", dest="ignore_secondaries",
1487                            default=False, action="store_true",
1488                            help="Ignore errors from secondaries"),
1489                m_node_opt, m_pri_node_opt, m_sec_node_opt,
1490                m_clust_opt, m_inst_opt,
1491                SUBMIT_OPT,
1492                ],
1493             "<instance>", "Reboots an instance"),
1494   'activate-disks': (ActivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1495                      "<instance>",
1496                      "Activate an instance's disks"),
1497   'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1498                        "<instance>",
1499                        "Deactivate an instance's disks"),
1500   'grow-disk': (GrowDisk, ARGS_FIXED(3),
1501                 [DEBUG_OPT, SUBMIT_OPT,
1502                  make_option("--no-wait-for-sync",
1503                              dest="wait_for_sync", default=True,
1504                              action="store_false",
1505                              help="Don't wait for sync (DANGEROUS!)"),
1506                  ],
1507                 "<instance> <disk> <size>", "Grow an instance's disk"),
1508   'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
1509                 "<instance_name>", "List the tags of the given instance"),
1510   'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1511                "<instance_name> tag...", "Add tags to the given instance"),
1512   'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1513                   "<instance_name> tag...", "Remove tags from given instance"),
1514   }
1515
1516 #: dictionary with aliases for commands
1517 aliases = {
1518   'activate_block_devs': 'activate-disks',
1519   'replace_disks': 'replace-disks',
1520   'start': 'startup',
1521   'stop': 'shutdown',
1522   }
1523
1524 if __name__ == '__main__':
1525   sys.exit(GenericMain(commands, aliases=aliases,
1526                        override={"tag_type": constants.TAG_INSTANCE}))