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