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


22
23
24
25
# pylint: disable-msg=W0401,W0614
# W0401: Wildcard import ganeti.cli
# W0614: Unused import %s from wildcard import (since we need cli)

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

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


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

44
45
46
47
48
49
  @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
50
51

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

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

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

65
  hvparams = dict(opts.hvparams)
66
  beparams = opts.beparams
67
  nicparams = opts.nicparams
68
69

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

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

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

84
85
86
  if opts.candidate_pool_size is None:
    opts.candidate_pool_size = constants.MASTER_POOL_SIZE_DEFAULT

87
88
89
  if opts.mac_prefix is None:
    opts.mac_prefix = constants.DEFAULT_MAC_PREFIX

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


108
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
109
110
111
def DestroyCluster(opts, args):
  """Destroy the cluster.

112
113
114
115
116
  @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
117

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

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


132
133
134
def RenameCluster(opts, args):
  """Rename the cluster.

135
136
137
138
139
  @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
140
141
142
143
144
145
146
147

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

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


156
157
158
159
160
161
162
163
164
165
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

  """
166
  op = opcodes.OpRedistributeConfig()
167
168
169
170
  SubmitOrSend(op, opts)
  return 0


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

174
175
176
177
178
  @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
179
180

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


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

194
195
196
197
198
  @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
199
200

  """
201
202
  master = bootstrap.GetMaster()
  ToStdout(master)
Iustin Pop's avatar
Iustin Pop committed
203
204
  return 0

205
206
207
208
209
210
211
212
213
214
215
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
216
217
218
219

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

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

230
  ToStdout("Cluster name: %s", result["name"])
231
  ToStdout("Cluster UUID: %s", result["uuid"])
Iustin Pop's avatar
Iustin Pop committed
232

233
234
235
  ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
  ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))

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

238
239
  ToStdout("Architecture (this node): %s (%s)",
           result["architecture"][0], result["architecture"][1])
Iustin Pop's avatar
Iustin Pop committed
240

241
242
243
244
245
246
247
  if result["tags"]:
    tags = ", ".join(utils.NiceSort(result["tags"]))
  else:
    tags = "(none)"

  ToStdout("Tags: %s", tags)

248
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
249
  ToStdout("Enabled hypervisors: %s", ", ".join(result["enabled_hypervisors"]))
250

251
  ToStdout("Hypervisor parameters:")
252
  _PrintGroupedParams(result["hvparams"])
253

254
  ToStdout("Cluster parameters:")
255
  ToStdout("  - candidate pool size: %s", result["candidate_pool_size"])
256
257
258
  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"])
259
260

  ToStdout("Default instance parameters:")
261
262
263
264
  _PrintGroupedParams(result["beparams"])

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

Iustin Pop's avatar
Iustin Pop committed
266
267
268
269
270
271
  return 0


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

272
273
274
275
276
277
  @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
278
279

  """
280
281
282
283
  filename = args[0]
  if not os.path.exists(filename):
    raise errors.OpPrereqError("No such filename '%s'" % filename)

Iustin Pop's avatar
Iustin Pop committed
284
285
  cl = GetClient()

286
287
  myname = utils.HostInfo().name

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

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

Iustin Pop's avatar
Iustin Pop committed
293
  srun = ssh.SshRunner(cluster_name=cluster_name)
294
295
  for node in results:
    if not srun.CopyFileToNode(node, filename):
296
      ToStderr("Copy of file %s to node %s failed", filename, node)
297

Iustin Pop's avatar
Iustin Pop committed
298
299
300
301
302
303
  return 0


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

304
305
306
307
308
  @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
309
310

  """
Iustin Pop's avatar
Iustin Pop committed
311
  cl = GetClient()
Michael Hanselmann's avatar
Michael Hanselmann committed
312

Iustin Pop's avatar
Iustin Pop committed
313
  command = " ".join(args)
314
315

  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
Iustin Pop's avatar
Iustin Pop committed
316
317
318

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

Iustin Pop's avatar
Iustin Pop committed
320
  srun = ssh.SshRunner(cluster_name=cluster_name)
321

Michael Hanselmann's avatar
Michael Hanselmann committed
322
  # Make sure master node is at list end
323
324
325
326
327
328
  if master_node in nodes:
    nodes.remove(master_node)
    nodes.append(master_node)

  for name in nodes:
    result = srun.Run(name, "root", command)
329
330
331
332
    ToStdout("------------------------------------------------")
    ToStdout("node: %s", name)
    ToStdout("%s", result.output)
    ToStdout("return code = %s", result.exit_code)
333
334

  return 0
Iustin Pop's avatar
Iustin Pop committed
335
336
337
338
339


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

340
341
342
343
344
  @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
345
346

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


360
361
362
def VerifyDisks(opts, args):
  """Verify integrity of cluster disks.

363
364
365
366
367
  @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
368
369
370
371

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

375
  bad_nodes, instances, missing = result
376

377
  retcode = constants.EXIT_SUCCESS
378

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

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

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

  return retcode


420
421
422
423
424
425
426
427
428
429
430
431
432
433
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)


434
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
435
436
437
438
439
440
441
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.

442
443
444
445
446
447
  @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
448
  """
449
450
451
452
453
454
455
456
457
  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
458
459


Iustin Pop's avatar
Iustin Pop committed
460
461
462
def SearchTags(opts, args):
  """Searches the tags on all the cluster.

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


480
481
482
def SetClusterParams(opts, args):
  """Modify the cluster.

483
484
485
486
487
  @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
488
489

  """
490
491
  if not (not opts.lvm_storage or opts.vg_name or
          opts.enabled_hypervisors or opts.hvparams or
492
493
          opts.beparams or opts.nicparams or
          opts.candidate_pool_size is not None):
494
    ToStderr("Please give at least one of the parameters.")
495
496
497
498
    return 1

  vg_name = opts.vg_name
  if not opts.lvm_storage and opts.vg_name:
499
    ToStdout("Options --no-lvm-storage and --vg-name conflict.")
500
    return 1
501
502
  elif not opts.lvm_storage:
    vg_name = ''
503

504
505
506
507
  hvlist = opts.enabled_hypervisors
  if hvlist is not None:
    hvlist = hvlist.split(",")

508
509
  # a list of (name, dict) we can pass directly to dict() (or [])
  hvparams = dict(opts.hvparams)
510
511
  for hv, hv_params in hvparams.iteritems():
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
512
513

  beparams = opts.beparams
514
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
515

516
517
518
  nicparams = opts.nicparams
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)

519
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
520
521
                                  enabled_hypervisors=hvlist,
                                  hvparams=hvparams,
522
                                  beparams=beparams,
523
                                  nicparams=nicparams,
524
                                  candidate_pool_size=opts.candidate_pool_size)
525
526
527
528
  SubmitOpCode(op)
  return 0


529
530
531
def QueueOps(opts, args):
  """Queue operations.

532
533
534
535
536
537
  @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

538
539
540
541
542
543
544
545
546
  """
  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]:
547
      val = "set"
548
    else:
549
550
      val = "unset"
    ToStdout("The drain flag is %s" % val)
551
552
553
  else:
    raise errors.OpPrereqError("Command '%s' is not valid." % command)

554
555
  return 0

556

557
558
559
560
561
562
563
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))


564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
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)
579
    ToStdout("The watcher is no longer paused.")
580
581
582
583
584

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

585
586
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
    _ShowWatcherPause(result)
587
588
589

  elif command == "info":
    result = client.QueryConfigValues(["watcher_pause"])
590
    _ShowWatcherPause(result)
591
592
593
594
595
596
597

  else:
    raise errors.OpPrereqError("Command '%s' is not valid." % command)

  return 0


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

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