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