Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ ed14ed48

History | View | Annotate | Download (27.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
from ganeti import compat
44

    
45

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

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

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

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

    
66
  if not opts.drbd_storage and opts.drbd_helper:
67
    ToStderr("Options --no-drbd-storage and --drbd-usermode-helper conflict.")
68
    return 1
69

    
70
  drbd_helper = opts.drbd_helper
71
  if opts.drbd_storage and not opts.drbd_helper:
72
    drbd_helper = constants.DEFAULT_DRBD_HELPER
73

    
74
  hvlist = opts.enabled_hypervisors
75
  if hvlist is None:
76
    hvlist = constants.DEFAULT_ENABLED_HYPERVISOR
77
  hvlist = hvlist.split(",")
78

    
79
  hvparams = dict(opts.hvparams)
80
  beparams = opts.beparams
81
  nicparams = opts.nicparams
82

    
83
  # prepare beparams dict
84
  beparams = objects.FillDict(constants.BEC_DEFAULTS, beparams)
85
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
86

    
87
  # prepare nicparams dict
88
  nicparams = objects.FillDict(constants.NICC_DEFAULTS, nicparams)
89
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
90

    
91
  # prepare hvparams dict
92
  for hv in constants.HYPER_TYPES:
93
    if hv not in hvparams:
94
      hvparams[hv] = {}
95
    hvparams[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], hvparams[hv])
96
    utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
97

    
98
  if opts.candidate_pool_size is None:
99
    opts.candidate_pool_size = constants.MASTER_POOL_SIZE_DEFAULT
100

    
101
  if opts.mac_prefix is None:
102
    opts.mac_prefix = constants.DEFAULT_MAC_PREFIX
103

    
104
  uid_pool = opts.uid_pool
105
  if uid_pool is not None:
106
    uid_pool = uidpool.ParseUidPool(uid_pool)
107

    
108
  bootstrap.InitCluster(cluster_name=args[0],
109
                        secondary_ip=opts.secondary_ip,
110
                        vg_name=vg_name,
111
                        mac_prefix=opts.mac_prefix,
112
                        master_netdev=opts.master_netdev,
113
                        file_storage_dir=opts.file_storage_dir,
114
                        enabled_hypervisors=hvlist,
115
                        hvparams=hvparams,
116
                        beparams=beparams,
117
                        nicparams=nicparams,
118
                        candidate_pool_size=opts.candidate_pool_size,
119
                        modify_etc_hosts=opts.modify_etc_hosts,
120
                        modify_ssh_setup=opts.modify_ssh_setup,
121
                        maintain_node_health=opts.maintain_node_health,
122
                        drbd_helper=drbd_helper,
123
                        uid_pool=uid_pool,
124
                        )
125
  op = opcodes.OpPostInitCluster()
126
  SubmitOpCode(op, opts=opts)
127
  return 0
128

    
129

    
130
@UsesRPC
131
def DestroyCluster(opts, args):
132
  """Destroy the cluster.
133

    
134
  @param opts: the command line options selected by the user
135
  @type args: list
136
  @param args: should be an empty list
137
  @rtype: int
138
  @return: the desired exit code
139

    
140
  """
141
  if not opts.yes_do_it:
142
    ToStderr("Destroying a cluster is irreversible. If you really want"
143
             " destroy this cluster, supply the --yes-do-it option.")
144
    return 1
145

    
146
  op = opcodes.OpDestroyCluster()
147
  master = SubmitOpCode(op, opts=opts)
148
  # if we reached this, the opcode didn't fail; we can proceed to
149
  # shutdown all the daemons
150
  bootstrap.FinalizeClusterDestroy(master)
151
  return 0
152

    
153

    
154
def RenameCluster(opts, args):
155
  """Rename the cluster.
156

    
157
  @param opts: the command line options selected by the user
158
  @type args: list
159
  @param args: should contain only one element, the new cluster name
160
  @rtype: int
161
  @return: the desired exit code
162

    
163
  """
164
  name = args[0]
165
  if not opts.force:
166
    usertext = ("This will rename the cluster to '%s'. If you are connected"
167
                " over the network to the cluster name, the operation is very"
168
                " dangerous as the IP address will be removed from the node"
169
                " and the change may not go through. Continue?") % name
170
    if not AskUser(usertext):
171
      return 1
172

    
173
  op = opcodes.OpRenameCluster(name=name)
174
  SubmitOpCode(op, opts=opts)
175
  return 0
176

    
177

    
178
def RedistributeConfig(opts, args):
179
  """Forces push of the cluster configuration.
180

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

    
187
  """
188
  op = opcodes.OpRedistributeConfig()
189
  SubmitOrSend(op, opts)
190
  return 0
191

    
192

    
193
def ShowClusterVersion(opts, args):
194
  """Write version of ganeti software to the standard output.
195

    
196
  @param opts: the command line options selected by the user
197
  @type args: list
198
  @param args: should be an empty list
199
  @rtype: int
200
  @return: the desired exit code
201

    
202
  """
203
  cl = GetClient()
204
  result = cl.QueryClusterInfo()
205
  ToStdout("Software version: %s", result["software_version"])
206
  ToStdout("Internode protocol: %s", result["protocol_version"])
207
  ToStdout("Configuration format: %s", result["config_version"])
208
  ToStdout("OS api version: %s", result["os_api_version"])
209
  ToStdout("Export interface: %s", result["export_version"])
210
  return 0
211

    
212

    
213
def ShowClusterMaster(opts, args):
214
  """Write name of master node to the standard output.
215

    
216
  @param opts: the command line options selected by the user
217
  @type args: list
218
  @param args: should be an empty list
219
  @rtype: int
220
  @return: the desired exit code
221

    
222
  """
223
  master = bootstrap.GetMaster()
224
  ToStdout(master)
225
  return 0
226

    
227

    
228
def _PrintGroupedParams(paramsdict, level=1, roman=False):
229
  """Print Grouped parameters (be, nic, disk) by group.
230

    
231
  @type paramsdict: dict of dicts
232
  @param paramsdict: {group: {param: value, ...}, ...}
233
  @type level: int
234
  @param level: Level of indention
235

    
236
  """
237
  indent = "  " * level
238
  for item, val in sorted(paramsdict.items()):
239
    if isinstance(val, dict):
240
      ToStdout("%s- %s:", indent, item)
241
      _PrintGroupedParams(val, level=level + 1, roman=roman)
242
    elif roman and isinstance(val, int):
243
      ToStdout("%s  %s: %s", indent, item, compat.TryToRoman(val))
244
    else:
245
      ToStdout("%s  %s: %s", indent, item, val)
246

    
247

    
248
def ShowClusterConfig(opts, args):
249
  """Shows cluster information.
250

    
251
  @param opts: the command line options selected by the user
252
  @type args: list
253
  @param args: should be an empty list
254
  @rtype: int
255
  @return: the desired exit code
256

    
257
  """
258
  cl = GetClient()
259
  result = cl.QueryClusterInfo()
260

    
261
  ToStdout("Cluster name: %s", result["name"])
262
  ToStdout("Cluster UUID: %s", result["uuid"])
263

    
264
  ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
265
  ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))
266

    
267
  ToStdout("Master node: %s", result["master"])
268

    
269
  ToStdout("Architecture (this node): %s (%s)",
270
           result["architecture"][0], result["architecture"][1])
271

    
272
  if result["tags"]:
273
    tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
274
  else:
275
    tags = "(none)"
276

    
277
  ToStdout("Tags: %s", tags)
278

    
279
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
280
  ToStdout("Enabled hypervisors: %s",
281
           utils.CommaJoin(result["enabled_hypervisors"]))
282

    
283
  ToStdout("Hypervisor parameters:")
284
  _PrintGroupedParams(result["hvparams"])
285

    
286
  ToStdout("OS-specific hypervisor parameters:")
287
  _PrintGroupedParams(result["os_hvp"])
288

    
289
  ToStdout("OS parameters:")
290
  _PrintGroupedParams(result["osparams"])
291

    
292
  ToStdout("Cluster parameters:")
293
  ToStdout("  - candidate pool size: %s",
294
            compat.TryToRoman(result["candidate_pool_size"],
295
                              convert=opts.roman_integers))
296
  ToStdout("  - master netdev: %s", result["master_netdev"])
297
  ToStdout("  - lvm volume group: %s", result["volume_group_name"])
298
  ToStdout("  - drbd usermode helper: %s", result["drbd_usermode_helper"])
299
  ToStdout("  - file storage path: %s", result["file_storage_dir"])
300
  ToStdout("  - maintenance of node health: %s",
301
           result["maintain_node_health"])
302
  ToStdout("  - uid pool: %s",
303
            uidpool.FormatUidPool(result["uid_pool"],
304
                                  roman=opts.roman_integers))
305

    
306
  ToStdout("Default instance parameters:")
307
  _PrintGroupedParams(result["beparams"], roman=opts.roman_integers)
308

    
309
  ToStdout("Default nic parameters:")
310
  _PrintGroupedParams(result["nicparams"], roman=opts.roman_integers)
311

    
312
  return 0
313

    
314

    
315
def ClusterCopyFile(opts, args):
316
  """Copy a file from master to some nodes.
317

    
318
  @param opts: the command line options selected by the user
319
  @type args: list
320
  @param args: should contain only one element, the path of
321
      the file to be copied
322
  @rtype: int
323
  @return: the desired exit code
324

    
325
  """
326
  filename = args[0]
327
  if not os.path.exists(filename):
328
    raise errors.OpPrereqError("No such filename '%s'" % filename,
329
                               errors.ECODE_INVAL)
330

    
331
  cl = GetClient()
332

    
333
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
334

    
335
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
336
                           secondary_ips=opts.use_replication_network)
337

    
338
  srun = ssh.SshRunner(cluster_name=cluster_name)
339
  for node in results:
340
    if not srun.CopyFileToNode(node, filename):
341
      ToStderr("Copy of file %s to node %s failed", filename, node)
342

    
343
  return 0
344

    
345

    
346
def RunClusterCommand(opts, args):
347
  """Run a command on some nodes.
348

    
349
  @param opts: the command line options selected by the user
350
  @type args: list
351
  @param args: should contain the command to be run and its arguments
352
  @rtype: int
353
  @return: the desired exit code
354

    
355
  """
356
  cl = GetClient()
357

    
358
  command = " ".join(args)
359

    
360
  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
361

    
362
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
363
                                                    "master_node"])
364

    
365
  srun = ssh.SshRunner(cluster_name=cluster_name)
366

    
367
  # Make sure master node is at list end
368
  if master_node in nodes:
369
    nodes.remove(master_node)
370
    nodes.append(master_node)
371

    
372
  for name in nodes:
373
    result = srun.Run(name, "root", command)
374
    ToStdout("------------------------------------------------")
375
    ToStdout("node: %s", name)
376
    ToStdout("%s", result.output)
377
    ToStdout("return code = %s", result.exit_code)
378

    
379
  return 0
380

    
381

    
382
def VerifyCluster(opts, args):
383
  """Verify integrity of cluster, performing various test on nodes.
384

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

    
391
  """
392
  skip_checks = []
393
  if opts.skip_nplusone_mem:
394
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
395
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
396
                               verbose=opts.verbose,
397
                               error_codes=opts.error_codes,
398
                               debug_simulate_errors=opts.simulate_errors)
399
  if SubmitOpCode(op, opts=opts):
400
    return 0
401
  else:
402
    return 1
403

    
404

    
405
def VerifyDisks(opts, args):
406
  """Verify integrity of cluster disks.
407

    
408
  @param opts: the command line options selected by the user
409
  @type args: list
410
  @param args: should be an empty list
411
  @rtype: int
412
  @return: the desired exit code
413

    
414
  """
415
  op = opcodes.OpVerifyDisks()
416
  result = SubmitOpCode(op, opts=opts)
417
  if not isinstance(result, (list, tuple)) or len(result) != 3:
418
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
419

    
420
  bad_nodes, instances, missing = result
421

    
422
  retcode = constants.EXIT_SUCCESS
423

    
424
  if bad_nodes:
425
    for node, text in bad_nodes.items():
426
      ToStdout("Error gathering data on node %s: %s",
427
               node, utils.SafeEncode(text[-400:]))
428
      retcode |= 1
429
      ToStdout("You need to fix these nodes first before fixing instances")
430

    
431
  if instances:
432
    for iname in instances:
433
      if iname in missing:
434
        continue
435
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
436
      try:
437
        ToStdout("Activating disks for instance '%s'", iname)
438
        SubmitOpCode(op, opts=opts)
439
      except errors.GenericError, err:
440
        nret, msg = FormatError(err)
441
        retcode |= nret
442
        ToStderr("Error activating disks for instance %s: %s", iname, msg)
443

    
444
  if missing:
445
    for iname, ival in missing.iteritems():
446
      all_missing = compat.all(x[0] in bad_nodes for x in ival)
447
      if all_missing:
448
        ToStdout("Instance %s cannot be verified as it lives on"
449
                 " broken nodes", iname)
450
      else:
451
        ToStdout("Instance %s has missing logical volumes:", iname)
452
        ival.sort()
453
        for node, vol in ival:
454
          if node in bad_nodes:
455
            ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
456
          else:
457
            ToStdout("\t%s /dev/xenvg/%s", node, vol)
458
    ToStdout("You need to run replace_disks for all the above"
459
           " instances, if this message persist after fixing nodes.")
460
    retcode |= 1
461

    
462
  return retcode
463

    
464

    
465
def RepairDiskSizes(opts, args):
466
  """Verify sizes of cluster disks.
467

    
468
  @param opts: the command line options selected by the user
469
  @type args: list
470
  @param args: optional list of instances to restrict check to
471
  @rtype: int
472
  @return: the desired exit code
473

    
474
  """
475
  op = opcodes.OpRepairDiskSizes(instances=args)
476
  SubmitOpCode(op, opts=opts)
477

    
478

    
479
@UsesRPC
480
def MasterFailover(opts, args):
481
  """Failover the master node.
482

    
483
  This command, when run on a non-master node, will cause the current
484
  master to cease being master, and the non-master to become new
485
  master.
486

    
487
  @param opts: the command line options selected by the user
488
  @type args: list
489
  @param args: should be an empty list
490
  @rtype: int
491
  @return: the desired exit code
492

    
493
  """
494
  if opts.no_voting:
495
    usertext = ("This will perform the failover even if most other nodes"
496
                " are down, or if this node is outdated. This is dangerous"
497
                " as it can lead to a non-consistent cluster. Check the"
498
                " gnt-cluster(8) man page before proceeding. Continue?")
499
    if not AskUser(usertext):
500
      return 1
501

    
502
  return bootstrap.MasterFailover(no_voting=opts.no_voting)
503

    
504

    
505
def SearchTags(opts, args):
506
  """Searches the tags on all the cluster.
507

    
508
  @param opts: the command line options selected by the user
509
  @type args: list
510
  @param args: should contain only one element, the tag pattern
511
  @rtype: int
512
  @return: the desired exit code
513

    
514
  """
515
  op = opcodes.OpSearchTags(pattern=args[0])
516
  result = SubmitOpCode(op, opts=opts)
517
  if not result:
518
    return 1
519
  result = list(result)
520
  result.sort()
521
  for path, tag in result:
522
    ToStdout("%s %s", path, tag)
523

    
524

    
525
def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
526
                 new_confd_hmac_key, new_cds, cds_filename,
527
                 force):
528
  """Renews cluster certificates, keys and secrets.
529

    
530
  @type new_cluster_cert: bool
531
  @param new_cluster_cert: Whether to generate a new cluster certificate
532
  @type new_rapi_cert: bool
533
  @param new_rapi_cert: Whether to generate a new RAPI certificate
534
  @type rapi_cert_filename: string
535
  @param rapi_cert_filename: Path to file containing new RAPI certificate
536
  @type new_confd_hmac_key: bool
537
  @param new_confd_hmac_key: Whether to generate a new HMAC key
538
  @type new_cds: bool
539
  @param new_cds: Whether to generate a new cluster domain secret
540
  @type cds_filename: string
541
  @param cds_filename: Path to file containing new cluster domain secret
542
  @type force: bool
543
  @param force: Whether to ask user for confirmation
544

    
545
  """
546
  if new_rapi_cert and rapi_cert_filename:
547
    ToStderr("Only one of the --new-rapi-certficate and --rapi-certificate"
548
             " options can be specified at the same time.")
549
    return 1
550

    
551
  if new_cds and cds_filename:
552
    ToStderr("Only one of the --new-cluster-domain-secret and"
553
             " --cluster-domain-secret options can be specified at"
554
             " the same time.")
555
    return 1
556

    
557
  if rapi_cert_filename:
558
    # Read and verify new certificate
559
    try:
560
      rapi_cert_pem = utils.ReadFile(rapi_cert_filename)
561

    
562
      OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
563
                                      rapi_cert_pem)
564
    except Exception, err: # pylint: disable-msg=W0703
565
      ToStderr("Can't load new RAPI certificate from %s: %s" %
566
               (rapi_cert_filename, str(err)))
567
      return 1
568

    
569
    try:
570
      OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, rapi_cert_pem)
571
    except Exception, err: # pylint: disable-msg=W0703
572
      ToStderr("Can't load new RAPI private key from %s: %s" %
573
               (rapi_cert_filename, str(err)))
574
      return 1
575

    
576
  else:
577
    rapi_cert_pem = None
578

    
579
  if cds_filename:
580
    try:
581
      cds = utils.ReadFile(cds_filename)
582
    except Exception, err: # pylint: disable-msg=W0703
583
      ToStderr("Can't load new cluster domain secret from %s: %s" %
584
               (cds_filename, str(err)))
585
      return 1
586
  else:
587
    cds = None
588

    
589
  if not force:
590
    usertext = ("This requires all daemons on all nodes to be restarted and"
591
                " may take some time. Continue?")
592
    if not AskUser(usertext):
593
      return 1
594

    
595
  def _RenewCryptoInner(ctx):
596
    ctx.feedback_fn("Updating certificates and keys")
597
    bootstrap.GenerateClusterCrypto(new_cluster_cert, new_rapi_cert,
598
                                    new_confd_hmac_key,
599
                                    new_cds,
600
                                    rapi_cert_pem=rapi_cert_pem,
601
                                    cds=cds)
602

    
603
    files_to_copy = []
604

    
605
    if new_cluster_cert:
606
      files_to_copy.append(constants.NODED_CERT_FILE)
607

    
608
    if new_rapi_cert or rapi_cert_pem:
609
      files_to_copy.append(constants.RAPI_CERT_FILE)
610

    
611
    if new_confd_hmac_key:
612
      files_to_copy.append(constants.CONFD_HMAC_KEY)
613

    
614
    if new_cds or cds:
615
      files_to_copy.append(constants.CLUSTER_DOMAIN_SECRET_FILE)
616

    
617
    if files_to_copy:
618
      for node_name in ctx.nonmaster_nodes:
619
        ctx.feedback_fn("Copying %s to %s" %
620
                        (", ".join(files_to_copy), node_name))
621
        for file_name in files_to_copy:
622
          ctx.ssh.CopyFileToNode(node_name, file_name)
623

    
624
  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
625

    
626
  ToStdout("All requested certificates and keys have been replaced."
627
           " Running \"gnt-cluster verify\" now is recommended.")
628

    
629
  return 0
630

    
631

    
632
def RenewCrypto(opts, args):
633
  """Renews cluster certificates, keys and secrets.
634

    
635
  """
636
  return _RenewCrypto(opts.new_cluster_cert,
637
                      opts.new_rapi_cert,
638
                      opts.rapi_cert,
639
                      opts.new_confd_hmac_key,
640
                      opts.new_cluster_domain_secret,
641
                      opts.cluster_domain_secret,
642
                      opts.force)
643

    
644

    
645
def SetClusterParams(opts, args):
646
  """Modify the cluster.
647

    
648
  @param opts: the command line options selected by the user
649
  @type args: list
650
  @param args: should be an empty list
651
  @rtype: int
652
  @return: the desired exit code
653

    
654
  """
655
  if not (not opts.lvm_storage or opts.vg_name or
656
          not opts.drbd_storage or opts.drbd_helper or
657
          opts.enabled_hypervisors or opts.hvparams or
658
          opts.beparams or opts.nicparams or
659
          opts.candidate_pool_size is not None or
660
          opts.uid_pool is not None or
661
          opts.maintain_node_health is not None or
662
          opts.add_uids is not None or
663
          opts.remove_uids is not None):
664
    ToStderr("Please give at least one of the parameters.")
665
    return 1
666

    
667
  vg_name = opts.vg_name
668
  if not opts.lvm_storage and opts.vg_name:
669
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
670
    return 1
671

    
672
  if not opts.lvm_storage:
673
    vg_name = ""
674

    
675
  drbd_helper = opts.drbd_helper
676
  if not opts.drbd_storage and opts.drbd_helper:
677
    ToStderr("Options --no-drbd-storage and --drbd-usermode-helper conflict.")
678
    return 1
679

    
680
  if not opts.drbd_storage:
681
    drbd_helper = ""
682

    
683
  hvlist = opts.enabled_hypervisors
684
  if hvlist is not None:
685
    hvlist = hvlist.split(",")
686

    
687
  # a list of (name, dict) we can pass directly to dict() (or [])
688
  hvparams = dict(opts.hvparams)
689
  for hv_params in hvparams.values():
690
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
691

    
692
  beparams = opts.beparams
693
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
694

    
695
  nicparams = opts.nicparams
696
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
697

    
698

    
699
  mnh = opts.maintain_node_health
700

    
701
  uid_pool = opts.uid_pool
702
  if uid_pool is not None:
703
    uid_pool = uidpool.ParseUidPool(uid_pool)
704

    
705
  add_uids = opts.add_uids
706
  if add_uids is not None:
707
    add_uids = uidpool.ParseUidPool(add_uids)
708

    
709
  remove_uids = opts.remove_uids
710
  if remove_uids is not None:
711
    remove_uids = uidpool.ParseUidPool(remove_uids)
712

    
713
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
714
                                  drbd_helper=drbd_helper,
715
                                  enabled_hypervisors=hvlist,
716
                                  hvparams=hvparams,
717
                                  os_hvp=None,
718
                                  beparams=beparams,
719
                                  nicparams=nicparams,
720
                                  candidate_pool_size=opts.candidate_pool_size,
721
                                  maintain_node_health=mnh,
722
                                  uid_pool=uid_pool,
723
                                  add_uids=add_uids,
724
                                  remove_uids=remove_uids)
725
  SubmitOpCode(op, opts=opts)
726
  return 0
727

    
728

    
729
def QueueOps(opts, args):
730
  """Queue operations.
731

    
732
  @param opts: the command line options selected by the user
733
  @type args: list
734
  @param args: should contain only one element, the subcommand
735
  @rtype: int
736
  @return: the desired exit code
737

    
738
  """
739
  command = args[0]
740
  client = GetClient()
741
  if command in ("drain", "undrain"):
742
    drain_flag = command == "drain"
743
    client.SetQueueDrainFlag(drain_flag)
744
  elif command == "info":
745
    result = client.QueryConfigValues(["drain_flag"])
746
    if result[0]:
747
      val = "set"
748
    else:
749
      val = "unset"
750
    ToStdout("The drain flag is %s" % val)
751
  else:
752
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
753
                               errors.ECODE_INVAL)
754

    
755
  return 0
756

    
757

    
758
def _ShowWatcherPause(until):
759
  if until is None or until < time.time():
760
    ToStdout("The watcher is not paused.")
761
  else:
762
    ToStdout("The watcher is paused until %s.", time.ctime(until))
763

    
764

    
765
def WatcherOps(opts, args):
766
  """Watcher operations.
767

    
768
  @param opts: the command line options selected by the user
769
  @type args: list
770
  @param args: should contain only one element, the subcommand
771
  @rtype: int
772
  @return: the desired exit code
773

    
774
  """
775
  command = args[0]
776
  client = GetClient()
777

    
778
  if command == "continue":
779
    client.SetWatcherPause(None)
780
    ToStdout("The watcher is no longer paused.")
781

    
782
  elif command == "pause":
783
    if len(args) < 2:
784
      raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
785

    
786
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
787
    _ShowWatcherPause(result)
788

    
789
  elif command == "info":
790
    result = client.QueryConfigValues(["watcher_pause"])
791
    _ShowWatcherPause(result[0])
792

    
793
  else:
794
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
795
                               errors.ECODE_INVAL)
796

    
797
  return 0
798

    
799

    
800
commands = {
801
  'init': (
802
    InitCluster, [ArgHost(min=1, max=1)],
803
    [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, GLOBAL_FILEDIR_OPT,
804
     HVLIST_OPT, MAC_PREFIX_OPT, MASTER_NETDEV_OPT, NIC_PARAMS_OPT,
805
     NOLVM_STORAGE_OPT, NOMODIFY_ETCHOSTS_OPT, NOMODIFY_SSH_SETUP_OPT,
806
     SECONDARY_IP_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT,
807
     UIDPOOL_OPT, DRBD_HELPER_OPT, NODRBD_STORAGE_OPT],
808
    "[opts...] <cluster_name>", "Initialises a new cluster configuration"),
809
  'destroy': (
810
    DestroyCluster, ARGS_NONE, [YES_DOIT_OPT],
811
    "", "Destroy cluster"),
812
  'rename': (
813
    RenameCluster, [ArgHost(min=1, max=1)],
814
    [FORCE_OPT],
815
    "<new_name>",
816
    "Renames the cluster"),
817
  'redist-conf': (
818
    RedistributeConfig, ARGS_NONE, [SUBMIT_OPT],
819
    "", "Forces a push of the configuration file and ssconf files"
820
    " to the nodes in the cluster"),
821
  'verify': (
822
    VerifyCluster, ARGS_NONE,
823
    [VERBOSE_OPT, DEBUG_SIMERR_OPT, ERROR_CODES_OPT, NONPLUS1_OPT],
824
    "", "Does a check on the cluster configuration"),
825
  'verify-disks': (
826
    VerifyDisks, ARGS_NONE, [],
827
    "", "Does a check on the cluster disk status"),
828
  'repair-disk-sizes': (
829
    RepairDiskSizes, ARGS_MANY_INSTANCES, [],
830
    "", "Updates mismatches in recorded disk sizes"),
831
  'masterfailover': (
832
    MasterFailover, ARGS_NONE, [NOVOTING_OPT],
833
    "", "Makes the current node the master"),
834
  'version': (
835
    ShowClusterVersion, ARGS_NONE, [],
836
    "", "Shows the cluster version"),
837
  'getmaster': (
838
    ShowClusterMaster, ARGS_NONE, [],
839
    "", "Shows the cluster master"),
840
  'copyfile': (
841
    ClusterCopyFile, [ArgFile(min=1, max=1)],
842
    [NODE_LIST_OPT, USE_REPL_NET_OPT],
843
    "[-n node...] <filename>", "Copies a file to all (or only some) nodes"),
844
  'command': (
845
    RunClusterCommand, [ArgCommand(min=1)],
846
    [NODE_LIST_OPT],
847
    "[-n node...] <command>", "Runs a command on all (or only some) nodes"),
848
  'info': (
849
    ShowClusterConfig, ARGS_NONE, [ROMAN_OPT],
850
    "[--roman]", "Show cluster configuration"),
851
  'list-tags': (
852
    ListTags, ARGS_NONE, [], "", "List the tags of the cluster"),
853
  'add-tags': (
854
    AddTags, [ArgUnknown()], [TAG_SRC_OPT],
855
    "tag...", "Add tags to the cluster"),
856
  'remove-tags': (
857
    RemoveTags, [ArgUnknown()], [TAG_SRC_OPT],
858
    "tag...", "Remove tags from the cluster"),
859
  'search-tags': (
860
    SearchTags, [ArgUnknown(min=1, max=1)],
861
    [], "", "Searches the tags on all objects on"
862
    " the cluster for a given pattern (regex)"),
863
  'queue': (
864
    QueueOps,
865
    [ArgChoice(min=1, max=1, choices=["drain", "undrain", "info"])],
866
    [], "drain|undrain|info", "Change queue properties"),
867
  'watcher': (
868
    WatcherOps,
869
    [ArgChoice(min=1, max=1, choices=["pause", "continue", "info"]),
870
     ArgSuggest(min=0, max=1, choices=["30m", "1h", "4h"])],
871
    [],
872
    "{pause <timespec>|continue|info}", "Change watcher properties"),
873
  'modify': (
874
    SetClusterParams, ARGS_NONE,
875
    [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, HVLIST_OPT,
876
     NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT,
877
     UIDPOOL_OPT, ADD_UIDS_OPT, REMOVE_UIDS_OPT, DRBD_HELPER_OPT,
878
     NODRBD_STORAGE_OPT],
879
    "[opts...]",
880
    "Alters the parameters of the cluster"),
881
  "renew-crypto": (
882
    RenewCrypto, ARGS_NONE,
883
    [NEW_CLUSTER_CERT_OPT, NEW_RAPI_CERT_OPT, RAPI_CERT_OPT,
884
     NEW_CONFD_HMAC_KEY_OPT, FORCE_OPT,
885
     NEW_CLUSTER_DOMAIN_SECRET_OPT, CLUSTER_DOMAIN_SECRET_OPT],
886
    "[opts...]",
887
    "Renews cluster certificates, keys and secrets"),
888
  }
889

    
890

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