4 # Copyright (C) 2006, 2007 Google Inc.
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.
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.
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
25 from optparse import make_option
26 from cStringIO import StringIO
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
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"
42 def _ExpandMultiNames(mode, names):
43 """Expand the given names using the passed mode.
46 - mode, which can be one of _SHUTDOWN_CLUSTER, _SHUTDOWN_NODES_BOTH,
47 _SHUTDOWN_NODES_PRI, _SHUTDOWN_NODES_SEC or _SHUTDOWN_INSTANCES
48 - names, which is a list of names; for cluster, it must be empty,
49 and for node and instance it must be a list of valid item
50 names (short names are valid as usual, e.g. node1 instead of
53 For _SHUTDOWN_CLUSTER, all instances will be returned. For
54 _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
55 primary/secondary will be shutdown. For _SHUTDOWN_NODES_BOTH, all
56 instances having those nodes as either primary or secondary will be
57 returned. For _SHUTDOWN_INSTANCES, the given instances will be
61 if mode == _SHUTDOWN_CLUSTER:
63 raise errors.OpPrereqError("Cluster filter mode takes no arguments")
64 op = opcodes.OpQueryInstances(output_fields=["name"], names=[])
65 idata = SubmitOpCode(op)
66 inames = [row[0] for row in idata]
68 elif mode in (_SHUTDOWN_NODES_BOTH,
72 raise errors.OpPrereqError("No node names passed")
73 op = opcodes.OpQueryNodes(output_fields=["name", "pinst_list",
74 "sinst_list"], names=names)
75 ndata = SubmitOpCode(op)
76 ipri = [row[1] for row in ndata]
77 pri_names = list(itertools.chain(*ipri))
78 isec = [row[2] for row in ndata]
79 sec_names = list(itertools.chain(*isec))
80 if mode == _SHUTDOWN_NODES_BOTH:
81 inames = pri_names + sec_names
82 elif mode == _SHUTDOWN_NODES_PRI:
84 elif mode == _SHUTDOWN_NODES_SEC:
87 raise errors.ProgrammerError("Unhandled shutdown type")
89 elif mode == _SHUTDOWN_INSTANCES:
91 raise errors.OpPrereqError("No instance names passed")
92 op = opcodes.OpQueryInstances(output_fields=["name"], names=names)
93 idata = SubmitOpCode(op)
94 inames = [row[0] for row in idata]
97 raise errors.OpPrereqError("Unknown mode '%s'" % mode)
102 def _ConfirmOperation(inames, text):
103 """Ask the user to confirm an operation on a list of instances.
105 This function is used to request confirmation for doing an operation
106 on a given list of instances.
108 The inames argument is what the selection algorithm computed, and
109 the text argument is the operation we should tell the user to
110 confirm (e.g. 'shutdown' or 'startup').
112 Returns: boolean depending on user's confirmation.
116 msg = ("The %s will operate on %d instances.\n"
117 "Do you want to continue?" % (text, count))
118 affected = ("\nAffected instances:\n" +
119 "\n".join([" %s" % name for name in inames]))
121 choices = [('y', True, 'Yes, execute the %s' % text),
122 ('n', False, 'No, abort the %s' % text)]
125 choices.insert(1, ('v', 'v', 'View the list of affected instances'))
130 choice = AskUser(ask, choices)
133 choice = AskUser(choices, msg + affected)
137 def ListInstances(opts, args):
138 """List nodes and their properties.
141 if opts.output is None:
142 selected_fields = ["name", "os", "pnode", "admin_state",
143 "oper_state", "oper_ram"]
145 selected_fields = opts.output.split(",")
147 op = opcodes.OpQueryInstances(output_fields=selected_fields, names=[])
148 output = SubmitOpCode(op)
150 if not opts.no_headers:
151 headers = {"name": "Instance", "os": "OS", "pnode": "Primary_node",
152 "snodes": "Secondary_Nodes", "admin_state": "Autostart",
153 "oper_state": "Status", "admin_ram": "Configured_memory",
154 "oper_ram": "Memory", "disk_template": "Disk_template",
155 "ip": "IP Address", "mac": "MAC Address",
157 "sda_size": "Disk/0", "sdb_size": "Disk/1"}
161 if opts.human_readable:
162 unitfields = ["admin_ram", "oper_ram", "sda_size", "sdb_size"]
166 numfields = ["admin_ram", "oper_ram", "sda_size", "sdb_size"]
168 # change raw values to nicer strings
170 for idx, field in enumerate(selected_fields):
172 if field == "snodes":
173 val = ",".join(val) or "-"
174 elif field == "admin_state":
179 elif field == "oper_state":
186 elif field == "oper_ram":
189 elif field == "sda_size" or field == "sdb_size":
194 data = GenerateTable(separator=opts.separator, headers=headers,
195 fields=selected_fields, unitfields=unitfields,
196 numfields=numfields, data=output)
199 logger.ToStdout(line)
204 def AddInstance(opts, args):
205 """Add an instance to the cluster.
208 opts - class with options as members
209 args - list with a single element, the instance name
211 mem - amount of memory to allocate to instance (MiB)
212 size - amount of disk space to allocate to instance (MiB)
213 os - which OS to run on instance
214 node - node to run new instance on
219 op = opcodes.OpCreateInstance(instance_name=instance, mem_size=opts.mem,
220 disk_size=opts.size, swap_size=opts.swap,
221 disk_template=opts.disk_template,
222 mode=constants.INSTANCE_CREATE,
223 os_type=opts.os, pnode=opts.node,
224 snode=opts.snode, vcpus=opts.vcpus,
225 ip=opts.ip, bridge=opts.bridge,
226 start=opts.start, ip_check=opts.ip_check,
227 wait_for_sync=opts.wait_for_sync)
232 def ReinstallInstance(opts, args):
233 """Reinstall an instance.
236 opts - class with options as members
237 args - list containing a single element, the instance name
240 instance_name = args[0]
243 usertext = ("This will reinstall the instance %s and remove "
244 "all data. Continue?") % instance_name
245 if not AskUser(usertext):
248 op = opcodes.OpReinstallInstance(instance_name=instance_name,
255 def RemoveInstance(opts, args):
256 """Remove an instance.
259 opts - class with options as members
260 args - list containing a single element, the instance name
263 instance_name = args[0]
267 usertext = ("This will remove the volumes of the instance %s"
268 " (including mirrors), thus removing all the data"
269 " of the instance. Continue?") % instance_name
270 if not AskUser(usertext):
273 op = opcodes.OpRemoveInstance(instance_name=instance_name)
278 def RenameInstance(opts, args):
279 """Rename an instance.
282 opts - class with options as members
283 args - list containing two elements, the instance name and the new name
286 op = opcodes.OpRenameInstance(instance_name=args[0],
288 ignore_ip=opts.ignore_ip)
294 def ActivateDisks(opts, args):
295 """Activate an instance's disks.
297 This serves two purposes:
298 - it allows one (as long as the instance is not running) to mount
299 the disks and modify them from the node
300 - it repairs inactive secondary drbds
303 instance_name = args[0]
304 op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
305 disks_info = SubmitOpCode(op)
306 for host, iname, nname in disks_info:
307 print "%s:%s:%s" % (host, iname, nname)
311 def DeactivateDisks(opts, args):
312 """Command-line interface for _ShutdownInstanceBlockDevices.
314 This function takes the instance name, looks for its primary node
315 and the tries to shutdown its block devices on that node.
318 instance_name = args[0]
319 op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
324 def StartupInstance(opts, args):
325 """Startup an instance.
328 opts - class with options as members
329 args - list containing a single element, the instance name
332 if opts.multi_mode is None:
333 opts.multi_mode = _SHUTDOWN_INSTANCES
334 inames = _ExpandMultiNames(opts.multi_mode, args)
335 multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
336 if not (opts.force_multi or not multi_on
337 or _ConfirmOperation(inames, "startup")):
340 op = opcodes.OpStartupInstance(instance_name=name,
342 extra_args=opts.extra_args)
344 logger.ToStdout("Starting up %s" % name)
349 def ShutdownInstance(opts, args):
350 """Shutdown an instance.
353 opts - class with options as members
354 args - list containing a single element, the instance name
357 if opts.multi_mode is None:
358 opts.multi_mode = _SHUTDOWN_INSTANCES
359 inames = _ExpandMultiNames(opts.multi_mode, args)
360 multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
361 if not (opts.force_multi or not multi_on
362 or _ConfirmOperation(inames, "shutdown")):
365 op = opcodes.OpShutdownInstance(instance_name=name)
367 logger.ToStdout("Shutting down %s" % name)
372 def AddMDDRBDComponent(opts, args):
373 """Add a new component to a remote_raid1 disk.
376 opts - class with options as members
377 args - list with a single element, the instance name
380 op = opcodes.OpAddMDDRBDComponent(instance_name=args[0],
382 remote_node=opts.node)
387 def RemoveMDDRBDComponent(opts, args):
388 """Remove a component from a remote_raid1 disk.
391 opts - class with options as members
392 args - list with a single element, the instance name
395 op = opcodes.OpRemoveMDDRBDComponent(instance_name=args[0],
402 def ReplaceDisks(opts, args):
403 """Replace the disks of an instance
406 opts - class with options as members
407 args - list with a single element, the instance name
410 instance_name = args[0]
411 new_secondary = opts.new_secondary
412 op = opcodes.OpReplaceDisks(instance_name=args[0],
413 remote_node=opts.new_secondary)
418 def FailoverInstance(opts, args):
419 """Failover an instance.
421 The failover is done by shutting it down on its present node and
422 starting it on the secondary.
425 opts - class with options as members
426 args - list with a single element, the instance name
428 force - whether to failover without asking questions.
431 instance_name = args[0]
435 usertext = ("Failover will happen to image %s."
436 " This requires a shutdown of the instance. Continue?" %
438 if not AskUser(usertext):
441 op = opcodes.OpFailoverInstance(instance_name=instance_name,
442 ignore_consistency=opts.ignore_consistency)
447 def ConnectToInstanceConsole(opts, args):
448 """Connect to the console of an instance.
451 opts - class with options as members
452 args - list with a single element, the instance name
455 instance_name = args[0]
457 op = opcodes.OpConnectConsole(instance_name=instance_name)
458 cmd, argv = SubmitOpCode(op)
459 # drop lock and exec so other commands can run while we have console
464 sys.stderr.write("Can't run console command %s with arguments:\n'%s'" %
465 (cmd, " ".join(argv)))
469 def _FormatBlockDevInfo(buf, dev, indent_level):
470 """Show block device information.
472 This is only used by ShowInstanceConfig(), but it's too big to be
473 left for an inline definition.
476 def helper(buf, dtype, status):
477 """Format one line for phsyical device status."""
479 buf.write("not active\n")
481 (path, major, minor, syncp, estt, degr) = status
482 buf.write("%s (%d:%d)" % (path, major, minor))
483 if dtype in ("md_raid1", "drbd"):
484 if syncp is not None:
485 sync_text = "*RECOVERING* %5.2f%%," % syncp
487 sync_text += " ETA %ds" % estt
489 sync_text += " ETA unknown"
491 sync_text = "in sync"
493 degr_text = "*DEGRADED*"
496 buf.write(" %s, status %s" % (sync_text, degr_text))
499 if dev["iv_name"] is not None:
500 data = " - %s, " % dev["iv_name"]
503 data += "type: %s" % dev["dev_type"]
504 if dev["logical_id"] is not None:
505 data += ", logical_id: %s" % (dev["logical_id"],)
506 elif dev["physical_id"] is not None:
507 data += ", physical_id: %s" % (dev["physical_id"],)
508 buf.write("%*s%s\n" % (2*indent_level, "", data))
509 buf.write("%*s primary: " % (2*indent_level, ""))
510 helper(buf, dev["dev_type"], dev["pstatus"])
513 buf.write("%*s secondary: " % (2*indent_level, ""))
514 helper(buf, dev["dev_type"], dev["sstatus"])
517 for child in dev["children"]:
518 _FormatBlockDevInfo(buf, child, indent_level+1)
521 def ShowInstanceConfig(opts, args):
522 """Compute instance run-time status.
526 op = opcodes.OpQueryInstanceData(instances=args)
527 result = SubmitOpCode(op)
530 logger.ToStdout("No instances.")
535 for instance_name in result:
536 instance = result[instance_name]
537 buf.write("Instance name: %s\n" % instance["name"])
538 buf.write("State: configured to be %s, actual state is %s\n" %
539 (instance["config_state"], instance["run_state"]))
540 buf.write(" Nodes:\n")
541 buf.write(" - primary: %s\n" % instance["pnode"])
542 buf.write(" - secondaries: %s\n" % ", ".join(instance["snodes"]))
543 buf.write(" Operating system: %s\n" % instance["os"])
544 buf.write(" Hardware:\n")
545 buf.write(" - memory: %dMiB\n" % instance["memory"])
546 buf.write(" - NICs: %s\n" %
547 ", ".join(["{MAC: %s, IP: %s, bridge: %s}" %
549 for mac, ip, bridge in instance["nics"]]))
550 buf.write(" Block devices:\n")
552 for device in instance["disks"]:
553 _FormatBlockDevInfo(buf, device, 1)
555 logger.ToStdout(buf.getvalue().rstrip('\n'))
559 def SetInstanceParms(opts, args):
560 """Modifies an instance.
562 All parameters take effect only at the next restart of the instance.
565 opts - class with options as members
566 args - list with a single element, the instance name
568 memory - the new memory size
569 vcpus - the new number of cpus
572 if not opts.mem and not opts.vcpus and not opts.ip and not opts.bridge:
573 logger.ToStdout("Please give at least one of the parameters.")
576 op = opcodes.OpSetInstanceParms(instance_name=args[0], mem=opts.mem,
577 vcpus=opts.vcpus, ip=opts.ip,
579 result = SubmitOpCode(op)
582 logger.ToStdout("Modified instance %s" % args[0])
583 for param, data in result:
584 logger.ToStdout(" - %-5s -> %s" % (param, data))
585 logger.ToStdout("Please don't forget that these parameters take effect"
586 " only at the next start of the instance.")
590 # options used in more than one cmd
591 node_opt = make_option("-n", "--node", dest="node", help="Target node",
594 os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
597 # multi-instance selection options
598 m_force_multi = make_option("--force-multiple", dest="force_multi",
599 help="Do not ask for confirmation when more than"
600 " one instance is affected",
601 action="store_true", default=False)
603 m_pri_node_opt = make_option("--primary", dest="multi_mode",
604 help="Filter by nodes (primary only)",
605 const=_SHUTDOWN_NODES_PRI, action="store_const")
607 m_sec_node_opt = make_option("--secondary", dest="multi_mode",
608 help="Filter by nodes (secondary only)",
609 const=_SHUTDOWN_NODES_SEC, action="store_const")
611 m_node_opt = make_option("--node", dest="multi_mode",
612 help="Filter by nodes (primary and secondary)",
613 const=_SHUTDOWN_NODES_BOTH, action="store_const")
615 m_clust_opt = make_option("--all", dest="multi_mode",
616 help="Select all instances in the cluster",
617 const=_SHUTDOWN_CLUSTER, action="store_const")
619 m_inst_opt = make_option("--instance", dest="multi_mode",
620 help="Filter by instance name [default]",
621 const=_SHUTDOWN_INSTANCES, action="store_const")
624 # this is defined separately due to readability only
628 cli_option("-s", "--os-size", dest="size", help="Disk size, in MiB unless"
630 default=20 * 1024, type="unit", metavar="<size>"),
631 cli_option("--swap-size", dest="swap", help="Swap size, in MiB unless a"
633 default=4 * 1024, type="unit", metavar="<size>"),
635 cli_option("-m", "--memory", dest="mem", help="Memory size (in MiB)",
636 default=128, type="unit", metavar="<mem>"),
637 make_option("-p", "--cpu", dest="vcpus", help="Number of virtual CPUs",
638 default=1, type="int", metavar="<PROC>"),
639 make_option("-t", "--disk-template", dest="disk_template",
640 help="Custom disk setup (diskless, plain, local_raid1 or"
641 " remote_raid1)", default=None, metavar="TEMPL"),
642 make_option("-i", "--ip", dest="ip",
643 help="IP address ('none' [default], 'auto', or specify address)",
644 default='none', type="string", metavar="<ADDRESS>"),
645 make_option("--no-wait-for-sync", dest="wait_for_sync", default=True,
646 action="store_false", help="Don't wait for sync (DANGEROUS!)"),
647 make_option("--secondary-node", dest="snode",
648 help="Secondary node for remote_raid1 disk layout",
650 make_option("-b", "--bridge", dest="bridge",
651 help="Bridge to connect this instance to",
652 default=None, metavar="<bridge>"),
653 make_option("--no-start", dest="start", default=True,
654 action="store_false", help="Don't start the instance after"
656 make_option("--no-ip-check", dest="ip_check", default=True,
657 action="store_false", help="Don't check that the instance's IP"
658 " is alive (only valid with --no-start)"),
662 'add': (AddInstance, ARGS_ONE, add_opts,
664 "Creates and adds a new instance to the cluster"),
665 'add-mirror': (AddMDDRBDComponent, ARGS_ONE,
666 [DEBUG_OPT, node_opt,
667 make_option("-b", "--disk", dest="disk", metavar="sdX",
668 help=("The name of the instance disk for which to"
669 " add the mirror"))],
670 "-n node -b disk <instance>",
671 "Creates a new mirror for the instance"),
672 'console': (ConnectToInstanceConsole, ARGS_ONE, [DEBUG_OPT],
674 "Opens a console on the specified instance"),
675 'failover': (FailoverInstance, ARGS_ONE,
676 [DEBUG_OPT, FORCE_OPT,
677 make_option("--ignore-consistency", dest="ignore_consistency",
678 action="store_true", default=False,
679 help="Ignore the consistency of the disks on"
683 "Stops the instance and starts it on the backup node, using"
684 " the remote mirror (only for instances of type remote_raid1)"),
685 'info': (ShowInstanceConfig, ARGS_ANY, [DEBUG_OPT], "[<instance>...]",
686 "Show information on the specified instance"),
687 'list': (ListInstances, ARGS_NONE,
688 [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
689 "", "Lists the instances and their status"),
690 'reinstall': (ReinstallInstance, ARGS_ONE, [DEBUG_OPT, FORCE_OPT, os_opt],
691 "[-f] <instance>", "Reinstall the instance"),
692 'remove': (RemoveInstance, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
693 "[-f] <instance>", "Shuts down the instance and removes it"),
694 'remove-mirror': (RemoveMDDRBDComponent, ARGS_ONE,
695 [DEBUG_OPT, node_opt,
696 make_option("-b", "--disk", dest="disk", metavar="sdX",
697 help=("The name of the instance disk"
698 " for which to add the mirror")),
699 make_option("-p", "--port", dest="port", metavar="PORT",
700 help=("The port of the drbd device"
701 " which to remove from the mirror"),
704 "-b disk -p port <instance>",
705 "Removes a mirror from the instance"),
706 'rename': (RenameInstance, ARGS_FIXED(2),
708 make_option("--no-ip-check", dest="ignore_ip",
709 help="Do not check that the IP of the new name"
711 default=False, action="store_true"),
713 "<instance> <new_name>", "Rename the instance"),
714 'replace-disks': (ReplaceDisks, ARGS_ONE,
716 make_option("-n", "--new-secondary", dest="new_secondary",
718 help=("New secondary node (if you want to"
719 " change the secondary)"))],
720 "[-n NODE] <instance>",
721 "Replaces all disks for the instance"),
722 'modify': (SetInstanceParms, ARGS_ONE,
723 [DEBUG_OPT, FORCE_OPT,
724 cli_option("-m", "--memory", dest="mem",
726 default=None, type="unit", metavar="<mem>"),
727 make_option("-p", "--cpu", dest="vcpus",
728 help="Number of virtual CPUs",
729 default=None, type="int", metavar="<PROC>"),
730 make_option("-i", "--ip", dest="ip",
731 help="IP address ('none' or numeric IP)",
732 default=None, type="string", metavar="<ADDRESS>"),
733 make_option("-b", "--bridge", dest="bridge",
734 help="Bridge to connect this instance to",
735 default=None, type="string", metavar="<bridge>"),
737 "<instance>", "Alters the parameters of an instance"),
738 'shutdown': (ShutdownInstance, ARGS_ANY,
739 [DEBUG_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt,
740 m_clust_opt, m_inst_opt, m_force_multi],
741 "<instance>", "Stops an instance"),
742 'startup': (StartupInstance, ARGS_ANY,
743 [DEBUG_OPT, FORCE_OPT, m_force_multi,
744 make_option("-e", "--extra", dest="extra_args",
745 help="Extra arguments for the instance's kernel",
746 default=None, type="string", metavar="<PARAMS>"),
747 m_node_opt, m_pri_node_opt, m_sec_node_opt,
748 m_clust_opt, m_inst_opt,
750 "<instance>", "Starts an instance"),
751 'activate-disks': (ActivateDisks, ARGS_ONE, [DEBUG_OPT],
753 "Activate an instance's disks"),
754 'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT],
756 "Deactivate an instance's disks"),
759 if __name__ == '__main__':
760 sys.exit(GenericMain(commands))