Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ 6a016df9

History | View | Annotate | Download (28.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
                        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
  cl = GetClient()
166

    
167
  (cluster_name, ) = cl.QueryConfigValues(["cluster_name"])
168

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

    
179
  op = opcodes.OpRenameCluster(name=new_name)
180
  result = SubmitOpCode(op, opts=opts, cl=cl)
181

    
182
  ToStdout("Cluster renamed from '%s' to '%s'", cluster_name, result)
183

    
184
  return 0
185

    
186

    
187
def RedistributeConfig(opts, args):
188
  """Forces push of the cluster configuration.
189

    
190
  @param opts: the command line options selected by the user
191
  @type args: list
192
  @param args: empty list
193
  @rtype: int
194
  @return: the desired exit code
195

    
196
  """
197
  op = opcodes.OpRedistributeConfig()
198
  SubmitOrSend(op, opts)
199
  return 0
200

    
201

    
202
def ShowClusterVersion(opts, args):
203
  """Write version of ganeti software to the standard output.
204

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

    
211
  """
212
  cl = GetClient()
213
  result = cl.QueryClusterInfo()
214
  ToStdout("Software version: %s", result["software_version"])
215
  ToStdout("Internode protocol: %s", result["protocol_version"])
216
  ToStdout("Configuration format: %s", result["config_version"])
217
  ToStdout("OS api version: %s", result["os_api_version"])
218
  ToStdout("Export interface: %s", result["export_version"])
219
  return 0
220

    
221

    
222
def ShowClusterMaster(opts, args):
223
  """Write name of master node to the standard output.
224

    
225
  @param opts: the command line options selected by the user
226
  @type args: list
227
  @param args: should be an empty list
228
  @rtype: int
229
  @return: the desired exit code
230

    
231
  """
232
  master = bootstrap.GetMaster()
233
  ToStdout(master)
234
  return 0
235

    
236

    
237
def _PrintGroupedParams(paramsdict, level=1, roman=False):
238
  """Print Grouped parameters (be, nic, disk) by group.
239

    
240
  @type paramsdict: dict of dicts
241
  @param paramsdict: {group: {param: value, ...}, ...}
242
  @type level: int
243
  @param level: Level of indention
244

    
245
  """
246
  indent = "  " * level
247
  for item, val in sorted(paramsdict.items()):
248
    if isinstance(val, dict):
249
      ToStdout("%s- %s:", indent, item)
250
      _PrintGroupedParams(val, level=level + 1, roman=roman)
251
    elif roman and isinstance(val, int):
252
      ToStdout("%s  %s: %s", indent, item, compat.TryToRoman(val))
253
    else:
254
      ToStdout("%s  %s: %s", indent, item, val)
255

    
256

    
257
def ShowClusterConfig(opts, args):
258
  """Shows cluster information.
259

    
260
  @param opts: the command line options selected by the user
261
  @type args: list
262
  @param args: should be an empty list
263
  @rtype: int
264
  @return: the desired exit code
265

    
266
  """
267
  cl = GetClient()
268
  result = cl.QueryClusterInfo()
269

    
270
  ToStdout("Cluster name: %s", result["name"])
271
  ToStdout("Cluster UUID: %s", result["uuid"])
272

    
273
  ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
274
  ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))
275

    
276
  ToStdout("Master node: %s", result["master"])
277

    
278
  ToStdout("Architecture (this node): %s (%s)",
279
           result["architecture"][0], result["architecture"][1])
280

    
281
  if result["tags"]:
282
    tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
283
  else:
284
    tags = "(none)"
285

    
286
  ToStdout("Tags: %s", tags)
287

    
288
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
289
  ToStdout("Enabled hypervisors: %s",
290
           utils.CommaJoin(result["enabled_hypervisors"]))
291

    
292
  ToStdout("Hypervisor parameters:")
293
  _PrintGroupedParams(result["hvparams"])
294

    
295
  ToStdout("OS-specific hypervisor parameters:")
296
  _PrintGroupedParams(result["os_hvp"])
297

    
298
  ToStdout("OS parameters:")
299
  _PrintGroupedParams(result["osparams"])
300

    
301
  ToStdout("Cluster parameters:")
302
  ToStdout("  - candidate pool size: %s",
303
            compat.TryToRoman(result["candidate_pool_size"],
304
                              convert=opts.roman_integers))
305
  ToStdout("  - master netdev: %s", result["master_netdev"])
306
  ToStdout("  - lvm volume group: %s", result["volume_group_name"])
307
  if result["reserved_lvs"]:
308
    reserved_lvs = utils.CommaJoin(result["reserved_lvs"])
309
  else:
310
    reserved_lvs = "(none)"
311
  ToStdout("  - lvm reserved volumes: %s", reserved_lvs)
312
  ToStdout("  - drbd usermode helper: %s", result["drbd_usermode_helper"])
313
  ToStdout("  - file storage path: %s", result["file_storage_dir"])
314
  ToStdout("  - maintenance of node health: %s",
315
           result["maintain_node_health"])
316
  ToStdout("  - uid pool: %s",
317
            uidpool.FormatUidPool(result["uid_pool"],
318
                                  roman=opts.roman_integers))
319
  ToStdout("  - default instance allocator: %s", result["default_iallocator"])
320

    
321
  ToStdout("Default instance parameters:")
322
  _PrintGroupedParams(result["beparams"], roman=opts.roman_integers)
323

    
324
  ToStdout("Default nic parameters:")
325
  _PrintGroupedParams(result["nicparams"], roman=opts.roman_integers)
326

    
327
  return 0
328

    
329

    
330
def ClusterCopyFile(opts, args):
331
  """Copy a file from master to some nodes.
332

    
333
  @param opts: the command line options selected by the user
334
  @type args: list
335
  @param args: should contain only one element, the path of
336
      the file to be copied
337
  @rtype: int
338
  @return: the desired exit code
339

    
340
  """
341
  filename = args[0]
342
  if not os.path.exists(filename):
343
    raise errors.OpPrereqError("No such filename '%s'" % filename,
344
                               errors.ECODE_INVAL)
345

    
346
  cl = GetClient()
347

    
348
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
349

    
350
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
351
                           secondary_ips=opts.use_replication_network)
352

    
353
  srun = ssh.SshRunner(cluster_name=cluster_name)
354
  for node in results:
355
    if not srun.CopyFileToNode(node, filename):
356
      ToStderr("Copy of file %s to node %s failed", filename, node)
357

    
358
  return 0
359

    
360

    
361
def RunClusterCommand(opts, args):
362
  """Run a command on some nodes.
363

    
364
  @param opts: the command line options selected by the user
365
  @type args: list
366
  @param args: should contain the command to be run and its arguments
367
  @rtype: int
368
  @return: the desired exit code
369

    
370
  """
371
  cl = GetClient()
372

    
373
  command = " ".join(args)
374

    
375
  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
376

    
377
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
378
                                                    "master_node"])
379

    
380
  srun = ssh.SshRunner(cluster_name=cluster_name)
381

    
382
  # Make sure master node is at list end
383
  if master_node in nodes:
384
    nodes.remove(master_node)
385
    nodes.append(master_node)
386

    
387
  for name in nodes:
388
    result = srun.Run(name, "root", command)
389
    ToStdout("------------------------------------------------")
390
    ToStdout("node: %s", name)
391
    ToStdout("%s", result.output)
392
    ToStdout("return code = %s", result.exit_code)
393

    
394
  return 0
395

    
396

    
397
def VerifyCluster(opts, args):
398
  """Verify integrity of cluster, performing various test on nodes.
399

    
400
  @param opts: the command line options selected by the user
401
  @type args: list
402
  @param args: should be an empty list
403
  @rtype: int
404
  @return: the desired exit code
405

    
406
  """
407
  skip_checks = []
408
  if opts.skip_nplusone_mem:
409
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
410
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
411
                               verbose=opts.verbose,
412
                               error_codes=opts.error_codes,
413
                               debug_simulate_errors=opts.simulate_errors)
414
  if SubmitOpCode(op, opts=opts):
415
    return 0
416
  else:
417
    return 1
418

    
419

    
420
def VerifyDisks(opts, args):
421
  """Verify integrity of cluster disks.
422

    
423
  @param opts: the command line options selected by the user
424
  @type args: list
425
  @param args: should be an empty list
426
  @rtype: int
427
  @return: the desired exit code
428

    
429
  """
430
  op = opcodes.OpVerifyDisks()
431
  result = SubmitOpCode(op, opts=opts)
432
  if not isinstance(result, (list, tuple)) or len(result) != 3:
433
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
434

    
435
  bad_nodes, instances, missing = result
436

    
437
  retcode = constants.EXIT_SUCCESS
438

    
439
  if bad_nodes:
440
    for node, text in bad_nodes.items():
441
      ToStdout("Error gathering data on node %s: %s",
442
               node, utils.SafeEncode(text[-400:]))
443
      retcode |= 1
444
      ToStdout("You need to fix these nodes first before fixing instances")
445

    
446
  if instances:
447
    for iname in instances:
448
      if iname in missing:
449
        continue
450
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
451
      try:
452
        ToStdout("Activating disks for instance '%s'", iname)
453
        SubmitOpCode(op, opts=opts)
454
      except errors.GenericError, err:
455
        nret, msg = FormatError(err)
456
        retcode |= nret
457
        ToStderr("Error activating disks for instance %s: %s", iname, msg)
458

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

    
477
  return retcode
478

    
479

    
480
def RepairDiskSizes(opts, args):
481
  """Verify sizes of cluster disks.
482

    
483
  @param opts: the command line options selected by the user
484
  @type args: list
485
  @param args: optional list of instances to restrict check to
486
  @rtype: int
487
  @return: the desired exit code
488

    
489
  """
490
  op = opcodes.OpRepairDiskSizes(instances=args)
491
  SubmitOpCode(op, opts=opts)
492

    
493

    
494
@UsesRPC
495
def MasterFailover(opts, args):
496
  """Failover the master node.
497

    
498
  This command, when run on a non-master node, will cause the current
499
  master to cease being master, and the non-master to become new
500
  master.
501

    
502
  @param opts: the command line options selected by the user
503
  @type args: list
504
  @param args: should be an empty list
505
  @rtype: int
506
  @return: the desired exit code
507

    
508
  """
509
  if opts.no_voting:
510
    usertext = ("This will perform the failover even if most other nodes"
511
                " are down, or if this node is outdated. This is dangerous"
512
                " as it can lead to a non-consistent cluster. Check the"
513
                " gnt-cluster(8) man page before proceeding. Continue?")
514
    if not AskUser(usertext):
515
      return 1
516

    
517
  return bootstrap.MasterFailover(no_voting=opts.no_voting)
518

    
519

    
520
def SearchTags(opts, args):
521
  """Searches the tags on all the cluster.
522

    
523
  @param opts: the command line options selected by the user
524
  @type args: list
525
  @param args: should contain only one element, the tag pattern
526
  @rtype: int
527
  @return: the desired exit code
528

    
529
  """
530
  op = opcodes.OpSearchTags(pattern=args[0])
531
  result = SubmitOpCode(op, opts=opts)
532
  if not result:
533
    return 1
534
  result = list(result)
535
  result.sort()
536
  for path, tag in result:
537
    ToStdout("%s %s", path, tag)
538

    
539

    
540
def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
541
                 new_confd_hmac_key, new_cds, cds_filename,
542
                 force):
543
  """Renews cluster certificates, keys and secrets.
544

    
545
  @type new_cluster_cert: bool
546
  @param new_cluster_cert: Whether to generate a new cluster certificate
547
  @type new_rapi_cert: bool
548
  @param new_rapi_cert: Whether to generate a new RAPI certificate
549
  @type rapi_cert_filename: string
550
  @param rapi_cert_filename: Path to file containing new RAPI certificate
551
  @type new_confd_hmac_key: bool
552
  @param new_confd_hmac_key: Whether to generate a new HMAC key
553
  @type new_cds: bool
554
  @param new_cds: Whether to generate a new cluster domain secret
555
  @type cds_filename: string
556
  @param cds_filename: Path to file containing new cluster domain secret
557
  @type force: bool
558
  @param force: Whether to ask user for confirmation
559

    
560
  """
561
  if new_rapi_cert and rapi_cert_filename:
562
    ToStderr("Only one of the --new-rapi-certficate and --rapi-certificate"
563
             " options can be specified at the same time.")
564
    return 1
565

    
566
  if new_cds and cds_filename:
567
    ToStderr("Only one of the --new-cluster-domain-secret and"
568
             " --cluster-domain-secret options can be specified at"
569
             " the same time.")
570
    return 1
571

    
572
  if rapi_cert_filename:
573
    # Read and verify new certificate
574
    try:
575
      rapi_cert_pem = utils.ReadFile(rapi_cert_filename)
576

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

    
584
    try:
585
      OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, rapi_cert_pem)
586
    except Exception, err: # pylint: disable-msg=W0703
587
      ToStderr("Can't load new RAPI private key from %s: %s" %
588
               (rapi_cert_filename, str(err)))
589
      return 1
590

    
591
  else:
592
    rapi_cert_pem = None
593

    
594
  if cds_filename:
595
    try:
596
      cds = utils.ReadFile(cds_filename)
597
    except Exception, err: # pylint: disable-msg=W0703
598
      ToStderr("Can't load new cluster domain secret from %s: %s" %
599
               (cds_filename, str(err)))
600
      return 1
601
  else:
602
    cds = None
603

    
604
  if not force:
605
    usertext = ("This requires all daemons on all nodes to be restarted and"
606
                " may take some time. Continue?")
607
    if not AskUser(usertext):
608
      return 1
609

    
610
  def _RenewCryptoInner(ctx):
611
    ctx.feedback_fn("Updating certificates and keys")
612
    bootstrap.GenerateClusterCrypto(new_cluster_cert, new_rapi_cert,
613
                                    new_confd_hmac_key,
614
                                    new_cds,
615
                                    rapi_cert_pem=rapi_cert_pem,
616
                                    cds=cds)
617

    
618
    files_to_copy = []
619

    
620
    if new_cluster_cert:
621
      files_to_copy.append(constants.NODED_CERT_FILE)
622

    
623
    if new_rapi_cert or rapi_cert_pem:
624
      files_to_copy.append(constants.RAPI_CERT_FILE)
625

    
626
    if new_confd_hmac_key:
627
      files_to_copy.append(constants.CONFD_HMAC_KEY)
628

    
629
    if new_cds or cds:
630
      files_to_copy.append(constants.CLUSTER_DOMAIN_SECRET_FILE)
631

    
632
    if files_to_copy:
633
      for node_name in ctx.nonmaster_nodes:
634
        ctx.feedback_fn("Copying %s to %s" %
635
                        (", ".join(files_to_copy), node_name))
636
        for file_name in files_to_copy:
637
          ctx.ssh.CopyFileToNode(node_name, file_name)
638

    
639
  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
640

    
641
  ToStdout("All requested certificates and keys have been replaced."
642
           " Running \"gnt-cluster verify\" now is recommended.")
643

    
644
  return 0
645

    
646

    
647
def RenewCrypto(opts, args):
648
  """Renews cluster certificates, keys and secrets.
649

    
650
  """
651
  return _RenewCrypto(opts.new_cluster_cert,
652
                      opts.new_rapi_cert,
653
                      opts.rapi_cert,
654
                      opts.new_confd_hmac_key,
655
                      opts.new_cluster_domain_secret,
656
                      opts.cluster_domain_secret,
657
                      opts.force)
658

    
659

    
660
def SetClusterParams(opts, args):
661
  """Modify the cluster.
662

    
663
  @param opts: the command line options selected by the user
664
  @type args: list
665
  @param args: should be an empty list
666
  @rtype: int
667
  @return: the desired exit code
668

    
669
  """
670
  if not (not opts.lvm_storage or opts.vg_name or
671
          not opts.drbd_storage or opts.drbd_helper or
672
          opts.enabled_hypervisors or opts.hvparams or
673
          opts.beparams or opts.nicparams or
674
          opts.candidate_pool_size is not None or
675
          opts.uid_pool is not None or
676
          opts.maintain_node_health is not None or
677
          opts.add_uids is not None or
678
          opts.remove_uids is not None or
679
          opts.default_iallocator is not None or
680
          opts.reserved_lvs is not None):
681
    ToStderr("Please give at least one of the parameters.")
682
    return 1
683

    
684
  vg_name = opts.vg_name
685
  if not opts.lvm_storage and opts.vg_name:
686
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
687
    return 1
688

    
689
  if not opts.lvm_storage:
690
    vg_name = ""
691

    
692
  drbd_helper = opts.drbd_helper
693
  if not opts.drbd_storage and opts.drbd_helper:
694
    ToStderr("Options --no-drbd-storage and --drbd-usermode-helper conflict.")
695
    return 1
696

    
697
  if not opts.drbd_storage:
698
    drbd_helper = ""
699

    
700
  hvlist = opts.enabled_hypervisors
701
  if hvlist is not None:
702
    hvlist = hvlist.split(",")
703

    
704
  # a list of (name, dict) we can pass directly to dict() (or [])
705
  hvparams = dict(opts.hvparams)
706
  for hv_params in hvparams.values():
707
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
708

    
709
  beparams = opts.beparams
710
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
711

    
712
  nicparams = opts.nicparams
713
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
714

    
715

    
716
  mnh = opts.maintain_node_health
717

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

    
722
  add_uids = opts.add_uids
723
  if add_uids is not None:
724
    add_uids = uidpool.ParseUidPool(add_uids)
725

    
726
  remove_uids = opts.remove_uids
727
  if remove_uids is not None:
728
    remove_uids = uidpool.ParseUidPool(remove_uids)
729

    
730
  if opts.reserved_lvs is not None:
731
    if opts.reserved_lvs == "":
732
      opts.reserved_lvs = []
733
    else:
734
      opts.reserved_lvs = utils.UnescapeAndSplit(opts.reserved_lvs, sep=",")
735

    
736
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
737
                                  drbd_helper=drbd_helper,
738
                                  enabled_hypervisors=hvlist,
739
                                  hvparams=hvparams,
740
                                  os_hvp=None,
741
                                  beparams=beparams,
742
                                  nicparams=nicparams,
743
                                  candidate_pool_size=opts.candidate_pool_size,
744
                                  maintain_node_health=mnh,
745
                                  uid_pool=uid_pool,
746
                                  add_uids=add_uids,
747
                                  remove_uids=remove_uids,
748
                                  default_iallocator=opts.default_iallocator,
749
                                  reserved_lvs=opts.reserved_lvs)
750
  SubmitOpCode(op, opts=opts)
751
  return 0
752

    
753

    
754
def QueueOps(opts, args):
755
  """Queue operations.
756

    
757
  @param opts: the command line options selected by the user
758
  @type args: list
759
  @param args: should contain only one element, the subcommand
760
  @rtype: int
761
  @return: the desired exit code
762

    
763
  """
764
  command = args[0]
765
  client = GetClient()
766
  if command in ("drain", "undrain"):
767
    drain_flag = command == "drain"
768
    client.SetQueueDrainFlag(drain_flag)
769
  elif command == "info":
770
    result = client.QueryConfigValues(["drain_flag"])
771
    if result[0]:
772
      val = "set"
773
    else:
774
      val = "unset"
775
    ToStdout("The drain flag is %s" % val)
776
  else:
777
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
778
                               errors.ECODE_INVAL)
779

    
780
  return 0
781

    
782

    
783
def _ShowWatcherPause(until):
784
  if until is None or until < time.time():
785
    ToStdout("The watcher is not paused.")
786
  else:
787
    ToStdout("The watcher is paused until %s.", time.ctime(until))
788

    
789

    
790
def WatcherOps(opts, args):
791
  """Watcher operations.
792

    
793
  @param opts: the command line options selected by the user
794
  @type args: list
795
  @param args: should contain only one element, the subcommand
796
  @rtype: int
797
  @return: the desired exit code
798

    
799
  """
800
  command = args[0]
801
  client = GetClient()
802

    
803
  if command == "continue":
804
    client.SetWatcherPause(None)
805
    ToStdout("The watcher is no longer paused.")
806

    
807
  elif command == "pause":
808
    if len(args) < 2:
809
      raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
810

    
811
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
812
    _ShowWatcherPause(result)
813

    
814
  elif command == "info":
815
    result = client.QueryConfigValues(["watcher_pause"])
816
    _ShowWatcherPause(result[0])
817

    
818
  else:
819
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
820
                               errors.ECODE_INVAL)
821

    
822
  return 0
823

    
824

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

    
916

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