Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ d512e84b

History | View | Annotate | Download (23.8 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2006, 2007 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21
"""Cluster related commands"""
22

    
23
# pylint: disable-msg=W0401,W0613,W0614,C0103
24
# W0401: Wildcard import ganeti.cli
25
# W0613: Unused argument, since all functions follow the same API
26
# W0614: Unused import %s from wildcard import (since we need cli)
27
# C0103: Invalid name gnt-cluster
28

    
29
import sys
30
import os.path
31
import time
32
import OpenSSL
33

    
34
from ganeti.cli import *
35
from ganeti import opcodes
36
from ganeti import constants
37
from ganeti import errors
38
from ganeti import utils
39
from ganeti import bootstrap
40
from ganeti import ssh
41
from ganeti import objects
42

    
43

    
44
@UsesRPC
45
def InitCluster(opts, args):
46
  """Initialize the cluster.
47

    
48
  @param opts: the command line options selected by the user
49
  @type args: list
50
  @param args: should contain only one element, the desired
51
      cluster name
52
  @rtype: int
53
  @return: the desired exit code
54

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

    
60
  vg_name = opts.vg_name
61
  if opts.lvm_storage and not opts.vg_name:
62
    vg_name = constants.DEFAULT_VG
63

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

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

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

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

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

    
88
  if opts.candidate_pool_size is None:
89
    opts.candidate_pool_size = constants.MASTER_POOL_SIZE_DEFAULT
90

    
91
  if opts.mac_prefix is None:
92
    opts.mac_prefix = constants.DEFAULT_MAC_PREFIX
93

    
94
  bootstrap.InitCluster(cluster_name=args[0],
95
                        secondary_ip=opts.secondary_ip,
96
                        vg_name=vg_name,
97
                        mac_prefix=opts.mac_prefix,
98
                        master_netdev=opts.master_netdev,
99
                        file_storage_dir=opts.file_storage_dir,
100
                        enabled_hypervisors=hvlist,
101
                        hvparams=hvparams,
102
                        beparams=beparams,
103
                        nicparams=nicparams,
104
                        candidate_pool_size=opts.candidate_pool_size,
105
                        modify_etc_hosts=opts.modify_etc_hosts,
106
                        modify_ssh_setup=opts.modify_ssh_setup,
107
                        )
108
  op = opcodes.OpPostInitCluster()
109
  SubmitOpCode(op, opts=opts)
110
  return 0
111

    
112

    
113
@UsesRPC
114
def DestroyCluster(opts, args):
115
  """Destroy the cluster.
116

    
117
  @param opts: the command line options selected by the user
118
  @type args: list
119
  @param args: should be an empty list
120
  @rtype: int
121
  @return: the desired exit code
122

    
123
  """
124
  if not opts.yes_do_it:
125
    ToStderr("Destroying a cluster is irreversible. If you really want"
126
             " destroy this cluster, supply the --yes-do-it option.")
127
    return 1
128

    
129
  op = opcodes.OpDestroyCluster()
130
  master = SubmitOpCode(op, opts=opts)
131
  # if we reached this, the opcode didn't fail; we can proceed to
132
  # shutdown all the daemons
133
  bootstrap.FinalizeClusterDestroy(master)
134
  return 0
135

    
136

    
137
def RenameCluster(opts, args):
138
  """Rename the cluster.
139

    
140
  @param opts: the command line options selected by the user
141
  @type args: list
142
  @param args: should contain only one element, the new cluster name
143
  @rtype: int
144
  @return: the desired exit code
145

    
146
  """
147
  name = args[0]
148
  if not opts.force:
149
    usertext = ("This will rename the cluster to '%s'. If you are connected"
150
                " over the network to the cluster name, the operation is very"
151
                " dangerous as the IP address will be removed from the node"
152
                " and the change may not go through. Continue?") % name
153
    if not AskUser(usertext):
154
      return 1
155

    
156
  op = opcodes.OpRenameCluster(name=name)
157
  SubmitOpCode(op, opts=opts)
158
  return 0
159

    
160

    
161
def RedistributeConfig(opts, args):
162
  """Forces push of the cluster configuration.
163

    
164
  @param opts: the command line options selected by the user
165
  @type args: list
166
  @param args: empty list
167
  @rtype: int
168
  @return: the desired exit code
169

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

    
175

    
176
def ShowClusterVersion(opts, args):
177
  """Write version of ganeti software to the standard output.
178

    
179
  @param opts: the command line options selected by the user
180
  @type args: list
181
  @param args: should be an empty list
182
  @rtype: int
183
  @return: the desired exit code
184

    
185
  """
186
  cl = GetClient()
187
  result = cl.QueryClusterInfo()
188
  ToStdout("Software version: %s", result["software_version"])
189
  ToStdout("Internode protocol: %s", result["protocol_version"])
190
  ToStdout("Configuration format: %s", result["config_version"])
191
  ToStdout("OS api version: %s", result["os_api_version"])
192
  ToStdout("Export interface: %s", result["export_version"])
193
  return 0
194

    
195

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

    
199
  @param opts: the command line options selected by the user
200
  @type args: list
201
  @param args: should be an empty list
202
  @rtype: int
203
  @return: the desired exit code
204

    
205
  """
206
  master = bootstrap.GetMaster()
207
  ToStdout(master)
208
  return 0
209

    
210

    
211
def _PrintGroupedParams(paramsdict, level=1):
212
  """Print Grouped parameters (be, nic, disk) by group.
213

    
214
  @type paramsdict: dict of dicts
215
  @param paramsdict: {group: {param: value, ...}, ...}
216
  @type level: int
217
  @param level: Level of indention
218

    
219
  """
220
  indent = "  " * level
221
  for item, val in paramsdict.items():
222
    if isinstance(val, dict):
223
      ToStdout("%s- %s:", indent, item)
224
      _PrintGroupedParams(val, level=level + 1)
225
    else:
226
      ToStdout("%s  %s: %s", indent, item, val)
227

    
228

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

    
232
  @param opts: the command line options selected by the user
233
  @type args: list
234
  @param args: should be an empty list
235
  @rtype: int
236
  @return: the desired exit code
237

    
238
  """
239
  cl = GetClient()
240
  result = cl.QueryClusterInfo()
241

    
242
  ToStdout("Cluster name: %s", result["name"])
243
  ToStdout("Cluster UUID: %s", result["uuid"])
244

    
245
  ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
246
  ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))
247

    
248
  ToStdout("Master node: %s", result["master"])
249

    
250
  ToStdout("Architecture (this node): %s (%s)",
251
           result["architecture"][0], result["architecture"][1])
252

    
253
  if result["tags"]:
254
    tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
255
  else:
256
    tags = "(none)"
257

    
258
  ToStdout("Tags: %s", tags)
259

    
260
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
261
  ToStdout("Enabled hypervisors: %s",
262
           utils.CommaJoin(result["enabled_hypervisors"]))
263

    
264
  ToStdout("Hypervisor parameters:")
265
  _PrintGroupedParams(result["hvparams"])
266

    
267
  ToStdout("OS specific hypervisor parameters:")
268
  _PrintGroupedParams(result["os_hvp"])
269

    
270
  ToStdout("Cluster parameters:")
271
  ToStdout("  - candidate pool size: %s", result["candidate_pool_size"])
272
  ToStdout("  - master netdev: %s", result["master_netdev"])
273
  ToStdout("  - lvm volume group: %s", result["volume_group_name"])
274
  ToStdout("  - file storage path: %s", result["file_storage_dir"])
275

    
276
  ToStdout("Default instance parameters:")
277
  _PrintGroupedParams(result["beparams"])
278

    
279
  ToStdout("Default nic parameters:")
280
  _PrintGroupedParams(result["nicparams"])
281

    
282
  return 0
283

    
284

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

    
288
  @param opts: the command line options selected by the user
289
  @type args: list
290
  @param args: should contain only one element, the path of
291
      the file to be copied
292
  @rtype: int
293
  @return: the desired exit code
294

    
295
  """
296
  filename = args[0]
297
  if not os.path.exists(filename):
298
    raise errors.OpPrereqError("No such filename '%s'" % filename,
299
                               errors.ECODE_INVAL)
300

    
301
  cl = GetClient()
302

    
303
  myname = utils.GetHostInfo().name
304

    
305
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
306

    
307
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl)
308
  results = [name for name in results if name != myname]
309

    
310
  srun = ssh.SshRunner(cluster_name=cluster_name)
311
  for node in results:
312
    if not srun.CopyFileToNode(node, filename):
313
      ToStderr("Copy of file %s to node %s failed", filename, node)
314

    
315
  return 0
316

    
317

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

    
321
  @param opts: the command line options selected by the user
322
  @type args: list
323
  @param args: should contain the command to be run and its arguments
324
  @rtype: int
325
  @return: the desired exit code
326

    
327
  """
328
  cl = GetClient()
329

    
330
  command = " ".join(args)
331

    
332
  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
333

    
334
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
335
                                                    "master_node"])
336

    
337
  srun = ssh.SshRunner(cluster_name=cluster_name)
338

    
339
  # Make sure master node is at list end
340
  if master_node in nodes:
341
    nodes.remove(master_node)
342
    nodes.append(master_node)
343

    
344
  for name in nodes:
345
    result = srun.Run(name, "root", command)
346
    ToStdout("------------------------------------------------")
347
    ToStdout("node: %s", name)
348
    ToStdout("%s", result.output)
349
    ToStdout("return code = %s", result.exit_code)
350

    
351
  return 0
352

    
353

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

    
357
  @param opts: the command line options selected by the user
358
  @type args: list
359
  @param args: should be an empty list
360
  @rtype: int
361
  @return: the desired exit code
362

    
363
  """
364
  skip_checks = []
365
  if opts.skip_nplusone_mem:
366
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
367
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
368
                               verbose=opts.verbose,
369
                               error_codes=opts.error_codes,
370
                               debug_simulate_errors=opts.simulate_errors)
371
  if SubmitOpCode(op, opts=opts):
372
    return 0
373
  else:
374
    return 1
375

    
376

    
377
def VerifyDisks(opts, args):
378
  """Verify integrity of cluster disks.
379

    
380
  @param opts: the command line options selected by the user
381
  @type args: list
382
  @param args: should be an empty list
383
  @rtype: int
384
  @return: the desired exit code
385

    
386
  """
387
  op = opcodes.OpVerifyDisks()
388
  result = SubmitOpCode(op, opts=opts)
389
  if not isinstance(result, (list, tuple)) or len(result) != 3:
390
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
391

    
392
  bad_nodes, instances, missing = result
393

    
394
  retcode = constants.EXIT_SUCCESS
395

    
396
  if bad_nodes:
397
    for node, text in bad_nodes.items():
398
      ToStdout("Error gathering data on node %s: %s",
399
               node, utils.SafeEncode(text[-400:]))
400
      retcode |= 1
401
      ToStdout("You need to fix these nodes first before fixing instances")
402

    
403
  if instances:
404
    for iname in instances:
405
      if iname in missing:
406
        continue
407
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
408
      try:
409
        ToStdout("Activating disks for instance '%s'", iname)
410
        SubmitOpCode(op, opts=opts)
411
      except errors.GenericError, err:
412
        nret, msg = FormatError(err)
413
        retcode |= nret
414
        ToStderr("Error activating disks for instance %s: %s", iname, msg)
415

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

    
434
  return retcode
435

    
436

    
437
def RepairDiskSizes(opts, args):
438
  """Verify sizes of cluster disks.
439

    
440
  @param opts: the command line options selected by the user
441
  @type args: list
442
  @param args: optional list of instances to restrict check to
443
  @rtype: int
444
  @return: the desired exit code
445

    
446
  """
447
  op = opcodes.OpRepairDiskSizes(instances=args)
448
  SubmitOpCode(op, opts=opts)
449

    
450

    
451
@UsesRPC
452
def MasterFailover(opts, args):
453
  """Failover the master node.
454

    
455
  This command, when run on a non-master node, will cause the current
456
  master to cease being master, and the non-master to become new
457
  master.
458

    
459
  @param opts: the command line options selected by the user
460
  @type args: list
461
  @param args: should be an empty list
462
  @rtype: int
463
  @return: the desired exit code
464

    
465
  """
466
  if opts.no_voting:
467
    usertext = ("This will perform the failover even if most other nodes"
468
                " are down, or if this node is outdated. This is dangerous"
469
                " as it can lead to a non-consistent cluster. Check the"
470
                " gnt-cluster(8) man page before proceeding. Continue?")
471
    if not AskUser(usertext):
472
      return 1
473

    
474
  return bootstrap.MasterFailover(no_voting=opts.no_voting)
475

    
476

    
477
def SearchTags(opts, args):
478
  """Searches the tags on all the cluster.
479

    
480
  @param opts: the command line options selected by the user
481
  @type args: list
482
  @param args: should contain only one element, the tag pattern
483
  @rtype: int
484
  @return: the desired exit code
485

    
486
  """
487
  op = opcodes.OpSearchTags(pattern=args[0])
488
  result = SubmitOpCode(op, opts=opts)
489
  if not result:
490
    return 1
491
  result = list(result)
492
  result.sort()
493
  for path, tag in result:
494
    ToStdout("%s %s", path, tag)
495

    
496

    
497
def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
498
                 new_confd_hmac_key, force):
499
  """Renews cluster certificates, keys and secrets.
500

    
501
  @type new_cluster_cert: bool
502
  @param new_cluster_cert: Whether to generate a new cluster certificate
503
  @type new_rapi_cert: bool
504
  @param new_rapi_cert: Whether to generate a new RAPI certificate
505
  @type rapi_cert_filename: string
506
  @param rapi_cert_filename: Path to file containing new RAPI certificate
507
  @type new_confd_hmac_key: bool
508
  @param new_confd_hmac_key: Whether to generate a new HMAC key
509
  @type force: bool
510
  @param force: Whether to ask user for confirmation
511

    
512
  """
513
  if new_rapi_cert and rapi_cert_filename:
514
    ToStderr("Only one of the --new-rapi-certficate and --rapi-certificate"
515
             " options can be specified at the same time.")
516
    return 1
517

    
518
  if rapi_cert_filename:
519
    # Read and verify new certificate
520
    try:
521
      rapi_cert_pem = utils.ReadFile(rapi_cert_filename)
522

    
523
      OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
524
                                      rapi_cert_pem)
525
    except Exception, err: # pylint: disable-msg=W0703
526
      ToStderr("Can't load new RAPI certificate from %s: %s" %
527
               (rapi_cert_filename, str(err)))
528
      return 1
529

    
530
    try:
531
      OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, rapi_cert_pem)
532
    except Exception, err: # pylint: disable-msg=W0703
533
      ToStderr("Can't load new RAPI private key from %s: %s" %
534
               (rapi_cert_filename, str(err)))
535
      return 1
536

    
537
  else:
538
    rapi_cert_pem = None
539

    
540
  if not force:
541
    usertext = ("This requires all daemons on all nodes to be restarted and"
542
                " may take some time. Continue?")
543
    if not AskUser(usertext):
544
      return 1
545

    
546
  def _RenewCryptoInner(ctx):
547
    ctx.feedback_fn("Updating certificates and keys")
548
    bootstrap.GenerateClusterCrypto(new_cluster_cert, new_rapi_cert,
549
                                    new_confd_hmac_key,
550
                                    rapi_cert_pem=rapi_cert_pem)
551

    
552
    files_to_copy = []
553

    
554
    if new_cluster_cert:
555
      files_to_copy.append(constants.NODED_CERT_FILE)
556

    
557
    if new_rapi_cert or rapi_cert_pem:
558
      files_to_copy.append(constants.RAPI_CERT_FILE)
559

    
560
    if new_confd_hmac_key:
561
      files_to_copy.append(constants.CONFD_HMAC_KEY)
562

    
563
    if files_to_copy:
564
      for node_name in ctx.nonmaster_nodes:
565
        ctx.feedback_fn("Copying %s to %s" %
566
                        (", ".join(files_to_copy), node_name))
567
        for file_name in files_to_copy:
568
          ctx.ssh.CopyFileToNode(node_name, file_name)
569

    
570
  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
571

    
572
  ToStdout("All requested certificates and keys have been replaced."
573
           " Running \"gnt-cluster verify\" now is recommended.")
574

    
575
  return 0
576

    
577

    
578
def RenewCrypto(opts, args):
579
  """Renews cluster certificates, keys and secrets.
580

    
581
  """
582
  return _RenewCrypto(opts.new_cluster_cert,
583
                      opts.new_rapi_cert,
584
                      opts.rapi_cert,
585
                      opts.new_confd_hmac_key,
586
                      opts.force)
587

    
588

    
589
def SetClusterParams(opts, args):
590
  """Modify the cluster.
591

    
592
  @param opts: the command line options selected by the user
593
  @type args: list
594
  @param args: should be an empty list
595
  @rtype: int
596
  @return: the desired exit code
597

    
598
  """
599
  if not (not opts.lvm_storage or opts.vg_name or
600
          opts.enabled_hypervisors or opts.hvparams or
601
          opts.beparams or opts.nicparams or
602
          opts.candidate_pool_size is not None):
603
    ToStderr("Please give at least one of the parameters.")
604
    return 1
605

    
606
  vg_name = opts.vg_name
607
  if not opts.lvm_storage and opts.vg_name:
608
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
609
    return 1
610

    
611
  if not opts.lvm_storage:
612
    vg_name = ""
613

    
614
  hvlist = opts.enabled_hypervisors
615
  if hvlist is not None:
616
    hvlist = hvlist.split(",")
617

    
618
  # a list of (name, dict) we can pass directly to dict() (or [])
619
  hvparams = dict(opts.hvparams)
620
  for hv_params in hvparams.values():
621
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
622

    
623
  beparams = opts.beparams
624
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
625

    
626
  nicparams = opts.nicparams
627
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
628

    
629
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
630
                                  enabled_hypervisors=hvlist,
631
                                  hvparams=hvparams,
632
                                  os_hvp=None,
633
                                  beparams=beparams,
634
                                  nicparams=nicparams,
635
                                  candidate_pool_size=opts.candidate_pool_size)
636
  SubmitOpCode(op, opts=opts)
637
  return 0
638

    
639

    
640
def QueueOps(opts, args):
641
  """Queue operations.
642

    
643
  @param opts: the command line options selected by the user
644
  @type args: list
645
  @param args: should contain only one element, the subcommand
646
  @rtype: int
647
  @return: the desired exit code
648

    
649
  """
650
  command = args[0]
651
  client = GetClient()
652
  if command in ("drain", "undrain"):
653
    drain_flag = command == "drain"
654
    client.SetQueueDrainFlag(drain_flag)
655
  elif command == "info":
656
    result = client.QueryConfigValues(["drain_flag"])
657
    if result[0]:
658
      val = "set"
659
    else:
660
      val = "unset"
661
    ToStdout("The drain flag is %s" % val)
662
  else:
663
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
664
                               errors.ECODE_INVAL)
665

    
666
  return 0
667

    
668

    
669
def _ShowWatcherPause(until):
670
  if until is None or until < time.time():
671
    ToStdout("The watcher is not paused.")
672
  else:
673
    ToStdout("The watcher is paused until %s.", time.ctime(until))
674

    
675

    
676
def WatcherOps(opts, args):
677
  """Watcher operations.
678

    
679
  @param opts: the command line options selected by the user
680
  @type args: list
681
  @param args: should contain only one element, the subcommand
682
  @rtype: int
683
  @return: the desired exit code
684

    
685
  """
686
  command = args[0]
687
  client = GetClient()
688

    
689
  if command == "continue":
690
    client.SetWatcherPause(None)
691
    ToStdout("The watcher is no longer paused.")
692

    
693
  elif command == "pause":
694
    if len(args) < 2:
695
      raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
696

    
697
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
698
    _ShowWatcherPause(result)
699

    
700
  elif command == "info":
701
    result = client.QueryConfigValues(["watcher_pause"])
702
    _ShowWatcherPause(result[0])
703

    
704
  else:
705
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
706
                               errors.ECODE_INVAL)
707

    
708
  return 0
709

    
710

    
711
commands = {
712
  'init': (
713
    InitCluster, [ArgHost(min=1, max=1)],
714
    [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, GLOBAL_FILEDIR_OPT,
715
     HVLIST_OPT, MAC_PREFIX_OPT, MASTER_NETDEV_OPT, NIC_PARAMS_OPT,
716
     NOLVM_STORAGE_OPT, NOMODIFY_ETCHOSTS_OPT, NOMODIFY_SSH_SETUP_OPT,
717
     SECONDARY_IP_OPT, VG_NAME_OPT],
718
    "[opts...] <cluster_name>", "Initialises a new cluster configuration"),
719
  'destroy': (
720
    DestroyCluster, ARGS_NONE, [YES_DOIT_OPT],
721
    "", "Destroy cluster"),
722
  'rename': (
723
    RenameCluster, [ArgHost(min=1, max=1)],
724
    [FORCE_OPT],
725
    "<new_name>",
726
    "Renames the cluster"),
727
  'redist-conf': (
728
    RedistributeConfig, ARGS_NONE, [SUBMIT_OPT],
729
    "", "Forces a push of the configuration file and ssconf files"
730
    " to the nodes in the cluster"),
731
  'verify': (
732
    VerifyCluster, ARGS_NONE,
733
    [VERBOSE_OPT, DEBUG_SIMERR_OPT, ERROR_CODES_OPT, NONPLUS1_OPT],
734
    "", "Does a check on the cluster configuration"),
735
  'verify-disks': (
736
    VerifyDisks, ARGS_NONE, [],
737
    "", "Does a check on the cluster disk status"),
738
  'repair-disk-sizes': (
739
    RepairDiskSizes, ARGS_MANY_INSTANCES, [],
740
    "", "Updates mismatches in recorded disk sizes"),
741
  'masterfailover': (
742
    MasterFailover, ARGS_NONE, [NOVOTING_OPT],
743
    "", "Makes the current node the master"),
744
  'version': (
745
    ShowClusterVersion, ARGS_NONE, [],
746
    "", "Shows the cluster version"),
747
  'getmaster': (
748
    ShowClusterMaster, ARGS_NONE, [],
749
    "", "Shows the cluster master"),
750
  'copyfile': (
751
    ClusterCopyFile, [ArgFile(min=1, max=1)],
752
    [NODE_LIST_OPT],
753
    "[-n node...] <filename>", "Copies a file to all (or only some) nodes"),
754
  'command': (
755
    RunClusterCommand, [ArgCommand(min=1)],
756
    [NODE_LIST_OPT],
757
    "[-n node...] <command>", "Runs a command on all (or only some) nodes"),
758
  'info': (
759
    ShowClusterConfig, ARGS_NONE, [],
760
    "", "Show cluster configuration"),
761
  'list-tags': (
762
    ListTags, ARGS_NONE, [], "", "List the tags of the cluster"),
763
  'add-tags': (
764
    AddTags, [ArgUnknown()], [TAG_SRC_OPT],
765
    "tag...", "Add tags to the cluster"),
766
  'remove-tags': (
767
    RemoveTags, [ArgUnknown()], [TAG_SRC_OPT],
768
    "tag...", "Remove tags from the cluster"),
769
  'search-tags': (
770
    SearchTags, [ArgUnknown(min=1, max=1)],
771
    [], "", "Searches the tags on all objects on"
772
    " the cluster for a given pattern (regex)"),
773
  'queue': (
774
    QueueOps,
775
    [ArgChoice(min=1, max=1, choices=["drain", "undrain", "info"])],
776
    [], "drain|undrain|info", "Change queue properties"),
777
  'watcher': (
778
    WatcherOps,
779
    [ArgChoice(min=1, max=1, choices=["pause", "continue", "info"]),
780
     ArgSuggest(min=0, max=1, choices=["30m", "1h", "4h"])],
781
    [],
782
    "{pause <timespec>|continue|info}", "Change watcher properties"),
783
  'modify': (
784
    SetClusterParams, ARGS_NONE,
785
    [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, HVLIST_OPT,
786
     NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, VG_NAME_OPT],
787
    "[opts...]",
788
    "Alters the parameters of the cluster"),
789
  "renew-crypto": (
790
    RenewCrypto, ARGS_NONE,
791
    [NEW_CLUSTER_CERT_OPT, NEW_RAPI_CERT_OPT, RAPI_CERT_OPT,
792
     NEW_CONFD_HMAC_KEY_OPT, FORCE_OPT],
793
    "[opts...]",
794
    "Renews cluster certificates, keys and secrets"),
795
  }
796

    
797

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