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