CLI changes to facilitate shared storage migration/failover
[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_NET_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   SubmitOpCode(op, cl=cl, opts=opts)
834   return 0
835
836
837 def MoveInstance(opts, args):
838   """Move an instance.
839
840   @param opts: the command line options selected by the user
841   @type args: list
842   @param args: should contain only one element, the instance name
843   @rtype: int
844   @return: the desired exit code
845
846   """
847   cl = GetClient()
848   instance_name = args[0]
849   force = opts.force
850
851   if not force:
852     usertext = ("Instance %s will be moved."
853                 " This requires a shutdown of the instance. Continue?" %
854                 (instance_name,))
855     if not AskUser(usertext):
856       return 1
857
858   op = opcodes.OpInstanceMove(instance_name=instance_name,
859                               target_node=opts.node,
860                               shutdown_timeout=opts.shutdown_timeout)
861   SubmitOrSend(op, opts, cl=cl)
862   return 0
863
864
865 def ConnectToInstanceConsole(opts, args):
866   """Connect to the console of an instance.
867
868   @param opts: the command line options selected by the user
869   @type args: list
870   @param args: should contain only one element, the instance name
871   @rtype: int
872   @return: the desired exit code
873
874   """
875   instance_name = args[0]
876
877   op = opcodes.OpInstanceConsole(instance_name=instance_name)
878
879   cl = GetClient()
880   try:
881     cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
882     console_data = SubmitOpCode(op, opts=opts, cl=cl)
883   finally:
884     # Ensure client connection is closed while external commands are run
885     cl.Close()
886
887   del cl
888
889   return _DoConsole(objects.InstanceConsole.FromDict(console_data),
890                     opts.show_command, cluster_name)
891
892
893 def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
894                _runcmd_fn=utils.RunCmd):
895   """Acts based on the result of L{opcodes.OpInstanceConsole}.
896
897   @type console: L{objects.InstanceConsole}
898   @param console: Console object
899   @type show_command: bool
900   @param show_command: Whether to just display commands
901   @type cluster_name: string
902   @param cluster_name: Cluster name as retrieved from master daemon
903
904   """
905   assert console.Validate()
906
907   if console.kind == constants.CONS_MESSAGE:
908     feedback_fn(console.message)
909   elif console.kind == constants.CONS_VNC:
910     feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
911                 " URL <vnc://%s:%s/>",
912                 console.instance, console.host, console.port,
913                 console.display, console.host, console.port)
914   elif console.kind == constants.CONS_SSH:
915     # Convert to string if not already one
916     if isinstance(console.command, basestring):
917       cmd = console.command
918     else:
919       cmd = utils.ShellQuoteArgs(console.command)
920
921     srun = ssh.SshRunner(cluster_name=cluster_name)
922     ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
923                             batch=True, quiet=False, tty=True)
924
925     if show_command:
926       feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
927     else:
928       result = _runcmd_fn(ssh_cmd, interactive=True)
929       if result.failed:
930         logging.error("Console command \"%s\" failed with reason '%s' and"
931                       " output %r", result.cmd, result.fail_reason,
932                       result.output)
933         raise errors.OpExecError("Connection to console of instance %s failed,"
934                                  " please check cluster configuration" %
935                                  console.instance)
936   else:
937     raise errors.GenericError("Unknown console type '%s'" % console.kind)
938
939   return constants.EXIT_SUCCESS
940
941
942 def _FormatLogicalID(dev_type, logical_id, roman):
943   """Formats the logical_id of a disk.
944
945   """
946   if dev_type == constants.LD_DRBD8:
947     node_a, node_b, port, minor_a, minor_b, key = logical_id
948     data = [
949       ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
950                                                             convert=roman))),
951       ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
952                                                             convert=roman))),
953       ("port", compat.TryToRoman(port, convert=roman)),
954       ("auth key", key),
955       ]
956   elif dev_type == constants.LD_LV:
957     vg_name, lv_name = logical_id
958     data = ["%s/%s" % (vg_name, lv_name)]
959   else:
960     data = [str(logical_id)]
961
962   return data
963
964
965 def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
966   """Show block device information.
967
968   This is only used by L{ShowInstanceConfig}, but it's too big to be
969   left for an inline definition.
970
971   @type idx: int
972   @param idx: the index of the current disk
973   @type top_level: boolean
974   @param top_level: if this a top-level disk?
975   @type dev: dict
976   @param dev: dictionary with disk information
977   @type static: boolean
978   @param static: wheter the device information doesn't contain
979       runtime information but only static data
980   @type roman: boolean
981   @param roman: whether to try to use roman integers
982   @return: a list of either strings, tuples or lists
983       (which should be formatted at a higher indent level)
984
985   """
986   def helper(dtype, status):
987     """Format one line for physical device status.
988
989     @type dtype: str
990     @param dtype: a constant from the L{constants.LDS_BLOCK} set
991     @type status: tuple
992     @param status: a tuple as returned from L{backend.FindBlockDevice}
993     @return: the string representing the status
994
995     """
996     if not status:
997       return "not active"
998     txt = ""
999     (path, major, minor, syncp, estt, degr, ldisk_status) = status
1000     if major is None:
1001       major_string = "N/A"
1002     else:
1003       major_string = str(compat.TryToRoman(major, convert=roman))
1004
1005     if minor is None:
1006       minor_string = "N/A"
1007     else:
1008       minor_string = str(compat.TryToRoman(minor, convert=roman))
1009
1010     txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1011     if dtype in (constants.LD_DRBD8, ):
1012       if syncp is not None:
1013         sync_text = "*RECOVERING* %5.2f%%," % syncp
1014         if estt:
1015           sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1016         else:
1017           sync_text += " ETA unknown"
1018       else:
1019         sync_text = "in sync"
1020       if degr:
1021         degr_text = "*DEGRADED*"
1022       else:
1023         degr_text = "ok"
1024       if ldisk_status == constants.LDS_FAULTY:
1025         ldisk_text = " *MISSING DISK*"
1026       elif ldisk_status == constants.LDS_UNKNOWN:
1027         ldisk_text = " *UNCERTAIN STATE*"
1028       else:
1029         ldisk_text = ""
1030       txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1031     elif dtype == constants.LD_LV:
1032       if ldisk_status == constants.LDS_FAULTY:
1033         ldisk_text = " *FAILED* (failed drive?)"
1034       else:
1035         ldisk_text = ""
1036       txt += ldisk_text
1037     return txt
1038
1039   # the header
1040   if top_level:
1041     if dev["iv_name"] is not None:
1042       txt = dev["iv_name"]
1043     else:
1044       txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1045   else:
1046     txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1047   if isinstance(dev["size"], int):
1048     nice_size = utils.FormatUnit(dev["size"], "h")
1049   else:
1050     nice_size = dev["size"]
1051   d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1052   data = []
1053   if top_level:
1054     data.append(("access mode", dev["mode"]))
1055   if dev["logical_id"] is not None:
1056     try:
1057       l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1058     except ValueError:
1059       l_id = [str(dev["logical_id"])]
1060     if len(l_id) == 1:
1061       data.append(("logical_id", l_id[0]))
1062     else:
1063       data.extend(l_id)
1064   elif dev["physical_id"] is not None:
1065     data.append("physical_id:")
1066     data.append([dev["physical_id"]])
1067   if not static:
1068     data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1069   if dev["sstatus"] and not static:
1070     data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1071
1072   if dev["children"]:
1073     data.append("child devices:")
1074     for c_idx, child in enumerate(dev["children"]):
1075       data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
1076   d1.append(data)
1077   return d1
1078
1079
1080 def _FormatList(buf, data, indent_level):
1081   """Formats a list of data at a given indent level.
1082
1083   If the element of the list is:
1084     - a string, it is simply formatted as is
1085     - a tuple, it will be split into key, value and the all the
1086       values in a list will be aligned all at the same start column
1087     - a list, will be recursively formatted
1088
1089   @type buf: StringIO
1090   @param buf: the buffer into which we write the output
1091   @param data: the list to format
1092   @type indent_level: int
1093   @param indent_level: the indent level to format at
1094
1095   """
1096   max_tlen = max([len(elem[0]) for elem in data
1097                  if isinstance(elem, tuple)] or [0])
1098   for elem in data:
1099     if isinstance(elem, basestring):
1100       buf.write("%*s%s\n" % (2*indent_level, "", elem))
1101     elif isinstance(elem, tuple):
1102       key, value = elem
1103       spacer = "%*s" % (max_tlen - len(key), "")
1104       buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1105     elif isinstance(elem, list):
1106       _FormatList(buf, elem, indent_level+1)
1107
1108
1109 def ShowInstanceConfig(opts, args):
1110   """Compute instance run-time status.
1111
1112   @param opts: the command line options selected by the user
1113   @type args: list
1114   @param args: either an empty list, and then we query all
1115       instances, or should contain a list of instance names
1116   @rtype: int
1117   @return: the desired exit code
1118
1119   """
1120   if not args and not opts.show_all:
1121     ToStderr("No instance selected."
1122              " Please pass in --all if you want to query all instances.\n"
1123              "Note that this can take a long time on a big cluster.")
1124     return 1
1125   elif args and opts.show_all:
1126     ToStderr("Cannot use --all if you specify instance names.")
1127     return 1
1128
1129   retcode = 0
1130   op = opcodes.OpInstanceQueryData(instances=args, static=opts.static)
1131   result = SubmitOpCode(op, opts=opts)
1132   if not result:
1133     ToStdout("No instances.")
1134     return 1
1135
1136   buf = StringIO()
1137   retcode = 0
1138   for instance_name in result:
1139     instance = result[instance_name]
1140     buf.write("Instance name: %s\n" % instance["name"])
1141     buf.write("UUID: %s\n" % instance["uuid"])
1142     buf.write("Serial number: %s\n" %
1143               compat.TryToRoman(instance["serial_no"],
1144                                 convert=opts.roman_integers))
1145     buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1146     buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1147     buf.write("State: configured to be %s" % instance["config_state"])
1148     if not opts.static:
1149       buf.write(", actual state is %s" % instance["run_state"])
1150     buf.write("\n")
1151     ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1152     ##          instance["auto_balance"])
1153     buf.write("  Nodes:\n")
1154     buf.write("    - primary: %s\n" % instance["pnode"])
1155     buf.write("    - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1156     buf.write("  Operating system: %s\n" % instance["os"])
1157     FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1158                         level=2)
1159     if instance.has_key("network_port"):
1160       buf.write("  Allocated network port: %s\n" %
1161                 compat.TryToRoman(instance["network_port"],
1162                                   convert=opts.roman_integers))
1163     buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1164
1165     # custom VNC console information
1166     vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1167                                                  None)
1168     if vnc_bind_address:
1169       port = instance["network_port"]
1170       display = int(port) - constants.VNC_BASE_PORT
1171       if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1172         vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1173                                                    port,
1174                                                    display)
1175       elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1176         vnc_console_port = ("%s:%s (node %s) (display %s)" %
1177                              (vnc_bind_address, port,
1178                               instance["pnode"], display))
1179       else:
1180         # vnc bind address is a file
1181         vnc_console_port = "%s:%s" % (instance["pnode"],
1182                                       vnc_bind_address)
1183       buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1184
1185     FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1186                         level=2)
1187     buf.write("  Hardware:\n")
1188     buf.write("    - VCPUs: %s\n" %
1189               compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1190                                 convert=opts.roman_integers))
1191     buf.write("    - memory: %sMiB\n" %
1192               compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
1193                                 convert=opts.roman_integers))
1194     buf.write("    - NICs:\n")
1195     for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1196       buf.write("      - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1197                 (idx, mac, ip, mode, link))
1198     buf.write("  Disk template: %s\n" % instance["disk_template"])
1199     buf.write("  Disks:\n")
1200
1201     for idx, device in enumerate(instance["disks"]):
1202       _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static,
1203                   opts.roman_integers), 2)
1204
1205   ToStdout(buf.getvalue().rstrip('\n'))
1206   return retcode
1207
1208
1209 def SetInstanceParams(opts, args):
1210   """Modifies an instance.
1211
1212   All parameters take effect only at the next restart of the instance.
1213
1214   @param opts: the command line options selected by the user
1215   @type args: list
1216   @param args: should contain only one element, the instance name
1217   @rtype: int
1218   @return: the desired exit code
1219
1220   """
1221   if not (opts.nics or opts.disks or opts.disk_template or
1222           opts.hvparams or opts.beparams or opts.os or opts.osparams):
1223     ToStderr("Please give at least one of the parameters.")
1224     return 1
1225
1226   for param in opts.beparams:
1227     if isinstance(opts.beparams[param], basestring):
1228       if opts.beparams[param].lower() == "default":
1229         opts.beparams[param] = constants.VALUE_DEFAULT
1230
1231   utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1232                       allowed_values=[constants.VALUE_DEFAULT])
1233
1234   for param in opts.hvparams:
1235     if isinstance(opts.hvparams[param], basestring):
1236       if opts.hvparams[param].lower() == "default":
1237         opts.hvparams[param] = constants.VALUE_DEFAULT
1238
1239   utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1240                       allowed_values=[constants.VALUE_DEFAULT])
1241
1242   for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1243     try:
1244       nic_op = int(nic_op)
1245       opts.nics[idx] = (nic_op, nic_dict)
1246     except (TypeError, ValueError):
1247       pass
1248
1249   for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1250     try:
1251       disk_op = int(disk_op)
1252       opts.disks[idx] = (disk_op, disk_dict)
1253     except (TypeError, ValueError):
1254       pass
1255     if disk_op == constants.DDM_ADD:
1256       if 'size' not in disk_dict:
1257         raise errors.OpPrereqError("Missing required parameter 'size'",
1258                                    errors.ECODE_INVAL)
1259       disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1260
1261   if (opts.disk_template and
1262       opts.disk_template in constants.DTS_NET_MIRROR and
1263       not opts.node):
1264     ToStderr("Changing the disk template to a mirrored one requires"
1265              " specifying a secondary node")
1266     return 1
1267
1268   op = opcodes.OpInstanceSetParams(instance_name=args[0],
1269                                    nics=opts.nics,
1270                                    disks=opts.disks,
1271                                    disk_template=opts.disk_template,
1272                                    remote_node=opts.node,
1273                                    hvparams=opts.hvparams,
1274                                    beparams=opts.beparams,
1275                                    os_name=opts.os,
1276                                    osparams=opts.osparams,
1277                                    force_variant=opts.force_variant,
1278                                    force=opts.force)
1279
1280   # even if here we process the result, we allow submit only
1281   result = SubmitOrSend(op, opts)
1282
1283   if result:
1284     ToStdout("Modified instance %s", args[0])
1285     for param, data in result:
1286       ToStdout(" - %-5s -> %s", param, data)
1287     ToStdout("Please don't forget that most parameters take effect"
1288              " only at the next start of the instance.")
1289   return 0
1290
1291
1292 # multi-instance selection options
1293 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1294                            help="Do not ask for confirmation when more than"
1295                            " one instance is affected",
1296                            action="store_true", default=False)
1297
1298 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1299                             help="Filter by nodes (primary only)",
1300                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1301
1302 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1303                             help="Filter by nodes (secondary only)",
1304                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1305
1306 m_node_opt = cli_option("--node", dest="multi_mode",
1307                         help="Filter by nodes (primary and secondary)",
1308                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1309
1310 m_clust_opt = cli_option("--all", dest="multi_mode",
1311                          help="Select all instances in the cluster",
1312                          const=_SHUTDOWN_CLUSTER, action="store_const")
1313
1314 m_inst_opt = cli_option("--instance", dest="multi_mode",
1315                         help="Filter by instance name [default]",
1316                         const=_SHUTDOWN_INSTANCES, action="store_const")
1317
1318 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1319                              help="Filter by node tag",
1320                              const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1321                              action="store_const")
1322
1323 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1324                                  help="Filter by primary node tag",
1325                                  const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1326                                  action="store_const")
1327
1328 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1329                                  help="Filter by secondary node tag",
1330                                  const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1331                                  action="store_const")
1332
1333 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1334                              help="Filter by instance tag",
1335                              const=_SHUTDOWN_INSTANCES_BY_TAGS,
1336                              action="store_const")
1337
1338 # this is defined separately due to readability only
1339 add_opts = [
1340   NOSTART_OPT,
1341   OS_OPT,
1342   FORCE_VARIANT_OPT,
1343   NO_INSTALL_OPT,
1344   ]
1345
1346 commands = {
1347   'add': (
1348     AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1349     "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1350     "Creates and adds a new instance to the cluster"),
1351   'batch-create': (
1352     BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1353     "<instances.json>",
1354     "Create a bunch of instances based on specs in the file."),
1355   'console': (
1356     ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1357     [SHOWCMD_OPT, PRIORITY_OPT],
1358     "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1359   'failover': (
1360     FailoverInstance, ARGS_ONE_INSTANCE,
1361     [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1362      DRY_RUN_OPT, PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT],
1363     "[-f] <instance>", "Stops the instance and starts it on the backup node,"
1364     " using the remote mirror (only for mirrored instances)"),
1365   'migrate': (
1366     MigrateInstance, ARGS_ONE_INSTANCE,
1367     [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1368      PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT],
1369     "[-f] <instance>", "Migrate instance to its secondary node"
1370     " (only for mirrored instances)"),
1371   'move': (
1372     MoveInstance, ARGS_ONE_INSTANCE,
1373     [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1374      DRY_RUN_OPT, PRIORITY_OPT],
1375     "[-f] <instance>", "Move instance to an arbitrary node"
1376     " (only for instances of type file and lv)"),
1377   'info': (
1378     ShowInstanceConfig, ARGS_MANY_INSTANCES,
1379     [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1380     "[-s] {--all | <instance>...}",
1381     "Show information on the specified instance(s)"),
1382   'list': (
1383     ListInstances, ARGS_MANY_INSTANCES,
1384     [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT],
1385     "[<instance>...]",
1386     "Lists the instances and their status. The available fields can be shown"
1387     " using the \"list-fields\" command (see the man page for details)."
1388     " The default field list is (in order): %s." %
1389     utils.CommaJoin(_LIST_DEF_FIELDS),
1390     ),
1391   "list-fields": (
1392     ListInstanceFields, [ArgUnknown()],
1393     [NOHDR_OPT, SEP_OPT],
1394     "[fields...]",
1395     "Lists all available fields for instances"),
1396   'reinstall': (
1397     ReinstallInstance, [ArgInstance()],
1398     [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1399      m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1400      m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1401      SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1402     "[-f] <instance>", "Reinstall a stopped instance"),
1403   'remove': (
1404     RemoveInstance, ARGS_ONE_INSTANCE,
1405     [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1406      DRY_RUN_OPT, PRIORITY_OPT],
1407     "[-f] <instance>", "Shuts down the instance and removes it"),
1408   'rename': (
1409     RenameInstance,
1410     [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1411     [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1412     "<instance> <new_name>", "Rename the instance"),
1413   'replace-disks': (
1414     ReplaceDisks, ARGS_ONE_INSTANCE,
1415     [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1416      NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1417      DRY_RUN_OPT, PRIORITY_OPT],
1418     "[-s|-p|-n NODE|-I NAME] <instance>",
1419     "Replaces all disks for the instance"),
1420   'modify': (
1421     SetInstanceParams, ARGS_ONE_INSTANCE,
1422     [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1423      DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1424      OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1425     "<instance>", "Alters the parameters of an instance"),
1426   'shutdown': (
1427     GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1428     [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1429      m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1430      m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1431      DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1432     "<instance>", "Stops an instance"),
1433   'startup': (
1434     GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1435     [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1436      m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1437      m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1438      BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1439     "<instance>", "Starts an instance"),
1440   'reboot': (
1441     GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1442     [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1443      m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1444      m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1445      m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1446     "<instance>", "Reboots an instance"),
1447   'activate-disks': (
1448     ActivateDisks, ARGS_ONE_INSTANCE,
1449     [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
1450     "<instance>", "Activate an instance's disks"),
1451   'deactivate-disks': (
1452     DeactivateDisks, ARGS_ONE_INSTANCE,
1453     [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1454     "[-f] <instance>", "Deactivate an instance's disks"),
1455   'recreate-disks': (
1456     RecreateDisks, ARGS_ONE_INSTANCE,
1457     [SUBMIT_OPT, DISKIDX_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1458     "<instance>", "Recreate an instance's disks"),
1459   'grow-disk': (
1460     GrowDisk,
1461     [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1462      ArgUnknown(min=1, max=1)],
1463     [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1464     "<instance> <disk> <size>", "Grow an instance's disk"),
1465   'list-tags': (
1466     ListTags, ARGS_ONE_INSTANCE, [PRIORITY_OPT],
1467     "<instance_name>", "List the tags of the given instance"),
1468   'add-tags': (
1469     AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1470     [TAG_SRC_OPT, PRIORITY_OPT],
1471     "<instance_name> tag...", "Add tags to the given instance"),
1472   'remove-tags': (
1473     RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1474     [TAG_SRC_OPT, PRIORITY_OPT],
1475     "<instance_name> tag...", "Remove tags from given instance"),
1476   }
1477
1478 #: dictionary with aliases for commands
1479 aliases = {
1480   'start': 'startup',
1481   'stop': 'shutdown',
1482   }
1483
1484
1485 def Main():
1486   return GenericMain(commands, aliases=aliases,
1487                      override={"tag_type": constants.TAG_INSTANCE})