Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ 7688d0d3

History | View | Annotate | Download (15.3 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
  # TODO: Query master
165
  cfg = ssconf.SimpleConfigReader()
166

    
167
  filename = args[0]
168
  if not os.path.exists(filename):
169
    raise errors.OpPrereqError("No such filename '%s'" % filename)
170

    
171
  myname = utils.HostInfo().name
172

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

    
181
  return 0
182

    
183

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

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

    
193
  """
194
  # TODO: Query master
195
  cfg = ssconf.SimpleConfigReader()
196

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

    
201
  sstore = ssconf.SimpleStore()
202
  master_node = sstore.GetMasterNode()
203
  srun = ssh.SshRunner(cfg)
204

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

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

    
217
  return 0
218

    
219

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

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

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

    
236

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

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

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

    
249
  nodes, nlvm, instances, missing = result
250

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

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

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

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

    
296
  return retcode
297

    
298

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

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

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

    
309

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

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

    
323

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

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

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

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

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

    
344

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

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

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