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