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