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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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


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

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

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

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


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

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


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

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

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


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

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

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

208
209
210
211
212
213
214
215
216
217
218
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
219
220
221
222

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

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

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

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

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

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

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

  ToStdout("Tags: %s", tags)

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

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

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

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

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

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


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

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

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

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

291
  myname = utils.GetHostInfo().name
292

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

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

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

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


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

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

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

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

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

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

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

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

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

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


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

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

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


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

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

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

380
  bad_nodes, instances, missing = result
381

382
  retcode = constants.EXIT_SUCCESS
383

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

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

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

  return retcode


425
426
427
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)
  SubmitOpCode(op)


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

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


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

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


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

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

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

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

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

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

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

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

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


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

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

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

560
561
  return 0

562

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


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

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

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

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

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

  return 0


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

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