gnt_cluster.py 30.4 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
  ndparams = opts.ndparams
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 ndparams dict
  ndparams = objects.FillDict(constants.NDC_DEFAULTS, ndparams)
  utils.ForceDictType(ndparams, constants.NDS_PARAMETER_TYPES)

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

102
103
104
  if opts.candidate_pool_size is None:
    opts.candidate_pool_size = constants.MASTER_POOL_SIZE_DEFAULT

105
106
107
  if opts.mac_prefix is None:
    opts.mac_prefix = constants.DEFAULT_MAC_PREFIX

108
109
110
111
  uid_pool = opts.uid_pool
  if uid_pool is not None:
    uid_pool = uidpool.ParseUidPool(uid_pool)

112
113
114
  if opts.prealloc_wipe_disks is None:
    opts.prealloc_wipe_disks = False

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

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


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

151
152
153
154
155
  @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
156

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

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


171
172
173
def RenameCluster(opts, args):
  """Rename the cluster.

174
175
176
177
178
  @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
179
180

  """
181
182
183
184
185
  cl = GetClient()

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

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

195
196
197
  op = opcodes.OpRenameCluster(name=new_name)
  result = SubmitOpCode(op, opts=opts, cl=cl)

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

201
202
203
  return 0


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

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


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

222
223
224
225
226
  @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
227
228

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


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

242
243
244
245
246
  @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
247
248

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

253

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

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

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

273

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

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

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

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

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

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

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

  ToStdout("Tags: %s", tags)

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

309
  ToStdout("Hypervisor parameters:")
310
  _PrintGroupedParams(result["hvparams"])
311

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

315
316
317
  ToStdout("OS parameters:")
  _PrintGroupedParams(result["osparams"])

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

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

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

Iustin Pop's avatar
Iustin Pop committed
346
347
348
349
350
351
  return 0


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

352
353
354
355
356
357
  @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
358
359

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

Iustin Pop's avatar
Iustin Pop committed
365
366
367
368
  cl = GetClient()

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

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

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

Iustin Pop's avatar
Iustin Pop committed
377
378
379
380
381
382
  return 0


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

383
384
385
386
387
  @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
388
389

  """
Iustin Pop's avatar
Iustin Pop committed
390
  cl = GetClient()
Michael Hanselmann's avatar
Michael Hanselmann committed
391

Iustin Pop's avatar
Iustin Pop committed
392
  command = " ".join(args)
393
394

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

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

Iustin Pop's avatar
Iustin Pop committed
399
  srun = ssh.SshRunner(cluster_name=cluster_name)
400

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

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

  return 0
Iustin Pop's avatar
Iustin Pop committed
414
415
416
417
418


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

419
420
421
422
423
  @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
424
425

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


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

442
443
444
445
446
  @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
447
448

  """
449
450
  cl = GetClient()

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

456
  bad_nodes, instances, missing = result
457

458
  retcode = constants.EXIT_SUCCESS
459

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

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

  if missing:
481
482
    (vg_name, ) = cl.QueryConfigValues(["volume_group_name"])

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

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

  return retcode


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


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

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


Iustin Pop's avatar
Iustin Pop committed
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
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
562
563
564
def SearchTags(opts, args):
  """Searches the tags on all the cluster.

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


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

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

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

    files_to_copy = []

    if new_cluster_cert:
663
      files_to_copy.append(constants.NODED_CERT_FILE)
664
665
666
667

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

668
669
    if new_confd_hmac_key:
      files_to_copy.append(constants.CONFD_HMAC_KEY)
670

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

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


702
703
704
def SetClusterParams(opts, args):
  """Modify the cluster.

705
706
707
708
709
  @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
710
711

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

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

  if not opts.lvm_storage:
    vg_name = ""
734

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

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

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

  beparams = opts.beparams
753
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
754

755
756
757
  nicparams = opts.nicparams
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)

758
759
  ndparams = opts.ndparams
  utils.ForceDictType(ndparams, constants.NDS_PARAMETER_TYPES)
760

761
762
  mnh = opts.maintain_node_health

763
764
765
766
  uid_pool = opts.uid_pool
  if uid_pool is not None:
    uid_pool = uidpool.ParseUidPool(uid_pool)

767
768
769
770
771
772
773
774
  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)

775
776
777
778
779
780
  if opts.reserved_lvs is not None:
    if opts.reserved_lvs == "":
      opts.reserved_lvs = []
    else:
      opts.reserved_lvs = utils.UnescapeAndSplit(opts.reserved_lvs, sep=",")

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


801
802
803
def QueueOps(opts, args):
  """Queue operations.

804
805
806
807
808
809
  @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

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

827
828
  return 0

829

830
831
832
833
834
835
836
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))


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

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

858
859
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
    _ShowWatcherPause(result)
860
861
862

  elif command == "info":
    result = client.QueryConfigValues(["watcher_pause"])
863
    _ShowWatcherPause(result[0])
864
865

  else:
866
867
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
                               errors.ECODE_INVAL)
868
869
870
871

  return 0


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

969

970
971
972
973
974
975
#: dictionary with aliases for commands
aliases = {
  'masterfailover': 'master-failover',
}


976
977
978
def Main():
  return GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER},
                     aliases=aliases)