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