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
34 "name", "dtotal", "dfree",
35 "mtotal", "mnode", "mfree",
36 "pinst_cnt", "sinst_cnt",
39 def AddNode(opts, args):
40 """Add node cli-to-processor bridge."""
41 logger.ToStderr("-- WARNING -- \n"
42 "Performing this operation is going to replace the ssh daemon keypair\n"
43 "on the target machine (%s) with the ones of the current one\n"
44 "and grant full intra-cluster ssh root access to/from it\n" % args[0])
45 op = opcodes.OpAddNode(node_name=args[0], secondary_ip=opts.secondary_ip,
50 def ListNodes(opts, args):
51 """List nodes and their properties.
54 if opts.output is None:
55 selected_fields = _LIST_DEF_FIELDS
56 elif opts.output.startswith("+"):
57 selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
59 selected_fields = opts.output.split(",")
61 op = opcodes.OpQueryNodes(output_fields=selected_fields, names=[])
62 output = SubmitOpCode(op)
64 if not opts.no_headers:
66 "name": "Node", "pinst_cnt": "Pinst", "sinst_cnt": "Sinst",
67 "pinst_list": "PriInstances", "sinst_list": "SecInstances",
68 "pip": "PrimaryIP", "sip": "SecondaryIP",
69 "dtotal": "DTotal", "dfree": "DFree",
70 "mtotal": "MTotal", "mnode": "MNode", "mfree": "MFree",
72 "ctotal": "CTotal", "cnodes": "CNodes", "csockets": "CSockets",
78 if opts.human_readable:
79 unitfields = ["dtotal", "dfree", "mtotal", "mnode", "mfree"]
83 numfields = ["dtotal", "dfree",
84 "mtotal", "mnode", "mfree",
85 "pinst_cnt", "sinst_cnt",
88 list_type_fields = ("pinst_list", "sinst_list", "tags")
89 # change raw values to nicer strings
91 for idx, field in enumerate(selected_fields):
93 if field in list_type_fields:
99 data = GenerateTable(separator=opts.separator, headers=headers,
100 fields=selected_fields, unitfields=unitfields,
101 numfields=numfields, data=output)
103 logger.ToStdout(line)
108 def EvacuateNode(opts, args):
109 """Relocate all secondary instance from a node.
113 selected_fields = ["name", "sinst_list"]
114 src_node, dst_node = args
116 op = opcodes.OpQueryNodes(output_fields=selected_fields, names=[src_node])
117 result = SubmitOpCode(op)
118 src_node, sinst = result[0]
119 op = opcodes.OpQueryNodes(output_fields=["name"], names=[dst_node])
120 result = SubmitOpCode(op)
121 dst_node = result[0][0]
123 if src_node == dst_node:
124 raise errors.OpPrereqError("Evacuate node needs different source and"
125 " target nodes (node %s given twice)" %
129 logger.ToStderr("No secondary instances on node %s, exiting." % src_node)
130 return constants.EXIT_SUCCESS
132 sinst = utils.NiceSort(sinst)
134 retcode = constants.EXIT_SUCCESS
136 if not force and not AskUser("Relocate instance(s) %s from node\n"
137 " %s to node\n %s?" %
138 (",".join("'%s'" % name for name in sinst),
139 src_node, dst_node)):
140 return constants.EXIT_CONFIRMATION
142 good_cnt = bad_cnt = 0
144 op = opcodes.OpReplaceDisks(instance_name=iname,
145 remote_node=dst_node,
146 mode=constants.REPLACE_DISK_ALL,
147 disks=["sda", "sdb"])
149 logger.ToStdout("Replacing disks for instance %s" % iname)
151 logger.ToStdout("Instance %s has been relocated" % iname)
153 except errors.GenericError, err:
154 nret, msg = FormatError(err)
156 logger.ToStderr("Error replacing disks for instance %s: %s" %
160 if retcode == constants.EXIT_SUCCESS:
161 logger.ToStdout("All %d instance(s) relocated successfully." % good_cnt)
163 logger.ToStdout("There were errors during the relocation:\n"
164 "%d error(s) out of %d instance(s)." %
165 (bad_cnt, good_cnt + bad_cnt))
169 def FailoverNode(opts, args):
170 """Failover all primary instance on a node.
174 selected_fields = ["name", "pinst_list"]
176 op = opcodes.OpQueryNodes(output_fields=selected_fields, names=args)
177 result = SubmitOpCode(op)
178 node, pinst = result[0]
181 logger.ToStderr("No primary instances on node %s, exiting." % node)
184 pinst = utils.NiceSort(pinst)
188 if not force and not AskUser("Fail over instance(s) %s?" %
189 (",".join("'%s'" % name for name in pinst))):
192 good_cnt = bad_cnt = 0
194 op = opcodes.OpFailoverInstance(instance_name=iname,
195 ignore_consistency=opts.ignore_consistency)
197 logger.ToStdout("Failing over instance %s" % iname)
199 logger.ToStdout("Instance %s has been failed over" % iname)
201 except errors.GenericError, err:
202 nret, msg = FormatError(err)
204 logger.ToStderr("Error failing over instance %s: %s" % (iname, msg))
208 logger.ToStdout("All %d instance(s) failed over successfully." % good_cnt)
210 logger.ToStdout("There were errors during the failover:\n"
211 "%d error(s) out of %d instance(s)." %
212 (bad_cnt, good_cnt + bad_cnt))
216 def MigrateNode(opts, args):
217 """Migrate all primary instance on a node.
221 selected_fields = ["name", "pinst_list"]
223 op = opcodes.OpQueryNodes(output_fields=selected_fields, names=args)
224 result = SubmitOpCode(op)
225 node, pinst = result[0]
228 logger.ToStderr("No primary instances on node %s, exiting." % node)
231 pinst = utils.NiceSort(pinst)
235 if not force and not AskUser("Fail over instance(s) %s?" %
236 (",".join("'%s'" % name for name in pinst))):
239 good_cnt = bad_cnt = 0
241 op = opcodes.OpMigrateInstance(instance_name=iname, live=opts.live,
244 logger.ToStdout("Migrating instance %s" % iname)
246 logger.ToStdout("Instance %s has been migrated" % iname)
248 except errors.GenericError, err:
249 nret, msg = FormatError(err)
251 logger.ToStderr("Error migrating instance %s: %s" % (iname, msg))
255 logger.ToStdout("All %d instance(s) migrated successfully." % good_cnt)
257 logger.ToStdout("There were errors during the migration:\n"
258 "%d error(s) out of %d instance(s)." %
259 (bad_cnt, good_cnt + bad_cnt))
263 def ShowNodeConfig(opts, args):
264 """Show node information.
267 op = opcodes.OpQueryNodes(output_fields=["name", "pip", "sip",
268 "pinst_list", "sinst_list"],
270 result = SubmitOpCode(op)
272 for name, primary_ip, secondary_ip, pinst, sinst in result:
273 logger.ToStdout("Node name: %s" % name)
274 logger.ToStdout(" primary ip: %s" % primary_ip)
275 logger.ToStdout(" secondary ip: %s" % secondary_ip)
277 logger.ToStdout(" primary for instances:")
279 logger.ToStdout(" - %s" % iname)
281 logger.ToStdout(" primary for no instances")
283 logger.ToStdout(" secondary for instances:")
285 logger.ToStdout(" - %s" % iname)
287 logger.ToStdout(" secondary for no instances")
292 def RemoveNode(opts, args):
293 """Remove node cli-to-processor bridge."""
294 op = opcodes.OpRemoveNode(node_name=args[0])
298 def ListVolumes(opts, args):
299 """List logical volumes on node(s).
302 if opts.output is None:
303 selected_fields = ["node", "phys", "vg",
304 "name", "size", "instance"]
306 selected_fields = opts.output.split(",")
308 op = opcodes.OpQueryNodeVolumes(nodes=args, output_fields=selected_fields)
309 output = SubmitOpCode(op)
311 if not opts.no_headers:
312 headers = {"node": "Node", "phys": "PhysDev",
313 "vg": "VG", "name": "Name",
314 "size": "Size", "instance": "Instance"}
318 if opts.human_readable:
319 unitfields = ["size"]
325 data = GenerateTable(separator=opts.separator, headers=headers,
326 fields=selected_fields, unitfields=unitfields,
327 numfields=numfields, data=output)
330 logger.ToStdout(line)
336 'add': (AddNode, ARGS_ONE,
338 make_option("-s", "--secondary-ip", dest="secondary_ip",
339 help="Specify the secondary ip for the node",
340 metavar="ADDRESS", default=None),
341 make_option("--readd", dest="readd",
342 default=False, action="store_true",
343 help="Readd old node after replacing it"),
345 "[-s ip] [--readd] <node_name>", "Add a node to the cluster"),
346 'evacuate': (EvacuateNode, ARGS_FIXED(2),
347 [DEBUG_OPT, FORCE_OPT],
349 "Relocate the secondary instances from the first node"
350 " to the second one (only for instances of type remote_raid1"
352 'failover': (FailoverNode, ARGS_ONE,
353 [DEBUG_OPT, FORCE_OPT,
354 make_option("--ignore-consistency", dest="ignore_consistency",
355 action="store_true", default=False,
356 help="Ignore the consistency of the disks on"
360 "Stops the primary instances on a node and start them on their"
361 " secondary node (only for instances of type remote_raid1"
363 'migrate': (MigrateNode, ARGS_ONE,
364 [DEBUG_OPT, FORCE_OPT,
365 make_option("--non-live", dest="live",
366 default=True, action="store_false",
367 help="Do a non-live migration (this usually means"
368 " freeze the instance, save the state,"
369 " transfer and only then resume running on the"
373 "Migrate all the primary instance on a node away from it"
374 " (only for instances of type drbd)"),
375 'info': (ShowNodeConfig, ARGS_ANY, [DEBUG_OPT],
376 "[<node_name>...]", "Show information about the node(s)"),
377 'list': (ListNodes, ARGS_NONE,
378 [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
379 "", "Lists the nodes in the cluster. The available fields"
380 " are (see the man page for details): name, pinst_cnt, pinst_list,"
381 " sinst_cnt, sinst_list, pip, sip, dtotal, dfree, mtotal, mnode,"
382 " mfree, bootid, ctotal, cnodes, csockets."
383 " The default field list is"
384 " (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
386 'remove': (RemoveNode, ARGS_ONE, [DEBUG_OPT],
387 "<node_name>", "Removes a node from the cluster"),
388 'volumes': (ListVolumes, ARGS_ANY,
389 [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
390 "[<node_name>...]", "List logical volumes on node(s)"),
391 'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
392 "<node_name>", "List the tags of the given node"),
393 'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
394 "<node_name> tag...", "Add tags to the given node"),
395 'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
396 "<node_name> tag...", "Remove tags from the given node"),
400 if __name__ == '__main__':
401 sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_NODE}))