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