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