gnt-cluster 27.9 KB
Newer Older
Iustin Pop's avatar
Iustin Pop committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/python
#

# Copyright (C) 2006, 2007 Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.

Iustin Pop's avatar
Iustin Pop committed
21
"""Cluster related commands"""
Iustin Pop's avatar
Iustin Pop committed
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)
Iustin Pop's avatar
Iustin Pop committed
27
# C0103: Invalid name gnt-cluster
28

Iustin Pop's avatar
Iustin Pop committed
29
import sys
30
import os.path
31
import time
32
import OpenSSL
Iustin Pop's avatar
Iustin Pop committed
33
34
35

from ganeti.cli import *
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
Iustin Pop's avatar
Iustin Pop committed
44
45


46
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
47
48
49
def InitCluster(opts, args):
  """Initialize the cluster.

50
51
52
53
54
55
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the desired
      cluster name
  @rtype: int
  @return: the desired exit code
Iustin Pop's avatar
Iustin Pop committed
56
57

  """
58
  if not opts.lvm_storage and opts.vg_name:
59
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
60
61
62
63
64
65
    return 1

  vg_name = opts.vg_name
  if opts.lvm_storage and not opts.vg_name:
    vg_name = constants.DEFAULT_VG

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

  drbd_helper = opts.drbd_helper
  if opts.drbd_storage and not opts.drbd_helper:
    drbd_helper = constants.DEFAULT_DRBD_HELPER

74
  hvlist = opts.enabled_hypervisors
75
76
  if hvlist is None:
    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
88
89
90
  # prepare nicparams dict
  nicparams = objects.FillDict(constants.NICC_DEFAULTS, nicparams)
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)

91
92
93
94
  # prepare hvparams dict
  for hv in constants.HYPER_TYPES:
    if hv not in hvparams:
      hvparams[hv] = {}
95
    hvparams[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], hvparams[hv])
96
    utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
97

98
99
100
  if opts.candidate_pool_size is None:
    opts.candidate_pool_size = constants.MASTER_POOL_SIZE_DEFAULT

101
102
103
  if opts.mac_prefix is None:
    opts.mac_prefix = constants.DEFAULT_MAC_PREFIX

104
105
106
107
  uid_pool = opts.uid_pool
  if uid_pool is not None:
    uid_pool = uidpool.ParseUidPool(uid_pool)

108
109
110
111
112
  bootstrap.InitCluster(cluster_name=args[0],
                        secondary_ip=opts.secondary_ip,
                        vg_name=vg_name,
                        mac_prefix=opts.mac_prefix,
                        master_netdev=opts.master_netdev,
113
114
115
                        file_storage_dir=opts.file_storage_dir,
                        enabled_hypervisors=hvlist,
                        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)
Iustin Pop's avatar
Iustin Pop committed
128
129
130
  return 0


131
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
132
133
134
def DestroyCluster(opts, args):
  """Destroy the cluster.

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

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

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


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

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

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

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


179
180
181
182
183
184
185
186
187
188
def RedistributeConfig(opts, args):
  """Forces push of the cluster configuration.

  @param opts: the command line options selected by the user
  @type args: list
  @param args: empty list
  @rtype: int
  @return: the desired exit code

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


Iustin Pop's avatar
Iustin Pop committed
194
195
196
def ShowClusterVersion(opts, args):
  """Write version of ganeti software to the standard output.

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

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


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

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

  """
224
225
  master = bootstrap.GetMaster()
  ToStdout(master)
Iustin Pop's avatar
Iustin Pop committed
226
227
  return 0

228

Guido Trotter's avatar
Guido Trotter committed
229
def _PrintGroupedParams(paramsdict, level=1, roman=False):
230
231
232
233
  """Print Grouped parameters (be, nic, disk) by group.

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

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

248

Iustin Pop's avatar
Iustin Pop committed
249
250
251
def ShowClusterConfig(opts, args):
  """Shows cluster information.

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

Iustin Pop's avatar
Iustin Pop committed
258
  """
259
260
  cl = GetClient()
  result = cl.QueryClusterInfo()
Iustin Pop's avatar
Iustin Pop committed
261

262
  ToStdout("Cluster name: %s", result["name"])
263
  ToStdout("Cluster UUID: %s", result["uuid"])
Iustin Pop's avatar
Iustin Pop committed
264

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

268
  ToStdout("Master node: %s", result["master"])
Iustin Pop's avatar
Iustin Pop committed
269

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

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

  ToStdout("Tags: %s", tags)

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

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

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

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

293
  ToStdout("Cluster parameters:")
Guido Trotter's avatar
Guido Trotter committed
294
295
296
  ToStdout("  - candidate pool size: %s",
            compat.TryToRoman(result["candidate_pool_size"],
                              convert=opts.roman_integers))
297
298
  ToStdout("  - master netdev: %s", result["master_netdev"])
  ToStdout("  - lvm volume group: %s", result["volume_group_name"])
299
300
301
302
303
  if result["reserved_lvs"]:
    reserved_lvs = utils.CommaJoin(result["reserved_lvs"])
  else:
    reserved_lvs = "(none)"
  ToStdout("  - lvm reserved volumes: %s", reserved_lvs)
304
  ToStdout("  - drbd usermode helper: %s", result["drbd_usermode_helper"])
305
  ToStdout("  - file storage path: %s", result["file_storage_dir"])
306
307
  ToStdout("  - maintenance of node health: %s",
           result["maintain_node_health"])
Guido Trotter's avatar
Guido Trotter committed
308
309
310
  ToStdout("  - uid pool: %s",
            uidpool.FormatUidPool(result["uid_pool"],
                                  roman=opts.roman_integers))
311
  ToStdout("  - default instance allocator: %s", result["default_iallocator"])
312
313

  ToStdout("Default instance parameters:")
Guido Trotter's avatar
Guido Trotter committed
314
  _PrintGroupedParams(result["beparams"], roman=opts.roman_integers)
315
316

  ToStdout("Default nic parameters:")
Guido Trotter's avatar
Guido Trotter committed
317
  _PrintGroupedParams(result["nicparams"], roman=opts.roman_integers)
318

Iustin Pop's avatar
Iustin Pop committed
319
320
321
322
323
324
  return 0


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

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

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

Iustin Pop's avatar
Iustin Pop committed
338
339
340
341
  cl = GetClient()

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

342
343
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
                           secondary_ips=opts.use_replication_network)
Michael Hanselmann's avatar
Michael Hanselmann committed
344

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

Iustin Pop's avatar
Iustin Pop committed
350
351
352
353
354
355
  return 0


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

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

  """
Iustin Pop's avatar
Iustin Pop committed
363
  cl = GetClient()
Michael Hanselmann's avatar
Michael Hanselmann committed
364

Iustin Pop's avatar
Iustin Pop committed
365
  command = " ".join(args)
366
367

  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
Iustin Pop's avatar
Iustin Pop committed
368
369
370

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

Iustin Pop's avatar
Iustin Pop committed
372
  srun = ssh.SshRunner(cluster_name=cluster_name)
373

Michael Hanselmann's avatar
Michael Hanselmann committed
374
  # Make sure master node is at list end
375
376
377
378
379
380
  if master_node in nodes:
    nodes.remove(master_node)
    nodes.append(master_node)

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

  return 0
Iustin Pop's avatar
Iustin Pop committed
387
388
389
390
391


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

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

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


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

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

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

427
  bad_nodes, instances, missing = result
428

429
  retcode = constants.EXIT_SUCCESS
430

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

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

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

  return retcode


472
473
474
475
476
477
478
479
480
481
482
def RepairDiskSizes(opts, args):
  """Verify sizes of cluster disks.

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

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


486
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
487
488
489
490
491
492
493
def MasterFailover(opts, args):
  """Failover the master node.

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

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

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

  return bootstrap.MasterFailover(no_voting=opts.no_voting)
Iustin Pop's avatar
Iustin Pop committed
510
511


Iustin Pop's avatar
Iustin Pop committed
512
513
514
def SearchTags(opts, args):
  """Searches the tags on all the cluster.

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

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


532
def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
Michael Hanselmann's avatar
Michael Hanselmann committed
533
534
                 new_confd_hmac_key, new_cds, cds_filename,
                 force):
535
536
537
538
539
540
541
542
  """Renews cluster certificates, keys and secrets.

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

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

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

564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
  if rapi_cert_filename:
    # Read and verify new certificate
    try:
      rapi_cert_pem = utils.ReadFile(rapi_cert_filename)

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

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

  else:
    rapi_cert_pem = None

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

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

  def _RenewCryptoInner(ctx):
    ctx.feedback_fn("Updating certificates and keys")
    bootstrap.GenerateClusterCrypto(new_cluster_cert, new_rapi_cert,
605
                                    new_confd_hmac_key,
Michael Hanselmann's avatar
Michael Hanselmann committed
606
607
608
                                    new_cds,
                                    rapi_cert_pem=rapi_cert_pem,
                                    cds=cds)
609
610
611
612

    files_to_copy = []

    if new_cluster_cert:
613
      files_to_copy.append(constants.NODED_CERT_FILE)
614
615
616
617

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

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

Michael Hanselmann's avatar
Michael Hanselmann committed
621
622
623
    if new_cds or cds:
      files_to_copy.append(constants.CLUSTER_DOMAIN_SECRET_FILE)

624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
    if files_to_copy:
      for node_name in ctx.nonmaster_nodes:
        ctx.feedback_fn("Copying %s to %s" %
                        (", ".join(files_to_copy), node_name))
        for file_name in files_to_copy:
          ctx.ssh.CopyFileToNode(node_name, file_name)

  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)

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

  return 0


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

  """
  return _RenewCrypto(opts.new_cluster_cert,
                      opts.new_rapi_cert,
                      opts.rapi_cert,
646
                      opts.new_confd_hmac_key,
Michael Hanselmann's avatar
Michael Hanselmann committed
647
648
                      opts.new_cluster_domain_secret,
                      opts.cluster_domain_secret,
649
650
651
                      opts.force)


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

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

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

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

  if not opts.lvm_storage:
    vg_name = ""
682

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

  if not opts.drbd_storage:
    drbd_helper = ""

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

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

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

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

706

707
708
  mnh = opts.maintain_node_health

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

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

  remove_uids = opts.remove_uids
  if remove_uids is not None:
    remove_uids = uidpool.ParseUidPool(remove_uids)

721
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
722
                                  drbd_helper=drbd_helper,
723
724
                                  enabled_hypervisors=hvlist,
                                  hvparams=hvparams,
725
                                  os_hvp=None,
726
                                  beparams=beparams,
727
                                  nicparams=nicparams,
728
                                  candidate_pool_size=opts.candidate_pool_size,
729
                                  maintain_node_health=mnh,
730
731
                                  uid_pool=uid_pool,
                                  add_uids=add_uids,
732
733
                                  remove_uids=remove_uids,
                                  default_iallocator=opts.default_iallocator)
734
  SubmitOpCode(op, opts=opts)
735
736
737
  return 0


738
739
740
def QueueOps(opts, args):
  """Queue operations.

741
742
743
744
745
746
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the subcommand
  @rtype: int
  @return: the desired exit code

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

764
765
  return 0

766

767
768
769
770
771
772
773
def _ShowWatcherPause(until):
  if until is None or until < time.time():
    ToStdout("The watcher is not paused.")
  else:
    ToStdout("The watcher is paused until %s.", time.ctime(until))


774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
def WatcherOps(opts, args):
  """Watcher operations.

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

  """
  command = args[0]
  client = GetClient()

  if command == "continue":
    client.SetWatcherPause(None)
789
    ToStdout("The watcher is no longer paused.")
790
791
792

  elif command == "pause":
    if len(args) < 2:
793
      raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
794

795
796
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
    _ShowWatcherPause(result)
797
798
799

  elif command == "info":
    result = client.QueryConfigValues(["watcher_pause"])
800
    _ShowWatcherPause(result[0])
801
802

  else:
803
804
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
                               errors.ECODE_INVAL)
805
806
807
808

  return 0


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

900

Iustin Pop's avatar
Iustin Pop committed
901
if __name__ == '__main__':
902
  sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))