ead004f82e41a810ce4a01dc1eab93d59daa14fb
[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 import time
31 from cStringIO import StringIO
32
33 from ganeti.cli import *
34 from ganeti import opcodes
35 from ganeti import constants
36 from ganeti import utils
37 from ganeti import errors
38
39
40 _SHUTDOWN_CLUSTER = "cluster"
41 _SHUTDOWN_NODES_BOTH = "nodes"
42 _SHUTDOWN_NODES_PRI = "nodes-pri"
43 _SHUTDOWN_NODES_SEC = "nodes-sec"
44 _SHUTDOWN_INSTANCES = "instances"
45
46
47 _VALUE_TRUE = "true"
48
49 #: default list of options for L{ListInstances}
50 _LIST_DEF_FIELDS = [
51   "name", "hypervisor", "os", "pnode", "status", "oper_ram",
52   ]
53
54
55 def _ExpandMultiNames(mode, names, client=None):
56   """Expand the given names using the passed mode.
57
58   For _SHUTDOWN_CLUSTER, all instances will be returned. For
59   _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
60   primary/secondary will be returned. For _SHUTDOWN_NODES_BOTH, all
61   instances having those nodes as either primary or secondary will be
62   returned. For _SHUTDOWN_INSTANCES, the given instances will be
63   returned.
64
65   @param mode: one of L{_SHUTDOWN_CLUSTER}, L{_SHUTDOWN_NODES_BOTH},
66       L{_SHUTDOWN_NODES_PRI}, L{_SHUTDOWN_NODES_SEC} or
67       L{_SHUTDOWN_INSTANCES}
68   @param names: a list of names; for cluster, it must be empty,
69       and for node and instance it must be a list of valid item
70       names (short names are valid as usual, e.g. node1 instead of
71       node1.example.com)
72   @rtype: list
73   @return: the list of names after the expansion
74   @raise errors.ProgrammerError: for unknown selection type
75   @raise errors.OpPrereqError: for invalid input parameters
76
77   """
78   if client is None:
79     client = GetClient()
80   if mode == _SHUTDOWN_CLUSTER:
81     if names:
82       raise errors.OpPrereqError("Cluster filter mode takes no arguments",
83                                  errors.ECODE_INVAL)
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", errors.ECODE_INVAL)
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                                  errors.ECODE_INVAL)
111     idata = client.QueryInstances(names, ["name"], False)
112     inames = [row[0] for row in idata]
113
114   else:
115     raise errors.OpPrereqError("Unknown mode '%s'" % mode, errors.ECODE_INVAL)
116
117   return inames
118
119
120 def _ConfirmOperation(inames, text, extra=""):
121   """Ask the user to confirm an operation on a list of instances.
122
123   This function is used to request confirmation for doing an operation
124   on a given list of instances.
125
126   @type inames: list
127   @param inames: the list of names that we display when
128       we ask for confirmation
129   @type text: str
130   @param text: the operation that the user should confirm
131       (e.g. I{shutdown} or I{startup})
132   @rtype: boolean
133   @return: True or False depending on user's confirmation.
134
135   """
136   count = len(inames)
137   msg = ("The %s will operate on %d instances.\n%s"
138          "Do you want to continue?" % (text, count, extra))
139   affected = ("\nAffected instances:\n" +
140               "\n".join(["  %s" % name for name in inames]))
141
142   choices = [('y', True, 'Yes, execute the %s' % text),
143              ('n', False, 'No, abort the %s' % text)]
144
145   if count > 20:
146     choices.insert(1, ('v', 'v', 'View the list of affected instances'))
147     ask = msg
148   else:
149     ask = msg + affected
150
151   choice = AskUser(ask, choices)
152   if choice == 'v':
153     choices.pop(1)
154     choice = AskUser(msg + affected, choices)
155   return choice
156
157
158 def _EnsureInstancesExist(client, names):
159   """Check for and ensure the given instance names exist.
160
161   This function will raise an OpPrereqError in case they don't
162   exist. Otherwise it will exit cleanly.
163
164   @type client: L{ganeti.luxi.Client}
165   @param client: the client to use for the query
166   @type names: list
167   @param names: the list of instance names to query
168   @raise errors.OpPrereqError: in case any instance is missing
169
170   """
171   # TODO: change LUQueryInstances to that it actually returns None
172   # instead of raising an exception, or devise a better mechanism
173   result = client.QueryInstances(names, ["name"], False)
174   for orig_name, row in zip(names, result):
175     if row[0] is None:
176       raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name,
177                                  errors.ECODE_NOENT)
178
179
180 def GenericManyOps(operation, fn):
181   """Generic multi-instance operations.
182
183   The will return a wrapper that processes the options and arguments
184   given, and uses the passed function to build the opcode needed for
185   the specific operation. Thus all the generic loop/confirmation code
186   is abstracted into this function.
187
188   """
189   def realfn(opts, args):
190     if opts.multi_mode is None:
191       opts.multi_mode = _SHUTDOWN_INSTANCES
192     cl = GetClient()
193     inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
194     if not inames:
195       raise errors.OpPrereqError("Selection filter does not match"
196                                  " any instances", errors.ECODE_INVAL)
197     multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
198     if not (opts.force_multi or not multi_on
199             or _ConfirmOperation(inames, operation)):
200       return 1
201     jex = JobExecutor(verbose=multi_on, cl=cl)
202     for name in inames:
203       op = fn(name, opts)
204       jex.QueueJob(name, op)
205     jex.WaitOrShow(not opts.submit_only)
206     return 0
207   return realfn
208
209
210 def ListInstances(opts, args):
211   """List instances and their properties.
212
213   @param opts: the command line options selected by the user
214   @type args: list
215   @param args: should be an empty list
216   @rtype: int
217   @return: the desired exit code
218
219   """
220   if opts.output is None:
221     selected_fields = _LIST_DEF_FIELDS
222   elif opts.output.startswith("+"):
223     selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
224   else:
225     selected_fields = opts.output.split(",")
226
227   output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
228
229   if not opts.no_headers:
230     headers = {
231       "name": "Instance", "os": "OS", "pnode": "Primary_node",
232       "snodes": "Secondary_Nodes", "admin_state": "Autostart",
233       "oper_state": "Running",
234       "oper_ram": "Memory", "disk_template": "Disk_template",
235       "ip": "IP_address", "mac": "MAC_address",
236       "nic_mode": "NIC_Mode", "nic_link": "NIC_Link",
237       "bridge": "Bridge",
238       "sda_size": "Disk/0", "sdb_size": "Disk/1",
239       "disk_usage": "DiskUsage",
240       "status": "Status", "tags": "Tags",
241       "network_port": "Network_port",
242       "hv/kernel_path": "Kernel_path",
243       "hv/initrd_path": "Initrd_path",
244       "hv/boot_order": "Boot_order",
245       "hv/acpi": "ACPI",
246       "hv/pae": "PAE",
247       "hv/cdrom_image_path": "CDROM_image_path",
248       "hv/nic_type": "NIC_type",
249       "hv/disk_type": "Disk_type",
250       "hv/vnc_bind_address": "VNC_bind_address",
251       "serial_no": "SerialNo", "hypervisor": "Hypervisor",
252       "hvparams": "Hypervisor_parameters",
253       "be/memory": "Configured_memory",
254       "be/vcpus": "VCPUs",
255       "vcpus": "VCPUs",
256       "be/auto_balance": "Auto_balance",
257       "disk.count": "Disks", "disk.sizes": "Disk_sizes",
258       "nic.count": "NICs", "nic.ips": "NIC_IPs",
259       "nic.modes": "NIC_modes", "nic.links": "NIC_links",
260       "nic.bridges": "NIC_bridges", "nic.macs": "NIC_MACs",
261       "ctime": "CTime", "mtime": "MTime", "uuid": "UUID",
262       }
263   else:
264     headers = None
265
266   unitfields = ["be/memory", "oper_ram", "sd(a|b)_size", "disk\.size/.*"]
267   numfields = ["be/memory", "oper_ram", "sd(a|b)_size", "be/vcpus",
268                "serial_no", "(disk|nic)\.count", "disk\.size/.*"]
269
270   list_type_fields = ("tags", "disk.sizes", "nic.macs", "nic.ips",
271                       "nic.modes", "nic.links", "nic.bridges")
272   # change raw values to nicer strings
273   for row in output:
274     for idx, field in enumerate(selected_fields):
275       val = row[idx]
276       if field == "snodes":
277         val = ",".join(val) or "-"
278       elif field == "admin_state":
279         if val:
280           val = "yes"
281         else:
282           val = "no"
283       elif field == "oper_state":
284         if val is None:
285           val = "(node down)"
286         elif val: # True
287           val = "running"
288         else:
289           val = "stopped"
290       elif field == "oper_ram":
291         if val is None:
292           val = "(node down)"
293       elif field == "sda_size" or field == "sdb_size":
294         if val is None:
295           val = "N/A"
296       elif field == "ctime" or field == "mtime":
297         val = utils.FormatTime(val)
298       elif field in list_type_fields:
299         val = ",".join(str(item) for item in val)
300       elif val is None:
301         val = "-"
302       row[idx] = str(val)
303
304   data = GenerateTable(separator=opts.separator, headers=headers,
305                        fields=selected_fields, unitfields=unitfields,
306                        numfields=numfields, data=output, units=opts.units)
307
308   for line in data:
309     ToStdout(line)
310
311   return 0
312
313
314 def AddInstance(opts, args):
315   """Add an instance to the cluster.
316
317   This is just a wrapper over GenericInstanceCreate.
318
319   """
320   return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
321   return 0
322
323
324 def BatchCreate(opts, args):
325   """Create instances using a definition file.
326
327   This function reads a json file with instances defined
328   in the form::
329
330     {"instance-name":{
331       "disk_size": [20480],
332       "template": "drbd",
333       "backend": {
334         "memory": 512,
335         "vcpus": 1 },
336       "os": "debootstrap",
337       "primary_node": "firstnode",
338       "secondary_node": "secondnode",
339       "iallocator": "dumb"}
340     }
341
342   Note that I{primary_node} and I{secondary_node} have precedence over
343   I{iallocator}.
344
345   @param opts: the command line options selected by the user
346   @type args: list
347   @param args: should contain one element, the json filename
348   @rtype: int
349   @return: the desired exit code
350
351   """
352   _DEFAULT_SPECS = {"disk_size": [20 * 1024],
353                     "backend": {},
354                     "iallocator": None,
355                     "primary_node": None,
356                     "secondary_node": None,
357                     "nics": None,
358                     "start": True,
359                     "ip_check": True,
360                     "hypervisor": None,
361                     "hvparams": {},
362                     "file_storage_dir": None,
363                     "file_driver": 'loop'}
364
365   def _PopulateWithDefaults(spec):
366     """Returns a new hash combined with default values."""
367     mydict = _DEFAULT_SPECS.copy()
368     mydict.update(spec)
369     return mydict
370
371   def _Validate(spec):
372     """Validate the instance specs."""
373     # Validate fields required under any circumstances
374     for required_field in ('os', 'template'):
375       if required_field not in spec:
376         raise errors.OpPrereqError('Required field "%s" is missing.' %
377                                    required_field, errors.ECODE_INVAL)
378     # Validate special fields
379     if spec['primary_node'] is not None:
380       if (spec['template'] in constants.DTS_NET_MIRROR and
381           spec['secondary_node'] is None):
382         raise errors.OpPrereqError('Template requires secondary node, but'
383                                    ' there was no secondary provided.',
384                                    errors.ECODE_INVAL)
385     elif spec['iallocator'] is None:
386       raise errors.OpPrereqError('You have to provide at least a primary_node'
387                                  ' or an iallocator.',
388                                  errors.ECODE_INVAL)
389
390     if (spec['hvparams'] and
391         not isinstance(spec['hvparams'], dict)):
392       raise errors.OpPrereqError('Hypervisor parameters must be a dict.',
393                                  errors.ECODE_INVAL)
394
395   json_filename = args[0]
396   try:
397     instance_data = simplejson.loads(utils.ReadFile(json_filename))
398   except Exception, err:
399     ToStderr("Can't parse the instance definition file: %s" % str(err))
400     return 1
401
402   jex = JobExecutor()
403
404   # Iterate over the instances and do:
405   #  * Populate the specs with default value
406   #  * Validate the instance specs
407   i_names = utils.NiceSort(instance_data.keys())
408   for name in i_names:
409     specs = instance_data[name]
410     specs = _PopulateWithDefaults(specs)
411     _Validate(specs)
412
413     hypervisor = specs['hypervisor']
414     hvparams = specs['hvparams']
415
416     disks = []
417     for elem in specs['disk_size']:
418       try:
419         size = utils.ParseUnit(elem)
420       except ValueError, err:
421         raise errors.OpPrereqError("Invalid disk size '%s' for"
422                                    " instance %s: %s" %
423                                    (elem, name, err), errors.ECODE_INVAL)
424       disks.append({"size": size})
425
426     utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
427     utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
428
429     tmp_nics = []
430     for field in ('ip', 'mac', 'mode', 'link', 'bridge'):
431       if field in specs:
432         if not tmp_nics:
433           tmp_nics.append({})
434         tmp_nics[0][field] = specs[field]
435
436     if specs['nics'] is not None and tmp_nics:
437       raise errors.OpPrereqError("'nics' list incompatible with using"
438                                  " individual nic fields as well",
439                                  errors.ECODE_INVAL)
440     elif specs['nics'] is not None:
441       tmp_nics = specs['nics']
442     elif not tmp_nics:
443       tmp_nics = [{}]
444
445     op = opcodes.OpCreateInstance(instance_name=name,
446                                   disks=disks,
447                                   disk_template=specs['template'],
448                                   mode=constants.INSTANCE_CREATE,
449                                   os_type=specs['os'],
450                                   force_variant=opts.force_variant,
451                                   pnode=specs['primary_node'],
452                                   snode=specs['secondary_node'],
453                                   nics=tmp_nics,
454                                   start=specs['start'],
455                                   ip_check=specs['ip_check'],
456                                   wait_for_sync=True,
457                                   iallocator=specs['iallocator'],
458                                   hypervisor=hypervisor,
459                                   hvparams=hvparams,
460                                   beparams=specs['backend'],
461                                   file_storage_dir=specs['file_storage_dir'],
462                                   file_driver=specs['file_driver'])
463
464     jex.QueueJob(name, op)
465   # we never want to wait, just show the submitted job IDs
466   jex.WaitOrShow(False)
467
468   return 0
469
470
471 def ReinstallInstance(opts, args):
472   """Reinstall an instance.
473
474   @param opts: the command line options selected by the user
475   @type args: list
476   @param args: should contain only one element, the name of the
477       instance to be reinstalled
478   @rtype: int
479   @return: the desired exit code
480
481   """
482   # first, compute the desired name list
483   if opts.multi_mode is None:
484     opts.multi_mode = _SHUTDOWN_INSTANCES
485
486   inames = _ExpandMultiNames(opts.multi_mode, args)
487   if not inames:
488     raise errors.OpPrereqError("Selection filter does not match any instances",
489                                errors.ECODE_INVAL)
490
491   # second, if requested, ask for an OS
492   if opts.select_os is True:
493     op = opcodes.OpDiagnoseOS(output_fields=["name", "valid", "variants"],
494                               names=[])
495     result = SubmitOpCode(op)
496
497     if not result:
498       ToStdout("Can't get the OS list")
499       return 1
500
501     ToStdout("Available OS templates:")
502     number = 0
503     choices = []
504     for (name, valid, variants) in result:
505       if valid:
506         for entry in CalculateOSNames(name, variants):
507           ToStdout("%3s: %s", number, entry)
508           choices.append(("%s" % number, entry, entry))
509           number += 1
510
511     choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
512     selected = AskUser("Enter OS template number (or x to abort):",
513                        choices)
514
515     if selected == 'exit':
516       ToStderr("User aborted reinstall, exiting")
517       return 1
518
519     os_name = selected
520   else:
521     os_name = opts.os
522
523   # third, get confirmation: multi-reinstall requires --force-multi
524   # *and* --force, single-reinstall just --force
525   multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
526   if multi_on:
527     warn_msg = "Note: this will remove *all* data for the below instances!\n"
528     if not ((opts.force_multi and opts.force) or
529             _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
530       return 1
531   else:
532     if not opts.force:
533       usertext = ("This will reinstall the instance %s and remove"
534                   " all data. Continue?") % inames[0]
535       if not AskUser(usertext):
536         return 1
537
538   jex = JobExecutor(verbose=multi_on)
539   for instance_name in inames:
540     op = opcodes.OpReinstallInstance(instance_name=instance_name,
541                                      os_type=os_name,
542                                      force_variant=opts.force_variant)
543     jex.QueueJob(instance_name, op)
544
545   jex.WaitOrShow(not opts.submit_only)
546   return 0
547
548
549 def RemoveInstance(opts, args):
550   """Remove an instance.
551
552   @param opts: the command line options selected by the user
553   @type args: list
554   @param args: should contain only one element, the name of
555       the instance to be removed
556   @rtype: int
557   @return: the desired exit code
558
559   """
560   instance_name = args[0]
561   force = opts.force
562   cl = GetClient()
563
564   if not force:
565     _EnsureInstancesExist(cl, [instance_name])
566
567     usertext = ("This will remove the volumes of the instance %s"
568                 " (including mirrors), thus removing all the data"
569                 " of the instance. Continue?") % instance_name
570     if not AskUser(usertext):
571       return 1
572
573   op = opcodes.OpRemoveInstance(instance_name=instance_name,
574                                 ignore_failures=opts.ignore_failures,
575                                 shutdown_timeout=opts.shutdown_timeout)
576   SubmitOrSend(op, opts, cl=cl)
577   return 0
578
579
580 def RenameInstance(opts, args):
581   """Rename an instance.
582
583   @param opts: the command line options selected by the user
584   @type args: list
585   @param args: should contain two elements, the old and the
586       new instance names
587   @rtype: int
588   @return: the desired exit code
589
590   """
591   op = opcodes.OpRenameInstance(instance_name=args[0],
592                                 new_name=args[1],
593                                 ignore_ip=opts.ignore_ip)
594   SubmitOrSend(op, opts)
595   return 0
596
597
598 def ActivateDisks(opts, args):
599   """Activate an instance's disks.
600
601   This serves two purposes:
602     - it allows (as long as the instance is not running)
603       mounting the disks and modifying them from the node
604     - it repairs inactive secondary drbds
605
606   @param opts: the command line options selected by the user
607   @type args: list
608   @param args: should contain only one element, the instance name
609   @rtype: int
610   @return: the desired exit code
611
612   """
613   instance_name = args[0]
614   op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
615                                        ignore_size=opts.ignore_size)
616   disks_info = SubmitOrSend(op, opts)
617   for host, iname, nname in disks_info:
618     ToStdout("%s:%s:%s", host, iname, nname)
619   return 0
620
621
622 def DeactivateDisks(opts, args):
623   """Deactivate an instance's disks.
624
625   This function takes the instance name, looks for its primary node
626   and the tries to shutdown its block devices on that node.
627
628   @param opts: the command line options selected by the user
629   @type args: list
630   @param args: should contain only one element, the instance name
631   @rtype: int
632   @return: the desired exit code
633
634   """
635   instance_name = args[0]
636   op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
637   SubmitOrSend(op, opts)
638   return 0
639
640
641 def RecreateDisks(opts, args):
642   """Recreate an instance's disks.
643
644   @param opts: the command line options selected by the user
645   @type args: list
646   @param args: should contain only one element, the instance name
647   @rtype: int
648   @return: the desired exit code
649
650   """
651   instance_name = args[0]
652   if opts.disks:
653     try:
654       opts.disks = [int(v) for v in opts.disks.split(",")]
655     except (ValueError, TypeError), err:
656       ToStderr("Invalid disks value: %s" % str(err))
657       return 1
658   else:
659     opts.disks = []
660
661   op = opcodes.OpRecreateInstanceDisks(instance_name=instance_name,
662                                        disks=opts.disks)
663   SubmitOrSend(op, opts)
664   return 0
665
666
667 def GrowDisk(opts, args):
668   """Grow an instance's disks.
669
670   @param opts: the command line options selected by the user
671   @type args: list
672   @param args: should contain two elements, the instance name
673       whose disks we grow and the disk name, e.g. I{sda}
674   @rtype: int
675   @return: the desired exit code
676
677   """
678   instance = args[0]
679   disk = args[1]
680   try:
681     disk = int(disk)
682   except ValueError, err:
683     raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
684                                errors.ECODE_INVAL)
685   amount = utils.ParseUnit(args[2])
686   op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
687                           wait_for_sync=opts.wait_for_sync)
688   SubmitOrSend(op, opts)
689   return 0
690
691
692 def _StartupInstance(name, opts):
693   """Startup instances.
694
695   This returns the opcode to start an instance, and its decorator will
696   wrap this into a loop starting all desired instances.
697
698   @param name: the name of the instance to act on
699   @param opts: the command line options selected by the user
700   @return: the opcode needed for the operation
701
702   """
703   op = opcodes.OpStartupInstance(instance_name=name,
704                                  force=opts.force)
705   # do not add these parameters to the opcode unless they're defined
706   if opts.hvparams:
707     op.hvparams = opts.hvparams
708   if opts.beparams:
709     op.beparams = opts.beparams
710   return op
711
712
713 def _RebootInstance(name, opts):
714   """Reboot instance(s).
715
716   This returns the opcode to reboot an instance, and its decorator
717   will wrap this into a loop rebooting all desired instances.
718
719   @param name: the name of the instance to act on
720   @param opts: the command line options selected by the user
721   @return: the opcode needed for the operation
722
723   """
724   return opcodes.OpRebootInstance(instance_name=name,
725                                   reboot_type=opts.reboot_type,
726                                   ignore_secondaries=opts.ignore_secondaries,
727                                   shutdown_timeout=opts.shutdown_timeout)
728
729
730 def _ShutdownInstance(name, opts):
731   """Shutdown an instance.
732
733   This returns the opcode to shutdown an instance, and its decorator
734   will wrap this into a loop shutting down all desired instances.
735
736   @param name: the name of the instance to act on
737   @param opts: the command line options selected by the user
738   @return: the opcode needed for the operation
739
740   """
741   return opcodes.OpShutdownInstance(instance_name=name,
742                                     timeout=opts.timeout)
743
744
745 def ReplaceDisks(opts, args):
746   """Replace the disks of an instance
747
748   @param opts: the command line options selected by the user
749   @type args: list
750   @param args: should contain only one element, the instance name
751   @rtype: int
752   @return: the desired exit code
753
754   """
755   instance_name = args[0]
756   new_2ndary = opts.dst_node
757   iallocator = opts.iallocator
758   if opts.disks is None:
759     disks = []
760   else:
761     try:
762       disks = [int(i) for i in opts.disks.split(",")]
763     except ValueError, err:
764       raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
765                                  errors.ECODE_INVAL)
766   cnt = [opts.on_primary, opts.on_secondary, opts.auto,
767          new_2ndary is not None, iallocator is not None].count(True)
768   if cnt != 1:
769     raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -i"
770                                " options must be passed", errors.ECODE_INVAL)
771   elif opts.on_primary:
772     mode = constants.REPLACE_DISK_PRI
773   elif opts.on_secondary:
774     mode = constants.REPLACE_DISK_SEC
775   elif opts.auto:
776     mode = constants.REPLACE_DISK_AUTO
777     if disks:
778       raise errors.OpPrereqError("Cannot specify disks when using automatic"
779                                  " mode", errors.ECODE_INVAL)
780   elif new_2ndary is not None or iallocator is not None:
781     # replace secondary
782     mode = constants.REPLACE_DISK_CHG
783
784   op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
785                               remote_node=new_2ndary, mode=mode,
786                               iallocator=iallocator)
787   SubmitOrSend(op, opts)
788   return 0
789
790
791 def FailoverInstance(opts, args):
792   """Failover an instance.
793
794   The failover is done by shutting it down on its present node and
795   starting it on the secondary.
796
797   @param opts: the command line options selected by the user
798   @type args: list
799   @param args: should contain only one element, the instance name
800   @rtype: int
801   @return: the desired exit code
802
803   """
804   cl = GetClient()
805   instance_name = args[0]
806   force = opts.force
807
808   if not force:
809     _EnsureInstancesExist(cl, [instance_name])
810
811     usertext = ("Failover will happen to image %s."
812                 " This requires a shutdown of the instance. Continue?" %
813                 (instance_name,))
814     if not AskUser(usertext):
815       return 1
816
817   op = opcodes.OpFailoverInstance(instance_name=instance_name,
818                                   ignore_consistency=opts.ignore_consistency,
819                                   shutdown_timeout=opts.shutdown_timeout)
820   SubmitOrSend(op, opts, cl=cl)
821   return 0
822
823
824 def MigrateInstance(opts, args):
825   """Migrate an instance.
826
827   The migrate is done without shutdown.
828
829   @param opts: the command line options selected by the user
830   @type args: list
831   @param args: should contain only one element, the instance name
832   @rtype: int
833   @return: the desired exit code
834
835   """
836   cl = GetClient()
837   instance_name = args[0]
838   force = opts.force
839
840   if not force:
841     _EnsureInstancesExist(cl, [instance_name])
842
843     if opts.cleanup:
844       usertext = ("Instance %s will be recovered from a failed migration."
845                   " Note that the migration procedure (including cleanup)" %
846                   (instance_name,))
847     else:
848       usertext = ("Instance %s will be migrated. Note that migration" %
849                   (instance_name,))
850     usertext += (" is **experimental** in this version."
851                 " This might impact the instance if anything goes wrong."
852                 " Continue?")
853     if not AskUser(usertext):
854       return 1
855
856   op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
857                                  cleanup=opts.cleanup)
858   SubmitOpCode(op, cl=cl)
859   return 0
860
861
862 def MoveInstance(opts, args):
863   """Move an instance.
864
865   @param opts: the command line options selected by the user
866   @type args: list
867   @param args: should contain only one element, the instance name
868   @rtype: int
869   @return: the desired exit code
870
871   """
872   cl = GetClient()
873   instance_name = args[0]
874   force = opts.force
875
876   if not force:
877     usertext = ("Instance %s will be moved."
878                 " This requires a shutdown of the instance. Continue?" %
879                 (instance_name,))
880     if not AskUser(usertext):
881       return 1
882
883   op = opcodes.OpMoveInstance(instance_name=instance_name,
884                               target_node=opts.node,
885                               shutdown_timeout=opts.shutdown_timeout)
886   SubmitOrSend(op, opts, cl=cl)
887   return 0
888
889
890 def ConnectToInstanceConsole(opts, args):
891   """Connect to the console of an instance.
892
893   @param opts: the command line options selected by the user
894   @type args: list
895   @param args: should contain only one element, the instance name
896   @rtype: int
897   @return: the desired exit code
898
899   """
900   instance_name = args[0]
901
902   op = opcodes.OpConnectConsole(instance_name=instance_name)
903   cmd = SubmitOpCode(op)
904
905   if opts.show_command:
906     ToStdout("%s", utils.ShellQuoteArgs(cmd))
907   else:
908     try:
909       os.execvp(cmd[0], cmd)
910     finally:
911       ToStderr("Can't run console command %s with arguments:\n'%s'",
912                cmd[0], " ".join(cmd))
913       os._exit(1)
914
915
916 def _FormatLogicalID(dev_type, logical_id):
917   """Formats the logical_id of a disk.
918
919   """
920   if dev_type == constants.LD_DRBD8:
921     node_a, node_b, port, minor_a, minor_b, key = logical_id
922     data = [
923       ("nodeA", "%s, minor=%s" % (node_a, minor_a)),
924       ("nodeB", "%s, minor=%s" % (node_b, minor_b)),
925       ("port", port),
926       ("auth key", key),
927       ]
928   elif dev_type == constants.LD_LV:
929     vg_name, lv_name = logical_id
930     data = ["%s/%s" % (vg_name, lv_name)]
931   else:
932     data = [str(logical_id)]
933
934   return data
935
936
937 def _FormatBlockDevInfo(idx, top_level, dev, static):
938   """Show block device information.
939
940   This is only used by L{ShowInstanceConfig}, but it's too big to be
941   left for an inline definition.
942
943   @type idx: int
944   @param idx: the index of the current disk
945   @type top_level: boolean
946   @param top_level: if this a top-level disk?
947   @type dev: dict
948   @param dev: dictionary with disk information
949   @type static: boolean
950   @param static: wheter the device information doesn't contain
951       runtime information but only static data
952   @return: a list of either strings, tuples or lists
953       (which should be formatted at a higher indent level)
954
955   """
956   def helper(dtype, status):
957     """Format one line for physical device status.
958
959     @type dtype: str
960     @param dtype: a constant from the L{constants.LDS_BLOCK} set
961     @type status: tuple
962     @param status: a tuple as returned from L{backend.FindBlockDevice}
963     @return: the string representing the status
964
965     """
966     if not status:
967       return "not active"
968     txt = ""
969     (path, major, minor, syncp, estt, degr, ldisk_status) = status
970     if major is None:
971       major_string = "N/A"
972     else:
973       major_string = str(major)
974
975     if minor is None:
976       minor_string = "N/A"
977     else:
978       minor_string = str(minor)
979
980     txt += ("%s (%s:%s)" % (path, major_string, minor_string))
981     if dtype in (constants.LD_DRBD8, ):
982       if syncp is not None:
983         sync_text = "*RECOVERING* %5.2f%%," % syncp
984         if estt:
985           sync_text += " ETA %ds" % estt
986         else:
987           sync_text += " ETA unknown"
988       else:
989         sync_text = "in sync"
990       if degr:
991         degr_text = "*DEGRADED*"
992       else:
993         degr_text = "ok"
994       if ldisk_status == constants.LDS_FAULTY:
995         ldisk_text = " *MISSING DISK*"
996       elif ldisk_status == constants.LDS_UNKNOWN:
997         ldisk_text = " *UNCERTAIN STATE*"
998       else:
999         ldisk_text = ""
1000       txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1001     elif dtype == constants.LD_LV:
1002       if ldisk_status == constants.LDS_FAULTY:
1003         ldisk_text = " *FAILED* (failed drive?)"
1004       else:
1005         ldisk_text = ""
1006       txt += ldisk_text
1007     return txt
1008
1009   # the header
1010   if top_level:
1011     if dev["iv_name"] is not None:
1012       txt = dev["iv_name"]
1013     else:
1014       txt = "disk %d" % idx
1015   else:
1016     txt = "child %d" % idx
1017   if isinstance(dev["size"], int):
1018     nice_size = utils.FormatUnit(dev["size"], "h")
1019   else:
1020     nice_size = dev["size"]
1021   d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1022   data = []
1023   if top_level:
1024     data.append(("access mode", dev["mode"]))
1025   if dev["logical_id"] is not None:
1026     try:
1027       l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"])
1028     except ValueError:
1029       l_id = [str(dev["logical_id"])]
1030     if len(l_id) == 1:
1031       data.append(("logical_id", l_id[0]))
1032     else:
1033       data.extend(l_id)
1034   elif dev["physical_id"] is not None:
1035     data.append("physical_id:")
1036     data.append([dev["physical_id"]])
1037   if not static:
1038     data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1039   if dev["sstatus"] and not static:
1040     data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1041
1042   if dev["children"]:
1043     data.append("child devices:")
1044     for c_idx, child in enumerate(dev["children"]):
1045       data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1046   d1.append(data)
1047   return d1
1048
1049
1050 def _FormatList(buf, data, indent_level):
1051   """Formats a list of data at a given indent level.
1052
1053   If the element of the list is:
1054     - a string, it is simply formatted as is
1055     - a tuple, it will be split into key, value and the all the
1056       values in a list will be aligned all at the same start column
1057     - a list, will be recursively formatted
1058
1059   @type buf: StringIO
1060   @param buf: the buffer into which we write the output
1061   @param data: the list to format
1062   @type indent_level: int
1063   @param indent_level: the indent level to format at
1064
1065   """
1066   max_tlen = max([len(elem[0]) for elem in data
1067                  if isinstance(elem, tuple)] or [0])
1068   for elem in data:
1069     if isinstance(elem, basestring):
1070       buf.write("%*s%s\n" % (2*indent_level, "", elem))
1071     elif isinstance(elem, tuple):
1072       key, value = elem
1073       spacer = "%*s" % (max_tlen - len(key), "")
1074       buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1075     elif isinstance(elem, list):
1076       _FormatList(buf, elem, indent_level+1)
1077
1078
1079 def ShowInstanceConfig(opts, args):
1080   """Compute instance run-time status.
1081
1082   @param opts: the command line options selected by the user
1083   @type args: list
1084   @param args: either an empty list, and then we query all
1085       instances, or should contain a list of instance names
1086   @rtype: int
1087   @return: the desired exit code
1088
1089   """
1090   if not args and not opts.show_all:
1091     ToStderr("No instance selected."
1092              " Please pass in --all if you want to query all instances.\n"
1093              "Note that this can take a long time on a big cluster.")
1094     return 1
1095   elif args and opts.show_all:
1096     ToStderr("Cannot use --all if you specify instance names.")
1097     return 1
1098
1099   retcode = 0
1100   op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1101   result = SubmitOpCode(op)
1102   if not result:
1103     ToStdout("No instances.")
1104     return 1
1105
1106   buf = StringIO()
1107   retcode = 0
1108   for instance_name in result:
1109     instance = result[instance_name]
1110     buf.write("Instance name: %s\n" % instance["name"])
1111     buf.write("UUID: %s\n" % instance["uuid"])
1112     buf.write("Serial number: %s\n" % instance["serial_no"])
1113     buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1114     buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1115     buf.write("State: configured to be %s" % instance["config_state"])
1116     if not opts.static:
1117       buf.write(", actual state is %s" % instance["run_state"])
1118     buf.write("\n")
1119     ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1120     ##          instance["auto_balance"])
1121     buf.write("  Nodes:\n")
1122     buf.write("    - primary: %s\n" % instance["pnode"])
1123     buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
1124     buf.write("  Operating system: %s\n" % instance["os"])
1125     if instance.has_key("network_port"):
1126       buf.write("  Allocated network port: %s\n" % instance["network_port"])
1127     buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1128
1129     # custom VNC console information
1130     vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1131                                                  None)
1132     if vnc_bind_address:
1133       port = instance["network_port"]
1134       display = int(port) - constants.VNC_BASE_PORT
1135       if display > 0 and vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
1136         vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1137                                                    port,
1138                                                    display)
1139       elif display > 0 and utils.IsValidIP(vnc_bind_address):
1140         vnc_console_port = ("%s:%s (node %s) (display %s)" %
1141                              (vnc_bind_address, port,
1142                               instance["pnode"], display))
1143       else:
1144         # vnc bind address is a file
1145         vnc_console_port = "%s:%s" % (instance["pnode"],
1146                                       vnc_bind_address)
1147       buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1148
1149     for key in instance["hv_actual"]:
1150       if key in instance["hv_instance"]:
1151         val = instance["hv_instance"][key]
1152       else:
1153         val = "default (%s)" % instance["hv_actual"][key]
1154       buf.write("    - %s: %s\n" % (key, val))
1155     buf.write("  Hardware:\n")
1156     buf.write("    - VCPUs: %d\n" %
1157               instance["be_actual"][constants.BE_VCPUS])
1158     buf.write("    - memory: %dMiB\n" %
1159               instance["be_actual"][constants.BE_MEMORY])
1160     buf.write("    - NICs:\n")
1161     for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1162       buf.write("      - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1163                 (idx, mac, ip, mode, link))
1164     buf.write("  Disks:\n")
1165
1166     for idx, device in enumerate(instance["disks"]):
1167       _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1168
1169   ToStdout(buf.getvalue().rstrip('\n'))
1170   return retcode
1171
1172
1173 def SetInstanceParams(opts, args):
1174   """Modifies an instance.
1175
1176   All parameters take effect only at the next restart of the instance.
1177
1178   @param opts: the command line options selected by the user
1179   @type args: list
1180   @param args: should contain only one element, the instance name
1181   @rtype: int
1182   @return: the desired exit code
1183
1184   """
1185   if not (opts.nics or opts.disks or
1186           opts.hvparams or opts.beparams):
1187     ToStderr("Please give at least one of the parameters.")
1188     return 1
1189
1190   for param in opts.beparams:
1191     if isinstance(opts.beparams[param], basestring):
1192       if opts.beparams[param].lower() == "default":
1193         opts.beparams[param] = constants.VALUE_DEFAULT
1194
1195   utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1196                       allowed_values=[constants.VALUE_DEFAULT])
1197
1198   for param in opts.hvparams:
1199     if isinstance(opts.hvparams[param], basestring):
1200       if opts.hvparams[param].lower() == "default":
1201         opts.hvparams[param] = constants.VALUE_DEFAULT
1202
1203   utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1204                       allowed_values=[constants.VALUE_DEFAULT])
1205
1206   for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1207     try:
1208       nic_op = int(nic_op)
1209       opts.nics[idx] = (nic_op, nic_dict)
1210     except ValueError:
1211       pass
1212
1213   for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1214     try:
1215       disk_op = int(disk_op)
1216       opts.disks[idx] = (disk_op, disk_dict)
1217     except ValueError:
1218       pass
1219     if disk_op == constants.DDM_ADD:
1220       if 'size' not in disk_dict:
1221         raise errors.OpPrereqError("Missing required parameter 'size'",
1222                                    errors.ECODE_INVAL)
1223       disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1224
1225   op = opcodes.OpSetInstanceParams(instance_name=args[0],
1226                                    nics=opts.nics,
1227                                    disks=opts.disks,
1228                                    hvparams=opts.hvparams,
1229                                    beparams=opts.beparams,
1230                                    force=opts.force)
1231
1232   # even if here we process the result, we allow submit only
1233   result = SubmitOrSend(op, opts)
1234
1235   if result:
1236     ToStdout("Modified instance %s", args[0])
1237     for param, data in result:
1238       ToStdout(" - %-5s -> %s", param, data)
1239     ToStdout("Please don't forget that these parameters take effect"
1240              " only at the next start of the instance.")
1241   return 0
1242
1243
1244 # multi-instance selection options
1245 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1246                            help="Do not ask for confirmation when more than"
1247                            " one instance is affected",
1248                            action="store_true", default=False)
1249
1250 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1251                             help="Filter by nodes (primary only)",
1252                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1253
1254 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1255                             help="Filter by nodes (secondary only)",
1256                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1257
1258 m_node_opt = cli_option("--node", dest="multi_mode",
1259                         help="Filter by nodes (primary and secondary)",
1260                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1261
1262 m_clust_opt = cli_option("--all", dest="multi_mode",
1263                          help="Select all instances in the cluster",
1264                          const=_SHUTDOWN_CLUSTER, action="store_const")
1265
1266 m_inst_opt = cli_option("--instance", dest="multi_mode",
1267                         help="Filter by instance name [default]",
1268                         const=_SHUTDOWN_INSTANCES, action="store_const")
1269
1270
1271 # this is defined separately due to readability only
1272 add_opts = [
1273   BACKEND_OPT,
1274   DISK_OPT,
1275   DISK_TEMPLATE_OPT,
1276   FILESTORE_DIR_OPT,
1277   FILESTORE_DRIVER_OPT,
1278   HYPERVISOR_OPT,
1279   IALLOCATOR_OPT,
1280   NET_OPT,
1281   NODE_PLACEMENT_OPT,
1282   NOIPCHECK_OPT,
1283   NONICS_OPT,
1284   NOSTART_OPT,
1285   NWSYNC_OPT,
1286   OS_OPT,
1287   FORCE_VARIANT_OPT,
1288   OS_SIZE_OPT,
1289   SUBMIT_OPT,
1290   ]
1291
1292 commands = {
1293   'add': (
1294     AddInstance, [ArgHost(min=1, max=1)], add_opts,
1295     "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1296     "Creates and adds a new instance to the cluster"),
1297   'batch-create': (
1298     BatchCreate, [ArgFile(min=1, max=1)], [],
1299     "<instances.json>",
1300     "Create a bunch of instances based on specs in the file."),
1301   'console': (
1302     ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1303     [SHOWCMD_OPT],
1304     "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1305   'failover': (
1306     FailoverInstance, ARGS_ONE_INSTANCE,
1307     [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT],
1308     "[-f] <instance>", "Stops the instance and starts it on the backup node,"
1309     " using the remote mirror (only for instances of type drbd)"),
1310   'migrate': (
1311     MigrateInstance, ARGS_ONE_INSTANCE,
1312     [FORCE_OPT, NONLIVE_OPT, CLEANUP_OPT],
1313     "[-f] <instance>", "Migrate instance to its secondary node"
1314     " (only for instances of type drbd)"),
1315   'move': (
1316     MoveInstance, ARGS_ONE_INSTANCE,
1317     [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT],
1318     "[-f] <instance>", "Move instance to an arbitrary node"
1319     " (only for instances of type file and lv)"),
1320   'info': (
1321     ShowInstanceConfig, ARGS_MANY_INSTANCES,
1322     [STATIC_OPT, ALL_OPT],
1323     "[-s] {--all | <instance>...}",
1324     "Show information on the specified instance(s)"),
1325   'list': (
1326     ListInstances, ARGS_MANY_INSTANCES,
1327     [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT],
1328     "[<instance>...]",
1329     "Lists the instances and their status. The available fields are"
1330     " (see the man page for details): status, oper_state, oper_ram,"
1331     " name, os, pnode, snodes, admin_state, admin_ram, disk_template,"
1332     " ip, mac, nic_mode, nic_link, sda_size, sdb_size, vcpus, serial_no,"
1333     " nic.count, nic.mac/N, nic.ip/N, nic.mode/N, nic.link/N,"
1334     " nic.macs, nic.ips, nic.modes, nic.links,"
1335     " disk.count, disk.size/N, disk.sizes,"
1336     " hv/NAME, be/memory, be/vcpus, be/auto_balance,"
1337     " hypervisor."
1338     " The default field"
1339     " list is (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
1340     ),
1341   'reinstall': (
1342     ReinstallInstance, [ArgInstance()],
1343     [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1344      m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SELECT_OS_OPT,
1345      SUBMIT_OPT],
1346     "[-f] <instance>", "Reinstall a stopped instance"),
1347   'remove': (
1348     RemoveInstance, ARGS_ONE_INSTANCE,
1349     [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT],
1350     "[-f] <instance>", "Shuts down the instance and removes it"),
1351   'rename': (
1352     RenameInstance,
1353     [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1354     [NOIPCHECK_OPT, SUBMIT_OPT],
1355     "<instance> <new_name>", "Rename the instance"),
1356   'replace-disks': (
1357     ReplaceDisks, ARGS_ONE_INSTANCE,
1358     [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT,
1359      NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT],
1360     "[-s|-p|-n NODE|-I NAME] <instance>",
1361     "Replaces all disks for the instance"),
1362   'modify': (
1363     SetInstanceParams, ARGS_ONE_INSTANCE,
1364     [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT],
1365     "<instance>", "Alters the parameters of an instance"),
1366   'shutdown': (
1367     GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1368     [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1369      m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT],
1370     "<instance>", "Stops an instance"),
1371   'startup': (
1372     GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1373     [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt,
1374      m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1375      BACKEND_OPT],
1376     "<instance>", "Starts an instance"),
1377   'reboot': (
1378     GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1379     [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1380      m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1381      SHUTDOWN_TIMEOUT_OPT],
1382     "<instance>", "Reboots an instance"),
1383   'activate-disks': (
1384     ActivateDisks, ARGS_ONE_INSTANCE, [SUBMIT_OPT, IGNORE_SIZE_OPT],
1385     "<instance>", "Activate an instance's disks"),
1386   'deactivate-disks': (
1387     DeactivateDisks, ARGS_ONE_INSTANCE, [SUBMIT_OPT],
1388     "<instance>", "Deactivate an instance's disks"),
1389   'recreate-disks': (
1390     RecreateDisks, ARGS_ONE_INSTANCE, [SUBMIT_OPT, DISKIDX_OPT],
1391     "<instance>", "Recreate an instance's disks"),
1392   'grow-disk': (
1393     GrowDisk,
1394     [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1395      ArgUnknown(min=1, max=1)],
1396     [SUBMIT_OPT, NWSYNC_OPT],
1397     "<instance> <disk> <size>", "Grow an instance's disk"),
1398   'list-tags': (
1399     ListTags, ARGS_ONE_INSTANCE, [],
1400     "<instance_name>", "List the tags of the given instance"),
1401   'add-tags': (
1402     AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1403     [TAG_SRC_OPT],
1404     "<instance_name> tag...", "Add tags to the given instance"),
1405   'remove-tags': (
1406     RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1407     [TAG_SRC_OPT],
1408     "<instance_name> tag...", "Remove tags from given instance"),
1409   }
1410
1411 #: dictionary with aliases for commands
1412 aliases = {
1413   'activate_block_devs': 'activate-disks',
1414   'replace_disks': 'replace-disks',
1415   'start': 'startup',
1416   'stop': 'shutdown',
1417   }
1418
1419
1420 if __name__ == '__main__':
1421   sys.exit(GenericMain(commands, aliases=aliases,
1422                        override={"tag_type": constants.TAG_INSTANCE}))