4 # Copyright (C) 2006, 2007, 2008 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
23 from optparse import make_option
25 from ganeti.cli import *
26 from ganeti import opcodes
27 from ganeti import logger
28 from ganeti import utils
29 from ganeti import constants
30 from ganeti import errors
31 from ganeti import bootstrap
35 "name", "dtotal", "dfree",
36 "mtotal", "mnode", "mfree",
37 "pinst_cnt", "sinst_cnt",
41 def AddNode(opts, args):
42 """Add node cli-to-processor bridge.
45 dns_data = utils.HostInfo(args[0])
49 op = opcodes.OpQueryNodes(output_fields=['name'], names=[node])
51 output = SubmitOpCode(op)
52 except (errors.OpPrereqError, errors.OpExecError):
55 logger.ToStderr("Node %s already in the cluster (as %s)"
56 " - please use --readd" % (args[0], output[0][0]))
59 logger.ToStderr("-- WARNING -- \n"
60 "Performing this operation is going to replace the ssh daemon keypair\n"
61 "on the target machine (%s) with the ones of the current one\n"
62 "and grant full intra-cluster ssh root access to/from it\n" % node)
64 bootstrap.SetupNodeDaemon(node, opts.ssh_key_check)
66 op = opcodes.OpAddNode(node_name=args[0], secondary_ip=opts.secondary_ip,
71 def ListNodes(opts, args):
72 """List nodes and their properties.
75 if opts.output is None:
76 selected_fields = _LIST_DEF_FIELDS
77 elif opts.output.startswith("+"):
78 selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
80 selected_fields = opts.output.split(",")
82 output = GetClient().QueryNodes([], selected_fields)
84 if not opts.no_headers:
86 "name": "Node", "pinst_cnt": "Pinst", "sinst_cnt": "Sinst",
87 "pinst_list": "PriInstances", "sinst_list": "SecInstances",
88 "pip": "PrimaryIP", "sip": "SecondaryIP",
89 "dtotal": "DTotal", "dfree": "DFree",
90 "mtotal": "MTotal", "mnode": "MNode", "mfree": "MFree",
98 if opts.human_readable:
99 unitfields = ["dtotal", "dfree", "mtotal", "mnode", "mfree"]
103 numfields = ["dtotal", "dfree",
104 "mtotal", "mnode", "mfree",
105 "pinst_cnt", "sinst_cnt",
108 list_type_fields = ("pinst_list", "sinst_list", "tags")
109 # change raw values to nicer strings
111 for idx, field in enumerate(selected_fields):
113 if field in list_type_fields:
119 data = GenerateTable(separator=opts.separator, headers=headers,
120 fields=selected_fields, unitfields=unitfields,
121 numfields=numfields, data=output)
123 logger.ToStdout(line)
128 def EvacuateNode(opts, args):
129 """Relocate all secondary instance from a node.
133 selected_fields = ["name", "sinst_list"]
134 src_node, dst_node = args
136 op = opcodes.OpQueryNodes(output_fields=selected_fields, names=[src_node])
137 result = SubmitOpCode(op)
138 src_node, sinst = result[0]
139 op = opcodes.OpQueryNodes(output_fields=["name"], names=[dst_node])
140 result = SubmitOpCode(op)
141 dst_node = result[0][0]
143 if src_node == dst_node:
144 raise errors.OpPrereqError("Evacuate node needs different source and"
145 " target nodes (node %s given twice)" %
149 logger.ToStderr("No secondary instances on node %s, exiting." % src_node)
150 return constants.EXIT_SUCCESS
152 sinst = utils.NiceSort(sinst)
154 retcode = constants.EXIT_SUCCESS
156 if not force and not AskUser("Relocate instance(s) %s from node\n"
157 " %s to node\n %s?" %
158 (",".join("'%s'" % name for name in sinst),
159 src_node, dst_node)):
160 return constants.EXIT_CONFIRMATION
162 good_cnt = bad_cnt = 0
164 op = opcodes.OpReplaceDisks(instance_name=iname,
165 remote_node=dst_node,
166 mode=constants.REPLACE_DISK_ALL,
167 disks=["sda", "sdb"])
169 logger.ToStdout("Replacing disks for instance %s" % iname)
171 logger.ToStdout("Instance %s has been relocated" % iname)
173 except errors.GenericError, err:
174 nret, msg = FormatError(err)
176 logger.ToStderr("Error replacing disks for instance %s: %s" %
180 if retcode == constants.EXIT_SUCCESS:
181 logger.ToStdout("All %d instance(s) relocated successfully." % good_cnt)
183 logger.ToStdout("There were errors during the relocation:\n"
184 "%d error(s) out of %d instance(s)." %
185 (bad_cnt, good_cnt + bad_cnt))
189 def FailoverNode(opts, args):
190 """Failover all primary instance on a node.
194 selected_fields = ["name", "pinst_list"]
196 op = opcodes.OpQueryNodes(output_fields=selected_fields, names=args)
197 result = SubmitOpCode(op)
198 node, pinst = result[0]
201 logger.ToStderr("No primary instances on node %s, exiting." % node)
204 pinst = utils.NiceSort(pinst)
208 if not force and not AskUser("Fail over instance(s) %s?" %
209 (",".join("'%s'" % name for name in pinst))):
212 good_cnt = bad_cnt = 0
214 op = opcodes.OpFailoverInstance(instance_name=iname,
215 ignore_consistency=opts.ignore_consistency)
217 logger.ToStdout("Failing over instance %s" % iname)
219 logger.ToStdout("Instance %s has been failed over" % iname)
221 except errors.GenericError, err:
222 nret, msg = FormatError(err)
224 logger.ToStderr("Error failing over instance %s: %s" % (iname, msg))
228 logger.ToStdout("All %d instance(s) failed over successfully." % good_cnt)
230 logger.ToStdout("There were errors during the failover:\n"
231 "%d error(s) out of %d instance(s)." %
232 (bad_cnt, good_cnt + bad_cnt))
236 def ShowNodeConfig(opts, args):
237 """Show node information.
240 op = opcodes.OpQueryNodes(output_fields=["name", "pip", "sip",
241 "pinst_list", "sinst_list"],
243 result = SubmitOpCode(op)
245 for name, primary_ip, secondary_ip, pinst, sinst in result:
246 logger.ToStdout("Node name: %s" % name)
247 logger.ToStdout(" primary ip: %s" % primary_ip)
248 logger.ToStdout(" secondary ip: %s" % secondary_ip)
250 logger.ToStdout(" primary for instances:")
252 logger.ToStdout(" - %s" % iname)
254 logger.ToStdout(" primary for no instances")
256 logger.ToStdout(" secondary for instances:")
258 logger.ToStdout(" - %s" % iname)
260 logger.ToStdout(" secondary for no instances")
265 def RemoveNode(opts, args):
266 """Remove node cli-to-processor bridge."""
267 op = opcodes.OpRemoveNode(node_name=args[0])
271 def ListVolumes(opts, args):
272 """List logical volumes on node(s).
275 if opts.output is None:
276 selected_fields = ["node", "phys", "vg",
277 "name", "size", "instance"]
279 selected_fields = opts.output.split(",")
281 op = opcodes.OpQueryNodeVolumes(nodes=args, output_fields=selected_fields)
282 output = SubmitOpCode(op)
284 if not opts.no_headers:
285 headers = {"node": "Node", "phys": "PhysDev",
286 "vg": "VG", "name": "Name",
287 "size": "Size", "instance": "Instance"}
291 if opts.human_readable:
292 unitfields = ["size"]
298 data = GenerateTable(separator=opts.separator, headers=headers,
299 fields=selected_fields, unitfields=unitfields,
300 numfields=numfields, data=output)
303 logger.ToStdout(line)
309 'add': (AddNode, ARGS_ONE,
311 make_option("-s", "--secondary-ip", dest="secondary_ip",
312 help="Specify the secondary ip for the node",
313 metavar="ADDRESS", default=None),
314 make_option("--readd", dest="readd",
315 default=False, action="store_true",
316 help="Readd old node after replacing it"),
317 make_option("--no-ssh-key-check", dest="ssh_key_check",
318 default=True, action="store_false",
319 help="Disable SSH key fingerprint checking"),
321 "[-s ip] [--readd] [--no-ssh-key-check] <node_name>",
322 "Add a node to the cluster"),
323 'evacuate': (EvacuateNode, ARGS_FIXED(2),
324 [DEBUG_OPT, FORCE_OPT],
326 "Relocate the secondary instances from the first node"
327 " to the second one (only for instances with drbd disk template"
329 'failover': (FailoverNode, ARGS_ONE,
330 [DEBUG_OPT, FORCE_OPT,
331 make_option("--ignore-consistency", dest="ignore_consistency",
332 action="store_true", default=False,
333 help="Ignore the consistency of the disks on"
337 "Stops the primary instances on a node and start them on their"
338 " secondary node (only for instances with drbd disk template)"),
339 'info': (ShowNodeConfig, ARGS_ANY, [DEBUG_OPT],
340 "[<node_name>...]", "Show information about the node(s)"),
341 'list': (ListNodes, ARGS_NONE,
342 [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT,
344 "", "Lists the nodes in the cluster. The available fields"
345 " are (see the man page for details): name, pinst_cnt, pinst_list,"
346 " sinst_cnt, sinst_list, pip, sip, dtotal, dfree, mtotal, mnode,"
347 " mfree, bootid, cpu_count. The default field list is"
348 " (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
350 'remove': (RemoveNode, ARGS_ONE, [DEBUG_OPT],
351 "<node_name>", "Removes a node from the cluster"),
352 'volumes': (ListVolumes, ARGS_ANY,
353 [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
354 "[<node_name>...]", "List logical volumes on node(s)"),
355 'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
356 "<node_name>", "List the tags of the given node"),
357 'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
358 "<node_name> tag...", "Add tags to the given node"),
359 'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
360 "<node_name> tag...", "Remove tags from the given node"),
364 if __name__ == '__main__':
365 sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_NODE}))