gnt-cluster 20.4 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
211
212
213
214
215
216
217
218
219
220
def _PrintGroupedParams(paramsdict):
  """Print Grouped parameters (be, nic, disk) by group.

  @type paramsdict: dict of dicts
  @param paramsdict: {group: {param: value, ...}, ...}

  """
  for gr_name, gr_dict in paramsdict.items():
    ToStdout("  - %s:", gr_name)
    for item, val in gr_dict.iteritems():
      ToStdout("      %s: %s", item, val)
Iustin Pop's avatar
Iustin Pop committed
221

222

Iustin Pop's avatar
Iustin Pop committed
223
224
225
def ShowClusterConfig(opts, args):
  """Shows cluster information.

226
227
228
229
230
231
  @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
232
  """
233
234
  cl = GetClient()
  result = cl.QueryClusterInfo()
Iustin Pop's avatar
Iustin Pop committed
235

236
  ToStdout("Cluster name: %s", result["name"])
237
  ToStdout("Cluster UUID: %s", result["uuid"])
Iustin Pop's avatar
Iustin Pop committed
238

239
240
241
  ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
  ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))

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

244
245
  ToStdout("Architecture (this node): %s (%s)",
           result["architecture"][0], result["architecture"][1])
Iustin Pop's avatar
Iustin Pop committed
246

247
  if result["tags"]:
248
    tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
249
250
251
252
253
  else:
    tags = "(none)"

  ToStdout("Tags: %s", tags)

254
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
255
256
  ToStdout("Enabled hypervisors: %s",
           utils.CommaJoin(result["enabled_hypervisors"]))
257

258
  ToStdout("Hypervisor parameters:")
259
  _PrintGroupedParams(result["hvparams"])
260

261
  ToStdout("Cluster parameters:")
262
  ToStdout("  - candidate pool size: %s", result["candidate_pool_size"])
263
264
265
  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"])
266
267

  ToStdout("Default instance parameters:")
268
269
270
271
  _PrintGroupedParams(result["beparams"])

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

Iustin Pop's avatar
Iustin Pop committed
273
274
275
276
277
278
  return 0


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

279
280
281
282
283
284
  @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
285
286

  """
287
288
  filename = args[0]
  if not os.path.exists(filename):
289
290
    raise errors.OpPrereqError("No such filename '%s'" % filename,
                               errors.ECODE_INVAL)
291

Iustin Pop's avatar
Iustin Pop committed
292
293
  cl = GetClient()

294
  myname = utils.GetHostInfo().name
295

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

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

Iustin Pop's avatar
Iustin Pop committed
301
  srun = ssh.SshRunner(cluster_name=cluster_name)
302
303
  for node in results:
    if not srun.CopyFileToNode(node, filename):
304
      ToStderr("Copy of file %s to node %s failed", filename, node)
305

Iustin Pop's avatar
Iustin Pop committed
306
307
308
309
310
311
  return 0


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

312
313
314
315
316
  @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
317
318

  """
Iustin Pop's avatar
Iustin Pop committed
319
  cl = GetClient()
Michael Hanselmann's avatar
Michael Hanselmann committed
320

Iustin Pop's avatar
Iustin Pop committed
321
  command = " ".join(args)
322
323

  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
Iustin Pop's avatar
Iustin Pop committed
324
325
326

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

Iustin Pop's avatar
Iustin Pop committed
328
  srun = ssh.SshRunner(cluster_name=cluster_name)
329

Michael Hanselmann's avatar
Michael Hanselmann committed
330
  # Make sure master node is at list end
331
332
333
334
335
336
  if master_node in nodes:
    nodes.remove(master_node)
    nodes.append(master_node)

  for name in nodes:
    result = srun.Run(name, "root", command)
337
338
339
340
    ToStdout("------------------------------------------------")
    ToStdout("node: %s", name)
    ToStdout("%s", result.output)
    ToStdout("return code = %s", result.exit_code)
341
342

  return 0
Iustin Pop's avatar
Iustin Pop committed
343
344
345
346
347


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

348
349
350
351
352
  @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
353
354

  """
Iustin Pop's avatar
Iustin Pop committed
355
  skip_checks = []
356
357
  if opts.skip_nplusone_mem:
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
358
359
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
                               verbose=opts.verbose,
360
361
                               error_codes=opts.error_codes,
                               debug_simulate_errors=opts.simulate_errors)
362
  if SubmitOpCode(op, opts=opts):
363
364
365
    return 0
  else:
    return 1
Iustin Pop's avatar
Iustin Pop committed
366
367


368
369
370
def VerifyDisks(opts, args):
  """Verify integrity of cluster disks.

371
372
373
374
375
  @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
376
377
378

  """
  op = opcodes.OpVerifyDisks()
379
  result = SubmitOpCode(op, opts=opts)
380
  if not isinstance(result, (list, tuple)) or len(result) != 3:
381
382
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")

383
  bad_nodes, instances, missing = result
384

385
  retcode = constants.EXIT_SUCCESS
386

387
388
389
  if bad_nodes:
    for node, text in bad_nodes.items():
      ToStdout("Error gathering data on node %s: %s",
390
               node, utils.SafeEncode(text[-400:]))
391
      retcode |= 1
392
      ToStdout("You need to fix these nodes first before fixing instances")
393

394
395
  if instances:
    for iname in instances:
396
397
      if iname in missing:
        continue
398
399
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
      try:
400
        ToStdout("Activating disks for instance '%s'", iname)
401
        SubmitOpCode(op, opts=opts)
402
403
404
      except errors.GenericError, err:
        nret, msg = FormatError(err)
        retcode |= nret
405
        ToStderr("Error activating disks for instance %s: %s", iname, msg)
406
407
408

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

  return retcode


428
429
430
431
432
433
434
435
436
437
438
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)
439
  SubmitOpCode(op, opts=opts)
440
441


442
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
443
444
445
446
447
448
449
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.

450
451
452
453
454
455
  @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
456
  """
457
458
459
460
461
462
463
464
465
  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
466
467


Iustin Pop's avatar
Iustin Pop committed
468
469
470
def SearchTags(opts, args):
  """Searches the tags on all the cluster.

471
472
473
474
475
476
  @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
477
478
  """
  op = opcodes.OpSearchTags(pattern=args[0])
479
  result = SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
480
481
482
483
484
  if not result:
    return 1
  result = list(result)
  result.sort()
  for path, tag in result:
485
    ToStdout("%s %s", path, tag)
Iustin Pop's avatar
Iustin Pop committed
486
487


488
489
490
def SetClusterParams(opts, args):
  """Modify the cluster.

491
492
493
494
495
  @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
496
497

  """
498
499
  if not (not opts.lvm_storage or opts.vg_name or
          opts.enabled_hypervisors or opts.hvparams or
500
501
          opts.beparams or opts.nicparams or
          opts.candidate_pool_size is not None):
502
    ToStderr("Please give at least one of the parameters.")
503
504
505
506
    return 1

  vg_name = opts.vg_name
  if not opts.lvm_storage and opts.vg_name:
507
    ToStdout("Options --no-lvm-storage and --vg-name conflict.")
508
    return 1
509
510
  elif not opts.lvm_storage:
    vg_name = ''
511

512
513
514
515
  hvlist = opts.enabled_hypervisors
  if hvlist is not None:
    hvlist = hvlist.split(",")

516
517
  # a list of (name, dict) we can pass directly to dict() (or [])
  hvparams = dict(opts.hvparams)
Iustin Pop's avatar
Iustin Pop committed
518
  for hv_params in hvparams.values():
519
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
520
521

  beparams = opts.beparams
522
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
523

524
525
526
  nicparams = opts.nicparams
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)

527
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
528
529
                                  enabled_hypervisors=hvlist,
                                  hvparams=hvparams,
530
                                  os_hvp=None,
531
                                  beparams=beparams,
532
                                  nicparams=nicparams,
533
                                  candidate_pool_size=opts.candidate_pool_size)
534
  SubmitOpCode(op, opts=opts)
535
536
537
  return 0


538
539
540
def QueueOps(opts, args):
  """Queue operations.

541
542
543
544
545
546
  @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

547
548
549
550
551
552
553
554
555
  """
  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]:
556
      val = "set"
557
    else:
558
559
      val = "unset"
    ToStdout("The drain flag is %s" % val)
560
  else:
561
562
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
                               errors.ECODE_INVAL)
563

564
565
  return 0

566

567
568
569
570
571
572
573
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))


574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
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)
589
    ToStdout("The watcher is no longer paused.")
590
591
592

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

595
596
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
    _ShowWatcherPause(result)
597
598
599

  elif command == "info":
    result = client.QueryConfigValues(["watcher_pause"])
600
    _ShowWatcherPause(result[0])
601
602

  else:
603
604
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
                               errors.ECODE_INVAL)
605
606
607
608

  return 0


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

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