Fix issue with tags on instance creation
[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   if opts.node:
611     pnode, snode = SplitNodeOption(opts.node)
612     nodes = [pnode]
613     if snode is not None:
614       nodes.append(snode)
615   else:
616     nodes = []
617
618   op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
619                                        disks=opts.disks,
620                                        nodes=nodes)
621   SubmitOrSend(op, opts)
622   return 0
623
624
625 def GrowDisk(opts, args):
626   """Grow an instance's disks.
627
628   @param opts: the command line options selected by the user
629   @type args: list
630   @param args: should contain two elements, the instance name
631       whose disks we grow and the disk name, e.g. I{sda}
632   @rtype: int
633   @return: the desired exit code
634
635   """
636   instance = args[0]
637   disk = args[1]
638   try:
639     disk = int(disk)
640   except (TypeError, ValueError), err:
641     raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
642                                errors.ECODE_INVAL)
643   amount = utils.ParseUnit(args[2])
644   op = opcodes.OpInstanceGrowDisk(instance_name=instance,
645                                   disk=disk, amount=amount,
646                                   wait_for_sync=opts.wait_for_sync)
647   SubmitOrSend(op, opts)
648   return 0
649
650
651 def _StartupInstance(name, opts):
652   """Startup instances.
653
654   This returns the opcode to start an instance, and its decorator will
655   wrap this into a loop starting all desired instances.
656
657   @param name: the name of the instance to act on
658   @param opts: the command line options selected by the user
659   @return: the opcode needed for the operation
660
661   """
662   op = opcodes.OpInstanceStartup(instance_name=name,
663                                  force=opts.force,
664                                  ignore_offline_nodes=opts.ignore_offline,
665                                  no_remember=opts.no_remember)
666   # do not add these parameters to the opcode unless they're defined
667   if opts.hvparams:
668     op.hvparams = opts.hvparams
669   if opts.beparams:
670     op.beparams = opts.beparams
671   return op
672
673
674 def _RebootInstance(name, opts):
675   """Reboot instance(s).
676
677   This returns the opcode to reboot an instance, and its decorator
678   will wrap this into a loop rebooting all desired instances.
679
680   @param name: the name of the instance to act on
681   @param opts: the command line options selected by the user
682   @return: the opcode needed for the operation
683
684   """
685   return opcodes.OpInstanceReboot(instance_name=name,
686                                   reboot_type=opts.reboot_type,
687                                   ignore_secondaries=opts.ignore_secondaries,
688                                   shutdown_timeout=opts.shutdown_timeout)
689
690
691 def _ShutdownInstance(name, opts):
692   """Shutdown an instance.
693
694   This returns the opcode to shutdown an instance, and its decorator
695   will wrap this into a loop shutting down all desired instances.
696
697   @param name: the name of the instance to act on
698   @param opts: the command line options selected by the user
699   @return: the opcode needed for the operation
700
701   """
702   return opcodes.OpInstanceShutdown(instance_name=name,
703                                     timeout=opts.timeout,
704                                     ignore_offline_nodes=opts.ignore_offline,
705                                     no_remember=opts.no_remember)
706
707
708 def ReplaceDisks(opts, args):
709   """Replace the disks of an instance
710
711   @param opts: the command line options selected by the user
712   @type args: list
713   @param args: should contain only one element, the instance name
714   @rtype: int
715   @return: the desired exit code
716
717   """
718   new_2ndary = opts.dst_node
719   iallocator = opts.iallocator
720   if opts.disks is None:
721     disks = []
722   else:
723     try:
724       disks = [int(i) for i in opts.disks.split(",")]
725     except (TypeError, ValueError), err:
726       raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
727                                  errors.ECODE_INVAL)
728   cnt = [opts.on_primary, opts.on_secondary, opts.auto,
729          new_2ndary is not None, iallocator is not None].count(True)
730   if cnt != 1:
731     raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -I"
732                                " options must be passed", errors.ECODE_INVAL)
733   elif opts.on_primary:
734     mode = constants.REPLACE_DISK_PRI
735   elif opts.on_secondary:
736     mode = constants.REPLACE_DISK_SEC
737   elif opts.auto:
738     mode = constants.REPLACE_DISK_AUTO
739     if disks:
740       raise errors.OpPrereqError("Cannot specify disks when using automatic"
741                                  " mode", errors.ECODE_INVAL)
742   elif new_2ndary is not None or iallocator is not None:
743     # replace secondary
744     mode = constants.REPLACE_DISK_CHG
745
746   op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
747                                       remote_node=new_2ndary, mode=mode,
748                                       iallocator=iallocator,
749                                       early_release=opts.early_release)
750   SubmitOrSend(op, opts)
751   return 0
752
753
754 def FailoverInstance(opts, args):
755   """Failover an instance.
756
757   The failover is done by shutting it down on its present node and
758   starting it on the secondary.
759
760   @param opts: the command line options selected by the user
761   @type args: list
762   @param args: should contain only one element, the instance name
763   @rtype: int
764   @return: the desired exit code
765
766   """
767   cl = GetClient()
768   instance_name = args[0]
769   force = opts.force
770   iallocator = opts.iallocator
771   target_node = opts.dst_node
772
773   if iallocator and target_node:
774     raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
775                                " node (-n) but not both", errors.ECODE_INVAL)
776
777   if not force:
778     _EnsureInstancesExist(cl, [instance_name])
779
780     usertext = ("Failover will happen to image %s."
781                 " This requires a shutdown of the instance. Continue?" %
782                 (instance_name,))
783     if not AskUser(usertext):
784       return 1
785
786   op = opcodes.OpInstanceFailover(instance_name=instance_name,
787                                   ignore_consistency=opts.ignore_consistency,
788                                   shutdown_timeout=opts.shutdown_timeout,
789                                   iallocator=iallocator,
790                                   target_node=target_node)
791   SubmitOrSend(op, opts, cl=cl)
792   return 0
793
794
795 def MigrateInstance(opts, args):
796   """Migrate an instance.
797
798   The migrate is done without shutdown.
799
800   @param opts: the command line options selected by the user
801   @type args: list
802   @param args: should contain only one element, the instance name
803   @rtype: int
804   @return: the desired exit code
805
806   """
807   cl = GetClient()
808   instance_name = args[0]
809   force = opts.force
810   iallocator = opts.iallocator
811   target_node = opts.dst_node
812
813   if iallocator and target_node:
814     raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
815                                " node (-n) but not both", errors.ECODE_INVAL)
816
817   if not force:
818     _EnsureInstancesExist(cl, [instance_name])
819
820     if opts.cleanup:
821       usertext = ("Instance %s will be recovered from a failed migration."
822                   " Note that the migration procedure (including cleanup)" %
823                   (instance_name,))
824     else:
825       usertext = ("Instance %s will be migrated. Note that migration" %
826                   (instance_name,))
827     usertext += (" might impact the instance if anything goes wrong"
828                  " (e.g. due to bugs in the hypervisor). Continue?")
829     if not AskUser(usertext):
830       return 1
831
832   # this should be removed once --non-live is deprecated
833   if not opts.live and opts.migration_mode is not None:
834     raise errors.OpPrereqError("Only one of the --non-live and "
835                                "--migration-mode options can be passed",
836                                errors.ECODE_INVAL)
837   if not opts.live: # --non-live passed
838     mode = constants.HT_MIGRATION_NONLIVE
839   else:
840     mode = opts.migration_mode
841
842   op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
843                                  cleanup=opts.cleanup, iallocator=iallocator,
844                                  target_node=target_node,
845                                  allow_failover=opts.allow_failover)
846   SubmitOpCode(op, cl=cl, opts=opts)
847   return 0
848
849
850 def MoveInstance(opts, args):
851   """Move an instance.
852
853   @param opts: the command line options selected by the user
854   @type args: list
855   @param args: should contain only one element, the instance name
856   @rtype: int
857   @return: the desired exit code
858
859   """
860   cl = GetClient()
861   instance_name = args[0]
862   force = opts.force
863
864   if not force:
865     usertext = ("Instance %s will be moved."
866                 " This requires a shutdown of the instance. Continue?" %
867                 (instance_name,))
868     if not AskUser(usertext):
869       return 1
870
871   op = opcodes.OpInstanceMove(instance_name=instance_name,
872                               target_node=opts.node,
873                               shutdown_timeout=opts.shutdown_timeout,
874                               ignore_consistency=opts.ignore_consistency)
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.OpInstanceConsole(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.OpInstanceConsole}.
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 ShowInstanceConfig(opts, args):
1124   """Compute instance run-time status.
1125
1126   @param opts: the command line options selected by the user
1127   @type args: list
1128   @param args: either an empty list, and then we query all
1129       instances, or should contain a list of instance names
1130   @rtype: int
1131   @return: the desired exit code
1132
1133   """
1134   if not args and not opts.show_all:
1135     ToStderr("No instance selected."
1136              " Please pass in --all if you want to query all instances.\n"
1137              "Note that this can take a long time on a big cluster.")
1138     return 1
1139   elif args and opts.show_all:
1140     ToStderr("Cannot use --all if you specify instance names.")
1141     return 1
1142
1143   retcode = 0
1144   op = opcodes.OpInstanceQueryData(instances=args, static=opts.static,
1145                                    use_locking=not opts.static)
1146   result = SubmitOpCode(op, opts=opts)
1147   if not result:
1148     ToStdout("No instances.")
1149     return 1
1150
1151   buf = StringIO()
1152   retcode = 0
1153   for instance_name in result:
1154     instance = result[instance_name]
1155     buf.write("Instance name: %s\n" % instance["name"])
1156     buf.write("UUID: %s\n" % instance["uuid"])
1157     buf.write("Serial number: %s\n" %
1158               compat.TryToRoman(instance["serial_no"],
1159                                 convert=opts.roman_integers))
1160     buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1161     buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1162     buf.write("State: configured to be %s" % instance["config_state"])
1163     if not opts.static:
1164       buf.write(", actual state is %s" % instance["run_state"])
1165     buf.write("\n")
1166     ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1167     ##          instance["auto_balance"])
1168     buf.write("  Nodes:\n")
1169     buf.write("    - primary: %s\n" % instance["pnode"])
1170     buf.write("    - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1171     buf.write("  Operating system: %s\n" % instance["os"])
1172     FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1173                         level=2)
1174     if instance.has_key("network_port"):
1175       buf.write("  Allocated network port: %s\n" %
1176                 compat.TryToRoman(instance["network_port"],
1177                                   convert=opts.roman_integers))
1178     buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1179
1180     # custom VNC console information
1181     vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1182                                                  None)
1183     if vnc_bind_address:
1184       port = instance["network_port"]
1185       display = int(port) - constants.VNC_BASE_PORT
1186       if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1187         vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1188                                                    port,
1189                                                    display)
1190       elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1191         vnc_console_port = ("%s:%s (node %s) (display %s)" %
1192                              (vnc_bind_address, port,
1193                               instance["pnode"], display))
1194       else:
1195         # vnc bind address is a file
1196         vnc_console_port = "%s:%s" % (instance["pnode"],
1197                                       vnc_bind_address)
1198       buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1199
1200     FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1201                         level=2)
1202     buf.write("  Hardware:\n")
1203     buf.write("    - VCPUs: %s\n" %
1204               compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1205                                 convert=opts.roman_integers))
1206     buf.write("    - memory: %sMiB\n" %
1207               compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
1208                                 convert=opts.roman_integers))
1209     buf.write("    - NICs:\n")
1210     for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1211       buf.write("      - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1212                 (idx, mac, ip, mode, link))
1213     buf.write("  Disk template: %s\n" % instance["disk_template"])
1214     buf.write("  Disks:\n")
1215
1216     for idx, device in enumerate(instance["disks"]):
1217       _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static,
1218                   opts.roman_integers), 2)
1219
1220   ToStdout(buf.getvalue().rstrip('\n'))
1221   return retcode
1222
1223
1224 def SetInstanceParams(opts, args):
1225   """Modifies an instance.
1226
1227   All parameters take effect only at the next restart of the instance.
1228
1229   @param opts: the command line options selected by the user
1230   @type args: list
1231   @param args: should contain only one element, the instance name
1232   @rtype: int
1233   @return: the desired exit code
1234
1235   """
1236   if not (opts.nics or opts.disks or opts.disk_template or
1237           opts.hvparams or opts.beparams or opts.os or opts.osparams):
1238     ToStderr("Please give at least one of the parameters.")
1239     return 1
1240
1241   for param in opts.beparams:
1242     if isinstance(opts.beparams[param], basestring):
1243       if opts.beparams[param].lower() == "default":
1244         opts.beparams[param] = constants.VALUE_DEFAULT
1245
1246   utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1247                       allowed_values=[constants.VALUE_DEFAULT])
1248
1249   for param in opts.hvparams:
1250     if isinstance(opts.hvparams[param], basestring):
1251       if opts.hvparams[param].lower() == "default":
1252         opts.hvparams[param] = constants.VALUE_DEFAULT
1253
1254   utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1255                       allowed_values=[constants.VALUE_DEFAULT])
1256
1257   for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1258     try:
1259       nic_op = int(nic_op)
1260       opts.nics[idx] = (nic_op, nic_dict)
1261     except (TypeError, ValueError):
1262       pass
1263
1264   for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1265     try:
1266       disk_op = int(disk_op)
1267       opts.disks[idx] = (disk_op, disk_dict)
1268     except (TypeError, ValueError):
1269       pass
1270     if disk_op == constants.DDM_ADD:
1271       if 'size' not in disk_dict:
1272         raise errors.OpPrereqError("Missing required parameter 'size'",
1273                                    errors.ECODE_INVAL)
1274       disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1275
1276   if (opts.disk_template and
1277       opts.disk_template in constants.DTS_INT_MIRROR and
1278       not opts.node):
1279     ToStderr("Changing the disk template to a mirrored one requires"
1280              " specifying a secondary node")
1281     return 1
1282
1283   op = opcodes.OpInstanceSetParams(instance_name=args[0],
1284                                    nics=opts.nics,
1285                                    disks=opts.disks,
1286                                    disk_template=opts.disk_template,
1287                                    remote_node=opts.node,
1288                                    hvparams=opts.hvparams,
1289                                    beparams=opts.beparams,
1290                                    os_name=opts.os,
1291                                    osparams=opts.osparams,
1292                                    force_variant=opts.force_variant,
1293                                    force=opts.force,
1294                                    wait_for_sync=opts.wait_for_sync)
1295
1296   # even if here we process the result, we allow submit only
1297   result = SubmitOrSend(op, opts)
1298
1299   if result:
1300     ToStdout("Modified instance %s", args[0])
1301     for param, data in result:
1302       ToStdout(" - %-5s -> %s", param, data)
1303     ToStdout("Please don't forget that most parameters take effect"
1304              " only at the next start of the instance.")
1305   return 0
1306
1307
1308 # multi-instance selection options
1309 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1310                            help="Do not ask for confirmation when more than"
1311                            " one instance is affected",
1312                            action="store_true", default=False)
1313
1314 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1315                             help="Filter by nodes (primary only)",
1316                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1317
1318 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1319                             help="Filter by nodes (secondary only)",
1320                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1321
1322 m_node_opt = cli_option("--node", dest="multi_mode",
1323                         help="Filter by nodes (primary and secondary)",
1324                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1325
1326 m_clust_opt = cli_option("--all", dest="multi_mode",
1327                          help="Select all instances in the cluster",
1328                          const=_SHUTDOWN_CLUSTER, action="store_const")
1329
1330 m_inst_opt = cli_option("--instance", dest="multi_mode",
1331                         help="Filter by instance name [default]",
1332                         const=_SHUTDOWN_INSTANCES, action="store_const")
1333
1334 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1335                              help="Filter by node tag",
1336                              const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1337                              action="store_const")
1338
1339 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1340                                  help="Filter by primary node tag",
1341                                  const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1342                                  action="store_const")
1343
1344 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1345                                  help="Filter by secondary node tag",
1346                                  const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1347                                  action="store_const")
1348
1349 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1350                              help="Filter by instance tag",
1351                              const=_SHUTDOWN_INSTANCES_BY_TAGS,
1352                              action="store_const")
1353
1354 # this is defined separately due to readability only
1355 add_opts = [
1356   NOSTART_OPT,
1357   OS_OPT,
1358   FORCE_VARIANT_OPT,
1359   NO_INSTALL_OPT,
1360   ]
1361
1362 commands = {
1363   'add': (
1364     AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1365     "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1366     "Creates and adds a new instance to the cluster"),
1367   'batch-create': (
1368     BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1369     "<instances.json>",
1370     "Create a bunch of instances based on specs in the file."),
1371   'console': (
1372     ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1373     [SHOWCMD_OPT, PRIORITY_OPT],
1374     "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1375   'failover': (
1376     FailoverInstance, ARGS_ONE_INSTANCE,
1377     [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1378      DRY_RUN_OPT, PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT],
1379     "[-f] <instance>", "Stops the instance and starts it on the backup node,"
1380     " using the remote mirror (only for mirrored instances)"),
1381   'migrate': (
1382     MigrateInstance, ARGS_ONE_INSTANCE,
1383     [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1384      PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT, ALLOW_FAILOVER_OPT],
1385     "[-f] <instance>", "Migrate instance to its secondary node"
1386     " (only for mirrored instances)"),
1387   'move': (
1388     MoveInstance, ARGS_ONE_INSTANCE,
1389     [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1390      DRY_RUN_OPT, PRIORITY_OPT, IGNORE_CONSIST_OPT],
1391     "[-f] <instance>", "Move instance to an arbitrary node"
1392     " (only for instances of type file and lv)"),
1393   'info': (
1394     ShowInstanceConfig, ARGS_MANY_INSTANCES,
1395     [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1396     "[-s] {--all | <instance>...}",
1397     "Show information on the specified instance(s)"),
1398   'list': (
1399     ListInstances, ARGS_MANY_INSTANCES,
1400     [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
1401      FORCE_FILTER_OPT],
1402     "[<instance>...]",
1403     "Lists the instances and their status. The available fields can be shown"
1404     " using the \"list-fields\" command (see the man page for details)."
1405     " The default field list is (in order): %s." %
1406     utils.CommaJoin(_LIST_DEF_FIELDS),
1407     ),
1408   "list-fields": (
1409     ListInstanceFields, [ArgUnknown()],
1410     [NOHDR_OPT, SEP_OPT],
1411     "[fields...]",
1412     "Lists all available fields for instances"),
1413   'reinstall': (
1414     ReinstallInstance, [ArgInstance()],
1415     [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1416      m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1417      m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1418      SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1419     "[-f] <instance>", "Reinstall a stopped instance"),
1420   'remove': (
1421     RemoveInstance, ARGS_ONE_INSTANCE,
1422     [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1423      DRY_RUN_OPT, PRIORITY_OPT],
1424     "[-f] <instance>", "Shuts down the instance and removes it"),
1425   'rename': (
1426     RenameInstance,
1427     [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1428     [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1429     "<instance> <new_name>", "Rename the instance"),
1430   'replace-disks': (
1431     ReplaceDisks, ARGS_ONE_INSTANCE,
1432     [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1433      NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1434      DRY_RUN_OPT, PRIORITY_OPT],
1435     "[-s|-p|-n NODE|-I NAME] <instance>",
1436     "Replaces all disks for the instance"),
1437   'modify': (
1438     SetInstanceParams, ARGS_ONE_INSTANCE,
1439     [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1440      DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1441      OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT, NWSYNC_OPT],
1442     "<instance>", "Alters the parameters of an instance"),
1443   'shutdown': (
1444     GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1445     [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1446      m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1447      m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1448      DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT, NO_REMEMBER_OPT],
1449     "<instance>", "Stops an instance"),
1450   'startup': (
1451     GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1452     [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1453      m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1454      m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1455      BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT,
1456      NO_REMEMBER_OPT],
1457     "<instance>", "Starts an instance"),
1458   'reboot': (
1459     GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1460     [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1461      m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1462      m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1463      m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1464     "<instance>", "Reboots an instance"),
1465   'activate-disks': (
1466     ActivateDisks, ARGS_ONE_INSTANCE,
1467     [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
1468     "<instance>", "Activate an instance's disks"),
1469   'deactivate-disks': (
1470     DeactivateDisks, ARGS_ONE_INSTANCE,
1471     [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1472     "[-f] <instance>", "Deactivate an instance's disks"),
1473   'recreate-disks': (
1474     RecreateDisks, ARGS_ONE_INSTANCE,
1475     [SUBMIT_OPT, DISKIDX_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1476     "<instance>", "Recreate an instance's disks"),
1477   'grow-disk': (
1478     GrowDisk,
1479     [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1480      ArgUnknown(min=1, max=1)],
1481     [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1482     "<instance> <disk> <size>", "Grow an instance's disk"),
1483   'list-tags': (
1484     ListTags, ARGS_ONE_INSTANCE, [PRIORITY_OPT],
1485     "<instance_name>", "List the tags of the given instance"),
1486   'add-tags': (
1487     AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1488     [TAG_SRC_OPT, PRIORITY_OPT],
1489     "<instance_name> tag...", "Add tags to the given instance"),
1490   'remove-tags': (
1491     RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1492     [TAG_SRC_OPT, PRIORITY_OPT],
1493     "<instance_name> tag...", "Remove tags from given instance"),
1494   }
1495
1496 #: dictionary with aliases for commands
1497 aliases = {
1498   'start': 'startup',
1499   'stop': 'shutdown',
1500   }
1501
1502
1503 def Main():
1504   return GenericMain(commands, aliases=aliases,
1505                      override={"tag_type": constants.TAG_INSTANCE})