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