gnt-node 14.8 KB
Newer Older
Iustin Pop's avatar
Iustin Pop committed
1
2
3
#!/usr/bin/python
#

4
# Copyright (C) 2006, 2007, 2008 Google Inc.
Iustin Pop's avatar
Iustin Pop committed
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.


22
23
24
25
# pylint: disable-msg=W0401,W0614
# W0401: Wildcard import ganeti.cli
# W0614: Unused import %s from wildcard import (since we need cli)

Iustin Pop's avatar
Iustin Pop committed
26
27
28
29
30
31
import sys
from optparse import make_option

from ganeti.cli import *
from ganeti import opcodes
from ganeti import utils
32
from ganeti import constants
Iustin Pop's avatar
Iustin Pop committed
33
from ganeti import errors
34
from ganeti import bootstrap
Iustin Pop's avatar
Iustin Pop committed
35
36


37
#: default list of field for L{ListNodes}
38
39
40
41
42
43
_LIST_DEF_FIELDS = [
  "name", "dtotal", "dfree",
  "mtotal", "mnode", "mfree",
  "pinst_cnt", "sinst_cnt",
  ]

Michael Hanselmann's avatar
Michael Hanselmann committed
44

45
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
46
def AddNode(opts, args):
47
48
49
50
51
52
53
  """Add a node to the cluster.

  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the new node name
  @rtype: int
  @return: the desired exit code
54
55

  """
Iustin Pop's avatar
Iustin Pop committed
56
  cl = GetClient()
57
58
59
60
61
  dns_data = utils.HostInfo(args[0])
  node = dns_data.name

  if not opts.readd:
    try:
Iustin Pop's avatar
Iustin Pop committed
62
      output = cl.QueryNodes(names=[node], fields=['name'])
63
64
65
    except (errors.OpPrereqError, errors.OpExecError):
      pass
    else:
66
67
      ToStderr("Node %s already in the cluster (as %s)"
               " - please use --readd", args[0], output[0][0])
68
69
      return 1

Iustin Pop's avatar
Iustin Pop committed
70
71
72
73
  # read the cluster name from the master
  output = cl.QueryConfigValues(['cluster_name'])
  cluster_name = output[0]

74
75
76
77
78
79
  ToStderr("-- WARNING -- \n"
           "Performing this operation is going to replace the ssh daemon"
           " keypair\n"
           "on the target machine (%s) with the ones of the"
           " current one\n"
           "and grant full intra-cluster ssh root access to/from it\n", node)
80

Iustin Pop's avatar
Iustin Pop committed
81
  bootstrap.SetupNodeDaemon(cluster_name, node, opts.ssh_key_check)
82

83
84
  op = opcodes.OpAddNode(node_name=args[0], secondary_ip=opts.secondary_ip,
                         readd=opts.readd)
Iustin Pop's avatar
Iustin Pop committed
85
86
87
88
89
90
  SubmitOpCode(op)


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

91
92
93
94
95
96
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should be an empty list
  @rtype: int
  @return: the desired exit code

Iustin Pop's avatar
Iustin Pop committed
97
98
  """
  if opts.output is None:
99
100
101
    selected_fields = _LIST_DEF_FIELDS
  elif opts.output.startswith("+"):
    selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
Iustin Pop's avatar
Iustin Pop committed
102
103
104
  else:
    selected_fields = opts.output.split(",")

105
  output = GetClient().QueryNodes([], selected_fields)
Iustin Pop's avatar
Iustin Pop committed
106
107

  if not opts.no_headers:
108
109
110
111
112
113
114
115
    headers = {
      "name": "Node", "pinst_cnt": "Pinst", "sinst_cnt": "Sinst",
      "pinst_list": "PriInstances", "sinst_list": "SecInstances",
      "pip": "PrimaryIP", "sip": "SecondaryIP",
      "dtotal": "DTotal", "dfree": "DFree",
      "mtotal": "MTotal", "mnode": "MNode", "mfree": "MFree",
      "bootid": "BootID",
      "ctotal": "CTotal",
116
      "tags": "Tags",
117
      "serial_no": "SerialNo",
118
119
      "master_candidate": "MasterC",
      "master": "IsMaster",
120
      }
121
122
123
  else:
    headers = None

124
  unitfields = ["dtotal", "dfree", "mtotal", "mnode", "mfree"]
125

126
127
  numfields = ["dtotal", "dfree",
               "mtotal", "mnode", "mfree",
128
               "pinst_cnt", "sinst_cnt",
129
               "ctotal", "serial_no"]
130

131
  list_type_fields = ("pinst_list", "sinst_list", "tags")
132
133
134
135
  # change raw values to nicer strings
  for row in output:
    for idx, field in enumerate(selected_fields):
      val = row[idx]
136
      if field in list_type_fields:
137
        val = ",".join(val)
138
139
140
141
142
      elif field in ('master', 'master_candidate'):
        if val:
          val = 'Y'
        else:
          val = 'N'
143
144
145
      elif val is None:
        val = "?"
      row[idx] = str(val)
146

147
148
  data = GenerateTable(separator=opts.separator, headers=headers,
                       fields=selected_fields, unitfields=unitfields,
149
                       numfields=numfields, data=output, units=opts.units)
150
  for line in data:
151
    ToStdout(line)
Iustin Pop's avatar
Iustin Pop committed
152
153
154
155

  return 0


Iustin Pop's avatar
Iustin Pop committed
156
157
158
def EvacuateNode(opts, args):
  """Relocate all secondary instance from a node.

159
160
161
162
163
164
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should be an empty list
  @rtype: int
  @return: the desired exit code

Iustin Pop's avatar
Iustin Pop committed
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
  """
  force = opts.force
  selected_fields = ["name", "sinst_list"]
  src_node, dst_node = args

  op = opcodes.OpQueryNodes(output_fields=selected_fields, names=[src_node])
  result = SubmitOpCode(op)
  src_node, sinst = result[0]
  op = opcodes.OpQueryNodes(output_fields=["name"], names=[dst_node])
  result = SubmitOpCode(op)
  dst_node = result[0][0]

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

  if not sinst:
183
    ToStderr("No secondary instances on node %s, exiting.", src_node)
Iustin Pop's avatar
Iustin Pop committed
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
    return constants.EXIT_SUCCESS

  sinst = utils.NiceSort(sinst)

  retcode = constants.EXIT_SUCCESS

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

  good_cnt = bad_cnt = 0
  for iname in sinst:
    op = opcodes.OpReplaceDisks(instance_name=iname,
199
200
201
                                remote_node=dst_node,
                                mode=constants.REPLACE_DISK_ALL,
                                disks=["sda", "sdb"])
Iustin Pop's avatar
Iustin Pop committed
202
    try:
203
      ToStdout("Replacing disks for instance %s", iname)
Iustin Pop's avatar
Iustin Pop committed
204
      SubmitOpCode(op)
205
      ToStdout("Instance %s has been relocated", iname)
Iustin Pop's avatar
Iustin Pop committed
206
207
208
209
      good_cnt += 1
    except errors.GenericError, err:
      nret, msg = FormatError(err)
      retcode |= nret
210
      ToStderr("Error replacing disks for instance %s: %s", iname, msg)
Iustin Pop's avatar
Iustin Pop committed
211
212
213
      bad_cnt += 1

  if retcode == constants.EXIT_SUCCESS:
214
    ToStdout("All %d instance(s) relocated successfully.", good_cnt)
Iustin Pop's avatar
Iustin Pop committed
215
  else:
216
217
    ToStdout("There were errors during the relocation:\n"
             "%d error(s) out of %d instance(s).", bad_cnt, good_cnt + bad_cnt)
Iustin Pop's avatar
Iustin Pop committed
218
219
220
  return retcode


Iustin Pop's avatar
Iustin Pop committed
221
222
223
def FailoverNode(opts, args):
  """Failover all primary instance on a node.

224
225
226
227
228
229
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should be an empty list
  @rtype: int
  @return: the desired exit code

Iustin Pop's avatar
Iustin Pop committed
230
231
232
233
234
235
236
237
238
  """
  force = opts.force
  selected_fields = ["name", "pinst_list"]

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

  if not pinst:
239
    ToStderr("No primary instances on node %s, exiting.", node)
Iustin Pop's avatar
Iustin Pop committed
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
    return 0

  pinst = utils.NiceSort(pinst)

  retcode = 0

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

  good_cnt = bad_cnt = 0
  for iname in pinst:
    op = opcodes.OpFailoverInstance(instance_name=iname,
                                    ignore_consistency=opts.ignore_consistency)
    try:
255
      ToStdout("Failing over instance %s", iname)
Iustin Pop's avatar
Iustin Pop committed
256
      SubmitOpCode(op)
257
      ToStdout("Instance %s has been failed over", iname)
Iustin Pop's avatar
Iustin Pop committed
258
259
260
261
      good_cnt += 1
    except errors.GenericError, err:
      nret, msg = FormatError(err)
      retcode |= nret
262
      ToStderr("Error failing over instance %s: %s", iname, msg)
Iustin Pop's avatar
Iustin Pop committed
263
264
265
      bad_cnt += 1

  if retcode == 0:
266
    ToStdout("All %d instance(s) failed over successfully.", good_cnt)
Iustin Pop's avatar
Iustin Pop committed
267
  else:
268
269
    ToStdout("There were errors during the failover:\n"
             "%d error(s) out of %d instance(s).", bad_cnt, good_cnt + bad_cnt)
Iustin Pop's avatar
Iustin Pop committed
270
271
272
  return retcode


Iustin Pop's avatar
Iustin Pop committed
273
274
275
def ShowNodeConfig(opts, args):
  """Show node information.

276
277
278
279
280
281
282
283
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should either be an empty list, in which case
      we show information about all nodes, or should contain
      a list of nodes to be queried for information
  @rtype: int
  @return: the desired exit code

Iustin Pop's avatar
Iustin Pop committed
284
  """
285
286
  op = opcodes.OpQueryNodes(output_fields=["name", "pip", "sip",
                                           "pinst_list", "sinst_list"],
287
                            names=args)
Iustin Pop's avatar
Iustin Pop committed
288
289
290
  result = SubmitOpCode(op)

  for name, primary_ip, secondary_ip, pinst, sinst in result:
291
292
293
    ToStdout("Node name: %s", name)
    ToStdout("  primary ip: %s", primary_ip)
    ToStdout("  secondary ip: %s", secondary_ip)
Iustin Pop's avatar
Iustin Pop committed
294
    if pinst:
295
      ToStdout("  primary for instances:")
Iustin Pop's avatar
Iustin Pop committed
296
      for iname in pinst:
297
        ToStdout("    - %s", iname)
Iustin Pop's avatar
Iustin Pop committed
298
    else:
299
      ToStdout("  primary for no instances")
Iustin Pop's avatar
Iustin Pop committed
300
    if sinst:
301
      ToStdout("  secondary for instances:")
Iustin Pop's avatar
Iustin Pop committed
302
      for iname in sinst:
303
        ToStdout("    - %s", iname)
Iustin Pop's avatar
Iustin Pop committed
304
    else:
305
      ToStdout("  secondary for no instances")
Iustin Pop's avatar
Iustin Pop committed
306
307
308
309
310

  return 0


def RemoveNode(opts, args):
311
312
313
314
315
316
317
318
319
320
  """Remove a node from the cluster.

  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the name of
      the node to be removed
  @rtype: int
  @return: the desired exit code

  """
Iustin Pop's avatar
Iustin Pop committed
321
322
  op = opcodes.OpRemoveNode(node_name=args[0])
  SubmitOpCode(op)
323
  return 0
Iustin Pop's avatar
Iustin Pop committed
324
325


326
327
328
def ListVolumes(opts, args):
  """List logical volumes on node(s).

329
330
331
332
333
334
335
336
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should either be an empty list, in which case
      we list data for all nodes, or contain a list of nodes
      to display data only for those
  @rtype: int
  @return: the desired exit code

337
338
339
340
341
342
343
344
345
346
347
  """
  if opts.output is None:
    selected_fields = ["node", "phys", "vg",
                       "name", "size", "instance"]
  else:
    selected_fields = opts.output.split(",")

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

  if not opts.no_headers:
348
349
350
351
352
353
    headers = {"node": "Node", "phys": "PhysDev",
               "vg": "VG", "name": "Name",
               "size": "Size", "instance": "Instance"}
  else:
    headers = None

354
  unitfields = ["size"]
355
356
357

  numfields = ["size"]

358
359
  data = GenerateTable(separator=opts.separator, headers=headers,
                       fields=selected_fields, unitfields=unitfields,
360
                       numfields=numfields, data=output, units=opts.units)
361
362

  for line in data:
363
    ToStdout(line)
364
365
366
367

  return 0


Iustin Pop's avatar
Iustin Pop committed
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
def SetNodeParams(opts, args):
  """Modifies a node.

  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the node name
  @rtype: int
  @return: the desired exit code

  """
  if opts.master_candidate is None:
    ToStderr("Please give at least one of the parameters.")
    return 1

  candidate = opts.master_candidate == 'yes'
  op = opcodes.OpSetNodeParams(node_name=args[0],
                              master_candidate=candidate,
                              force=opts.force)

  # even if here we process the result, we allow submit only
  result = SubmitOrSend(op, opts)

  if result:
    ToStdout("Modified node %s", args[0])
    for param, data in result:
      ToStdout(" - %-5s -> %s", param, data)
  return 0


Iustin Pop's avatar
Iustin Pop committed
397
398
399
400
401
commands = {
  'add': (AddNode, ARGS_ONE,
          [DEBUG_OPT,
           make_option("-s", "--secondary-ip", dest="secondary_ip",
                       help="Specify the secondary ip for the node",
402
403
404
405
                       metavar="ADDRESS", default=None),
           make_option("--readd", dest="readd",
                       default=False, action="store_true",
                       help="Readd old node after replacing it"),
406
407
408
           make_option("--no-ssh-key-check", dest="ssh_key_check",
                       default=True, action="store_false",
                       help="Disable SSH key fingerprint checking"),
409
           ],
410
411
          "[-s ip] [--readd] [--no-ssh-key-check] <node_name>",
          "Add a node to the cluster"),
Iustin Pop's avatar
Iustin Pop committed
412
413
  'evacuate': (EvacuateNode, ARGS_FIXED(2),
               [DEBUG_OPT, FORCE_OPT],
414
               "[-f] <src> <dst>",
Iustin Pop's avatar
Iustin Pop committed
415
               "Relocate the secondary instances from the first node"
416
417
               " to the second one (only for instances with drbd disk template"
               ),
Iustin Pop's avatar
Iustin Pop committed
418
419
420
421
422
423
424
  'failover': (FailoverNode, ARGS_ONE,
               [DEBUG_OPT, FORCE_OPT,
                make_option("--ignore-consistency", dest="ignore_consistency",
                            action="store_true", default=False,
                            help="Ignore the consistency of the disks on"
                            " the secondary"),
                ],
425
               "[-f] <node>",
Iustin Pop's avatar
Iustin Pop committed
426
               "Stops the primary instances on a node and start them on their"
427
               " secondary node (only for instances with drbd disk template)"),
Iustin Pop's avatar
Iustin Pop committed
428
  'info': (ShowNodeConfig, ARGS_ANY, [DEBUG_OPT],
429
           "[<node_name>...]", "Show information about the node(s)"),
Iustin Pop's avatar
Iustin Pop committed
430
  'list': (ListNodes, ARGS_NONE,
431
432
           [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT,
            SUBMIT_OPT],
433
           "", "Lists the nodes in the cluster. The available fields"
434
435
           " are (see the man page for details): name, pinst_cnt, pinst_list,"
           " sinst_cnt, sinst_list, pip, sip, dtotal, dfree, mtotal, mnode,"
436
437
           " mfree, bootid, cpu_count, serial_no."
           " The default field list is"
438
439
           " (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
           ),
Iustin Pop's avatar
Iustin Pop committed
440
441
442
443
444
445
446
447
  'modify': (SetNodeParams, ARGS_ONE,
             [DEBUG_OPT, FORCE_OPT,
              SUBMIT_OPT,
              make_option("-C", "--master-candidate", dest="master_candidate",
                          choices=('yes', 'no'), default=None,
                          help="Set the master_candidate flag on the node"),
              ],
             "<instance>", "Alters the parameters of an instance"),
Iustin Pop's avatar
Iustin Pop committed
448
  'remove': (RemoveNode, ARGS_ONE, [DEBUG_OPT],
449
             "<node_name>", "Removes a node from the cluster"),
450
451
  'volumes': (ListVolumes, ARGS_ANY,
              [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
452
              "[<node_name>...]", "List logical volumes on node(s)"),
453
  'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
454
                "<node_name>", "List the tags of the given node"),
455
  'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
456
               "<node_name> tag...", "Add tags to the given node"),
457
  'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
458
                  "<node_name> tag...", "Remove tags from the given node"),
Iustin Pop's avatar
Iustin Pop committed
459
460
461
462
  }


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