gnt-node list: Query filter support
[ganeti-local] / lib / client / gnt_instance.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21 """Instance related commands"""
22
23 # pylint: disable-msg=W0401,W0614,C0103
24 # W0401: Wildcard import ganeti.cli
25 # W0614: Unused import %s from wildcard import (since we need cli)
26 # C0103: Invalid name gnt-instance
27
28 import itertools
29 import simplejson
30 import logging
31 from cStringIO import StringIO
32
33 from ganeti.cli import *
34 from ganeti import opcodes
35 from ganeti import constants
36 from ganeti import compat
37 from ganeti import utils
38 from ganeti import errors
39 from ganeti import netutils
40 from ganeti import ssh
41 from ganeti import objects
42
43
44 _SHUTDOWN_CLUSTER = "cluster"
45 _SHUTDOWN_NODES_BOTH = "nodes"
46 _SHUTDOWN_NODES_PRI = "nodes-pri"
47 _SHUTDOWN_NODES_SEC = "nodes-sec"
48 _SHUTDOWN_NODES_BOTH_BY_TAGS = "nodes-by-tags"
49 _SHUTDOWN_NODES_PRI_BY_TAGS = "nodes-pri-by-tags"
50 _SHUTDOWN_NODES_SEC_BY_TAGS = "nodes-sec-by-tags"
51 _SHUTDOWN_INSTANCES = "instances"
52 _SHUTDOWN_INSTANCES_BY_TAGS = "instances-by-tags"
53
54 _SHUTDOWN_NODES_TAGS_MODES = (
55     _SHUTDOWN_NODES_BOTH_BY_TAGS,
56     _SHUTDOWN_NODES_PRI_BY_TAGS,
57     _SHUTDOWN_NODES_SEC_BY_TAGS)
58
59
60 #: default list of options for L{ListInstances}
61 _LIST_DEF_FIELDS = [
62   "name", "hypervisor", "os", "pnode", "status", "oper_ram",
63   ]
64
65
66 def _ExpandMultiNames(mode, names, client=None):
67   """Expand the given names using the passed mode.
68
69   For _SHUTDOWN_CLUSTER, all instances will be returned. For
70   _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
71   primary/secondary will be returned. For _SHUTDOWN_NODES_BOTH, all
72   instances having those nodes as either primary or secondary will be
73   returned. For _SHUTDOWN_INSTANCES, the given instances will be
74   returned.
75
76   @param mode: one of L{_SHUTDOWN_CLUSTER}, L{_SHUTDOWN_NODES_BOTH},
77       L{_SHUTDOWN_NODES_PRI}, L{_SHUTDOWN_NODES_SEC} or
78       L{_SHUTDOWN_INSTANCES}
79   @param names: a list of names; for cluster, it must be empty,
80       and for node and instance it must be a list of valid item
81       names (short names are valid as usual, e.g. node1 instead of
82       node1.example.com)
83   @rtype: list
84   @return: the list of names after the expansion
85   @raise errors.ProgrammerError: for unknown selection type
86   @raise errors.OpPrereqError: for invalid input parameters
87
88   """
89   # pylint: disable-msg=W0142
90
91   if client is None:
92     client = GetClient()
93   if mode == _SHUTDOWN_CLUSTER:
94     if names:
95       raise errors.OpPrereqError("Cluster filter mode takes no arguments",
96                                  errors.ECODE_INVAL)
97     idata = client.QueryInstances([], ["name"], False)
98     inames = [row[0] for row in idata]
99
100   elif mode in (_SHUTDOWN_NODES_BOTH,
101                 _SHUTDOWN_NODES_PRI,
102                 _SHUTDOWN_NODES_SEC) + _SHUTDOWN_NODES_TAGS_MODES:
103     if mode in _SHUTDOWN_NODES_TAGS_MODES:
104       if not names:
105         raise errors.OpPrereqError("No node tags passed", errors.ECODE_INVAL)
106       ndata = client.QueryNodes([], ["name", "pinst_list",
107                                      "sinst_list", "tags"], False)
108       ndata = [row for row in ndata if set(row[3]).intersection(names)]
109     else:
110       if not names:
111         raise errors.OpPrereqError("No node names passed", errors.ECODE_INVAL)
112       ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
113                               False)
114
115     ipri = [row[1] for row in ndata]
116     pri_names = list(itertools.chain(*ipri))
117     isec = [row[2] for row in ndata]
118     sec_names = list(itertools.chain(*isec))
119     if mode in (_SHUTDOWN_NODES_BOTH, _SHUTDOWN_NODES_BOTH_BY_TAGS):
120       inames = pri_names + sec_names
121     elif mode in (_SHUTDOWN_NODES_PRI, _SHUTDOWN_NODES_PRI_BY_TAGS):
122       inames = pri_names
123     elif mode in (_SHUTDOWN_NODES_SEC, _SHUTDOWN_NODES_SEC_BY_TAGS):
124       inames = sec_names
125     else:
126       raise errors.ProgrammerError("Unhandled shutdown type")
127   elif mode == _SHUTDOWN_INSTANCES:
128     if not names:
129       raise errors.OpPrereqError("No instance names passed",
130                                  errors.ECODE_INVAL)
131     idata = client.QueryInstances(names, ["name"], False)
132     inames = [row[0] for row in idata]
133   elif mode == _SHUTDOWN_INSTANCES_BY_TAGS:
134     if not names:
135       raise errors.OpPrereqError("No instance tags passed",
136                                  errors.ECODE_INVAL)
137     idata = client.QueryInstances([], ["name", "tags"], False)
138     inames = [row[0] for row in idata if set(row[1]).intersection(names)]
139   else:
140     raise errors.OpPrereqError("Unknown mode '%s'" % mode, errors.ECODE_INVAL)
141
142   return inames
143
144
145 def _EnsureInstancesExist(client, names):
146   """Check for and ensure the given instance names exist.
147
148   This function will raise an OpPrereqError in case they don't
149   exist. Otherwise it will exit cleanly.
150
151   @type client: L{ganeti.luxi.Client}
152   @param client: the client to use for the query
153   @type names: list
154   @param names: the list of instance names to query
155   @raise errors.OpPrereqError: in case any instance is missing
156
157   """
158   # TODO: change LUInstanceQuery to that it actually returns None
159   # instead of raising an exception, or devise a better mechanism
160   result = client.QueryInstances(names, ["name"], False)
161   for orig_name, row in zip(names, result):
162     if row[0] is None:
163       raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name,
164                                  errors.ECODE_NOENT)
165
166
167 def GenericManyOps(operation, fn):
168   """Generic multi-instance operations.
169
170   The will return a wrapper that processes the options and arguments
171   given, and uses the passed function to build the opcode needed for
172   the specific operation. Thus all the generic loop/confirmation code
173   is abstracted into this function.
174
175   """
176   def realfn(opts, args):
177     if opts.multi_mode is None:
178       opts.multi_mode = _SHUTDOWN_INSTANCES
179     cl = GetClient()
180     inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
181     if not inames:
182       if opts.multi_mode == _SHUTDOWN_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 != _SHUTDOWN_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"],
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-msg=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-msg=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 = _SHUTDOWN_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 != _SHUTDOWN_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   op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
611                                        disks=opts.disks)
612   SubmitOrSend(op, opts)
613   return 0
614
615
616 def GrowDisk(opts, args):
617   """Grow an instance's disks.
618
619   @param opts: the command line options selected by the user
620   @type args: list
621   @param args: should contain two elements, the instance name
622       whose disks we grow and the disk name, e.g. I{sda}
623   @rtype: int
624   @return: the desired exit code
625
626   """
627   instance = args[0]
628   disk = args[1]
629   try:
630     disk = int(disk)
631   except (TypeError, ValueError), err:
632     raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
633                                errors.ECODE_INVAL)
634   amount = utils.ParseUnit(args[2])
635   op = opcodes.OpInstanceGrowDisk(instance_name=instance,
636                                   disk=disk, amount=amount,
637                                   wait_for_sync=opts.wait_for_sync)
638   SubmitOrSend(op, opts)
639   return 0
640
641
642 def _StartupInstance(name, opts):
643   """Startup instances.
644
645   This returns the opcode to start an instance, and its decorator will
646   wrap this into a loop starting all desired instances.
647
648   @param name: the name of the instance to act on
649   @param opts: the command line options selected by the user
650   @return: the opcode needed for the operation
651
652   """
653   op = opcodes.OpInstanceStartup(instance_name=name,
654                                  force=opts.force,
655                                  ignore_offline_nodes=opts.ignore_offline)
656   # do not add these parameters to the opcode unless they're defined
657   if opts.hvparams:
658     op.hvparams = opts.hvparams
659   if opts.beparams:
660     op.beparams = opts.beparams
661   return op
662
663
664 def _RebootInstance(name, opts):
665   """Reboot instance(s).
666
667   This returns the opcode to reboot an instance, and its decorator
668   will wrap this into a loop rebooting all desired instances.
669
670   @param name: the name of the instance to act on
671   @param opts: the command line options selected by the user
672   @return: the opcode needed for the operation
673
674   """
675   return opcodes.OpInstanceReboot(instance_name=name,
676                                   reboot_type=opts.reboot_type,
677                                   ignore_secondaries=opts.ignore_secondaries,
678                                   shutdown_timeout=opts.shutdown_timeout)
679
680
681 def _ShutdownInstance(name, opts):
682   """Shutdown an instance.
683
684   This returns the opcode to shutdown an instance, and its decorator
685   will wrap this into a loop shutting down all desired instances.
686
687   @param name: the name of the instance to act on
688   @param opts: the command line options selected by the user
689   @return: the opcode needed for the operation
690
691   """
692   return opcodes.OpInstanceShutdown(instance_name=name,
693                                     timeout=opts.timeout,
694                                     ignore_offline_nodes=opts.ignore_offline)
695
696
697 def ReplaceDisks(opts, args):
698   """Replace the disks of an instance
699
700   @param opts: the command line options selected by the user
701   @type args: list
702   @param args: should contain only one element, the instance name
703   @rtype: int
704   @return: the desired exit code
705
706   """
707   new_2ndary = opts.dst_node
708   iallocator = opts.iallocator
709   if opts.disks is None:
710     disks = []
711   else:
712     try:
713       disks = [int(i) for i in opts.disks.split(",")]
714     except (TypeError, ValueError), err:
715       raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
716                                  errors.ECODE_INVAL)
717   cnt = [opts.on_primary, opts.on_secondary, opts.auto,
718          new_2ndary is not None, iallocator is not None].count(True)
719   if cnt != 1:
720     raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -i"
721                                " options must be passed", errors.ECODE_INVAL)
722   elif opts.on_primary:
723     mode = constants.REPLACE_DISK_PRI
724   elif opts.on_secondary:
725     mode = constants.REPLACE_DISK_SEC
726   elif opts.auto:
727     mode = constants.REPLACE_DISK_AUTO
728     if disks:
729       raise errors.OpPrereqError("Cannot specify disks when using automatic"
730                                  " mode", errors.ECODE_INVAL)
731   elif new_2ndary is not None or iallocator is not None:
732     # replace secondary
733     mode = constants.REPLACE_DISK_CHG
734
735   op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
736                                       remote_node=new_2ndary, mode=mode,
737                                       iallocator=iallocator,
738                                       early_release=opts.early_release)
739   SubmitOrSend(op, opts)
740   return 0
741
742
743 def FailoverInstance(opts, args):
744   """Failover an instance.
745
746   The failover is done by shutting it down on its present node and
747   starting it on the secondary.
748
749   @param opts: the command line options selected by the user
750   @type args: list
751   @param args: should contain only one element, the instance name
752   @rtype: int
753   @return: the desired exit code
754
755   """
756   cl = GetClient()
757   instance_name = args[0]
758   force = opts.force
759   iallocator = opts.iallocator
760   target_node = opts.dst_node
761
762   if iallocator and target_node:
763     raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
764                                " node (-n) but not both", errors.ECODE_INVAL)
765
766   if not force:
767     _EnsureInstancesExist(cl, [instance_name])
768
769     usertext = ("Failover will happen to image %s."
770                 " This requires a shutdown of the instance. Continue?" %
771                 (instance_name,))
772     if not AskUser(usertext):
773       return 1
774
775   op = opcodes.OpInstanceFailover(instance_name=instance_name,
776                                   ignore_consistency=opts.ignore_consistency,
777                                   shutdown_timeout=opts.shutdown_timeout,
778                                   iallocator=iallocator,
779                                   target_node=target_node)
780   SubmitOrSend(op, opts, cl=cl)
781   return 0
782
783
784 def MigrateInstance(opts, args):
785   """Migrate an instance.
786
787   The migrate is done without shutdown.
788
789   @param opts: the command line options selected by the user
790   @type args: list
791   @param args: should contain only one element, the instance name
792   @rtype: int
793   @return: the desired exit code
794
795   """
796   cl = GetClient()
797   instance_name = args[0]
798   force = opts.force
799   iallocator = opts.iallocator
800   target_node = opts.dst_node
801
802   if iallocator and target_node:
803     raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
804                                " node (-n) but not both", errors.ECODE_INVAL)
805
806   if not force:
807     _EnsureInstancesExist(cl, [instance_name])
808
809     if opts.cleanup:
810       usertext = ("Instance %s will be recovered from a failed migration."
811                   " Note that the migration procedure (including cleanup)" %
812                   (instance_name,))
813     else:
814       usertext = ("Instance %s will be migrated. Note that migration" %
815                   (instance_name,))
816     usertext += (" might impact the instance if anything goes wrong"
817                  " (e.g. due to bugs in the hypervisor). Continue?")
818     if not AskUser(usertext):
819       return 1
820
821   # this should be removed once --non-live is deprecated
822   if not opts.live and opts.migration_mode is not None:
823     raise errors.OpPrereqError("Only one of the --non-live and "
824                                "--migration-mode options can be passed",
825                                errors.ECODE_INVAL)
826   if not opts.live: # --non-live passed
827     mode = constants.HT_MIGRATION_NONLIVE
828   else:
829     mode = opts.migration_mode
830
831   op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
832                                  cleanup=opts.cleanup, iallocator=iallocator,
833                                  target_node=target_node,
834                                  allow_failover=opts.allow_failover)
835   SubmitOpCode(op, cl=cl, opts=opts)
836   return 0
837
838
839 def MoveInstance(opts, args):
840   """Move an instance.
841
842   @param opts: the command line options selected by the user
843   @type args: list
844   @param args: should contain only one element, the instance name
845   @rtype: int
846   @return: the desired exit code
847
848   """
849   cl = GetClient()
850   instance_name = args[0]
851   force = opts.force
852
853   if not force:
854     usertext = ("Instance %s will be moved."
855                 " This requires a shutdown of the instance. Continue?" %
856                 (instance_name,))
857     if not AskUser(usertext):
858       return 1
859
860   op = opcodes.OpInstanceMove(instance_name=instance_name,
861                               target_node=opts.node,
862                               shutdown_timeout=opts.shutdown_timeout)
863   SubmitOrSend(op, opts, cl=cl)
864   return 0
865
866
867 def ConnectToInstanceConsole(opts, args):
868   """Connect to the console of an instance.
869
870   @param opts: the command line options selected by the user
871   @type args: list
872   @param args: should contain only one element, the instance name
873   @rtype: int
874   @return: the desired exit code
875
876   """
877   instance_name = args[0]
878
879   op = opcodes.OpInstanceConsole(instance_name=instance_name)
880
881   cl = GetClient()
882   try:
883     cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
884     console_data = SubmitOpCode(op, opts=opts, cl=cl)
885   finally:
886     # Ensure client connection is closed while external commands are run
887     cl.Close()
888
889   del cl
890
891   return _DoConsole(objects.InstanceConsole.FromDict(console_data),
892                     opts.show_command, cluster_name)
893
894
895 def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
896                _runcmd_fn=utils.RunCmd):
897   """Acts based on the result of L{opcodes.OpInstanceConsole}.
898
899   @type console: L{objects.InstanceConsole}
900   @param console: Console object
901   @type show_command: bool
902   @param show_command: Whether to just display commands
903   @type cluster_name: string
904   @param cluster_name: Cluster name as retrieved from master daemon
905
906   """
907   assert console.Validate()
908
909   if console.kind == constants.CONS_MESSAGE:
910     feedback_fn(console.message)
911   elif console.kind == constants.CONS_VNC:
912     feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
913                 " URL <vnc://%s:%s/>",
914                 console.instance, console.host, console.port,
915                 console.display, console.host, console.port)
916   elif console.kind == constants.CONS_SSH:
917     # Convert to string if not already one
918     if isinstance(console.command, basestring):
919       cmd = console.command
920     else:
921       cmd = utils.ShellQuoteArgs(console.command)
922
923     srun = ssh.SshRunner(cluster_name=cluster_name)
924     ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
925                             batch=True, quiet=False, tty=True)
926
927     if show_command:
928       feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
929     else:
930       result = _runcmd_fn(ssh_cmd, interactive=True)
931       if result.failed:
932         logging.error("Console command \"%s\" failed with reason '%s' and"
933                       " output %r", result.cmd, result.fail_reason,
934                       result.output)
935         raise errors.OpExecError("Connection to console of instance %s failed,"
936                                  " please check cluster configuration" %
937                                  console.instance)
938   else:
939     raise errors.GenericError("Unknown console type '%s'" % console.kind)
940
941   return constants.EXIT_SUCCESS
942
943
944 def _FormatLogicalID(dev_type, logical_id, roman):
945   """Formats the logical_id of a disk.
946
947   """
948   if dev_type == constants.LD_DRBD8:
949     node_a, node_b, port, minor_a, minor_b, key = logical_id
950     data = [
951       ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
952                                                             convert=roman))),
953       ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
954                                                             convert=roman))),
955       ("port", compat.TryToRoman(port, convert=roman)),
956       ("auth key", key),
957       ]
958   elif dev_type == constants.LD_LV:
959     vg_name, lv_name = logical_id
960     data = ["%s/%s" % (vg_name, lv_name)]
961   else:
962     data = [str(logical_id)]
963
964   return data
965
966
967 def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
968   """Show block device information.
969
970   This is only used by L{ShowInstanceConfig}, but it's too big to be
971   left for an inline definition.
972
973   @type idx: int
974   @param idx: the index of the current disk
975   @type top_level: boolean
976   @param top_level: if this a top-level disk?
977   @type dev: dict
978   @param dev: dictionary with disk information
979   @type static: boolean
980   @param static: wheter the device information doesn't contain
981       runtime information but only static data
982   @type roman: boolean
983   @param roman: whether to try to use roman integers
984   @return: a list of either strings, tuples or lists
985       (which should be formatted at a higher indent level)
986
987   """
988   def helper(dtype, status):
989     """Format one line for physical device status.
990
991     @type dtype: str
992     @param dtype: a constant from the L{constants.LDS_BLOCK} set
993     @type status: tuple
994     @param status: a tuple as returned from L{backend.FindBlockDevice}
995     @return: the string representing the status
996
997     """
998     if not status:
999       return "not active"
1000     txt = ""
1001     (path, major, minor, syncp, estt, degr, ldisk_status) = status
1002     if major is None:
1003       major_string = "N/A"
1004     else:
1005       major_string = str(compat.TryToRoman(major, convert=roman))
1006
1007     if minor is None:
1008       minor_string = "N/A"
1009     else:
1010       minor_string = str(compat.TryToRoman(minor, convert=roman))
1011
1012     txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1013     if dtype in (constants.LD_DRBD8, ):
1014       if syncp is not None:
1015         sync_text = "*RECOVERING* %5.2f%%," % syncp
1016         if estt:
1017           sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1018         else:
1019           sync_text += " ETA unknown"
1020       else:
1021         sync_text = "in sync"
1022       if degr:
1023         degr_text = "*DEGRADED*"
1024       else:
1025         degr_text = "ok"
1026       if ldisk_status == constants.LDS_FAULTY:
1027         ldisk_text = " *MISSING DISK*"
1028       elif ldisk_status == constants.LDS_UNKNOWN:
1029         ldisk_text = " *UNCERTAIN STATE*"
1030       else:
1031         ldisk_text = ""
1032       txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1033     elif dtype == constants.LD_LV:
1034       if ldisk_status == constants.LDS_FAULTY:
1035         ldisk_text = " *FAILED* (failed drive?)"
1036       else:
1037         ldisk_text = ""
1038       txt += ldisk_text
1039     return txt
1040
1041   # the header
1042   if top_level:
1043     if dev["iv_name"] is not None:
1044       txt = dev["iv_name"]
1045     else:
1046       txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1047   else:
1048     txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1049   if isinstance(dev["size"], int):
1050     nice_size = utils.FormatUnit(dev["size"], "h")
1051   else:
1052     nice_size = dev["size"]
1053   d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1054   data = []
1055   if top_level:
1056     data.append(("access mode", dev["mode"]))
1057   if dev["logical_id"] is not None:
1058     try:
1059       l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1060     except ValueError:
1061       l_id = [str(dev["logical_id"])]
1062     if len(l_id) == 1:
1063       data.append(("logical_id", l_id[0]))
1064     else:
1065       data.extend(l_id)
1066   elif dev["physical_id"] is not None:
1067     data.append("physical_id:")
1068     data.append([dev["physical_id"]])
1069   if not static:
1070     data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1071   if dev["sstatus"] and not static:
1072     data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1073
1074   if dev["children"]:
1075     data.append("child devices:")
1076     for c_idx, child in enumerate(dev["children"]):
1077       data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
1078   d1.append(data)
1079   return d1
1080
1081
1082 def _FormatList(buf, data, indent_level):
1083   """Formats a list of data at a given indent level.
1084
1085   If the element of the list is:
1086     - a string, it is simply formatted as is
1087     - a tuple, it will be split into key, value and the all the
1088       values in a list will be aligned all at the same start column
1089     - a list, will be recursively formatted
1090
1091   @type buf: StringIO
1092   @param buf: the buffer into which we write the output
1093   @param data: the list to format
1094   @type indent_level: int
1095   @param indent_level: the indent level to format at
1096
1097   """
1098   max_tlen = max([len(elem[0]) for elem in data
1099                  if isinstance(elem, tuple)] or [0])
1100   for elem in data:
1101     if isinstance(elem, basestring):
1102       buf.write("%*s%s\n" % (2*indent_level, "", elem))
1103     elif isinstance(elem, tuple):
1104       key, value = elem
1105       spacer = "%*s" % (max_tlen - len(key), "")
1106       buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1107     elif isinstance(elem, list):
1108       _FormatList(buf, elem, indent_level+1)
1109
1110
1111 def ShowInstanceConfig(opts, args):
1112   """Compute instance run-time status.
1113
1114   @param opts: the command line options selected by the user
1115   @type args: list
1116   @param args: either an empty list, and then we query all
1117       instances, or should contain a list of instance names
1118   @rtype: int
1119   @return: the desired exit code
1120
1121   """
1122   if not args and not opts.show_all:
1123     ToStderr("No instance selected."
1124              " Please pass in --all if you want to query all instances.\n"
1125              "Note that this can take a long time on a big cluster.")
1126     return 1
1127   elif args and opts.show_all:
1128     ToStderr("Cannot use --all if you specify instance names.")
1129     return 1
1130
1131   retcode = 0
1132   op = opcodes.OpInstanceQueryData(instances=args, static=opts.static)
1133   result = SubmitOpCode(op, opts=opts)
1134   if not result:
1135     ToStdout("No instances.")
1136     return 1
1137
1138   buf = StringIO()
1139   retcode = 0
1140   for instance_name in result:
1141     instance = result[instance_name]
1142     buf.write("Instance name: %s\n" % instance["name"])
1143     buf.write("UUID: %s\n" % instance["uuid"])
1144     buf.write("Serial number: %s\n" %
1145               compat.TryToRoman(instance["serial_no"],
1146                                 convert=opts.roman_integers))
1147     buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1148     buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1149     buf.write("State: configured to be %s" % instance["config_state"])
1150     if not opts.static:
1151       buf.write(", actual state is %s" % instance["run_state"])
1152     buf.write("\n")
1153     ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1154     ##          instance["auto_balance"])
1155     buf.write("  Nodes:\n")
1156     buf.write("    - primary: %s\n" % instance["pnode"])
1157     buf.write("    - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1158     buf.write("  Operating system: %s\n" % instance["os"])
1159     FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1160                         level=2)
1161     if instance.has_key("network_port"):
1162       buf.write("  Allocated network port: %s\n" %
1163                 compat.TryToRoman(instance["network_port"],
1164                                   convert=opts.roman_integers))
1165     buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1166
1167     # custom VNC console information
1168     vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1169                                                  None)
1170     if vnc_bind_address:
1171       port = instance["network_port"]
1172       display = int(port) - constants.VNC_BASE_PORT
1173       if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1174         vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1175                                                    port,
1176                                                    display)
1177       elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1178         vnc_console_port = ("%s:%s (node %s) (display %s)" %
1179                              (vnc_bind_address, port,
1180                               instance["pnode"], display))
1181       else:
1182         # vnc bind address is a file
1183         vnc_console_port = "%s:%s" % (instance["pnode"],
1184                                       vnc_bind_address)
1185       buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1186
1187     FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1188                         level=2)
1189     buf.write("  Hardware:\n")
1190     buf.write("    - VCPUs: %s\n" %
1191               compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1192                                 convert=opts.roman_integers))
1193     buf.write("    - memory: %sMiB\n" %
1194               compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
1195                                 convert=opts.roman_integers))
1196     buf.write("    - NICs:\n")
1197     for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1198       buf.write("      - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1199                 (idx, mac, ip, mode, link))
1200     buf.write("  Disk template: %s\n" % instance["disk_template"])
1201     buf.write("  Disks:\n")
1202
1203     for idx, device in enumerate(instance["disks"]):
1204       _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static,
1205                   opts.roman_integers), 2)
1206
1207   ToStdout(buf.getvalue().rstrip('\n'))
1208   return retcode
1209
1210
1211 def SetInstanceParams(opts, args):
1212   """Modifies an instance.
1213
1214   All parameters take effect only at the next restart of the instance.
1215
1216   @param opts: the command line options selected by the user
1217   @type args: list
1218   @param args: should contain only one element, the instance name
1219   @rtype: int
1220   @return: the desired exit code
1221
1222   """
1223   if not (opts.nics or opts.disks or opts.disk_template or
1224           opts.hvparams or opts.beparams or opts.os or opts.osparams):
1225     ToStderr("Please give at least one of the parameters.")
1226     return 1
1227
1228   for param in opts.beparams:
1229     if isinstance(opts.beparams[param], basestring):
1230       if opts.beparams[param].lower() == "default":
1231         opts.beparams[param] = constants.VALUE_DEFAULT
1232
1233   utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1234                       allowed_values=[constants.VALUE_DEFAULT])
1235
1236   for param in opts.hvparams:
1237     if isinstance(opts.hvparams[param], basestring):
1238       if opts.hvparams[param].lower() == "default":
1239         opts.hvparams[param] = constants.VALUE_DEFAULT
1240
1241   utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1242                       allowed_values=[constants.VALUE_DEFAULT])
1243
1244   for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1245     try:
1246       nic_op = int(nic_op)
1247       opts.nics[idx] = (nic_op, nic_dict)
1248     except (TypeError, ValueError):
1249       pass
1250
1251   for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1252     try:
1253       disk_op = int(disk_op)
1254       opts.disks[idx] = (disk_op, disk_dict)
1255     except (TypeError, ValueError):
1256       pass
1257     if disk_op == constants.DDM_ADD:
1258       if 'size' not in disk_dict:
1259         raise errors.OpPrereqError("Missing required parameter 'size'",
1260                                    errors.ECODE_INVAL)
1261       disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1262
1263   if (opts.disk_template and
1264       opts.disk_template in constants.DTS_INT_MIRROR and
1265       not opts.node):
1266     ToStderr("Changing the disk template to a mirrored one requires"
1267              " specifying a secondary node")
1268     return 1
1269
1270   op = opcodes.OpInstanceSetParams(instance_name=args[0],
1271                                    nics=opts.nics,
1272                                    disks=opts.disks,
1273                                    disk_template=opts.disk_template,
1274                                    remote_node=opts.node,
1275                                    hvparams=opts.hvparams,
1276                                    beparams=opts.beparams,
1277                                    os_name=opts.os,
1278                                    osparams=opts.osparams,
1279                                    force_variant=opts.force_variant,
1280                                    force=opts.force)
1281
1282   # even if here we process the result, we allow submit only
1283   result = SubmitOrSend(op, opts)
1284
1285   if result:
1286     ToStdout("Modified instance %s", args[0])
1287     for param, data in result:
1288       ToStdout(" - %-5s -> %s", param, data)
1289     ToStdout("Please don't forget that most parameters take effect"
1290              " only at the next start of the instance.")
1291   return 0
1292
1293
1294 # multi-instance selection options
1295 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1296                            help="Do not ask for confirmation when more than"
1297                            " one instance is affected",
1298                            action="store_true", default=False)
1299
1300 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1301                             help="Filter by nodes (primary only)",
1302                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1303
1304 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1305                             help="Filter by nodes (secondary only)",
1306                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1307
1308 m_node_opt = cli_option("--node", dest="multi_mode",
1309                         help="Filter by nodes (primary and secondary)",
1310                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1311
1312 m_clust_opt = cli_option("--all", dest="multi_mode",
1313                          help="Select all instances in the cluster",
1314                          const=_SHUTDOWN_CLUSTER, action="store_const")
1315
1316 m_inst_opt = cli_option("--instance", dest="multi_mode",
1317                         help="Filter by instance name [default]",
1318                         const=_SHUTDOWN_INSTANCES, action="store_const")
1319
1320 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1321                              help="Filter by node tag",
1322                              const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1323                              action="store_const")
1324
1325 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1326                                  help="Filter by primary node tag",
1327                                  const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1328                                  action="store_const")
1329
1330 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1331                                  help="Filter by secondary node tag",
1332                                  const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1333                                  action="store_const")
1334
1335 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1336                              help="Filter by instance tag",
1337                              const=_SHUTDOWN_INSTANCES_BY_TAGS,
1338                              action="store_const")
1339
1340 # this is defined separately due to readability only
1341 add_opts = [
1342   NOSTART_OPT,
1343   OS_OPT,
1344   FORCE_VARIANT_OPT,
1345   NO_INSTALL_OPT,
1346   ]
1347
1348 commands = {
1349   'add': (
1350     AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1351     "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1352     "Creates and adds a new instance to the cluster"),
1353   'batch-create': (
1354     BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1355     "<instances.json>",
1356     "Create a bunch of instances based on specs in the file."),
1357   'console': (
1358     ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1359     [SHOWCMD_OPT, PRIORITY_OPT],
1360     "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1361   'failover': (
1362     FailoverInstance, ARGS_ONE_INSTANCE,
1363     [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1364      DRY_RUN_OPT, PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT],
1365     "[-f] <instance>", "Stops the instance and starts it on the backup node,"
1366     " using the remote mirror (only for mirrored instances)"),
1367   'migrate': (
1368     MigrateInstance, ARGS_ONE_INSTANCE,
1369     [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1370      PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT, ALLOW_FAILOVER_OPT],
1371     "[-f] <instance>", "Migrate instance to its secondary node"
1372     " (only for mirrored instances)"),
1373   'move': (
1374     MoveInstance, ARGS_ONE_INSTANCE,
1375     [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1376      DRY_RUN_OPT, PRIORITY_OPT],
1377     "[-f] <instance>", "Move instance to an arbitrary node"
1378     " (only for instances of type file and lv)"),
1379   'info': (
1380     ShowInstanceConfig, ARGS_MANY_INSTANCES,
1381     [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1382     "[-s] {--all | <instance>...}",
1383     "Show information on the specified instance(s)"),
1384   'list': (
1385     ListInstances, ARGS_MANY_INSTANCES,
1386     [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
1387      FORCE_FILTER_OPT],
1388     "[<instance>...]",
1389     "Lists the instances and their status. The available fields can be shown"
1390     " using the \"list-fields\" command (see the man page for details)."
1391     " The default field list is (in order): %s." %
1392     utils.CommaJoin(_LIST_DEF_FIELDS),
1393     ),
1394   "list-fields": (
1395     ListInstanceFields, [ArgUnknown()],
1396     [NOHDR_OPT, SEP_OPT],
1397     "[fields...]",
1398     "Lists all available fields for instances"),
1399   'reinstall': (
1400     ReinstallInstance, [ArgInstance()],
1401     [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1402      m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1403      m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1404      SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1405     "[-f] <instance>", "Reinstall a stopped instance"),
1406   'remove': (
1407     RemoveInstance, ARGS_ONE_INSTANCE,
1408     [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1409      DRY_RUN_OPT, PRIORITY_OPT],
1410     "[-f] <instance>", "Shuts down the instance and removes it"),
1411   'rename': (
1412     RenameInstance,
1413     [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1414     [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1415     "<instance> <new_name>", "Rename the instance"),
1416   'replace-disks': (
1417     ReplaceDisks, ARGS_ONE_INSTANCE,
1418     [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1419      NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1420      DRY_RUN_OPT, PRIORITY_OPT],
1421     "[-s|-p|-n NODE|-I NAME] <instance>",
1422     "Replaces all disks for the instance"),
1423   'modify': (
1424     SetInstanceParams, ARGS_ONE_INSTANCE,
1425     [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1426      DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1427      OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1428     "<instance>", "Alters the parameters of an instance"),
1429   'shutdown': (
1430     GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1431     [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1432      m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1433      m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1434      DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1435     "<instance>", "Stops an instance"),
1436   'startup': (
1437     GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1438     [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1439      m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1440      m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1441      BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1442     "<instance>", "Starts an instance"),
1443   'reboot': (
1444     GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1445     [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1446      m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1447      m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1448      m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1449     "<instance>", "Reboots an instance"),
1450   'activate-disks': (
1451     ActivateDisks, ARGS_ONE_INSTANCE,
1452     [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
1453     "<instance>", "Activate an instance's disks"),
1454   'deactivate-disks': (
1455     DeactivateDisks, ARGS_ONE_INSTANCE,
1456     [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1457     "[-f] <instance>", "Deactivate an instance's disks"),
1458   'recreate-disks': (
1459     RecreateDisks, ARGS_ONE_INSTANCE,
1460     [SUBMIT_OPT, DISKIDX_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1461     "<instance>", "Recreate an instance's disks"),
1462   'grow-disk': (
1463     GrowDisk,
1464     [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1465      ArgUnknown(min=1, max=1)],
1466     [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1467     "<instance> <disk> <size>", "Grow an instance's disk"),
1468   'list-tags': (
1469     ListTags, ARGS_ONE_INSTANCE, [PRIORITY_OPT],
1470     "<instance_name>", "List the tags of the given instance"),
1471   'add-tags': (
1472     AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1473     [TAG_SRC_OPT, PRIORITY_OPT],
1474     "<instance_name> tag...", "Add tags to the given instance"),
1475   'remove-tags': (
1476     RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1477     [TAG_SRC_OPT, PRIORITY_OPT],
1478     "<instance_name> tag...", "Remove tags from given instance"),
1479   }
1480
1481 #: dictionary with aliases for commands
1482 aliases = {
1483   'start': 'startup',
1484   'stop': 'shutdown',
1485   }
1486
1487
1488 def Main():
1489   return GenericMain(commands, aliases=aliases,
1490                      override={"tag_type": constants.TAG_INSTANCE})