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