Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ 1338f2b4

History | View | Annotate | Download (24.4 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
from ganeti import uidpool
43

    
44

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

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

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

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

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

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

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

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

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

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

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

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

    
114

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

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

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

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

    
138

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

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

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

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

    
162

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

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

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

    
177

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

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

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

    
197

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

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

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

    
212

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

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

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

    
230

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
286
  return 0
287

    
288

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

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

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

    
305
  cl = GetClient()
306

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

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

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

    
317
  return 0
318

    
319

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

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

    
329
  """
330
  cl = GetClient()
331

    
332
  command = " ".join(args)
333

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

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

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

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

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

    
353
  return 0
354

    
355

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

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

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

    
378

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

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

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

    
394
  bad_nodes, instances, missing = result
395

    
396
  retcode = constants.EXIT_SUCCESS
397

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

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

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

    
436
  return retcode
437

    
438

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

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

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

    
452

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

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

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

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

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

    
478

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

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

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

    
498

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

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

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

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

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

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

    
539
  else:
540
    rapi_cert_pem = None
541

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

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

    
554
    files_to_copy = []
555

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

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

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

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

    
572
  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
573

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

    
577
  return 0
578

    
579

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

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

    
590

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

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

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

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

    
615
  if not opts.lvm_storage:
616
    vg_name = ""
617

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

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

    
627
  beparams = opts.beparams
628
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
629

    
630
  nicparams = opts.nicparams
631
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
632

    
633

    
634
  mnh = opts.maintain_node_health
635

    
636
  uid_pool = opts.uid_pool
637
  if uid_pool is not None:
638
    uid_pool = uidpool.ParseUidPool(uid_pool)
639

    
640
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
641
                                  enabled_hypervisors=hvlist,
642
                                  hvparams=hvparams,
643
                                  os_hvp=None,
644
                                  beparams=beparams,
645
                                  nicparams=nicparams,
646
                                  candidate_pool_size=opts.candidate_pool_size,
647
                                  maintain_node_health=mnh,
648
                                  uid_pool=uid_pool)
649
  SubmitOpCode(op, opts=opts)
650
  return 0
651

    
652

    
653
def QueueOps(opts, args):
654
  """Queue operations.
655

    
656
  @param opts: the command line options selected by the user
657
  @type args: list
658
  @param args: should contain only one element, the subcommand
659
  @rtype: int
660
  @return: the desired exit code
661

    
662
  """
663
  command = args[0]
664
  client = GetClient()
665
  if command in ("drain", "undrain"):
666
    drain_flag = command == "drain"
667
    client.SetQueueDrainFlag(drain_flag)
668
  elif command == "info":
669
    result = client.QueryConfigValues(["drain_flag"])
670
    if result[0]:
671
      val = "set"
672
    else:
673
      val = "unset"
674
    ToStdout("The drain flag is %s" % val)
675
  else:
676
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
677
                               errors.ECODE_INVAL)
678

    
679
  return 0
680

    
681

    
682
def _ShowWatcherPause(until):
683
  if until is None or until < time.time():
684
    ToStdout("The watcher is not paused.")
685
  else:
686
    ToStdout("The watcher is paused until %s.", time.ctime(until))
687

    
688

    
689
def WatcherOps(opts, args):
690
  """Watcher operations.
691

    
692
  @param opts: the command line options selected by the user
693
  @type args: list
694
  @param args: should contain only one element, the subcommand
695
  @rtype: int
696
  @return: the desired exit code
697

    
698
  """
699
  command = args[0]
700
  client = GetClient()
701

    
702
  if command == "continue":
703
    client.SetWatcherPause(None)
704
    ToStdout("The watcher is no longer paused.")
705

    
706
  elif command == "pause":
707
    if len(args) < 2:
708
      raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
709

    
710
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
711
    _ShowWatcherPause(result)
712

    
713
  elif command == "info":
714
    result = client.QueryConfigValues(["watcher_pause"])
715
    _ShowWatcherPause(result[0])
716

    
717
  else:
718
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
719
                               errors.ECODE_INVAL)
720

    
721
  return 0
722

    
723

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

    
811

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