Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ ce7151ae

History | View | Annotate | Download (14.9 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
  SubmitOpCode(op)
78
  return 0
79

    
80

    
81
def RenameCluster(opts, args):
82
  """Rename the cluster.
83

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

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

    
98
  op = opcodes.OpRenameCluster(name=name)
99
  SubmitOpCode(op)
100
  return 0
101

    
102

    
103
def ShowClusterVersion(opts, args):
104
  """Write version of ganeti software to the standard output.
105

    
106
  Args:
107
    opts - class with options as members
108

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

    
119

    
120
def ShowClusterMaster(opts, args):
121
  """Write name of master node to the standard output.
122

    
123
  Args:
124
    opts - class with options as members
125

    
126
  """
127
  sstore = ssconf.SimpleStore()
128
  print sstore.GetMasterNode()
129
  return 0
130

    
131

    
132
def ShowClusterConfig(opts, args):
133
  """Shows cluster information.
134

    
135
  """
136
  op = opcodes.OpQueryClusterInfo()
137
  result = SubmitOpCode(op)
138

    
139
  print ("Cluster name: %s" % result["name"])
140

    
141
  print ("Master node: %s" % result["master"])
142

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

    
146
  print ("Cluster hypervisor: %s" % result["hypervisor_type"])
147

    
148
  return 0
149

    
150

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

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

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

    
165
  myname = utils.HostInfo().name
166

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

    
175
  return 0
176

    
177

    
178
def RunClusterCommand(opts, args):
179
  """Run a command on some nodes.
180

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

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

    
192
  sstore = ssconf.SimpleStore()
193
  master_node = sstore.GetMasterNode()
194
  srun = ssh.SshRunner(sstore=sstore)
195

    
196
  if master_node in nodes:
197
    nodes.remove(master_node)
198
    nodes.append(master_node)
199

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

    
207
  return 0
208

    
209

    
210
def VerifyCluster(opts, args):
211
  """Verify integrity of cluster, performing various test on nodes.
212

    
213
  Args:
214
    opts - class with options as members
215

    
216
  """
217
  skip_checks = []
218
  if opts.skip_nplusone_mem:
219
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
220
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
221
  result = SubmitOpCode(op)
222
  return result
223

    
224

    
225
def VerifyDisks(opts, args):
226
  """Verify integrity of cluster disks.
227

    
228
  Args:
229
    opts - class with options as members
230

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

    
237
  nodes, nlvm, instances, missing = result
238

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

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

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

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

    
284
  return retcode
285

    
286

    
287
def MasterFailover(opts, args):
288
  """Failover the master node.
289

    
290
  This command, when run on a non-master node, will cause the current
291
  master to cease being master, and the non-master to become new
292
  master.
293

    
294
  """
295
  return bootstrap.MasterFailover()
296

    
297

    
298
def SearchTags(opts, args):
299
  """Searches the tags on all the cluster.
300

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

    
311

    
312
def SetClusterParams(opts, args):
313
  """Modify the cluster.
314

    
315
  Args:
316
    opts - class with options as members
317

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

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

    
328
  op = opcodes.OpSetClusterParams(vg_name=opts.vg_name)
329
  SubmitOpCode(op)
330
  return 0
331

    
332

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

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

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