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