Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ 1f05af2b

History | View | Annotate | Download (15 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
  result = SubmitOpCode(op)
225
  return result
226

    
227

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

    
231
  Args:
232
    opts - class with options as members
233

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

    
240
  nodes, nlvm, instances, missing = result
241

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

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

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

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

    
287
  return retcode
288

    
289

    
290
def MasterFailover(opts, args):
291
  """Failover the master node.
292

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

    
297
  """
298
  return bootstrap.MasterFailover()
299

    
300

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

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

    
314

    
315
def SetClusterParams(opts, args):
316
  """Modify the cluster.
317

    
318
  Args:
319
    opts - class with options as members
320

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

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

    
331
  op = opcodes.OpSetClusterParams(vg_name=opts.vg_name)
332
  SubmitOpCode(op)
333
  return 0
334

    
335

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

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

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