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