gnt-cluster 19.9 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
                        modify_ssh_setup=opts.modify_ssh_setup,
103
                        )
104
105
  op = opcodes.OpPostInitCluster()
  SubmitOpCode(op)
Iustin Pop's avatar
Iustin Pop committed
106
107
108
  return 0


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

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

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

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


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

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

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

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


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

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


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

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

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


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

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

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

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

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

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

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

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

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

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

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

  ToStdout("Tags: %s", tags)

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

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

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

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

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

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


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

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

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

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

287
288
  myname = utils.HostInfo().name

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

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

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

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


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

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

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

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

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

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

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

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

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

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


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

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

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


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

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

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

376
  bad_nodes, instances, missing = result
377

378
  retcode = constants.EXIT_SUCCESS
379

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

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

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

  return retcode


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


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

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


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

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


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

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

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

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

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

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

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

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

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


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

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

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

555
556
  return 0

557

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


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

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

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

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

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

  return 0


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

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