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