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