Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ 3953242f

History | View | Annotate | Download (24.2 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
                        maintain_node_health=opts.maintain_node_health,
108
                        )
109
  op = opcodes.OpPostInitCluster()
110
  SubmitOpCode(op, opts=opts)
111
  return 0
112

    
113

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

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

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

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

    
137

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

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

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

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

    
161

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

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

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

    
176

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

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

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

    
196

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

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

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

    
211

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

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

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

    
229

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

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

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

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

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

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

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

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

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

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

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

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

    
271
  ToStdout("Cluster parameters:")
272
  ToStdout("  - candidate pool size: %s", result["candidate_pool_size"])
273
  ToStdout("  - master netdev: %s", result["master_netdev"])
274
  ToStdout("  - lvm volume group: %s", result["volume_group_name"])
275
  ToStdout("  - file storage path: %s", result["file_storage_dir"])
276
  ToStdout("  - maintenance of node health: %s",
277
           result["maintain_node_health"])
278

    
279
  ToStdout("Default instance parameters:")
280
  _PrintGroupedParams(result["beparams"])
281

    
282
  ToStdout("Default nic parameters:")
283
  _PrintGroupedParams(result["nicparams"])
284

    
285
  return 0
286

    
287

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

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

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

    
304
  cl = GetClient()
305

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

    
308
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
309
                           secondary_ips=opts.use_replication_network)
310

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

    
316
  return 0
317

    
318

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

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

    
328
  """
329
  cl = GetClient()
330

    
331
  command = " ".join(args)
332

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

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

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

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

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

    
352
  return 0
353

    
354

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

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

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

    
377

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

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

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

    
393
  bad_nodes, instances, missing = result
394

    
395
  retcode = constants.EXIT_SUCCESS
396

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

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

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

    
435
  return retcode
436

    
437

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

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

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

    
451

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

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

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

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

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

    
477

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

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

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

    
497

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

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

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

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

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

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

    
538
  else:
539
    rapi_cert_pem = None
540

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

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

    
553
    files_to_copy = []
554

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

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

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

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

    
571
  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
572

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

    
576
  return 0
577

    
578

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

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

    
589

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

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

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

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

    
613
  if not opts.lvm_storage:
614
    vg_name = ""
615

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

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

    
625
  beparams = opts.beparams
626
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
627

    
628
  nicparams = opts.nicparams
629
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
630

    
631
  mnh = opts.maintain_node_health
632

    
633
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
634
                                  enabled_hypervisors=hvlist,
635
                                  hvparams=hvparams,
636
                                  os_hvp=None,
637
                                  beparams=beparams,
638
                                  nicparams=nicparams,
639
                                  candidate_pool_size=opts.candidate_pool_size,
640
                                  maintain_node_health=mnh)
641
  SubmitOpCode(op, opts=opts)
642
  return 0
643

    
644

    
645
def QueueOps(opts, args):
646
  """Queue operations.
647

    
648
  @param opts: the command line options selected by the user
649
  @type args: list
650
  @param args: should contain only one element, the subcommand
651
  @rtype: int
652
  @return: the desired exit code
653

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

    
671
  return 0
672

    
673

    
674
def _ShowWatcherPause(until):
675
  if until is None or until < time.time():
676
    ToStdout("The watcher is not paused.")
677
  else:
678
    ToStdout("The watcher is paused until %s.", time.ctime(until))
679

    
680

    
681
def WatcherOps(opts, args):
682
  """Watcher operations.
683

    
684
  @param opts: the command line options selected by the user
685
  @type args: list
686
  @param args: should contain only one element, the subcommand
687
  @rtype: int
688
  @return: the desired exit code
689

    
690
  """
691
  command = args[0]
692
  client = GetClient()
693

    
694
  if command == "continue":
695
    client.SetWatcherPause(None)
696
    ToStdout("The watcher is no longer paused.")
697

    
698
  elif command == "pause":
699
    if len(args) < 2:
700
      raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
701

    
702
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
703
    _ShowWatcherPause(result)
704

    
705
  elif command == "info":
706
    result = client.QueryConfigValues(["watcher_pause"])
707
    _ShowWatcherPause(result[0])
708

    
709
  else:
710
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
711
                               errors.ECODE_INVAL)
712

    
713
  return 0
714

    
715

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

    
802

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