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