gnt-cluster 28.4 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
  cl = GetClient()

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

  new_name = args[0]
170
  if not opts.force:
171
172
173
174
175
    usertext = ("This will rename the cluster from '%s' 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?") % (cluster_name, new_name)
176
    if not AskUser(usertext):
177
178
      return 1

179
180
181
182
183
  op = opcodes.OpRenameCluster(name=new_name)
  result = SubmitOpCode(op, opts=opts, cl=cl)

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

184
185
186
  return 0


187
188
189
190
191
192
193
194
195
196
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

  """
197
  op = opcodes.OpRedistributeConfig()
198
199
200
201
  SubmitOrSend(op, opts)
  return 0


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

205
206
207
208
209
  @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
210
211

  """
212
213
  cl = GetClient()
  result = cl.QueryClusterInfo()
214
215
216
217
218
  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
219
220
221
222
223
224
  return 0


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

225
226
227
228
229
  @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
230
231

  """
232
233
  master = bootstrap.GetMaster()
  ToStdout(master)
Iustin Pop's avatar
Iustin Pop committed
234
235
  return 0

236

Guido Trotter's avatar
Guido Trotter committed
237
def _PrintGroupedParams(paramsdict, level=1, roman=False):
238
239
240
241
  """Print Grouped parameters (be, nic, disk) by group.

  @type paramsdict: dict of dicts
  @param paramsdict: {group: {param: value, ...}, ...}
242
243
  @type level: int
  @param level: Level of indention
244
245

  """
246
  indent = "  " * level
247
  for item, val in sorted(paramsdict.items()):
248
249
    if isinstance(val, dict):
      ToStdout("%s- %s:", indent, item)
Guido Trotter's avatar
Guido Trotter committed
250
251
252
      _PrintGroupedParams(val, level=level + 1, roman=roman)
    elif roman and isinstance(val, int):
      ToStdout("%s  %s: %s", indent, item, compat.TryToRoman(val))
253
254
    else:
      ToStdout("%s  %s: %s", indent, item, val)
Iustin Pop's avatar
Iustin Pop committed
255

256

Iustin Pop's avatar
Iustin Pop committed
257
258
259
def ShowClusterConfig(opts, args):
  """Shows cluster information.

260
261
262
263
264
265
  @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
266
  """
267
268
  cl = GetClient()
  result = cl.QueryClusterInfo()
Iustin Pop's avatar
Iustin Pop committed
269

270
  ToStdout("Cluster name: %s", result["name"])
271
  ToStdout("Cluster UUID: %s", result["uuid"])
Iustin Pop's avatar
Iustin Pop committed
272

273
274
275
  ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
  ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))

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

278
279
  ToStdout("Architecture (this node): %s (%s)",
           result["architecture"][0], result["architecture"][1])
Iustin Pop's avatar
Iustin Pop committed
280

281
  if result["tags"]:
282
    tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
283
284
285
286
287
  else:
    tags = "(none)"

  ToStdout("Tags: %s", tags)

288
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
289
290
  ToStdout("Enabled hypervisors: %s",
           utils.CommaJoin(result["enabled_hypervisors"]))
291

292
  ToStdout("Hypervisor parameters:")
293
  _PrintGroupedParams(result["hvparams"])
294

295
  ToStdout("OS-specific hypervisor parameters:")
296
297
  _PrintGroupedParams(result["os_hvp"])

298
299
300
  ToStdout("OS parameters:")
  _PrintGroupedParams(result["osparams"])

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

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

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

Iustin Pop's avatar
Iustin Pop committed
327
328
329
330
331
332
  return 0


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

333
334
335
336
337
338
  @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
339
340

  """
341
342
  filename = args[0]
  if not os.path.exists(filename):
343
344
    raise errors.OpPrereqError("No such filename '%s'" % filename,
                               errors.ECODE_INVAL)
345

Iustin Pop's avatar
Iustin Pop committed
346
347
348
349
  cl = GetClient()

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

350
351
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
                           secondary_ips=opts.use_replication_network)
Michael Hanselmann's avatar
Michael Hanselmann committed
352

Iustin Pop's avatar
Iustin Pop committed
353
  srun = ssh.SshRunner(cluster_name=cluster_name)
354
355
  for node in results:
    if not srun.CopyFileToNode(node, filename):
356
      ToStderr("Copy of file %s to node %s failed", filename, node)
357

Iustin Pop's avatar
Iustin Pop committed
358
359
360
361
362
363
  return 0


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

364
365
366
367
368
  @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
369
370

  """
Iustin Pop's avatar
Iustin Pop committed
371
  cl = GetClient()
Michael Hanselmann's avatar
Michael Hanselmann committed
372

Iustin Pop's avatar
Iustin Pop committed
373
  command = " ".join(args)
374
375

  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
Iustin Pop's avatar
Iustin Pop committed
376
377
378

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

Iustin Pop's avatar
Iustin Pop committed
380
  srun = ssh.SshRunner(cluster_name=cluster_name)
381

Michael Hanselmann's avatar
Michael Hanselmann committed
382
  # Make sure master node is at list end
383
384
385
386
387
388
  if master_node in nodes:
    nodes.remove(master_node)
    nodes.append(master_node)

  for name in nodes:
    result = srun.Run(name, "root", command)
389
390
391
392
    ToStdout("------------------------------------------------")
    ToStdout("node: %s", name)
    ToStdout("%s", result.output)
    ToStdout("return code = %s", result.exit_code)
393
394

  return 0
Iustin Pop's avatar
Iustin Pop committed
395
396
397
398
399


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

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

  """
Iustin Pop's avatar
Iustin Pop committed
407
  skip_checks = []
408
409
  if opts.skip_nplusone_mem:
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
410
411
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
                               verbose=opts.verbose,
412
413
                               error_codes=opts.error_codes,
                               debug_simulate_errors=opts.simulate_errors)
414
  if SubmitOpCode(op, opts=opts):
415
416
417
    return 0
  else:
    return 1
Iustin Pop's avatar
Iustin Pop committed
418
419


420
421
422
def VerifyDisks(opts, args):
  """Verify integrity of cluster disks.

423
424
425
426
427
  @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
428
429
430

  """
  op = opcodes.OpVerifyDisks()
431
  result = SubmitOpCode(op, opts=opts)
432
  if not isinstance(result, (list, tuple)) or len(result) != 3:
433
434
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")

435
  bad_nodes, instances, missing = result
436

437
  retcode = constants.EXIT_SUCCESS
438

439
440
441
  if bad_nodes:
    for node, text in bad_nodes.items():
      ToStdout("Error gathering data on node %s: %s",
442
               node, utils.SafeEncode(text[-400:]))
443
      retcode |= 1
444
      ToStdout("You need to fix these nodes first before fixing instances")
445

446
447
  if instances:
    for iname in instances:
448
449
      if iname in missing:
        continue
450
451
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
      try:
452
        ToStdout("Activating disks for instance '%s'", iname)
453
        SubmitOpCode(op, opts=opts)
454
455
456
      except errors.GenericError, err:
        nret, msg = FormatError(err)
        retcode |= nret
457
        ToStderr("Error activating disks for instance %s: %s", iname, msg)
458
459
460

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

  return retcode


480
481
482
483
484
485
486
487
488
489
490
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)
491
  SubmitOpCode(op, opts=opts)
492
493


494
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
495
496
497
498
499
500
501
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.

502
503
504
505
506
507
  @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
508
  """
509
510
511
512
513
514
515
516
517
  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
518
519


Iustin Pop's avatar
Iustin Pop committed
520
521
522
def SearchTags(opts, args):
  """Searches the tags on all the cluster.

523
524
525
526
527
528
  @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
529
530
  """
  op = opcodes.OpSearchTags(pattern=args[0])
531
  result = SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
532
533
534
535
536
  if not result:
    return 1
  result = list(result)
  result.sort()
  for path, tag in result:
537
    ToStdout("%s %s", path, tag)
Iustin Pop's avatar
Iustin Pop committed
538
539


540
def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
Michael Hanselmann's avatar
Michael Hanselmann committed
541
542
                 new_confd_hmac_key, new_cds, cds_filename,
                 force):
543
544
545
546
547
548
549
550
  """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
551
552
  @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
553
554
555
556
  @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
557
558
559
560
561
562
563
564
565
  @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
566
567
568
569
570
571
  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

572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
  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
594
595
596
597
598
599
600
601
602
603
  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

604
605
606
607
608
609
610
611
612
  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,
613
                                    new_confd_hmac_key,
Michael Hanselmann's avatar
Michael Hanselmann committed
614
615
616
                                    new_cds,
                                    rapi_cert_pem=rapi_cert_pem,
                                    cds=cds)
617
618
619
620

    files_to_copy = []

    if new_cluster_cert:
621
      files_to_copy.append(constants.NODED_CERT_FILE)
622
623
624
625

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

626
627
    if new_confd_hmac_key:
      files_to_copy.append(constants.CONFD_HMAC_KEY)
628

Michael Hanselmann's avatar
Michael Hanselmann committed
629
630
631
    if new_cds or cds:
      files_to_copy.append(constants.CLUSTER_DOMAIN_SECRET_FILE)

632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
    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,
654
                      opts.new_confd_hmac_key,
Michael Hanselmann's avatar
Michael Hanselmann committed
655
656
                      opts.new_cluster_domain_secret,
                      opts.cluster_domain_secret,
657
658
659
                      opts.force)


660
661
662
def SetClusterParams(opts, args):
  """Modify the cluster.

663
664
665
666
667
  @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
668
669

  """
670
  if not (not opts.lvm_storage or opts.vg_name or
671
          not opts.drbd_storage or opts.drbd_helper or
672
          opts.enabled_hypervisors or opts.hvparams or
673
          opts.beparams or opts.nicparams or
674
          opts.candidate_pool_size is not None or
675
          opts.uid_pool is not None or
676
677
          opts.maintain_node_health is not None or
          opts.add_uids is not None or
678
          opts.remove_uids is not None or
679
680
          opts.default_iallocator is not None or
          opts.reserved_lvs is not None):
681
    ToStderr("Please give at least one of the parameters.")
682
683
684
685
    return 1

  vg_name = opts.vg_name
  if not opts.lvm_storage and opts.vg_name:
686
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
687
    return 1
688
689
690

  if not opts.lvm_storage:
    vg_name = ""
691

692
693
694
695
696
697
698
699
  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 = ""

700
701
702
703
  hvlist = opts.enabled_hypervisors
  if hvlist is not None:
    hvlist = hvlist.split(",")

704
705
  # a list of (name, dict) we can pass directly to dict() (or [])
  hvparams = dict(opts.hvparams)
Iustin Pop's avatar
Iustin Pop committed
706
  for hv_params in hvparams.values():
707
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
708
709

  beparams = opts.beparams
710
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
711

712
713
714
  nicparams = opts.nicparams
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)

715

716
717
  mnh = opts.maintain_node_health

718
719
720
721
  uid_pool = opts.uid_pool
  if uid_pool is not None:
    uid_pool = uidpool.ParseUidPool(uid_pool)

722
723
724
725
726
727
728
729
  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)

730
731
732
733
734
735
  if opts.reserved_lvs is not None:
    if opts.reserved_lvs == "":
      opts.reserved_lvs = []
    else:
      opts.reserved_lvs = utils.UnescapeAndSplit(opts.reserved_lvs, sep=",")

736
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
737
                                  drbd_helper=drbd_helper,
738
739
                                  enabled_hypervisors=hvlist,
                                  hvparams=hvparams,
740
                                  os_hvp=None,
741
                                  beparams=beparams,
742
                                  nicparams=nicparams,
743
                                  candidate_pool_size=opts.candidate_pool_size,
744
                                  maintain_node_health=mnh,
745
746
                                  uid_pool=uid_pool,
                                  add_uids=add_uids,
747
                                  remove_uids=remove_uids,
748
749
                                  default_iallocator=opts.default_iallocator,
                                  reserved_lvs=opts.reserved_lvs)
750
  SubmitOpCode(op, opts=opts)
751
752
753
  return 0


754
755
756
def QueueOps(opts, args):
  """Queue operations.

757
758
759
760
761
762
  @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

763
764
765
766
767
768
769
770
771
  """
  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]:
772
      val = "set"
773
    else:
774
775
      val = "unset"
    ToStdout("The drain flag is %s" % val)
776
  else:
777
778
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
                               errors.ECODE_INVAL)
779

780
781
  return 0

782

783
784
785
786
787
788
789
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))


790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
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)
805
    ToStdout("The watcher is no longer paused.")
806
807
808

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

811
812
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
    _ShowWatcherPause(result)
813
814
815

  elif command == "info":
    result = client.QueryConfigValues(["watcher_pause"])
816
    _ShowWatcherPause(result[0])
817
818

  else:
819
820
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
                               errors.ECODE_INVAL)
821
822
823
824

  return 0


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

916

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