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