gnt_cluster.py 30.5 KB
Newer Older
1
#
Iustin Pop's avatar
Iustin Pop committed
2
3
#

4
# Copyright (C) 2006, 2007, 2010 Google Inc.
Iustin Pop's avatar
Iustin Pop committed
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#
# 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

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

from ganeti.cli import *
from ganeti import opcodes
35
from ganeti import constants
36
from ganeti import errors
37
from ganeti import utils
38
from ganeti import bootstrap
39
from ganeti import ssh
40
from ganeti import objects
41
from ganeti import uidpool
42
from ganeti import compat
Iustin Pop's avatar
Iustin Pop committed
43
44


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

49
50
51
52
53
54
  @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
55
56

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

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

65
66
67
68
69
70
71
72
  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

73
  hvlist = opts.enabled_hypervisors
74
75
  if hvlist is None:
    hvlist = constants.DEFAULT_ENABLED_HYPERVISOR
76
  hvlist = hvlist.split(",")
77

78
  hvparams = dict(opts.hvparams)
79
  beparams = opts.beparams
80
  nicparams = opts.nicparams
81
82

  # prepare beparams dict
83
  beparams = objects.FillDict(constants.BEC_DEFAULTS, beparams)
84
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
85

86
87
88
89
  # prepare nicparams dict
  nicparams = objects.FillDict(constants.NICC_DEFAULTS, nicparams)
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)

90
91
92
93
94
95
96
  # prepare ndparams dict
  if opts.ndparams is None:
    ndparams = dict(constants.NDC_DEFAULTS)
  else:
    ndparams = objects.FillDict(constants.NDC_DEFAULTS, opts.ndparams)
    utils.ForceDictType(ndparams, constants.NDS_PARAMETER_TYPES)

97
98
99
100
  # prepare hvparams dict
  for hv in constants.HYPER_TYPES:
    if hv not in hvparams:
      hvparams[hv] = {}
101
    hvparams[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], hvparams[hv])
102
    utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
103

104
105
106
  if opts.candidate_pool_size is None:
    opts.candidate_pool_size = constants.MASTER_POOL_SIZE_DEFAULT

107
108
109
  if opts.mac_prefix is None:
    opts.mac_prefix = constants.DEFAULT_MAC_PREFIX

110
111
112
113
  uid_pool = opts.uid_pool
  if uid_pool is not None:
    uid_pool = uidpool.ParseUidPool(uid_pool)

114
115
116
  if opts.prealloc_wipe_disks is None:
    opts.prealloc_wipe_disks = False

117
118
119
120
121
122
  try:
    primary_ip_version = int(opts.primary_ip_version)
  except (ValueError, TypeError), err:
    ToStderr("Invalid primary ip version value: %s" % str(err))
    return 1

123
124
125
126
127
  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,
128
129
130
                        file_storage_dir=opts.file_storage_dir,
                        enabled_hypervisors=hvlist,
                        hvparams=hvparams,
131
                        beparams=beparams,
132
                        nicparams=nicparams,
133
                        ndparams=ndparams,
134
                        candidate_pool_size=opts.candidate_pool_size,
135
                        modify_etc_hosts=opts.modify_etc_hosts,
136
                        modify_ssh_setup=opts.modify_ssh_setup,
137
                        maintain_node_health=opts.maintain_node_health,
138
                        drbd_helper=drbd_helper,
139
                        uid_pool=uid_pool,
140
                        default_iallocator=opts.default_iallocator,
141
                        primary_ip_version=primary_ip_version,
142
                        prealloc_wipe_disks=opts.prealloc_wipe_disks,
143
                        )
144
  op = opcodes.OpPostInitCluster()
145
  SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
146
147
148
  return 0


149
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
150
151
152
def DestroyCluster(opts, args):
  """Destroy the cluster.

153
154
155
156
157
  @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
158

Iustin Pop's avatar
Iustin Pop committed
159
160
  """
  if not opts.yes_do_it:
161
162
    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
163
164
165
    return 1

  op = opcodes.OpDestroyCluster()
166
  master = SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
167
168
169
  # 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
170
171
172
  return 0


173
174
175
def RenameCluster(opts, args):
  """Rename the cluster.

176
177
178
179
180
  @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
181
182

  """
183
184
185
186
187
  cl = GetClient()

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

  new_name = args[0]
188
  if not opts.force:
189
190
191
192
193
    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)
194
    if not AskUser(usertext):
195
196
      return 1

197
198
199
  op = opcodes.OpRenameCluster(name=new_name)
  result = SubmitOpCode(op, opts=opts, cl=cl)

200
201
  if result:
    ToStdout("Cluster renamed from '%s' to '%s'", cluster_name, result)
202

203
204
205
  return 0


206
207
208
209
210
211
212
213
214
215
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

  """
216
  op = opcodes.OpRedistributeConfig()
217
218
219
220
  SubmitOrSend(op, opts)
  return 0


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

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

  """
231
232
  cl = GetClient()
  result = cl.QueryClusterInfo()
233
234
235
236
237
  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
238
239
240
241
242
243
  return 0


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

244
245
246
247
248
  @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
249
250

  """
251
252
  master = bootstrap.GetMaster()
  ToStdout(master)
Iustin Pop's avatar
Iustin Pop committed
253
254
  return 0

255

Guido Trotter's avatar
Guido Trotter committed
256
def _PrintGroupedParams(paramsdict, level=1, roman=False):
257
258
259
260
  """Print Grouped parameters (be, nic, disk) by group.

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

  """
265
  indent = "  " * level
266
  for item, val in sorted(paramsdict.items()):
267
268
    if isinstance(val, dict):
      ToStdout("%s- %s:", indent, item)
Guido Trotter's avatar
Guido Trotter committed
269
270
271
      _PrintGroupedParams(val, level=level + 1, roman=roman)
    elif roman and isinstance(val, int):
      ToStdout("%s  %s: %s", indent, item, compat.TryToRoman(val))
272
273
    else:
      ToStdout("%s  %s: %s", indent, item, val)
Iustin Pop's avatar
Iustin Pop committed
274

275

Iustin Pop's avatar
Iustin Pop committed
276
277
278
def ShowClusterConfig(opts, args):
  """Shows cluster information.

279
280
281
282
283
284
  @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
285
  """
286
287
  cl = GetClient()
  result = cl.QueryClusterInfo()
Iustin Pop's avatar
Iustin Pop committed
288

289
  ToStdout("Cluster name: %s", result["name"])
290
  ToStdout("Cluster UUID: %s", result["uuid"])
Iustin Pop's avatar
Iustin Pop committed
291

292
293
294
  ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
  ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))

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

297
298
  ToStdout("Architecture (this node): %s (%s)",
           result["architecture"][0], result["architecture"][1])
Iustin Pop's avatar
Iustin Pop committed
299

300
  if result["tags"]:
301
    tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
302
303
304
305
306
  else:
    tags = "(none)"

  ToStdout("Tags: %s", tags)

307
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
308
309
  ToStdout("Enabled hypervisors: %s",
           utils.CommaJoin(result["enabled_hypervisors"]))
310

311
  ToStdout("Hypervisor parameters:")
312
  _PrintGroupedParams(result["hvparams"])
313

314
  ToStdout("OS-specific hypervisor parameters:")
315
316
  _PrintGroupedParams(result["os_hvp"])

317
318
319
  ToStdout("OS parameters:")
  _PrintGroupedParams(result["osparams"])

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

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

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

Iustin Pop's avatar
Iustin Pop committed
348
349
350
351
352
353
  return 0


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

354
355
356
357
358
359
  @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
360
361

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

Iustin Pop's avatar
Iustin Pop committed
367
368
369
370
  cl = GetClient()

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

371
372
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
                           secondary_ips=opts.use_replication_network)
Michael Hanselmann's avatar
Michael Hanselmann committed
373

Iustin Pop's avatar
Iustin Pop committed
374
  srun = ssh.SshRunner(cluster_name=cluster_name)
375
376
  for node in results:
    if not srun.CopyFileToNode(node, filename):
377
      ToStderr("Copy of file %s to node %s failed", filename, node)
378

Iustin Pop's avatar
Iustin Pop committed
379
380
381
382
383
384
  return 0


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

385
386
387
388
389
  @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
390
391

  """
Iustin Pop's avatar
Iustin Pop committed
392
  cl = GetClient()
Michael Hanselmann's avatar
Michael Hanselmann committed
393

Iustin Pop's avatar
Iustin Pop committed
394
  command = " ".join(args)
395
396

  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
Iustin Pop's avatar
Iustin Pop committed
397
398
399

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

Iustin Pop's avatar
Iustin Pop committed
401
  srun = ssh.SshRunner(cluster_name=cluster_name)
402

Michael Hanselmann's avatar
Michael Hanselmann committed
403
  # Make sure master node is at list end
404
405
406
407
408
409
  if master_node in nodes:
    nodes.remove(master_node)
    nodes.append(master_node)

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

  return 0
Iustin Pop's avatar
Iustin Pop committed
416
417
418
419
420


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

421
422
423
424
425
  @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
426
427

  """
Iustin Pop's avatar
Iustin Pop committed
428
  skip_checks = []
429
430
  if opts.skip_nplusone_mem:
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
431
432
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
                               verbose=opts.verbose,
433
434
                               error_codes=opts.error_codes,
                               debug_simulate_errors=opts.simulate_errors)
435
  if SubmitOpCode(op, opts=opts):
436
437
438
    return 0
  else:
    return 1
Iustin Pop's avatar
Iustin Pop committed
439
440


441
442
443
def VerifyDisks(opts, args):
  """Verify integrity of cluster disks.

444
445
446
447
448
  @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
449
450

  """
451
452
  cl = GetClient()

453
  op = opcodes.OpVerifyDisks()
454
  result = SubmitOpCode(op, opts=opts, cl=cl)
455
  if not isinstance(result, (list, tuple)) or len(result) != 3:
456
457
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")

458
  bad_nodes, instances, missing = result
459

460
  retcode = constants.EXIT_SUCCESS
461

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

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

  if missing:
483
484
    (vg_name, ) = cl.QueryConfigValues(["volume_group_name"])

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

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

  return retcode


506
507
508
509
510
511
512
513
514
515
516
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)
517
  SubmitOpCode(op, opts=opts)
518
519


520
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
521
522
523
524
525
526
527
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.

528
529
530
531
532
533
  @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
534
  """
535
536
537
538
539
540
541
542
543
  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
544
545


Iustin Pop's avatar
Iustin Pop committed
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
def MasterPing(opts, args):
  """Checks if the master is alive.

  @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

  """
  try:
    cl = GetClient()
    cl.QueryClusterInfo()
    return 0
  except Exception: # pylint: disable-msg=W0703
    return 1


Iustin Pop's avatar
Iustin Pop committed
564
565
566
def SearchTags(opts, args):
  """Searches the tags on all the cluster.

567
568
569
570
571
572
  @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
573
574
  """
  op = opcodes.OpSearchTags(pattern=args[0])
575
  result = SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
576
577
578
579
580
  if not result:
    return 1
  result = list(result)
  result.sort()
  for path, tag in result:
581
    ToStdout("%s %s", path, tag)
Iustin Pop's avatar
Iustin Pop committed
582
583


584
def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
Michael Hanselmann's avatar
Michael Hanselmann committed
585
586
                 new_confd_hmac_key, new_cds, cds_filename,
                 force):
587
588
589
590
591
592
593
594
  """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
595
596
  @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
597
598
599
600
  @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
601
602
603
604
605
606
607
608
609
  @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
610
611
612
613
614
615
  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

616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
  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
638
639
640
641
642
643
644
645
646
647
  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

648
649
650
651
652
653
654
655
656
  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,
657
                                    new_confd_hmac_key,
Michael Hanselmann's avatar
Michael Hanselmann committed
658
659
660
                                    new_cds,
                                    rapi_cert_pem=rapi_cert_pem,
                                    cds=cds)
661
662
663
664

    files_to_copy = []

    if new_cluster_cert:
665
      files_to_copy.append(constants.NODED_CERT_FILE)
666
667
668
669

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

670
671
    if new_confd_hmac_key:
      files_to_copy.append(constants.CONFD_HMAC_KEY)
672

Michael Hanselmann's avatar
Michael Hanselmann committed
673
674
675
    if new_cds or cds:
      files_to_copy.append(constants.CLUSTER_DOMAIN_SECRET_FILE)

676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
    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,
698
                      opts.new_confd_hmac_key,
Michael Hanselmann's avatar
Michael Hanselmann committed
699
700
                      opts.new_cluster_domain_secret,
                      opts.cluster_domain_secret,
701
702
703
                      opts.force)


704
705
706
def SetClusterParams(opts, args):
  """Modify the cluster.

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

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

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

  if not opts.lvm_storage:
    vg_name = ""
736

737
738
739
740
741
742
743
744
  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 = ""

745
746
747
748
  hvlist = opts.enabled_hypervisors
  if hvlist is not None:
    hvlist = hvlist.split(",")

749
750
  # a list of (name, dict) we can pass directly to dict() (or [])
  hvparams = dict(opts.hvparams)
Iustin Pop's avatar
Iustin Pop committed
751
  for hv_params in hvparams.values():
752
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
753
754

  beparams = opts.beparams
755
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
756

757
758
759
  nicparams = opts.nicparams
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)

760
761
762
  ndparams = opts.ndparams
  if ndparams is not None:
    utils.ForceDictType(ndparams, constants.NDS_PARAMETER_TYPES)
763

764
765
  mnh = opts.maintain_node_health

766
767
768
769
  uid_pool = opts.uid_pool
  if uid_pool is not None:
    uid_pool = uidpool.ParseUidPool(uid_pool)

770
771
772
773
774
775
776
777
  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)

778
779
780
781
782
783
  if opts.reserved_lvs is not None:
    if opts.reserved_lvs == "":
      opts.reserved_lvs = []
    else:
      opts.reserved_lvs = utils.UnescapeAndSplit(opts.reserved_lvs, sep=",")

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


804
805
806
def QueueOps(opts, args):
  """Queue operations.

807
808
809
810
811
812
  @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

813
814
815
816
817
818
819
820
821
  """
  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]:
822
      val = "set"
823
    else:
824
825
      val = "unset"
    ToStdout("The drain flag is %s" % val)
826
  else:
827
828
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
                               errors.ECODE_INVAL)
829

830
831
  return 0

832

833
834
835
836
837
838
839
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))


840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
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)
855
    ToStdout("The watcher is no longer paused.")
856
857
858

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

861
862
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
    _ShowWatcherPause(result)
863
864
865

  elif command == "info":
    result = client.QueryConfigValues(["watcher_pause"])
866
    _ShowWatcherPause(result[0])
867
868

  else:
869
870
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
                               errors.ECODE_INVAL)
871
872
873
874

  return 0


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

972

973
974
975
976
977
978
#: dictionary with aliases for commands
aliases = {
  'masterfailover': 'master-failover',
}


979
980
981
def Main():
  return GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER},
                     aliases=aliases)