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