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