gnt-cluster 20.6 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
Iustin Pop's avatar
Iustin Pop committed
32
33
34

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


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

47
48
49
50
51
52
  @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
53
54

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

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

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

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

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

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

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

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

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

93
94
95
96
97
  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,
98
99
100
                        file_storage_dir=opts.file_storage_dir,
                        enabled_hypervisors=hvlist,
                        hvparams=hvparams,
101
                        beparams=beparams,
102
                        nicparams=nicparams,
103
                        candidate_pool_size=opts.candidate_pool_size,
104
                        modify_etc_hosts=opts.modify_etc_hosts,
105
                        modify_ssh_setup=opts.modify_ssh_setup,
106
                        )
107
  op = opcodes.OpPostInitCluster()
108
  SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
109
110
111
  return 0


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

116
117
118
119
120
  @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
121

Iustin Pop's avatar
Iustin Pop committed
122
123
  """
  if not opts.yes_do_it:
124
125
    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
126
127
128
    return 1

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


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

139
140
141
142
143
  @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
144
145
146
147
148
149
150
151

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

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


160
161
162
163
164
165
166
167
168
169
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

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


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

178
179
180
181
182
  @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
183
184

  """
185
186
  cl = GetClient()
  result = cl.QueryClusterInfo()
187
188
189
190
191
  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
192
193
194
195
196
197
  return 0


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

198
199
200
201
202
  @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
203
204

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

209

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

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

  """
219
220
221
222
223
224
225
  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
226

227

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

231
232
233
234
235
236
  @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
237
  """
238
239
  cl = GetClient()
  result = cl.QueryClusterInfo()
Iustin Pop's avatar
Iustin Pop committed
240

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

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

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

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

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

  ToStdout("Tags: %s", tags)

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

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

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

269
  ToStdout("Cluster parameters:")
270
  ToStdout("  - candidate pool size: %s", result["candidate_pool_size"])
271
272
273
  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"])
274
275

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

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

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


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

287
288
289
290
291
292
  @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
293
294

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

Iustin Pop's avatar
Iustin Pop committed
300
301
  cl = GetClient()

302
  myname = utils.GetHostInfo().name
303

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

306
307
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl)
  results = [name for name in results if name != myname]
Michael Hanselmann's avatar
Michael Hanselmann committed
308

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

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


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

320
321
322
323
324
  @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
325
326

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

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

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

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

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

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

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

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


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

356
357
358
359
360
  @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
361
362

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


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

379
380
381
382
383
  @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
384
385
386

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

391
  bad_nodes, instances, missing = result
392

393
  retcode = constants.EXIT_SUCCESS
394

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

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

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

  return retcode


436
437
438
439
440
441
442
443
444
445
446
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)
447
  SubmitOpCode(op, opts=opts)
448
449


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

458
459
460
461
462
463
  @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
464
  """
465
466
467
468
469
470
471
472
473
  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
474
475


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

479
480
481
482
483
484
  @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
485
486
  """
  op = opcodes.OpSearchTags(pattern=args[0])
487
  result = SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
488
489
490
491
492
  if not result:
    return 1
  result = list(result)
  result.sort()
  for path, tag in result:
493
    ToStdout("%s %s", path, tag)
Iustin Pop's avatar
Iustin Pop committed
494
495


496
497
498
def SetClusterParams(opts, args):
  """Modify the cluster.

499
500
501
502
503
  @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
504
505

  """
506
507
  if not (not opts.lvm_storage or opts.vg_name or
          opts.enabled_hypervisors or opts.hvparams or
508
509
          opts.beparams or opts.nicparams or
          opts.candidate_pool_size is not None):
510
    ToStderr("Please give at least one of the parameters.")
511
512
513
514
    return 1

  vg_name = opts.vg_name
  if not opts.lvm_storage and opts.vg_name:
515
    ToStdout("Options --no-lvm-storage and --vg-name conflict.")
516
    return 1
517
518
  elif not opts.lvm_storage:
    vg_name = ''
519

520
521
522
523
  hvlist = opts.enabled_hypervisors
  if hvlist is not None:
    hvlist = hvlist.split(",")

524
525
  # a list of (name, dict) we can pass directly to dict() (or [])
  hvparams = dict(opts.hvparams)
Iustin Pop's avatar
Iustin Pop committed
526
  for hv_params in hvparams.values():
527
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
528
529

  beparams = opts.beparams
530
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
531

532
533
534
  nicparams = opts.nicparams
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)

535
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
536
537
                                  enabled_hypervisors=hvlist,
                                  hvparams=hvparams,
538
                                  os_hvp=None,
539
                                  beparams=beparams,
540
                                  nicparams=nicparams,
541
                                  candidate_pool_size=opts.candidate_pool_size)
542
  SubmitOpCode(op, opts=opts)
543
544
545
  return 0


546
547
548
def QueueOps(opts, args):
  """Queue operations.

549
550
551
552
553
554
  @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

555
556
557
558
559
560
561
562
563
  """
  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]:
564
      val = "set"
565
    else:
566
567
      val = "unset"
    ToStdout("The drain flag is %s" % val)
568
  else:
569
570
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
                               errors.ECODE_INVAL)
571

572
573
  return 0

574

575
576
577
578
579
580
581
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))


582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
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)
597
    ToStdout("The watcher is no longer paused.")
598
599
600

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

603
604
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
    _ShowWatcherPause(result)
605
606
607

  elif command == "info":
    result = client.QueryConfigValues(["watcher_pause"])
608
    _ShowWatcherPause(result[0])
609
610

  else:
611
612
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
                               errors.ECODE_INVAL)
613
614
615
616

  return 0


Iustin Pop's avatar
Iustin Pop committed
617
commands = {
618
619
  'init': (
    InitCluster, [ArgHost(min=1, max=1)],
620
    [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, GLOBAL_FILEDIR_OPT,
621
     HVLIST_OPT, MAC_PREFIX_OPT, MASTER_NETDEV_OPT, NIC_PARAMS_OPT,
622
623
     NOLVM_STORAGE_OPT, NOMODIFY_ETCHOSTS_OPT, NOMODIFY_SSH_SETUP_OPT,
     SECONDARY_IP_OPT, VG_NAME_OPT],
624
625
    "[opts...] <cluster_name>", "Initialises a new cluster configuration"),
  'destroy': (
626
    DestroyCluster, ARGS_NONE, [YES_DOIT_OPT],
627
628
629
    "", "Destroy cluster"),
  'rename': (
    RenameCluster, [ArgHost(min=1, max=1)],
630
    [FORCE_OPT],
631
632
633
    "<new_name>",
    "Renames the cluster"),
  'redist-conf': (
634
    RedistributeConfig, ARGS_NONE, [SUBMIT_OPT],
635
636
637
638
    "", "Forces a push of the configuration file and ssconf files"
    " to the nodes in the cluster"),
  'verify': (
    VerifyCluster, ARGS_NONE,
639
    [VERBOSE_OPT, DEBUG_SIMERR_OPT, ERROR_CODES_OPT, NONPLUS1_OPT],
640
641
    "", "Does a check on the cluster configuration"),
  'verify-disks': (
642
    VerifyDisks, ARGS_NONE, [],
643
644
    "", "Does a check on the cluster disk status"),
  'repair-disk-sizes': (
645
    RepairDiskSizes, ARGS_MANY_INSTANCES, [],
646
647
    "", "Updates mismatches in recorded disk sizes"),
  'masterfailover': (
648
    MasterFailover, ARGS_NONE, [NOVOTING_OPT],
649
650
    "", "Makes the current node the master"),
  'version': (
651
    ShowClusterVersion, ARGS_NONE, [],
652
653
    "", "Shows the cluster version"),
  'getmaster': (
654
    ShowClusterMaster, ARGS_NONE, [],
655
656
657
    "", "Shows the cluster master"),
  'copyfile': (
    ClusterCopyFile, [ArgFile(min=1, max=1)],
658
    [NODE_LIST_OPT],
659
660
661
    "[-n node...] <filename>", "Copies a file to all (or only some) nodes"),
  'command': (
    RunClusterCommand, [ArgCommand(min=1)],
662
    [NODE_LIST_OPT],
663
664
    "[-n node...] <command>", "Runs a command on all (or only some) nodes"),
  'info': (
665
    ShowClusterConfig, ARGS_NONE, [],
666
667
    "", "Show cluster configuration"),
  'list-tags': (
668
    ListTags, ARGS_NONE, [], "", "List the tags of the cluster"),
669
  'add-tags': (
670
    AddTags, [ArgUnknown()], [TAG_SRC_OPT],
671
672
    "tag...", "Add tags to the cluster"),
  'remove-tags': (
673
    RemoveTags, [ArgUnknown()], [TAG_SRC_OPT],
674
675
676
    "tag...", "Remove tags from the cluster"),
  'search-tags': (
    SearchTags, [ArgUnknown(min=1, max=1)],
677
    [], "", "Searches the tags on all objects on"
678
679
680
681
    " the cluster for a given pattern (regex)"),
  'queue': (
    QueueOps,
    [ArgChoice(min=1, max=1, choices=["drain", "undrain", "info"])],
682
    [], "drain|undrain|info", "Change queue properties"),
683
684
685
686
  'watcher': (
    WatcherOps,
    [ArgChoice(min=1, max=1, choices=["pause", "continue", "info"]),
     ArgSuggest(min=0, max=1, choices=["30m", "1h", "4h"])],
687
    [],
688
689
690
    "{pause <timespec>|continue|info}", "Change watcher properties"),
  'modify': (
    SetClusterParams, ARGS_NONE,
691
    [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, HVLIST_OPT,
692
693
694
     NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, VG_NAME_OPT],
    "[opts...]",
    "Alters the parameters of the cluster"),
Iustin Pop's avatar
Iustin Pop committed
695
696
697
  }

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