Fix gnt-instance modify with beparams
[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 import sys
23 import os
24 import itertools
25 import simplejson
26 from optparse import make_option
27 from cStringIO import StringIO
28
29 from ganeti.cli import *
30 from ganeti import cli
31 from ganeti import opcodes
32 from ganeti import logger
33 from ganeti import constants
34 from ganeti import utils
35 from ganeti import errors
36
37
38 _SHUTDOWN_CLUSTER = "cluster"
39 _SHUTDOWN_NODES_BOTH = "nodes"
40 _SHUTDOWN_NODES_PRI = "nodes-pri"
41 _SHUTDOWN_NODES_SEC = "nodes-sec"
42 _SHUTDOWN_INSTANCES = "instances"
43
44
45 _VALUE_TRUE = "true"
46
47 _LIST_DEF_FIELDS = [
48   "name", "hypervisor", "os", "pnode", "status", "oper_ram",
49   ]
50
51
52 def _ExpandMultiNames(mode, names):
53   """Expand the given names using the passed mode.
54
55   Args:
56     - mode, which can be one of _SHUTDOWN_CLUSTER, _SHUTDOWN_NODES_BOTH,
57       _SHUTDOWN_NODES_PRI, _SHUTDOWN_NODES_SEC or _SHUTDOWN_INSTANCES
58     - names, which is a list of names; for cluster, it must be empty,
59       and for node and instance it must be a list of valid item
60       names (short names are valid as usual, e.g. node1 instead of
61       node1.example.com)
62
63   For _SHUTDOWN_CLUSTER, all instances will be returned. For
64   _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
65   primary/secondary will be shutdown. For _SHUTDOWN_NODES_BOTH, all
66   instances having those nodes as either primary or secondary will be
67   returned. For _SHUTDOWN_INSTANCES, the given instances will be
68   returned.
69
70   """
71   if mode == _SHUTDOWN_CLUSTER:
72     if names:
73       raise errors.OpPrereqError("Cluster filter mode takes no arguments")
74     client = GetClient()
75     idata = client.QueryInstances([], ["name"])
76     inames = [row[0] for row in idata]
77
78   elif mode in (_SHUTDOWN_NODES_BOTH,
79                 _SHUTDOWN_NODES_PRI,
80                 _SHUTDOWN_NODES_SEC):
81     if not names:
82       raise errors.OpPrereqError("No node names passed")
83     client = GetClient()
84     ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"])
85     ipri = [row[1] for row in ndata]
86     pri_names = list(itertools.chain(*ipri))
87     isec = [row[2] for row in ndata]
88     sec_names = list(itertools.chain(*isec))
89     if mode == _SHUTDOWN_NODES_BOTH:
90       inames = pri_names + sec_names
91     elif mode == _SHUTDOWN_NODES_PRI:
92       inames = pri_names
93     elif mode == _SHUTDOWN_NODES_SEC:
94       inames = sec_names
95     else:
96       raise errors.ProgrammerError("Unhandled shutdown type")
97
98   elif mode == _SHUTDOWN_INSTANCES:
99     if not names:
100       raise errors.OpPrereqError("No instance names passed")
101     client = GetClient()
102     idata = client.QueryInstances(names, ["name"])
103     inames = [row[0] for row in idata]
104
105   else:
106     raise errors.OpPrereqError("Unknown mode '%s'" % mode)
107
108   return inames
109
110
111 def _ConfirmOperation(inames, text):
112   """Ask the user to confirm an operation on a list of instances.
113
114   This function is used to request confirmation for doing an operation
115   on a given list of instances.
116
117   The inames argument is what the selection algorithm computed, and
118   the text argument is the operation we should tell the user to
119   confirm (e.g. 'shutdown' or 'startup').
120
121   Returns: boolean depending on user's confirmation.
122
123   """
124   count = len(inames)
125   msg = ("The %s will operate on %d instances.\n"
126          "Do you want to continue?" % (text, count))
127   affected = ("\nAffected instances:\n" +
128               "\n".join(["  %s" % name for name in inames]))
129
130   choices = [('y', True, 'Yes, execute the %s' % text),
131              ('n', False, 'No, abort the %s' % text)]
132
133   if count > 20:
134     choices.insert(1, ('v', 'v', 'View the list of affected instances'))
135     ask = msg
136   else:
137     ask = msg + affected
138
139   choice = AskUser(ask, choices)
140   if choice == 'v':
141     choices.pop(1)
142     choice = AskUser(msg + affected, choices)
143   return choice
144
145
146 def _TransformPath(user_input):
147   """Transform a user path into a canonical value.
148
149   This function transforms the a path passed as textual information
150   into the constants that the LU code expects.
151
152   """
153   if user_input:
154     if user_input.lower() == "default":
155       result_path = constants.VALUE_DEFAULT
156     elif user_input.lower() == "none":
157       result_path = constants.VALUE_NONE
158     else:
159       if not os.path.isabs(user_input):
160         raise errors.OpPrereqError("Path '%s' is not an absolute filename" %
161                                    user_input)
162       result_path = user_input
163   else:
164     result_path = constants.VALUE_DEFAULT
165
166   return result_path
167
168
169 def ListInstances(opts, args):
170   """List instances and their properties.
171
172   """
173   if opts.output is None:
174     selected_fields = _LIST_DEF_FIELDS
175   elif opts.output.startswith("+"):
176     selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
177   else:
178     selected_fields = opts.output.split(",")
179
180   output = GetClient().QueryInstances([], selected_fields)
181
182   if not opts.no_headers:
183     headers = {
184       "name": "Instance", "os": "OS", "pnode": "Primary_node",
185       "snodes": "Secondary_Nodes", "admin_state": "Autostart",
186       "oper_state": "Running",
187       "oper_ram": "Memory", "disk_template": "Disk_template",
188       "ip": "IP_address", "mac": "MAC_address",
189       "bridge": "Bridge",
190       "sda_size": "Disk/0", "sdb_size": "Disk/1",
191       "status": "Status", "tags": "Tags",
192       "network_port": "Network_port",
193       "hv/kernel_path": "Kernel_path",
194       "hv/initrd_path": "Initrd_path",
195       "hv/boot_order": "HVM_boot_order",
196       "hv/acpi": "HVM_ACPI",
197       "hv/pae": "HVM_PAE",
198       "hv/cdrom_image_path": "HVM_CDROM_image_path",
199       "hv/nic_type": "HVM_NIC_type",
200       "hv/disk_type": "HVM_Disk_type",
201       "hv/vnc_bind_address": "VNC_bind_address",
202       "serial_no": "SerialNo", "hypervisor": "Hypervisor",
203       "hvparams": "Hypervisor_parameters",
204       "be/memory": "Configured_memory",
205       "be/vcpus": "VCPUs",
206       "be/auto_balance": "Auto_balance",
207       }
208   else:
209     headers = None
210
211   if opts.human_readable:
212     unitfields = ["be/memory", "oper_ram", "sda_size", "sdb_size"]
213   else:
214     unitfields = None
215
216   numfields = ["be/memory", "oper_ram", "sda_size", "sdb_size", "be/vcpus",
217                "serial_no"]
218
219   list_type_fields = ("tags",)
220   # change raw values to nicer strings
221   for row in output:
222     for idx, field in enumerate(selected_fields):
223       val = row[idx]
224       if field == "snodes":
225         val = ",".join(val) or "-"
226       elif field == "admin_state":
227         if val:
228           val = "yes"
229         else:
230           val = "no"
231       elif field == "oper_state":
232         if val is None:
233           val = "(node down)"
234         elif val: # True
235           val = "running"
236         else:
237           val = "stopped"
238       elif field == "oper_ram":
239         if val is None:
240           val = "(node down)"
241       elif field == "sda_size" or field == "sdb_size":
242         if val is None:
243           val = "N/A"
244       elif field in list_type_fields:
245         val = ",".join(val)
246       elif val is None:
247         val = "-"
248       row[idx] = str(val)
249
250   data = GenerateTable(separator=opts.separator, headers=headers,
251                        fields=selected_fields, unitfields=unitfields,
252                        numfields=numfields, data=output)
253
254   for line in data:
255     logger.ToStdout(line)
256
257   return 0
258
259
260 def AddInstance(opts, args):
261   """Add an instance to the cluster.
262
263   Args:
264     opts - class with options as members
265     args - list with a single element, the instance name
266   Opts used:
267     mem - amount of memory to allocate to instance (MiB)
268     size - amount of disk space to allocate to instance (MiB)
269     os - which OS to run on instance
270     node - node to run new instance on
271
272   """
273   instance = args[0]
274
275   (pnode, snode) = SplitNodeOption(opts.node)
276
277   hypervisor = None
278   hvparams = {}
279   if opts.hypervisor:
280     hypervisor, hvparams = opts.hypervisor
281
282   ValidateBeParams(opts.beparams)
283
284 ##  kernel_path = _TransformPath(opts.kernel_path)
285 ##  initrd_path = _TransformPath(opts.initrd_path)
286
287 ##  hvm_acpi = opts.hvm_acpi == _VALUE_TRUE
288 ##  hvm_pae = opts.hvm_pae == _VALUE_TRUE
289
290 ##  if ((opts.hvm_cdrom_image_path is not None) and
291 ##      (opts.hvm_cdrom_image_path.lower() == constants.VALUE_NONE)):
292 ##    hvm_cdrom_image_path = None
293 ##  else:
294 ##    hvm_cdrom_image_path = opts.hvm_cdrom_image_path
295
296   op = opcodes.OpCreateInstance(instance_name=instance,
297                                 disk_size=opts.size, swap_size=opts.swap,
298                                 disk_template=opts.disk_template,
299                                 mode=constants.INSTANCE_CREATE,
300                                 os_type=opts.os, pnode=pnode,
301                                 snode=snode,
302                                 ip=opts.ip, bridge=opts.bridge,
303                                 start=opts.start, ip_check=opts.ip_check,
304                                 wait_for_sync=opts.wait_for_sync,
305                                 mac=opts.mac,
306                                 hypervisor=hypervisor,
307                                 hvparams=hvparams,
308                                 beparams=opts.beparams,
309                                 iallocator=opts.iallocator,
310                                 file_storage_dir=opts.file_storage_dir,
311                                 file_driver=opts.file_driver,
312                                 )
313
314   SubmitOrSend(op, opts)
315   return 0
316
317
318 def BatchCreate(opts, args):
319   """Create instances on a batched base.
320
321   This function reads a json with instances defined in the form:
322
323   {"instance-name": {"disk_size": 25,
324                      "swap_size": 1024,
325                      "template": "drbd",
326                      "backend": { "memory": 512,
327                                   "vcpus": 1 },
328                      "os": "etch-image",
329                      "primary_node": "firstnode",
330                      "secondary_node": "secondnode",
331                      "iallocator": "dumb"}}
332
333   primary_node and secondary_node has precedence over iallocator.
334
335   Args:
336     opts: The parsed command line options
337     args: Argument passed to the command in our case the json file
338
339   """
340   _DEFAULT_SPECS = {"disk_size": 20 * 1024,
341                     "swap_size": 4 * 1024,
342                     "backend": {},
343                     "iallocator": None,
344                     "primary_node": None,
345                     "secondary_node": None,
346                     "ip": 'none',
347                     "mac": 'auto',
348                     "bridge": None,
349                     "start": True,
350                     "ip_check": True,
351                     "hypervisor": None,
352                     "file_storage_dir": None,
353                     "file_driver": 'loop'}
354
355   def _PopulateWithDefaults(spec):
356     """Returns a new hash combined with default values."""
357     dict = _DEFAULT_SPECS.copy()
358     dict.update(spec)
359     return dict
360
361   def _Validate(spec):
362     """Validate the instance specs."""
363     # Validate fields required under any circumstances
364     for required_field in ('os', 'template'):
365       if required_field not in spec:
366         raise errors.OpPrereqError('Required field "%s" is missing.' %
367                                    required_field)
368     # Validate special fields
369     if spec['primary_node'] is not None:
370       if (spec['template'] in constants.DTS_NET_MIRROR and
371           spec['secondary_node'] is None):
372         raise errors.OpPrereqError('Template requires secondary node, but'
373                                    ' there was no secondary provided.')
374     elif spec['iallocator'] is None:
375       raise errors.OpPrereqError('You have to provide at least a primary_node'
376                                  ' or an iallocator.')
377
378     if (spec['hypervisor'] and
379         not isinstance(spec['hypervisor'], dict)):
380       raise errors.OpPrereqError('Hypervisor parameters must be a dict.')
381
382   json_filename = args[0]
383   fd = open(json_filename, 'r')
384   try:
385     instance_data = simplejson.load(fd)
386   finally:
387     fd.close()
388
389   # Iterate over the instances and do:
390   #  * Populate the specs with default value
391   #  * Validate the instance specs
392   for (name, specs) in instance_data.iteritems():
393     specs = _PopulateWithDefaults(specs)
394     _Validate(specs)
395
396     hypervisor = None
397     hvparams = {}
398     if specs['hypervisor']:
399       hypervisor, hvparams = specs['hypervisor'].iteritems()
400
401     op = opcodes.OpCreateInstance(instance_name=name,
402                                   disk_size=specs['disk_size'],
403                                   swap_size=specs['swap_size'],
404                                   disk_template=specs['template'],
405                                   mode=constants.INSTANCE_CREATE,
406                                   os_type=specs['os'],
407                                   pnode=specs['primary_node'],
408                                   snode=specs['secondary_node'],
409                                   ip=specs['ip'], bridge=specs['bridge'],
410                                   start=specs['start'],
411                                   ip_check=specs['ip_check'],
412                                   wait_for_sync=True,
413                                   mac=specs['mac'],
414                                   iallocator=specs['iallocator'],
415                                   hypervisor=hypervisor,
416                                   hvparams=hvparams,
417                                   beparams=specs['backend'],
418                                   file_storage_dir=specs['file_storage_dir'],
419                                   file_driver=specs['file_driver'])
420
421     print '%s: %s' % (name, cli.SendJob([op]))
422
423   return 0
424
425
426 def ReinstallInstance(opts, args):
427   """Reinstall an instance.
428
429   Args:
430     opts - class with options as members
431     args - list containing a single element, the instance name
432
433   """
434   instance_name = args[0]
435
436   if opts.select_os is True:
437     op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
438     result = SubmitOpCode(op)
439
440     if not result:
441       logger.ToStdout("Can't get the OS list")
442       return 1
443
444     logger.ToStdout("Available OS templates:")
445     number = 0
446     choices = []
447     for entry in result:
448       logger.ToStdout("%3s: %s" % (number, entry[0]))
449       choices.append(("%s" % number, entry[0], entry[0]))
450       number = number + 1
451
452     choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
453     selected = AskUser("Enter OS template name or number (or x to abort):",
454                        choices)
455
456     if selected == 'exit':
457       logger.ToStdout("User aborted reinstall, exiting")
458       return 1
459
460     os = selected
461   else:
462     os = opts.os
463
464   if not opts.force:
465     usertext = ("This will reinstall the instance %s and remove"
466                 " all data. Continue?") % instance_name
467     if not AskUser(usertext):
468       return 1
469
470   op = opcodes.OpReinstallInstance(instance_name=instance_name,
471                                    os_type=os)
472   SubmitOrSend(op, opts)
473
474   return 0
475
476
477 def RemoveInstance(opts, args):
478   """Remove an instance.
479
480   Args:
481     opts - class with options as members
482     args - list containing a single element, the instance name
483
484   """
485   instance_name = args[0]
486   force = opts.force
487
488   if not force:
489     usertext = ("This will remove the volumes of the instance %s"
490                 " (including mirrors), thus removing all the data"
491                 " of the instance. Continue?") % instance_name
492     if not AskUser(usertext):
493       return 1
494
495   op = opcodes.OpRemoveInstance(instance_name=instance_name,
496                                 ignore_failures=opts.ignore_failures)
497   SubmitOrSend(op, opts)
498   return 0
499
500
501 def RenameInstance(opts, args):
502   """Rename an instance.
503
504   Args:
505     opts - class with options as members
506     args - list containing two elements, the instance name and the new name
507
508   """
509   op = opcodes.OpRenameInstance(instance_name=args[0],
510                                 new_name=args[1],
511                                 ignore_ip=opts.ignore_ip)
512   SubmitOrSend(op, opts)
513   return 0
514
515
516 def ActivateDisks(opts, args):
517   """Activate an instance's disks.
518
519   This serves two purposes:
520     - it allows one (as long as the instance is not running) to mount
521     the disks and modify them from the node
522     - it repairs inactive secondary drbds
523
524   """
525   instance_name = args[0]
526   op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
527   disks_info = SubmitOrSend(op, opts)
528   for host, iname, nname in disks_info:
529     print "%s:%s:%s" % (host, iname, nname)
530   return 0
531
532
533 def DeactivateDisks(opts, args):
534   """Command-line interface for _ShutdownInstanceBlockDevices.
535
536   This function takes the instance name, looks for its primary node
537   and the tries to shutdown its block devices on that node.
538
539   """
540   instance_name = args[0]
541   op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
542   SubmitOrSend(op, opts)
543   return 0
544
545
546 def GrowDisk(opts, args):
547   """Command-line interface for _ShutdownInstanceBlockDevices.
548
549   This function takes the instance name, looks for its primary node
550   and the tries to shutdown its block devices on that node.
551
552   """
553   instance = args[0]
554   disk = args[1]
555   amount = utils.ParseUnit(args[2])
556   op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
557                           wait_for_sync=opts.wait_for_sync)
558   SubmitOrSend(op, opts)
559   return 0
560
561
562 def StartupInstance(opts, args):
563   """Startup an instance.
564
565   Args:
566     opts - class with options as members
567     args - list containing a single element, the instance name
568
569   """
570   if opts.multi_mode is None:
571     opts.multi_mode = _SHUTDOWN_INSTANCES
572   inames = _ExpandMultiNames(opts.multi_mode, args)
573   if not inames:
574     raise errors.OpPrereqError("Selection filter does not match any instances")
575   multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
576   if not (opts.force_multi or not multi_on
577           or _ConfirmOperation(inames, "startup")):
578     return 1
579   for name in inames:
580     op = opcodes.OpStartupInstance(instance_name=name,
581                                    force=opts.force,
582                                    extra_args=opts.extra_args)
583     if multi_on:
584       logger.ToStdout("Starting up %s" % name)
585     try:
586       SubmitOrSend(op, opts)
587     except JobSubmittedException, err:
588       _, txt = FormatError(err)
589       logger.ToStdout("%s" % txt)
590   return 0
591
592
593 def RebootInstance(opts, args):
594   """Reboot an instance
595
596   Args:
597     opts - class with options as members
598     args - list containing a single element, the instance name
599
600   """
601   if opts.multi_mode is None:
602     opts.multi_mode = _SHUTDOWN_INSTANCES
603   inames = _ExpandMultiNames(opts.multi_mode, args)
604   if not inames:
605     raise errors.OpPrereqError("Selection filter does not match any instances")
606   multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
607   if not (opts.force_multi or not multi_on
608           or _ConfirmOperation(inames, "reboot")):
609     return 1
610   for name in inames:
611     op = opcodes.OpRebootInstance(instance_name=name,
612                                   reboot_type=opts.reboot_type,
613                                   ignore_secondaries=opts.ignore_secondaries)
614
615     SubmitOrSend(op, opts)
616   return 0
617
618
619 def ShutdownInstance(opts, args):
620   """Shutdown an instance.
621
622   Args:
623     opts - class with options as members
624     args - list containing a single element, the instance name
625
626   """
627   if opts.multi_mode is None:
628     opts.multi_mode = _SHUTDOWN_INSTANCES
629   inames = _ExpandMultiNames(opts.multi_mode, args)
630   if not inames:
631     raise errors.OpPrereqError("Selection filter does not match any instances")
632   multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
633   if not (opts.force_multi or not multi_on
634           or _ConfirmOperation(inames, "shutdown")):
635     return 1
636   for name in inames:
637     op = opcodes.OpShutdownInstance(instance_name=name)
638     if multi_on:
639       logger.ToStdout("Shutting down %s" % name)
640     try:
641       SubmitOrSend(op, opts)
642     except JobSubmittedException, err:
643       _, txt = FormatError(err)
644       logger.ToStdout("%s" % txt)
645   return 0
646
647
648 def ReplaceDisks(opts, args):
649   """Replace the disks of an instance
650
651   Args:
652     opts - class with options as members
653     args - list with a single element, the instance name
654
655   """
656   instance_name = args[0]
657   new_2ndary = opts.new_secondary
658   iallocator = opts.iallocator
659   if opts.disks is None:
660     disks = ["sda", "sdb"]
661   else:
662     disks = opts.disks.split(",")
663   if opts.on_primary == opts.on_secondary: # no -p or -s passed, or both passed
664     mode = constants.REPLACE_DISK_ALL
665   elif opts.on_primary: # only on primary:
666     mode = constants.REPLACE_DISK_PRI
667     if new_2ndary is not None or iallocator is not None:
668       raise errors.OpPrereqError("Can't change secondary node on primary disk"
669                                  " replacement")
670   elif opts.on_secondary is not None or iallocator is not None:
671     # only on secondary
672     mode = constants.REPLACE_DISK_SEC
673
674   op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
675                               remote_node=new_2ndary, mode=mode,
676                               iallocator=iallocator)
677   SubmitOrSend(op, opts)
678   return 0
679
680
681 def FailoverInstance(opts, args):
682   """Failover an instance.
683
684   The failover is done by shutting it down on its present node and
685   starting it on the secondary.
686
687   Args:
688     opts - class with options as members
689     args - list with a single element, the instance name
690   Opts used:
691     force - whether to failover without asking questions.
692
693   """
694   instance_name = args[0]
695   force = opts.force
696
697   if not force:
698     usertext = ("Failover will happen to image %s."
699                 " This requires a shutdown of the instance. Continue?" %
700                 (instance_name,))
701     if not AskUser(usertext):
702       return 1
703
704   op = opcodes.OpFailoverInstance(instance_name=instance_name,
705                                   ignore_consistency=opts.ignore_consistency)
706   SubmitOrSend(op, opts)
707   return 0
708
709
710 def ConnectToInstanceConsole(opts, args):
711   """Connect to the console of an instance.
712
713   Args:
714     opts - class with options as members
715     args - list with a single element, the instance name
716
717   """
718   instance_name = args[0]
719
720   op = opcodes.OpConnectConsole(instance_name=instance_name)
721   cmd = SubmitOpCode(op)
722
723   if opts.show_command:
724     print utils.ShellQuoteArgs(cmd)
725   else:
726     try:
727       os.execvp(cmd[0], cmd)
728     finally:
729       sys.stderr.write("Can't run console command %s with arguments:\n'%s'" %
730                        (cmd, " ".join(argv)))
731       os._exit(1)
732
733
734 def _FormatBlockDevInfo(buf, dev, indent_level, static):
735   """Show block device information.
736
737   This is only used by ShowInstanceConfig(), but it's too big to be
738   left for an inline definition.
739
740   """
741   def helper(buf, dtype, status):
742     """Format one line for physical device status."""
743     if not status:
744       buf.write("not active\n")
745     else:
746       (path, major, minor, syncp, estt, degr, ldisk) = status
747       if major is None:
748         major_string = "N/A"
749       else:
750         major_string = str(major)
751
752       if minor is None:
753         minor_string = "N/A"
754       else:
755         minor_string = str(minor)
756
757       buf.write("%s (%s:%s)" % (path, major_string, minor_string))
758       if dtype in (constants.LD_DRBD8, ):
759         if syncp is not None:
760           sync_text = "*RECOVERING* %5.2f%%," % syncp
761           if estt:
762             sync_text += " ETA %ds" % estt
763           else:
764             sync_text += " ETA unknown"
765         else:
766           sync_text = "in sync"
767         if degr:
768           degr_text = "*DEGRADED*"
769         else:
770           degr_text = "ok"
771         if ldisk:
772           ldisk_text = " *MISSING DISK*"
773         else:
774           ldisk_text = ""
775         buf.write(" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
776       elif dtype == constants.LD_LV:
777         if ldisk:
778           ldisk_text = " *FAILED* (failed drive?)"
779         else:
780           ldisk_text = ""
781         buf.write(ldisk_text)
782       buf.write("\n")
783
784   if dev["iv_name"] is not None:
785     data = "  - %s, " % dev["iv_name"]
786   else:
787     data = "  - "
788   data += "type: %s" % dev["dev_type"]
789   if dev["logical_id"] is not None:
790     data += ", logical_id: %s" % (dev["logical_id"],)
791   elif dev["physical_id"] is not None:
792     data += ", physical_id: %s" % (dev["physical_id"],)
793   buf.write("%*s%s\n" % (2*indent_level, "", data))
794   if not static:
795     buf.write("%*s    primary:   " % (2*indent_level, ""))
796     helper(buf, dev["dev_type"], dev["pstatus"])
797
798   if dev["sstatus"] and not static:
799     buf.write("%*s    secondary: " % (2*indent_level, ""))
800     helper(buf, dev["dev_type"], dev["sstatus"])
801
802   if dev["children"]:
803     for child in dev["children"]:
804       _FormatBlockDevInfo(buf, child, indent_level+1, static)
805
806
807 def ShowInstanceConfig(opts, args):
808   """Compute instance run-time status.
809
810   """
811   retcode = 0
812   op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
813   result = SubmitOpCode(op)
814   if not result:
815     logger.ToStdout("No instances.")
816     return 1
817
818   buf = StringIO()
819   retcode = 0
820   for instance_name in result:
821     instance = result[instance_name]
822     buf.write("Instance name: %s\n" % instance["name"])
823     buf.write("State: configured to be %s" % instance["config_state"])
824     if not opts.static:
825       buf.write(", actual state is %s" % instance["run_state"])
826     buf.write("\n")
827     ##buf.write("Considered for memory checks in cluster verify: %s\n" %
828     ##          instance["auto_balance"])
829     buf.write("  Nodes:\n")
830     buf.write("    - primary: %s\n" % instance["pnode"])
831     buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
832     buf.write("  Operating system: %s\n" % instance["os"])
833     if instance.has_key("network_port"):
834       buf.write("  Allocated network port: %s\n" % instance["network_port"])
835     buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
836     if instance["hypervisor"] == constants.HT_XEN_PVM:
837       hvattrs = ((constants.HV_KERNEL_PATH, "kernel path"),
838                  (constants.HV_INITRD_PATH, "initrd path"))
839     elif instance["hypervisor"] == constants.HT_XEN_HVM:
840       hvattrs = ((constants.HV_BOOT_ORDER, "boot order"),
841                  (constants.HV_ACPI, "ACPI"),
842                  (constants.HV_PAE, "PAE"),
843                  (constants.HV_CDROM_IMAGE_PATH, "virtual CDROM"),
844                  (constants.HV_NIC_TYPE, "NIC type"),
845                  (constants.HV_DISK_TYPE, "Disk type"),
846                  (constants.HV_VNC_BIND_ADDRESS, "VNC bind address"),
847                  )
848       # custom console information for HVM
849       vnc_bind_address = instance["hv_actual"][constants.HV_VNC_BIND_ADDRESS]
850       if vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
851         vnc_console_port = "%s:%s" % (instance["pnode"],
852                                       instance["network_port"])
853       elif vnc_bind_address == constants.LOCALHOST_IP_ADDRESS:
854         vnc_console_port = "%s:%s on node %s" % (vnc_bind_address,
855                                                  instance["network_port"],
856                                                  instance["pnode"])
857       else:
858         vnc_console_port = "%s:%s" % (vnc_bind_address,
859                                       instance["network_port"])
860       buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
861
862     else:
863       # auto-handle other hypervisor types
864       hvattrs = [(key, key) for key in instance["hv_actual"]]
865
866     for key, desc in hvattrs:
867       if key in instance["hv_instance"]:
868         val = instance["hv_instance"][key]
869       else:
870         val = "default (%s)" % instance["hv_actual"][key]
871       buf.write("    - %s: %s\n" % (desc, val))
872     buf.write("  Hardware:\n")
873     buf.write("    - VCPUs: %d\n" %
874               instance["be_actual"][constants.BE_VCPUS])
875     buf.write("    - memory: %dMiB\n" %
876               instance["be_actual"][constants.BE_MEMORY])
877     buf.write("    - NICs: %s\n" %
878               ", ".join(["{MAC: %s, IP: %s, bridge: %s}" %
879                          (mac, ip, bridge)
880                          for mac, ip, bridge in instance["nics"]]))
881     buf.write("  Block devices:\n")
882
883     for device in instance["disks"]:
884       _FormatBlockDevInfo(buf, device, 1, opts.static)
885
886   logger.ToStdout(buf.getvalue().rstrip('\n'))
887   return retcode
888
889
890 def SetInstanceParams(opts, args):
891   """Modifies an instance.
892
893   All parameters take effect only at the next restart of the instance.
894
895   Args:
896     opts - class with options as members
897     args - list with a single element, the instance name
898   Opts used:
899     mac - the new MAC address of the instance
900
901   """
902   if not (opts.ip or opts.bridge or opts.mac or
903           opts.hypervisor or opts.beparams):
904     logger.ToStdout("Please give at least one of the parameters.")
905     return 1
906
907   if constants.BE_MEMORY in opts.beparams:
908     opts.beparams[constants.BE_MEMORY] = utils.ParseUnit(
909       opts.beparams[constants.BE_MEMORY])
910
911   op = opcodes.OpSetInstanceParams(instance_name=args[0],
912                                    ip=opts.ip,
913                                    bridge=opts.bridge, mac=opts.mac,
914                                    hvparams=opts.hypervisor,
915                                    beparams=opts.beparams,
916                                    force=opts.force)
917
918   # even if here we process the result, we allow submit only
919   result = SubmitOrSend(op, opts)
920
921   if result:
922     logger.ToStdout("Modified instance %s" % args[0])
923     for param, data in result:
924       logger.ToStdout(" - %-5s -> %s" % (param, data))
925     logger.ToStdout("Please don't forget that these parameters take effect"
926                     " only at the next start of the instance.")
927   return 0
928
929
930 # options used in more than one cmd
931 node_opt = make_option("-n", "--node", dest="node", help="Target node",
932                        metavar="<node>")
933
934 os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
935                     metavar="<os>")
936
937 # multi-instance selection options
938 m_force_multi = make_option("--force-multiple", dest="force_multi",
939                             help="Do not ask for confirmation when more than"
940                             " one instance is affected",
941                             action="store_true", default=False)
942
943 m_pri_node_opt = make_option("--primary", dest="multi_mode",
944                              help="Filter by nodes (primary only)",
945                              const=_SHUTDOWN_NODES_PRI, action="store_const")
946
947 m_sec_node_opt = make_option("--secondary", dest="multi_mode",
948                              help="Filter by nodes (secondary only)",
949                              const=_SHUTDOWN_NODES_SEC, action="store_const")
950
951 m_node_opt = make_option("--node", dest="multi_mode",
952                          help="Filter by nodes (primary and secondary)",
953                          const=_SHUTDOWN_NODES_BOTH, action="store_const")
954
955 m_clust_opt = make_option("--all", dest="multi_mode",
956                           help="Select all instances in the cluster",
957                           const=_SHUTDOWN_CLUSTER, action="store_const")
958
959 m_inst_opt = make_option("--instance", dest="multi_mode",
960                          help="Filter by instance name [default]",
961                          const=_SHUTDOWN_INSTANCES, action="store_const")
962
963
964 # this is defined separately due to readability only
965 add_opts = [
966   DEBUG_OPT,
967   make_option("-n", "--node", dest="node",
968               help="Target node and optional secondary node",
969               metavar="<pnode>[:<snode>]"),
970   cli_option("-s", "--os-size", dest="size", help="Disk size, in MiB unless"
971              " a suffix is used",
972              default=20 * 1024, type="unit", metavar="<size>"),
973   cli_option("--swap-size", dest="swap", help="Swap size, in MiB unless a"
974              " suffix is used",
975              default=4 * 1024, type="unit", metavar="<size>"),
976   os_opt,
977   keyval_option("-B", "--backend", dest="beparams",
978                 type="keyval", default={},
979                 help="Backend parameters"),
980   make_option("-t", "--disk-template", dest="disk_template",
981               help="Custom disk setup (diskless, file, plain or drbd)",
982               default=None, metavar="TEMPL"),
983   make_option("-i", "--ip", dest="ip",
984               help="IP address ('none' [default], 'auto', or specify address)",
985               default='none', type="string", metavar="<ADDRESS>"),
986   make_option("--mac", dest="mac",
987               help="MAC address ('auto' [default], or specify address)",
988               default='auto', type="string", metavar="<MACADDRESS>"),
989   make_option("--no-wait-for-sync", dest="wait_for_sync", default=True,
990               action="store_false", help="Don't wait for sync (DANGEROUS!)"),
991   make_option("-b", "--bridge", dest="bridge",
992               help="Bridge to connect this instance to",
993               default=None, metavar="<bridge>"),
994   make_option("--no-start", dest="start", default=True,
995               action="store_false", help="Don't start the instance after"
996               " creation"),
997   make_option("--no-ip-check", dest="ip_check", default=True,
998               action="store_false", help="Don't check that the instance's IP"
999               " is alive (only valid with --no-start)"),
1000   make_option("--file-storage-dir", dest="file_storage_dir",
1001               help="Relative path under default cluster-wide file storage dir"
1002               " to store file-based disks", default=None,
1003               metavar="<DIR>"),
1004   make_option("--file-driver", dest="file_driver", help="Driver to use"
1005               " for image files", default="loop", metavar="<DRIVER>"),
1006   make_option("--iallocator", metavar="<NAME>",
1007               help="Select nodes for the instance automatically using the"
1008               " <NAME> iallocator plugin", default=None, type="string"),
1009   ikv_option("-H", "--hypervisor", dest="hypervisor",
1010               help="Hypervisor and hypervisor options, in the format"
1011               " hypervisor:option=value,option=value,...", default=None,
1012               type="identkeyval"),
1013   SUBMIT_OPT,
1014   ]
1015
1016 commands = {
1017   'add': (AddInstance, ARGS_ONE, add_opts,
1018           "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1019           "Creates and adds a new instance to the cluster"),
1020   'batch-create': (BatchCreate, ARGS_ONE,
1021                    [DEBUG_OPT],
1022                    "<instances_file.json>",
1023                    "Create a bunch of instances based on specs in the file."),
1024   'console': (ConnectToInstanceConsole, ARGS_ONE,
1025               [DEBUG_OPT,
1026                make_option("--show-cmd", dest="show_command",
1027                            action="store_true", default=False,
1028                            help=("Show command instead of executing it"))],
1029               "[--show-cmd] <instance>",
1030               "Opens a console on the specified instance"),
1031   'failover': (FailoverInstance, ARGS_ONE,
1032                [DEBUG_OPT, FORCE_OPT,
1033                 make_option("--ignore-consistency", dest="ignore_consistency",
1034                             action="store_true", default=False,
1035                             help="Ignore the consistency of the disks on"
1036                             " the secondary"),
1037                 SUBMIT_OPT,
1038                 ],
1039                "[-f] <instance>",
1040                "Stops the instance and starts it on the backup node, using"
1041                " the remote mirror (only for instances of type drbd)"),
1042   'info': (ShowInstanceConfig, ARGS_ANY,
1043            [DEBUG_OPT,
1044             make_option("-s", "--static", dest="static",
1045                         action="store_true", default=False,
1046                         help="Only show configuration data, not runtime data"),
1047             ], "[-s] [<instance>...]",
1048            "Show information on the specified instance(s)"),
1049   'list': (ListInstances, ARGS_NONE,
1050            [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT], "",
1051            "Lists the instances and their status. The available fields are"
1052            " (see the man page for details): status, oper_state, oper_ram,"
1053            " name, os, pnode, snodes, admin_state, admin_ram, disk_template,"
1054            " ip, mac, bridge, sda_size, sdb_size, vcpus, serial_no,"
1055            " hypervisor."
1056            " The default field"
1057            " list is (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
1058            ),
1059   'reinstall': (ReinstallInstance, ARGS_ONE,
1060                 [DEBUG_OPT, FORCE_OPT, os_opt,
1061                  make_option("--select-os", dest="select_os",
1062                              action="store_true", default=False,
1063                              help="Interactive OS reinstall, lists available"
1064                              " OS templates for selection"),
1065                  SUBMIT_OPT,
1066                  ],
1067                 "[-f] <instance>", "Reinstall a stopped instance"),
1068   'remove': (RemoveInstance, ARGS_ONE,
1069              [DEBUG_OPT, FORCE_OPT,
1070               make_option("--ignore-failures", dest="ignore_failures",
1071                           action="store_true", default=False,
1072                           help=("Remove the instance from the cluster even"
1073                                 " if there are failures during the removal"
1074                                 " process (shutdown, disk removal, etc.)")),
1075               SUBMIT_OPT,
1076               ],
1077              "[-f] <instance>", "Shuts down the instance and removes it"),
1078   'rename': (RenameInstance, ARGS_FIXED(2),
1079              [DEBUG_OPT,
1080               make_option("--no-ip-check", dest="ignore_ip",
1081                           help="Do not check that the IP of the new name"
1082                           " is alive",
1083                           default=False, action="store_true"),
1084               SUBMIT_OPT,
1085               ],
1086              "<instance> <new_name>", "Rename the instance"),
1087   'replace-disks': (ReplaceDisks, ARGS_ONE,
1088                     [DEBUG_OPT,
1089                      make_option("-n", "--new-secondary", dest="new_secondary",
1090                                  help=("New secondary node (for secondary"
1091                                        " node change)"), metavar="NODE"),
1092                      make_option("-p", "--on-primary", dest="on_primary",
1093                                  default=False, action="store_true",
1094                                  help=("Replace the disk(s) on the primary"
1095                                        " node (only for the drbd template)")),
1096                      make_option("-s", "--on-secondary", dest="on_secondary",
1097                                  default=False, action="store_true",
1098                                  help=("Replace the disk(s) on the secondary"
1099                                        " node (only for the drbd template)")),
1100                      make_option("--disks", dest="disks", default=None,
1101                                  help=("Comma-separated list of disks"
1102                                        " to replace (e.g. sda) (optional,"
1103                                        " defaults to all disks")),
1104                      make_option("--iallocator", metavar="<NAME>",
1105                                  help="Select new secondary for the instance"
1106                                  " automatically using the"
1107                                  " <NAME> iallocator plugin (enables"
1108                                  " secondary node replacement)",
1109                                  default=None, type="string"),
1110                      SUBMIT_OPT,
1111                      ],
1112                     "[-s|-p|-n NODE] <instance>",
1113                     "Replaces all disks for the instance"),
1114   'modify': (SetInstanceParams, ARGS_ONE,
1115              [DEBUG_OPT, FORCE_OPT,
1116               make_option("-i", "--ip", dest="ip",
1117                           help="IP address ('none' or numeric IP)",
1118                           default=None, type="string", metavar="<ADDRESS>"),
1119               make_option("-b", "--bridge", dest="bridge",
1120                           help="Bridge to connect this instance to",
1121                           default=None, type="string", metavar="<bridge>"),
1122               make_option("--mac", dest="mac",
1123                           help="MAC address", default=None,
1124                           type="string", metavar="<MACADDRESS>"),
1125               keyval_option("-H", "--hypervisor", type="keyval",
1126                             default={}, dest="hypervisor",
1127                             help="Change hypervisor parameters"),
1128               keyval_option("-B", "--backend", type="keyval",
1129                             default={}, dest="beparams",
1130                             help="Change backend parameters"),
1131               SUBMIT_OPT,
1132               ],
1133              "<instance>", "Alters the parameters of an instance"),
1134   'shutdown': (ShutdownInstance, ARGS_ANY,
1135                [DEBUG_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1136                 m_clust_opt, m_inst_opt, m_force_multi,
1137                 SUBMIT_OPT,
1138                 ],
1139                "<instance>", "Stops an instance"),
1140   'startup': (StartupInstance, ARGS_ANY,
1141               [DEBUG_OPT, FORCE_OPT, m_force_multi,
1142                make_option("-e", "--extra", dest="extra_args",
1143                            help="Extra arguments for the instance's kernel",
1144                            default=None, type="string", metavar="<PARAMS>"),
1145                m_node_opt, m_pri_node_opt, m_sec_node_opt,
1146                m_clust_opt, m_inst_opt,
1147                SUBMIT_OPT,
1148                ],
1149             "<instance>", "Starts an instance"),
1150
1151   'reboot': (RebootInstance, ARGS_ANY,
1152               [DEBUG_OPT, m_force_multi,
1153                make_option("-e", "--extra", dest="extra_args",
1154                            help="Extra arguments for the instance's kernel",
1155                            default=None, type="string", metavar="<PARAMS>"),
1156                make_option("-t", "--type", dest="reboot_type",
1157                            help="Type of reboot: soft/hard/full",
1158                            default=constants.INSTANCE_REBOOT_HARD,
1159                            type="string", metavar="<REBOOT>"),
1160                make_option("--ignore-secondaries", dest="ignore_secondaries",
1161                            default=False, action="store_true",
1162                            help="Ignore errors from secondaries"),
1163                m_node_opt, m_pri_node_opt, m_sec_node_opt,
1164                m_clust_opt, m_inst_opt,
1165                SUBMIT_OPT,
1166                ],
1167             "<instance>", "Reboots an instance"),
1168   'activate-disks': (ActivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1169                      "<instance>",
1170                      "Activate an instance's disks"),
1171   'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1172                        "<instance>",
1173                        "Deactivate an instance's disks"),
1174   'grow-disk': (GrowDisk, ARGS_FIXED(3),
1175                 [DEBUG_OPT, SUBMIT_OPT,
1176                  make_option("--no-wait-for-sync",
1177                              dest="wait_for_sync", default=True,
1178                              action="store_false",
1179                              help="Don't wait for sync (DANGEROUS!)"),
1180                  ],
1181                 "<instance> <disk> <size>", "Grow an instance's disk"),
1182   'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
1183                 "<instance_name>", "List the tags of the given instance"),
1184   'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1185                "<instance_name> tag...", "Add tags to the given instance"),
1186   'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1187                   "<instance_name> tag...", "Remove tags from given instance"),
1188   }
1189
1190 aliases = {
1191   'activate_block_devs': 'activate-disks',
1192   'replace_disks': 'replace-disks',
1193   'start': 'startup',
1194   'stop': 'shutdown',
1195   }
1196
1197 if __name__ == '__main__':
1198   sys.exit(GenericMain(commands, aliases=aliases,
1199                        override={"tag_type": constants.TAG_INSTANCE}))