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