Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ e7323b5e

History | View | Annotate | Download (29.3 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2006, 2007, 2010 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
  try:
109
    primary_ip_version = int(opts.primary_ip_version)
110
  except (ValueError, TypeError), err:
111
    ToStderr("Invalid primary ip version value: %s" % str(err))
112
    return 1
113

    
114
  bootstrap.InitCluster(cluster_name=args[0],
115
                        secondary_ip=opts.secondary_ip,
116
                        vg_name=vg_name,
117
                        mac_prefix=opts.mac_prefix,
118
                        master_netdev=opts.master_netdev,
119
                        file_storage_dir=opts.file_storage_dir,
120
                        enabled_hypervisors=hvlist,
121
                        hvparams=hvparams,
122
                        beparams=beparams,
123
                        nicparams=nicparams,
124
                        candidate_pool_size=opts.candidate_pool_size,
125
                        modify_etc_hosts=opts.modify_etc_hosts,
126
                        modify_ssh_setup=opts.modify_ssh_setup,
127
                        maintain_node_health=opts.maintain_node_health,
128
                        drbd_helper=drbd_helper,
129
                        uid_pool=uid_pool,
130
                        default_iallocator=opts.default_iallocator,
131
                        primary_ip_version=primary_ip_version,
132
                        )
133
  op = opcodes.OpPostInitCluster()
134
  SubmitOpCode(op, opts=opts)
135
  return 0
136

    
137

    
138
@UsesRPC
139
def DestroyCluster(opts, args):
140
  """Destroy the cluster.
141

    
142
  @param opts: the command line options selected by the user
143
  @type args: list
144
  @param args: should be an empty list
145
  @rtype: int
146
  @return: the desired exit code
147

    
148
  """
149
  if not opts.yes_do_it:
150
    ToStderr("Destroying a cluster is irreversible. If you really want"
151
             " destroy this cluster, supply the --yes-do-it option.")
152
    return 1
153

    
154
  op = opcodes.OpDestroyCluster()
155
  master = SubmitOpCode(op, opts=opts)
156
  # if we reached this, the opcode didn't fail; we can proceed to
157
  # shutdown all the daemons
158
  bootstrap.FinalizeClusterDestroy(master)
159
  return 0
160

    
161

    
162
def RenameCluster(opts, args):
163
  """Rename the cluster.
164

    
165
  @param opts: the command line options selected by the user
166
  @type args: list
167
  @param args: should contain only one element, the new cluster name
168
  @rtype: int
169
  @return: the desired exit code
170

    
171
  """
172
  cl = GetClient()
173

    
174
  (cluster_name, ) = cl.QueryConfigValues(["cluster_name"])
175

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

    
186
  op = opcodes.OpRenameCluster(name=new_name)
187
  result = SubmitOpCode(op, opts=opts, cl=cl)
188

    
189
  ToStdout("Cluster renamed from '%s' to '%s'", cluster_name, result)
190

    
191
  return 0
192

    
193

    
194
def RedistributeConfig(opts, args):
195
  """Forces push of the cluster configuration.
196

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

    
203
  """
204
  op = opcodes.OpRedistributeConfig()
205
  SubmitOrSend(op, opts)
206
  return 0
207

    
208

    
209
def ShowClusterVersion(opts, args):
210
  """Write version of ganeti software to the standard output.
211

    
212
  @param opts: the command line options selected by the user
213
  @type args: list
214
  @param args: should be an empty list
215
  @rtype: int
216
  @return: the desired exit code
217

    
218
  """
219
  cl = GetClient()
220
  result = cl.QueryClusterInfo()
221
  ToStdout("Software version: %s", result["software_version"])
222
  ToStdout("Internode protocol: %s", result["protocol_version"])
223
  ToStdout("Configuration format: %s", result["config_version"])
224
  ToStdout("OS api version: %s", result["os_api_version"])
225
  ToStdout("Export interface: %s", result["export_version"])
226
  return 0
227

    
228

    
229
def ShowClusterMaster(opts, args):
230
  """Write name of master node to the standard output.
231

    
232
  @param opts: the command line options selected by the user
233
  @type args: list
234
  @param args: should be an empty list
235
  @rtype: int
236
  @return: the desired exit code
237

    
238
  """
239
  master = bootstrap.GetMaster()
240
  ToStdout(master)
241
  return 0
242

    
243

    
244
def _PrintGroupedParams(paramsdict, level=1, roman=False):
245
  """Print Grouped parameters (be, nic, disk) by group.
246

    
247
  @type paramsdict: dict of dicts
248
  @param paramsdict: {group: {param: value, ...}, ...}
249
  @type level: int
250
  @param level: Level of indention
251

    
252
  """
253
  indent = "  " * level
254
  for item, val in sorted(paramsdict.items()):
255
    if isinstance(val, dict):
256
      ToStdout("%s- %s:", indent, item)
257
      _PrintGroupedParams(val, level=level + 1, roman=roman)
258
    elif roman and isinstance(val, int):
259
      ToStdout("%s  %s: %s", indent, item, compat.TryToRoman(val))
260
    else:
261
      ToStdout("%s  %s: %s", indent, item, val)
262

    
263

    
264
def ShowClusterConfig(opts, args):
265
  """Shows cluster information.
266

    
267
  @param opts: the command line options selected by the user
268
  @type args: list
269
  @param args: should be an empty list
270
  @rtype: int
271
  @return: the desired exit code
272

    
273
  """
274
  cl = GetClient()
275
  result = cl.QueryClusterInfo()
276

    
277
  ToStdout("Cluster name: %s", result["name"])
278
  ToStdout("Cluster UUID: %s", result["uuid"])
279

    
280
  ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
281
  ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))
282

    
283
  ToStdout("Master node: %s", result["master"])
284

    
285
  ToStdout("Architecture (this node): %s (%s)",
286
           result["architecture"][0], result["architecture"][1])
287

    
288
  if result["tags"]:
289
    tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
290
  else:
291
    tags = "(none)"
292

    
293
  ToStdout("Tags: %s", tags)
294

    
295
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
296
  ToStdout("Enabled hypervisors: %s",
297
           utils.CommaJoin(result["enabled_hypervisors"]))
298

    
299
  ToStdout("Hypervisor parameters:")
300
  _PrintGroupedParams(result["hvparams"])
301

    
302
  ToStdout("OS-specific hypervisor parameters:")
303
  _PrintGroupedParams(result["os_hvp"])
304

    
305
  ToStdout("OS parameters:")
306
  _PrintGroupedParams(result["osparams"])
307

    
308
  ToStdout("Cluster parameters:")
309
  ToStdout("  - candidate pool size: %s",
310
            compat.TryToRoman(result["candidate_pool_size"],
311
                              convert=opts.roman_integers))
312
  ToStdout("  - master netdev: %s", result["master_netdev"])
313
  ToStdout("  - lvm volume group: %s", result["volume_group_name"])
314
  if result["reserved_lvs"]:
315
    reserved_lvs = utils.CommaJoin(result["reserved_lvs"])
316
  else:
317
    reserved_lvs = "(none)"
318
  ToStdout("  - lvm reserved volumes: %s", reserved_lvs)
319
  ToStdout("  - drbd usermode helper: %s", result["drbd_usermode_helper"])
320
  ToStdout("  - file storage path: %s", result["file_storage_dir"])
321
  ToStdout("  - maintenance of node health: %s",
322
           result["maintain_node_health"])
323
  ToStdout("  - uid pool: %s",
324
            uidpool.FormatUidPool(result["uid_pool"],
325
                                  roman=opts.roman_integers))
326
  ToStdout("  - default instance allocator: %s", result["default_iallocator"])
327
  ToStdout("  - primary ip version: %d", result["primary_ip_version"])
328

    
329
  ToStdout("Default instance parameters:")
330
  _PrintGroupedParams(result["beparams"], roman=opts.roman_integers)
331

    
332
  ToStdout("Default nic parameters:")
333
  _PrintGroupedParams(result["nicparams"], roman=opts.roman_integers)
334

    
335
  return 0
336

    
337

    
338
def ClusterCopyFile(opts, args):
339
  """Copy a file from master to some nodes.
340

    
341
  @param opts: the command line options selected by the user
342
  @type args: list
343
  @param args: should contain only one element, the path of
344
      the file to be copied
345
  @rtype: int
346
  @return: the desired exit code
347

    
348
  """
349
  filename = args[0]
350
  if not os.path.exists(filename):
351
    raise errors.OpPrereqError("No such filename '%s'" % filename,
352
                               errors.ECODE_INVAL)
353

    
354
  cl = GetClient()
355

    
356
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
357

    
358
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
359
                           secondary_ips=opts.use_replication_network)
360

    
361
  srun = ssh.SshRunner(cluster_name=cluster_name)
362
  for node in results:
363
    if not srun.CopyFileToNode(node, filename):
364
      ToStderr("Copy of file %s to node %s failed", filename, node)
365

    
366
  return 0
367

    
368

    
369
def RunClusterCommand(opts, args):
370
  """Run a command on some nodes.
371

    
372
  @param opts: the command line options selected by the user
373
  @type args: list
374
  @param args: should contain the command to be run and its arguments
375
  @rtype: int
376
  @return: the desired exit code
377

    
378
  """
379
  cl = GetClient()
380

    
381
  command = " ".join(args)
382

    
383
  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
384

    
385
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
386
                                                    "master_node"])
387

    
388
  srun = ssh.SshRunner(cluster_name=cluster_name)
389

    
390
  # Make sure master node is at list end
391
  if master_node in nodes:
392
    nodes.remove(master_node)
393
    nodes.append(master_node)
394

    
395
  for name in nodes:
396
    result = srun.Run(name, "root", command)
397
    ToStdout("------------------------------------------------")
398
    ToStdout("node: %s", name)
399
    ToStdout("%s", result.output)
400
    ToStdout("return code = %s", result.exit_code)
401

    
402
  return 0
403

    
404

    
405
def VerifyCluster(opts, args):
406
  """Verify integrity of cluster, performing various test on nodes.
407

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

    
414
  """
415
  skip_checks = []
416
  if opts.skip_nplusone_mem:
417
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
418
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
419
                               verbose=opts.verbose,
420
                               error_codes=opts.error_codes,
421
                               debug_simulate_errors=opts.simulate_errors)
422
  if SubmitOpCode(op, opts=opts):
423
    return 0
424
  else:
425
    return 1
426

    
427

    
428
def VerifyDisks(opts, args):
429
  """Verify integrity of cluster disks.
430

    
431
  @param opts: the command line options selected by the user
432
  @type args: list
433
  @param args: should be an empty list
434
  @rtype: int
435
  @return: the desired exit code
436

    
437
  """
438
  op = opcodes.OpVerifyDisks()
439
  result = SubmitOpCode(op, opts=opts)
440
  if not isinstance(result, (list, tuple)) or len(result) != 3:
441
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
442

    
443
  bad_nodes, instances, missing = result
444

    
445
  retcode = constants.EXIT_SUCCESS
446

    
447
  if bad_nodes:
448
    for node, text in bad_nodes.items():
449
      ToStdout("Error gathering data on node %s: %s",
450
               node, utils.SafeEncode(text[-400:]))
451
      retcode |= 1
452
      ToStdout("You need to fix these nodes first before fixing instances")
453

    
454
  if instances:
455
    for iname in instances:
456
      if iname in missing:
457
        continue
458
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
459
      try:
460
        ToStdout("Activating disks for instance '%s'", iname)
461
        SubmitOpCode(op, opts=opts)
462
      except errors.GenericError, err:
463
        nret, msg = FormatError(err)
464
        retcode |= nret
465
        ToStderr("Error activating disks for instance %s: %s", iname, msg)
466

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

    
485
  return retcode
486

    
487

    
488
def RepairDiskSizes(opts, args):
489
  """Verify sizes of cluster disks.
490

    
491
  @param opts: the command line options selected by the user
492
  @type args: list
493
  @param args: optional list of instances to restrict check to
494
  @rtype: int
495
  @return: the desired exit code
496

    
497
  """
498
  op = opcodes.OpRepairDiskSizes(instances=args)
499
  SubmitOpCode(op, opts=opts)
500

    
501

    
502
@UsesRPC
503
def MasterFailover(opts, args):
504
  """Failover the master node.
505

    
506
  This command, when run on a non-master node, will cause the current
507
  master to cease being master, and the non-master to become new
508
  master.
509

    
510
  @param opts: the command line options selected by the user
511
  @type args: list
512
  @param args: should be an empty list
513
  @rtype: int
514
  @return: the desired exit code
515

    
516
  """
517
  if opts.no_voting:
518
    usertext = ("This will perform the failover even if most other nodes"
519
                " are down, or if this node is outdated. This is dangerous"
520
                " as it can lead to a non-consistent cluster. Check the"
521
                " gnt-cluster(8) man page before proceeding. Continue?")
522
    if not AskUser(usertext):
523
      return 1
524

    
525
  return bootstrap.MasterFailover(no_voting=opts.no_voting)
526

    
527

    
528
def MasterPing(opts, args):
529
  """Checks if the master is alive.
530

    
531
  @param opts: the command line options selected by the user
532
  @type args: list
533
  @param args: should be an empty list
534
  @rtype: int
535
  @return: the desired exit code
536

    
537
  """
538
  try:
539
    cl = GetClient()
540
    cl.QueryClusterInfo()
541
    return 0
542
  except Exception: # pylint: disable-msg=W0703
543
    return 1
544

    
545

    
546
def SearchTags(opts, args):
547
  """Searches the tags on all the cluster.
548

    
549
  @param opts: the command line options selected by the user
550
  @type args: list
551
  @param args: should contain only one element, the tag pattern
552
  @rtype: int
553
  @return: the desired exit code
554

    
555
  """
556
  op = opcodes.OpSearchTags(pattern=args[0])
557
  result = SubmitOpCode(op, opts=opts)
558
  if not result:
559
    return 1
560
  result = list(result)
561
  result.sort()
562
  for path, tag in result:
563
    ToStdout("%s %s", path, tag)
564

    
565

    
566
def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
567
                 new_confd_hmac_key, new_cds, cds_filename,
568
                 force):
569
  """Renews cluster certificates, keys and secrets.
570

    
571
  @type new_cluster_cert: bool
572
  @param new_cluster_cert: Whether to generate a new cluster certificate
573
  @type new_rapi_cert: bool
574
  @param new_rapi_cert: Whether to generate a new RAPI certificate
575
  @type rapi_cert_filename: string
576
  @param rapi_cert_filename: Path to file containing new RAPI certificate
577
  @type new_confd_hmac_key: bool
578
  @param new_confd_hmac_key: Whether to generate a new HMAC key
579
  @type new_cds: bool
580
  @param new_cds: Whether to generate a new cluster domain secret
581
  @type cds_filename: string
582
  @param cds_filename: Path to file containing new cluster domain secret
583
  @type force: bool
584
  @param force: Whether to ask user for confirmation
585

    
586
  """
587
  if new_rapi_cert and rapi_cert_filename:
588
    ToStderr("Only one of the --new-rapi-certficate and --rapi-certificate"
589
             " options can be specified at the same time.")
590
    return 1
591

    
592
  if new_cds and cds_filename:
593
    ToStderr("Only one of the --new-cluster-domain-secret and"
594
             " --cluster-domain-secret options can be specified at"
595
             " the same time.")
596
    return 1
597

    
598
  if rapi_cert_filename:
599
    # Read and verify new certificate
600
    try:
601
      rapi_cert_pem = utils.ReadFile(rapi_cert_filename)
602

    
603
      OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
604
                                      rapi_cert_pem)
605
    except Exception, err: # pylint: disable-msg=W0703
606
      ToStderr("Can't load new RAPI certificate from %s: %s" %
607
               (rapi_cert_filename, str(err)))
608
      return 1
609

    
610
    try:
611
      OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, rapi_cert_pem)
612
    except Exception, err: # pylint: disable-msg=W0703
613
      ToStderr("Can't load new RAPI private key from %s: %s" %
614
               (rapi_cert_filename, str(err)))
615
      return 1
616

    
617
  else:
618
    rapi_cert_pem = None
619

    
620
  if cds_filename:
621
    try:
622
      cds = utils.ReadFile(cds_filename)
623
    except Exception, err: # pylint: disable-msg=W0703
624
      ToStderr("Can't load new cluster domain secret from %s: %s" %
625
               (cds_filename, str(err)))
626
      return 1
627
  else:
628
    cds = None
629

    
630
  if not force:
631
    usertext = ("This requires all daemons on all nodes to be restarted and"
632
                " may take some time. Continue?")
633
    if not AskUser(usertext):
634
      return 1
635

    
636
  def _RenewCryptoInner(ctx):
637
    ctx.feedback_fn("Updating certificates and keys")
638
    bootstrap.GenerateClusterCrypto(new_cluster_cert, new_rapi_cert,
639
                                    new_confd_hmac_key,
640
                                    new_cds,
641
                                    rapi_cert_pem=rapi_cert_pem,
642
                                    cds=cds)
643

    
644
    files_to_copy = []
645

    
646
    if new_cluster_cert:
647
      files_to_copy.append(constants.NODED_CERT_FILE)
648

    
649
    if new_rapi_cert or rapi_cert_pem:
650
      files_to_copy.append(constants.RAPI_CERT_FILE)
651

    
652
    if new_confd_hmac_key:
653
      files_to_copy.append(constants.CONFD_HMAC_KEY)
654

    
655
    if new_cds or cds:
656
      files_to_copy.append(constants.CLUSTER_DOMAIN_SECRET_FILE)
657

    
658
    if files_to_copy:
659
      for node_name in ctx.nonmaster_nodes:
660
        ctx.feedback_fn("Copying %s to %s" %
661
                        (", ".join(files_to_copy), node_name))
662
        for file_name in files_to_copy:
663
          ctx.ssh.CopyFileToNode(node_name, file_name)
664

    
665
  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
666

    
667
  ToStdout("All requested certificates and keys have been replaced."
668
           " Running \"gnt-cluster verify\" now is recommended.")
669

    
670
  return 0
671

    
672

    
673
def RenewCrypto(opts, args):
674
  """Renews cluster certificates, keys and secrets.
675

    
676
  """
677
  return _RenewCrypto(opts.new_cluster_cert,
678
                      opts.new_rapi_cert,
679
                      opts.rapi_cert,
680
                      opts.new_confd_hmac_key,
681
                      opts.new_cluster_domain_secret,
682
                      opts.cluster_domain_secret,
683
                      opts.force)
684

    
685

    
686
def SetClusterParams(opts, args):
687
  """Modify the cluster.
688

    
689
  @param opts: the command line options selected by the user
690
  @type args: list
691
  @param args: should be an empty list
692
  @rtype: int
693
  @return: the desired exit code
694

    
695
  """
696
  if not (not opts.lvm_storage or opts.vg_name or
697
          not opts.drbd_storage or opts.drbd_helper or
698
          opts.enabled_hypervisors or opts.hvparams or
699
          opts.beparams or opts.nicparams or
700
          opts.candidate_pool_size is not None or
701
          opts.uid_pool is not None or
702
          opts.maintain_node_health is not None or
703
          opts.add_uids is not None or
704
          opts.remove_uids is not None or
705
          opts.default_iallocator is not None or
706
          opts.reserved_lvs is not None):
707
    ToStderr("Please give at least one of the parameters.")
708
    return 1
709

    
710
  vg_name = opts.vg_name
711
  if not opts.lvm_storage and opts.vg_name:
712
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
713
    return 1
714

    
715
  if not opts.lvm_storage:
716
    vg_name = ""
717

    
718
  drbd_helper = opts.drbd_helper
719
  if not opts.drbd_storage and opts.drbd_helper:
720
    ToStderr("Options --no-drbd-storage and --drbd-usermode-helper conflict.")
721
    return 1
722

    
723
  if not opts.drbd_storage:
724
    drbd_helper = ""
725

    
726
  hvlist = opts.enabled_hypervisors
727
  if hvlist is not None:
728
    hvlist = hvlist.split(",")
729

    
730
  # a list of (name, dict) we can pass directly to dict() (or [])
731
  hvparams = dict(opts.hvparams)
732
  for hv_params in hvparams.values():
733
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
734

    
735
  beparams = opts.beparams
736
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
737

    
738
  nicparams = opts.nicparams
739
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
740

    
741

    
742
  mnh = opts.maintain_node_health
743

    
744
  uid_pool = opts.uid_pool
745
  if uid_pool is not None:
746
    uid_pool = uidpool.ParseUidPool(uid_pool)
747

    
748
  add_uids = opts.add_uids
749
  if add_uids is not None:
750
    add_uids = uidpool.ParseUidPool(add_uids)
751

    
752
  remove_uids = opts.remove_uids
753
  if remove_uids is not None:
754
    remove_uids = uidpool.ParseUidPool(remove_uids)
755

    
756
  if opts.reserved_lvs is not None:
757
    if opts.reserved_lvs == "":
758
      opts.reserved_lvs = []
759
    else:
760
      opts.reserved_lvs = utils.UnescapeAndSplit(opts.reserved_lvs, sep=",")
761

    
762
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
763
                                  drbd_helper=drbd_helper,
764
                                  enabled_hypervisors=hvlist,
765
                                  hvparams=hvparams,
766
                                  os_hvp=None,
767
                                  beparams=beparams,
768
                                  nicparams=nicparams,
769
                                  candidate_pool_size=opts.candidate_pool_size,
770
                                  maintain_node_health=mnh,
771
                                  uid_pool=uid_pool,
772
                                  add_uids=add_uids,
773
                                  remove_uids=remove_uids,
774
                                  default_iallocator=opts.default_iallocator,
775
                                  reserved_lvs=opts.reserved_lvs)
776
  SubmitOpCode(op, opts=opts)
777
  return 0
778

    
779

    
780
def QueueOps(opts, args):
781
  """Queue operations.
782

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

    
789
  """
790
  command = args[0]
791
  client = GetClient()
792
  if command in ("drain", "undrain"):
793
    drain_flag = command == "drain"
794
    client.SetQueueDrainFlag(drain_flag)
795
  elif command == "info":
796
    result = client.QueryConfigValues(["drain_flag"])
797
    if result[0]:
798
      val = "set"
799
    else:
800
      val = "unset"
801
    ToStdout("The drain flag is %s" % val)
802
  else:
803
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
804
                               errors.ECODE_INVAL)
805

    
806
  return 0
807

    
808

    
809
def _ShowWatcherPause(until):
810
  if until is None or until < time.time():
811
    ToStdout("The watcher is not paused.")
812
  else:
813
    ToStdout("The watcher is paused until %s.", time.ctime(until))
814

    
815

    
816
def WatcherOps(opts, args):
817
  """Watcher operations.
818

    
819
  @param opts: the command line options selected by the user
820
  @type args: list
821
  @param args: should contain only one element, the subcommand
822
  @rtype: int
823
  @return: the desired exit code
824

    
825
  """
826
  command = args[0]
827
  client = GetClient()
828

    
829
  if command == "continue":
830
    client.SetWatcherPause(None)
831
    ToStdout("The watcher is no longer paused.")
832

    
833
  elif command == "pause":
834
    if len(args) < 2:
835
      raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
836

    
837
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
838
    _ShowWatcherPause(result)
839

    
840
  elif command == "info":
841
    result = client.QueryConfigValues(["watcher_pause"])
842
    _ShowWatcherPause(result[0])
843

    
844
  else:
845
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
846
                               errors.ECODE_INVAL)
847

    
848
  return 0
849

    
850

    
851
commands = {
852
  'init': (
853
    InitCluster, [ArgHost(min=1, max=1)],
854
    [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, GLOBAL_FILEDIR_OPT,
855
     HVLIST_OPT, MAC_PREFIX_OPT, MASTER_NETDEV_OPT, NIC_PARAMS_OPT,
856
     NOLVM_STORAGE_OPT, NOMODIFY_ETCHOSTS_OPT, NOMODIFY_SSH_SETUP_OPT,
857
     SECONDARY_IP_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT,
858
     UIDPOOL_OPT, DRBD_HELPER_OPT, NODRBD_STORAGE_OPT,
859
     DEFAULT_IALLOCATOR_OPT, PRIMARY_IP_VERSION_OPT],
860
    "[opts...] <cluster_name>", "Initialises a new cluster configuration"),
861
  'destroy': (
862
    DestroyCluster, ARGS_NONE, [YES_DOIT_OPT],
863
    "", "Destroy cluster"),
864
  'rename': (
865
    RenameCluster, [ArgHost(min=1, max=1)],
866
    [FORCE_OPT],
867
    "<new_name>",
868
    "Renames the cluster"),
869
  'redist-conf': (
870
    RedistributeConfig, ARGS_NONE, [SUBMIT_OPT],
871
    "", "Forces a push of the configuration file and ssconf files"
872
    " to the nodes in the cluster"),
873
  'verify': (
874
    VerifyCluster, ARGS_NONE,
875
    [VERBOSE_OPT, DEBUG_SIMERR_OPT, ERROR_CODES_OPT, NONPLUS1_OPT],
876
    "", "Does a check on the cluster configuration"),
877
  'verify-disks': (
878
    VerifyDisks, ARGS_NONE, [],
879
    "", "Does a check on the cluster disk status"),
880
  'repair-disk-sizes': (
881
    RepairDiskSizes, ARGS_MANY_INSTANCES, [],
882
    "", "Updates mismatches in recorded disk sizes"),
883
  'master-failover': (
884
    MasterFailover, ARGS_NONE, [NOVOTING_OPT],
885
    "", "Makes the current node the master"),
886
  'master-ping': (
887
    MasterPing, ARGS_NONE, [],
888
    "", "Checks if the master is alive"),
889
  'version': (
890
    ShowClusterVersion, ARGS_NONE, [],
891
    "", "Shows the cluster version"),
892
  'getmaster': (
893
    ShowClusterMaster, ARGS_NONE, [],
894
    "", "Shows the cluster master"),
895
  'copyfile': (
896
    ClusterCopyFile, [ArgFile(min=1, max=1)],
897
    [NODE_LIST_OPT, USE_REPL_NET_OPT],
898
    "[-n node...] <filename>", "Copies a file to all (or only some) nodes"),
899
  'command': (
900
    RunClusterCommand, [ArgCommand(min=1)],
901
    [NODE_LIST_OPT],
902
    "[-n node...] <command>", "Runs a command on all (or only some) nodes"),
903
  'info': (
904
    ShowClusterConfig, ARGS_NONE, [ROMAN_OPT],
905
    "[--roman]", "Show cluster configuration"),
906
  'list-tags': (
907
    ListTags, ARGS_NONE, [], "", "List the tags of the cluster"),
908
  'add-tags': (
909
    AddTags, [ArgUnknown()], [TAG_SRC_OPT],
910
    "tag...", "Add tags to the cluster"),
911
  'remove-tags': (
912
    RemoveTags, [ArgUnknown()], [TAG_SRC_OPT],
913
    "tag...", "Remove tags from the cluster"),
914
  'search-tags': (
915
    SearchTags, [ArgUnknown(min=1, max=1)],
916
    [], "", "Searches the tags on all objects on"
917
    " the cluster for a given pattern (regex)"),
918
  'queue': (
919
    QueueOps,
920
    [ArgChoice(min=1, max=1, choices=["drain", "undrain", "info"])],
921
    [], "drain|undrain|info", "Change queue properties"),
922
  'watcher': (
923
    WatcherOps,
924
    [ArgChoice(min=1, max=1, choices=["pause", "continue", "info"]),
925
     ArgSuggest(min=0, max=1, choices=["30m", "1h", "4h"])],
926
    [],
927
    "{pause <timespec>|continue|info}", "Change watcher properties"),
928
  'modify': (
929
    SetClusterParams, ARGS_NONE,
930
    [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, HVLIST_OPT,
931
     NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT,
932
     UIDPOOL_OPT, ADD_UIDS_OPT, REMOVE_UIDS_OPT, DRBD_HELPER_OPT,
933
     NODRBD_STORAGE_OPT, DEFAULT_IALLOCATOR_OPT, RESERVED_LVS_OPT],
934
    "[opts...]",
935
    "Alters the parameters of the cluster"),
936
  "renew-crypto": (
937
    RenewCrypto, ARGS_NONE,
938
    [NEW_CLUSTER_CERT_OPT, NEW_RAPI_CERT_OPT, RAPI_CERT_OPT,
939
     NEW_CONFD_HMAC_KEY_OPT, FORCE_OPT,
940
     NEW_CLUSTER_DOMAIN_SECRET_OPT, CLUSTER_DOMAIN_SECRET_OPT],
941
    "[opts...]",
942
    "Renews cluster certificates, keys and secrets"),
943
  }
944

    
945

    
946
#: dictionary with aliases for commands
947
aliases = {
948
  'masterfailover': 'master-failover',
949
}
950

    
951

    
952
if __name__ == '__main__':
953
  sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER},
954
                       aliases=aliases))