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