gnt-node migrate: pass the cleanup option
[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 _LIST_DEF_FIELDS = [
34   "name", "dtotal", "dfree",
35   "mtotal", "mnode", "mfree",
36   "pinst_cnt", "sinst_cnt",
37   ]
38
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,
46                          readd=opts.readd)
47   SubmitOpCode(op)
48
49
50 def ListNodes(opts, args):
51   """List nodes and their properties.
52
53   """
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(",")
58   else:
59     selected_fields = opts.output.split(",")
60
61   op = opcodes.OpQueryNodes(output_fields=selected_fields, names=[])
62   output = SubmitOpCode(op)
63
64   if not opts.no_headers:
65     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",
71       "bootid": "BootID",
72       "ctotal": "CTotal", "cnodes": "CNodes", "csockets": "CSockets",
73       "tags": "Tags",
74       }
75   else:
76     headers = None
77
78   if opts.human_readable:
79     unitfields = ["dtotal", "dfree", "mtotal", "mnode", "mfree"]
80   else:
81     unitfields = None
82
83   numfields = ["dtotal", "dfree",
84                "mtotal", "mnode", "mfree",
85                "pinst_cnt", "sinst_cnt",
86                "ctotal"]
87
88   list_type_fields = ("pinst_list", "sinst_list", "tags")
89   # change raw values to nicer strings
90   for row in output:
91     for idx, field in enumerate(selected_fields):
92       val = row[idx]
93       if field in list_type_fields:
94         val = ",".join(val)
95       elif val is None:
96         val = "?"
97       row[idx] = str(val)
98
99   data = GenerateTable(separator=opts.separator, headers=headers,
100                        fields=selected_fields, unitfields=unitfields,
101                        numfields=numfields, data=output)
102   for line in data:
103     logger.ToStdout(line)
104
105   return 0
106
107
108 def EvacuateNode(opts, args):
109   """Relocate all secondary instance from a node.
110
111   """
112   force = opts.force
113   selected_fields = ["name", "sinst_list"]
114   src_node, dst_node = args
115
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]
122
123   if src_node == dst_node:
124     raise errors.OpPrereqError("Evacuate node needs different source and"
125                                " target nodes (node %s given twice)" %
126                                src_node)
127
128   if not sinst:
129     logger.ToStderr("No secondary instances on node %s, exiting." % src_node)
130     return constants.EXIT_SUCCESS
131
132   sinst = utils.NiceSort(sinst)
133
134   retcode = constants.EXIT_SUCCESS
135
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
141
142   good_cnt = bad_cnt = 0
143   for iname in sinst:
144     op = opcodes.OpReplaceDisks(instance_name=iname,
145                                 remote_node=dst_node,
146                                 mode=constants.REPLACE_DISK_ALL,
147                                 disks=["sda", "sdb"])
148     try:
149       logger.ToStdout("Replacing disks for instance %s" % iname)
150       SubmitOpCode(op)
151       logger.ToStdout("Instance %s has been relocated" % iname)
152       good_cnt += 1
153     except errors.GenericError, err:
154       nret, msg = FormatError(err)
155       retcode |= nret
156       logger.ToStderr("Error replacing disks for instance %s: %s" %
157                       (iname, msg))
158       bad_cnt += 1
159
160   if retcode == constants.EXIT_SUCCESS:
161     logger.ToStdout("All %d instance(s) relocated successfully." % good_cnt)
162   else:
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))
166   return retcode
167
168
169 def FailoverNode(opts, args):
170   """Failover all primary instance on a node.
171
172   """
173   force = opts.force
174   selected_fields = ["name", "pinst_list"]
175
176   op = opcodes.OpQueryNodes(output_fields=selected_fields, names=args)
177   result = SubmitOpCode(op)
178   node, pinst = result[0]
179
180   if not pinst:
181     logger.ToStderr("No primary instances on node %s, exiting." % node)
182     return 0
183
184   pinst = utils.NiceSort(pinst)
185
186   retcode = 0
187
188   if not force and not AskUser("Fail over instance(s) %s?" %
189                                (",".join("'%s'" % name for name in pinst))):
190     return 2
191
192   good_cnt = bad_cnt = 0
193   for iname in pinst:
194     op = opcodes.OpFailoverInstance(instance_name=iname,
195                                     ignore_consistency=opts.ignore_consistency)
196     try:
197       logger.ToStdout("Failing over instance %s" % iname)
198       SubmitOpCode(op)
199       logger.ToStdout("Instance %s has been failed over" % iname)
200       good_cnt += 1
201     except errors.GenericError, err:
202       nret, msg = FormatError(err)
203       retcode |= nret
204       logger.ToStderr("Error failing over instance %s: %s" % (iname, msg))
205       bad_cnt += 1
206
207   if retcode == 0:
208     logger.ToStdout("All %d instance(s) failed over successfully." % good_cnt)
209   else:
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))
213   return retcode
214
215
216 def MigrateNode(opts, args):
217   """Migrate all primary instance on a node.
218
219   """
220   force = opts.force
221   selected_fields = ["name", "pinst_list"]
222
223   op = opcodes.OpQueryNodes(output_fields=selected_fields, names=args)
224   result = SubmitOpCode(op)
225   node, pinst = result[0]
226
227   if not pinst:
228     logger.ToStderr("No primary instances on node %s, exiting." % node)
229     return 0
230
231   pinst = utils.NiceSort(pinst)
232
233   retcode = 0
234
235   if not force and not AskUser("Fail over instance(s) %s?" %
236                                (",".join("'%s'" % name for name in pinst))):
237     return 2
238
239   good_cnt = bad_cnt = 0
240   for iname in pinst:
241     op = opcodes.OpMigrateInstance(instance_name=iname, live=opts.live,
242                                    cleanup=False)
243     try:
244       logger.ToStdout("Migrating instance %s" % iname)
245       SubmitOpCode(op)
246       logger.ToStdout("Instance %s has been migrated" % iname)
247       good_cnt += 1
248     except errors.GenericError, err:
249       nret, msg = FormatError(err)
250       retcode |= nret
251       logger.ToStderr("Error migrating instance %s: %s" % (iname, msg))
252       bad_cnt += 1
253
254   if retcode == 0:
255     logger.ToStdout("All %d instance(s) migrated successfully." % good_cnt)
256   else:
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))
260   return retcode
261
262
263 def ShowNodeConfig(opts, args):
264   """Show node information.
265
266   """
267   op = opcodes.OpQueryNodes(output_fields=["name", "pip", "sip",
268                                            "pinst_list", "sinst_list"],
269                             names=args)
270   result = SubmitOpCode(op)
271
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)
276     if pinst:
277       logger.ToStdout("  primary for instances:")
278       for iname in pinst:
279         logger.ToStdout("    - %s" % iname)
280     else:
281       logger.ToStdout("  primary for no instances")
282     if sinst:
283       logger.ToStdout("  secondary for instances:")
284       for iname in sinst:
285         logger.ToStdout("    - %s" % iname)
286     else:
287       logger.ToStdout("  secondary for no instances")
288
289   return 0
290
291
292 def RemoveNode(opts, args):
293   """Remove node cli-to-processor bridge."""
294   op = opcodes.OpRemoveNode(node_name=args[0])
295   SubmitOpCode(op)
296
297
298 def ListVolumes(opts, args):
299   """List logical volumes on node(s).
300
301   """
302   if opts.output is None:
303     selected_fields = ["node", "phys", "vg",
304                        "name", "size", "instance"]
305   else:
306     selected_fields = opts.output.split(",")
307
308   op = opcodes.OpQueryNodeVolumes(nodes=args, output_fields=selected_fields)
309   output = SubmitOpCode(op)
310
311   if not opts.no_headers:
312     headers = {"node": "Node", "phys": "PhysDev",
313                "vg": "VG", "name": "Name",
314                "size": "Size", "instance": "Instance"}
315   else:
316     headers = None
317
318   if opts.human_readable:
319     unitfields = ["size"]
320   else:
321     unitfields = None
322
323   numfields = ["size"]
324
325   data = GenerateTable(separator=opts.separator, headers=headers,
326                        fields=selected_fields, unitfields=unitfields,
327                        numfields=numfields, data=output)
328
329   for line in data:
330     logger.ToStdout(line)
331
332   return 0
333
334
335 commands = {
336   'add': (AddNode, ARGS_ONE,
337           [DEBUG_OPT,
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"),
344            ],
345           "[-s ip] [--readd] <node_name>", "Add a node to the cluster"),
346   'evacuate': (EvacuateNode, ARGS_FIXED(2),
347                [DEBUG_OPT, FORCE_OPT],
348                "[-f] <src> <dst>",
349                "Relocate the secondary instances from the first node"
350                " to the second one (only for instances of type remote_raid1"
351                " or drbd)"),
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"
357                             " the secondary"),
358                 ],
359                "[-f] <node>",
360                "Stops the primary instances on a node and start them on their"
361                " secondary node (only for instances of type remote_raid1"
362                " or drbd)"),
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"
370                             " secondary node)"),
371                 ],
372                "[-f] <node>",
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),
385            ),
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"),
397   }
398
399
400 if __name__ == '__main__':
401   sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_NODE}))