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