Add --readd option to “gnt-node add”
[ganeti-local] / scripts / gnt-node
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2006, 2007, 2008 Google Inc.
5 #
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.
10 #
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.
15 #
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
19 # 02110-1301, USA.
20
21
22 import sys
23 from optparse import make_option
24
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
32
33 def AddNode(opts, args):
34   """Add node cli-to-processor bridge."""
35   logger.ToStderr("-- WARNING -- \n"
36     "Performing this operation is going to replace the ssh daemon keypair\n"
37     "on the target machine (%s) with the ones of the current one\n"
38     "and grant full intra-cluster ssh root access to/from it\n" % args[0])
39   op = opcodes.OpAddNode(node_name=args[0], secondary_ip=opts.secondary_ip,
40                          readd=opts.readd)
41   SubmitOpCode(op)
42
43
44 def ListNodes(opts, args):
45   """List nodes and their properties.
46
47   """
48   if opts.output is None:
49     selected_fields = ["name", "dtotal", "dfree",
50                        "mtotal", "mnode", "mfree",
51                        "pinst_cnt", "sinst_cnt"]
52   else:
53     selected_fields = opts.output.split(",")
54
55   op = opcodes.OpQueryNodes(output_fields=selected_fields, names=[])
56   output = SubmitOpCode(op)
57
58   if not opts.no_headers:
59     headers = {"name": "Node", "pinst_cnt": "Pinst", "sinst_cnt": "Sinst",
60                "pinst_list": "PriInstances", "sinst_list": "SecInstances",
61                "pip": "PrimaryIP", "sip": "SecondaryIP",
62                "dtotal": "DTotal", "dfree": "DFree",
63                "mtotal": "MTotal", "mnode": "MNode", "mfree": "MFree",
64                "bootid": "BootID"}
65   else:
66     headers = None
67
68   if opts.human_readable:
69     unitfields = ["dtotal", "dfree", "mtotal", "mnode", "mfree"]
70   else:
71     unitfields = None
72
73   numfields = ["dtotal", "dfree",
74                "mtotal", "mnode", "mfree",
75                "pinst_cnt", "sinst_cnt"]
76
77   # change raw values to nicer strings
78   for row in output:
79     for idx, field in enumerate(selected_fields):
80       val = row[idx]
81       if field == "pinst_list":
82         val = ",".join(val)
83       elif field == "sinst_list":
84         val = ",".join(val)
85       elif val is None:
86         val = "?"
87       row[idx] = str(val)
88
89   data = GenerateTable(separator=opts.separator, headers=headers,
90                        fields=selected_fields, unitfields=unitfields,
91                        numfields=numfields, data=output)
92   for line in data:
93     logger.ToStdout(line)
94
95   return 0
96
97
98 def EvacuateNode(opts, args):
99   """Relocate all secondary instance from a node.
100
101   """
102   force = opts.force
103   selected_fields = ["name", "sinst_list"]
104   src_node, dst_node = args
105
106   op = opcodes.OpQueryNodes(output_fields=selected_fields, names=[src_node])
107   result = SubmitOpCode(op)
108   src_node, sinst = result[0]
109   op = opcodes.OpQueryNodes(output_fields=["name"], names=[dst_node])
110   result = SubmitOpCode(op)
111   dst_node = result[0][0]
112
113   if src_node == dst_node:
114     raise errors.OpPrereqError("Evacuate node needs different source and"
115                                " target nodes (node %s given twice)" %
116                                src_node)
117
118   if not sinst:
119     logger.ToStderr("No secondary instances on node %s, exiting." % src_node)
120     return constants.EXIT_SUCCESS
121
122   sinst = utils.NiceSort(sinst)
123
124   retcode = constants.EXIT_SUCCESS
125
126   if not force and not AskUser("Relocate instance(s) %s from node\n"
127                                " %s to node\n %s?" %
128                                (",".join("'%s'" % name for name in sinst),
129                                src_node, dst_node)):
130     return constants.EXIT_CONFIRMATION
131
132   good_cnt = bad_cnt = 0
133   for iname in sinst:
134     op = opcodes.OpReplaceDisks(instance_name=iname,
135                                 remote_node=dst_node,
136                                 mode=constants.REPLACE_DISK_ALL,
137                                 disks=["sda", "sdb"])
138     try:
139       logger.ToStdout("Replacing disks for instance %s" % iname)
140       SubmitOpCode(op)
141       logger.ToStdout("Instance %s has been relocated" % iname)
142       good_cnt += 1
143     except errors.GenericError, err:
144       nret, msg = FormatError(err)
145       retcode |= nret
146       logger.ToStderr("Error replacing disks for instance %s: %s" %
147                       (iname, msg))
148       bad_cnt += 1
149
150   if retcode == constants.EXIT_SUCCESS:
151     logger.ToStdout("All %d instance(s) relocated successfully." % good_cnt)
152   else:
153     logger.ToStdout("There were errors during the relocation:\n"
154                     "%d error(s) out of %d instance(s)." %
155                     (bad_cnt, good_cnt + bad_cnt))
156   return retcode
157
158
159 def FailoverNode(opts, args):
160   """Failover all primary instance on a node.
161
162   """
163   force = opts.force
164   selected_fields = ["name", "pinst_list"]
165
166   op = opcodes.OpQueryNodes(output_fields=selected_fields, names=args)
167   result = SubmitOpCode(op)
168   node, pinst = result[0]
169
170   if not pinst:
171     logger.ToStderr("No primary instances on node %s, exiting." % node)
172     return 0
173
174   pinst = utils.NiceSort(pinst)
175
176   retcode = 0
177
178   if not force and not AskUser("Fail over instance(s) %s?" %
179                                (",".join("'%s'" % name for name in pinst))):
180     return 2
181
182   good_cnt = bad_cnt = 0
183   for iname in pinst:
184     op = opcodes.OpFailoverInstance(instance_name=iname,
185                                     ignore_consistency=opts.ignore_consistency)
186     try:
187       logger.ToStdout("Failing over instance %s" % iname)
188       SubmitOpCode(op)
189       logger.ToStdout("Instance %s has been failed over" % iname)
190       good_cnt += 1
191     except errors.GenericError, err:
192       nret, msg = FormatError(err)
193       retcode |= nret
194       logger.ToStderr("Error failing over instance %s: %s" % (iname, msg))
195       bad_cnt += 1
196
197   if retcode == 0:
198     logger.ToStdout("All %d instance(s) failed over successfully." % good_cnt)
199   else:
200     logger.ToStdout("There were errors during the failover:\n"
201                     "%d error(s) out of %d instance(s)." %
202                     (bad_cnt, good_cnt + bad_cnt))
203   return retcode
204
205
206 def ShowNodeConfig(opts, args):
207   """Show node information.
208
209   """
210   op = opcodes.OpQueryNodes(output_fields=["name", "pip", "sip",
211                                            "pinst_list", "sinst_list"],
212                             names=args)
213   result = SubmitOpCode(op)
214
215   for name, primary_ip, secondary_ip, pinst, sinst in result:
216     logger.ToStdout("Node name: %s" % name)
217     logger.ToStdout("  primary ip: %s" % primary_ip)
218     logger.ToStdout("  secondary ip: %s" % secondary_ip)
219     if pinst:
220       logger.ToStdout("  primary for instances:")
221       for iname in pinst:
222         logger.ToStdout("    - %s" % iname)
223     else:
224       logger.ToStdout("  primary for no instances")
225     if sinst:
226       logger.ToStdout("  secondary for instances:")
227       for iname in sinst:
228         logger.ToStdout("    - %s" % iname)
229     else:
230       logger.ToStdout("  secondary for no instances")
231
232   return 0
233
234
235 def RemoveNode(opts, args):
236   """Remove node cli-to-processor bridge."""
237   op = opcodes.OpRemoveNode(node_name=args[0])
238   SubmitOpCode(op)
239
240
241 def ListVolumes(opts, args):
242   """List logical volumes on node(s).
243
244   """
245   if opts.output is None:
246     selected_fields = ["node", "phys", "vg",
247                        "name", "size", "instance"]
248   else:
249     selected_fields = opts.output.split(",")
250
251   op = opcodes.OpQueryNodeVolumes(nodes=args, output_fields=selected_fields)
252   output = SubmitOpCode(op)
253
254   if not opts.no_headers:
255     headers = {"node": "Node", "phys": "PhysDev",
256                "vg": "VG", "name": "Name",
257                "size": "Size", "instance": "Instance"}
258   else:
259     headers = None
260
261   if opts.human_readable:
262     unitfields = ["size"]
263   else:
264     unitfields = None
265
266   numfields = ["size"]
267
268   data = GenerateTable(separator=opts.separator, headers=headers,
269                        fields=selected_fields, unitfields=unitfields,
270                        numfields=numfields, data=output)
271
272   for line in data:
273     logger.ToStdout(line)
274
275   return 0
276
277
278 commands = {
279   'add': (AddNode, ARGS_ONE,
280           [DEBUG_OPT,
281            make_option("-s", "--secondary-ip", dest="secondary_ip",
282                        help="Specify the secondary ip for the node",
283                        metavar="ADDRESS", default=None),
284            make_option("--readd", dest="readd",
285                        default=False, action="store_true",
286                        help="Readd old node after replacing it"),
287            ],
288           "[-s ip] <node_name>", "Add a node to the cluster"),
289   'evacuate': (EvacuateNode, ARGS_FIXED(2),
290                [DEBUG_OPT, FORCE_OPT],
291                "[-f] <src_node> <dst_node>",
292                "Relocate the secondary instances from the first node"
293                " to the second one (only for instances of type remote_raid1"
294                " drbd)"),
295   'failover': (FailoverNode, ARGS_ONE,
296                [DEBUG_OPT, FORCE_OPT,
297                 make_option("--ignore-consistency", dest="ignore_consistency",
298                             action="store_true", default=False,
299                             help="Ignore the consistency of the disks on"
300                             " the secondary"),
301                 ],
302                "[-f] <node>",
303                "Stops the primary instances on a node and start them on their"
304                " secondary node (only for instances of type remote_raid1)"),
305   'info': (ShowNodeConfig, ARGS_ANY, [DEBUG_OPT],
306            "[<node_name>...]", "Show information about the node(s)"),
307   'list': (ListNodes, ARGS_NONE,
308            [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
309            "", "Lists the nodes in the cluster. The available fields"
310            " are (see the man page for details): name, pinst_cnt, pinst_list,"
311            " sinst_cnt, sinst_list, pip, sip, dtotal, dfree, mtotal, mnode,"
312            " mfree, bootid. The default field list is (in order): name,"
313            " dtotal, dfree, mtotal, mnode, mfree, pinst_cnt, sinst_cnt."),
314   'remove': (RemoveNode, ARGS_ONE, [DEBUG_OPT],
315              "<node_name>", "Removes a node from the cluster"),
316   'volumes': (ListVolumes, ARGS_ANY,
317               [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
318               "[<node_name>...]", "List logical volumes on node(s)"),
319   'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
320                 "<node_name>", "List the tags of the given node"),
321   'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
322                "<node_name> tag...", "Add tags to the given node"),
323   'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
324                   "<node_name> tag...", "Remove tags from the given node"),
325   }
326
327
328 if __name__ == '__main__':
329   sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_NODE}))