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