gnt-cluster 19.3 KB
Newer Older
Iustin Pop's avatar
Iustin Pop committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
#!/usr/bin/python
#

# Copyright (C) 2006, 2007 Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.


import sys
from optparse import make_option
import pprint
25
import os.path
Iustin Pop's avatar
Iustin Pop committed
26 27 28

from ganeti.cli import *
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 34
from ganeti import ssh
from ganeti import ssconf
Iustin Pop's avatar
Iustin Pop committed
35 36 37 38 39 40 41 42 43 44


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

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

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

  vg_name = opts.vg_name
  if opts.lvm_storage and not opts.vg_name:
    vg_name = constants.DEFAULT_VG

53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
  hvlist = opts.enabled_hypervisors
  if hvlist is not None:
    hvlist = hvlist.split(",")
  else:
    hvlist = constants.DEFAULT_ENABLED_HYPERVISOR

  hvparams = opts.hvparams
  if hvparams:
    # a list of (name, dict) we can pass directly to dict()
    hvparams = dict(opts.hvparams)
  else:
    # otherwise init as empty dict
    hvparams = {}

  beparams = opts.beparams
  # check for invalid parameters
  for parameter in beparams:
    if parameter not in constants.BES_PARAMETERS:
      print "Invalid backend parameter: %s" % parameter
      return 1

  # prepare beparams dict
  for parameter in constants.BES_PARAMETERS:
    if parameter not in beparams:
      beparams[parameter] = constants.BEC_DEFAULTS[parameter]

  # type wrangling
  try:
    beparams[constants.BE_VCPUS] = int(beparams[constants.BE_VCPUS])
  except ValueError:
    print "%s must be an integer" % constants.BE_VCPUS
    return 1

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

  # prepare hvparams dict
  for hv in constants.HYPER_TYPES:
    if hv not in hvparams:
      hvparams[hv] = {}
    for parameter in constants.HVC_DEFAULTS[hv]:
      if parameter not in hvparams[hv]:
        hvparams[hv][parameter] = constants.HVC_DEFAULTS[hv][parameter]

  for hv in hvlist:
    if hv not in constants.HYPER_TYPES:
      print "invalid hypervisor: %s" % hv
      return 1

101 102 103 104 105 106
  bootstrap.InitCluster(cluster_name=args[0],
                        secondary_ip=opts.secondary_ip,
                        vg_name=vg_name,
                        mac_prefix=opts.mac_prefix,
                        def_bridge=opts.def_bridge,
                        master_netdev=opts.master_netdev,
107 108 109 110
                        file_storage_dir=opts.file_storage_dir,
                        enabled_hypervisors=hvlist,
                        hvparams=hvparams,
                        beparams=beparams)
Iustin Pop's avatar
Iustin Pop committed
111 112 113 114 115 116 117 118
  return 0


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

  Args:
    opts - class with options as members
119

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

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


134 135 136 137 138 139 140 141 142 143 144 145 146 147
def RenameCluster(opts, args):
  """Rename the cluster.

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

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

  op = opcodes.OpRenameCluster(name=name)
  SubmitOpCode(op)
  return 0


Iustin Pop's avatar
Iustin Pop committed
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
def ShowClusterVersion(opts, args):
  """Write version of ganeti software to the standard output.

  Args:
    opts - class with options as members

  """
  op = opcodes.OpQueryClusterInfo()
  result = SubmitOpCode(op)
  print ("Software version: %s" % result["software_version"])
  print ("Internode protocol: %s" % result["protocol_version"])
  print ("Configuration format: %s" % result["config_version"])
  print ("OS api version: %s" % result["os_api_version"])
  print ("Export interface: %s" % result["export_version"])
  return 0


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

  Args:
    opts - class with options as members

  """
Michael Hanselmann's avatar
Michael Hanselmann committed
180
  print GetClient().QueryConfigValues(["master_node"])[0]
Iustin Pop's avatar
Iustin Pop committed
181 182 183 184 185 186 187 188 189 190 191 192 193 194
  return 0


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

  """
  op = opcodes.OpQueryClusterInfo()
  result = SubmitOpCode(op)

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

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

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

198 199 200 201 202 203 204 205 206 207 208 209 210 211
  print ("Default hypervisor: %s" % result["hypervisor_type"])
  print ("Enabled hypervisors: %s" % ", ".join(result["enabled_hypervisors"]))

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

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

Iustin Pop's avatar
Iustin Pop committed
213 214 215 216 217 218 219 220 221 222 223 224 225
  return 0


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

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

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

Iustin Pop's avatar
Iustin Pop committed
230 231
  cl = GetClient()

232 233
  myname = utils.HostInfo().name

Iustin Pop's avatar
Iustin Pop committed
234 235
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]

236
  op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
Iustin Pop's avatar
Iustin Pop committed
237
  results = [row[0] for row in SubmitOpCode(op, cl=cl) if row[0] != myname]
Michael Hanselmann's avatar
Michael Hanselmann committed
238

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

Iustin Pop's avatar
Iustin Pop committed
245 246 247 248 249 250 251 252 253 254 255 256 257
  return 0


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

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

  """
Iustin Pop's avatar
Iustin Pop committed
258
  cl = GetClient()
Michael Hanselmann's avatar
Michael Hanselmann committed
259

Iustin Pop's avatar
Iustin Pop committed
260
  command = " ".join(args)
261
  op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
Iustin Pop's avatar
Iustin Pop committed
262 263 264 265
  nodes = [row[0] for row in SubmitOpCode(op, cl=cl)]

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

Iustin Pop's avatar
Iustin Pop committed
267
  srun = ssh.SshRunner(cluster_name=cluster_name)
268

Michael Hanselmann's avatar
Michael Hanselmann committed
269
  # Make sure master node is at list end
270 271 272 273 274 275
  if master_node in nodes:
    nodes.remove(master_node)
    nodes.append(master_node)

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

  return 0
Iustin Pop's avatar
Iustin Pop committed
282 283 284 285 286 287 288 289 290


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

  Args:
    opts - class with options as members

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


301 302 303 304 305 306 307 308 309
def VerifyDisks(opts, args):
  """Verify integrity of cluster disks.

  Args:
    opts - class with options as members

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

313 314
  nodes, nlvm, instances, missing = result

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

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

328 329
  if instances:
    for iname in instances:
330 331
      if iname in missing:
        continue
332 333 334 335 336 337 338
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
      try:
        print "Activating disks for instance '%s'" % iname
        SubmitOpCode(op)
      except errors.GenericError, err:
        nret, msg = FormatError(err)
        retcode |= nret
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
        print >> sys.stderr, ("Error activating disks for instance %s: %s" %
                              (iname, msg))

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

  return retcode


Iustin Pop's avatar
Iustin Pop committed
363 364 365 366 367 368 369 370
def MasterFailover(opts, args):
  """Failover the master node.

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

  """
371
  return bootstrap.MasterFailover()
Iustin Pop's avatar
Iustin Pop committed
372 373


Iustin Pop's avatar
Iustin Pop committed
374 375 376 377 378 379 380 381 382 383 384 385 386 387
def SearchTags(opts, args):
  """Searches the tags on all the cluster.

  """
  op = opcodes.OpSearchTags(pattern=args[0])
  result = SubmitOpCode(op)
  if not result:
    return 1
  result = list(result)
  result.sort()
  for path, tag in result:
    print "%s %s" % (path, tag)


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

  Args:
    opts - class with options as members

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

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

406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
  hvlist = opts.enabled_hypervisors
  if hvlist is not None:
    hvlist = hvlist.split(",")

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

  beparams = opts.beparams

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


425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
def QueueOps(opts, args):
  """Queue operations.

  """
  command = args[0]
  client = GetClient()
  if command in ("drain", "undrain"):
    drain_flag = command == "drain"
    client.SetQueueDrainFlag(drain_flag)
  elif command == "info":
    result = client.QueryConfigValues(["drain_flag"])
    print "The drain flag is",
    if result[0]:
      print "set"
    else:
      print "unset"
  return 0

Iustin Pop's avatar
Iustin Pop committed
443 444 445
# this is an option common to more than one command, so we declare
# it here and reuse it
node_option = make_option("-n", "--node", action="append", dest="nodes",
446 447 448
                          help="Node to copy to (if not given, all nodes),"
                               " can be given multiple times",
                          metavar="<node>", default=[])
Iustin Pop's avatar
Iustin Pop committed
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466

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

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