Modify gnt-instance rename to support --no-check-name
[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   if not opts.check_name:
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                                 ignore_ip=not opts.ip_check,
631                                 check_name=opts.check_name)
632   SubmitOrSend(op, opts)
633   return 0
634
635
636 def ActivateDisks(opts, args):
637   """Activate an instance's disks.
638
639   This serves two purposes:
640     - it allows (as long as the instance is not running)
641       mounting the disks and modifying them from the node
642     - it repairs inactive secondary drbds
643
644   @param opts: the command line options selected by the user
645   @type args: list
646   @param args: should contain only one element, the instance name
647   @rtype: int
648   @return: the desired exit code
649
650   """
651   instance_name = args[0]
652   op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
653                                        ignore_size=opts.ignore_size)
654   disks_info = SubmitOrSend(op, opts)
655   for host, iname, nname in disks_info:
656     ToStdout("%s:%s:%s", host, iname, nname)
657   return 0
658
659
660 def DeactivateDisks(opts, args):
661   """Deactivate an instance's disks.
662
663   This function takes the instance name, looks for its primary node
664   and the tries to shutdown its block devices on that node.
665
666   @param opts: the command line options selected by the user
667   @type args: list
668   @param args: should contain only one element, the instance name
669   @rtype: int
670   @return: the desired exit code
671
672   """
673   instance_name = args[0]
674   op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
675   SubmitOrSend(op, opts)
676   return 0
677
678
679 def RecreateDisks(opts, args):
680   """Recreate an instance's disks.
681
682   @param opts: the command line options selected by the user
683   @type args: list
684   @param args: should contain only one element, the instance name
685   @rtype: int
686   @return: the desired exit code
687
688   """
689   instance_name = args[0]
690   if opts.disks:
691     try:
692       opts.disks = [int(v) for v in opts.disks.split(",")]
693     except (ValueError, TypeError), err:
694       ToStderr("Invalid disks value: %s" % str(err))
695       return 1
696   else:
697     opts.disks = []
698
699   op = opcodes.OpRecreateInstanceDisks(instance_name=instance_name,
700                                        disks=opts.disks)
701   SubmitOrSend(op, opts)
702   return 0
703
704
705 def GrowDisk(opts, args):
706   """Grow an instance's disks.
707
708   @param opts: the command line options selected by the user
709   @type args: list
710   @param args: should contain two elements, the instance name
711       whose disks we grow and the disk name, e.g. I{sda}
712   @rtype: int
713   @return: the desired exit code
714
715   """
716   instance = args[0]
717   disk = args[1]
718   try:
719     disk = int(disk)
720   except (TypeError, ValueError), err:
721     raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
722                                errors.ECODE_INVAL)
723   amount = utils.ParseUnit(args[2])
724   op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
725                           wait_for_sync=opts.wait_for_sync)
726   SubmitOrSend(op, opts)
727   return 0
728
729
730 def _StartupInstance(name, opts):
731   """Startup instances.
732
733   This returns the opcode to start an instance, and its decorator will
734   wrap this into a loop starting all desired instances.
735
736   @param name: the name of the instance to act on
737   @param opts: the command line options selected by the user
738   @return: the opcode needed for the operation
739
740   """
741   op = opcodes.OpStartupInstance(instance_name=name,
742                                  force=opts.force)
743   # do not add these parameters to the opcode unless they're defined
744   if opts.hvparams:
745     op.hvparams = opts.hvparams
746   if opts.beparams:
747     op.beparams = opts.beparams
748   return op
749
750
751 def _RebootInstance(name, opts):
752   """Reboot instance(s).
753
754   This returns the opcode to reboot an instance, and its decorator
755   will wrap this into a loop rebooting all desired instances.
756
757   @param name: the name of the instance to act on
758   @param opts: the command line options selected by the user
759   @return: the opcode needed for the operation
760
761   """
762   return opcodes.OpRebootInstance(instance_name=name,
763                                   reboot_type=opts.reboot_type,
764                                   ignore_secondaries=opts.ignore_secondaries,
765                                   shutdown_timeout=opts.shutdown_timeout)
766
767
768 def _ShutdownInstance(name, opts):
769   """Shutdown an instance.
770
771   This returns the opcode to shutdown an instance, and its decorator
772   will wrap this into a loop shutting down all desired instances.
773
774   @param name: the name of the instance to act on
775   @param opts: the command line options selected by the user
776   @return: the opcode needed for the operation
777
778   """
779   return opcodes.OpShutdownInstance(instance_name=name,
780                                     timeout=opts.timeout)
781
782
783 def ReplaceDisks(opts, args):
784   """Replace the disks of an instance
785
786   @param opts: the command line options selected by the user
787   @type args: list
788   @param args: should contain only one element, the instance name
789   @rtype: int
790   @return: the desired exit code
791
792   """
793   new_2ndary = opts.dst_node
794   iallocator = opts.iallocator
795   if opts.disks is None:
796     disks = []
797   else:
798     try:
799       disks = [int(i) for i in opts.disks.split(",")]
800     except (TypeError, ValueError), err:
801       raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
802                                  errors.ECODE_INVAL)
803   cnt = [opts.on_primary, opts.on_secondary, opts.auto,
804          new_2ndary is not None, iallocator is not None].count(True)
805   if cnt != 1:
806     raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -i"
807                                " options must be passed", errors.ECODE_INVAL)
808   elif opts.on_primary:
809     mode = constants.REPLACE_DISK_PRI
810   elif opts.on_secondary:
811     mode = constants.REPLACE_DISK_SEC
812   elif opts.auto:
813     mode = constants.REPLACE_DISK_AUTO
814     if disks:
815       raise errors.OpPrereqError("Cannot specify disks when using automatic"
816                                  " mode", errors.ECODE_INVAL)
817   elif new_2ndary is not None or iallocator is not None:
818     # replace secondary
819     mode = constants.REPLACE_DISK_CHG
820
821   op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
822                               remote_node=new_2ndary, mode=mode,
823                               iallocator=iallocator,
824                               early_release=opts.early_release)
825   SubmitOrSend(op, opts)
826   return 0
827
828
829 def FailoverInstance(opts, args):
830   """Failover an instance.
831
832   The failover is done by shutting it down on its present node and
833   starting it on the secondary.
834
835   @param opts: the command line options selected by the user
836   @type args: list
837   @param args: should contain only one element, the instance name
838   @rtype: int
839   @return: the desired exit code
840
841   """
842   cl = GetClient()
843   instance_name = args[0]
844   force = opts.force
845
846   if not force:
847     _EnsureInstancesExist(cl, [instance_name])
848
849     usertext = ("Failover will happen to image %s."
850                 " This requires a shutdown of the instance. Continue?" %
851                 (instance_name,))
852     if not AskUser(usertext):
853       return 1
854
855   op = opcodes.OpFailoverInstance(instance_name=instance_name,
856                                   ignore_consistency=opts.ignore_consistency,
857                                   shutdown_timeout=opts.shutdown_timeout)
858   SubmitOrSend(op, opts, cl=cl)
859   return 0
860
861
862 def MigrateInstance(opts, args):
863   """Migrate an instance.
864
865   The migrate is done without shutdown.
866
867   @param opts: the command line options selected by the user
868   @type args: list
869   @param args: should contain only one element, the instance name
870   @rtype: int
871   @return: the desired exit code
872
873   """
874   cl = GetClient()
875   instance_name = args[0]
876   force = opts.force
877
878   if not force:
879     _EnsureInstancesExist(cl, [instance_name])
880
881     if opts.cleanup:
882       usertext = ("Instance %s will be recovered from a failed migration."
883                   " Note that the migration procedure (including cleanup)" %
884                   (instance_name,))
885     else:
886       usertext = ("Instance %s will be migrated. Note that migration" %
887                   (instance_name,))
888     usertext += (" might impact the instance if anything goes wrong"
889                  " (e.g. due to bugs in the hypervisor). Continue?")
890     if not AskUser(usertext):
891       return 1
892
893   op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
894                                  cleanup=opts.cleanup)
895   SubmitOpCode(op, cl=cl, opts=opts)
896   return 0
897
898
899 def MoveInstance(opts, args):
900   """Move an instance.
901
902   @param opts: the command line options selected by the user
903   @type args: list
904   @param args: should contain only one element, the instance name
905   @rtype: int
906   @return: the desired exit code
907
908   """
909   cl = GetClient()
910   instance_name = args[0]
911   force = opts.force
912
913   if not force:
914     usertext = ("Instance %s will be moved."
915                 " This requires a shutdown of the instance. Continue?" %
916                 (instance_name,))
917     if not AskUser(usertext):
918       return 1
919
920   op = opcodes.OpMoveInstance(instance_name=instance_name,
921                               target_node=opts.node,
922                               shutdown_timeout=opts.shutdown_timeout)
923   SubmitOrSend(op, opts, cl=cl)
924   return 0
925
926
927 def ConnectToInstanceConsole(opts, args):
928   """Connect to the console of an instance.
929
930   @param opts: the command line options selected by the user
931   @type args: list
932   @param args: should contain only one element, the instance name
933   @rtype: int
934   @return: the desired exit code
935
936   """
937   instance_name = args[0]
938
939   op = opcodes.OpConnectConsole(instance_name=instance_name)
940   cmd = SubmitOpCode(op, opts=opts)
941
942   if opts.show_command:
943     ToStdout("%s", utils.ShellQuoteArgs(cmd))
944   else:
945     try:
946       os.execvp(cmd[0], cmd)
947     finally:
948       ToStderr("Can't run console command %s with arguments:\n'%s'",
949                cmd[0], " ".join(cmd))
950       os._exit(1) # pylint: disable-msg=W0212
951
952
953 def _FormatLogicalID(dev_type, logical_id, roman):
954   """Formats the logical_id of a disk.
955
956   """
957   if dev_type == constants.LD_DRBD8:
958     node_a, node_b, port, minor_a, minor_b, key = logical_id
959     data = [
960       ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
961                                                             convert=roman))),
962       ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
963                                                             convert=roman))),
964       ("port", compat.TryToRoman(port, convert=roman)),
965       ("auth key", key),
966       ]
967   elif dev_type == constants.LD_LV:
968     vg_name, lv_name = logical_id
969     data = ["%s/%s" % (vg_name, lv_name)]
970   else:
971     data = [str(logical_id)]
972
973   return data
974
975
976 def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
977   """Show block device information.
978
979   This is only used by L{ShowInstanceConfig}, but it's too big to be
980   left for an inline definition.
981
982   @type idx: int
983   @param idx: the index of the current disk
984   @type top_level: boolean
985   @param top_level: if this a top-level disk?
986   @type dev: dict
987   @param dev: dictionary with disk information
988   @type static: boolean
989   @param static: wheter the device information doesn't contain
990       runtime information but only static data
991   @type roman: boolean
992   @param roman: whether to try to use roman integers
993   @return: a list of either strings, tuples or lists
994       (which should be formatted at a higher indent level)
995
996   """
997   def helper(dtype, status):
998     """Format one line for physical device status.
999
1000     @type dtype: str
1001     @param dtype: a constant from the L{constants.LDS_BLOCK} set
1002     @type status: tuple
1003     @param status: a tuple as returned from L{backend.FindBlockDevice}
1004     @return: the string representing the status
1005
1006     """
1007     if not status:
1008       return "not active"
1009     txt = ""
1010     (path, major, minor, syncp, estt, degr, ldisk_status) = status
1011     if major is None:
1012       major_string = "N/A"
1013     else:
1014       major_string = str(compat.TryToRoman(major, convert=roman))
1015
1016     if minor is None:
1017       minor_string = "N/A"
1018     else:
1019       minor_string = str(compat.TryToRoman(minor, convert=roman))
1020
1021     txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1022     if dtype in (constants.LD_DRBD8, ):
1023       if syncp is not None:
1024         sync_text = "*RECOVERING* %5.2f%%," % syncp
1025         if estt:
1026           sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1027         else:
1028           sync_text += " ETA unknown"
1029       else:
1030         sync_text = "in sync"
1031       if degr:
1032         degr_text = "*DEGRADED*"
1033       else:
1034         degr_text = "ok"
1035       if ldisk_status == constants.LDS_FAULTY:
1036         ldisk_text = " *MISSING DISK*"
1037       elif ldisk_status == constants.LDS_UNKNOWN:
1038         ldisk_text = " *UNCERTAIN STATE*"
1039       else:
1040         ldisk_text = ""
1041       txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1042     elif dtype == constants.LD_LV:
1043       if ldisk_status == constants.LDS_FAULTY:
1044         ldisk_text = " *FAILED* (failed drive?)"
1045       else:
1046         ldisk_text = ""
1047       txt += ldisk_text
1048     return txt
1049
1050   # the header
1051   if top_level:
1052     if dev["iv_name"] is not None:
1053       txt = dev["iv_name"]
1054     else:
1055       txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1056   else:
1057     txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1058   if isinstance(dev["size"], int):
1059     nice_size = utils.FormatUnit(dev["size"], "h")
1060   else:
1061     nice_size = dev["size"]
1062   d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1063   data = []
1064   if top_level:
1065     data.append(("access mode", dev["mode"]))
1066   if dev["logical_id"] is not None:
1067     try:
1068       l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1069     except ValueError:
1070       l_id = [str(dev["logical_id"])]
1071     if len(l_id) == 1:
1072       data.append(("logical_id", l_id[0]))
1073     else:
1074       data.extend(l_id)
1075   elif dev["physical_id"] is not None:
1076     data.append("physical_id:")
1077     data.append([dev["physical_id"]])
1078   if not static:
1079     data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1080   if dev["sstatus"] and not static:
1081     data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1082
1083   if dev["children"]:
1084     data.append("child devices:")
1085     for c_idx, child in enumerate(dev["children"]):
1086       data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
1087   d1.append(data)
1088   return d1
1089
1090
1091 def _FormatList(buf, data, indent_level):
1092   """Formats a list of data at a given indent level.
1093
1094   If the element of the list is:
1095     - a string, it is simply formatted as is
1096     - a tuple, it will be split into key, value and the all the
1097       values in a list will be aligned all at the same start column
1098     - a list, will be recursively formatted
1099
1100   @type buf: StringIO
1101   @param buf: the buffer into which we write the output
1102   @param data: the list to format
1103   @type indent_level: int
1104   @param indent_level: the indent level to format at
1105
1106   """
1107   max_tlen = max([len(elem[0]) for elem in data
1108                  if isinstance(elem, tuple)] or [0])
1109   for elem in data:
1110     if isinstance(elem, basestring):
1111       buf.write("%*s%s\n" % (2*indent_level, "", elem))
1112     elif isinstance(elem, tuple):
1113       key, value = elem
1114       spacer = "%*s" % (max_tlen - len(key), "")
1115       buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1116     elif isinstance(elem, list):
1117       _FormatList(buf, elem, indent_level+1)
1118
1119
1120 def _FormatParameterDict(buf, per_inst, actual):
1121   """Formats a parameter dictionary.
1122
1123   @type buf: L{StringIO}
1124   @param buf: the buffer into which to write
1125   @type per_inst: dict
1126   @param per_inst: the instance's own parameters
1127   @type actual: dict
1128   @param actual: the current parameter set (including defaults)
1129
1130   """
1131   for key in sorted(actual):
1132     val = per_inst.get(key, "default (%s)" % actual[key])
1133     buf.write("    - %s: %s\n" % (key, val))
1134
1135 def ShowInstanceConfig(opts, args):
1136   """Compute instance run-time status.
1137
1138   @param opts: the command line options selected by the user
1139   @type args: list
1140   @param args: either an empty list, and then we query all
1141       instances, or should contain a list of instance names
1142   @rtype: int
1143   @return: the desired exit code
1144
1145   """
1146   if not args and not opts.show_all:
1147     ToStderr("No instance selected."
1148              " Please pass in --all if you want to query all instances.\n"
1149              "Note that this can take a long time on a big cluster.")
1150     return 1
1151   elif args and opts.show_all:
1152     ToStderr("Cannot use --all if you specify instance names.")
1153     return 1
1154
1155   retcode = 0
1156   op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1157   result = SubmitOpCode(op, opts=opts)
1158   if not result:
1159     ToStdout("No instances.")
1160     return 1
1161
1162   buf = StringIO()
1163   retcode = 0
1164   for instance_name in result:
1165     instance = result[instance_name]
1166     buf.write("Instance name: %s\n" % instance["name"])
1167     buf.write("UUID: %s\n" % instance["uuid"])
1168     buf.write("Serial number: %s\n" %
1169               compat.TryToRoman(instance["serial_no"],
1170                                 convert=opts.roman_integers))
1171     buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1172     buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1173     buf.write("State: configured to be %s" % instance["config_state"])
1174     if not opts.static:
1175       buf.write(", actual state is %s" % instance["run_state"])
1176     buf.write("\n")
1177     ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1178     ##          instance["auto_balance"])
1179     buf.write("  Nodes:\n")
1180     buf.write("    - primary: %s\n" % instance["pnode"])
1181     buf.write("    - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1182     buf.write("  Operating system: %s\n" % instance["os"])
1183     _FormatParameterDict(buf, instance["os_instance"], instance["os_actual"])
1184     if instance.has_key("network_port"):
1185       buf.write("  Allocated network port: %s\n" %
1186                 compat.TryToRoman(instance["network_port"],
1187                                   convert=opts.roman_integers))
1188     buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1189
1190     # custom VNC console information
1191     vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1192                                                  None)
1193     if vnc_bind_address:
1194       port = instance["network_port"]
1195       display = int(port) - constants.VNC_BASE_PORT
1196       if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1197         vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1198                                                    port,
1199                                                    display)
1200       elif display > 0 and utils.IsValidIP4(vnc_bind_address):
1201         vnc_console_port = ("%s:%s (node %s) (display %s)" %
1202                              (vnc_bind_address, port,
1203                               instance["pnode"], display))
1204       else:
1205         # vnc bind address is a file
1206         vnc_console_port = "%s:%s" % (instance["pnode"],
1207                                       vnc_bind_address)
1208       buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1209
1210     _FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"])
1211     buf.write("  Hardware:\n")
1212     buf.write("    - VCPUs: %s\n" %
1213               compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1214                                 convert=opts.roman_integers))
1215     buf.write("    - memory: %sMiB\n" %
1216               compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
1217                                 convert=opts.roman_integers))
1218     buf.write("    - NICs:\n")
1219     for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1220       buf.write("      - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1221                 (idx, mac, ip, mode, link))
1222     buf.write("  Disks:\n")
1223
1224     for idx, device in enumerate(instance["disks"]):
1225       _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static,
1226                   opts.roman_integers), 2)
1227
1228   ToStdout(buf.getvalue().rstrip('\n'))
1229   return retcode
1230
1231
1232 def SetInstanceParams(opts, args):
1233   """Modifies an instance.
1234
1235   All parameters take effect only at the next restart of the instance.
1236
1237   @param opts: the command line options selected by the user
1238   @type args: list
1239   @param args: should contain only one element, the instance name
1240   @rtype: int
1241   @return: the desired exit code
1242
1243   """
1244   if not (opts.nics or opts.disks or opts.disk_template or
1245           opts.hvparams or opts.beparams or opts.os or opts.osparams):
1246     ToStderr("Please give at least one of the parameters.")
1247     return 1
1248
1249   for param in opts.beparams:
1250     if isinstance(opts.beparams[param], basestring):
1251       if opts.beparams[param].lower() == "default":
1252         opts.beparams[param] = constants.VALUE_DEFAULT
1253
1254   utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1255                       allowed_values=[constants.VALUE_DEFAULT])
1256
1257   for param in opts.hvparams:
1258     if isinstance(opts.hvparams[param], basestring):
1259       if opts.hvparams[param].lower() == "default":
1260         opts.hvparams[param] = constants.VALUE_DEFAULT
1261
1262   utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1263                       allowed_values=[constants.VALUE_DEFAULT])
1264
1265   for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1266     try:
1267       nic_op = int(nic_op)
1268       opts.nics[idx] = (nic_op, nic_dict)
1269     except (TypeError, ValueError):
1270       pass
1271
1272   for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1273     try:
1274       disk_op = int(disk_op)
1275       opts.disks[idx] = (disk_op, disk_dict)
1276     except (TypeError, ValueError):
1277       pass
1278     if disk_op == constants.DDM_ADD:
1279       if 'size' not in disk_dict:
1280         raise errors.OpPrereqError("Missing required parameter 'size'",
1281                                    errors.ECODE_INVAL)
1282       disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1283
1284   if (opts.disk_template and
1285       opts.disk_template in constants.DTS_NET_MIRROR and
1286       not opts.node):
1287     ToStderr("Changing the disk template to a mirrored one requires"
1288              " specifying a secondary node")
1289     return 1
1290
1291   op = opcodes.OpSetInstanceParams(instance_name=args[0],
1292                                    nics=opts.nics,
1293                                    disks=opts.disks,
1294                                    disk_template=opts.disk_template,
1295                                    remote_node=opts.node,
1296                                    hvparams=opts.hvparams,
1297                                    beparams=opts.beparams,
1298                                    os_name=opts.os,
1299                                    osparams=opts.osparams,
1300                                    force_variant=opts.force_variant,
1301                                    force=opts.force)
1302
1303   # even if here we process the result, we allow submit only
1304   result = SubmitOrSend(op, opts)
1305
1306   if result:
1307     ToStdout("Modified instance %s", args[0])
1308     for param, data in result:
1309       ToStdout(" - %-5s -> %s", param, data)
1310     ToStdout("Please don't forget that most parameters take effect"
1311              " only at the next start of the instance.")
1312   return 0
1313
1314
1315 # multi-instance selection options
1316 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1317                            help="Do not ask for confirmation when more than"
1318                            " one instance is affected",
1319                            action="store_true", default=False)
1320
1321 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1322                             help="Filter by nodes (primary only)",
1323                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1324
1325 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1326                             help="Filter by nodes (secondary only)",
1327                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1328
1329 m_node_opt = cli_option("--node", dest="multi_mode",
1330                         help="Filter by nodes (primary and secondary)",
1331                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1332
1333 m_clust_opt = cli_option("--all", dest="multi_mode",
1334                          help="Select all instances in the cluster",
1335                          const=_SHUTDOWN_CLUSTER, action="store_const")
1336
1337 m_inst_opt = cli_option("--instance", dest="multi_mode",
1338                         help="Filter by instance name [default]",
1339                         const=_SHUTDOWN_INSTANCES, action="store_const")
1340
1341 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1342                              help="Filter by node tag",
1343                              const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1344                              action="store_const")
1345
1346 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1347                                  help="Filter by primary node tag",
1348                                  const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1349                                  action="store_const")
1350
1351 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1352                                  help="Filter by secondary node tag",
1353                                  const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1354                                  action="store_const")
1355
1356 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1357                              help="Filter by instance tag",
1358                              const=_SHUTDOWN_INSTANCES_BY_TAGS,
1359                              action="store_const")
1360
1361 # this is defined separately due to readability only
1362 add_opts = [
1363   BACKEND_OPT,
1364   DISK_OPT,
1365   DISK_TEMPLATE_OPT,
1366   FILESTORE_DIR_OPT,
1367   FILESTORE_DRIVER_OPT,
1368   HYPERVISOR_OPT,
1369   IALLOCATOR_OPT,
1370   NET_OPT,
1371   NODE_PLACEMENT_OPT,
1372   NOIPCHECK_OPT,
1373   NONAMECHECK_OPT,
1374   NONICS_OPT,
1375   NOSTART_OPT,
1376   NWSYNC_OPT,
1377   OSPARAMS_OPT,
1378   OS_OPT,
1379   FORCE_VARIANT_OPT,
1380   NO_INSTALL_OPT,
1381   OS_SIZE_OPT,
1382   SUBMIT_OPT,
1383   ]
1384
1385 commands = {
1386   'add': (
1387     AddInstance, [ArgHost(min=1, max=1)], 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)], [],
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],
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     "[-f] <instance>", "Stops the instance and starts it on the backup node,"
1402     " using the remote mirror (only for instances of type drbd)"),
1403   'migrate': (
1404     MigrateInstance, ARGS_ONE_INSTANCE,
1405     [FORCE_OPT, NONLIVE_OPT, CLEANUP_OPT],
1406     "[-f] <instance>", "Migrate instance to its secondary node"
1407     " (only for instances of type drbd)"),
1408   'move': (
1409     MoveInstance, ARGS_ONE_INSTANCE,
1410     [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT],
1411     "[-f] <instance>", "Move instance to an arbitrary node"
1412     " (only for instances of type file and lv)"),
1413   'info': (
1414     ShowInstanceConfig, ARGS_MANY_INSTANCES,
1415     [STATIC_OPT, ALL_OPT, ROMAN_OPT],
1416     "[-s] {--all | <instance>...}",
1417     "Show information on the specified instance(s)"),
1418   'list': (
1419     ListInstances, ARGS_MANY_INSTANCES,
1420     [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT, ROMAN_OPT],
1421     "[<instance>...]",
1422     "Lists the instances and their status. The available fields are"
1423     " (see the man page for details): status, oper_state, oper_ram,"
1424     " name, os, pnode, snodes, admin_state, admin_ram, disk_template,"
1425     " ip, mac, nic_mode, nic_link, sda_size, sdb_size, vcpus, serial_no,"
1426     " nic.count, nic.mac/N, nic.ip/N, nic.mode/N, nic.link/N,"
1427     " nic.macs, nic.ips, nic.modes, nic.links,"
1428     " disk.count, disk.size/N, disk.sizes,"
1429     " hv/NAME, be/memory, be/vcpus, be/auto_balance,"
1430     " hypervisor."
1431     " The default field"
1432     " list is (in order): %s." % utils.CommaJoin(_LIST_DEF_FIELDS),
1433     ),
1434   'reinstall': (
1435     ReinstallInstance, [ArgInstance()],
1436     [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1437      m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1438      m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1439      SUBMIT_OPT],
1440     "[-f] <instance>", "Reinstall a stopped instance"),
1441   'remove': (
1442     RemoveInstance, ARGS_ONE_INSTANCE,
1443     [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT],
1444     "[-f] <instance>", "Shuts down the instance and removes it"),
1445   'rename': (
1446     RenameInstance,
1447     [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1448     [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT],
1449     "<instance> <new_name>", "Rename the instance"),
1450   'replace-disks': (
1451     ReplaceDisks, ARGS_ONE_INSTANCE,
1452     [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1453      NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT],
1454     "[-s|-p|-n NODE|-I NAME] <instance>",
1455     "Replaces all disks for the instance"),
1456   'modify': (
1457     SetInstanceParams, ARGS_ONE_INSTANCE,
1458     [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1459      DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1460      OSPARAMS_OPT],
1461     "<instance>", "Alters the parameters of an instance"),
1462   'shutdown': (
1463     GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1464     [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1465      m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1466      m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT],
1467     "<instance>", "Stops an instance"),
1468   'startup': (
1469     GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1470     [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1471      m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1472      m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1473      BACKEND_OPT],
1474     "<instance>", "Starts an instance"),
1475   'reboot': (
1476     GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1477     [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1478      m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1479      m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1480      m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT],
1481     "<instance>", "Reboots an instance"),
1482   'activate-disks': (
1483     ActivateDisks, ARGS_ONE_INSTANCE, [SUBMIT_OPT, IGNORE_SIZE_OPT],
1484     "<instance>", "Activate an instance's disks"),
1485   'deactivate-disks': (
1486     DeactivateDisks, ARGS_ONE_INSTANCE, [SUBMIT_OPT],
1487     "<instance>", "Deactivate an instance's disks"),
1488   'recreate-disks': (
1489     RecreateDisks, ARGS_ONE_INSTANCE, [SUBMIT_OPT, DISKIDX_OPT],
1490     "<instance>", "Recreate an instance's disks"),
1491   'grow-disk': (
1492     GrowDisk,
1493     [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1494      ArgUnknown(min=1, max=1)],
1495     [SUBMIT_OPT, NWSYNC_OPT],
1496     "<instance> <disk> <size>", "Grow an instance's disk"),
1497   'list-tags': (
1498     ListTags, ARGS_ONE_INSTANCE, [],
1499     "<instance_name>", "List the tags of the given instance"),
1500   'add-tags': (
1501     AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1502     [TAG_SRC_OPT],
1503     "<instance_name> tag...", "Add tags to the given instance"),
1504   'remove-tags': (
1505     RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1506     [TAG_SRC_OPT],
1507     "<instance_name> tag...", "Remove tags from given instance"),
1508   }
1509
1510 #: dictionary with aliases for commands
1511 aliases = {
1512   'activate_block_devs': 'activate-disks',
1513   'replace_disks': 'replace-disks',
1514   'start': 'startup',
1515   'stop': 'shutdown',
1516   }
1517
1518
1519 if __name__ == '__main__':
1520   sys.exit(GenericMain(commands, aliases=aliases,
1521                        override={"tag_type": constants.TAG_INSTANCE}))