Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ bf4af505

History | View | Annotate | Download (27.7 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
                        default_iallocator=opts.default_iallocator,
125
                        )
126
  op = opcodes.OpPostInitCluster()
127
  SubmitOpCode(op, opts=opts)
128
  return 0
129

    
130

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

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

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

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

    
154

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

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

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

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

    
178

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

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

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

    
193

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

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

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

    
213

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

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

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

    
228

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

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

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

    
248

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
314
  return 0
315

    
316

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

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

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

    
333
  cl = GetClient()
334

    
335
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
336

    
337
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
338
                           secondary_ips=opts.use_replication_network)
339

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

    
345
  return 0
346

    
347

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

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

    
357
  """
358
  cl = GetClient()
359

    
360
  command = " ".join(args)
361

    
362
  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
363

    
364
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
365
                                                    "master_node"])
366

    
367
  srun = ssh.SshRunner(cluster_name=cluster_name)
368

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

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

    
381
  return 0
382

    
383

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

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

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

    
406

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

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

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

    
422
  bad_nodes, instances, missing = result
423

    
424
  retcode = constants.EXIT_SUCCESS
425

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

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

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

    
464
  return retcode
465

    
466

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

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

    
476
  """
477
  op = opcodes.OpRepairDiskSizes(instances=args)
478
  SubmitOpCode(op, opts=opts)
479

    
480

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

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

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

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

    
504
  return bootstrap.MasterFailover(no_voting=opts.no_voting)
505

    
506

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

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

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

    
526

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

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

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

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

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

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

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

    
578
  else:
579
    rapi_cert_pem = None
580

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

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

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

    
605
    files_to_copy = []
606

    
607
    if new_cluster_cert:
608
      files_to_copy.append(constants.NODED_CERT_FILE)
609

    
610
    if new_rapi_cert or rapi_cert_pem:
611
      files_to_copy.append(constants.RAPI_CERT_FILE)
612

    
613
    if new_confd_hmac_key:
614
      files_to_copy.append(constants.CONFD_HMAC_KEY)
615

    
616
    if new_cds or cds:
617
      files_to_copy.append(constants.CLUSTER_DOMAIN_SECRET_FILE)
618

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

    
626
  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
627

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

    
631
  return 0
632

    
633

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

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

    
646

    
647
def SetClusterParams(opts, args):
648
  """Modify the cluster.
649

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

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

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

    
675
  if not opts.lvm_storage:
676
    vg_name = ""
677

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

    
683
  if not opts.drbd_storage:
684
    drbd_helper = ""
685

    
686
  hvlist = opts.enabled_hypervisors
687
  if hvlist is not None:
688
    hvlist = hvlist.split(",")
689

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

    
695
  beparams = opts.beparams
696
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
697

    
698
  nicparams = opts.nicparams
699
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
700

    
701

    
702
  mnh = opts.maintain_node_health
703

    
704
  uid_pool = opts.uid_pool
705
  if uid_pool is not None:
706
    uid_pool = uidpool.ParseUidPool(uid_pool)
707

    
708
  add_uids = opts.add_uids
709
  if add_uids is not None:
710
    add_uids = uidpool.ParseUidPool(add_uids)
711

    
712
  remove_uids = opts.remove_uids
713
  if remove_uids is not None:
714
    remove_uids = uidpool.ParseUidPool(remove_uids)
715

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

    
732

    
733
def QueueOps(opts, args):
734
  """Queue operations.
735

    
736
  @param opts: the command line options selected by the user
737
  @type args: list
738
  @param args: should contain only one element, the subcommand
739
  @rtype: int
740
  @return: the desired exit code
741

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

    
759
  return 0
760

    
761

    
762
def _ShowWatcherPause(until):
763
  if until is None or until < time.time():
764
    ToStdout("The watcher is not paused.")
765
  else:
766
    ToStdout("The watcher is paused until %s.", time.ctime(until))
767

    
768

    
769
def WatcherOps(opts, args):
770
  """Watcher operations.
771

    
772
  @param opts: the command line options selected by the user
773
  @type args: list
774
  @param args: should contain only one element, the subcommand
775
  @rtype: int
776
  @return: the desired exit code
777

    
778
  """
779
  command = args[0]
780
  client = GetClient()
781

    
782
  if command == "continue":
783
    client.SetWatcherPause(None)
784
    ToStdout("The watcher is no longer paused.")
785

    
786
  elif command == "pause":
787
    if len(args) < 2:
788
      raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
789

    
790
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
791
    _ShowWatcherPause(result)
792

    
793
  elif command == "info":
794
    result = client.QueryConfigValues(["watcher_pause"])
795
    _ShowWatcherPause(result[0])
796

    
797
  else:
798
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
799
                               errors.ECODE_INVAL)
800

    
801
  return 0
802

    
803

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

    
895

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