gnt_cluster.py 30.6 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
          opts.reserved_lvs is not None or
725
          opts.master_netdev is not None or
726
          opts.prealloc_wipe_disks is not None):
727
    ToStderr("Please give at least one of the parameters.")
728
729
730
731
    return 1

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

  if not opts.lvm_storage:
    vg_name = ""
737

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

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

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

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

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

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

765
766
  mnh = opts.maintain_node_health

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

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

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

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


806
807
808
def QueueOps(opts, args):
  """Queue operations.

809
810
811
812
813
814
  @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

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

832
833
  return 0

834

835
836
837
838
839
840
841
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))


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

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

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

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

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

  return 0


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

974

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


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