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