Remove an unused function
[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     jex.QueueJob(name, op)
691   jex.WaitOrShow(not opts.submit_only)
692   return 0
693
694
695 def RebootInstance(opts, args):
696   """Reboot instance(s).
697
698   Depending on the parameters given, this will reboot one or more
699   instances.
700
701   @param opts: the command line options selected by the user
702   @type args: list
703   @param args: the instance or node names based on which we
704       create the final selection (in conjunction with the
705       opts argument)
706   @rtype: int
707   @return: the desired exit code
708
709   """
710   cl = GetClient()
711   if opts.multi_mode is None:
712     opts.multi_mode = _SHUTDOWN_INSTANCES
713   inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
714   if not inames:
715     raise errors.OpPrereqError("Selection filter does not match any instances")
716   multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
717   if not (opts.force_multi or not multi_on
718           or _ConfirmOperation(inames, "reboot")):
719     return 1
720   jex = JobExecutor(verbose=multi_on, cl=cl)
721   for name in inames:
722     op = opcodes.OpRebootInstance(instance_name=name,
723                                   reboot_type=opts.reboot_type,
724                                   ignore_secondaries=opts.ignore_secondaries)
725     jex.QueueJob(name, op)
726   jex.WaitOrShow(not opts.submit_only)
727   return 0
728
729
730 def ShutdownInstance(opts, args):
731   """Shutdown an instance.
732
733   @param opts: the command line options selected by the user
734   @type args: list
735   @param args: the instance or node names based on which we
736       create the final selection (in conjunction with the
737       opts argument)
738   @rtype: int
739   @return: the desired exit code
740
741   """
742   cl = GetClient()
743   if opts.multi_mode is None:
744     opts.multi_mode = _SHUTDOWN_INSTANCES
745   inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
746   if not inames:
747     raise errors.OpPrereqError("Selection filter does not match any instances")
748   multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
749   if not (opts.force_multi or not multi_on
750           or _ConfirmOperation(inames, "shutdown")):
751     return 1
752
753   jex = cli.JobExecutor(verbose=multi_on, cl=cl)
754   for name in inames:
755     op = opcodes.OpShutdownInstance(instance_name=name)
756     jex.QueueJob(name, op)
757   jex.WaitOrShow(not opts.submit_only)
758   return 0
759
760
761 def ReplaceDisks(opts, args):
762   """Replace the disks of an instance
763
764   @param opts: the command line options selected by the user
765   @type args: list
766   @param args: should contain only one element, the instance name
767   @rtype: int
768   @return: the desired exit code
769
770   """
771   instance_name = args[0]
772   new_2ndary = opts.new_secondary
773   iallocator = opts.iallocator
774   if opts.disks is None:
775     disks = []
776   else:
777     try:
778       disks = [int(i) for i in opts.disks.split(",")]
779     except ValueError, err:
780       raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
781   cnt = [opts.on_primary, opts.on_secondary,
782          new_2ndary is not None, iallocator is not None].count(True)
783   if cnt != 1:
784     raise errors.OpPrereqError("One and only one of the -p, -s, -n and -i"
785                                " options must be passed")
786   elif opts.on_primary:
787     mode = constants.REPLACE_DISK_PRI
788   elif opts.on_secondary:
789     mode = constants.REPLACE_DISK_SEC
790   elif new_2ndary is not None or iallocator is not None:
791     # replace secondary
792     mode = constants.REPLACE_DISK_CHG
793
794   op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
795                               remote_node=new_2ndary, mode=mode,
796                               iallocator=iallocator)
797   SubmitOrSend(op, opts)
798   return 0
799
800
801 def FailoverInstance(opts, args):
802   """Failover an instance.
803
804   The failover is done by shutting it down on its present node and
805   starting it on the secondary.
806
807   @param opts: the command line options selected by the user
808   @type args: list
809   @param args: should contain only one element, the instance name
810   @rtype: int
811   @return: the desired exit code
812
813   """
814   cl = GetClient()
815   instance_name = args[0]
816   force = opts.force
817
818   if not force:
819     _EnsureInstancesExist(cl, [instance_name])
820
821     usertext = ("Failover will happen to image %s."
822                 " This requires a shutdown of the instance. Continue?" %
823                 (instance_name,))
824     if not AskUser(usertext):
825       return 1
826
827   op = opcodes.OpFailoverInstance(instance_name=instance_name,
828                                   ignore_consistency=opts.ignore_consistency)
829   SubmitOrSend(op, opts, cl=cl)
830   return 0
831
832
833 def MigrateInstance(opts, args):
834   """Migrate an instance.
835
836   The migrate is done without shutdown.
837
838   @param opts: the command line options selected by the user
839   @type args: list
840   @param args: should contain only one element, the instance name
841   @rtype: int
842   @return: the desired exit code
843
844   """
845   cl = GetClient()
846   instance_name = args[0]
847   force = opts.force
848
849   if not force:
850     _EnsureInstancesExist(cl, [instance_name])
851
852     if opts.cleanup:
853       usertext = ("Instance %s will be recovered from a failed migration."
854                   " Note that the migration procedure (including cleanup)" %
855                   (instance_name,))
856     else:
857       usertext = ("Instance %s will be migrated. Note that migration" %
858                   (instance_name,))
859     usertext += (" is **experimental** in this version."
860                 " This might impact the instance if anything goes wrong."
861                 " Continue?")
862     if not AskUser(usertext):
863       return 1
864
865   op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
866                                  cleanup=opts.cleanup)
867   SubmitOpCode(op, cl=cl)
868   return 0
869
870
871 def ConnectToInstanceConsole(opts, args):
872   """Connect to the console of an instance.
873
874   @param opts: the command line options selected by the user
875   @type args: list
876   @param args: should contain only one element, the instance name
877   @rtype: int
878   @return: the desired exit code
879
880   """
881   instance_name = args[0]
882
883   op = opcodes.OpConnectConsole(instance_name=instance_name)
884   cmd = SubmitOpCode(op)
885
886   if opts.show_command:
887     ToStdout("%s", utils.ShellQuoteArgs(cmd))
888   else:
889     try:
890       os.execvp(cmd[0], cmd)
891     finally:
892       ToStderr("Can't run console command %s with arguments:\n'%s'",
893                cmd[0], " ".join(cmd))
894       os._exit(1)
895
896
897 def _FormatLogicalID(dev_type, logical_id):
898   """Formats the logical_id of a disk.
899
900   """
901   if dev_type == constants.LD_DRBD8:
902     node_a, node_b, port, minor_a, minor_b, key = logical_id
903     data = [
904       ("nodeA", "%s, minor=%s" % (node_a, minor_a)),
905       ("nodeB", "%s, minor=%s" % (node_b, minor_b)),
906       ("port", port),
907       ("auth key", key),
908       ]
909   elif dev_type == constants.LD_LV:
910     vg_name, lv_name = logical_id
911     data = ["%s/%s" % (vg_name, lv_name)]
912   else:
913     data = [str(logical_id)]
914
915   return data
916
917
918 def _FormatBlockDevInfo(idx, top_level, dev, static):
919   """Show block device information.
920
921   This is only used by L{ShowInstanceConfig}, but it's too big to be
922   left for an inline definition.
923
924   @type idx: int
925   @param idx: the index of the current disk
926   @type top_level: boolean
927   @param top_level: if this a top-level disk?
928   @type dev: dict
929   @param dev: dictionary with disk information
930   @type static: boolean
931   @param static: wheter the device information doesn't contain
932       runtime information but only static data
933   @return: a list of either strings, tuples or lists
934       (which should be formatted at a higher indent level)
935
936   """
937   def helper(dtype, status):
938     """Format one line for physical device status.
939
940     @type dtype: str
941     @param dtype: a constant from the L{constants.LDS_BLOCK} set
942     @type status: tuple
943     @param status: a tuple as returned from L{backend.FindBlockDevice}
944     @return: the string representing the status
945
946     """
947     if not status:
948       return "not active"
949     txt = ""
950     (path, major, minor, syncp, estt, degr, ldisk) = status
951     if major is None:
952       major_string = "N/A"
953     else:
954       major_string = str(major)
955
956     if minor is None:
957       minor_string = "N/A"
958     else:
959       minor_string = str(minor)
960
961     txt += ("%s (%s:%s)" % (path, major_string, minor_string))
962     if dtype in (constants.LD_DRBD8, ):
963       if syncp is not None:
964         sync_text = "*RECOVERING* %5.2f%%," % syncp
965         if estt:
966           sync_text += " ETA %ds" % estt
967         else:
968           sync_text += " ETA unknown"
969       else:
970         sync_text = "in sync"
971       if degr:
972         degr_text = "*DEGRADED*"
973       else:
974         degr_text = "ok"
975       if ldisk:
976         ldisk_text = " *MISSING DISK*"
977       else:
978         ldisk_text = ""
979       txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
980     elif dtype == constants.LD_LV:
981       if ldisk:
982         ldisk_text = " *FAILED* (failed drive?)"
983       else:
984         ldisk_text = ""
985       txt += ldisk_text
986     return txt
987
988   # the header
989   if top_level:
990     if dev["iv_name"] is not None:
991       txt = dev["iv_name"]
992     else:
993       txt = "disk %d" % idx
994   else:
995     txt = "child %d" % idx
996   d1 = ["- %s: %s" % (txt, dev["dev_type"])]
997   data = []
998   if top_level:
999     data.append(("access mode", dev["mode"]))
1000   if dev["logical_id"] is not None:
1001     try:
1002       l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"])
1003     except ValueError:
1004       l_id = [str(dev["logical_id"])]
1005     if len(l_id) == 1:
1006       data.append(("logical_id", l_id[0]))
1007     else:
1008       data.extend(l_id)
1009   elif dev["physical_id"] is not None:
1010     data.append("physical_id:")
1011     data.append([dev["physical_id"]])
1012   if not static:
1013     data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1014   if dev["sstatus"] and not static:
1015     data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1016
1017   if dev["children"]:
1018     data.append("child devices:")
1019     for c_idx, child in enumerate(dev["children"]):
1020       data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1021   d1.append(data)
1022   return d1
1023
1024
1025 def _FormatList(buf, data, indent_level):
1026   """Formats a list of data at a given indent level.
1027
1028   If the element of the list is:
1029     - a string, it is simply formatted as is
1030     - a tuple, it will be split into key, value and the all the
1031       values in a list will be aligned all at the same start column
1032     - a list, will be recursively formatted
1033
1034   @type buf: StringIO
1035   @param buf: the buffer into which we write the output
1036   @param data: the list to format
1037   @type indent_level: int
1038   @param indent_level: the indent level to format at
1039
1040   """
1041   max_tlen = max([len(elem[0]) for elem in data
1042                  if isinstance(elem, tuple)] or [0])
1043   for elem in data:
1044     if isinstance(elem, basestring):
1045       buf.write("%*s%s\n" % (2*indent_level, "", elem))
1046     elif isinstance(elem, tuple):
1047       key, value = elem
1048       spacer = "%*s" % (max_tlen - len(key), "")
1049       buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1050     elif isinstance(elem, list):
1051       _FormatList(buf, elem, indent_level+1)
1052
1053 def ShowInstanceConfig(opts, args):
1054   """Compute instance run-time status.
1055
1056   @param opts: the command line options selected by the user
1057   @type args: list
1058   @param args: either an empty list, and then we query all
1059       instances, or should contain a list of instance names
1060   @rtype: int
1061   @return: the desired exit code
1062
1063   """
1064   if not args and not opts.show_all:
1065     ToStderr("No instance selected."
1066              " Please pass in --all if you want to query all instances.\n"
1067              "Note that this can take a long time on a big cluster.")
1068     return 1
1069   elif args and opts.show_all:
1070     ToStderr("Cannot use --all if you specify instance names.")
1071     return 1
1072
1073   retcode = 0
1074   op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1075   result = SubmitOpCode(op)
1076   if not result:
1077     ToStdout("No instances.")
1078     return 1
1079
1080   buf = StringIO()
1081   retcode = 0
1082   for instance_name in result:
1083     instance = result[instance_name]
1084     buf.write("Instance name: %s\n" % instance["name"])
1085     buf.write("State: configured to be %s" % instance["config_state"])
1086     if not opts.static:
1087       buf.write(", actual state is %s" % instance["run_state"])
1088     buf.write("\n")
1089     ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1090     ##          instance["auto_balance"])
1091     buf.write("  Nodes:\n")
1092     buf.write("    - primary: %s\n" % instance["pnode"])
1093     buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
1094     buf.write("  Operating system: %s\n" % instance["os"])
1095     if instance.has_key("network_port"):
1096       buf.write("  Allocated network port: %s\n" % instance["network_port"])
1097     buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1098
1099     # custom VNC console information
1100     vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1101                                                  None)
1102     if vnc_bind_address:
1103       port = instance["network_port"]
1104       display = int(port) - constants.VNC_BASE_PORT
1105       if display > 0 and vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
1106         vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1107                                                    port,
1108                                                    display)
1109       elif display > 0 and utils.IsValidIP(vnc_bind_address):
1110         vnc_console_port = ("%s:%s (node %s) (display %s)" %
1111                              (vnc_bind_address, port,
1112                               instance["pnode"], display))
1113       else:
1114         # vnc bind address is a file
1115         vnc_console_port = "%s:%s" % (instance["pnode"],
1116                                       vnc_bind_address)
1117       buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1118
1119     for key in instance["hv_actual"]:
1120       if key in instance["hv_instance"]:
1121         val = instance["hv_instance"][key]
1122       else:
1123         val = "default (%s)" % instance["hv_actual"][key]
1124       buf.write("    - %s: %s\n" % (key, val))
1125     buf.write("  Hardware:\n")
1126     buf.write("    - VCPUs: %d\n" %
1127               instance["be_actual"][constants.BE_VCPUS])
1128     buf.write("    - memory: %dMiB\n" %
1129               instance["be_actual"][constants.BE_MEMORY])
1130     buf.write("    - NICs:\n")
1131     for idx, (mac, ip, bridge) in enumerate(instance["nics"]):
1132       buf.write("      - nic/%d: MAC: %s, IP: %s, bridge: %s\n" %
1133                 (idx, mac, ip, bridge))
1134     buf.write("  Disks:\n")
1135
1136     for idx, device in enumerate(instance["disks"]):
1137       _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1138
1139   ToStdout(buf.getvalue().rstrip('\n'))
1140   return retcode
1141
1142
1143 def SetInstanceParams(opts, args):
1144   """Modifies an instance.
1145
1146   All parameters take effect only at the next restart of the instance.
1147
1148   @param opts: the command line options selected by the user
1149   @type args: list
1150   @param args: should contain only one element, the instance name
1151   @rtype: int
1152   @return: the desired exit code
1153
1154   """
1155   if not (opts.nics or opts.disks or
1156           opts.hypervisor or opts.beparams):
1157     ToStderr("Please give at least one of the parameters.")
1158     return 1
1159
1160   for param in opts.beparams:
1161     if isinstance(opts.beparams[param], basestring):
1162       if opts.beparams[param].lower() == "default":
1163         opts.beparams[param] = constants.VALUE_DEFAULT
1164
1165   utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1166                       allowed_values=[constants.VALUE_DEFAULT])
1167
1168   for param in opts.hypervisor:
1169     if isinstance(opts.hypervisor[param], basestring):
1170       if opts.hypervisor[param].lower() == "default":
1171         opts.hypervisor[param] = constants.VALUE_DEFAULT
1172
1173   utils.ForceDictType(opts.hypervisor, constants.HVS_PARAMETER_TYPES,
1174                       allowed_values=[constants.VALUE_DEFAULT])
1175
1176   for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1177     try:
1178       nic_op = int(nic_op)
1179       opts.nics[idx] = (nic_op, nic_dict)
1180     except ValueError:
1181       pass
1182
1183   for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1184     try:
1185       disk_op = int(disk_op)
1186       opts.disks[idx] = (disk_op, disk_dict)
1187     except ValueError:
1188       pass
1189     if disk_op == constants.DDM_ADD:
1190       if 'size' not in disk_dict:
1191         raise errors.OpPrereqError("Missing required parameter 'size'")
1192       disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1193
1194   op = opcodes.OpSetInstanceParams(instance_name=args[0],
1195                                    nics=opts.nics,
1196                                    disks=opts.disks,
1197                                    hvparams=opts.hypervisor,
1198                                    beparams=opts.beparams,
1199                                    force=opts.force)
1200
1201   # even if here we process the result, we allow submit only
1202   result = SubmitOrSend(op, opts)
1203
1204   if result:
1205     ToStdout("Modified instance %s", args[0])
1206     for param, data in result:
1207       ToStdout(" - %-5s -> %s", param, data)
1208     ToStdout("Please don't forget that these parameters take effect"
1209              " only at the next start of the instance.")
1210   return 0
1211
1212
1213 # options used in more than one cmd
1214 node_opt = make_option("-n", "--node", dest="node", help="Target node",
1215                        metavar="<node>")
1216
1217 os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1218                     metavar="<os>")
1219
1220 # multi-instance selection options
1221 m_force_multi = make_option("--force-multiple", dest="force_multi",
1222                             help="Do not ask for confirmation when more than"
1223                             " one instance is affected",
1224                             action="store_true", default=False)
1225
1226 m_pri_node_opt = make_option("--primary", dest="multi_mode",
1227                              help="Filter by nodes (primary only)",
1228                              const=_SHUTDOWN_NODES_PRI, action="store_const")
1229
1230 m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1231                              help="Filter by nodes (secondary only)",
1232                              const=_SHUTDOWN_NODES_SEC, action="store_const")
1233
1234 m_node_opt = make_option("--node", dest="multi_mode",
1235                          help="Filter by nodes (primary and secondary)",
1236                          const=_SHUTDOWN_NODES_BOTH, action="store_const")
1237
1238 m_clust_opt = make_option("--all", dest="multi_mode",
1239                           help="Select all instances in the cluster",
1240                           const=_SHUTDOWN_CLUSTER, action="store_const")
1241
1242 m_inst_opt = make_option("--instance", dest="multi_mode",
1243                          help="Filter by instance name [default]",
1244                          const=_SHUTDOWN_INSTANCES, action="store_const")
1245
1246
1247 # this is defined separately due to readability only
1248 add_opts = [
1249   DEBUG_OPT,
1250   make_option("-n", "--node", dest="node",
1251               help="Target node and optional secondary node",
1252               metavar="<pnode>[:<snode>]"),
1253   os_opt,
1254   keyval_option("-B", "--backend", dest="beparams",
1255                 type="keyval", default={},
1256                 help="Backend parameters"),
1257   make_option("-t", "--disk-template", dest="disk_template",
1258               help="Custom disk setup (diskless, file, plain or drbd)",
1259               default=None, metavar="TEMPL"),
1260   cli_option("-s", "--os-size", dest="sd_size", help="Disk size for a"
1261              " single-disk configuration, when not using the --disk option,"
1262              " in MiB unless a suffix is used",
1263              default=None, type="unit", metavar="<size>"),
1264   ikv_option("--disk", help="Disk information",
1265              default=[], dest="disks",
1266              action="append",
1267              type="identkeyval"),
1268   ikv_option("--net", help="NIC information",
1269              default=[], dest="nics",
1270              action="append",
1271              type="identkeyval"),
1272   make_option("--no-nics", default=False, action="store_true",
1273               help="Do not create any network cards for the instance"),
1274   make_option("--no-wait-for-sync", dest="wait_for_sync", default=True,
1275               action="store_false", help="Don't wait for sync (DANGEROUS!)"),
1276   make_option("--no-start", dest="start", default=True,
1277               action="store_false", help="Don't start the instance after"
1278               " creation"),
1279   make_option("--no-ip-check", dest="ip_check", default=True,
1280               action="store_false", help="Don't check that the instance's IP"
1281               " is alive (only valid with --no-start)"),
1282   make_option("--file-storage-dir", dest="file_storage_dir",
1283               help="Relative path under default cluster-wide file storage dir"
1284               " to store file-based disks", default=None,
1285               metavar="<DIR>"),
1286   make_option("--file-driver", dest="file_driver", help="Driver to use"
1287               " for image files", default="loop", metavar="<DRIVER>"),
1288   make_option("-I", "--iallocator", metavar="<NAME>",
1289               help="Select nodes for the instance automatically using the"
1290               " <NAME> iallocator plugin", default=None, type="string"),
1291   ikv_option("-H", "--hypervisor", dest="hypervisor",
1292               help="Hypervisor and hypervisor options, in the format"
1293               " hypervisor:option=value,option=value,...", default=None,
1294               type="identkeyval"),
1295   SUBMIT_OPT,
1296   ]
1297
1298 commands = {
1299   'add': (AddInstance, ARGS_ONE, add_opts,
1300           "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1301           "Creates and adds a new instance to the cluster"),
1302   'batch-create': (BatchCreate, ARGS_ONE,
1303                    [DEBUG_OPT],
1304                    "<instances_file.json>",
1305                    "Create a bunch of instances based on specs in the file."),
1306   'console': (ConnectToInstanceConsole, ARGS_ONE,
1307               [DEBUG_OPT,
1308                make_option("--show-cmd", dest="show_command",
1309                            action="store_true", default=False,
1310                            help=("Show command instead of executing it"))],
1311               "[--show-cmd] <instance>",
1312               "Opens a console on the specified instance"),
1313   'failover': (FailoverInstance, ARGS_ONE,
1314                [DEBUG_OPT, FORCE_OPT,
1315                 make_option("--ignore-consistency", dest="ignore_consistency",
1316                             action="store_true", default=False,
1317                             help="Ignore the consistency of the disks on"
1318                             " the secondary"),
1319                 SUBMIT_OPT,
1320                 ],
1321                "[-f] <instance>",
1322                "Stops the instance and starts it on the backup node, using"
1323                " the remote mirror (only for instances of type drbd)"),
1324   'migrate': (MigrateInstance, ARGS_ONE,
1325                [DEBUG_OPT, FORCE_OPT,
1326                 make_option("--non-live", dest="live",
1327                             default=True, action="store_false",
1328                             help="Do a non-live migration (this usually means"
1329                             " freeze the instance, save the state,"
1330                             " transfer and only then resume running on the"
1331                             " secondary node)"),
1332                 make_option("--cleanup", dest="cleanup",
1333                             default=False, action="store_true",
1334                             help="Instead of performing the migration, try to"
1335                             " recover from a failed cleanup. This is safe"
1336                             " to run even if the instance is healthy, but it"
1337                             " will create extra replication traffic and "
1338                             " disrupt briefly the replication (like during the"
1339                             " migration"),
1340                 ],
1341                "[-f] <instance>",
1342                "Migrate instance to its secondary node"
1343                " (only for instances of type drbd)"),
1344   'info': (ShowInstanceConfig, ARGS_ANY,
1345            [DEBUG_OPT,
1346             make_option("-s", "--static", dest="static",
1347                         action="store_true", default=False,
1348                         help="Only show configuration data, not runtime data"),
1349             make_option("--all", dest="show_all",
1350                         default=False, action="store_true",
1351                         help="Show info on all instances on the cluster."
1352                         " This can take a long time to run, use wisely."),
1353             ], "[-s] {--all | <instance>...}",
1354            "Show information on the specified instance(s)"),
1355   'list': (ListInstances, ARGS_ANY,
1356            [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT],
1357            "[<instance>...]",
1358            "Lists the instances and their status. The available fields are"
1359            " (see the man page for details): status, oper_state, oper_ram,"
1360            " name, os, pnode, snodes, admin_state, admin_ram, disk_template,"
1361            " ip, mac, bridge, sda_size, sdb_size, vcpus, serial_no,"
1362            " hypervisor."
1363            " The default field"
1364            " list is (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
1365            ),
1366   'reinstall': (ReinstallInstance, ARGS_ONE,
1367                 [DEBUG_OPT, FORCE_OPT, os_opt,
1368                  make_option("--select-os", dest="select_os",
1369                              action="store_true", default=False,
1370                              help="Interactive OS reinstall, lists available"
1371                              " OS templates for selection"),
1372                  SUBMIT_OPT,
1373                  ],
1374                 "[-f] <instance>", "Reinstall a stopped instance"),
1375   'remove': (RemoveInstance, ARGS_ONE,
1376              [DEBUG_OPT, FORCE_OPT,
1377               make_option("--ignore-failures", dest="ignore_failures",
1378                           action="store_true", default=False,
1379                           help=("Remove the instance from the cluster even"
1380                                 " if there are failures during the removal"
1381                                 " process (shutdown, disk removal, etc.)")),
1382               SUBMIT_OPT,
1383               ],
1384              "[-f] <instance>", "Shuts down the instance and removes it"),
1385   'rename': (RenameInstance, ARGS_FIXED(2),
1386              [DEBUG_OPT,
1387               make_option("--no-ip-check", dest="ignore_ip",
1388                           help="Do not check that the IP of the new name"
1389                           " is alive",
1390                           default=False, action="store_true"),
1391               SUBMIT_OPT,
1392               ],
1393              "<instance> <new_name>", "Rename the instance"),
1394   'replace-disks': (ReplaceDisks, ARGS_ONE,
1395                     [DEBUG_OPT,
1396                      make_option("-n", "--new-secondary", dest="new_secondary",
1397                                  help=("New secondary node (for secondary"
1398                                        " node change)"), metavar="NODE",
1399                                  default=None),
1400                      make_option("-p", "--on-primary", dest="on_primary",
1401                                  default=False, action="store_true",
1402                                  help=("Replace the disk(s) on the primary"
1403                                        " node (only for the drbd template)")),
1404                      make_option("-s", "--on-secondary", dest="on_secondary",
1405                                  default=False, action="store_true",
1406                                  help=("Replace the disk(s) on the secondary"
1407                                        " node (only for the drbd template)")),
1408                      make_option("--disks", dest="disks", default=None,
1409                                  help="Comma-separated list of disks"
1410                                  " indices to replace (e.g. 0,2) (optional,"
1411                                  " defaults to all disks)"),
1412                      make_option("-I", "--iallocator", metavar="<NAME>",
1413                                  help="Select new secondary for the instance"
1414                                  " automatically using the"
1415                                  " <NAME> iallocator plugin (enables"
1416                                  " secondary node replacement)",
1417                                  default=None, type="string"),
1418                      SUBMIT_OPT,
1419                      ],
1420                     "[-s|-p|-n NODE|-I NAME] <instance>",
1421                     "Replaces all disks for the instance"),
1422   'modify': (SetInstanceParams, ARGS_ONE,
1423              [DEBUG_OPT, FORCE_OPT,
1424               keyval_option("-H", "--hypervisor", type="keyval",
1425                             default={}, dest="hypervisor",
1426                             help="Change hypervisor parameters"),
1427               keyval_option("-B", "--backend", type="keyval",
1428                             default={}, dest="beparams",
1429                             help="Change backend parameters"),
1430               ikv_option("--disk", help="Disk changes",
1431                          default=[], dest="disks",
1432                          action="append",
1433                          type="identkeyval"),
1434               ikv_option("--net", help="NIC changes",
1435                          default=[], dest="nics",
1436                          action="append",
1437                          type="identkeyval"),
1438               SUBMIT_OPT,
1439               ],
1440              "<instance>", "Alters the parameters of an instance"),
1441   'shutdown': (ShutdownInstance, ARGS_ANY,
1442                [DEBUG_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1443                 m_clust_opt, m_inst_opt, m_force_multi,
1444                 SUBMIT_OPT,
1445                 ],
1446                "<instance>", "Stops an instance"),
1447   'startup': (StartupInstance, ARGS_ANY,
1448               [DEBUG_OPT, FORCE_OPT, m_force_multi,
1449                m_node_opt, m_pri_node_opt, m_sec_node_opt,
1450                m_clust_opt, m_inst_opt,
1451                SUBMIT_OPT,
1452                ],
1453             "<instance>", "Starts an instance"),
1454
1455   'reboot': (RebootInstance, ARGS_ANY,
1456               [DEBUG_OPT, m_force_multi,
1457                make_option("-t", "--type", dest="reboot_type",
1458                            help="Type of reboot: soft/hard/full",
1459                            default=constants.INSTANCE_REBOOT_HARD,
1460                            type="string", metavar="<REBOOT>"),
1461                make_option("--ignore-secondaries", dest="ignore_secondaries",
1462                            default=False, action="store_true",
1463                            help="Ignore errors from secondaries"),
1464                m_node_opt, m_pri_node_opt, m_sec_node_opt,
1465                m_clust_opt, m_inst_opt,
1466                SUBMIT_OPT,
1467                ],
1468             "<instance>", "Reboots an instance"),
1469   'activate-disks': (ActivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1470                      "<instance>",
1471                      "Activate an instance's disks"),
1472   'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1473                        "<instance>",
1474                        "Deactivate an instance's disks"),
1475   'grow-disk': (GrowDisk, ARGS_FIXED(3),
1476                 [DEBUG_OPT, SUBMIT_OPT,
1477                  make_option("--no-wait-for-sync",
1478                              dest="wait_for_sync", default=True,
1479                              action="store_false",
1480                              help="Don't wait for sync (DANGEROUS!)"),
1481                  ],
1482                 "<instance> <disk> <size>", "Grow an instance's disk"),
1483   'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
1484                 "<instance_name>", "List the tags of the given instance"),
1485   'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1486                "<instance_name> tag...", "Add tags to the given instance"),
1487   'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1488                   "<instance_name> tag...", "Remove tags from given instance"),
1489   }
1490
1491 #: dictionary with aliases for commands
1492 aliases = {
1493   'activate_block_devs': 'activate-disks',
1494   'replace_disks': 'replace-disks',
1495   'start': 'startup',
1496   'stop': 'shutdown',
1497   }
1498
1499 if __name__ == '__main__':
1500   sys.exit(GenericMain(commands, aliases=aliases,
1501                        override={"tag_type": constants.TAG_INSTANCE}))