gnt-cluster 20.1 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
  if result["tags"]:
243
    tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
244
245
246
247
248
  else:
    tags = "(none)"

  ToStdout("Tags: %s", tags)

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

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

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

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

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

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


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

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

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

Iustin Pop's avatar
Iustin Pop committed
287
288
  cl = GetClient()

289
  myname = utils.GetHostInfo().name
290

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

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

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

Iustin Pop's avatar
Iustin Pop committed
301
302
303
304
305
306
  return 0


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

307
308
309
310
311
  @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
312
313

  """
Iustin Pop's avatar
Iustin Pop committed
314
  cl = GetClient()
Michael Hanselmann's avatar
Michael Hanselmann committed
315

Iustin Pop's avatar
Iustin Pop committed
316
  command = " ".join(args)
317
318

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

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

Iustin Pop's avatar
Iustin Pop committed
323
  srun = ssh.SshRunner(cluster_name=cluster_name)
324

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

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

  return 0
Iustin Pop's avatar
Iustin Pop committed
338
339
340
341
342


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

343
344
345
346
347
  @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
348
349

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


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

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

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

378
  bad_nodes, instances, missing = result
379

380
  retcode = constants.EXIT_SUCCESS
381

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

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

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

  return retcode


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


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

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


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

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


483
484
485
def SetClusterParams(opts, args):
  """Modify the cluster.

486
487
488
489
490
  @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
491
492

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

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

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

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

  beparams = opts.beparams
517
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
518

519
520
521
  nicparams = opts.nicparams
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)

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


532
533
534
def QueueOps(opts, args):
  """Queue operations.

535
536
537
538
539
540
  @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

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

558
559
  return 0

560

561
562
563
564
565
566
567
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))


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

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

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

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

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

  return 0


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

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