gnt-cluster 26.5 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
  hvlist = opts.enabled_hypervisors
67
68
  if hvlist is None:
    hvlist = constants.DEFAULT_ENABLED_HYPERVISOR
69
  hvlist = hvlist.split(",")
70

71
  hvparams = dict(opts.hvparams)
72
  beparams = opts.beparams
73
  nicparams = opts.nicparams
74
75

  # prepare beparams dict
76
  beparams = objects.FillDict(constants.BEC_DEFAULTS, beparams)
77
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
78

79
80
81
82
  # prepare nicparams dict
  nicparams = objects.FillDict(constants.NICC_DEFAULTS, nicparams)
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)

83
84
85
86
  # prepare hvparams dict
  for hv in constants.HYPER_TYPES:
    if hv not in hvparams:
      hvparams[hv] = {}
87
    hvparams[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], hvparams[hv])
88
    utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
89

90
91
92
  if opts.candidate_pool_size is None:
    opts.candidate_pool_size = constants.MASTER_POOL_SIZE_DEFAULT

93
94
95
  if opts.mac_prefix is None:
    opts.mac_prefix = constants.DEFAULT_MAC_PREFIX

96
97
98
99
  uid_pool = opts.uid_pool
  if uid_pool is not None:
    uid_pool = uidpool.ParseUidPool(uid_pool)

100
101
102
103
104
  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,
105
106
107
                        file_storage_dir=opts.file_storage_dir,
                        enabled_hypervisors=hvlist,
                        hvparams=hvparams,
108
                        beparams=beparams,
109
                        nicparams=nicparams,
110
                        candidate_pool_size=opts.candidate_pool_size,
111
                        modify_etc_hosts=opts.modify_etc_hosts,
112
                        modify_ssh_setup=opts.modify_ssh_setup,
113
                        maintain_node_health=opts.maintain_node_health,
114
                        uid_pool=uid_pool,
115
                        )
116
  op = opcodes.OpPostInitCluster()
117
  SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
118
119
120
  return 0


121
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
122
123
124
def DestroyCluster(opts, args):
  """Destroy the cluster.

125
126
127
128
129
  @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
130

Iustin Pop's avatar
Iustin Pop committed
131
132
  """
  if not opts.yes_do_it:
133
134
    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
135
136
137
    return 1

  op = opcodes.OpDestroyCluster()
138
  master = SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
139
140
141
  # 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
142
143
144
  return 0


145
146
147
def RenameCluster(opts, args):
  """Rename the cluster.

148
149
150
151
152
  @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
153
154
155
156
157
158
159
160

  """
  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
161
    if not AskUser(usertext):
162
163
164
      return 1

  op = opcodes.OpRenameCluster(name=name)
165
  SubmitOpCode(op, opts=opts)
166
167
168
  return 0


169
170
171
172
173
174
175
176
177
178
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

  """
179
  op = opcodes.OpRedistributeConfig()
180
181
182
183
  SubmitOrSend(op, opts)
  return 0


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

187
188
189
190
191
  @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
192
193

  """
194
195
  cl = GetClient()
  result = cl.QueryClusterInfo()
196
197
198
199
200
  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
201
202
203
204
205
206
  return 0


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

207
208
209
210
211
  @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
212
213

  """
214
215
  master = bootstrap.GetMaster()
  ToStdout(master)
Iustin Pop's avatar
Iustin Pop committed
216
217
  return 0

218

Guido Trotter's avatar
Guido Trotter committed
219
def _PrintGroupedParams(paramsdict, level=1, roman=False):
220
221
222
223
  """Print Grouped parameters (be, nic, disk) by group.

  @type paramsdict: dict of dicts
  @param paramsdict: {group: {param: value, ...}, ...}
224
225
  @type level: int
  @param level: Level of indention
226
227

  """
228
  indent = "  " * level
229
  for item, val in sorted(paramsdict.items()):
230
231
    if isinstance(val, dict):
      ToStdout("%s- %s:", indent, item)
Guido Trotter's avatar
Guido Trotter committed
232
233
234
      _PrintGroupedParams(val, level=level + 1, roman=roman)
    elif roman and isinstance(val, int):
      ToStdout("%s  %s: %s", indent, item, compat.TryToRoman(val))
235
236
    else:
      ToStdout("%s  %s: %s", indent, item, val)
Iustin Pop's avatar
Iustin Pop committed
237

238

Iustin Pop's avatar
Iustin Pop committed
239
240
241
def ShowClusterConfig(opts, args):
  """Shows cluster information.

242
243
244
245
246
247
  @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
248
  """
249
250
  cl = GetClient()
  result = cl.QueryClusterInfo()
Iustin Pop's avatar
Iustin Pop committed
251

252
  ToStdout("Cluster name: %s", result["name"])
253
  ToStdout("Cluster UUID: %s", result["uuid"])
Iustin Pop's avatar
Iustin Pop committed
254

255
256
257
  ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
  ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))

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

260
261
  ToStdout("Architecture (this node): %s (%s)",
           result["architecture"][0], result["architecture"][1])
Iustin Pop's avatar
Iustin Pop committed
262

263
  if result["tags"]:
264
    tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
265
266
267
268
269
  else:
    tags = "(none)"

  ToStdout("Tags: %s", tags)

270
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
271
272
  ToStdout("Enabled hypervisors: %s",
           utils.CommaJoin(result["enabled_hypervisors"]))
273

274
  ToStdout("Hypervisor parameters:")
275
  _PrintGroupedParams(result["hvparams"])
276

277
278
279
  ToStdout("OS specific hypervisor parameters:")
  _PrintGroupedParams(result["os_hvp"])

280
  ToStdout("Cluster parameters:")
Guido Trotter's avatar
Guido Trotter committed
281
282
283
  ToStdout("  - candidate pool size: %s",
            compat.TryToRoman(result["candidate_pool_size"],
                              convert=opts.roman_integers))
284
285
286
  ToStdout("  - master netdev: %s", result["master_netdev"])
  ToStdout("  - lvm volume group: %s", result["volume_group_name"])
  ToStdout("  - file storage path: %s", result["file_storage_dir"])
287
288
  ToStdout("  - maintenance of node health: %s",
           result["maintain_node_health"])
Guido Trotter's avatar
Guido Trotter committed
289
290
291
  ToStdout("  - uid pool: %s",
            uidpool.FormatUidPool(result["uid_pool"],
                                  roman=opts.roman_integers))
292
293

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

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

Iustin Pop's avatar
Iustin Pop committed
299
300
301
302
303
304
  return 0


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

305
306
307
308
309
310
  @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
311
312

  """
313
314
  filename = args[0]
  if not os.path.exists(filename):
315
316
    raise errors.OpPrereqError("No such filename '%s'" % filename,
                               errors.ECODE_INVAL)
317

Iustin Pop's avatar
Iustin Pop committed
318
319
320
321
  cl = GetClient()

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

322
323
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
                           secondary_ips=opts.use_replication_network)
Michael Hanselmann's avatar
Michael Hanselmann committed
324

Iustin Pop's avatar
Iustin Pop committed
325
  srun = ssh.SshRunner(cluster_name=cluster_name)
326
327
  for node in results:
    if not srun.CopyFileToNode(node, filename):
328
      ToStderr("Copy of file %s to node %s failed", filename, node)
329

Iustin Pop's avatar
Iustin Pop committed
330
331
332
333
334
335
  return 0


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

336
337
338
339
340
  @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
341
342

  """
Iustin Pop's avatar
Iustin Pop committed
343
  cl = GetClient()
Michael Hanselmann's avatar
Michael Hanselmann committed
344

Iustin Pop's avatar
Iustin Pop committed
345
  command = " ".join(args)
346
347

  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
Iustin Pop's avatar
Iustin Pop committed
348
349
350

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

Iustin Pop's avatar
Iustin Pop committed
352
  srun = ssh.SshRunner(cluster_name=cluster_name)
353

Michael Hanselmann's avatar
Michael Hanselmann committed
354
  # Make sure master node is at list end
355
356
357
358
359
360
  if master_node in nodes:
    nodes.remove(master_node)
    nodes.append(master_node)

  for name in nodes:
    result = srun.Run(name, "root", command)
361
362
363
364
    ToStdout("------------------------------------------------")
    ToStdout("node: %s", name)
    ToStdout("%s", result.output)
    ToStdout("return code = %s", result.exit_code)
365
366

  return 0
Iustin Pop's avatar
Iustin Pop committed
367
368
369
370
371


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

372
373
374
375
376
  @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
377
378

  """
Iustin Pop's avatar
Iustin Pop committed
379
  skip_checks = []
380
381
  if opts.skip_nplusone_mem:
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
382
383
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
                               verbose=opts.verbose,
384
385
                               error_codes=opts.error_codes,
                               debug_simulate_errors=opts.simulate_errors)
386
  if SubmitOpCode(op, opts=opts):
387
388
389
    return 0
  else:
    return 1
Iustin Pop's avatar
Iustin Pop committed
390
391


392
393
394
def VerifyDisks(opts, args):
  """Verify integrity of cluster disks.

395
396
397
398
399
  @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
400
401
402

  """
  op = opcodes.OpVerifyDisks()
403
  result = SubmitOpCode(op, opts=opts)
404
  if not isinstance(result, (list, tuple)) or len(result) != 3:
405
406
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")

407
  bad_nodes, instances, missing = result
408

409
  retcode = constants.EXIT_SUCCESS
410

411
412
413
  if bad_nodes:
    for node, text in bad_nodes.items():
      ToStdout("Error gathering data on node %s: %s",
414
               node, utils.SafeEncode(text[-400:]))
415
      retcode |= 1
416
      ToStdout("You need to fix these nodes first before fixing instances")
417

418
419
  if instances:
    for iname in instances:
420
421
      if iname in missing:
        continue
422
423
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
      try:
424
        ToStdout("Activating disks for instance '%s'", iname)
425
        SubmitOpCode(op, opts=opts)
426
427
428
      except errors.GenericError, err:
        nret, msg = FormatError(err)
        retcode |= nret
429
        ToStderr("Error activating disks for instance %s: %s", iname, msg)
430
431
432

  if missing:
    for iname, ival in missing.iteritems():
433
      all_missing = compat.all(ival, lambda x: x[0] in bad_nodes)
434
      if all_missing:
435
436
        ToStdout("Instance %s cannot be verified as it lives on"
                 " broken nodes", iname)
437
      else:
438
        ToStdout("Instance %s has missing logical volumes:", iname)
439
440
        ival.sort()
        for node, vol in ival:
441
          if node in bad_nodes:
442
            ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
443
          else:
444
445
            ToStdout("\t%s /dev/xenvg/%s", node, vol)
    ToStdout("You need to run replace_disks for all the above"
446
447
           " instances, if this message persist after fixing nodes.")
    retcode |= 1
448
449
450
451

  return retcode


452
453
454
455
456
457
458
459
460
461
462
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)
463
  SubmitOpCode(op, opts=opts)
464
465


466
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
467
468
469
470
471
472
473
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.

474
475
476
477
478
479
  @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
480
  """
481
482
483
484
485
486
487
488
489
  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
490
491


Iustin Pop's avatar
Iustin Pop committed
492
493
494
def SearchTags(opts, args):
  """Searches the tags on all the cluster.

495
496
497
498
499
500
  @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
501
502
  """
  op = opcodes.OpSearchTags(pattern=args[0])
503
  result = SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
504
505
506
507
508
  if not result:
    return 1
  result = list(result)
  result.sort()
  for path, tag in result:
509
    ToStdout("%s %s", path, tag)
Iustin Pop's avatar
Iustin Pop committed
510
511


512
def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
Michael Hanselmann's avatar
Michael Hanselmann committed
513
514
                 new_confd_hmac_key, new_cds, cds_filename,
                 force):
515
516
517
518
519
520
521
522
  """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
523
524
  @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
525
526
527
528
  @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
529
530
531
532
533
534
535
536
537
  @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
538
539
540
541
542
543
  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

544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
  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
566
567
568
569
570
571
572
573
574
575
  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

576
577
578
579
580
581
582
583
584
  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,
585
                                    new_confd_hmac_key,
Michael Hanselmann's avatar
Michael Hanselmann committed
586
587
588
                                    new_cds,
                                    rapi_cert_pem=rapi_cert_pem,
                                    cds=cds)
589
590
591
592

    files_to_copy = []

    if new_cluster_cert:
593
      files_to_copy.append(constants.NODED_CERT_FILE)
594
595
596
597

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

598
599
    if new_confd_hmac_key:
      files_to_copy.append(constants.CONFD_HMAC_KEY)
600

Michael Hanselmann's avatar
Michael Hanselmann committed
601
602
603
    if new_cds or cds:
      files_to_copy.append(constants.CLUSTER_DOMAIN_SECRET_FILE)

604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
    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,
626
                      opts.new_confd_hmac_key,
Michael Hanselmann's avatar
Michael Hanselmann committed
627
628
                      opts.new_cluster_domain_secret,
                      opts.cluster_domain_secret,
629
630
631
                      opts.force)


632
633
634
def SetClusterParams(opts, args):
  """Modify the cluster.

635
636
637
638
639
  @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
640
641

  """
642
643
  if not (not opts.lvm_storage or opts.vg_name or
          opts.enabled_hypervisors or opts.hvparams or
644
          opts.beparams or opts.nicparams or
645
          opts.candidate_pool_size is not None or
646
          opts.uid_pool is not None or
647
648
649
          opts.maintain_node_health is not None or
          opts.add_uids is not None or
          opts.remove_uids is not None):
650
    ToStderr("Please give at least one of the parameters.")
651
652
653
654
    return 1

  vg_name = opts.vg_name
  if not opts.lvm_storage and opts.vg_name:
655
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
656
    return 1
657
658
659

  if not opts.lvm_storage:
    vg_name = ""
660

661
662
663
664
  hvlist = opts.enabled_hypervisors
  if hvlist is not None:
    hvlist = hvlist.split(",")

665
666
  # a list of (name, dict) we can pass directly to dict() (or [])
  hvparams = dict(opts.hvparams)
Iustin Pop's avatar
Iustin Pop committed
667
  for hv_params in hvparams.values():
668
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
669
670

  beparams = opts.beparams
671
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
672

673
674
675
  nicparams = opts.nicparams
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)

676

677
678
  mnh = opts.maintain_node_health

679
680
681
682
  uid_pool = opts.uid_pool
  if uid_pool is not None:
    uid_pool = uidpool.ParseUidPool(uid_pool)

683
684
685
686
687
688
689
690
  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)

691
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
692
693
                                  enabled_hypervisors=hvlist,
                                  hvparams=hvparams,
694
                                  os_hvp=None,
695
                                  beparams=beparams,
696
                                  nicparams=nicparams,
697
                                  candidate_pool_size=opts.candidate_pool_size,
698
                                  maintain_node_health=mnh,
699
700
701
                                  uid_pool=uid_pool,
                                  add_uids=add_uids,
                                  remove_uids=remove_uids)
702
  SubmitOpCode(op, opts=opts)
703
704
705
  return 0


706
707
708
def QueueOps(opts, args):
  """Queue operations.

709
710
711
712
713
714
  @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

715
716
717
718
719
720
721
722
723
  """
  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]:
724
      val = "set"
725
    else:
726
727
      val = "unset"
    ToStdout("The drain flag is %s" % val)
728
  else:
729
730
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
                               errors.ECODE_INVAL)
731

732
733
  return 0

734

735
736
737
738
739
740
741
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))


742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
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)
757
    ToStdout("The watcher is no longer paused.")
758
759
760

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

763
764
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
    _ShowWatcherPause(result)
765
766
767

  elif command == "info":
    result = client.QueryConfigValues(["watcher_pause"])
768
    _ShowWatcherPause(result[0])
769
770

  else:
771
772
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
                               errors.ECODE_INVAL)
773
774
775
776

  return 0


Iustin Pop's avatar
Iustin Pop committed
777
commands = {
778
779
  'init': (
    InitCluster, [ArgHost(min=1, max=1)],
780
    [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, GLOBAL_FILEDIR_OPT,
781
     HVLIST_OPT, MAC_PREFIX_OPT, MASTER_NETDEV_OPT, NIC_PARAMS_OPT,
782
     NOLVM_STORAGE_OPT, NOMODIFY_ETCHOSTS_OPT, NOMODIFY_SSH_SETUP_OPT,
783
784
     SECONDARY_IP_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT,
     UIDPOOL_OPT],
785
786
    "[opts...] <cluster_name>", "Initialises a new cluster configuration"),
  'destroy': (
787
    DestroyCluster, ARGS_NONE, [YES_DOIT_OPT],
788
789
790
    "", "Destroy cluster"),
  'rename': (
    RenameCluster, [ArgHost(min=1, max=1)],
791
    [FORCE_OPT],
792
793
794
    "<new_name>",
    "Renames the cluster"),
  'redist-conf': (
795
    RedistributeConfig, ARGS_NONE, [SUBMIT_OPT],
796
797
798
799
    "", "Forces a push of the configuration file and ssconf files"
    " to the nodes in the cluster"),
  'verify': (
    VerifyCluster, ARGS_NONE,
800
    [VERBOSE_OPT, DEBUG_SIMERR_OPT, ERROR_CODES_OPT, NONPLUS1_OPT],
801
802
    "", "Does a check on the cluster configuration"),
  'verify-disks': (
803
    VerifyDisks, ARGS_NONE, [],
804
805
    "", "Does a check on the cluster disk status"),
  'repair-disk-sizes': (
806
    RepairDiskSizes, ARGS_MANY_INSTANCES, [],
807
808
    "", "Updates mismatches in recorded disk sizes"),
  'masterfailover': (
809
    MasterFailover, ARGS_NONE, [NOVOTING_OPT],
810
811
    "", "Makes the current node the master"),
  'version': (
812
    ShowClusterVersion, ARGS_NONE, [],
813
814
    "", "Shows the cluster version"),
  'getmaster': (
815
    ShowClusterMaster, ARGS_NONE, [],
816
817
818
    "", "Shows the cluster master"),
  'copyfile': (
    ClusterCopyFile, [ArgFile(min=1, max=1)],
819
    [NODE_LIST_OPT, USE_REPL_NET_OPT],
820
821
822
    "[-n node...] <filename>", "Copies a file to all (or only some) nodes"),
  'command': (
    RunClusterCommand, [ArgCommand(min=1)],
823
    [NODE_LIST_OPT],
824
825
    "[-n node...] <command>", "Runs a command on all (or only some) nodes"),
  'info': (
Guido Trotter's avatar
Guido Trotter committed
826
827
    ShowClusterConfig, ARGS_NONE, [ROMAN_OPT],
    "[--roman]", "Show cluster configuration"),
828
  'list-tags': (
829
    ListTags, ARGS_NONE, [], "", "List the tags of the cluster"),
830
  'add-tags': (
831
    AddTags, [ArgUnknown()], [TAG_SRC_OPT],
832
833
    "tag...", "Add tags to the cluster"),
  'remove-tags': (
834
    RemoveTags, [ArgUnknown()], [TAG_SRC_OPT],
835
836
837
    "tag...", "Remove tags from the cluster"),
  'search-tags': (
    SearchTags, [ArgUnknown(min=1, max=1)],
838
    [], "", "Searches the tags on all objects on"
839
840
841
842
    " the cluster for a given pattern (regex)"),
  'queue': (
    QueueOps,
    [ArgChoice(min=1, max=1, choices=["drain", "undrain", "info"])],
843
    [], "drain|undrain|info", "Change queue properties"),
844
845
846
847
  'watcher': (
    WatcherOps,
    [ArgChoice(min=1, max=1, choices=["pause", "continue", "info"]),
     ArgSuggest(min=0, max=1, choices=["30m", "1h", "4h"])],
848
    [],
849
850
851
    "{pause <timespec>|continue|info}", "Change watcher properties"),
  'modify': (
    SetClusterParams, ARGS_NONE,
852
    [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, HVLIST_OPT,
853
     NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT,
854
     UIDPOOL_OPT, ADD_UIDS_OPT, REMOVE_UIDS_OPT],
855
856
    "[opts...]",
    "Alters the parameters of the cluster"),
857
858
  "renew-crypto": (
    RenewCrypto, ARGS_NONE,
859
    [NEW_CLUSTER_CERT_OPT, NEW_RAPI_CERT_OPT, RAPI_CERT_OPT,
Michael Hanselmann's avatar
Michael Hanselmann committed
860
861
     NEW_CONFD_HMAC_KEY_OPT, FORCE_OPT,
     NEW_CLUSTER_DOMAIN_SECRET_OPT, CLUSTER_DOMAIN_SECRET_OPT],
862
863
    "[opts...]",
    "Renews cluster certificates, keys and secrets"),
Iustin Pop's avatar
Iustin Pop committed
864
865
  }

866

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