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