Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ 02691904

History | View | Annotate | Download (19.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
# pylint: disable-msg=W0401,W0614
23
# W0401: Wildcard import ganeti.cli
24
# W0614: Unused import %s from wildcard import (since we need cli)
25

    
26
import sys
27
from optparse import make_option
28
import os.path
29

    
30
from ganeti.cli import *
31
from ganeti import opcodes
32
from ganeti import constants
33
from ganeti import errors
34
from ganeti import utils
35
from ganeti import bootstrap
36
from ganeti import ssh
37

    
38

    
39
def InitCluster(opts, args):
40
  """Initialize the cluster.
41

    
42
  Args:
43
    opts - class with options as members
44
    args - list of arguments, expected to be [clustername]
45

    
46
  """
47
  if not opts.lvm_storage and opts.vg_name:
48
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
49
    return 1
50

    
51
  vg_name = opts.vg_name
52
  if opts.lvm_storage and not opts.vg_name:
53
    vg_name = constants.DEFAULT_VG
54

    
55
  hvlist = opts.enabled_hypervisors
56
  if hvlist is not None:
57
    hvlist = hvlist.split(",")
58
  else:
59
    hvlist = [constants.DEFAULT_ENABLED_HYPERVISOR]
60

    
61
  # avoid an impossible situation
62
  if opts.default_hypervisor in hvlist:
63
    default_hypervisor = opts.default_hypervisor
64
  else:
65
    default_hypervisor = hvlist[0]
66

    
67
  hvparams = opts.hvparams
68
  if hvparams:
69
    # a list of (name, dict) we can pass directly to dict()
70
    hvparams = dict(opts.hvparams)
71
  else:
72
    # otherwise init as empty dict
73
    hvparams = {}
74

    
75
  beparams = opts.beparams
76
  # check for invalid parameters
77
  for parameter in beparams:
78
    if parameter not in constants.BES_PARAMETERS:
79
      print "Invalid backend parameter: %s" % parameter
80
      return 1
81

    
82
  # prepare beparams dict
83
  for parameter in constants.BES_PARAMETERS:
84
    if parameter not in beparams:
85
      beparams[parameter] = constants.BEC_DEFAULTS[parameter]
86

    
87
  # type wrangling
88
  try:
89
    beparams[constants.BE_VCPUS] = int(beparams[constants.BE_VCPUS])
90
  except ValueError:
91
    print "%s must be an integer" % constants.BE_VCPUS
92
    return 1
93

    
94
  beparams[constants.BE_MEMORY] = utils.ParseUnit(beparams[constants.BE_MEMORY])
95

    
96
  # prepare hvparams dict
97
  for hv in constants.HYPER_TYPES:
98
    if hv not in hvparams:
99
      hvparams[hv] = {}
100
    for parameter in constants.HVC_DEFAULTS[hv]:
101
      if parameter not in hvparams[hv]:
102
        hvparams[hv][parameter] = constants.HVC_DEFAULTS[hv][parameter]
103

    
104
  for hv in hvlist:
105
    if hv not in constants.HYPER_TYPES:
106
      print "invalid hypervisor: %s" % hv
107
      return 1
108

    
109
  bootstrap.InitCluster(cluster_name=args[0],
110
                        secondary_ip=opts.secondary_ip,
111
                        vg_name=vg_name,
112
                        mac_prefix=opts.mac_prefix,
113
                        def_bridge=opts.def_bridge,
114
                        master_netdev=opts.master_netdev,
115
                        file_storage_dir=opts.file_storage_dir,
116
                        enabled_hypervisors=hvlist,
117
                        default_hypervisor=default_hypervisor,
118
                        hvparams=hvparams,
119
                        beparams=beparams)
120
  return 0
121

    
122

    
123
def DestroyCluster(opts, args):
124
  """Destroy the cluster.
125

    
126
  Args:
127
    opts - class with options as members
128

    
129
  """
130
  if not opts.yes_do_it:
131
    ToStderr("Destroying a cluster is irreversible. If you really want"
132
             " destroy this cluster, supply the --yes-do-it option.")
133
    return 1
134

    
135
  op = opcodes.OpDestroyCluster()
136
  master = SubmitOpCode(op)
137
  # if we reached this, the opcode didn't fail; we can proceed to
138
  # shutdown all the daemons
139
  bootstrap.FinalizeClusterDestroy(master)
140
  return 0
141

    
142

    
143
def RenameCluster(opts, args):
144
  """Rename the cluster.
145

    
146
  Args:
147
    opts - class with options as members, we use force only
148
    args - list of arguments, expected to be [new_name]
149

    
150
  """
151
  name = args[0]
152
  if not opts.force:
153
    usertext = ("This will rename the cluster to '%s'. If you are connected"
154
                " over the network to the cluster name, the operation is very"
155
                " dangerous as the IP address will be removed from the node"
156
                " and the change may not go through. Continue?") % name
157
    if not AskUser(usertext):
158
      return 1
159

    
160
  op = opcodes.OpRenameCluster(name=name)
161
  SubmitOpCode(op)
162
  return 0
163

    
164

    
165
def ShowClusterVersion(opts, args):
166
  """Write version of ganeti software to the standard output.
167

    
168
  Args:
169
    opts - class with options as members
170

    
171
  """
172
  op = opcodes.OpQueryClusterInfo()
173
  result = SubmitOpCode(op)
174
  ToStdout("Software version: %s", result["software_version"])
175
  ToStdout("Internode protocol: %s", result["protocol_version"])
176
  ToStdout("Configuration format: %s", result["config_version"])
177
  ToStdout("OS api version: %s", result["os_api_version"])
178
  ToStdout("Export interface: %s", result["export_version"])
179
  return 0
180

    
181

    
182
def ShowClusterMaster(opts, args):
183
  """Write name of master node to the standard output.
184

    
185
  Args:
186
    opts - class with options as members
187

    
188
  """
189
  ToStdout("%s", GetClient().QueryConfigValues(["master_node"])[0])
190
  return 0
191

    
192

    
193
def ShowClusterConfig(opts, args):
194
  """Shows cluster information.
195

    
196
  """
197
  op = opcodes.OpQueryClusterInfo()
198
  result = SubmitOpCode(op)
199

    
200
  ToStdout("Cluster name: %s", result["name"])
201

    
202
  ToStdout("Master node: %s", result["master"])
203

    
204
  ToStdout("Architecture (this node): %s (%s)",
205
           result["architecture"][0], result["architecture"][1])
206

    
207
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
208
  ToStdout("Enabled hypervisors: %s", ", ".join(result["enabled_hypervisors"]))
209

    
210
  ToStdout("Hypervisor parameters:")
211
  for hv_name, hv_dict in result["hvparams"].items():
212
    ToStdout("  - %s:", hv_name)
213
    for item, val in hv_dict.iteritems():
214
      ToStdout("      %s: %s", item, val)
215

    
216
  ToStdout("Cluster parameters:")
217
  for gr_name, gr_dict in result["beparams"].items():
218
    ToStdout("  - %s:", gr_name)
219
    for item, val in gr_dict.iteritems():
220
      ToStdout("      %s: %s", item, val)
221

    
222
  return 0
223

    
224

    
225
def ClusterCopyFile(opts, args):
226
  """Copy a file from master to some nodes.
227

    
228
  Args:
229
    opts - class with options as members
230
    args - list containing a single element, the file name
231
  Opts used:
232
    nodes - list containing the name of target nodes; if empty, all nodes
233

    
234
  """
235
  filename = args[0]
236
  if not os.path.exists(filename):
237
    raise errors.OpPrereqError("No such filename '%s'" % filename)
238

    
239
  cl = GetClient()
240

    
241
  myname = utils.HostInfo().name
242

    
243
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
244

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

    
248
  srun = ssh.SshRunner(cluster_name=cluster_name)
249
  for node in results:
250
    if not srun.CopyFileToNode(node, filename):
251
      ToStderr("Copy of file %s to node %s failed", filename, node)
252

    
253
  return 0
254

    
255

    
256
def RunClusterCommand(opts, args):
257
  """Run a command on some nodes.
258

    
259
  Args:
260
    opts - class with options as members
261
    args - the command list as a list
262
  Opts used:
263
    nodes: list containing the name of target nodes; if empty, all nodes
264

    
265
  """
266
  cl = GetClient()
267

    
268
  command = " ".join(args)
269
  op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
270
  nodes = [row[0] for row in SubmitOpCode(op, cl=cl)]
271

    
272
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
273
                                                    "master_node"])
274

    
275
  srun = ssh.SshRunner(cluster_name=cluster_name)
276

    
277
  # Make sure master node is at list end
278
  if master_node in nodes:
279
    nodes.remove(master_node)
280
    nodes.append(master_node)
281

    
282
  for name in nodes:
283
    result = srun.Run(name, "root", command)
284
    ToStdout("------------------------------------------------")
285
    ToStdout("node: %s", name)
286
    ToStdout("%s", result.output)
287
    ToStdout("return code = %s", result.exit_code)
288

    
289
  return 0
290

    
291

    
292
def VerifyCluster(opts, args):
293
  """Verify integrity of cluster, performing various test on nodes.
294

    
295
  Args:
296
    opts - class with options as members
297

    
298
  """
299
  skip_checks = []
300
  if opts.skip_nplusone_mem:
301
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
302
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
303
  if SubmitOpCode(op):
304
    return 0
305
  else:
306
    return 1
307

    
308

    
309
def VerifyDisks(opts, args):
310
  """Verify integrity of cluster disks.
311

    
312
  Args:
313
    opts - class with options as members
314

    
315
  """
316
  op = opcodes.OpVerifyDisks()
317
  result = SubmitOpCode(op)
318
  if not isinstance(result, (list, tuple)) or len(result) != 4:
319
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
320

    
321
  nodes, nlvm, instances, missing = result
322

    
323
  if nodes:
324
    ToStdout("Nodes unreachable or with bad data:")
325
    for name in nodes:
326
      ToStdout("\t%s", name)
327
  retcode = constants.EXIT_SUCCESS
328

    
329
  if nlvm:
330
    for node, text in nlvm.iteritems():
331
      ToStdout("Error on node %s: LVM error: %s",
332
               node, text[-400:].encode('string_escape'))
333
      retcode |= 1
334
      ToStdout("You need to fix these nodes first before fixing instances")
335

    
336
  if instances:
337
    for iname in instances:
338
      if iname in missing:
339
        continue
340
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
341
      try:
342
        ToStdout("Activating disks for instance '%s'", iname)
343
        SubmitOpCode(op)
344
      except errors.GenericError, err:
345
        nret, msg = FormatError(err)
346
        retcode |= nret
347
        ToStderr("Error activating disks for instance %s: %s", iname, msg)
348

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

    
367
  return retcode
368

    
369

    
370
def MasterFailover(opts, args):
371
  """Failover the master node.
372

    
373
  This command, when run on a non-master node, will cause the current
374
  master to cease being master, and the non-master to become new
375
  master.
376

    
377
  """
378
  return bootstrap.MasterFailover()
379

    
380

    
381
def SearchTags(opts, args):
382
  """Searches the tags on all the cluster.
383

    
384
  """
385
  op = opcodes.OpSearchTags(pattern=args[0])
386
  result = SubmitOpCode(op)
387
  if not result:
388
    return 1
389
  result = list(result)
390
  result.sort()
391
  for path, tag in result:
392
    ToStdout("%s %s", path, tag)
393

    
394

    
395
def SetClusterParams(opts, args):
396
  """Modify the cluster.
397

    
398
  Args:
399
    opts - class with options as members
400

    
401
  """
402
  if not (not opts.lvm_storage or opts.vg_name or
403
          opts.enabled_hypervisors or opts.hvparams or
404
          opts.beparams):
405
    ToStderr("Please give at least one of the parameters.")
406
    return 1
407

    
408
  vg_name = opts.vg_name
409
  if not opts.lvm_storage and opts.vg_name:
410
    ToStdout("Options --no-lvm-storage and --vg-name conflict.")
411
    return 1
412

    
413
  hvlist = opts.enabled_hypervisors
414
  if hvlist is not None:
415
    hvlist = hvlist.split(",")
416

    
417
  hvparams = opts.hvparams
418
  if hvparams:
419
    # a list of (name, dict) we can pass directly to dict()
420
    hvparams = dict(opts.hvparams)
421

    
422
  beparams = opts.beparams
423

    
424
  op = opcodes.OpSetClusterParams(vg_name=opts.vg_name,
425
                                  enabled_hypervisors=hvlist,
426
                                  hvparams=hvparams,
427
                                  beparams=beparams)
428
  SubmitOpCode(op)
429
  return 0
430

    
431

    
432
def QueueOps(opts, args):
433
  """Queue operations.
434

    
435
  """
436
  command = args[0]
437
  client = GetClient()
438
  if command in ("drain", "undrain"):
439
    drain_flag = command == "drain"
440
    client.SetQueueDrainFlag(drain_flag)
441
  elif command == "info":
442
    result = client.QueryConfigValues(["drain_flag"])
443
    if result[0]:
444
      val = "set"
445
    else:
446
      val = "unset"
447
    ToStdout("The drain flag is %s" % val)
448
  return 0
449

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

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

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