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