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