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