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