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