Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-node @ c54784d9

History | View | Annotate | Download (11.7 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
from ganeti import bootstrap
32

    
33

    
34
_LIST_DEF_FIELDS = [
35
  "name", "dtotal", "dfree",
36
  "mtotal", "mnode", "mfree",
37
  "pinst_cnt", "sinst_cnt",
38
  ]
39

    
40
def AddNode(opts, args):
41
  """Add node cli-to-processor bridge.
42

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

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

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

    
63
  bootstrap.SetupNodeDaemon(node)
64

    
65
  op = opcodes.OpAddNode(node_name=args[0], secondary_ip=opts.secondary_ip,
66
                         readd=opts.readd)
67
  SubmitOpCode(op)
68

    
69

    
70
def ListNodes(opts, args):
71
  """List nodes and their properties.
72

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

    
81
  output = GetClient().QueryNodes([], selected_fields)
82

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

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

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

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

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

    
124
  return 0
125

    
126

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

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

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

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

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

    
151
  sinst = utils.NiceSort(sinst)
152

    
153
  retcode = constants.EXIT_SUCCESS
154

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

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

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

    
187

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

    
191
  """
192
  force = opts.force
193
  selected_fields = ["name", "pinst_list"]
194

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

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

    
203
  pinst = utils.NiceSort(pinst)
204

    
205
  retcode = 0
206

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

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

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

    
234

    
235
def ShowNodeConfig(opts, args):
236
  """Show node information.
237

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

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

    
261
  return 0
262

    
263

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

    
269

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

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

    
280
  op = opcodes.OpQueryNodeVolumes(nodes=args, output_fields=selected_fields)
281
  output = SubmitOpCode(op)
282

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

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

    
295
  numfields = ["size"]
296

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

    
301
  for line in data:
302
    logger.ToStdout(line)
303

    
304
  return 0
305

    
306

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

    
358

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