gnt-cluster 26.6 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
  ToStdout("OS-specific hypervisor parameters:")
278
279
  _PrintGroupedParams(result["os_hvp"])

280
281
282
  ToStdout("OS parameters:")
  _PrintGroupedParams(result["osparams"])

283
  ToStdout("Cluster parameters:")
Guido Trotter's avatar
Guido Trotter committed
284
285
286
  ToStdout("  - candidate pool size: %s",
            compat.TryToRoman(result["candidate_pool_size"],
                              convert=opts.roman_integers))
287
288
289
  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"])
290
291
  ToStdout("  - maintenance of node health: %s",
           result["maintain_node_health"])
Guido Trotter's avatar
Guido Trotter committed
292
293
294
  ToStdout("  - uid pool: %s",
            uidpool.FormatUidPool(result["uid_pool"],
                                  roman=opts.roman_integers))
295
296

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

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

Iustin Pop's avatar
Iustin Pop committed
302
303
304
305
306
307
  return 0


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

308
309
310
311
312
313
  @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
314
315

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

Iustin Pop's avatar
Iustin Pop committed
321
322
323
324
  cl = GetClient()

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

325
326
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
                           secondary_ips=opts.use_replication_network)
Michael Hanselmann's avatar
Michael Hanselmann committed
327

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

Iustin Pop's avatar
Iustin Pop committed
333
334
335
336
337
338
  return 0


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

339
340
341
342
343
  @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
344
345

  """
Iustin Pop's avatar
Iustin Pop committed
346
  cl = GetClient()
Michael Hanselmann's avatar
Michael Hanselmann committed
347

Iustin Pop's avatar
Iustin Pop committed
348
  command = " ".join(args)
349
350

  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
Iustin Pop's avatar
Iustin Pop committed
351
352
353

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

Iustin Pop's avatar
Iustin Pop committed
355
  srun = ssh.SshRunner(cluster_name=cluster_name)
356

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

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

  return 0
Iustin Pop's avatar
Iustin Pop committed
370
371
372
373
374


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

375
376
377
378
379
  @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
380
381

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


395
396
397
def VerifyDisks(opts, args):
  """Verify integrity of cluster disks.

398
399
400
401
402
  @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
403
404
405

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

410
  bad_nodes, instances, missing = result
411

412
  retcode = constants.EXIT_SUCCESS
413

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

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

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

  return retcode


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


469
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
470
471
472
473
474
475
476
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.

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


Iustin Pop's avatar
Iustin Pop committed
495
496
497
def SearchTags(opts, args):
  """Searches the tags on all the cluster.

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


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

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

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

    files_to_copy = []

    if new_cluster_cert:
596
      files_to_copy.append(constants.NODED_CERT_FILE)
597
598
599
600

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

601
602
    if new_confd_hmac_key:
      files_to_copy.append(constants.CONFD_HMAC_KEY)
603

Michael Hanselmann's avatar
Michael Hanselmann committed
604
605
606
    if new_cds or cds:
      files_to_copy.append(constants.CLUSTER_DOMAIN_SECRET_FILE)

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


635
636
637
def SetClusterParams(opts, args):
  """Modify the cluster.

638
639
640
641
642
  @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
643
644

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

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

  if not opts.lvm_storage:
    vg_name = ""
663

664
665
666
667
  hvlist = opts.enabled_hypervisors
  if hvlist is not None:
    hvlist = hvlist.split(",")

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

  beparams = opts.beparams
674
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
675

676
677
678
  nicparams = opts.nicparams
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)

679

680
681
  mnh = opts.maintain_node_health

682
683
684
685
  uid_pool = opts.uid_pool
  if uid_pool is not None:
    uid_pool = uidpool.ParseUidPool(uid_pool)

686
687
688
689
690
691
692
693
  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)

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


709
710
711
def QueueOps(opts, args):
  """Queue operations.

712
713
714
715
716
717
  @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

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

735
736
  return 0

737

738
739
740
741
742
743
744
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))


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

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

766
767
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
    _ShowWatcherPause(result)
768
769
770

  elif command == "info":
    result = client.QueryConfigValues(["watcher_pause"])
771
    _ShowWatcherPause(result[0])
772
773

  else:
774
775
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
                               errors.ECODE_INVAL)
776
777
778
779

  return 0


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

869

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