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