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