gnt-cluster 23.9 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
#!/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.

Iustin Pop's avatar
Iustin Pop committed
21
"""Cluster related commands"""
Iustin Pop's avatar
Iustin Pop committed
22

23
# pylint: disable-msg=W0401,W0613,W0614,C0103
24
# W0401: Wildcard import ganeti.cli
25
# W0613: Unused argument, since all functions follow the same API
26
# W0614: Unused import %s from wildcard import (since we need cli)
Iustin Pop's avatar
Iustin Pop committed
27
# C0103: Invalid name gnt-cluster
28

Iustin Pop's avatar
Iustin Pop committed
29
import sys
30
import os.path
31
import time
32
import OpenSSL
Iustin Pop's avatar
Iustin Pop committed
33 34 35

from ganeti.cli import *
from ganeti import opcodes
36
from ganeti import constants
37
from ganeti import errors
38
from ganeti import utils
39
from ganeti import bootstrap
40
from ganeti import ssh
41
from ganeti import objects
Iustin Pop's avatar
Iustin Pop committed
42 43


44
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
45 46 47
def InitCluster(opts, args):
  """Initialize the cluster.

48 49 50 51 52 53
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the desired
      cluster name
  @rtype: int
  @return: the desired exit code
Iustin Pop's avatar
Iustin Pop committed
54 55

  """
56
  if not opts.lvm_storage and opts.vg_name:
57
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
58 59 60 61 62 63
    return 1

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

64
  hvlist = opts.enabled_hypervisors
65 66
  if hvlist is None:
    hvlist = constants.DEFAULT_ENABLED_HYPERVISOR
67
  hvlist = hvlist.split(",")
68

69
  hvparams = dict(opts.hvparams)
70
  beparams = opts.beparams
71
  nicparams = opts.nicparams
72 73

  # prepare beparams dict
74
  beparams = objects.FillDict(constants.BEC_DEFAULTS, beparams)
75
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
76

77 78 79 80
  # prepare nicparams dict
  nicparams = objects.FillDict(constants.NICC_DEFAULTS, nicparams)
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)

81 82 83 84
  # prepare hvparams dict
  for hv in constants.HYPER_TYPES:
    if hv not in hvparams:
      hvparams[hv] = {}
85
    hvparams[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], hvparams[hv])
86
    utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
87

88 89 90
  if opts.candidate_pool_size is None:
    opts.candidate_pool_size = constants.MASTER_POOL_SIZE_DEFAULT

91 92 93
  if opts.mac_prefix is None:
    opts.mac_prefix = constants.DEFAULT_MAC_PREFIX

94 95 96 97 98
  bootstrap.InitCluster(cluster_name=args[0],
                        secondary_ip=opts.secondary_ip,
                        vg_name=vg_name,
                        mac_prefix=opts.mac_prefix,
                        master_netdev=opts.master_netdev,
99 100 101
                        file_storage_dir=opts.file_storage_dir,
                        enabled_hypervisors=hvlist,
                        hvparams=hvparams,
102
                        beparams=beparams,
103
                        nicparams=nicparams,
104
                        candidate_pool_size=opts.candidate_pool_size,
105
                        modify_etc_hosts=opts.modify_etc_hosts,
106
                        modify_ssh_setup=opts.modify_ssh_setup,
107
                        )
108
  op = opcodes.OpPostInitCluster()
109
  SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
110 111 112
  return 0


113
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
114 115 116
def DestroyCluster(opts, args):
  """Destroy the cluster.

117 118 119 120 121
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should be an empty list
  @rtype: int
  @return: the desired exit code
122

Iustin Pop's avatar
Iustin Pop committed
123 124
  """
  if not opts.yes_do_it:
125 126
    ToStderr("Destroying a cluster is irreversible. If you really want"
             " destroy this cluster, supply the --yes-do-it option.")
Iustin Pop's avatar
Iustin Pop committed
127 128 129
    return 1

  op = opcodes.OpDestroyCluster()
130
  master = SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
131 132 133
  # 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
134 135 136
  return 0


137 138 139
def RenameCluster(opts, args):
  """Rename the cluster.

140 141 142 143 144
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the new cluster name
  @rtype: int
  @return: the desired exit code
145 146 147 148 149 150 151 152

  """
  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
153
    if not AskUser(usertext):
154 155 156
      return 1

  op = opcodes.OpRenameCluster(name=name)
157
  SubmitOpCode(op, opts=opts)
158 159 160
  return 0


161 162 163 164 165 166 167 168 169 170
def RedistributeConfig(opts, args):
  """Forces push of the cluster configuration.

  @param opts: the command line options selected by the user
  @type args: list
  @param args: empty list
  @rtype: int
  @return: the desired exit code

  """
171
  op = opcodes.OpRedistributeConfig()
172 173 174 175
  SubmitOrSend(op, opts)
  return 0


Iustin Pop's avatar
Iustin Pop committed
176 177 178
def ShowClusterVersion(opts, args):
  """Write version of ganeti software to the standard output.

179 180 181 182 183
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should be an empty list
  @rtype: int
  @return: the desired exit code
Iustin Pop's avatar
Iustin Pop committed
184 185

  """
186 187
  cl = GetClient()
  result = cl.QueryClusterInfo()
188 189 190 191 192
  ToStdout("Software version: %s", result["software_version"])
  ToStdout("Internode protocol: %s", result["protocol_version"])
  ToStdout("Configuration format: %s", result["config_version"])
  ToStdout("OS api version: %s", result["os_api_version"])
  ToStdout("Export interface: %s", result["export_version"])
Iustin Pop's avatar
Iustin Pop committed
193 194 195 196 197 198
  return 0


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

199 200 201 202 203
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should be an empty list
  @rtype: int
  @return: the desired exit code
Iustin Pop's avatar
Iustin Pop committed
204 205

  """
206 207
  master = bootstrap.GetMaster()
  ToStdout(master)
Iustin Pop's avatar
Iustin Pop committed
208 209
  return 0

210

211
def _PrintGroupedParams(paramsdict, level=1):
212 213 214 215
  """Print Grouped parameters (be, nic, disk) by group.

  @type paramsdict: dict of dicts
  @param paramsdict: {group: {param: value, ...}, ...}
216 217
  @type level: int
  @param level: Level of indention
218 219

  """
220 221 222 223 224 225 226
  indent = "  " * level
  for item, val in paramsdict.items():
    if isinstance(val, dict):
      ToStdout("%s- %s:", indent, item)
      _PrintGroupedParams(val, level=level + 1)
    else:
      ToStdout("%s  %s: %s", indent, item, val)
Iustin Pop's avatar
Iustin Pop committed
227

228

Iustin Pop's avatar
Iustin Pop committed
229 230 231
def ShowClusterConfig(opts, args):
  """Shows cluster information.

232 233 234 235 236 237
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should be an empty list
  @rtype: int
  @return: the desired exit code

Iustin Pop's avatar
Iustin Pop committed
238
  """
239 240
  cl = GetClient()
  result = cl.QueryClusterInfo()
Iustin Pop's avatar
Iustin Pop committed
241

242
  ToStdout("Cluster name: %s", result["name"])
243
  ToStdout("Cluster UUID: %s", result["uuid"])
Iustin Pop's avatar
Iustin Pop committed
244

245 246 247
  ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
  ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))

248
  ToStdout("Master node: %s", result["master"])
Iustin Pop's avatar
Iustin Pop committed
249

250 251
  ToStdout("Architecture (this node): %s (%s)",
           result["architecture"][0], result["architecture"][1])
Iustin Pop's avatar
Iustin Pop committed
252

253
  if result["tags"]:
254
    tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
255 256 257 258 259
  else:
    tags = "(none)"

  ToStdout("Tags: %s", tags)

260
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
261 262
  ToStdout("Enabled hypervisors: %s",
           utils.CommaJoin(result["enabled_hypervisors"]))
263

264
  ToStdout("Hypervisor parameters:")
265
  _PrintGroupedParams(result["hvparams"])
266

267 268 269
  ToStdout("OS specific hypervisor parameters:")
  _PrintGroupedParams(result["os_hvp"])

270
  ToStdout("Cluster parameters:")
271
  ToStdout("  - candidate pool size: %s", result["candidate_pool_size"])
272 273 274
  ToStdout("  - master netdev: %s", result["master_netdev"])
  ToStdout("  - lvm volume group: %s", result["volume_group_name"])
  ToStdout("  - file storage path: %s", result["file_storage_dir"])
275 276

  ToStdout("Default instance parameters:")
277 278 279 280
  _PrintGroupedParams(result["beparams"])

  ToStdout("Default nic parameters:")
  _PrintGroupedParams(result["nicparams"])
281

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


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

288 289 290 291 292 293
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the path of
      the file to be copied
  @rtype: int
  @return: the desired exit code
Iustin Pop's avatar
Iustin Pop committed
294 295

  """
296 297
  filename = args[0]
  if not os.path.exists(filename):
298 299
    raise errors.OpPrereqError("No such filename '%s'" % filename,
                               errors.ECODE_INVAL)
300

Iustin Pop's avatar
Iustin Pop committed
301 302 303 304
  cl = GetClient()

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

305 306
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
                           secondary_ips=opts.use_replication_network)
Michael Hanselmann's avatar
Michael Hanselmann committed
307

Iustin Pop's avatar
Iustin Pop committed
308
  srun = ssh.SshRunner(cluster_name=cluster_name)
309 310
  for node in results:
    if not srun.CopyFileToNode(node, filename):
311
      ToStderr("Copy of file %s to node %s failed", filename, node)
312

Iustin Pop's avatar
Iustin Pop committed
313 314 315 316 317 318
  return 0


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

319 320 321 322 323
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain the command to be run and its arguments
  @rtype: int
  @return: the desired exit code
Iustin Pop's avatar
Iustin Pop committed
324 325

  """
Iustin Pop's avatar
Iustin Pop committed
326
  cl = GetClient()
Michael Hanselmann's avatar
Michael Hanselmann committed
327

Iustin Pop's avatar
Iustin Pop committed
328
  command = " ".join(args)
329 330

  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
Iustin Pop's avatar
Iustin Pop committed
331 332 333

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

Iustin Pop's avatar
Iustin Pop committed
335
  srun = ssh.SshRunner(cluster_name=cluster_name)
336

Michael Hanselmann's avatar
Michael Hanselmann committed
337
  # Make sure master node is at list end
338 339 340 341 342 343
  if master_node in nodes:
    nodes.remove(master_node)
    nodes.append(master_node)

  for name in nodes:
    result = srun.Run(name, "root", command)
344 345 346 347
    ToStdout("------------------------------------------------")
    ToStdout("node: %s", name)
    ToStdout("%s", result.output)
    ToStdout("return code = %s", result.exit_code)
348 349

  return 0
Iustin Pop's avatar
Iustin Pop committed
350 351 352 353 354


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

355 356 357 358 359
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should be an empty list
  @rtype: int
  @return: the desired exit code
Iustin Pop's avatar
Iustin Pop committed
360 361

  """
Iustin Pop's avatar
Iustin Pop committed
362
  skip_checks = []
363 364
  if opts.skip_nplusone_mem:
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
365 366
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
                               verbose=opts.verbose,
367 368
                               error_codes=opts.error_codes,
                               debug_simulate_errors=opts.simulate_errors)
369
  if SubmitOpCode(op, opts=opts):
370 371 372
    return 0
  else:
    return 1
Iustin Pop's avatar
Iustin Pop committed
373 374


375 376 377
def VerifyDisks(opts, args):
  """Verify integrity of cluster disks.

378 379 380 381 382
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should be an empty list
  @rtype: int
  @return: the desired exit code
383 384 385

  """
  op = opcodes.OpVerifyDisks()
386
  result = SubmitOpCode(op, opts=opts)
387
  if not isinstance(result, (list, tuple)) or len(result) != 3:
388 389
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")

390
  bad_nodes, instances, missing = result
391

392
  retcode = constants.EXIT_SUCCESS
393

394 395 396
  if bad_nodes:
    for node, text in bad_nodes.items():
      ToStdout("Error gathering data on node %s: %s",
397
               node, utils.SafeEncode(text[-400:]))
398
      retcode |= 1
399
      ToStdout("You need to fix these nodes first before fixing instances")
400

401 402
  if instances:
    for iname in instances:
403 404
      if iname in missing:
        continue
405 406
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
      try:
407
        ToStdout("Activating disks for instance '%s'", iname)
408
        SubmitOpCode(op, opts=opts)
409 410 411
      except errors.GenericError, err:
        nret, msg = FormatError(err)
        retcode |= nret
412
        ToStderr("Error activating disks for instance %s: %s", iname, msg)
413 414 415

  if missing:
    for iname, ival in missing.iteritems():
416
      all_missing = utils.all(ival, lambda x: x[0] in bad_nodes)
417
      if all_missing:
418 419
        ToStdout("Instance %s cannot be verified as it lives on"
                 " broken nodes", iname)
420
      else:
421
        ToStdout("Instance %s has missing logical volumes:", iname)
422 423
        ival.sort()
        for node, vol in ival:
424
          if node in bad_nodes:
425
            ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
426
          else:
427 428
            ToStdout("\t%s /dev/xenvg/%s", node, vol)
    ToStdout("You need to run replace_disks for all the above"
429 430
           " instances, if this message persist after fixing nodes.")
    retcode |= 1
431 432 433 434

  return retcode


435 436 437 438 439 440 441 442 443 444 445
def RepairDiskSizes(opts, args):
  """Verify sizes of cluster disks.

  @param opts: the command line options selected by the user
  @type args: list
  @param args: optional list of instances to restrict check to
  @rtype: int
  @return: the desired exit code

  """
  op = opcodes.OpRepairDiskSizes(instances=args)
446
  SubmitOpCode(op, opts=opts)
447 448


449
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
450 451 452 453 454 455 456
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.

457 458 459 460 461 462
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should be an empty list
  @rtype: int
  @return: the desired exit code

Iustin Pop's avatar
Iustin Pop committed
463
  """
464 465 466 467 468 469 470 471 472
  if opts.no_voting:
    usertext = ("This will perform the failover even if most other nodes"
                " are down, or if this node is outdated. This is dangerous"
                " as it can lead to a non-consistent cluster. Check the"
                " gnt-cluster(8) man page before proceeding. Continue?")
    if not AskUser(usertext):
      return 1

  return bootstrap.MasterFailover(no_voting=opts.no_voting)
Iustin Pop's avatar
Iustin Pop committed
473 474


Iustin Pop's avatar
Iustin Pop committed
475 476 477
def SearchTags(opts, args):
  """Searches the tags on all the cluster.

478 479 480 481 482 483
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the tag pattern
  @rtype: int
  @return: the desired exit code

Iustin Pop's avatar
Iustin Pop committed
484 485
  """
  op = opcodes.OpSearchTags(pattern=args[0])
486
  result = SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
487 488 489 490 491
  if not result:
    return 1
  result = list(result)
  result.sort()
  for path, tag in result:
492
    ToStdout("%s %s", path, tag)
Iustin Pop's avatar
Iustin Pop committed
493 494


495
def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
496
                 new_confd_hmac_key, force):
497 498 499 500 501 502 503 504
  """Renews cluster certificates, keys and secrets.

  @type new_cluster_cert: bool
  @param new_cluster_cert: Whether to generate a new cluster certificate
  @type new_rapi_cert: bool
  @param new_rapi_cert: Whether to generate a new RAPI certificate
  @type rapi_cert_filename: string
  @param rapi_cert_filename: Path to file containing new RAPI certificate
505 506
  @type new_confd_hmac_key: bool
  @param new_confd_hmac_key: Whether to generate a new HMAC key
507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546
  @type force: bool
  @param force: Whether to ask user for confirmation

  """
  if new_rapi_cert and rapi_cert_filename:
    ToStderr("Only one of the --new-rapi-certficate and --rapi-certificate"
             " options can be specified at the same time.")
    return 1

  if rapi_cert_filename:
    # Read and verify new certificate
    try:
      rapi_cert_pem = utils.ReadFile(rapi_cert_filename)

      OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
                                      rapi_cert_pem)
    except Exception, err: # pylint: disable-msg=W0703
      ToStderr("Can't load new RAPI certificate from %s: %s" %
               (rapi_cert_filename, str(err)))
      return 1

    try:
      OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, rapi_cert_pem)
    except Exception, err: # pylint: disable-msg=W0703
      ToStderr("Can't load new RAPI private key from %s: %s" %
               (rapi_cert_filename, str(err)))
      return 1

  else:
    rapi_cert_pem = None

  if not force:
    usertext = ("This requires all daemons on all nodes to be restarted and"
                " may take some time. Continue?")
    if not AskUser(usertext):
      return 1

  def _RenewCryptoInner(ctx):
    ctx.feedback_fn("Updating certificates and keys")
    bootstrap.GenerateClusterCrypto(new_cluster_cert, new_rapi_cert,
547
                                    new_confd_hmac_key,
548 549 550 551 552
                                    rapi_cert_pem=rapi_cert_pem)

    files_to_copy = []

    if new_cluster_cert:
553
      files_to_copy.append(constants.NODED_CERT_FILE)
554 555 556 557

    if new_rapi_cert or rapi_cert_pem:
      files_to_copy.append(constants.RAPI_CERT_FILE)

558 559
    if new_confd_hmac_key:
      files_to_copy.append(constants.CONFD_HMAC_KEY)
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582

    if files_to_copy:
      for node_name in ctx.nonmaster_nodes:
        ctx.feedback_fn("Copying %s to %s" %
                        (", ".join(files_to_copy), node_name))
        for file_name in files_to_copy:
          ctx.ssh.CopyFileToNode(node_name, file_name)

  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)

  ToStdout("All requested certificates and keys have been replaced."
           " Running \"gnt-cluster verify\" now is recommended.")

  return 0


def RenewCrypto(opts, args):
  """Renews cluster certificates, keys and secrets.

  """
  return _RenewCrypto(opts.new_cluster_cert,
                      opts.new_rapi_cert,
                      opts.rapi_cert,
583
                      opts.new_confd_hmac_key,
584 585 586
                      opts.force)


587 588 589
def SetClusterParams(opts, args):
  """Modify the cluster.

590 591 592 593 594
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should be an empty list
  @rtype: int
  @return: the desired exit code
595 596

  """
597 598
  if not (not opts.lvm_storage or opts.vg_name or
          opts.enabled_hypervisors or opts.hvparams or
599 600
          opts.beparams or opts.nicparams or
          opts.candidate_pool_size is not None):
601
    ToStderr("Please give at least one of the parameters.")
602 603 604 605
    return 1

  vg_name = opts.vg_name
  if not opts.lvm_storage and opts.vg_name:
606
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
607
    return 1
608 609 610

  if not opts.lvm_storage:
    vg_name = ""
611

612 613 614 615
  hvlist = opts.enabled_hypervisors
  if hvlist is not None:
    hvlist = hvlist.split(",")

616 617
  # a list of (name, dict) we can pass directly to dict() (or [])
  hvparams = dict(opts.hvparams)
Iustin Pop's avatar
Iustin Pop committed
618
  for hv_params in hvparams.values():
619
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
620 621

  beparams = opts.beparams
622
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
623

624 625 626
  nicparams = opts.nicparams
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)

627
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
628 629
                                  enabled_hypervisors=hvlist,
                                  hvparams=hvparams,
630
                                  os_hvp=None,
631
                                  beparams=beparams,
632
                                  nicparams=nicparams,
633
                                  candidate_pool_size=opts.candidate_pool_size)
634
  SubmitOpCode(op, opts=opts)
635 636 637
  return 0


638 639 640
def QueueOps(opts, args):
  """Queue operations.

641 642 643 644 645 646
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the subcommand
  @rtype: int
  @return: the desired exit code

647 648 649 650 651 652 653 654 655
  """
  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"])
    if result[0]:
656
      val = "set"
657
    else:
658 659
      val = "unset"
    ToStdout("The drain flag is %s" % val)
660
  else:
661 662
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
                               errors.ECODE_INVAL)
663

664 665
  return 0

666

667 668 669 670 671 672 673
def _ShowWatcherPause(until):
  if until is None or until < time.time():
    ToStdout("The watcher is not paused.")
  else:
    ToStdout("The watcher is paused until %s.", time.ctime(until))


674 675 676 677 678 679 680 681 682 683 684 685 686 687 688
def WatcherOps(opts, args):
  """Watcher operations.

  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the subcommand
  @rtype: int
  @return: the desired exit code

  """
  command = args[0]
  client = GetClient()

  if command == "continue":
    client.SetWatcherPause(None)
689
    ToStdout("The watcher is no longer paused.")
690 691 692

  elif command == "pause":
    if len(args) < 2:
693
      raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
694

695 696
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
    _ShowWatcherPause(result)
697 698 699

  elif command == "info":
    result = client.QueryConfigValues(["watcher_pause"])
700
    _ShowWatcherPause(result[0])
701 702

  else:
703 704
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
                               errors.ECODE_INVAL)
705 706 707 708

  return 0


Iustin Pop's avatar
Iustin Pop committed
709
commands = {
710 711
  'init': (
    InitCluster, [ArgHost(min=1, max=1)],
712
    [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, GLOBAL_FILEDIR_OPT,
713
     HVLIST_OPT, MAC_PREFIX_OPT, MASTER_NETDEV_OPT, NIC_PARAMS_OPT,
714 715
     NOLVM_STORAGE_OPT, NOMODIFY_ETCHOSTS_OPT, NOMODIFY_SSH_SETUP_OPT,
     SECONDARY_IP_OPT, VG_NAME_OPT],
716 717
    "[opts...] <cluster_name>", "Initialises a new cluster configuration"),
  'destroy': (
718
    DestroyCluster, ARGS_NONE, [YES_DOIT_OPT],
719 720 721
    "", "Destroy cluster"),
  'rename': (
    RenameCluster, [ArgHost(min=1, max=1)],
722
    [FORCE_OPT],
723 724 725
    "<new_name>",
    "Renames the cluster"),
  'redist-conf': (
726
    RedistributeConfig, ARGS_NONE, [SUBMIT_OPT],
727 728 729 730
    "", "Forces a push of the configuration file and ssconf files"
    " to the nodes in the cluster"),
  'verify': (
    VerifyCluster, ARGS_NONE,
731
    [VERBOSE_OPT, DEBUG_SIMERR_OPT, ERROR_CODES_OPT, NONPLUS1_OPT],
732 733
    "", "Does a check on the cluster configuration"),
  'verify-disks': (
734
    VerifyDisks, ARGS_NONE, [],
735 736
    "", "Does a check on the cluster disk status"),
  'repair-disk-sizes': (
737
    RepairDiskSizes, ARGS_MANY_INSTANCES, [],
738 739
    "", "Updates mismatches in recorded disk sizes"),
  'masterfailover': (
740
    MasterFailover, ARGS_NONE, [NOVOTING_OPT],
741 742
    "", "Makes the current node the master"),
  'version': (
743
    ShowClusterVersion, ARGS_NONE, [],
744 745
    "", "Shows the cluster version"),
  'getmaster': (
746
    ShowClusterMaster, ARGS_NONE, [],
747 748 749
    "", "Shows the cluster master"),
  'copyfile': (
    ClusterCopyFile, [ArgFile(min=1, max=1)],
750
    [NODE_LIST_OPT, USE_REPL_NET_OPT],
751 752 753
    "[-n node...] <filename>", "Copies a file to all (or only some) nodes"),
  'command': (
    RunClusterCommand, [ArgCommand(min=1)],
754
    [NODE_LIST_OPT],
755 756
    "[-n node...] <command>", "Runs a command on all (or only some) nodes"),
  'info': (
757
    ShowClusterConfig, ARGS_NONE, [],
758 759
    "", "Show cluster configuration"),
  'list-tags': (
760
    ListTags, ARGS_NONE, [], "", "List the tags of the cluster"),
761
  'add-tags': (
762
    AddTags, [ArgUnknown()], [TAG_SRC_OPT],
763 764
    "tag...", "Add tags to the cluster"),
  'remove-tags': (
765
    RemoveTags, [ArgUnknown()], [TAG_SRC_OPT],
766 767 768
    "tag...", "Remove tags from the cluster"),
  'search-tags': (
    SearchTags, [ArgUnknown(min=1, max=1)],
769
    [], "", "Searches the tags on all objects on"
770 771 772 773
    " the cluster for a given pattern (regex)"),
  'queue': (
    QueueOps,
    [ArgChoice(min=1, max=1, choices=["drain", "undrain", "info"])],
774
    [], "drain|undrain|info", "Change queue properties"),
775 776 777 778
  'watcher': (
    WatcherOps,
    [ArgChoice(min=1, max=1, choices=["pause", "continue", "info"]),
     ArgSuggest(min=0, max=1, choices=["30m", "1h", "4h"])],
779
    [],
780 781 782
    "{pause <timespec>|continue|info}", "Change watcher properties"),
  'modify': (
    SetClusterParams, ARGS_NONE,
783
    [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, HVLIST_OPT,
784 785 786
     NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, VG_NAME_OPT],
    "[opts...]",
    "Alters the parameters of the cluster"),
787 788
  "renew-crypto": (
    RenewCrypto, ARGS_NONE,
789 790
    [NEW_CLUSTER_CERT_OPT, NEW_RAPI_CERT_OPT, RAPI_CERT_OPT,
     NEW_CONFD_HMAC_KEY_OPT, FORCE_OPT],
791 792
    "[opts...]",
    "Renews cluster certificates, keys and secrets"),
Iustin Pop's avatar
Iustin Pop committed
793 794
  }

795

Iustin Pop's avatar
Iustin Pop committed
796
if __name__ == '__main__':
797
  sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))