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