Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_cluster.py @ 25be0c75

History | View | Annotate | Download (30.7 kB)

1
#
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 os.path
30
import time
31
import OpenSSL
32

    
33
from ganeti.cli import *
34
from ganeti import opcodes
35
from ganeti import constants
36
from ganeti import errors
37
from ganeti import utils
38
from ganeti import bootstrap
39
from ganeti import ssh
40
from ganeti import objects
41
from ganeti import uidpool
42
from ganeti import compat
43

    
44

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

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

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

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

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

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

    
73
  master_netdev = opts.master_netdev
74
  if master_netdev is None:
75
    master_netdev = constants.DEFAULT_BRIDGE
76

    
77
  hvlist = opts.enabled_hypervisors
78
  if hvlist is None:
79
    hvlist = constants.DEFAULT_ENABLED_HYPERVISOR
80
  hvlist = hvlist.split(",")
81

    
82
  hvparams = dict(opts.hvparams)
83
  beparams = opts.beparams
84
  nicparams = opts.nicparams
85

    
86
  # prepare beparams dict
87
  beparams = objects.FillDict(constants.BEC_DEFAULTS, beparams)
88
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
89

    
90
  # prepare nicparams dict
91
  nicparams = objects.FillDict(constants.NICC_DEFAULTS, nicparams)
92
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
93

    
94
  # prepare ndparams dict
95
  if opts.ndparams is None:
96
    ndparams = dict(constants.NDC_DEFAULTS)
97
  else:
98
    ndparams = objects.FillDict(constants.NDC_DEFAULTS, opts.ndparams)
99
    utils.ForceDictType(ndparams, constants.NDS_PARAMETER_TYPES)
100

    
101
  # prepare hvparams dict
102
  for hv in constants.HYPER_TYPES:
103
    if hv not in hvparams:
104
      hvparams[hv] = {}
105
    hvparams[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], hvparams[hv])
106
    utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
107

    
108
  if opts.candidate_pool_size is None:
109
    opts.candidate_pool_size = constants.MASTER_POOL_SIZE_DEFAULT
110

    
111
  if opts.mac_prefix is None:
112
    opts.mac_prefix = constants.DEFAULT_MAC_PREFIX
113

    
114
  uid_pool = opts.uid_pool
115
  if uid_pool is not None:
116
    uid_pool = uidpool.ParseUidPool(uid_pool)
117

    
118
  if opts.prealloc_wipe_disks is None:
119
    opts.prealloc_wipe_disks = False
120

    
121
  try:
122
    primary_ip_version = int(opts.primary_ip_version)
123
  except (ValueError, TypeError), err:
124
    ToStderr("Invalid primary ip version value: %s" % str(err))
125
    return 1
126

    
127
  bootstrap.InitCluster(cluster_name=args[0],
128
                        secondary_ip=opts.secondary_ip,
129
                        vg_name=vg_name,
130
                        mac_prefix=opts.mac_prefix,
131
                        master_netdev=master_netdev,
132
                        file_storage_dir=opts.file_storage_dir,
133
                        enabled_hypervisors=hvlist,
134
                        hvparams=hvparams,
135
                        beparams=beparams,
136
                        nicparams=nicparams,
137
                        ndparams=ndparams,
138
                        candidate_pool_size=opts.candidate_pool_size,
139
                        modify_etc_hosts=opts.modify_etc_hosts,
140
                        modify_ssh_setup=opts.modify_ssh_setup,
141
                        maintain_node_health=opts.maintain_node_health,
142
                        drbd_helper=drbd_helper,
143
                        uid_pool=uid_pool,
144
                        default_iallocator=opts.default_iallocator,
145
                        primary_ip_version=primary_ip_version,
146
                        prealloc_wipe_disks=opts.prealloc_wipe_disks,
147
                        )
148
  op = opcodes.OpPostInitCluster()
149
  SubmitOpCode(op, opts=opts)
150
  return 0
151

    
152

    
153
@UsesRPC
154
def DestroyCluster(opts, args):
155
  """Destroy the cluster.
156

157
  @param opts: the command line options selected by the user
158
  @type args: list
159
  @param args: should be an empty list
160
  @rtype: int
161
  @return: the desired exit code
162

163
  """
164
  if not opts.yes_do_it:
165
    ToStderr("Destroying a cluster is irreversible. If you really want"
166
             " destroy this cluster, supply the --yes-do-it option.")
167
    return 1
168

    
169
  op = opcodes.OpDestroyCluster()
170
  master = SubmitOpCode(op, opts=opts)
171
  # if we reached this, the opcode didn't fail; we can proceed to
172
  # shutdown all the daemons
173
  bootstrap.FinalizeClusterDestroy(master)
174
  return 0
175

    
176

    
177
def RenameCluster(opts, args):
178
  """Rename the cluster.
179

180
  @param opts: the command line options selected by the user
181
  @type args: list
182
  @param args: should contain only one element, the new cluster name
183
  @rtype: int
184
  @return: the desired exit code
185

186
  """
187
  cl = GetClient()
188

    
189
  (cluster_name, ) = cl.QueryConfigValues(["cluster_name"])
190

    
191
  new_name = args[0]
192
  if not opts.force:
193
    usertext = ("This will rename the cluster from '%s' to '%s'. If you are"
194
                " connected over the network to the cluster name, the"
195
                " operation is very dangerous as the IP address will be"
196
                " removed from the node and the change may not go through."
197
                " Continue?") % (cluster_name, new_name)
198
    if not AskUser(usertext):
199
      return 1
200

    
201
  op = opcodes.OpRenameCluster(name=new_name)
202
  result = SubmitOpCode(op, opts=opts, cl=cl)
203

    
204
  if result:
205
    ToStdout("Cluster renamed from '%s' to '%s'", cluster_name, result)
206

    
207
  return 0
208

    
209

    
210
def RedistributeConfig(opts, args):
211
  """Forces push of the cluster configuration.
212

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

219
  """
220
  op = opcodes.OpRedistributeConfig()
221
  SubmitOrSend(op, opts)
222
  return 0
223

    
224

    
225
def ShowClusterVersion(opts, args):
226
  """Write version of ganeti software to the standard output.
227

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

234
  """
235
  cl = GetClient()
236
  result = cl.QueryClusterInfo()
237
  ToStdout("Software version: %s", result["software_version"])
238
  ToStdout("Internode protocol: %s", result["protocol_version"])
239
  ToStdout("Configuration format: %s", result["config_version"])
240
  ToStdout("OS api version: %s", result["os_api_version"])
241
  ToStdout("Export interface: %s", result["export_version"])
242
  return 0
243

    
244

    
245
def ShowClusterMaster(opts, args):
246
  """Write name of master node to the standard output.
247

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

254
  """
255
  master = bootstrap.GetMaster()
256
  ToStdout(master)
257
  return 0
258

    
259

    
260
def _PrintGroupedParams(paramsdict, level=1, roman=False):
261
  """Print Grouped parameters (be, nic, disk) by group.
262

263
  @type paramsdict: dict of dicts
264
  @param paramsdict: {group: {param: value, ...}, ...}
265
  @type level: int
266
  @param level: Level of indention
267

268
  """
269
  indent = "  " * level
270
  for item, val in sorted(paramsdict.items()):
271
    if isinstance(val, dict):
272
      ToStdout("%s- %s:", indent, item)
273
      _PrintGroupedParams(val, level=level + 1, roman=roman)
274
    elif roman and isinstance(val, int):
275
      ToStdout("%s  %s: %s", indent, item, compat.TryToRoman(val))
276
    else:
277
      ToStdout("%s  %s: %s", indent, item, val)
278

    
279

    
280
def ShowClusterConfig(opts, args):
281
  """Shows cluster information.
282

283
  @param opts: the command line options selected by the user
284
  @type args: list
285
  @param args: should be an empty list
286
  @rtype: int
287
  @return: the desired exit code
288

289
  """
290
  cl = GetClient()
291
  result = cl.QueryClusterInfo()
292

    
293
  ToStdout("Cluster name: %s", result["name"])
294
  ToStdout("Cluster UUID: %s", result["uuid"])
295

    
296
  ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
297
  ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))
298

    
299
  ToStdout("Master node: %s", result["master"])
300

    
301
  ToStdout("Architecture (this node): %s (%s)",
302
           result["architecture"][0], result["architecture"][1])
303

    
304
  if result["tags"]:
305
    tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
306
  else:
307
    tags = "(none)"
308

    
309
  ToStdout("Tags: %s", tags)
310

    
311
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
312
  ToStdout("Enabled hypervisors: %s",
313
           utils.CommaJoin(result["enabled_hypervisors"]))
314

    
315
  ToStdout("Hypervisor parameters:")
316
  _PrintGroupedParams(result["hvparams"])
317

    
318
  ToStdout("OS-specific hypervisor parameters:")
319
  _PrintGroupedParams(result["os_hvp"])
320

    
321
  ToStdout("OS parameters:")
322
  _PrintGroupedParams(result["osparams"])
323

    
324
  ToStdout("Cluster parameters:")
325
  ToStdout("  - candidate pool size: %s",
326
            compat.TryToRoman(result["candidate_pool_size"],
327
                              convert=opts.roman_integers))
328
  ToStdout("  - master netdev: %s", result["master_netdev"])
329
  ToStdout("  - lvm volume group: %s", result["volume_group_name"])
330
  if result["reserved_lvs"]:
331
    reserved_lvs = utils.CommaJoin(result["reserved_lvs"])
332
  else:
333
    reserved_lvs = "(none)"
334
  ToStdout("  - lvm reserved volumes: %s", reserved_lvs)
335
  ToStdout("  - drbd usermode helper: %s", result["drbd_usermode_helper"])
336
  ToStdout("  - file storage path: %s", result["file_storage_dir"])
337
  ToStdout("  - maintenance of node health: %s",
338
           result["maintain_node_health"])
339
  ToStdout("  - uid pool: %s",
340
            uidpool.FormatUidPool(result["uid_pool"],
341
                                  roman=opts.roman_integers))
342
  ToStdout("  - default instance allocator: %s", result["default_iallocator"])
343
  ToStdout("  - primary ip version: %d", result["primary_ip_version"])
344
  ToStdout("  - preallocation wipe disks: %s", result["prealloc_wipe_disks"])
345

    
346
  ToStdout("Default instance parameters:")
347
  _PrintGroupedParams(result["beparams"], roman=opts.roman_integers)
348

    
349
  ToStdout("Default nic parameters:")
350
  _PrintGroupedParams(result["nicparams"], roman=opts.roman_integers)
351

    
352
  return 0
353

    
354

    
355
def ClusterCopyFile(opts, args):
356
  """Copy a file from master to some nodes.
357

358
  @param opts: the command line options selected by the user
359
  @type args: list
360
  @param args: should contain only one element, the path of
361
      the file to be copied
362
  @rtype: int
363
  @return: the desired exit code
364

365
  """
366
  filename = args[0]
367
  if not os.path.exists(filename):
368
    raise errors.OpPrereqError("No such filename '%s'" % filename,
369
                               errors.ECODE_INVAL)
370

    
371
  cl = GetClient()
372

    
373
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
374

    
375
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
376
                           secondary_ips=opts.use_replication_network)
377

    
378
  srun = ssh.SshRunner(cluster_name=cluster_name)
379
  for node in results:
380
    if not srun.CopyFileToNode(node, filename):
381
      ToStderr("Copy of file %s to node %s failed", filename, node)
382

    
383
  return 0
384

    
385

    
386
def RunClusterCommand(opts, args):
387
  """Run a command on some nodes.
388

389
  @param opts: the command line options selected by the user
390
  @type args: list
391
  @param args: should contain the command to be run and its arguments
392
  @rtype: int
393
  @return: the desired exit code
394

395
  """
396
  cl = GetClient()
397

    
398
  command = " ".join(args)
399

    
400
  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
401

    
402
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
403
                                                    "master_node"])
404

    
405
  srun = ssh.SshRunner(cluster_name=cluster_name)
406

    
407
  # Make sure master node is at list end
408
  if master_node in nodes:
409
    nodes.remove(master_node)
410
    nodes.append(master_node)
411

    
412
  for name in nodes:
413
    result = srun.Run(name, "root", command)
414
    ToStdout("------------------------------------------------")
415
    ToStdout("node: %s", name)
416
    ToStdout("%s", result.output)
417
    ToStdout("return code = %s", result.exit_code)
418

    
419
  return 0
420

    
421

    
422
def VerifyCluster(opts, args):
423
  """Verify integrity of cluster, performing various test on nodes.
424

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

431
  """
432
  skip_checks = []
433
  if opts.skip_nplusone_mem:
434
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
435
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
436
                               verbose=opts.verbose,
437
                               error_codes=opts.error_codes,
438
                               debug_simulate_errors=opts.simulate_errors)
439
  if SubmitOpCode(op, opts=opts):
440
    return 0
441
  else:
442
    return 1
443

    
444

    
445
def VerifyDisks(opts, args):
446
  """Verify integrity of cluster disks.
447

448
  @param opts: the command line options selected by the user
449
  @type args: list
450
  @param args: should be an empty list
451
  @rtype: int
452
  @return: the desired exit code
453

454
  """
455
  cl = GetClient()
456

    
457
  op = opcodes.OpVerifyDisks()
458
  result = SubmitOpCode(op, opts=opts, cl=cl)
459
  if not isinstance(result, (list, tuple)) or len(result) != 3:
460
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
461

    
462
  bad_nodes, instances, missing = result
463

    
464
  retcode = constants.EXIT_SUCCESS
465

    
466
  if bad_nodes:
467
    for node, text in bad_nodes.items():
468
      ToStdout("Error gathering data on node %s: %s",
469
               node, utils.SafeEncode(text[-400:]))
470
      retcode |= 1
471
      ToStdout("You need to fix these nodes first before fixing instances")
472

    
473
  if instances:
474
    for iname in instances:
475
      if iname in missing:
476
        continue
477
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
478
      try:
479
        ToStdout("Activating disks for instance '%s'", iname)
480
        SubmitOpCode(op, opts=opts, cl=cl)
481
      except errors.GenericError, err:
482
        nret, msg = FormatError(err)
483
        retcode |= nret
484
        ToStderr("Error activating disks for instance %s: %s", iname, msg)
485

    
486
  if missing:
487
    (vg_name, ) = cl.QueryConfigValues(["volume_group_name"])
488

    
489
    for iname, ival in missing.iteritems():
490
      all_missing = compat.all(x[0] in bad_nodes for x in ival)
491
      if all_missing:
492
        ToStdout("Instance %s cannot be verified as it lives on"
493
                 " broken nodes", iname)
494
      else:
495
        ToStdout("Instance %s has missing logical volumes:", iname)
496
        ival.sort()
497
        for node, vol in ival:
498
          if node in bad_nodes:
499
            ToStdout("\tbroken node %s /dev/%s/%s", node, vg_name, vol)
500
          else:
501
            ToStdout("\t%s /dev/%s/%s", node, vg_name, vol)
502

    
503
    ToStdout("You need to run replace_disks for all the above"
504
             " instances, if this message persist after fixing nodes.")
505
    retcode |= 1
506

    
507
  return retcode
508

    
509

    
510
def RepairDiskSizes(opts, args):
511
  """Verify sizes of cluster disks.
512

513
  @param opts: the command line options selected by the user
514
  @type args: list
515
  @param args: optional list of instances to restrict check to
516
  @rtype: int
517
  @return: the desired exit code
518

519
  """
520
  op = opcodes.OpRepairDiskSizes(instances=args)
521
  SubmitOpCode(op, opts=opts)
522

    
523

    
524
@UsesRPC
525
def MasterFailover(opts, args):
526
  """Failover the master node.
527

528
  This command, when run on a non-master node, will cause the current
529
  master to cease being master, and the non-master to become new
530
  master.
531

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

538
  """
539
  if opts.no_voting:
540
    usertext = ("This will perform the failover even if most other nodes"
541
                " are down, or if this node is outdated. This is dangerous"
542
                " as it can lead to a non-consistent cluster. Check the"
543
                " gnt-cluster(8) man page before proceeding. Continue?")
544
    if not AskUser(usertext):
545
      return 1
546

    
547
  return bootstrap.MasterFailover(no_voting=opts.no_voting)
548

    
549

    
550
def MasterPing(opts, args):
551
  """Checks if the master is alive.
552

553
  @param opts: the command line options selected by the user
554
  @type args: list
555
  @param args: should be an empty list
556
  @rtype: int
557
  @return: the desired exit code
558

559
  """
560
  try:
561
    cl = GetClient()
562
    cl.QueryClusterInfo()
563
    return 0
564
  except Exception: # pylint: disable-msg=W0703
565
    return 1
566

    
567

    
568
def SearchTags(opts, args):
569
  """Searches the tags on all the cluster.
570

571
  @param opts: the command line options selected by the user
572
  @type args: list
573
  @param args: should contain only one element, the tag pattern
574
  @rtype: int
575
  @return: the desired exit code
576

577
  """
578
  op = opcodes.OpSearchTags(pattern=args[0])
579
  result = SubmitOpCode(op, opts=opts)
580
  if not result:
581
    return 1
582
  result = list(result)
583
  result.sort()
584
  for path, tag in result:
585
    ToStdout("%s %s", path, tag)
586

    
587

    
588
def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
589
                 new_confd_hmac_key, new_cds, cds_filename,
590
                 force):
591
  """Renews cluster certificates, keys and secrets.
592

593
  @type new_cluster_cert: bool
594
  @param new_cluster_cert: Whether to generate a new cluster certificate
595
  @type new_rapi_cert: bool
596
  @param new_rapi_cert: Whether to generate a new RAPI certificate
597
  @type rapi_cert_filename: string
598
  @param rapi_cert_filename: Path to file containing new RAPI certificate
599
  @type new_confd_hmac_key: bool
600
  @param new_confd_hmac_key: Whether to generate a new HMAC key
601
  @type new_cds: bool
602
  @param new_cds: Whether to generate a new cluster domain secret
603
  @type cds_filename: string
604
  @param cds_filename: Path to file containing new cluster domain secret
605
  @type force: bool
606
  @param force: Whether to ask user for confirmation
607

608
  """
609
  if new_rapi_cert and rapi_cert_filename:
610
    ToStderr("Only one of the --new-rapi-certficate and --rapi-certificate"
611
             " options can be specified at the same time.")
612
    return 1
613

    
614
  if new_cds and cds_filename:
615
    ToStderr("Only one of the --new-cluster-domain-secret and"
616
             " --cluster-domain-secret options can be specified at"
617
             " the same time.")
618
    return 1
619

    
620
  if rapi_cert_filename:
621
    # Read and verify new certificate
622
    try:
623
      rapi_cert_pem = utils.ReadFile(rapi_cert_filename)
624

    
625
      OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
626
                                      rapi_cert_pem)
627
    except Exception, err: # pylint: disable-msg=W0703
628
      ToStderr("Can't load new RAPI certificate from %s: %s" %
629
               (rapi_cert_filename, str(err)))
630
      return 1
631

    
632
    try:
633
      OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, rapi_cert_pem)
634
    except Exception, err: # pylint: disable-msg=W0703
635
      ToStderr("Can't load new RAPI private key from %s: %s" %
636
               (rapi_cert_filename, str(err)))
637
      return 1
638

    
639
  else:
640
    rapi_cert_pem = None
641

    
642
  if cds_filename:
643
    try:
644
      cds = utils.ReadFile(cds_filename)
645
    except Exception, err: # pylint: disable-msg=W0703
646
      ToStderr("Can't load new cluster domain secret from %s: %s" %
647
               (cds_filename, str(err)))
648
      return 1
649
  else:
650
    cds = None
651

    
652
  if not force:
653
    usertext = ("This requires all daemons on all nodes to be restarted and"
654
                " may take some time. Continue?")
655
    if not AskUser(usertext):
656
      return 1
657

    
658
  def _RenewCryptoInner(ctx):
659
    ctx.feedback_fn("Updating certificates and keys")
660
    bootstrap.GenerateClusterCrypto(new_cluster_cert, new_rapi_cert,
661
                                    new_confd_hmac_key,
662
                                    new_cds,
663
                                    rapi_cert_pem=rapi_cert_pem,
664
                                    cds=cds)
665

    
666
    files_to_copy = []
667

    
668
    if new_cluster_cert:
669
      files_to_copy.append(constants.NODED_CERT_FILE)
670

    
671
    if new_rapi_cert or rapi_cert_pem:
672
      files_to_copy.append(constants.RAPI_CERT_FILE)
673

    
674
    if new_confd_hmac_key:
675
      files_to_copy.append(constants.CONFD_HMAC_KEY)
676

    
677
    if new_cds or cds:
678
      files_to_copy.append(constants.CLUSTER_DOMAIN_SECRET_FILE)
679

    
680
    if files_to_copy:
681
      for node_name in ctx.nonmaster_nodes:
682
        ctx.feedback_fn("Copying %s to %s" %
683
                        (", ".join(files_to_copy), node_name))
684
        for file_name in files_to_copy:
685
          ctx.ssh.CopyFileToNode(node_name, file_name)
686

    
687
  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
688

    
689
  ToStdout("All requested certificates and keys have been replaced."
690
           " Running \"gnt-cluster verify\" now is recommended.")
691

    
692
  return 0
693

    
694

    
695
def RenewCrypto(opts, args):
696
  """Renews cluster certificates, keys and secrets.
697

698
  """
699
  return _RenewCrypto(opts.new_cluster_cert,
700
                      opts.new_rapi_cert,
701
                      opts.rapi_cert,
702
                      opts.new_confd_hmac_key,
703
                      opts.new_cluster_domain_secret,
704
                      opts.cluster_domain_secret,
705
                      opts.force)
706

    
707

    
708
def SetClusterParams(opts, args):
709
  """Modify the cluster.
710

711
  @param opts: the command line options selected by the user
712
  @type args: list
713
  @param args: should be an empty list
714
  @rtype: int
715
  @return: the desired exit code
716

717
  """
718
  if not (not opts.lvm_storage or opts.vg_name or
719
          not opts.drbd_storage or opts.drbd_helper or
720
          opts.enabled_hypervisors or opts.hvparams or
721
          opts.beparams or opts.nicparams or opts.ndparams or
722
          opts.candidate_pool_size is not None or
723
          opts.uid_pool is not None or
724
          opts.maintain_node_health is not None or
725
          opts.add_uids is not None or
726
          opts.remove_uids is not None or
727
          opts.default_iallocator is not None or
728
          opts.reserved_lvs is not None or
729
          opts.master_netdev is not None or
730
          opts.prealloc_wipe_disks is not None):
731
    ToStderr("Please give at least one of the parameters.")
732
    return 1
733

    
734
  vg_name = opts.vg_name
735
  if not opts.lvm_storage and opts.vg_name:
736
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
737
    return 1
738

    
739
  if not opts.lvm_storage:
740
    vg_name = ""
741

    
742
  drbd_helper = opts.drbd_helper
743
  if not opts.drbd_storage and opts.drbd_helper:
744
    ToStderr("Options --no-drbd-storage and --drbd-usermode-helper conflict.")
745
    return 1
746

    
747
  if not opts.drbd_storage:
748
    drbd_helper = ""
749

    
750
  hvlist = opts.enabled_hypervisors
751
  if hvlist is not None:
752
    hvlist = hvlist.split(",")
753

    
754
  # a list of (name, dict) we can pass directly to dict() (or [])
755
  hvparams = dict(opts.hvparams)
756
  for hv_params in hvparams.values():
757
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
758

    
759
  beparams = opts.beparams
760
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
761

    
762
  nicparams = opts.nicparams
763
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
764

    
765
  ndparams = opts.ndparams
766
  if ndparams is not None:
767
    utils.ForceDictType(ndparams, constants.NDS_PARAMETER_TYPES)
768

    
769
  mnh = opts.maintain_node_health
770

    
771
  uid_pool = opts.uid_pool
772
  if uid_pool is not None:
773
    uid_pool = uidpool.ParseUidPool(uid_pool)
774

    
775
  add_uids = opts.add_uids
776
  if add_uids is not None:
777
    add_uids = uidpool.ParseUidPool(add_uids)
778

    
779
  remove_uids = opts.remove_uids
780
  if remove_uids is not None:
781
    remove_uids = uidpool.ParseUidPool(remove_uids)
782

    
783
  if opts.reserved_lvs is not None:
784
    if opts.reserved_lvs == "":
785
      opts.reserved_lvs = []
786
    else:
787
      opts.reserved_lvs = utils.UnescapeAndSplit(opts.reserved_lvs, sep=",")
788

    
789
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
790
                                  drbd_helper=drbd_helper,
791
                                  enabled_hypervisors=hvlist,
792
                                  hvparams=hvparams,
793
                                  os_hvp=None,
794
                                  beparams=beparams,
795
                                  nicparams=nicparams,
796
                                  ndparams=ndparams,
797
                                  candidate_pool_size=opts.candidate_pool_size,
798
                                  maintain_node_health=mnh,
799
                                  uid_pool=uid_pool,
800
                                  add_uids=add_uids,
801
                                  remove_uids=remove_uids,
802
                                  default_iallocator=opts.default_iallocator,
803
                                  prealloc_wipe_disks=opts.prealloc_wipe_disks,
804
                                  master_netdev=opts.master_netdev,
805
                                  reserved_lvs=opts.reserved_lvs)
806
  SubmitOpCode(op, opts=opts)
807
  return 0
808

    
809

    
810
def QueueOps(opts, args):
811
  """Queue operations.
812

813
  @param opts: the command line options selected by the user
814
  @type args: list
815
  @param args: should contain only one element, the subcommand
816
  @rtype: int
817
  @return: the desired exit code
818

819
  """
820
  command = args[0]
821
  client = GetClient()
822
  if command in ("drain", "undrain"):
823
    drain_flag = command == "drain"
824
    client.SetQueueDrainFlag(drain_flag)
825
  elif command == "info":
826
    result = client.QueryConfigValues(["drain_flag"])
827
    if result[0]:
828
      val = "set"
829
    else:
830
      val = "unset"
831
    ToStdout("The drain flag is %s" % val)
832
  else:
833
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
834
                               errors.ECODE_INVAL)
835

    
836
  return 0
837

    
838

    
839
def _ShowWatcherPause(until):
840
  if until is None or until < time.time():
841
    ToStdout("The watcher is not paused.")
842
  else:
843
    ToStdout("The watcher is paused until %s.", time.ctime(until))
844

    
845

    
846
def WatcherOps(opts, args):
847
  """Watcher operations.
848

849
  @param opts: the command line options selected by the user
850
  @type args: list
851
  @param args: should contain only one element, the subcommand
852
  @rtype: int
853
  @return: the desired exit code
854

855
  """
856
  command = args[0]
857
  client = GetClient()
858

    
859
  if command == "continue":
860
    client.SetWatcherPause(None)
861
    ToStdout("The watcher is no longer paused.")
862

    
863
  elif command == "pause":
864
    if len(args) < 2:
865
      raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
866

    
867
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
868
    _ShowWatcherPause(result)
869

    
870
  elif command == "info":
871
    result = client.QueryConfigValues(["watcher_pause"])
872
    _ShowWatcherPause(result[0])
873

    
874
  else:
875
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
876
                               errors.ECODE_INVAL)
877

    
878
  return 0
879

    
880

    
881
commands = {
882
  'init': (
883
    InitCluster, [ArgHost(min=1, max=1)],
884
    [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, GLOBAL_FILEDIR_OPT,
885
     HVLIST_OPT, MAC_PREFIX_OPT, MASTER_NETDEV_OPT, NIC_PARAMS_OPT,
886
     NOLVM_STORAGE_OPT, NOMODIFY_ETCHOSTS_OPT, NOMODIFY_SSH_SETUP_OPT,
887
     SECONDARY_IP_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT,
888
     UIDPOOL_OPT, DRBD_HELPER_OPT, NODRBD_STORAGE_OPT,
889
     DEFAULT_IALLOCATOR_OPT, PRIMARY_IP_VERSION_OPT, PREALLOC_WIPE_DISKS_OPT,
890
     NODE_PARAMS_OPT],
891
    "[opts...] <cluster_name>", "Initialises a new cluster configuration"),
892
  'destroy': (
893
    DestroyCluster, ARGS_NONE, [YES_DOIT_OPT],
894
    "", "Destroy cluster"),
895
  'rename': (
896
    RenameCluster, [ArgHost(min=1, max=1)],
897
    [FORCE_OPT, DRY_RUN_OPT],
898
    "<new_name>",
899
    "Renames the cluster"),
900
  'redist-conf': (
901
    RedistributeConfig, ARGS_NONE, [SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
902
    "", "Forces a push of the configuration file and ssconf files"
903
    " to the nodes in the cluster"),
904
  'verify': (
905
    VerifyCluster, ARGS_NONE,
906
    [VERBOSE_OPT, DEBUG_SIMERR_OPT, ERROR_CODES_OPT, NONPLUS1_OPT,
907
     DRY_RUN_OPT, PRIORITY_OPT],
908
    "", "Does a check on the cluster configuration"),
909
  'verify-disks': (
910
    VerifyDisks, ARGS_NONE, [PRIORITY_OPT],
911
    "", "Does a check on the cluster disk status"),
912
  'repair-disk-sizes': (
913
    RepairDiskSizes, ARGS_MANY_INSTANCES, [DRY_RUN_OPT, PRIORITY_OPT],
914
    "", "Updates mismatches in recorded disk sizes"),
915
  'master-failover': (
916
    MasterFailover, ARGS_NONE, [NOVOTING_OPT],
917
    "", "Makes the current node the master"),
918
  'master-ping': (
919
    MasterPing, ARGS_NONE, [],
920
    "", "Checks if the master is alive"),
921
  'version': (
922
    ShowClusterVersion, ARGS_NONE, [],
923
    "", "Shows the cluster version"),
924
  'getmaster': (
925
    ShowClusterMaster, ARGS_NONE, [],
926
    "", "Shows the cluster master"),
927
  'copyfile': (
928
    ClusterCopyFile, [ArgFile(min=1, max=1)],
929
    [NODE_LIST_OPT, USE_REPL_NET_OPT],
930
    "[-n node...] <filename>", "Copies a file to all (or only some) nodes"),
931
  'command': (
932
    RunClusterCommand, [ArgCommand(min=1)],
933
    [NODE_LIST_OPT],
934
    "[-n node...] <command>", "Runs a command on all (or only some) nodes"),
935
  'info': (
936
    ShowClusterConfig, ARGS_NONE, [ROMAN_OPT],
937
    "[--roman]", "Show cluster configuration"),
938
  'list-tags': (
939
    ListTags, ARGS_NONE, [], "", "List the tags of the cluster"),
940
  'add-tags': (
941
    AddTags, [ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT],
942
    "tag...", "Add tags to the cluster"),
943
  'remove-tags': (
944
    RemoveTags, [ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT],
945
    "tag...", "Remove tags from the cluster"),
946
  'search-tags': (
947
    SearchTags, [ArgUnknown(min=1, max=1)], [PRIORITY_OPT], "",
948
    "Searches the tags on all objects on"
949
    " the cluster for a given pattern (regex)"),
950
  'queue': (
951
    QueueOps,
952
    [ArgChoice(min=1, max=1, choices=["drain", "undrain", "info"])],
953
    [], "drain|undrain|info", "Change queue properties"),
954
  'watcher': (
955
    WatcherOps,
956
    [ArgChoice(min=1, max=1, choices=["pause", "continue", "info"]),
957
     ArgSuggest(min=0, max=1, choices=["30m", "1h", "4h"])],
958
    [],
959
    "{pause <timespec>|continue|info}", "Change watcher properties"),
960
  'modify': (
961
    SetClusterParams, ARGS_NONE,
962
    [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, HVLIST_OPT, MASTER_NETDEV_OPT,
963
     NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT,
964
     UIDPOOL_OPT, ADD_UIDS_OPT, REMOVE_UIDS_OPT, DRBD_HELPER_OPT,
965
     NODRBD_STORAGE_OPT, DEFAULT_IALLOCATOR_OPT, RESERVED_LVS_OPT,
966
     DRY_RUN_OPT, PRIORITY_OPT, PREALLOC_WIPE_DISKS_OPT, NODE_PARAMS_OPT],
967
    "[opts...]",
968
    "Alters the parameters of the cluster"),
969
  "renew-crypto": (
970
    RenewCrypto, ARGS_NONE,
971
    [NEW_CLUSTER_CERT_OPT, NEW_RAPI_CERT_OPT, RAPI_CERT_OPT,
972
     NEW_CONFD_HMAC_KEY_OPT, FORCE_OPT,
973
     NEW_CLUSTER_DOMAIN_SECRET_OPT, CLUSTER_DOMAIN_SECRET_OPT],
974
    "[opts...]",
975
    "Renews cluster certificates, keys and secrets"),
976
  }
977

    
978

    
979
#: dictionary with aliases for commands
980
aliases = {
981
  'masterfailover': 'master-failover',
982
}
983

    
984

    
985
def Main():
986
  return GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER},
987
                     aliases=aliases)