Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ 852bbc95

History | View | Annotate | Download (24.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
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
  ToStdout("  - uid pool: %s", uidpool.FormatUidPool(result["uid_pool"]))
280

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

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

    
287
  return 0
288

    
289

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

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

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

    
306
  cl = GetClient()
307

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

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

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

    
318
  return 0
319

    
320

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

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

    
330
  """
331
  cl = GetClient()
332

    
333
  command = " ".join(args)
334

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

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

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

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

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

    
354
  return 0
355

    
356

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

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

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

    
379

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

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

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

    
395
  bad_nodes, instances, missing = result
396

    
397
  retcode = constants.EXIT_SUCCESS
398

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

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

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

    
437
  return retcode
438

    
439

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

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

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

    
453

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

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

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

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

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

    
479

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

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

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

    
499

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

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

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

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

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

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

    
540
  else:
541
    rapi_cert_pem = None
542

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

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

    
555
    files_to_copy = []
556

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

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

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

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

    
573
  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
574

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

    
578
  return 0
579

    
580

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

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

    
591

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

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

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

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

    
618
  if not opts.lvm_storage:
619
    vg_name = ""
620

    
621
  hvlist = opts.enabled_hypervisors
622
  if hvlist is not None:
623
    hvlist = hvlist.split(",")
624

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

    
630
  beparams = opts.beparams
631
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
632

    
633
  nicparams = opts.nicparams
634
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
635

    
636

    
637
  mnh = opts.maintain_node_health
638

    
639
  uid_pool = opts.uid_pool
640
  if uid_pool is not None:
641
    uid_pool = uidpool.ParseUidPool(uid_pool)
642

    
643
  add_uids = opts.add_uids
644
  if add_uids is not None:
645
    add_uids = uidpool.ParseUidPool(add_uids)
646

    
647
  remove_uids = opts.remove_uids
648
  if remove_uids is not None:
649
    remove_uids = uidpool.ParseUidPool(remove_uids)
650

    
651
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
652
                                  enabled_hypervisors=hvlist,
653
                                  hvparams=hvparams,
654
                                  os_hvp=None,
655
                                  beparams=beparams,
656
                                  nicparams=nicparams,
657
                                  candidate_pool_size=opts.candidate_pool_size,
658
                                  maintain_node_health=mnh,
659
                                  uid_pool=uid_pool,
660
                                  add_uids=add_uids,
661
                                  remove_uids=remove_uids)
662
  SubmitOpCode(op, opts=opts)
663
  return 0
664

    
665

    
666
def QueueOps(opts, args):
667
  """Queue operations.
668

    
669
  @param opts: the command line options selected by the user
670
  @type args: list
671
  @param args: should contain only one element, the subcommand
672
  @rtype: int
673
  @return: the desired exit code
674

    
675
  """
676
  command = args[0]
677
  client = GetClient()
678
  if command in ("drain", "undrain"):
679
    drain_flag = command == "drain"
680
    client.SetQueueDrainFlag(drain_flag)
681
  elif command == "info":
682
    result = client.QueryConfigValues(["drain_flag"])
683
    if result[0]:
684
      val = "set"
685
    else:
686
      val = "unset"
687
    ToStdout("The drain flag is %s" % val)
688
  else:
689
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
690
                               errors.ECODE_INVAL)
691

    
692
  return 0
693

    
694

    
695
def _ShowWatcherPause(until):
696
  if until is None or until < time.time():
697
    ToStdout("The watcher is not paused.")
698
  else:
699
    ToStdout("The watcher is paused until %s.", time.ctime(until))
700

    
701

    
702
def WatcherOps(opts, args):
703
  """Watcher operations.
704

    
705
  @param opts: the command line options selected by the user
706
  @type args: list
707
  @param args: should contain only one element, the subcommand
708
  @rtype: int
709
  @return: the desired exit code
710

    
711
  """
712
  command = args[0]
713
  client = GetClient()
714

    
715
  if command == "continue":
716
    client.SetWatcherPause(None)
717
    ToStdout("The watcher is no longer paused.")
718

    
719
  elif command == "pause":
720
    if len(args) < 2:
721
      raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
722

    
723
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
724
    _ShowWatcherPause(result)
725

    
726
  elif command == "info":
727
    result = client.QueryConfigValues(["watcher_pause"])
728
    _ShowWatcherPause(result[0])
729

    
730
  else:
731
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
732
                               errors.ECODE_INVAL)
733

    
734
  return 0
735

    
736

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

    
824

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