gnt-cluster 20.2 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
108
  op = opcodes.OpPostInitCluster()
  SubmitOpCode(op)
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()
Iustin Pop's avatar
Iustin Pop committed
129
130
131
132
  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
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
156
157
158
159
      return 1

  op = opcodes.OpRenameCluster(name=name)
  SubmitOpCode(op)
  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
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
220
221
222
223

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

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

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

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

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

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

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

  ToStdout("Tags: %s", tags)

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

256
  ToStdout("Hypervisor parameters:")
257
  _PrintGroupedParams(result["hvparams"])
258

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

  ToStdout("Default instance parameters:")
266
267
268
269
  _PrintGroupedParams(result["beparams"])

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

Iustin Pop's avatar
Iustin Pop committed
271
272
273
274
275
276
  return 0


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

277
278
279
280
281
282
  @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
283
284

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

Iustin Pop's avatar
Iustin Pop committed
290
291
  cl = GetClient()

292
  myname = utils.GetHostInfo().name
293

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

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

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

Iustin Pop's avatar
Iustin Pop committed
304
305
306
307
308
309
  return 0


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

310
311
312
313
314
  @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
315
316

  """
Iustin Pop's avatar
Iustin Pop committed
317
  cl = GetClient()
Michael Hanselmann's avatar
Michael Hanselmann committed
318

Iustin Pop's avatar
Iustin Pop committed
319
  command = " ".join(args)
320
321

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

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

Iustin Pop's avatar
Iustin Pop committed
326
  srun = ssh.SshRunner(cluster_name=cluster_name)
327

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

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

  return 0
Iustin Pop's avatar
Iustin Pop committed
341
342
343
344
345


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

346
347
348
349
350
  @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
351
352

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


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

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

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

381
  bad_nodes, instances, missing = result
382

383
  retcode = constants.EXIT_SUCCESS
384

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

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

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

  return retcode


426
427
428
429
430
431
432
433
434
435
436
437
438
439
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)
  SubmitOpCode(op)


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

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


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

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


486
487
488
def SetClusterParams(opts, args):
  """Modify the cluster.

489
490
491
492
493
  @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
494
495

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

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

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

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

  beparams = opts.beparams
520
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
521

522
523
524
  nicparams = opts.nicparams
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)

525
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
526
527
                                  enabled_hypervisors=hvlist,
                                  hvparams=hvparams,
528
                                  beparams=beparams,
529
                                  nicparams=nicparams,
530
                                  candidate_pool_size=opts.candidate_pool_size)
531
532
533
534
  SubmitOpCode(op)
  return 0


535
536
537
def QueueOps(opts, args):
  """Queue operations.

538
539
540
541
542
543
  @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

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

561
562
  return 0

563

564
565
566
567
568
569
570
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))


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

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

592
593
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
    _ShowWatcherPause(result)
594
595
596

  elif command == "info":
    result = client.QueryConfigValues(["watcher_pause"])
597
    _ShowWatcherPause(result)
598
599

  else:
600
601
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
                               errors.ECODE_INVAL)
602
603
604
605

  return 0


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

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