Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ 9d91c6ab

History | View | Annotate | Download (25.1 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
  uid_pool = opts.uid_pool
96
  if uid_pool is not None:
97
    uid_pool = uidpool.ParseUidPool(uid_pool)
98

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

    
119

    
120
@UsesRPC
121
def DestroyCluster(opts, args):
122
  """Destroy the cluster.
123

    
124
  @param opts: the command line options selected by the user
125
  @type args: list
126
  @param args: should be an empty list
127
  @rtype: int
128
  @return: the desired exit code
129

    
130
  """
131
  if not opts.yes_do_it:
132
    ToStderr("Destroying a cluster is irreversible. If you really want"
133
             " destroy this cluster, supply the --yes-do-it option.")
134
    return 1
135

    
136
  op = opcodes.OpDestroyCluster()
137
  master = SubmitOpCode(op, opts=opts)
138
  # if we reached this, the opcode didn't fail; we can proceed to
139
  # shutdown all the daemons
140
  bootstrap.FinalizeClusterDestroy(master)
141
  return 0
142

    
143

    
144
def RenameCluster(opts, args):
145
  """Rename the cluster.
146

    
147
  @param opts: the command line options selected by the user
148
  @type args: list
149
  @param args: should contain only one element, the new cluster name
150
  @rtype: int
151
  @return: the desired exit code
152

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

    
163
  op = opcodes.OpRenameCluster(name=name)
164
  SubmitOpCode(op, opts=opts)
165
  return 0
166

    
167

    
168
def RedistributeConfig(opts, args):
169
  """Forces push of the cluster configuration.
170

    
171
  @param opts: the command line options selected by the user
172
  @type args: list
173
  @param args: empty list
174
  @rtype: int
175
  @return: the desired exit code
176

    
177
  """
178
  op = opcodes.OpRedistributeConfig()
179
  SubmitOrSend(op, opts)
180
  return 0
181

    
182

    
183
def ShowClusterVersion(opts, args):
184
  """Write version of ganeti software to the standard output.
185

    
186
  @param opts: the command line options selected by the user
187
  @type args: list
188
  @param args: should be an empty list
189
  @rtype: int
190
  @return: the desired exit code
191

    
192
  """
193
  cl = GetClient()
194
  result = cl.QueryClusterInfo()
195
  ToStdout("Software version: %s", result["software_version"])
196
  ToStdout("Internode protocol: %s", result["protocol_version"])
197
  ToStdout("Configuration format: %s", result["config_version"])
198
  ToStdout("OS api version: %s", result["os_api_version"])
199
  ToStdout("Export interface: %s", result["export_version"])
200
  return 0
201

    
202

    
203
def ShowClusterMaster(opts, args):
204
  """Write name of master node to the standard output.
205

    
206
  @param opts: the command line options selected by the user
207
  @type args: list
208
  @param args: should be an empty list
209
  @rtype: int
210
  @return: the desired exit code
211

    
212
  """
213
  master = bootstrap.GetMaster()
214
  ToStdout(master)
215
  return 0
216

    
217

    
218
def _PrintGroupedParams(paramsdict, level=1):
219
  """Print Grouped parameters (be, nic, disk) by group.
220

    
221
  @type paramsdict: dict of dicts
222
  @param paramsdict: {group: {param: value, ...}, ...}
223
  @type level: int
224
  @param level: Level of indention
225

    
226
  """
227
  indent = "  " * level
228
  for item, val in sorted(paramsdict.items()):
229
    if isinstance(val, dict):
230
      ToStdout("%s- %s:", indent, item)
231
      _PrintGroupedParams(val, level=level + 1)
232
    else:
233
      ToStdout("%s  %s: %s", indent, item, val)
234

    
235

    
236
def ShowClusterConfig(opts, args):
237
  """Shows cluster information.
238

    
239
  @param opts: the command line options selected by the user
240
  @type args: list
241
  @param args: should be an empty list
242
  @rtype: int
243
  @return: the desired exit code
244

    
245
  """
246
  cl = GetClient()
247
  result = cl.QueryClusterInfo()
248

    
249
  ToStdout("Cluster name: %s", result["name"])
250
  ToStdout("Cluster UUID: %s", result["uuid"])
251

    
252
  ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
253
  ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))
254

    
255
  ToStdout("Master node: %s", result["master"])
256

    
257
  ToStdout("Architecture (this node): %s (%s)",
258
           result["architecture"][0], result["architecture"][1])
259

    
260
  if result["tags"]:
261
    tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
262
  else:
263
    tags = "(none)"
264

    
265
  ToStdout("Tags: %s", tags)
266

    
267
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
268
  ToStdout("Enabled hypervisors: %s",
269
           utils.CommaJoin(result["enabled_hypervisors"]))
270

    
271
  ToStdout("Hypervisor parameters:")
272
  _PrintGroupedParams(result["hvparams"])
273

    
274
  ToStdout("OS specific hypervisor parameters:")
275
  _PrintGroupedParams(result["os_hvp"])
276

    
277
  ToStdout("Cluster parameters:")
278
  ToStdout("  - candidate pool size: %s", result["candidate_pool_size"])
279
  ToStdout("  - master netdev: %s", result["master_netdev"])
280
  ToStdout("  - lvm volume group: %s", result["volume_group_name"])
281
  ToStdout("  - file storage path: %s", result["file_storage_dir"])
282
  ToStdout("  - maintenance of node health: %s",
283
           result["maintain_node_health"])
284
  ToStdout("  - uid pool: %s", uidpool.FormatUidPool(result["uid_pool"]))
285

    
286
  ToStdout("Default instance parameters:")
287
  _PrintGroupedParams(result["beparams"])
288

    
289
  ToStdout("Default nic parameters:")
290
  _PrintGroupedParams(result["nicparams"])
291

    
292
  return 0
293

    
294

    
295
def ClusterCopyFile(opts, args):
296
  """Copy a file from master to some nodes.
297

    
298
  @param opts: the command line options selected by the user
299
  @type args: list
300
  @param args: should contain only one element, the path of
301
      the file to be copied
302
  @rtype: int
303
  @return: the desired exit code
304

    
305
  """
306
  filename = args[0]
307
  if not os.path.exists(filename):
308
    raise errors.OpPrereqError("No such filename '%s'" % filename,
309
                               errors.ECODE_INVAL)
310

    
311
  cl = GetClient()
312

    
313
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
314

    
315
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
316
                           secondary_ips=opts.use_replication_network)
317

    
318
  srun = ssh.SshRunner(cluster_name=cluster_name)
319
  for node in results:
320
    if not srun.CopyFileToNode(node, filename):
321
      ToStderr("Copy of file %s to node %s failed", filename, node)
322

    
323
  return 0
324

    
325

    
326
def RunClusterCommand(opts, args):
327
  """Run a command on some nodes.
328

    
329
  @param opts: the command line options selected by the user
330
  @type args: list
331
  @param args: should contain the command to be run and its arguments
332
  @rtype: int
333
  @return: the desired exit code
334

    
335
  """
336
  cl = GetClient()
337

    
338
  command = " ".join(args)
339

    
340
  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
341

    
342
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
343
                                                    "master_node"])
344

    
345
  srun = ssh.SshRunner(cluster_name=cluster_name)
346

    
347
  # Make sure master node is at list end
348
  if master_node in nodes:
349
    nodes.remove(master_node)
350
    nodes.append(master_node)
351

    
352
  for name in nodes:
353
    result = srun.Run(name, "root", command)
354
    ToStdout("------------------------------------------------")
355
    ToStdout("node: %s", name)
356
    ToStdout("%s", result.output)
357
    ToStdout("return code = %s", result.exit_code)
358

    
359
  return 0
360

    
361

    
362
def VerifyCluster(opts, args):
363
  """Verify integrity of cluster, performing various test on nodes.
364

    
365
  @param opts: the command line options selected by the user
366
  @type args: list
367
  @param args: should be an empty list
368
  @rtype: int
369
  @return: the desired exit code
370

    
371
  """
372
  skip_checks = []
373
  if opts.skip_nplusone_mem:
374
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
375
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
376
                               verbose=opts.verbose,
377
                               error_codes=opts.error_codes,
378
                               debug_simulate_errors=opts.simulate_errors)
379
  if SubmitOpCode(op, opts=opts):
380
    return 0
381
  else:
382
    return 1
383

    
384

    
385
def VerifyDisks(opts, args):
386
  """Verify integrity of cluster disks.
387

    
388
  @param opts: the command line options selected by the user
389
  @type args: list
390
  @param args: should be an empty list
391
  @rtype: int
392
  @return: the desired exit code
393

    
394
  """
395
  op = opcodes.OpVerifyDisks()
396
  result = SubmitOpCode(op, opts=opts)
397
  if not isinstance(result, (list, tuple)) or len(result) != 3:
398
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
399

    
400
  bad_nodes, instances, missing = result
401

    
402
  retcode = constants.EXIT_SUCCESS
403

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

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

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

    
442
  return retcode
443

    
444

    
445
def RepairDiskSizes(opts, args):
446
  """Verify sizes of cluster disks.
447

    
448
  @param opts: the command line options selected by the user
449
  @type args: list
450
  @param args: optional list of instances to restrict check to
451
  @rtype: int
452
  @return: the desired exit code
453

    
454
  """
455
  op = opcodes.OpRepairDiskSizes(instances=args)
456
  SubmitOpCode(op, opts=opts)
457

    
458

    
459
@UsesRPC
460
def MasterFailover(opts, args):
461
  """Failover the master node.
462

    
463
  This command, when run on a non-master node, will cause the current
464
  master to cease being master, and the non-master to become new
465
  master.
466

    
467
  @param opts: the command line options selected by the user
468
  @type args: list
469
  @param args: should be an empty list
470
  @rtype: int
471
  @return: the desired exit code
472

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

    
482
  return bootstrap.MasterFailover(no_voting=opts.no_voting)
483

    
484

    
485
def SearchTags(opts, args):
486
  """Searches the tags on all the cluster.
487

    
488
  @param opts: the command line options selected by the user
489
  @type args: list
490
  @param args: should contain only one element, the tag pattern
491
  @rtype: int
492
  @return: the desired exit code
493

    
494
  """
495
  op = opcodes.OpSearchTags(pattern=args[0])
496
  result = SubmitOpCode(op, opts=opts)
497
  if not result:
498
    return 1
499
  result = list(result)
500
  result.sort()
501
  for path, tag in result:
502
    ToStdout("%s %s", path, tag)
503

    
504

    
505
def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
506
                 new_confd_hmac_key, force):
507
  """Renews cluster certificates, keys and secrets.
508

    
509
  @type new_cluster_cert: bool
510
  @param new_cluster_cert: Whether to generate a new cluster certificate
511
  @type new_rapi_cert: bool
512
  @param new_rapi_cert: Whether to generate a new RAPI certificate
513
  @type rapi_cert_filename: string
514
  @param rapi_cert_filename: Path to file containing new RAPI certificate
515
  @type new_confd_hmac_key: bool
516
  @param new_confd_hmac_key: Whether to generate a new HMAC key
517
  @type force: bool
518
  @param force: Whether to ask user for confirmation
519

    
520
  """
521
  if new_rapi_cert and rapi_cert_filename:
522
    ToStderr("Only one of the --new-rapi-certficate and --rapi-certificate"
523
             " options can be specified at the same time.")
524
    return 1
525

    
526
  if rapi_cert_filename:
527
    # Read and verify new certificate
528
    try:
529
      rapi_cert_pem = utils.ReadFile(rapi_cert_filename)
530

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

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

    
545
  else:
546
    rapi_cert_pem = None
547

    
548
  if not force:
549
    usertext = ("This requires all daemons on all nodes to be restarted and"
550
                " may take some time. Continue?")
551
    if not AskUser(usertext):
552
      return 1
553

    
554
  def _RenewCryptoInner(ctx):
555
    ctx.feedback_fn("Updating certificates and keys")
556
    bootstrap.GenerateClusterCrypto(new_cluster_cert, new_rapi_cert,
557
                                    new_confd_hmac_key,
558
                                    rapi_cert_pem=rapi_cert_pem)
559

    
560
    files_to_copy = []
561

    
562
    if new_cluster_cert:
563
      files_to_copy.append(constants.NODED_CERT_FILE)
564

    
565
    if new_rapi_cert or rapi_cert_pem:
566
      files_to_copy.append(constants.RAPI_CERT_FILE)
567

    
568
    if new_confd_hmac_key:
569
      files_to_copy.append(constants.CONFD_HMAC_KEY)
570

    
571
    if files_to_copy:
572
      for node_name in ctx.nonmaster_nodes:
573
        ctx.feedback_fn("Copying %s to %s" %
574
                        (", ".join(files_to_copy), node_name))
575
        for file_name in files_to_copy:
576
          ctx.ssh.CopyFileToNode(node_name, file_name)
577

    
578
  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
579

    
580
  ToStdout("All requested certificates and keys have been replaced."
581
           " Running \"gnt-cluster verify\" now is recommended.")
582

    
583
  return 0
584

    
585

    
586
def RenewCrypto(opts, args):
587
  """Renews cluster certificates, keys and secrets.
588

    
589
  """
590
  return _RenewCrypto(opts.new_cluster_cert,
591
                      opts.new_rapi_cert,
592
                      opts.rapi_cert,
593
                      opts.new_confd_hmac_key,
594
                      opts.force)
595

    
596

    
597
def SetClusterParams(opts, args):
598
  """Modify the cluster.
599

    
600
  @param opts: the command line options selected by the user
601
  @type args: list
602
  @param args: should be an empty list
603
  @rtype: int
604
  @return: the desired exit code
605

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

    
618
  vg_name = opts.vg_name
619
  if not opts.lvm_storage and opts.vg_name:
620
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
621
    return 1
622

    
623
  if not opts.lvm_storage:
624
    vg_name = ""
625

    
626
  hvlist = opts.enabled_hypervisors
627
  if hvlist is not None:
628
    hvlist = hvlist.split(",")
629

    
630
  # a list of (name, dict) we can pass directly to dict() (or [])
631
  hvparams = dict(opts.hvparams)
632
  for hv_params in hvparams.values():
633
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
634

    
635
  beparams = opts.beparams
636
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
637

    
638
  nicparams = opts.nicparams
639
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
640

    
641

    
642
  mnh = opts.maintain_node_health
643

    
644
  uid_pool = opts.uid_pool
645
  if uid_pool is not None:
646
    uid_pool = uidpool.ParseUidPool(uid_pool)
647

    
648
  add_uids = opts.add_uids
649
  if add_uids is not None:
650
    add_uids = uidpool.ParseUidPool(add_uids)
651

    
652
  remove_uids = opts.remove_uids
653
  if remove_uids is not None:
654
    remove_uids = uidpool.ParseUidPool(remove_uids)
655

    
656
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
657
                                  enabled_hypervisors=hvlist,
658
                                  hvparams=hvparams,
659
                                  os_hvp=None,
660
                                  beparams=beparams,
661
                                  nicparams=nicparams,
662
                                  candidate_pool_size=opts.candidate_pool_size,
663
                                  maintain_node_health=mnh,
664
                                  uid_pool=uid_pool,
665
                                  add_uids=add_uids,
666
                                  remove_uids=remove_uids)
667
  SubmitOpCode(op, opts=opts)
668
  return 0
669

    
670

    
671
def QueueOps(opts, args):
672
  """Queue operations.
673

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

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

    
697
  return 0
698

    
699

    
700
def _ShowWatcherPause(until):
701
  if until is None or until < time.time():
702
    ToStdout("The watcher is not paused.")
703
  else:
704
    ToStdout("The watcher is paused until %s.", time.ctime(until))
705

    
706

    
707
def WatcherOps(opts, args):
708
  """Watcher operations.
709

    
710
  @param opts: the command line options selected by the user
711
  @type args: list
712
  @param args: should contain only one element, the subcommand
713
  @rtype: int
714
  @return: the desired exit code
715

    
716
  """
717
  command = args[0]
718
  client = GetClient()
719

    
720
  if command == "continue":
721
    client.SetWatcherPause(None)
722
    ToStdout("The watcher is no longer paused.")
723

    
724
  elif command == "pause":
725
    if len(args) < 2:
726
      raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
727

    
728
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
729
    _ShowWatcherPause(result)
730

    
731
  elif command == "info":
732
    result = client.QueryConfigValues(["watcher_pause"])
733
    _ShowWatcherPause(result[0])
734

    
735
  else:
736
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
737
                               errors.ECODE_INVAL)
738

    
739
  return 0
740

    
741

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

    
830

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