Add timeout options to other LUs
[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                                 timeout=opts.timeout)
568   SubmitOrSend(op, opts, cl=cl)
569   return 0
570
571
572 def RenameInstance(opts, args):
573   """Rename an instance.
574
575   @param opts: the command line options selected by the user
576   @type args: list
577   @param args: should contain two elements, the old and the
578       new instance names
579   @rtype: int
580   @return: the desired exit code
581
582   """
583   op = opcodes.OpRenameInstance(instance_name=args[0],
584                                 new_name=args[1],
585                                 ignore_ip=opts.ignore_ip)
586   SubmitOrSend(op, opts)
587   return 0
588
589
590 def ActivateDisks(opts, args):
591   """Activate an instance's disks.
592
593   This serves two purposes:
594     - it allows (as long as the instance is not running)
595       mounting the disks and modifying them from the node
596     - it repairs inactive secondary drbds
597
598   @param opts: the command line options selected by the user
599   @type args: list
600   @param args: should contain only one element, the instance name
601   @rtype: int
602   @return: the desired exit code
603
604   """
605   instance_name = args[0]
606   op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
607                                        ignore_size=opts.ignore_size)
608   disks_info = SubmitOrSend(op, opts)
609   for host, iname, nname in disks_info:
610     ToStdout("%s:%s:%s", host, iname, nname)
611   return 0
612
613
614 def DeactivateDisks(opts, args):
615   """Deactivate an instance's disks.
616
617   This function takes the instance name, looks for its primary node
618   and the tries to shutdown its block devices on that node.
619
620   @param opts: the command line options selected by the user
621   @type args: list
622   @param args: should contain only one element, the instance name
623   @rtype: int
624   @return: the desired exit code
625
626   """
627   instance_name = args[0]
628   op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
629   SubmitOrSend(op, opts)
630   return 0
631
632
633 def RecreateDisks(opts, args):
634   """Recreate an instance's disks.
635
636   @param opts: the command line options selected by the user
637   @type args: list
638   @param args: should contain only one element, the instance name
639   @rtype: int
640   @return: the desired exit code
641
642   """
643   instance_name = args[0]
644   if opts.disks:
645     try:
646       opts.disks = [int(v) for v in opts.disks.split(",")]
647     except (ValueError, TypeError), err:
648       ToStderr("Invalid disks value: %s" % str(err))
649       return 1
650   else:
651     opts.disks = []
652
653   op = opcodes.OpRecreateInstanceDisks(instance_name=instance_name,
654                                        disks=opts.disks)
655   SubmitOrSend(op, opts)
656   return 0
657
658
659 def GrowDisk(opts, args):
660   """Grow an instance's disks.
661
662   @param opts: the command line options selected by the user
663   @type args: list
664   @param args: should contain two elements, the instance name
665       whose disks we grow and the disk name, e.g. I{sda}
666   @rtype: int
667   @return: the desired exit code
668
669   """
670   instance = args[0]
671   disk = args[1]
672   try:
673     disk = int(disk)
674   except ValueError, err:
675     raise errors.OpPrereqError("Invalid disk index: %s" % str(err))
676   amount = utils.ParseUnit(args[2])
677   op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
678                           wait_for_sync=opts.wait_for_sync)
679   SubmitOrSend(op, opts)
680   return 0
681
682
683 def _StartupInstance(name, opts):
684   """Startup instances.
685
686   This returns the opcode to start an instance, and its decorator will
687   wrap this into a loop starting all desired instances.
688
689   @param name: the name of the instance to act on
690   @param opts: the command line options selected by the user
691   @return: the opcode needed for the operation
692
693   """
694   op = opcodes.OpStartupInstance(instance_name=name,
695                                  force=opts.force)
696   # do not add these parameters to the opcode unless they're defined
697   if opts.hvparams:
698     op.hvparams = opts.hvparams
699   if opts.beparams:
700     op.beparams = opts.beparams
701   return op
702
703
704 def _RebootInstance(name, opts):
705   """Reboot instance(s).
706
707   This returns the opcode to reboot an instance, and its decorator
708   will wrap this into a loop rebooting all desired instances.
709
710   @param name: the name of the instance to act on
711   @param opts: the command line options selected by the user
712   @return: the opcode needed for the operation
713
714   """
715   return opcodes.OpRebootInstance(instance_name=name,
716                                   reboot_type=opts.reboot_type,
717                                   ignore_secondaries=opts.ignore_secondaries,
718                                   timeout=opts.timeout)
719
720
721 def _ShutdownInstance(name, opts):
722   """Shutdown an instance.
723
724   This returns the opcode to shutdown an instance, and its decorator
725   will wrap this into a loop shutting down all desired instances.
726
727   @param name: the name of the instance to act on
728   @param opts: the command line options selected by the user
729   @return: the opcode needed for the operation
730
731   """
732   return opcodes.OpShutdownInstance(instance_name=name,
733                                     timeout=opts.timeout)
734
735
736 def ReplaceDisks(opts, args):
737   """Replace the disks of an instance
738
739   @param opts: the command line options selected by the user
740   @type args: list
741   @param args: should contain only one element, the instance name
742   @rtype: int
743   @return: the desired exit code
744
745   """
746   instance_name = args[0]
747   new_2ndary = opts.dst_node
748   iallocator = opts.iallocator
749   if opts.disks is None:
750     disks = []
751   else:
752     try:
753       disks = [int(i) for i in opts.disks.split(",")]
754     except ValueError, err:
755       raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
756   cnt = [opts.on_primary, opts.on_secondary, opts.auto,
757          new_2ndary is not None, iallocator is not None].count(True)
758   if cnt != 1:
759     raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -i"
760                                " options must be passed")
761   elif opts.on_primary:
762     mode = constants.REPLACE_DISK_PRI
763   elif opts.on_secondary:
764     mode = constants.REPLACE_DISK_SEC
765   elif opts.auto:
766     mode = constants.REPLACE_DISK_AUTO
767     if disks:
768       raise errors.OpPrereqError("Cannot specify disks when using automatic"
769                                  " mode")
770   elif new_2ndary is not None or iallocator is not None:
771     # replace secondary
772     mode = constants.REPLACE_DISK_CHG
773
774   op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
775                               remote_node=new_2ndary, mode=mode,
776                               iallocator=iallocator)
777   SubmitOrSend(op, opts)
778   return 0
779
780
781 def FailoverInstance(opts, args):
782   """Failover an instance.
783
784   The failover is done by shutting it down on its present node and
785   starting it on the secondary.
786
787   @param opts: the command line options selected by the user
788   @type args: list
789   @param args: should contain only one element, the instance name
790   @rtype: int
791   @return: the desired exit code
792
793   """
794   cl = GetClient()
795   instance_name = args[0]
796   force = opts.force
797
798   if not force:
799     _EnsureInstancesExist(cl, [instance_name])
800
801     usertext = ("Failover will happen to image %s."
802                 " This requires a shutdown of the instance. Continue?" %
803                 (instance_name,))
804     if not AskUser(usertext):
805       return 1
806
807   op = opcodes.OpFailoverInstance(instance_name=instance_name,
808                                   ignore_consistency=opts.ignore_consistency,
809                                   timeout=opts.timeout)
810   SubmitOrSend(op, opts, cl=cl)
811   return 0
812
813
814 def MigrateInstance(opts, args):
815   """Migrate an instance.
816
817   The migrate is done without shutdown.
818
819   @param opts: the command line options selected by the user
820   @type args: list
821   @param args: should contain only one element, the instance name
822   @rtype: int
823   @return: the desired exit code
824
825   """
826   cl = GetClient()
827   instance_name = args[0]
828   force = opts.force
829
830   if not force:
831     _EnsureInstancesExist(cl, [instance_name])
832
833     if opts.cleanup:
834       usertext = ("Instance %s will be recovered from a failed migration."
835                   " Note that the migration procedure (including cleanup)" %
836                   (instance_name,))
837     else:
838       usertext = ("Instance %s will be migrated. Note that migration" %
839                   (instance_name,))
840     usertext += (" is **experimental** in this version."
841                 " This might impact the instance if anything goes wrong."
842                 " Continue?")
843     if not AskUser(usertext):
844       return 1
845
846   op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
847                                  cleanup=opts.cleanup)
848   SubmitOpCode(op, cl=cl)
849   return 0
850
851
852 def MoveInstance(opts, args):
853   """Move an instance.
854
855   @param opts: the command line options selected by the user
856   @type args: list
857   @param args: should contain only one element, the instance name
858   @rtype: int
859   @return: the desired exit code
860
861   """
862   cl = GetClient()
863   instance_name = args[0]
864   force = opts.force
865
866   if not force:
867     usertext = ("Instance %s will be moved."
868                 " This requires a shutdown of the instance. Continue?" %
869                 (instance_name,))
870     if not AskUser(usertext):
871       return 1
872
873   op = opcodes.OpMoveInstance(instance_name=instance_name,
874                               target_node=opts.node,
875                               timeout=opts.timeout)
876   SubmitOrSend(op, opts, cl=cl)
877   return 0
878
879
880 def ConnectToInstanceConsole(opts, args):
881   """Connect to the console of an instance.
882
883   @param opts: the command line options selected by the user
884   @type args: list
885   @param args: should contain only one element, the instance name
886   @rtype: int
887   @return: the desired exit code
888
889   """
890   instance_name = args[0]
891
892   op = opcodes.OpConnectConsole(instance_name=instance_name)
893   cmd = SubmitOpCode(op)
894
895   if opts.show_command:
896     ToStdout("%s", utils.ShellQuoteArgs(cmd))
897   else:
898     try:
899       os.execvp(cmd[0], cmd)
900     finally:
901       ToStderr("Can't run console command %s with arguments:\n'%s'",
902                cmd[0], " ".join(cmd))
903       os._exit(1)
904
905
906 def _FormatLogicalID(dev_type, logical_id):
907   """Formats the logical_id of a disk.
908
909   """
910   if dev_type == constants.LD_DRBD8:
911     node_a, node_b, port, minor_a, minor_b, key = logical_id
912     data = [
913       ("nodeA", "%s, minor=%s" % (node_a, minor_a)),
914       ("nodeB", "%s, minor=%s" % (node_b, minor_b)),
915       ("port", port),
916       ("auth key", key),
917       ]
918   elif dev_type == constants.LD_LV:
919     vg_name, lv_name = logical_id
920     data = ["%s/%s" % (vg_name, lv_name)]
921   else:
922     data = [str(logical_id)]
923
924   return data
925
926
927 def _FormatBlockDevInfo(idx, top_level, dev, static):
928   """Show block device information.
929
930   This is only used by L{ShowInstanceConfig}, but it's too big to be
931   left for an inline definition.
932
933   @type idx: int
934   @param idx: the index of the current disk
935   @type top_level: boolean
936   @param top_level: if this a top-level disk?
937   @type dev: dict
938   @param dev: dictionary with disk information
939   @type static: boolean
940   @param static: wheter the device information doesn't contain
941       runtime information but only static data
942   @return: a list of either strings, tuples or lists
943       (which should be formatted at a higher indent level)
944
945   """
946   def helper(dtype, status):
947     """Format one line for physical device status.
948
949     @type dtype: str
950     @param dtype: a constant from the L{constants.LDS_BLOCK} set
951     @type status: tuple
952     @param status: a tuple as returned from L{backend.FindBlockDevice}
953     @return: the string representing the status
954
955     """
956     if not status:
957       return "not active"
958     txt = ""
959     (path, major, minor, syncp, estt, degr, ldisk_status) = status
960     if major is None:
961       major_string = "N/A"
962     else:
963       major_string = str(major)
964
965     if minor is None:
966       minor_string = "N/A"
967     else:
968       minor_string = str(minor)
969
970     txt += ("%s (%s:%s)" % (path, major_string, minor_string))
971     if dtype in (constants.LD_DRBD8, ):
972       if syncp is not None:
973         sync_text = "*RECOVERING* %5.2f%%," % syncp
974         if estt:
975           sync_text += " ETA %ds" % estt
976         else:
977           sync_text += " ETA unknown"
978       else:
979         sync_text = "in sync"
980       if degr:
981         degr_text = "*DEGRADED*"
982       else:
983         degr_text = "ok"
984       if ldisk_status == constants.LDS_FAULTY:
985         ldisk_text = " *MISSING DISK*"
986       elif ldisk_status == constants.LDS_UNKNOWN:
987         ldisk_text = " *UNCERTAIN STATE*"
988       else:
989         ldisk_text = ""
990       txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
991     elif dtype == constants.LD_LV:
992       if ldisk_status == constants.LDS_FAULTY:
993         ldisk_text = " *FAILED* (failed drive?)"
994       else:
995         ldisk_text = ""
996       txt += ldisk_text
997     return txt
998
999   # the header
1000   if top_level:
1001     if dev["iv_name"] is not None:
1002       txt = dev["iv_name"]
1003     else:
1004       txt = "disk %d" % idx
1005   else:
1006     txt = "child %d" % idx
1007   if isinstance(dev["size"], int):
1008     nice_size = utils.FormatUnit(dev["size"], "h")
1009   else:
1010     nice_size = dev["size"]
1011   d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1012   data = []
1013   if top_level:
1014     data.append(("access mode", dev["mode"]))
1015   if dev["logical_id"] is not None:
1016     try:
1017       l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"])
1018     except ValueError:
1019       l_id = [str(dev["logical_id"])]
1020     if len(l_id) == 1:
1021       data.append(("logical_id", l_id[0]))
1022     else:
1023       data.extend(l_id)
1024   elif dev["physical_id"] is not None:
1025     data.append("physical_id:")
1026     data.append([dev["physical_id"]])
1027   if not static:
1028     data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1029   if dev["sstatus"] and not static:
1030     data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1031
1032   if dev["children"]:
1033     data.append("child devices:")
1034     for c_idx, child in enumerate(dev["children"]):
1035       data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1036   d1.append(data)
1037   return d1
1038
1039
1040 def _FormatList(buf, data, indent_level):
1041   """Formats a list of data at a given indent level.
1042
1043   If the element of the list is:
1044     - a string, it is simply formatted as is
1045     - a tuple, it will be split into key, value and the all the
1046       values in a list will be aligned all at the same start column
1047     - a list, will be recursively formatted
1048
1049   @type buf: StringIO
1050   @param buf: the buffer into which we write the output
1051   @param data: the list to format
1052   @type indent_level: int
1053   @param indent_level: the indent level to format at
1054
1055   """
1056   max_tlen = max([len(elem[0]) for elem in data
1057                  if isinstance(elem, tuple)] or [0])
1058   for elem in data:
1059     if isinstance(elem, basestring):
1060       buf.write("%*s%s\n" % (2*indent_level, "", elem))
1061     elif isinstance(elem, tuple):
1062       key, value = elem
1063       spacer = "%*s" % (max_tlen - len(key), "")
1064       buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1065     elif isinstance(elem, list):
1066       _FormatList(buf, elem, indent_level+1)
1067
1068
1069 def ShowInstanceConfig(opts, args):
1070   """Compute instance run-time status.
1071
1072   @param opts: the command line options selected by the user
1073   @type args: list
1074   @param args: either an empty list, and then we query all
1075       instances, or should contain a list of instance names
1076   @rtype: int
1077   @return: the desired exit code
1078
1079   """
1080   if not args and not opts.show_all:
1081     ToStderr("No instance selected."
1082              " Please pass in --all if you want to query all instances.\n"
1083              "Note that this can take a long time on a big cluster.")
1084     return 1
1085   elif args and opts.show_all:
1086     ToStderr("Cannot use --all if you specify instance names.")
1087     return 1
1088
1089   retcode = 0
1090   op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1091   result = SubmitOpCode(op)
1092   if not result:
1093     ToStdout("No instances.")
1094     return 1
1095
1096   buf = StringIO()
1097   retcode = 0
1098   for instance_name in result:
1099     instance = result[instance_name]
1100     buf.write("Instance name: %s\n" % instance["name"])
1101     buf.write("UUID: %s\n" % instance["uuid"])
1102     buf.write("Serial number: %s\n" % instance["serial_no"])
1103     buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1104     buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1105     buf.write("State: configured to be %s" % instance["config_state"])
1106     if not opts.static:
1107       buf.write(", actual state is %s" % instance["run_state"])
1108     buf.write("\n")
1109     ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1110     ##          instance["auto_balance"])
1111     buf.write("  Nodes:\n")
1112     buf.write("    - primary: %s\n" % instance["pnode"])
1113     buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
1114     buf.write("  Operating system: %s\n" % instance["os"])
1115     if instance.has_key("network_port"):
1116       buf.write("  Allocated network port: %s\n" % instance["network_port"])
1117     buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1118
1119     # custom VNC console information
1120     vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1121                                                  None)
1122     if vnc_bind_address:
1123       port = instance["network_port"]
1124       display = int(port) - constants.VNC_BASE_PORT
1125       if display > 0 and vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
1126         vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1127                                                    port,
1128                                                    display)
1129       elif display > 0 and utils.IsValidIP(vnc_bind_address):
1130         vnc_console_port = ("%s:%s (node %s) (display %s)" %
1131                              (vnc_bind_address, port,
1132                               instance["pnode"], display))
1133       else:
1134         # vnc bind address is a file
1135         vnc_console_port = "%s:%s" % (instance["pnode"],
1136                                       vnc_bind_address)
1137       buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1138
1139     for key in instance["hv_actual"]:
1140       if key in instance["hv_instance"]:
1141         val = instance["hv_instance"][key]
1142       else:
1143         val = "default (%s)" % instance["hv_actual"][key]
1144       buf.write("    - %s: %s\n" % (key, val))
1145     buf.write("  Hardware:\n")
1146     buf.write("    - VCPUs: %d\n" %
1147               instance["be_actual"][constants.BE_VCPUS])
1148     buf.write("    - memory: %dMiB\n" %
1149               instance["be_actual"][constants.BE_MEMORY])
1150     buf.write("    - NICs:\n")
1151     for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1152       buf.write("      - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1153                 (idx, mac, ip, mode, link))
1154     buf.write("  Disks:\n")
1155
1156     for idx, device in enumerate(instance["disks"]):
1157       _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1158
1159   ToStdout(buf.getvalue().rstrip('\n'))
1160   return retcode
1161
1162
1163 def SetInstanceParams(opts, args):
1164   """Modifies an instance.
1165
1166   All parameters take effect only at the next restart of the instance.
1167
1168   @param opts: the command line options selected by the user
1169   @type args: list
1170   @param args: should contain only one element, the instance name
1171   @rtype: int
1172   @return: the desired exit code
1173
1174   """
1175   if not (opts.nics or opts.disks or
1176           opts.hvparams or opts.beparams):
1177     ToStderr("Please give at least one of the parameters.")
1178     return 1
1179
1180   for param in opts.beparams:
1181     if isinstance(opts.beparams[param], basestring):
1182       if opts.beparams[param].lower() == "default":
1183         opts.beparams[param] = constants.VALUE_DEFAULT
1184
1185   utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1186                       allowed_values=[constants.VALUE_DEFAULT])
1187
1188   for param in opts.hvparams:
1189     if isinstance(opts.hvparams[param], basestring):
1190       if opts.hvparams[param].lower() == "default":
1191         opts.hvparams[param] = constants.VALUE_DEFAULT
1192
1193   utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1194                       allowed_values=[constants.VALUE_DEFAULT])
1195
1196   for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1197     try:
1198       nic_op = int(nic_op)
1199       opts.nics[idx] = (nic_op, nic_dict)
1200     except ValueError:
1201       pass
1202
1203   for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1204     try:
1205       disk_op = int(disk_op)
1206       opts.disks[idx] = (disk_op, disk_dict)
1207     except ValueError:
1208       pass
1209     if disk_op == constants.DDM_ADD:
1210       if 'size' not in disk_dict:
1211         raise errors.OpPrereqError("Missing required parameter 'size'")
1212       disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1213
1214   op = opcodes.OpSetInstanceParams(instance_name=args[0],
1215                                    nics=opts.nics,
1216                                    disks=opts.disks,
1217                                    hvparams=opts.hvparams,
1218                                    beparams=opts.beparams,
1219                                    force=opts.force)
1220
1221   # even if here we process the result, we allow submit only
1222   result = SubmitOrSend(op, opts)
1223
1224   if result:
1225     ToStdout("Modified instance %s", args[0])
1226     for param, data in result:
1227       ToStdout(" - %-5s -> %s", param, data)
1228     ToStdout("Please don't forget that these parameters take effect"
1229              " only at the next start of the instance.")
1230   return 0
1231
1232
1233 # multi-instance selection options
1234 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1235                            help="Do not ask for confirmation when more than"
1236                            " one instance is affected",
1237                            action="store_true", default=False)
1238
1239 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1240                             help="Filter by nodes (primary only)",
1241                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1242
1243 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1244                             help="Filter by nodes (secondary only)",
1245                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1246
1247 m_node_opt = cli_option("--node", dest="multi_mode",
1248                         help="Filter by nodes (primary and secondary)",
1249                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1250
1251 m_clust_opt = cli_option("--all", dest="multi_mode",
1252                          help="Select all instances in the cluster",
1253                          const=_SHUTDOWN_CLUSTER, action="store_const")
1254
1255 m_inst_opt = cli_option("--instance", dest="multi_mode",
1256                         help="Filter by instance name [default]",
1257                         const=_SHUTDOWN_INSTANCES, action="store_const")
1258
1259
1260 # this is defined separately due to readability only
1261 add_opts = [
1262   BACKEND_OPT,
1263   DISK_OPT,
1264   DISK_TEMPLATE_OPT,
1265   FILESTORE_DIR_OPT,
1266   FILESTORE_DRIVER_OPT,
1267   HYPERVISOR_OPT,
1268   IALLOCATOR_OPT,
1269   NET_OPT,
1270   NODE_PLACEMENT_OPT,
1271   NOIPCHECK_OPT,
1272   NONICS_OPT,
1273   NOSTART_OPT,
1274   NWSYNC_OPT,
1275   OS_OPT,
1276   FORCE_VARIANT_OPT,
1277   OS_SIZE_OPT,
1278   SUBMIT_OPT,
1279   ]
1280
1281 commands = {
1282   'add': (
1283     AddInstance, [ArgHost(min=1, max=1)], add_opts,
1284     "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1285     "Creates and adds a new instance to the cluster"),
1286   'batch-create': (
1287     BatchCreate, [ArgFile(min=1, max=1)], [],
1288     "<instances.json>",
1289     "Create a bunch of instances based on specs in the file."),
1290   'console': (
1291     ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1292     [SHOWCMD_OPT],
1293     "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1294   'failover': (
1295     FailoverInstance, ARGS_ONE_INSTANCE,
1296     [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT],
1297     "[-f] <instance>", "Stops the instance and starts it on the backup node,"
1298     " using the remote mirror (only for instances of type drbd)"),
1299   'migrate': (
1300     MigrateInstance, ARGS_ONE_INSTANCE,
1301     [FORCE_OPT, NONLIVE_OPT, CLEANUP_OPT],
1302     "[-f] <instance>", "Migrate instance to its secondary node"
1303     " (only for instances of type drbd)"),
1304   'move': (
1305     MoveInstance, ARGS_ONE_INSTANCE,
1306     [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT],
1307     "[-f] <instance>", "Move instance to an arbitrary node"
1308     " (only for instances of type file and lv)"),
1309   'info': (
1310     ShowInstanceConfig, ARGS_MANY_INSTANCES,
1311     [STATIC_OPT, ALL_OPT],
1312     "[-s] {--all | <instance>...}",
1313     "Show information on the specified instance(s)"),
1314   'list': (
1315     ListInstances, ARGS_MANY_INSTANCES,
1316     [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT],
1317     "[<instance>...]",
1318     "Lists the instances and their status. The available fields are"
1319     " (see the man page for details): status, oper_state, oper_ram,"
1320     " name, os, pnode, snodes, admin_state, admin_ram, disk_template,"
1321     " ip, mac, mode, link, sda_size, sdb_size, vcpus, serial_no,"
1322     " hypervisor."
1323     " The default field"
1324     " list is (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
1325     ),
1326   'reinstall': (
1327     ReinstallInstance, [ArgInstance()],
1328     [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1329      m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SELECT_OS_OPT,
1330      SUBMIT_OPT],
1331     "[-f] <instance>", "Reinstall a stopped instance"),
1332   'remove': (
1333     RemoveInstance, ARGS_ONE_INSTANCE,
1334     [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT],
1335     "[-f] <instance>", "Shuts down the instance and removes it"),
1336   'rename': (
1337     RenameInstance,
1338     [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1339     [NOIPCHECK_OPT, SUBMIT_OPT],
1340     "<instance> <new_name>", "Rename the instance"),
1341   'replace-disks': (
1342     ReplaceDisks, ARGS_ONE_INSTANCE,
1343     [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT,
1344      NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT],
1345     "[-s|-p|-n NODE|-I NAME] <instance>",
1346     "Replaces all disks for the instance"),
1347   'modify': (
1348     SetInstanceParams, ARGS_ONE_INSTANCE,
1349     [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT],
1350     "<instance>", "Alters the parameters of an instance"),
1351   'shutdown': (
1352     GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1353     [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1354      m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT],
1355     "<instance>", "Stops an instance"),
1356   'startup': (
1357     GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1358     [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt,
1359      m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1360      BACKEND_OPT],
1361     "<instance>", "Starts an instance"),
1362   'reboot': (
1363     GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1364     [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1365      m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1366      SHUTDOWN_TIMEOUT_OPT],
1367     "<instance>", "Reboots an instance"),
1368   'activate-disks': (
1369     ActivateDisks, ARGS_ONE_INSTANCE, [SUBMIT_OPT, IGNORE_SIZE_OPT],
1370     "<instance>", "Activate an instance's disks"),
1371   'deactivate-disks': (
1372     DeactivateDisks, ARGS_ONE_INSTANCE, [SUBMIT_OPT],
1373     "<instance>", "Deactivate an instance's disks"),
1374   'recreate-disks': (
1375     RecreateDisks, ARGS_ONE_INSTANCE, [SUBMIT_OPT, DISKIDX_OPT],
1376     "<instance>", "Recreate an instance's disks"),
1377   'grow-disk': (
1378     GrowDisk,
1379     [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1380      ArgUnknown(min=1, max=1)],
1381     [SUBMIT_OPT, NWSYNC_OPT],
1382     "<instance> <disk> <size>", "Grow an instance's disk"),
1383   'list-tags': (
1384     ListTags, ARGS_ONE_INSTANCE, [],
1385     "<instance_name>", "List the tags of the given instance"),
1386   'add-tags': (
1387     AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1388     [TAG_SRC_OPT],
1389     "<instance_name> tag...", "Add tags to the given instance"),
1390   'remove-tags': (
1391     RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1392     [TAG_SRC_OPT],
1393     "<instance_name> tag...", "Remove tags from given instance"),
1394   }
1395
1396 #: dictionary with aliases for commands
1397 aliases = {
1398   'activate_block_devs': 'activate-disks',
1399   'replace_disks': 'replace-disks',
1400   'start': 'startup',
1401   'stop': 'shutdown',
1402   }
1403
1404
1405 if __name__ == '__main__':
1406   sys.exit(GenericMain(commands, aliases=aliases,
1407                        override={"tag_type": constants.TAG_INSTANCE}))