Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ 74adc100

History | View | Annotate | Download (23.9 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
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
304

    
305
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
306
                           secondary_ips=opts.use_replication_network)
307

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

    
313
  return 0
314

    
315

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

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

    
325
  """
326
  cl = GetClient()
327

    
328
  command = " ".join(args)
329

    
330
  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
331

    
332
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
333
                                                    "master_node"])
334

    
335
  srun = ssh.SshRunner(cluster_name=cluster_name)
336

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

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

    
349
  return 0
350

    
351

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

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

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

    
374

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

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

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

    
390
  bad_nodes, instances, missing = result
391

    
392
  retcode = constants.EXIT_SUCCESS
393

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

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

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

    
432
  return retcode
433

    
434

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

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

    
444
  """
445
  op = opcodes.OpRepairDiskSizes(instances=args)
446
  SubmitOpCode(op, opts=opts)
447

    
448

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

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

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

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

    
472
  return bootstrap.MasterFailover(no_voting=opts.no_voting)
473

    
474

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

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

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

    
494

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

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

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

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

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

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

    
535
  else:
536
    rapi_cert_pem = None
537

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

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

    
550
    files_to_copy = []
551

    
552
    if new_cluster_cert:
553
      files_to_copy.append(constants.NODED_CERT_FILE)
554

    
555
    if new_rapi_cert or rapi_cert_pem:
556
      files_to_copy.append(constants.RAPI_CERT_FILE)
557

    
558
    if new_confd_hmac_key:
559
      files_to_copy.append(constants.CONFD_HMAC_KEY)
560

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

    
568
  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
569

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

    
573
  return 0
574

    
575

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

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

    
586

    
587
def SetClusterParams(opts, args):
588
  """Modify the cluster.
589

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

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

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

    
609
  if not opts.lvm_storage:
610
    vg_name = ""
611

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

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

    
621
  beparams = opts.beparams
622
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
623

    
624
  nicparams = opts.nicparams
625
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
626

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

    
637

    
638
def QueueOps(opts, args):
639
  """Queue operations.
640

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

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

    
664
  return 0
665

    
666

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

    
673

    
674
def WatcherOps(opts, args):
675
  """Watcher operations.
676

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

    
683
  """
684
  command = args[0]
685
  client = GetClient()
686

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

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

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

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

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

    
706
  return 0
707

    
708

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

    
795

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