Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ f38ea602

History | View | Annotate | Download (28.2 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
  if result["reserved_lvs"]:
300
    reserved_lvs = utils.CommaJoin(result["reserved_lvs"])
301
  else:
302
    reserved_lvs = "(none)"
303
  ToStdout("  - lvm reserved volumes: %s", reserved_lvs)
304
  ToStdout("  - drbd usermode helper: %s", result["drbd_usermode_helper"])
305
  ToStdout("  - file storage path: %s", result["file_storage_dir"])
306
  ToStdout("  - maintenance of node health: %s",
307
           result["maintain_node_health"])
308
  ToStdout("  - uid pool: %s",
309
            uidpool.FormatUidPool(result["uid_pool"],
310
                                  roman=opts.roman_integers))
311
  ToStdout("  - default instance allocator: %s", result["default_iallocator"])
312

    
313
  ToStdout("Default instance parameters:")
314
  _PrintGroupedParams(result["beparams"], roman=opts.roman_integers)
315

    
316
  ToStdout("Default nic parameters:")
317
  _PrintGroupedParams(result["nicparams"], roman=opts.roman_integers)
318

    
319
  return 0
320

    
321

    
322
def ClusterCopyFile(opts, args):
323
  """Copy a file from master to some nodes.
324

    
325
  @param opts: the command line options selected by the user
326
  @type args: list
327
  @param args: should contain only one element, the path of
328
      the file to be copied
329
  @rtype: int
330
  @return: the desired exit code
331

    
332
  """
333
  filename = args[0]
334
  if not os.path.exists(filename):
335
    raise errors.OpPrereqError("No such filename '%s'" % filename,
336
                               errors.ECODE_INVAL)
337

    
338
  cl = GetClient()
339

    
340
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
341

    
342
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
343
                           secondary_ips=opts.use_replication_network)
344

    
345
  srun = ssh.SshRunner(cluster_name=cluster_name)
346
  for node in results:
347
    if not srun.CopyFileToNode(node, filename):
348
      ToStderr("Copy of file %s to node %s failed", filename, node)
349

    
350
  return 0
351

    
352

    
353
def RunClusterCommand(opts, args):
354
  """Run a command on some nodes.
355

    
356
  @param opts: the command line options selected by the user
357
  @type args: list
358
  @param args: should contain the command to be run and its arguments
359
  @rtype: int
360
  @return: the desired exit code
361

    
362
  """
363
  cl = GetClient()
364

    
365
  command = " ".join(args)
366

    
367
  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
368

    
369
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
370
                                                    "master_node"])
371

    
372
  srun = ssh.SshRunner(cluster_name=cluster_name)
373

    
374
  # Make sure master node is at list end
375
  if master_node in nodes:
376
    nodes.remove(master_node)
377
    nodes.append(master_node)
378

    
379
  for name in nodes:
380
    result = srun.Run(name, "root", command)
381
    ToStdout("------------------------------------------------")
382
    ToStdout("node: %s", name)
383
    ToStdout("%s", result.output)
384
    ToStdout("return code = %s", result.exit_code)
385

    
386
  return 0
387

    
388

    
389
def VerifyCluster(opts, args):
390
  """Verify integrity of cluster, performing various test on nodes.
391

    
392
  @param opts: the command line options selected by the user
393
  @type args: list
394
  @param args: should be an empty list
395
  @rtype: int
396
  @return: the desired exit code
397

    
398
  """
399
  skip_checks = []
400
  if opts.skip_nplusone_mem:
401
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
402
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
403
                               verbose=opts.verbose,
404
                               error_codes=opts.error_codes,
405
                               debug_simulate_errors=opts.simulate_errors)
406
  if SubmitOpCode(op, opts=opts):
407
    return 0
408
  else:
409
    return 1
410

    
411

    
412
def VerifyDisks(opts, args):
413
  """Verify integrity of cluster disks.
414

    
415
  @param opts: the command line options selected by the user
416
  @type args: list
417
  @param args: should be an empty list
418
  @rtype: int
419
  @return: the desired exit code
420

    
421
  """
422
  op = opcodes.OpVerifyDisks()
423
  result = SubmitOpCode(op, opts=opts)
424
  if not isinstance(result, (list, tuple)) or len(result) != 3:
425
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
426

    
427
  bad_nodes, instances, missing = result
428

    
429
  retcode = constants.EXIT_SUCCESS
430

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

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

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

    
469
  return retcode
470

    
471

    
472
def RepairDiskSizes(opts, args):
473
  """Verify sizes of cluster disks.
474

    
475
  @param opts: the command line options selected by the user
476
  @type args: list
477
  @param args: optional list of instances to restrict check to
478
  @rtype: int
479
  @return: the desired exit code
480

    
481
  """
482
  op = opcodes.OpRepairDiskSizes(instances=args)
483
  SubmitOpCode(op, opts=opts)
484

    
485

    
486
@UsesRPC
487
def MasterFailover(opts, args):
488
  """Failover the master node.
489

    
490
  This command, when run on a non-master node, will cause the current
491
  master to cease being master, and the non-master to become new
492
  master.
493

    
494
  @param opts: the command line options selected by the user
495
  @type args: list
496
  @param args: should be an empty list
497
  @rtype: int
498
  @return: the desired exit code
499

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

    
509
  return bootstrap.MasterFailover(no_voting=opts.no_voting)
510

    
511

    
512
def SearchTags(opts, args):
513
  """Searches the tags on all the cluster.
514

    
515
  @param opts: the command line options selected by the user
516
  @type args: list
517
  @param args: should contain only one element, the tag pattern
518
  @rtype: int
519
  @return: the desired exit code
520

    
521
  """
522
  op = opcodes.OpSearchTags(pattern=args[0])
523
  result = SubmitOpCode(op, opts=opts)
524
  if not result:
525
    return 1
526
  result = list(result)
527
  result.sort()
528
  for path, tag in result:
529
    ToStdout("%s %s", path, tag)
530

    
531

    
532
def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
533
                 new_confd_hmac_key, new_cds, cds_filename,
534
                 force):
535
  """Renews cluster certificates, keys and secrets.
536

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

    
552
  """
553
  if new_rapi_cert and rapi_cert_filename:
554
    ToStderr("Only one of the --new-rapi-certficate and --rapi-certificate"
555
             " options can be specified at the same time.")
556
    return 1
557

    
558
  if new_cds and cds_filename:
559
    ToStderr("Only one of the --new-cluster-domain-secret and"
560
             " --cluster-domain-secret options can be specified at"
561
             " the same time.")
562
    return 1
563

    
564
  if rapi_cert_filename:
565
    # Read and verify new certificate
566
    try:
567
      rapi_cert_pem = utils.ReadFile(rapi_cert_filename)
568

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

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

    
583
  else:
584
    rapi_cert_pem = None
585

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

    
596
  if not force:
597
    usertext = ("This requires all daemons on all nodes to be restarted and"
598
                " may take some time. Continue?")
599
    if not AskUser(usertext):
600
      return 1
601

    
602
  def _RenewCryptoInner(ctx):
603
    ctx.feedback_fn("Updating certificates and keys")
604
    bootstrap.GenerateClusterCrypto(new_cluster_cert, new_rapi_cert,
605
                                    new_confd_hmac_key,
606
                                    new_cds,
607
                                    rapi_cert_pem=rapi_cert_pem,
608
                                    cds=cds)
609

    
610
    files_to_copy = []
611

    
612
    if new_cluster_cert:
613
      files_to_copy.append(constants.NODED_CERT_FILE)
614

    
615
    if new_rapi_cert or rapi_cert_pem:
616
      files_to_copy.append(constants.RAPI_CERT_FILE)
617

    
618
    if new_confd_hmac_key:
619
      files_to_copy.append(constants.CONFD_HMAC_KEY)
620

    
621
    if new_cds or cds:
622
      files_to_copy.append(constants.CLUSTER_DOMAIN_SECRET_FILE)
623

    
624
    if files_to_copy:
625
      for node_name in ctx.nonmaster_nodes:
626
        ctx.feedback_fn("Copying %s to %s" %
627
                        (", ".join(files_to_copy), node_name))
628
        for file_name in files_to_copy:
629
          ctx.ssh.CopyFileToNode(node_name, file_name)
630

    
631
  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
632

    
633
  ToStdout("All requested certificates and keys have been replaced."
634
           " Running \"gnt-cluster verify\" now is recommended.")
635

    
636
  return 0
637

    
638

    
639
def RenewCrypto(opts, args):
640
  """Renews cluster certificates, keys and secrets.
641

    
642
  """
643
  return _RenewCrypto(opts.new_cluster_cert,
644
                      opts.new_rapi_cert,
645
                      opts.rapi_cert,
646
                      opts.new_confd_hmac_key,
647
                      opts.new_cluster_domain_secret,
648
                      opts.cluster_domain_secret,
649
                      opts.force)
650

    
651

    
652
def SetClusterParams(opts, args):
653
  """Modify the cluster.
654

    
655
  @param opts: the command line options selected by the user
656
  @type args: list
657
  @param args: should be an empty list
658
  @rtype: int
659
  @return: the desired exit code
660

    
661
  """
662
  if not (not opts.lvm_storage or opts.vg_name or
663
          not opts.drbd_storage or opts.drbd_helper or
664
          opts.enabled_hypervisors or opts.hvparams or
665
          opts.beparams or opts.nicparams or
666
          opts.candidate_pool_size is not None or
667
          opts.uid_pool is not None or
668
          opts.maintain_node_health is not None or
669
          opts.add_uids is not None or
670
          opts.remove_uids is not None or
671
          opts.default_iallocator is not None or
672
          opts.reserved_lvs is not None):
673
    ToStderr("Please give at least one of the parameters.")
674
    return 1
675

    
676
  vg_name = opts.vg_name
677
  if not opts.lvm_storage and opts.vg_name:
678
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
679
    return 1
680

    
681
  if not opts.lvm_storage:
682
    vg_name = ""
683

    
684
  drbd_helper = opts.drbd_helper
685
  if not opts.drbd_storage and opts.drbd_helper:
686
    ToStderr("Options --no-drbd-storage and --drbd-usermode-helper conflict.")
687
    return 1
688

    
689
  if not opts.drbd_storage:
690
    drbd_helper = ""
691

    
692
  hvlist = opts.enabled_hypervisors
693
  if hvlist is not None:
694
    hvlist = hvlist.split(",")
695

    
696
  # a list of (name, dict) we can pass directly to dict() (or [])
697
  hvparams = dict(opts.hvparams)
698
  for hv_params in hvparams.values():
699
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
700

    
701
  beparams = opts.beparams
702
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
703

    
704
  nicparams = opts.nicparams
705
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
706

    
707

    
708
  mnh = opts.maintain_node_health
709

    
710
  uid_pool = opts.uid_pool
711
  if uid_pool is not None:
712
    uid_pool = uidpool.ParseUidPool(uid_pool)
713

    
714
  add_uids = opts.add_uids
715
  if add_uids is not None:
716
    add_uids = uidpool.ParseUidPool(add_uids)
717

    
718
  remove_uids = opts.remove_uids
719
  if remove_uids is not None:
720
    remove_uids = uidpool.ParseUidPool(remove_uids)
721

    
722
  if opts.reserved_lvs is not None:
723
    if opts.reserved_lvs == "":
724
      opts.reserved_lvs = []
725
    else:
726
      opts.reserved_lvs = utils.UnescapeAndSplit(opts.reserved_lvs, sep=",")
727

    
728
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
729
                                  drbd_helper=drbd_helper,
730
                                  enabled_hypervisors=hvlist,
731
                                  hvparams=hvparams,
732
                                  os_hvp=None,
733
                                  beparams=beparams,
734
                                  nicparams=nicparams,
735
                                  candidate_pool_size=opts.candidate_pool_size,
736
                                  maintain_node_health=mnh,
737
                                  uid_pool=uid_pool,
738
                                  add_uids=add_uids,
739
                                  remove_uids=remove_uids,
740
                                  default_iallocator=opts.default_iallocator,
741
                                  reserved_lvs=opts.reserved_lvs)
742
  SubmitOpCode(op, opts=opts)
743
  return 0
744

    
745

    
746
def QueueOps(opts, args):
747
  """Queue operations.
748

    
749
  @param opts: the command line options selected by the user
750
  @type args: list
751
  @param args: should contain only one element, the subcommand
752
  @rtype: int
753
  @return: the desired exit code
754

    
755
  """
756
  command = args[0]
757
  client = GetClient()
758
  if command in ("drain", "undrain"):
759
    drain_flag = command == "drain"
760
    client.SetQueueDrainFlag(drain_flag)
761
  elif command == "info":
762
    result = client.QueryConfigValues(["drain_flag"])
763
    if result[0]:
764
      val = "set"
765
    else:
766
      val = "unset"
767
    ToStdout("The drain flag is %s" % val)
768
  else:
769
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
770
                               errors.ECODE_INVAL)
771

    
772
  return 0
773

    
774

    
775
def _ShowWatcherPause(until):
776
  if until is None or until < time.time():
777
    ToStdout("The watcher is not paused.")
778
  else:
779
    ToStdout("The watcher is paused until %s.", time.ctime(until))
780

    
781

    
782
def WatcherOps(opts, args):
783
  """Watcher operations.
784

    
785
  @param opts: the command line options selected by the user
786
  @type args: list
787
  @param args: should contain only one element, the subcommand
788
  @rtype: int
789
  @return: the desired exit code
790

    
791
  """
792
  command = args[0]
793
  client = GetClient()
794

    
795
  if command == "continue":
796
    client.SetWatcherPause(None)
797
    ToStdout("The watcher is no longer paused.")
798

    
799
  elif command == "pause":
800
    if len(args) < 2:
801
      raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
802

    
803
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
804
    _ShowWatcherPause(result)
805

    
806
  elif command == "info":
807
    result = client.QueryConfigValues(["watcher_pause"])
808
    _ShowWatcherPause(result[0])
809

    
810
  else:
811
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
812
                               errors.ECODE_INVAL)
813

    
814
  return 0
815

    
816

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

    
908

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