Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-node @ 05ccd983

History | View | Annotate | Download (11.6 kB)

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

    
42
  """
43
  dns_data = utils.HostInfo(args[0])
44
  node = dns_data.name
45

    
46
  if not opts.readd:
47
    op = opcodes.OpQueryNodes(output_fields=['name'], names=[node])
48
    try:
49
      output = SubmitOpCode(op)
50
    except (errors.OpPrereqError, errors.OpExecError):
51
      pass
52
    else:
53
      logger.ToStderr("Node %s already in the cluster (as %s)"
54
                      " - please use --readd" % (args[0], output[0][0]))
55
      return 1
56

    
57
  logger.ToStderr("-- WARNING -- \n"
58
    "Performing this operation is going to replace the ssh daemon keypair\n"
59
    "on the target machine (%s) with the ones of the current one\n"
60
    "and grant full intra-cluster ssh root access to/from it\n" % node)
61

    
62
  op = opcodes.OpAddNode(node_name=args[0], secondary_ip=opts.secondary_ip,
63
                         readd=opts.readd)
64
  SubmitOpCode(op)
65

    
66

    
67
def ListNodes(opts, args):
68
  """List nodes and their properties.
69

    
70
  """
71
  if opts.output is None:
72
    selected_fields = _LIST_DEF_FIELDS
73
  elif opts.output.startswith("+"):
74
    selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
75
  else:
76
    selected_fields = opts.output.split(",")
77

    
78
  op = opcodes.OpQueryNodes(output_fields=selected_fields, names=[])
79
  output = SubmitOpCode(op)
80

    
81
  if not opts.no_headers:
82
    headers = {
83
      "name": "Node", "pinst_cnt": "Pinst", "sinst_cnt": "Sinst",
84
      "pinst_list": "PriInstances", "sinst_list": "SecInstances",
85
      "pip": "PrimaryIP", "sip": "SecondaryIP",
86
      "dtotal": "DTotal", "dfree": "DFree",
87
      "mtotal": "MTotal", "mnode": "MNode", "mfree": "MFree",
88
      "bootid": "BootID",
89
      "ctotal": "CTotal",
90
      "tags": "Tags",
91
      }
92
  else:
93
    headers = None
94

    
95
  if opts.human_readable:
96
    unitfields = ["dtotal", "dfree", "mtotal", "mnode", "mfree"]
97
  else:
98
    unitfields = None
99

    
100
  numfields = ["dtotal", "dfree",
101
               "mtotal", "mnode", "mfree",
102
               "pinst_cnt", "sinst_cnt",
103
               "ctotal"]
104

    
105
  list_type_fields = ("pinst_list", "sinst_list", "tags")
106
  # change raw values to nicer strings
107
  for row in output:
108
    for idx, field in enumerate(selected_fields):
109
      val = row[idx]
110
      if field in list_type_fields:
111
        val = ",".join(val)
112
      elif val is None:
113
        val = "?"
114
      row[idx] = str(val)
115

    
116
  data = GenerateTable(separator=opts.separator, headers=headers,
117
                       fields=selected_fields, unitfields=unitfields,
118
                       numfields=numfields, data=output)
119
  for line in data:
120
    logger.ToStdout(line)
121

    
122
  return 0
123

    
124

    
125
def EvacuateNode(opts, args):
126
  """Relocate all secondary instance from a node.
127

    
128
  """
129
  force = opts.force
130
  selected_fields = ["name", "sinst_list"]
131
  src_node, dst_node = args
132

    
133
  op = opcodes.OpQueryNodes(output_fields=selected_fields, names=[src_node])
134
  result = SubmitOpCode(op)
135
  src_node, sinst = result[0]
136
  op = opcodes.OpQueryNodes(output_fields=["name"], names=[dst_node])
137
  result = SubmitOpCode(op)
138
  dst_node = result[0][0]
139

    
140
  if src_node == dst_node:
141
    raise errors.OpPrereqError("Evacuate node needs different source and"
142
                               " target nodes (node %s given twice)" %
143
                               src_node)
144

    
145
  if not sinst:
146
    logger.ToStderr("No secondary instances on node %s, exiting." % src_node)
147
    return constants.EXIT_SUCCESS
148

    
149
  sinst = utils.NiceSort(sinst)
150

    
151
  retcode = constants.EXIT_SUCCESS
152

    
153
  if not force and not AskUser("Relocate instance(s) %s from node\n"
154
                               " %s to node\n %s?" %
155
                               (",".join("'%s'" % name for name in sinst),
156
                               src_node, dst_node)):
157
    return constants.EXIT_CONFIRMATION
158

    
159
  good_cnt = bad_cnt = 0
160
  for iname in sinst:
161
    op = opcodes.OpReplaceDisks(instance_name=iname,
162
                                remote_node=dst_node,
163
                                mode=constants.REPLACE_DISK_ALL,
164
                                disks=["sda", "sdb"])
165
    try:
166
      logger.ToStdout("Replacing disks for instance %s" % iname)
167
      SubmitOpCode(op)
168
      logger.ToStdout("Instance %s has been relocated" % iname)
169
      good_cnt += 1
170
    except errors.GenericError, err:
171
      nret, msg = FormatError(err)
172
      retcode |= nret
173
      logger.ToStderr("Error replacing disks for instance %s: %s" %
174
                      (iname, msg))
175
      bad_cnt += 1
176

    
177
  if retcode == constants.EXIT_SUCCESS:
178
    logger.ToStdout("All %d instance(s) relocated successfully." % good_cnt)
179
  else:
180
    logger.ToStdout("There were errors during the relocation:\n"
181
                    "%d error(s) out of %d instance(s)." %
182
                    (bad_cnt, good_cnt + bad_cnt))
183
  return retcode
184

    
185

    
186
def FailoverNode(opts, args):
187
  """Failover all primary instance on a node.
188

    
189
  """
190
  force = opts.force
191
  selected_fields = ["name", "pinst_list"]
192

    
193
  op = opcodes.OpQueryNodes(output_fields=selected_fields, names=args)
194
  result = SubmitOpCode(op)
195
  node, pinst = result[0]
196

    
197
  if not pinst:
198
    logger.ToStderr("No primary instances on node %s, exiting." % node)
199
    return 0
200

    
201
  pinst = utils.NiceSort(pinst)
202

    
203
  retcode = 0
204

    
205
  if not force and not AskUser("Fail over instance(s) %s?" %
206
                               (",".join("'%s'" % name for name in pinst))):
207
    return 2
208

    
209
  good_cnt = bad_cnt = 0
210
  for iname in pinst:
211
    op = opcodes.OpFailoverInstance(instance_name=iname,
212
                                    ignore_consistency=opts.ignore_consistency)
213
    try:
214
      logger.ToStdout("Failing over instance %s" % iname)
215
      SubmitOpCode(op)
216
      logger.ToStdout("Instance %s has been failed over" % iname)
217
      good_cnt += 1
218
    except errors.GenericError, err:
219
      nret, msg = FormatError(err)
220
      retcode |= nret
221
      logger.ToStderr("Error failing over instance %s: %s" % (iname, msg))
222
      bad_cnt += 1
223

    
224
  if retcode == 0:
225
    logger.ToStdout("All %d instance(s) failed over successfully." % good_cnt)
226
  else:
227
    logger.ToStdout("There were errors during the failover:\n"
228
                    "%d error(s) out of %d instance(s)." %
229
                    (bad_cnt, good_cnt + bad_cnt))
230
  return retcode
231

    
232

    
233
def ShowNodeConfig(opts, args):
234
  """Show node information.
235

    
236
  """
237
  op = opcodes.OpQueryNodes(output_fields=["name", "pip", "sip",
238
                                           "pinst_list", "sinst_list"],
239
                            names=args)
240
  result = SubmitOpCode(op)
241

    
242
  for name, primary_ip, secondary_ip, pinst, sinst in result:
243
    logger.ToStdout("Node name: %s" % name)
244
    logger.ToStdout("  primary ip: %s" % primary_ip)
245
    logger.ToStdout("  secondary ip: %s" % secondary_ip)
246
    if pinst:
247
      logger.ToStdout("  primary for instances:")
248
      for iname in pinst:
249
        logger.ToStdout("    - %s" % iname)
250
    else:
251
      logger.ToStdout("  primary for no instances")
252
    if sinst:
253
      logger.ToStdout("  secondary for instances:")
254
      for iname in sinst:
255
        logger.ToStdout("    - %s" % iname)
256
    else:
257
      logger.ToStdout("  secondary for no instances")
258

    
259
  return 0
260

    
261

    
262
def RemoveNode(opts, args):
263
  """Remove node cli-to-processor bridge."""
264
  op = opcodes.OpRemoveNode(node_name=args[0])
265
  SubmitOpCode(op)
266

    
267

    
268
def ListVolumes(opts, args):
269
  """List logical volumes on node(s).
270

    
271
  """
272
  if opts.output is None:
273
    selected_fields = ["node", "phys", "vg",
274
                       "name", "size", "instance"]
275
  else:
276
    selected_fields = opts.output.split(",")
277

    
278
  op = opcodes.OpQueryNodeVolumes(nodes=args, output_fields=selected_fields)
279
  output = SubmitOpCode(op)
280

    
281
  if not opts.no_headers:
282
    headers = {"node": "Node", "phys": "PhysDev",
283
               "vg": "VG", "name": "Name",
284
               "size": "Size", "instance": "Instance"}
285
  else:
286
    headers = None
287

    
288
  if opts.human_readable:
289
    unitfields = ["size"]
290
  else:
291
    unitfields = None
292

    
293
  numfields = ["size"]
294

    
295
  data = GenerateTable(separator=opts.separator, headers=headers,
296
                       fields=selected_fields, unitfields=unitfields,
297
                       numfields=numfields, data=output)
298

    
299
  for line in data:
300
    logger.ToStdout(line)
301

    
302
  return 0
303

    
304

    
305
commands = {
306
  'add': (AddNode, ARGS_ONE,
307
          [DEBUG_OPT,
308
           make_option("-s", "--secondary-ip", dest="secondary_ip",
309
                       help="Specify the secondary ip for the node",
310
                       metavar="ADDRESS", default=None),
311
           make_option("--readd", dest="readd",
312
                       default=False, action="store_true",
313
                       help="Readd old node after replacing it"),
314
           ],
315
          "[-s ip] [--readd] <node_name>", "Add a node to the cluster"),
316
  'evacuate': (EvacuateNode, ARGS_FIXED(2),
317
               [DEBUG_OPT, FORCE_OPT],
318
               "[-f] <src> <dst>",
319
               "Relocate the secondary instances from the first node"
320
               " to the second one (only for instances with drbd disk template"
321
               ),
322
  'failover': (FailoverNode, ARGS_ONE,
323
               [DEBUG_OPT, FORCE_OPT,
324
                make_option("--ignore-consistency", dest="ignore_consistency",
325
                            action="store_true", default=False,
326
                            help="Ignore the consistency of the disks on"
327
                            " the secondary"),
328
                ],
329
               "[-f] <node>",
330
               "Stops the primary instances on a node and start them on their"
331
               " secondary node (only for instances with drbd disk template)"),
332
  'info': (ShowNodeConfig, ARGS_ANY, [DEBUG_OPT],
333
           "[<node_name>...]", "Show information about the node(s)"),
334
  'list': (ListNodes, ARGS_NONE,
335
           [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
336
           "", "Lists the nodes in the cluster. The available fields"
337
           " are (see the man page for details): name, pinst_cnt, pinst_list,"
338
           " sinst_cnt, sinst_list, pip, sip, dtotal, dfree, mtotal, mnode,"
339
           " mfree, bootid, cpu_count. The default field list is"
340
           " (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
341
           ),
342
  'remove': (RemoveNode, ARGS_ONE, [DEBUG_OPT],
343
             "<node_name>", "Removes a node from the cluster"),
344
  'volumes': (ListVolumes, ARGS_ANY,
345
              [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
346
              "[<node_name>...]", "List logical volumes on node(s)"),
347
  'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
348
                "<node_name>", "List the tags of the given node"),
349
  'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
350
               "<node_name> tag...", "Add tags to the given node"),
351
  'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
352
                  "<node_name> tag...", "Remove tags from the given node"),
353
  }
354

    
355

    
356
if __name__ == '__main__':
357
  sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_NODE}))