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