Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-node @ 38d7239a

History | View | Annotate | Download (12 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

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

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

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

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

    
64
  bootstrap.SetupNodeDaemon(node, opts.ssh_key_check)
65

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

    
70

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

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

    
82
  output = GetClient().QueryNodes([], selected_fields)
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
      "serial_no": "SerialNo",
95
      }
96
  else:
97
    headers = None
98

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

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

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

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

    
126
  return 0
127

    
128

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

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

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

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

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

    
153
  sinst = utils.NiceSort(sinst)
154

    
155
  retcode = constants.EXIT_SUCCESS
156

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

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

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

    
189

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

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

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

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

    
205
  pinst = utils.NiceSort(pinst)
206

    
207
  retcode = 0
208

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

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

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

    
236

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

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

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

    
263
  return 0
264

    
265

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

    
271

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

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

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

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

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

    
297
  numfields = ["size"]
298

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

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

    
306
  return 0
307

    
308

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

    
365

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