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 ListInstances(opts, args):
103 """List nodes and their properties.
106 if opts.output is None:
107 selected_fields = ["name", "os", "pnode", "admin_state",
108 "oper_state", "oper_ram"]
110 selected_fields = opts.output.split(",")
112 op = opcodes.OpQueryInstances(output_fields=selected_fields, names=[])
113 output = SubmitOpCode(op)
115 if not opts.no_headers:
116 headers = {"name": "Instance", "os": "OS", "pnode": "Primary_node",
117 "snodes": "Secondary_Nodes", "admin_state": "Autostart",
118 "oper_state": "Status", "admin_ram": "Configured_memory",
119 "oper_ram": "Memory", "disk_template": "Disk_template",
120 "ip": "IP Address", "mac": "MAC Address",
122 "sda_size": "Disk/0", "sdb_size": "Disk/1"}
126 if opts.human_readable:
127 unitfields = ["admin_ram", "oper_ram", "sda_size", "sdb_size"]
131 numfields = ["admin_ram", "oper_ram", "sda_size", "sdb_size"]
133 # change raw values to nicer strings
135 for idx, field in enumerate(selected_fields):
137 if field == "snodes":
138 val = ",".join(val) or "-"
139 elif field == "admin_state":
144 elif field == "oper_state":
151 elif field == "oper_ram":
154 elif field == "sda_size" or field == "sdb_size":
159 data = GenerateTable(separator=opts.separator, headers=headers,
160 fields=selected_fields, unitfields=unitfields,
161 numfields=numfields, data=output)
164 logger.ToStdout(line)
169 def AddInstance(opts, args):
170 """Add an instance to the cluster.
173 opts - class with options as members
174 args - list with a single element, the instance name
176 mem - amount of memory to allocate to instance (MiB)
177 size - amount of disk space to allocate to instance (MiB)
178 os - which OS to run on instance
179 node - node to run new instance on
184 op = opcodes.OpCreateInstance(instance_name=instance, mem_size=opts.mem,
185 disk_size=opts.size, swap_size=opts.swap,
186 disk_template=opts.disk_template,
187 mode=constants.INSTANCE_CREATE,
188 os_type=opts.os, pnode=opts.node,
189 snode=opts.snode, vcpus=opts.vcpus,
190 ip=opts.ip, bridge=opts.bridge, start=True,
191 wait_for_sync=opts.wait_for_sync)
196 def ReinstallInstance(opts, args):
197 """Reinstall an instance.
200 opts - class with options as members
201 args - list containing a single element, the instance name
204 instance_name = args[0]
207 usertext = ("This will reinstall the instance %s and remove "
208 "all data. Continue?") % instance_name
209 if not opts._ask_user(usertext):
212 op = opcodes.OpReinstallInstance(instance_name=instance_name,
219 def RemoveInstance(opts, args):
220 """Remove an instance.
223 opts - class with options as members
224 args - list containing a single element, the instance name
227 instance_name = args[0]
231 usertext = ("This will remove the volumes of the instance %s"
232 " (including mirrors), thus removing all the data"
233 " of the instance. Continue?") % instance_name
234 if not opts._ask_user(usertext):
237 op = opcodes.OpRemoveInstance(instance_name=instance_name)
242 def RenameInstance(opts, args):
243 """Reinstall an instance.
246 opts - class with options as members
247 args - list containing two elements, the instance name and the new name
250 op = opcodes.OpRenameInstance(instance_name=args[0],
252 ignore_ip=opts.ignore_ip)
258 def ActivateDisks(opts, args):
259 """Activate an instance's disks.
261 This serves two purposes:
262 - it allows one (as long as the instance is not running) to mount
263 the disks and modify them from the node
264 - it repairs inactive secondary drbds
267 instance_name = args[0]
268 op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
269 disks_info = SubmitOpCode(op)
270 for host, iname, nname in disks_info:
271 print "%s:%s:%s" % (host, iname, nname)
275 def DeactivateDisks(opts, args):
276 """Command-line interface for _ShutdownInstanceBlockDevices.
278 This function takes the instance name, looks for its primary node
279 and the tries to shutdown its block devices on that node.
282 instance_name = args[0]
283 op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
288 def StartupInstance(opts, args):
289 """Startup an instance.
292 opts - class with options as members
293 args - list containing a single element, the instance name
296 if opts.multi_mode is None:
297 opts.multi_mode = _SHUTDOWN_INSTANCES
298 inames = _ExpandMultiNames(opts.multi_mode, args)
299 multi_on = len(inames) > 1
301 op = opcodes.OpStartupInstance(instance_name=name,
303 extra_args=opts.extra_args)
305 logger.ToStdout("Starting up %s" % name)
310 def ShutdownInstance(opts, args):
311 """Shutdown an instance.
314 opts - class with options as members
315 args - list containing a single element, the instance name
318 if opts.multi_mode is None:
319 opts.multi_mode = _SHUTDOWN_INSTANCES
320 inames = _ExpandMultiNames(opts.multi_mode, args)
321 multi_on = len(inames) > 1
323 op = opcodes.OpShutdownInstance(instance_name=name)
325 logger.ToStdout("Shutting down %s" % name)
330 def AddMDDRBDComponent(opts, args):
331 """Add a new component to a remote_raid1 disk.
334 opts - class with options as members
335 args - list with a single element, the instance name
338 op = opcodes.OpAddMDDRBDComponent(instance_name=args[0],
340 remote_node=opts.node)
345 def RemoveMDDRBDComponent(opts, args):
346 """Remove a component from a remote_raid1 disk.
349 opts - class with options as members
350 args - list with a single element, the instance name
353 op = opcodes.OpRemoveMDDRBDComponent(instance_name=args[0],
360 def ReplaceDisks(opts, args):
361 """Replace the disks of an instance
364 opts - class with options as members
365 args - list with a single element, the instance name
368 instance_name = args[0]
369 new_secondary = opts.new_secondary
370 op = opcodes.OpReplaceDisks(instance_name=args[0],
371 remote_node=opts.new_secondary)
376 def FailoverInstance(opts, args):
377 """Failover an instance.
379 The failover is done by shutting it down on its present node and
380 starting it on the secondary.
383 opts - class with options as members
384 args - list with a single element, the instance name
386 force - whether to failover without asking questions.
389 instance_name = args[0]
393 usertext = ("Failover will happen to image %s."
394 " This requires a shutdown of the instance. Continue?" %
396 if not opts._ask_user(usertext):
399 op = opcodes.OpFailoverInstance(instance_name=instance_name,
400 ignore_consistency=opts.ignore_consistency)
405 def ConnectToInstanceConsole(opts, args):
406 """Connect to the console of an instance.
409 opts - class with options as members
410 args - list with a single element, the instance name
413 instance_name = args[0]
415 op = opcodes.OpConnectConsole(instance_name=instance_name)
416 cmd, argv = SubmitOpCode(op)
417 # drop lock and exec so other commands can run while we have console
422 sys.stderr.write("Can't run console command %s with arguments:\n'%s'" %
423 (cmd, " ".join(argv)))
427 def _FormatBlockDevInfo(buf, dev, indent_level):
428 """Show block device information.
430 This is only used by ShowInstanceConfig(), but it's too big to be
431 left for an inline definition.
434 def helper(buf, dtype, status):
435 """Format one line for phsyical device status."""
437 buf.write("not active\n")
439 (path, major, minor, syncp, estt, degr) = status
440 buf.write("%s (%d:%d)" % (path, major, minor))
441 if dtype in ("md_raid1", "drbd"):
442 if syncp is not None:
443 sync_text = "*RECOVERING* %5.2f%%," % syncp
445 sync_text += " ETA %ds" % estt
447 sync_text += " ETA unknown"
449 sync_text = "in sync"
451 degr_text = "*DEGRADED*"
454 buf.write(" %s, status %s" % (sync_text, degr_text))
457 if dev["iv_name"] is not None:
458 data = " - %s, " % dev["iv_name"]
461 data += "type: %s" % dev["dev_type"]
462 if dev["logical_id"] is not None:
463 data += ", logical_id: %s" % (dev["logical_id"],)
464 elif dev["physical_id"] is not None:
465 data += ", physical_id: %s" % (dev["physical_id"],)
466 buf.write("%*s%s\n" % (2*indent_level, "", data))
467 buf.write("%*s primary: " % (2*indent_level, ""))
468 helper(buf, dev["dev_type"], dev["pstatus"])
471 buf.write("%*s secondary: " % (2*indent_level, ""))
472 helper(buf, dev["dev_type"], dev["sstatus"])
475 for child in dev["children"]:
476 _FormatBlockDevInfo(buf, child, indent_level+1)
479 def ShowInstanceConfig(opts, args):
480 """Compute instance run-time status.
484 op = opcodes.OpQueryInstanceData(instances=args)
485 result = SubmitOpCode(op)
488 logger.ToStdout("No instances.")
493 for instance_name in result:
494 instance = result[instance_name]
495 buf.write("Instance name: %s\n" % instance["name"])
496 buf.write("State: configured to be %s, actual state is %s\n" %
497 (instance["config_state"], instance["run_state"]))
498 buf.write(" Nodes:\n")
499 buf.write(" - primary: %s\n" % instance["pnode"])
500 buf.write(" - secondaries: %s\n" % ", ".join(instance["snodes"]))
501 buf.write(" Operating system: %s\n" % instance["os"])
502 buf.write(" Hardware:\n")
503 buf.write(" - memory: %dMiB\n" % instance["memory"])
504 buf.write(" - NICs: %s\n" %
505 ", ".join(["{MAC: %s, IP: %s, bridge: %s}" %
507 for mac, ip, bridge in instance["nics"]]))
508 buf.write(" Block devices:\n")
510 for device in instance["disks"]:
511 _FormatBlockDevInfo(buf, device, 1)
513 logger.ToStdout(buf.getvalue().rstrip('\n'))
517 def SetInstanceParms(opts, args):
518 """Modifies an instance.
520 All parameters take effect only at the next restart of the instance.
523 opts - class with options as members
524 args - list with a single element, the instance name
526 memory - the new memory size
527 vcpus - the new number of cpus
530 if not opts.mem and not opts.vcpus and not opts.ip and not opts.bridge:
531 logger.ToStdout("Please give at least one of the parameters.")
534 op = opcodes.OpSetInstanceParms(instance_name=args[0], mem=opts.mem,
535 vcpus=opts.vcpus, ip=opts.ip,
537 result = SubmitOpCode(op)
540 logger.ToStdout("Modified instance %s" % args[0])
541 for param, data in result:
542 logger.ToStdout(" - %-5s -> %s" % (param, data))
543 logger.ToStdout("Please don't forget that these parameters take effect"
544 " only at the next start of the instance.")
548 # options used in more than one cmd
549 node_opt = make_option("-n", "--node", dest="node", help="Target node",
552 os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
555 # multi-instance selection options
556 m_pri_node_opt = make_option("--primary", dest="multi_mode",
557 help="Filter by nodes (primary only)",
558 const=_SHUTDOWN_NODES_PRI, action="store_const")
560 m_sec_node_opt = make_option("--secondary", dest="multi_mode",
561 help="Filter by nodes (secondary only)",
562 const=_SHUTDOWN_NODES_SEC, action="store_const")
564 m_node_opt = make_option("--node", dest="multi_mode",
565 help="Filter by nodes (primary and secondary)",
566 const=_SHUTDOWN_NODES_BOTH, action="store_const")
568 m_clust_opt = make_option("--all", dest="multi_mode",
569 help="Select all instances in the cluster",
570 const=_SHUTDOWN_CLUSTER, action="store_const")
572 m_inst_opt = make_option("--instance", dest="multi_mode",
573 help="Filter by instance name [default]",
574 const=_SHUTDOWN_INSTANCES, action="store_const")
577 # this is defined separately due to readability only
581 cli_option("-s", "--os-size", dest="size", help="Disk size, in MiB unless"
583 default=20 * 1024, type="unit", metavar="<size>"),
584 cli_option("--swap-size", dest="swap", help="Swap size, in MiB unless a"
586 default=4 * 1024, type="unit", metavar="<size>"),
588 cli_option("-m", "--memory", dest="mem", help="Memory size (in MiB)",
589 default=128, type="unit", metavar="<mem>"),
590 make_option("-p", "--cpu", dest="vcpus", help="Number of virtual CPUs",
591 default=1, type="int", metavar="<PROC>"),
592 make_option("-t", "--disk-template", dest="disk_template",
593 help="Custom disk setup (diskless, plain, local_raid1 or"
594 " remote_raid1)", default=None, metavar="TEMPL"),
595 make_option("-i", "--ip", dest="ip",
596 help="IP address ('none' [default], 'auto', or specify address)",
597 default='none', type="string", metavar="<ADDRESS>"),
598 make_option("--no-wait-for-sync", dest="wait_for_sync", default=True,
599 action="store_false", help="Don't wait for sync (DANGEROUS!)"),
600 make_option("--secondary-node", dest="snode",
601 help="Secondary node for remote_raid1 disk layout",
603 make_option("-b", "--bridge", dest="bridge",
604 help="Bridge to connect this instance to",
605 default=None, metavar="<bridge>")
609 'add': (AddInstance, ARGS_ONE, add_opts,
611 "Creates and adds a new instance to the cluster"),
612 'add-mirror': (AddMDDRBDComponent, ARGS_ONE,
613 [DEBUG_OPT, node_opt,
614 make_option("-b", "--disk", dest="disk", metavar="sdX",
615 help=("The name of the instance disk for which to"
616 " add the mirror"))],
617 "-n node -b disk <instance>",
618 "Creates a new mirror for the instance"),
619 'console': (ConnectToInstanceConsole, ARGS_ONE, [DEBUG_OPT],
621 "Opens a console on the specified instance"),
622 'failover': (FailoverInstance, ARGS_ONE,
623 [DEBUG_OPT, FORCE_OPT,
624 make_option("--ignore-consistency", dest="ignore_consistency",
625 action="store_true", default=False,
626 help="Ignore the consistency of the disks on"
630 "Stops the instance and starts it on the backup node, using"
631 " the remote mirror (only for instances of type remote_raid1)"),
632 'info': (ShowInstanceConfig, ARGS_ANY, [DEBUG_OPT], "[<instance>...]",
633 "Show information on the specified instance"),
634 'list': (ListInstances, ARGS_NONE,
635 [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
636 "", "Lists the instances and their status"),
637 'reinstall': (ReinstallInstance, ARGS_ONE, [DEBUG_OPT, FORCE_OPT, os_opt],
638 "[-f] <instance>", "Reinstall the instance"),
639 'remove': (RemoveInstance, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
640 "[-f] <instance>", "Shuts down the instance and removes it"),
641 'remove-mirror': (RemoveMDDRBDComponent, ARGS_ONE,
642 [DEBUG_OPT, node_opt,
643 make_option("-b", "--disk", dest="disk", metavar="sdX",
644 help=("The name of the instance disk"
645 " for which to add the mirror")),
646 make_option("-p", "--port", dest="port", metavar="PORT",
647 help=("The port of the drbd device"
648 " which to remove from the mirror"),
651 "-b disk -p port <instance>",
652 "Removes a mirror from the instance"),
653 'rename': (RenameInstance, ARGS_FIXED(2),
655 make_option("--no-ip-check", dest="ignore_ip",
656 help="Do not check that the IP of the new name"
658 default=False, action="store_true"),
660 "<instance> <new_name>", "Rename the instance"),
661 'replace-disks': (ReplaceDisks, ARGS_ONE,
663 make_option("-n", "--new-secondary", dest="new_secondary",
665 help=("New secondary node (if you want to"
666 " change the secondary)"))],
667 "[-n NODE] <instance>",
668 "Replaces all disks for the instance"),
669 'modify': (SetInstanceParms, ARGS_ONE,
670 [DEBUG_OPT, FORCE_OPT,
671 cli_option("-m", "--memory", dest="mem",
673 default=None, type="unit", metavar="<mem>"),
674 make_option("-p", "--cpu", dest="vcpus",
675 help="Number of virtual CPUs",
676 default=None, type="int", metavar="<PROC>"),
677 make_option("-i", "--ip", dest="ip",
678 help="IP address ('none' or numeric IP)",
679 default=None, type="string", metavar="<ADDRESS>"),
680 make_option("-b", "--bridge", dest="bridge",
681 help="Bridge to connect this instance to",
682 default=None, type="string", metavar="<bridge>"),
684 "<instance>", "Alters the parameters of an instance"),
685 'shutdown': (ShutdownInstance, ARGS_ANY,
686 [DEBUG_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt,
687 m_clust_opt, m_inst_opt],
688 "<instance>", "Stops an instance"),
689 'startup': (StartupInstance, ARGS_ANY,
690 [DEBUG_OPT, FORCE_OPT,
691 make_option("-e", "--extra", dest="extra_args",
692 help="Extra arguments for the instance's kernel",
693 default=None, type="string", metavar="<PARAMS>"),
694 m_node_opt, m_pri_node_opt, m_sec_node_opt,
695 m_clust_opt, m_inst_opt,
697 "<instance>", "Starts an instance"),
698 'activate-disks': (ActivateDisks, ARGS_ONE, [DEBUG_OPT],
700 "Activate an instance's disks"),
701 'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT],
703 "Deactivate an instance's disks"),
706 if __name__ == '__main__':
707 sys.exit(GenericMain(commands))