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