Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-node @ 94428652

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
  op = opcodes.OpQueryNodes(output_fields=selected_fields, names=[])
82
  output = SubmitOrSend(op, opts)
83

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

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

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

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

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

    
125
  return 0
126

    
127

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

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

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

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

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

    
152
  sinst = utils.NiceSort(sinst)
153

    
154
  retcode = constants.EXIT_SUCCESS
155

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

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

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

    
188

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

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

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

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

    
204
  pinst = utils.NiceSort(pinst)
205

    
206
  retcode = 0
207

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

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

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

    
235

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

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

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

    
262
  return 0
263

    
264

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

    
270

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

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

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

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

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

    
296
  numfields = ["size"]
297

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

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

    
305
  return 0
306

    
307

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

    
359

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