Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-node @ 3a24c527

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 utils
28
from ganeti import constants
29
from ganeti import errors
30
from ganeti import bootstrap
31

    
32

    
33
_LIST_DEF_FIELDS = [
34
  "name", "dtotal", "dfree",
35
  "mtotal", "mnode", "mfree",
36
  "pinst_cnt", "sinst_cnt",
37
  ]
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
      ToStderr("Node %s already in the cluster (as %s)"
55
               " - please use --readd", args[0], output[0][0])
56
      return 1
57

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

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

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

    
71

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

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

    
83
  output = GetClient().QueryNodes([], selected_fields)
84

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

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

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

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

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

    
127
  return 0
128

    
129

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

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

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

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

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

    
154
  sinst = utils.NiceSort(sinst)
155

    
156
  retcode = constants.EXIT_SUCCESS
157

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

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

    
181
  if retcode == constants.EXIT_SUCCESS:
182
    ToStdout("All %d instance(s) relocated successfully.", good_cnt)
183
  else:
184
    ToStdout("There were errors during the relocation:\n"
185
             "%d error(s) out of %d instance(s).", 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
    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
      ToStdout("Failing over instance %s", iname)
218
      SubmitOpCode(op)
219
      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
      ToStderr("Error failing over instance %s: %s", iname, msg)
225
      bad_cnt += 1
226

    
227
  if retcode == 0:
228
    ToStdout("All %d instance(s) failed over successfully.", good_cnt)
229
  else:
230
    ToStdout("There were errors during the failover:\n"
231
             "%d error(s) out of %d instance(s).", 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
    ToStdout("Node name: %s", name)
246
    ToStdout("  primary ip: %s", primary_ip)
247
    ToStdout("  secondary ip: %s", secondary_ip)
248
    if pinst:
249
      ToStdout("  primary for instances:")
250
      for iname in pinst:
251
        ToStdout("    - %s", iname)
252
    else:
253
      ToStdout("  primary for no instances")
254
    if sinst:
255
      ToStdout("  secondary for instances:")
256
      for iname in sinst:
257
        ToStdout("    - %s", iname)
258
    else:
259
      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
    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
           make_option("--no-ssh-key-check", dest="ssh_key_check",
317
                       default=True, action="store_false",
318
                       help="Disable SSH key fingerprint checking"),
319
           ],
320
          "[-s ip] [--readd] [--no-ssh-key-check] <node_name>",
321
          "Add a node to the cluster"),
322
  'evacuate': (EvacuateNode, ARGS_FIXED(2),
323
               [DEBUG_OPT, FORCE_OPT],
324
               "[-f] <src> <dst>",
325
               "Relocate the secondary instances from the first node"
326
               " to the second one (only for instances with drbd disk template"
327
               ),
328
  'failover': (FailoverNode, ARGS_ONE,
329
               [DEBUG_OPT, FORCE_OPT,
330
                make_option("--ignore-consistency", dest="ignore_consistency",
331
                            action="store_true", default=False,
332
                            help="Ignore the consistency of the disks on"
333
                            " the secondary"),
334
                ],
335
               "[-f] <node>",
336
               "Stops the primary instances on a node and start them on their"
337
               " secondary node (only for instances with drbd disk template)"),
338
  'info': (ShowNodeConfig, ARGS_ANY, [DEBUG_OPT],
339
           "[<node_name>...]", "Show information about the node(s)"),
340
  'list': (ListNodes, ARGS_NONE,
341
           [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT,
342
            SUBMIT_OPT],
343
           "", "Lists the nodes in the cluster. The available fields"
344
           " are (see the man page for details): name, pinst_cnt, pinst_list,"
345
           " sinst_cnt, sinst_list, pip, sip, dtotal, dfree, mtotal, mnode,"
346
           " mfree, bootid, cpu_count, serial_no."
347
           " The default field list is"
348
           " (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
349
           ),
350
  'remove': (RemoveNode, ARGS_ONE, [DEBUG_OPT],
351
             "<node_name>", "Removes a node from the cluster"),
352
  'volumes': (ListVolumes, ARGS_ANY,
353
              [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
354
              "[<node_name>...]", "List logical volumes on node(s)"),
355
  'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
356
                "<node_name>", "List the tags of the given node"),
357
  'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
358
               "<node_name> tag...", "Add tags to the given node"),
359
  'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
360
                  "<node_name> tag...", "Remove tags from the given node"),
361
  }
362

    
363

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