Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ 4300c4b6

History | View | Annotate | Download (15.1 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
  sstore = ssconf.SimpleStore()
131
  print sstore.GetMasterNode()
132
  return 0
133

    
134

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

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

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

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

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

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

    
151
  return 0
152

    
153

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

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

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

    
168
  myname = utils.HostInfo().name
169

    
170
  op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
171
  results = [row[0] for row in SubmitOpCode(op) if row[0] != myname]
172
  srun = ssh.SshRunner()
173
  for node in results:
174
    if not srun.CopyFileToNode(node, filename):
175
      print >> sys.stderr, ("Copy of file %s to node %s failed" %
176
                            (filename, node))
177

    
178
  return 0
179

    
180

    
181
def RunClusterCommand(opts, args):
182
  """Run a command on some nodes.
183

    
184
  Args:
185
    opts - class with options as members
186
    args - the command list as a list
187
  Opts used:
188
    nodes: list containing the name of target nodes; if empty, all nodes
189

    
190
  """
191
  command = " ".join(args)
192
  op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
193
  nodes = [row[0] for row in SubmitOpCode(op)]
194

    
195
  sstore = ssconf.SimpleStore()
196
  master_node = sstore.GetMasterNode()
197
  srun = ssh.SshRunner(sstore=sstore)
198

    
199
  if master_node in nodes:
200
    nodes.remove(master_node)
201
    nodes.append(master_node)
202

    
203
  for name in nodes:
204
    result = srun.Run(name, "root", command)
205
    print ("------------------------------------------------")
206
    print ("node: %s" % name)
207
    print ("%s" % result.output)
208
    print ("return code = %s" % result.exit_code)
209

    
210
  return 0
211

    
212

    
213
def VerifyCluster(opts, args):
214
  """Verify integrity of cluster, performing various test on nodes.
215

    
216
  Args:
217
    opts - class with options as members
218

    
219
  """
220
  skip_checks = []
221
  if opts.skip_nplusone_mem:
222
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
223
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
224
  if SubmitOpCode(op):
225
    return 0
226
  else:
227
    return 1
228

    
229

    
230
def VerifyDisks(opts, args):
231
  """Verify integrity of cluster disks.
232

    
233
  Args:
234
    opts - class with options as members
235

    
236
  """
237
  op = opcodes.OpVerifyDisks()
238
  result = SubmitOpCode(op)
239
  if not isinstance(result, tuple) or len(result) != 4:
240
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
241

    
242
  nodes, nlvm, instances, missing = result
243

    
244
  if nodes:
245
    print "Nodes unreachable or with bad data:"
246
    for name in nodes:
247
      print "\t%s" % name
248
  retcode = constants.EXIT_SUCCESS
249

    
250
  if nlvm:
251
    for node, text in nlvm.iteritems():
252
      print ("Error on node %s: LVM error: %s" %
253
             (node, text[-400:].encode('string_escape')))
254
      retcode |= 1
255
      print "You need to fix these nodes first before fixing instances"
256

    
257
  if instances:
258
    for iname in instances:
259
      if iname in missing:
260
        continue
261
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
262
      try:
263
        print "Activating disks for instance '%s'" % iname
264
        SubmitOpCode(op)
265
      except errors.GenericError, err:
266
        nret, msg = FormatError(err)
267
        retcode |= nret
268
        print >> sys.stderr, ("Error activating disks for instance %s: %s" %
269
                              (iname, msg))
270

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

    
289
  return retcode
290

    
291

    
292
def MasterFailover(opts, args):
293
  """Failover the master node.
294

    
295
  This command, when run on a non-master node, will cause the current
296
  master to cease being master, and the non-master to become new
297
  master.
298

    
299
  """
300
  return bootstrap.MasterFailover()
301

    
302

    
303
def SearchTags(opts, args):
304
  """Searches the tags on all the cluster.
305

    
306
  """
307
  op = opcodes.OpSearchTags(pattern=args[0])
308
  result = SubmitOpCode(op)
309
  if not result:
310
    return 1
311
  result = list(result)
312
  result.sort()
313
  for path, tag in result:
314
    print "%s %s" % (path, tag)
315

    
316

    
317
def SetClusterParams(opts, args):
318
  """Modify the cluster.
319

    
320
  Args:
321
    opts - class with options as members
322

    
323
  """
324
  if not (not opts.lvm_storage or opts.vg_name):
325
    print "Please give at least one of the parameters."
326
    return 1
327

    
328
  vg_name = opts.vg_name
329
  if not opts.lvm_storage and opts.vg_name:
330
    print ("Options --no-lvm-storage and --vg-name conflict.")
331
    return 1
332

    
333
  op = opcodes.OpSetClusterParams(vg_name=opts.vg_name)
334
  SubmitOpCode(op)
335
  return 0
336

    
337

    
338
# this is an option common to more than one command, so we declare
339
# it here and reuse it
340
node_option = make_option("-n", "--node", action="append", dest="nodes",
341
                          help="Node to copy to (if not given, all nodes),"
342
                               " can be given multiple times",
343
                          metavar="<node>", default=[])
344

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

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