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