Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ ea3a925f

History | View | Annotate | Download (19.8 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
  hvlist = opts.enabled_hypervisors
54
  if hvlist is not None:
55
    hvlist = hvlist.split(",")
56
  else:
57
    hvlist = constants.DEFAULT_ENABLED_HYPERVISOR
58

    
59
  hvparams = opts.hvparams
60
  if hvparams:
61
    # a list of (name, dict) we can pass directly to dict()
62
    hvparams = dict(opts.hvparams)
63
  else:
64
    # otherwise init as empty dict
65
    hvparams = {}
66

    
67
  beparams = opts.beparams
68
  # check for invalid parameters
69
  for parameter in beparams:
70
    if parameter not in constants.BES_PARAMETERS:
71
      print "Invalid backend parameter: %s" % parameter
72
      return 1
73

    
74
  # prepare beparams dict
75
  for parameter in constants.BES_PARAMETERS:
76
    if parameter not in beparams:
77
      beparams[parameter] = constants.BEC_DEFAULTS[parameter]
78

    
79
  # type wrangling
80
  try:
81
    beparams[constants.BE_VCPUS] = int(beparams[constants.BE_VCPUS])
82
  except ValueError:
83
    print "%s must be an integer" % constants.BE_VCPUS
84
    return 1
85

    
86
  beparams[constants.BE_MEMORY] = utils.ParseUnit(beparams[constants.BE_MEMORY])
87

    
88
  # prepare hvparams dict
89
  for hv in constants.HYPER_TYPES:
90
    if hv not in hvparams:
91
      hvparams[hv] = {}
92
    for parameter in constants.HVC_DEFAULTS[hv]:
93
      if parameter not in hvparams[hv]:
94
        hvparams[hv][parameter] = constants.HVC_DEFAULTS[hv][parameter]
95

    
96
  for hv in hvlist:
97
    if hv not in constants.HYPER_TYPES:
98
      print "invalid hypervisor: %s" % hv
99
      return 1
100

    
101
  bootstrap.InitCluster(cluster_name=args[0],
102
                        secondary_ip=opts.secondary_ip,
103
                        hypervisor_type=opts.hypervisor_type,
104
                        vg_name=vg_name,
105
                        mac_prefix=opts.mac_prefix,
106
                        def_bridge=opts.def_bridge,
107
                        master_netdev=opts.master_netdev,
108
                        file_storage_dir=opts.file_storage_dir,
109
                        enabled_hypervisors=hvlist,
110
                        hvparams=hvparams,
111
                        beparams=beparams)
112
  return 0
113

    
114

    
115
def DestroyCluster(opts, args):
116
  """Destroy the cluster.
117

    
118
  Args:
119
    opts - class with options as members
120

    
121
  """
122
  if not opts.yes_do_it:
123
    print ("Destroying a cluster is irreversibly. If you really want destroy"
124
           " this cluster, supply the --yes-do-it option.")
125
    return 1
126

    
127
  op = opcodes.OpDestroyCluster()
128
  master = SubmitOpCode(op)
129
  # if we reached this, the opcode didn't fail; we can proceed to
130
  # shutdown all the daemons
131
  bootstrap.FinalizeClusterDestroy(master)
132
  return 0
133

    
134

    
135
def RenameCluster(opts, args):
136
  """Rename the cluster.
137

    
138
  Args:
139
    opts - class with options as members, we use force only
140
    args - list of arguments, expected to be [new_name]
141

    
142
  """
143
  name = args[0]
144
  if not opts.force:
145
    usertext = ("This will rename the cluster to '%s'. If you are connected"
146
                " over the network to the cluster name, the operation is very"
147
                " dangerous as the IP address will be removed from the node"
148
                " and the change may not go through. Continue?") % name
149
    if not AskUser(usertext):
150
      return 1
151

    
152
  op = opcodes.OpRenameCluster(name=name)
153
  SubmitOpCode(op)
154
  return 0
155

    
156

    
157
def ShowClusterVersion(opts, args):
158
  """Write version of ganeti software to the standard output.
159

    
160
  Args:
161
    opts - class with options as members
162

    
163
  """
164
  op = opcodes.OpQueryClusterInfo()
165
  result = SubmitOpCode(op)
166
  print ("Software version: %s" % result["software_version"])
167
  print ("Internode protocol: %s" % result["protocol_version"])
168
  print ("Configuration format: %s" % result["config_version"])
169
  print ("OS api version: %s" % result["os_api_version"])
170
  print ("Export interface: %s" % result["export_version"])
171
  return 0
172

    
173

    
174
def ShowClusterMaster(opts, args):
175
  """Write name of master node to the standard output.
176

    
177
  Args:
178
    opts - class with options as members
179

    
180
  """
181
  print GetClient().QueryConfigValues(["master_node"])[0]
182
  return 0
183

    
184

    
185
def ShowClusterConfig(opts, args):
186
  """Shows cluster information.
187

    
188
  """
189
  op = opcodes.OpQueryClusterInfo()
190
  result = SubmitOpCode(op)
191

    
192
  print ("Cluster name: %s" % result["name"])
193

    
194
  print ("Master node: %s" % result["master"])
195

    
196
  print ("Architecture (this node): %s (%s)" %
197
         (result["architecture"][0], result["architecture"][1]))
198

    
199
  print ("Default hypervisor: %s" % result["hypervisor_type"])
200
  print ("Enabled hypervisors: %s" % ", ".join(result["enabled_hypervisors"]))
201

    
202
  print "Hypervisor parameters:"
203
  for hv_name, hv_dict in result["hvparams"].items():
204
    print "  - %s:" % hv_name
205
    for item, val in hv_dict.iteritems():
206
      print "      %s: %s" % (item, val)
207

    
208
  print "Cluster parameters:"
209
  for gr_name, gr_dict in result["beparams"].items():
210
    print "  - %s:" % gr_name
211
    for item, val in gr_dict.iteritems():
212
      print "      %s: %s" % (item, val)
213

    
214
  return 0
215

    
216

    
217
def ClusterCopyFile(opts, args):
218
  """Copy a file from master to some nodes.
219

    
220
  Args:
221
    opts - class with options as members
222
    args - list containing a single element, the file name
223
  Opts used:
224
    nodes - list containing the name of target nodes; if empty, all nodes
225

    
226
  """
227
  filename = args[0]
228
  if not os.path.exists(filename):
229
    raise errors.OpPrereqError("No such filename '%s'" % filename)
230

    
231
  cl = GetClient()
232

    
233
  myname = utils.HostInfo().name
234

    
235
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
236

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

    
240
  srun = ssh.SshRunner(cluster_name=cluster_name)
241
  for node in results:
242
    if not srun.CopyFileToNode(node, filename):
243
      print >> sys.stderr, ("Copy of file %s to node %s failed" %
244
                            (filename, node))
245

    
246
  return 0
247

    
248

    
249
def RunClusterCommand(opts, args):
250
  """Run a command on some nodes.
251

    
252
  Args:
253
    opts - class with options as members
254
    args - the command list as a list
255
  Opts used:
256
    nodes: list containing the name of target nodes; if empty, all nodes
257

    
258
  """
259
  cl = GetClient()
260

    
261
  command = " ".join(args)
262
  op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
263
  nodes = [row[0] for row in SubmitOpCode(op, cl=cl)]
264

    
265
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
266
                                                    "master_node"])
267

    
268
  srun = ssh.SshRunner(cluster_name=cluster_name)
269

    
270
  # Make sure master node is at list end
271
  if master_node in nodes:
272
    nodes.remove(master_node)
273
    nodes.append(master_node)
274

    
275
  for name in nodes:
276
    result = srun.Run(name, "root", command)
277
    print ("------------------------------------------------")
278
    print ("node: %s" % name)
279
    print ("%s" % result.output)
280
    print ("return code = %s" % result.exit_code)
281

    
282
  return 0
283

    
284

    
285
def VerifyCluster(opts, args):
286
  """Verify integrity of cluster, performing various test on nodes.
287

    
288
  Args:
289
    opts - class with options as members
290

    
291
  """
292
  skip_checks = []
293
  if opts.skip_nplusone_mem:
294
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
295
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
296
  if SubmitOpCode(op):
297
    return 0
298
  else:
299
    return 1
300

    
301

    
302
def VerifyDisks(opts, args):
303
  """Verify integrity of cluster disks.
304

    
305
  Args:
306
    opts - class with options as members
307

    
308
  """
309
  op = opcodes.OpVerifyDisks()
310
  result = SubmitOpCode(op)
311
  if not isinstance(result, (list, tuple)) or len(result) != 4:
312
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
313

    
314
  nodes, nlvm, instances, missing = result
315

    
316
  if nodes:
317
    print "Nodes unreachable or with bad data:"
318
    for name in nodes:
319
      print "\t%s" % name
320
  retcode = constants.EXIT_SUCCESS
321

    
322
  if nlvm:
323
    for node, text in nlvm.iteritems():
324
      print ("Error on node %s: LVM error: %s" %
325
             (node, text[-400:].encode('string_escape')))
326
      retcode |= 1
327
      print "You need to fix these nodes first before fixing instances"
328

    
329
  if instances:
330
    for iname in instances:
331
      if iname in missing:
332
        continue
333
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
334
      try:
335
        print "Activating disks for instance '%s'" % iname
336
        SubmitOpCode(op)
337
      except errors.GenericError, err:
338
        nret, msg = FormatError(err)
339
        retcode |= nret
340
        print >> sys.stderr, ("Error activating disks for instance %s: %s" %
341
                              (iname, msg))
342

    
343
  if missing:
344
    for iname, ival in missing.iteritems():
345
      all_missing = utils.all(ival, lambda x: x[0] in nlvm)
346
      if all_missing:
347
        print ("Instance %s cannot be verified as it lives on"
348
               " broken nodes" % iname)
349
      else:
350
        print "Instance %s has missing logical volumes:" % iname
351
        ival.sort()
352
        for node, vol in ival:
353
          if node in nlvm:
354
            print ("\tbroken node %s /dev/xenvg/%s" % (node, vol))
355
          else:
356
            print ("\t%s /dev/xenvg/%s" % (node, vol))
357
    print ("You need to run replace_disks for all the above"
358
           " instances, if this message persist after fixing nodes.")
359
    retcode |= 1
360

    
361
  return retcode
362

    
363

    
364
def MasterFailover(opts, args):
365
  """Failover the master node.
366

    
367
  This command, when run on a non-master node, will cause the current
368
  master to cease being master, and the non-master to become new
369
  master.
370

    
371
  """
372
  return bootstrap.MasterFailover()
373

    
374

    
375
def SearchTags(opts, args):
376
  """Searches the tags on all the cluster.
377

    
378
  """
379
  op = opcodes.OpSearchTags(pattern=args[0])
380
  result = SubmitOpCode(op)
381
  if not result:
382
    return 1
383
  result = list(result)
384
  result.sort()
385
  for path, tag in result:
386
    print "%s %s" % (path, tag)
387

    
388

    
389
def SetClusterParams(opts, args):
390
  """Modify the cluster.
391

    
392
  Args:
393
    opts - class with options as members
394

    
395
  """
396
  if not (not opts.lvm_storage or opts.vg_name or
397
          opts.enabled_hypervisors or opts.hvparams or
398
          opts.beparams):
399
    print "Please give at least one of the parameters."
400
    return 1
401

    
402
  vg_name = opts.vg_name
403
  if not opts.lvm_storage and opts.vg_name:
404
    print ("Options --no-lvm-storage and --vg-name conflict.")
405
    return 1
406

    
407
  hvlist = opts.enabled_hypervisors
408
  if hvlist is not None:
409
    hvlist = hvlist.split(",")
410

    
411
  hvparams = opts.hvparams
412
  if hvparams:
413
    # a list of (name, dict) we can pass directly to dict()
414
    hvparams = dict(opts.hvparams)
415

    
416
  beparams = opts.beparams
417

    
418
  op = opcodes.OpSetClusterParams(vg_name=opts.vg_name,
419
                                  enabled_hypervisors=hvlist,
420
                                  hvparams=hvparams,
421
                                  beparams=beparams)
422
  SubmitOpCode(op)
423
  return 0
424

    
425

    
426
def QueueOps(opts, args):
427
  """Queue operations.
428

    
429
  """
430
  command = args[0]
431
  client = GetClient()
432
  if command in ("drain", "undrain"):
433
    drain_flag = command == "drain"
434
    client.SetQueueDrainFlag(drain_flag)
435
  elif command == "info":
436
    result = client.QueryConfigValues(["drain_flag"])
437
    print "The drain flag is",
438
    if result[0]:
439
      print "set"
440
    else:
441
      print "unset"
442
  return 0
443

    
444
# this is an option common to more than one command, so we declare
445
# it here and reuse it
446
node_option = make_option("-n", "--node", action="append", dest="nodes",
447
                          help="Node to copy to (if not given, all nodes),"
448
                               " can be given multiple times",
449
                          metavar="<node>", default=[])
450

    
451
commands = {
452
  'init': (InitCluster, ARGS_ONE,
453
           [DEBUG_OPT,
454
            make_option("-s", "--secondary-ip", dest="secondary_ip",
455
                        help="Specify the secondary ip for this node;"
456
                        " if given, the entire cluster must have secondary"
457
                        " addresses",
458
                        metavar="ADDRESS", default=None),
459
            make_option("-t", "--hypervisor-type", dest="hypervisor_type",
460
                        help="Specify the hypervisor type "
461
                        "(xen-pvm, kvm, fake, xen-hvm)",
462
                        metavar="TYPE", choices=["xen-pvm",
463
                                                 "kvm",
464
                                                 "fake",
465
                                                 "xen-hvm"],
466
                        default="xen-pvm",),
467
            make_option("-m", "--mac-prefix", dest="mac_prefix",
468
                        help="Specify the mac prefix for the instance IP"
469
                        " addresses, in the format XX:XX:XX",
470
                        metavar="PREFIX",
471
                        default="aa:00:00",),
472
            make_option("-g", "--vg-name", dest="vg_name",
473
                        help="Specify the volume group name "
474
                        " (cluster-wide) for disk allocation [xenvg]",
475
                        metavar="VG",
476
                        default=None,),
477
            make_option("-b", "--bridge", dest="def_bridge",
478
                        help="Specify the default bridge name (cluster-wide)"
479
                          " to connect the instances to [%s]" %
480
                          constants.DEFAULT_BRIDGE,
481
                        metavar="BRIDGE",
482
                        default=constants.DEFAULT_BRIDGE,),
483
            make_option("--master-netdev", dest="master_netdev",
484
                        help="Specify the node interface (cluster-wide)"
485
                          " on which the master IP address will be added "
486
                          " [%s]" % constants.DEFAULT_BRIDGE,
487
                        metavar="NETDEV",
488
                        default=constants.DEFAULT_BRIDGE,),
489
            make_option("--file-storage-dir", dest="file_storage_dir",
490
                        help="Specify the default directory (cluster-wide)"
491
                             " for storing the file-based disks [%s]" %
492
                             constants.DEFAULT_FILE_STORAGE_DIR,
493
                        metavar="DIR",
494
                        default=constants.DEFAULT_FILE_STORAGE_DIR,),
495
            make_option("--no-lvm-storage", dest="lvm_storage",
496
                        help="No support for lvm based instances"
497
                             " (cluster-wide)",
498
                        action="store_false", default=True,),
499
            make_option("--enabled-hypervisors", dest="enabled_hypervisors",
500
                        help="Comma-separated list of hypervisors",
501
                        type="string", default=None),
502
            ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
503
                       help="Hypervisor and hypervisor options, in the"
504
                         " format"
505
                       " hypervisor:option=value,option=value,...",
506
                       default=[],
507
                       action="append",
508
                       type="identkeyval"),
509
            keyval_option("-B", "--backend-parameters", dest="beparams",
510
                          type="keyval", default={},
511
                          help="Backend parameters"),
512
            ],
513
           "[opts...] <cluster_name>",
514
           "Initialises a new cluster configuration"),
515
  'destroy': (DestroyCluster, ARGS_NONE,
516
              [DEBUG_OPT,
517
               make_option("--yes-do-it", dest="yes_do_it",
518
                           help="Destroy cluster",
519
                           action="store_true"),
520
              ],
521
              "", "Destroy cluster"),
522
  'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
523
               "<new_name>",
524
               "Renames the cluster"),
525
  'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
526
             make_option("--no-nplus1-mem", dest="skip_nplusone_mem",
527
                         help="Skip N+1 memory redundancy tests",
528
                         action="store_true",
529
                         default=False,),
530
             ],
531
             "", "Does a check on the cluster configuration"),
532
  'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
533
                   "", "Does a check on the cluster disk status"),
534
  'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT],
535
                     "", "Makes the current node the master"),
536
  'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
537
              "", "Shows the cluster version"),
538
  'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
539
                "", "Shows the cluster master"),
540
  'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
541
               "[-n node...] <filename>",
542
               "Copies a file to all (or only some) nodes"),
543
  'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
544
              "[-n node...] <command>",
545
              "Runs a command on all (or only some) nodes"),
546
  'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
547
                 "", "Show cluster configuration"),
548
  'list-tags': (ListTags, ARGS_NONE,
549
                [DEBUG_OPT], "", "List the tags of the cluster"),
550
  'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
551
               "tag...", "Add tags to the cluster"),
552
  'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
553
                  "tag...", "Remove tags from the cluster"),
554
  'search-tags': (SearchTags, ARGS_ONE,
555
                  [DEBUG_OPT], "", "Searches the tags on all objects on"
556
                  " the cluster for a given pattern (regex)"),
557
  'queue': (QueueOps, ARGS_ONE, [DEBUG_OPT],
558
            "drain|undrain|info", "Change queue properties"),
559
  'modify': (SetClusterParams, ARGS_NONE,
560
             [DEBUG_OPT,
561
              make_option("-g", "--vg-name", dest="vg_name",
562
                          help="Specify the volume group name "
563
                          " (cluster-wide) for disk allocation "
564
                          "and enable lvm based storage",
565
                          metavar="VG",),
566
              make_option("--no-lvm-storage", dest="lvm_storage",
567
                          help="Disable support for lvm based instances"
568
                               " (cluster-wide)",
569
                          action="store_false", default=True,),
570
              make_option("--enabled-hypervisors", dest="enabled_hypervisors",
571
                          help="Comma-separated list of hypervisors",
572
                          type="string", default=None),
573
              ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
574
                         help="Hypervisor and hypervisor options, in the"
575
                         " format"
576
                         " hypervisor:option=value,option=value,...",
577
                         default=[],
578
                         action="append",
579
                         type="identkeyval"),
580
              keyval_option("-B", "--backend-parameters", dest="beparams",
581
                            type="keyval", default={},
582
                            help="Backend parameters"),
583
              ],
584
             "[opts...]",
585
             "Alters the parameters of the cluster"),
586
  }
587

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