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",
94 "serial_no": "SerialNo",
99 if opts.human_readable:
100 unitfields = ["dtotal", "dfree", "mtotal", "mnode", "mfree"]
104 numfields = ["dtotal", "dfree",
105 "mtotal", "mnode", "mfree",
106 "pinst_cnt", "sinst_cnt",
107 "ctotal", "serial_no"]
109 list_type_fields = ("pinst_list", "sinst_list", "tags")
110 # change raw values to nicer strings
112 for idx, field in enumerate(selected_fields):
114 if field in list_type_fields:
120 data = GenerateTable(separator=opts.separator, headers=headers,
121 fields=selected_fields, unitfields=unitfields,
122 numfields=numfields, data=output)
124 logger.ToStdout(line)
129 def EvacuateNode(opts, args):
130 """Relocate all secondary instance from a node.
134 selected_fields = ["name", "sinst_list"]
135 src_node, dst_node = args
137 op = opcodes.OpQueryNodes(output_fields=selected_fields, names=[src_node])
138 result = SubmitOpCode(op)
139 src_node, sinst = result[0]
140 op = opcodes.OpQueryNodes(output_fields=["name"], names=[dst_node])
141 result = SubmitOpCode(op)
142 dst_node = result[0][0]
144 if src_node == dst_node:
145 raise errors.OpPrereqError("Evacuate node needs different source and"
146 " target nodes (node %s given twice)" %
150 logger.ToStderr("No secondary instances on node %s, exiting." % src_node)
151 return constants.EXIT_SUCCESS
153 sinst = utils.NiceSort(sinst)
155 retcode = constants.EXIT_SUCCESS
157 if not force and not AskUser("Relocate instance(s) %s from node\n"
158 " %s to node\n %s?" %
159 (",".join("'%s'" % name for name in sinst),
160 src_node, dst_node)):
161 return constants.EXIT_CONFIRMATION
163 good_cnt = bad_cnt = 0
165 op = opcodes.OpReplaceDisks(instance_name=iname,
166 remote_node=dst_node,
167 mode=constants.REPLACE_DISK_ALL,
168 disks=["sda", "sdb"])
170 logger.ToStdout("Replacing disks for instance %s" % iname)
172 logger.ToStdout("Instance %s has been relocated" % iname)
174 except errors.GenericError, err:
175 nret, msg = FormatError(err)
177 logger.ToStderr("Error replacing disks for instance %s: %s" %
181 if retcode == constants.EXIT_SUCCESS:
182 logger.ToStdout("All %d instance(s) relocated successfully." % good_cnt)
184 logger.ToStdout("There were errors during the relocation:\n"
185 "%d error(s) out of %d instance(s)." %
186 (bad_cnt, good_cnt + bad_cnt))
190 def FailoverNode(opts, args):
191 """Failover all primary instance on a node.
195 selected_fields = ["name", "pinst_list"]
197 op = opcodes.OpQueryNodes(output_fields=selected_fields, names=args)
198 result = SubmitOpCode(op)
199 node, pinst = result[0]
202 logger.ToStderr("No primary instances on node %s, exiting." % node)
205 pinst = utils.NiceSort(pinst)
209 if not force and not AskUser("Fail over instance(s) %s?" %
210 (",".join("'%s'" % name for name in pinst))):
213 good_cnt = bad_cnt = 0
215 op = opcodes.OpFailoverInstance(instance_name=iname,
216 ignore_consistency=opts.ignore_consistency)
218 logger.ToStdout("Failing over instance %s" % iname)
220 logger.ToStdout("Instance %s has been failed over" % iname)
222 except errors.GenericError, err:
223 nret, msg = FormatError(err)
225 logger.ToStderr("Error failing over instance %s: %s" % (iname, msg))
229 logger.ToStdout("All %d instance(s) failed over successfully." % good_cnt)
231 logger.ToStdout("There were errors during the failover:\n"
232 "%d error(s) out of %d instance(s)." %
233 (bad_cnt, good_cnt + bad_cnt))
237 def ShowNodeConfig(opts, args):
238 """Show node information.
241 op = opcodes.OpQueryNodes(output_fields=["name", "pip", "sip",
242 "pinst_list", "sinst_list"],
244 result = SubmitOpCode(op)
246 for name, primary_ip, secondary_ip, pinst, sinst in result:
247 logger.ToStdout("Node name: %s" % name)
248 logger.ToStdout(" primary ip: %s" % primary_ip)
249 logger.ToStdout(" secondary ip: %s" % secondary_ip)
251 logger.ToStdout(" primary for instances:")
253 logger.ToStdout(" - %s" % iname)
255 logger.ToStdout(" primary for no instances")
257 logger.ToStdout(" secondary for instances:")
259 logger.ToStdout(" - %s" % iname)
261 logger.ToStdout(" secondary for no instances")
266 def RemoveNode(opts, args):
267 """Remove node cli-to-processor bridge."""
268 op = opcodes.OpRemoveNode(node_name=args[0])
272 def ListVolumes(opts, args):
273 """List logical volumes on node(s).
276 if opts.output is None:
277 selected_fields = ["node", "phys", "vg",
278 "name", "size", "instance"]
280 selected_fields = opts.output.split(",")
282 op = opcodes.OpQueryNodeVolumes(nodes=args, output_fields=selected_fields)
283 output = SubmitOpCode(op)
285 if not opts.no_headers:
286 headers = {"node": "Node", "phys": "PhysDev",
287 "vg": "VG", "name": "Name",
288 "size": "Size", "instance": "Instance"}
292 if opts.human_readable:
293 unitfields = ["size"]
299 data = GenerateTable(separator=opts.separator, headers=headers,
300 fields=selected_fields, unitfields=unitfields,
301 numfields=numfields, data=output)
304 logger.ToStdout(line)
310 'add': (AddNode, ARGS_ONE,
312 make_option("-s", "--secondary-ip", dest="secondary_ip",
313 help="Specify the secondary ip for the node",
314 metavar="ADDRESS", default=None),
315 make_option("--readd", dest="readd",
316 default=False, action="store_true",
317 help="Readd old node after replacing it"),
318 make_option("--no-ssh-key-check", dest="ssh_key_check",
319 default=True, action="store_false",
320 help="Disable SSH key fingerprint checking"),
322 "[-s ip] [--readd] [--no-ssh-key-check] <node_name>",
323 "Add a node to the cluster"),
324 'evacuate': (EvacuateNode, ARGS_FIXED(2),
325 [DEBUG_OPT, FORCE_OPT],
327 "Relocate the secondary instances from the first node"
328 " to the second one (only for instances with drbd disk template"
330 'failover': (FailoverNode, ARGS_ONE,
331 [DEBUG_OPT, FORCE_OPT,
332 make_option("--ignore-consistency", dest="ignore_consistency",
333 action="store_true", default=False,
334 help="Ignore the consistency of the disks on"
338 "Stops the primary instances on a node and start them on their"
339 " secondary node (only for instances with drbd disk template)"),
340 'info': (ShowNodeConfig, ARGS_ANY, [DEBUG_OPT],
341 "[<node_name>...]", "Show information about the node(s)"),
342 'list': (ListNodes, ARGS_NONE,
343 [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT,
345 "", "Lists the nodes in the cluster. The available fields"
346 " are (see the man page for details): name, pinst_cnt, pinst_list,"
347 " sinst_cnt, sinst_list, pip, sip, dtotal, dfree, mtotal, mnode,"
348 " mfree, bootid, cpu_count, serial_no."
349 " The default field list is"
350 " (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
352 'remove': (RemoveNode, ARGS_ONE, [DEBUG_OPT],
353 "<node_name>", "Removes a node from the cluster"),
354 'volumes': (ListVolumes, ARGS_ANY,
355 [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
356 "[<node_name>...]", "List logical volumes on node(s)"),
357 'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
358 "<node_name>", "List the tags of the given node"),
359 'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
360 "<node_name> tag...", "Add tags to the given node"),
361 'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
362 "<node_name> tag...", "Remove tags from the given node"),
366 if __name__ == '__main__':
367 sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_NODE}))