Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ 56bece1f

History | View | Annotate | Download (15.4 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2006, 2007 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
import pprint
25
import os.path
26

    
27
from ganeti.cli import *
28
from ganeti import opcodes
29
from ganeti import constants
30
from ganeti import errors
31
from ganeti import utils
32
from ganeti import bootstrap
33
from ganeti import ssh
34
from ganeti import ssconf
35

    
36

    
37
def InitCluster(opts, args):
38
  """Initialize the cluster.
39

    
40
  Args:
41
    opts - class with options as members
42
    args - list of arguments, expected to be [clustername]
43

    
44
  """
45
  if not opts.lvm_storage and opts.vg_name:
46
    print ("Options --no-lvm-storage and --vg-name conflict.")
47
    return 1
48

    
49
  vg_name = opts.vg_name
50
  if opts.lvm_storage and not opts.vg_name:
51
    vg_name = constants.DEFAULT_VG
52

    
53
  bootstrap.InitCluster(cluster_name=args[0],
54
                        secondary_ip=opts.secondary_ip,
55
                        hypervisor_type=opts.hypervisor_type,
56
                        vg_name=vg_name,
57
                        mac_prefix=opts.mac_prefix,
58
                        def_bridge=opts.def_bridge,
59
                        master_netdev=opts.master_netdev,
60
                        file_storage_dir=opts.file_storage_dir)
61
  return 0
62

    
63

    
64
def DestroyCluster(opts, args):
65
  """Destroy the cluster.
66

    
67
  Args:
68
    opts - class with options as members
69

    
70
  """
71
  if not opts.yes_do_it:
72
    print ("Destroying a cluster is irreversibly. If you really want destroy"
73
           " this cluster, supply the --yes-do-it option.")
74
    return 1
75

    
76
  op = opcodes.OpDestroyCluster()
77
  master = SubmitOpCode(op)
78
  # if we reached this, the opcode didn't fail; we can proceed to
79
  # shutdown all the daemons
80
  bootstrap.FinalizeClusterDestroy(master)
81
  return 0
82

    
83

    
84
def RenameCluster(opts, args):
85
  """Rename the cluster.
86

    
87
  Args:
88
    opts - class with options as members, we use force only
89
    args - list of arguments, expected to be [new_name]
90

    
91
  """
92
  name = args[0]
93
  if not opts.force:
94
    usertext = ("This will rename the cluster to '%s'. If you are connected"
95
                " over the network to the cluster name, the operation is very"
96
                " dangerous as the IP address will be removed from the node"
97
                " and the change may not go through. Continue?") % name
98
    if not AskUser(usertext):
99
      return 1
100

    
101
  op = opcodes.OpRenameCluster(name=name)
102
  SubmitOpCode(op)
103
  return 0
104

    
105

    
106
def ShowClusterVersion(opts, args):
107
  """Write version of ganeti software to the standard output.
108

    
109
  Args:
110
    opts - class with options as members
111

    
112
  """
113
  op = opcodes.OpQueryClusterInfo()
114
  result = SubmitOpCode(op)
115
  print ("Software version: %s" % result["software_version"])
116
  print ("Internode protocol: %s" % result["protocol_version"])
117
  print ("Configuration format: %s" % result["config_version"])
118
  print ("OS api version: %s" % result["os_api_version"])
119
  print ("Export interface: %s" % result["export_version"])
120
  return 0
121

    
122

    
123
def ShowClusterMaster(opts, args):
124
  """Write name of master node to the standard output.
125

    
126
  Args:
127
    opts - class with options as members
128

    
129
  """
130
  print GetClient().QueryConfigValues(["master_node"])[0]
131
  return 0
132

    
133

    
134
def ShowClusterConfig(opts, args):
135
  """Shows cluster information.
136

    
137
  """
138
  op = opcodes.OpQueryClusterInfo()
139
  result = SubmitOpCode(op)
140

    
141
  print ("Cluster name: %s" % result["name"])
142

    
143
  print ("Master node: %s" % result["master"])
144

    
145
  print ("Architecture (this node): %s (%s)" %
146
         (result["architecture"][0], result["architecture"][1]))
147

    
148
  print ("Cluster hypervisor: %s" % result["hypervisor_type"])
149

    
150
  return 0
151

    
152

    
153
def ClusterCopyFile(opts, args):
154
  """Copy a file from master to some nodes.
155

    
156
  Args:
157
    opts - class with options as members
158
    args - list containing a single element, the file name
159
  Opts used:
160
    nodes - list containing the name of target nodes; if empty, all nodes
161

    
162
  """
163
  filename = args[0]
164
  if not os.path.exists(filename):
165
    raise errors.OpPrereqError("No such filename '%s'" % filename)
166

    
167
  cl = GetClient()
168

    
169
  myname = utils.HostInfo().name
170

    
171
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
172

    
173
  op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
174
  results = [row[0] for row in SubmitOpCode(op, cl=cl) if row[0] != myname]
175

    
176
  srun = ssh.SshRunner(cluster_name=cluster_name)
177
  for node in results:
178
    if not srun.CopyFileToNode(node, filename):
179
      print >> sys.stderr, ("Copy of file %s to node %s failed" %
180
                            (filename, node))
181

    
182
  return 0
183

    
184

    
185
def RunClusterCommand(opts, args):
186
  """Run a command on some nodes.
187

    
188
  Args:
189
    opts - class with options as members
190
    args - the command list as a list
191
  Opts used:
192
    nodes: list containing the name of target nodes; if empty, all nodes
193

    
194
  """
195
  cl = GetClient()
196

    
197
  command = " ".join(args)
198
  op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
199
  nodes = [row[0] for row in SubmitOpCode(op, cl=cl)]
200

    
201
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
202
                                                    "master_node"])
203

    
204
  srun = ssh.SshRunner(cluster_name=cluster_name)
205

    
206
  # Make sure master node is at list end
207
  if master_node in nodes:
208
    nodes.remove(master_node)
209
    nodes.append(master_node)
210

    
211
  for name in nodes:
212
    result = srun.Run(name, "root", command)
213
    print ("------------------------------------------------")
214
    print ("node: %s" % name)
215
    print ("%s" % result.output)
216
    print ("return code = %s" % result.exit_code)
217

    
218
  return 0
219

    
220

    
221
def VerifyCluster(opts, args):
222
  """Verify integrity of cluster, performing various test on nodes.
223

    
224
  Args:
225
    opts - class with options as members
226

    
227
  """
228
  skip_checks = []
229
  if opts.skip_nplusone_mem:
230
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
231
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
232
  if SubmitOpCode(op):
233
    return 0
234
  else:
235
    return 1
236

    
237

    
238
def VerifyDisks(opts, args):
239
  """Verify integrity of cluster disks.
240

    
241
  Args:
242
    opts - class with options as members
243

    
244
  """
245
  op = opcodes.OpVerifyDisks()
246
  result = SubmitOpCode(op)
247
  if not isinstance(result, (list, tuple)) or len(result) != 4:
248
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
249

    
250
  nodes, nlvm, instances, missing = result
251

    
252
  if nodes:
253
    print "Nodes unreachable or with bad data:"
254
    for name in nodes:
255
      print "\t%s" % name
256
  retcode = constants.EXIT_SUCCESS
257

    
258
  if nlvm:
259
    for node, text in nlvm.iteritems():
260
      print ("Error on node %s: LVM error: %s" %
261
             (node, text[-400:].encode('string_escape')))
262
      retcode |= 1
263
      print "You need to fix these nodes first before fixing instances"
264

    
265
  if instances:
266
    for iname in instances:
267
      if iname in missing:
268
        continue
269
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
270
      try:
271
        print "Activating disks for instance '%s'" % iname
272
        SubmitOpCode(op)
273
      except errors.GenericError, err:
274
        nret, msg = FormatError(err)
275
        retcode |= nret
276
        print >> sys.stderr, ("Error activating disks for instance %s: %s" %
277
                              (iname, msg))
278

    
279
  if missing:
280
    for iname, ival in missing.iteritems():
281
      all_missing = utils.all(ival, lambda x: x[0] in nlvm)
282
      if all_missing:
283
        print ("Instance %s cannot be verified as it lives on"
284
               " broken nodes" % iname)
285
      else:
286
        print "Instance %s has missing logical volumes:" % iname
287
        ival.sort()
288
        for node, vol in ival:
289
          if node in nlvm:
290
            print ("\tbroken node %s /dev/xenvg/%s" % (node, vol))
291
          else:
292
            print ("\t%s /dev/xenvg/%s" % (node, vol))
293
    print ("You need to run replace_disks for all the above"
294
           " instances, if this message persist after fixing nodes.")
295
    retcode |= 1
296

    
297
  return retcode
298

    
299

    
300
def MasterFailover(opts, args):
301
  """Failover the master node.
302

    
303
  This command, when run on a non-master node, will cause the current
304
  master to cease being master, and the non-master to become new
305
  master.
306

    
307
  """
308
  return bootstrap.MasterFailover()
309

    
310

    
311
def SearchTags(opts, args):
312
  """Searches the tags on all the cluster.
313

    
314
  """
315
  op = opcodes.OpSearchTags(pattern=args[0])
316
  result = SubmitOpCode(op)
317
  if not result:
318
    return 1
319
  result = list(result)
320
  result.sort()
321
  for path, tag in result:
322
    print "%s %s" % (path, tag)
323

    
324

    
325
def SetClusterParams(opts, args):
326
  """Modify the cluster.
327

    
328
  Args:
329
    opts - class with options as members
330

    
331
  """
332
  if not (not opts.lvm_storage or opts.vg_name):
333
    print "Please give at least one of the parameters."
334
    return 1
335

    
336
  vg_name = opts.vg_name
337
  if not opts.lvm_storage and opts.vg_name:
338
    print ("Options --no-lvm-storage and --vg-name conflict.")
339
    return 1
340

    
341
  op = opcodes.OpSetClusterParams(vg_name=opts.vg_name)
342
  SubmitOpCode(op)
343
  return 0
344

    
345

    
346
# this is an option common to more than one command, so we declare
347
# it here and reuse it
348
node_option = make_option("-n", "--node", action="append", dest="nodes",
349
                          help="Node to copy to (if not given, all nodes),"
350
                               " can be given multiple times",
351
                          metavar="<node>", default=[])
352

    
353
commands = {
354
  'init': (InitCluster, ARGS_ONE,
355
           [DEBUG_OPT,
356
            make_option("-s", "--secondary-ip", dest="secondary_ip",
357
                        help="Specify the secondary ip for this node;"
358
                        " if given, the entire cluster must have secondary"
359
                        " addresses",
360
                        metavar="ADDRESS", default=None),
361
            make_option("-t", "--hypervisor-type", dest="hypervisor_type",
362
                        help="Specify the hypervisor type "
363
                        "(xen-3.0, kvm, fake, xen-hvm-3.1)",
364
                        metavar="TYPE", choices=["xen-3.0",
365
                                                 "kvm",
366
                                                 "fake",
367
                                                 "xen-hvm-3.1"],
368
                        default="xen-3.0",),
369
            make_option("-m", "--mac-prefix", dest="mac_prefix",
370
                        help="Specify the mac prefix for the instance IP"
371
                        " addresses, in the format XX:XX:XX",
372
                        metavar="PREFIX",
373
                        default="aa:00:00",),
374
            make_option("-g", "--vg-name", dest="vg_name",
375
                        help="Specify the volume group name "
376
                        " (cluster-wide) for disk allocation [xenvg]",
377
                        metavar="VG",
378
                        default=None,),
379
            make_option("-b", "--bridge", dest="def_bridge",
380
                        help="Specify the default bridge name (cluster-wide)"
381
                          " to connect the instances to [%s]" %
382
                          constants.DEFAULT_BRIDGE,
383
                        metavar="BRIDGE",
384
                        default=constants.DEFAULT_BRIDGE,),
385
            make_option("--master-netdev", dest="master_netdev",
386
                        help="Specify the node interface (cluster-wide)"
387
                          " on which the master IP address will be added "
388
                          " [%s]" % constants.DEFAULT_BRIDGE,
389
                        metavar="NETDEV",
390
                        default=constants.DEFAULT_BRIDGE,),
391
            make_option("--file-storage-dir", dest="file_storage_dir",
392
                        help="Specify the default directory (cluster-wide)"
393
                             " for storing the file-based disks [%s]" %
394
                             constants.DEFAULT_FILE_STORAGE_DIR,
395
                        metavar="DIR",
396
                        default=constants.DEFAULT_FILE_STORAGE_DIR,),
397
            make_option("--no-lvm-storage", dest="lvm_storage",
398
                        help="No support for lvm based instances"
399
                             " (cluster-wide)",
400
                        action="store_false", default=True,),
401
            ],
402
           "[opts...] <cluster_name>",
403
           "Initialises a new cluster configuration"),
404
  'destroy': (DestroyCluster, ARGS_NONE,
405
              [DEBUG_OPT,
406
               make_option("--yes-do-it", dest="yes_do_it",
407
                           help="Destroy cluster",
408
                           action="store_true"),
409
              ],
410
              "", "Destroy cluster"),
411
  'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
412
               "<new_name>",
413
               "Renames the cluster"),
414
  'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
415
             make_option("--no-nplus1-mem", dest="skip_nplusone_mem",
416
                         help="Skip N+1 memory redundancy tests",
417
                         action="store_true",
418
                         default=False,),
419
             ],
420
             "", "Does a check on the cluster configuration"),
421
  'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
422
                   "", "Does a check on the cluster disk status"),
423
  'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT],
424
                     "", "Makes the current node the master"),
425
  'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
426
              "", "Shows the cluster version"),
427
  'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
428
                "", "Shows the cluster master"),
429
  'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
430
               "[-n node...] <filename>",
431
               "Copies a file to all (or only some) nodes"),
432
  'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
433
              "[-n node...] <command>",
434
              "Runs a command on all (or only some) nodes"),
435
  'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
436
                 "", "Show cluster configuration"),
437
  'list-tags': (ListTags, ARGS_NONE,
438
                [DEBUG_OPT], "", "List the tags of the cluster"),
439
  'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
440
               "tag...", "Add tags to the cluster"),
441
  'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
442
                  "tag...", "Remove tags from the cluster"),
443
  'search-tags': (SearchTags, ARGS_ONE,
444
                  [DEBUG_OPT], "", "Searches the tags on all objects on"
445
                  " the cluster for a given pattern (regex)"),
446
  'modify': (SetClusterParams, ARGS_NONE,
447
             [DEBUG_OPT,
448
              make_option("-g", "--vg-name", dest="vg_name",
449
                          help="Specify the volume group name "
450
                          " (cluster-wide) for disk allocation "
451
                          "and enable lvm based storage",
452
                          metavar="VG",),
453
              make_option("--no-lvm-storage", dest="lvm_storage",
454
                          help="Disable support for lvm based instances"
455
                               " (cluster-wide)",
456
                          action="store_false", default=True,),
457
              ],
458
             "[opts...]",
459
             "Alters the parameters of the cluster"),
460
  }
461

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