Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ 4342e89b

History | View | Annotate | Download (19.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
  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
                        vg_name=vg_name,
104
                        mac_prefix=opts.mac_prefix,
105
                        def_bridge=opts.def_bridge,
106
                        master_netdev=opts.master_netdev,
107
                        file_storage_dir=opts.file_storage_dir,
108
                        enabled_hypervisors=hvlist,
109
                        hvparams=hvparams,
110
                        beparams=beparams)
111
  return 0
112

    
113

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

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

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

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

    
133

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

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

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

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

    
155

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

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

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

    
172

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

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

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

    
183

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

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

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

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

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

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

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

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

    
213
  return 0
214

    
215

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

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

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

    
230
  cl = GetClient()
231

    
232
  myname = utils.HostInfo().name
233

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

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

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

    
245
  return 0
246

    
247

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

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

    
257
  """
258
  cl = GetClient()
259

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

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

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

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

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

    
281
  return 0
282

    
283

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

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

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

    
300

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

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

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

    
313
  nodes, nlvm, instances, missing = result
314

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

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

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

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

    
360
  return retcode
361

    
362

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

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

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

    
373

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

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

    
387

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

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

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

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

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

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

    
415
  beparams = opts.beparams
416

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

    
424

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

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

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

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

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