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