gnt_cluster.py 30 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
  # prepare hvparams dict
  for hv in constants.HYPER_TYPES:
    if hv not in hvparams:
      hvparams[hv] = {}
94
    hvparams[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], hvparams[hv])
95
    utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
96

97
98
99
  if opts.candidate_pool_size is None:
    opts.candidate_pool_size = constants.MASTER_POOL_SIZE_DEFAULT

100
101
102
  if opts.mac_prefix is None:
    opts.mac_prefix = constants.DEFAULT_MAC_PREFIX

103
104
105
106
  uid_pool = opts.uid_pool
  if uid_pool is not None:
    uid_pool = uidpool.ParseUidPool(uid_pool)

107
108
109
  if opts.prealloc_wipe_disks is None:
    opts.prealloc_wipe_disks = False

110
111
112
113
114
115
  try:
    primary_ip_version = int(opts.primary_ip_version)
  except (ValueError, TypeError), err:
    ToStderr("Invalid primary ip version value: %s" % str(err))
    return 1

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


141
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
142
143
144
def DestroyCluster(opts, args):
  """Destroy the cluster.

145
146
147
148
149
  @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
150

Iustin Pop's avatar
Iustin Pop committed
151
152
  """
  if not opts.yes_do_it:
153
154
    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
155
156
157
    return 1

  op = opcodes.OpDestroyCluster()
158
  master = SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
159
160
161
  # 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
162
163
164
  return 0


165
166
167
def RenameCluster(opts, args):
  """Rename the cluster.

168
169
170
171
172
  @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
173
174

  """
175
176
177
178
179
  cl = GetClient()

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

  new_name = args[0]
180
  if not opts.force:
181
182
183
184
185
    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)
186
    if not AskUser(usertext):
187
188
      return 1

189
190
191
  op = opcodes.OpRenameCluster(name=new_name)
  result = SubmitOpCode(op, opts=opts, cl=cl)

192
193
  if result:
    ToStdout("Cluster renamed from '%s' to '%s'", cluster_name, result)
194

195
196
197
  return 0


198
199
200
201
202
203
204
205
206
207
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

  """
208
  op = opcodes.OpRedistributeConfig()
209
210
211
212
  SubmitOrSend(op, opts)
  return 0


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

216
217
218
219
220
  @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
221
222

  """
223
224
  cl = GetClient()
  result = cl.QueryClusterInfo()
225
226
227
228
229
  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
230
231
232
233
234
235
  return 0


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

236
237
238
239
240
  @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
241
242

  """
243
244
  master = bootstrap.GetMaster()
  ToStdout(master)
Iustin Pop's avatar
Iustin Pop committed
245
246
  return 0

247

Guido Trotter's avatar
Guido Trotter committed
248
def _PrintGroupedParams(paramsdict, level=1, roman=False):
249
250
251
252
  """Print Grouped parameters (be, nic, disk) by group.

  @type paramsdict: dict of dicts
  @param paramsdict: {group: {param: value, ...}, ...}
253
254
  @type level: int
  @param level: Level of indention
255
256

  """
257
  indent = "  " * level
258
  for item, val in sorted(paramsdict.items()):
259
260
    if isinstance(val, dict):
      ToStdout("%s- %s:", indent, item)
Guido Trotter's avatar
Guido Trotter committed
261
262
263
      _PrintGroupedParams(val, level=level + 1, roman=roman)
    elif roman and isinstance(val, int):
      ToStdout("%s  %s: %s", indent, item, compat.TryToRoman(val))
264
265
    else:
      ToStdout("%s  %s: %s", indent, item, val)
Iustin Pop's avatar
Iustin Pop committed
266

267

Iustin Pop's avatar
Iustin Pop committed
268
269
270
def ShowClusterConfig(opts, args):
  """Shows cluster information.

271
272
273
274
275
276
  @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
277
  """
278
279
  cl = GetClient()
  result = cl.QueryClusterInfo()
Iustin Pop's avatar
Iustin Pop committed
280

281
  ToStdout("Cluster name: %s", result["name"])
282
  ToStdout("Cluster UUID: %s", result["uuid"])
Iustin Pop's avatar
Iustin Pop committed
283

284
285
286
  ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
  ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))

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

289
290
  ToStdout("Architecture (this node): %s (%s)",
           result["architecture"][0], result["architecture"][1])
Iustin Pop's avatar
Iustin Pop committed
291

292
  if result["tags"]:
293
    tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
294
295
296
297
298
  else:
    tags = "(none)"

  ToStdout("Tags: %s", tags)

299
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
300
301
  ToStdout("Enabled hypervisors: %s",
           utils.CommaJoin(result["enabled_hypervisors"]))
302

303
  ToStdout("Hypervisor parameters:")
304
  _PrintGroupedParams(result["hvparams"])
305

306
  ToStdout("OS-specific hypervisor parameters:")
307
308
  _PrintGroupedParams(result["os_hvp"])

309
310
311
  ToStdout("OS parameters:")
  _PrintGroupedParams(result["osparams"])

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

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

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

Iustin Pop's avatar
Iustin Pop committed
340
341
342
343
344
345
  return 0


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

346
347
348
349
350
351
  @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
352
353

  """
354
355
  filename = args[0]
  if not os.path.exists(filename):
356
357
    raise errors.OpPrereqError("No such filename '%s'" % filename,
                               errors.ECODE_INVAL)
358

Iustin Pop's avatar
Iustin Pop committed
359
360
361
362
  cl = GetClient()

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

363
364
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
                           secondary_ips=opts.use_replication_network)
Michael Hanselmann's avatar
Michael Hanselmann committed
365

Iustin Pop's avatar
Iustin Pop committed
366
  srun = ssh.SshRunner(cluster_name=cluster_name)
367
368
  for node in results:
    if not srun.CopyFileToNode(node, filename):
369
      ToStderr("Copy of file %s to node %s failed", filename, node)
370

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


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

377
378
379
380
381
  @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
382
383

  """
Iustin Pop's avatar
Iustin Pop committed
384
  cl = GetClient()
Michael Hanselmann's avatar
Michael Hanselmann committed
385

Iustin Pop's avatar
Iustin Pop committed
386
  command = " ".join(args)
387
388

  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
Iustin Pop's avatar
Iustin Pop committed
389
390
391

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

Iustin Pop's avatar
Iustin Pop committed
393
  srun = ssh.SshRunner(cluster_name=cluster_name)
394

Michael Hanselmann's avatar
Michael Hanselmann committed
395
  # Make sure master node is at list end
396
397
398
399
400
401
  if master_node in nodes:
    nodes.remove(master_node)
    nodes.append(master_node)

  for name in nodes:
    result = srun.Run(name, "root", command)
402
403
404
405
    ToStdout("------------------------------------------------")
    ToStdout("node: %s", name)
    ToStdout("%s", result.output)
    ToStdout("return code = %s", result.exit_code)
406
407

  return 0
Iustin Pop's avatar
Iustin Pop committed
408
409
410
411
412


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

413
414
415
416
417
  @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
418
419

  """
Iustin Pop's avatar
Iustin Pop committed
420
  skip_checks = []
421
422
  if opts.skip_nplusone_mem:
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
423
424
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
                               verbose=opts.verbose,
425
426
                               error_codes=opts.error_codes,
                               debug_simulate_errors=opts.simulate_errors)
427
  if SubmitOpCode(op, opts=opts):
428
429
430
    return 0
  else:
    return 1
Iustin Pop's avatar
Iustin Pop committed
431
432


433
434
435
def VerifyDisks(opts, args):
  """Verify integrity of cluster disks.

436
437
438
439
440
  @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
441
442

  """
443
444
  cl = GetClient()

445
  op = opcodes.OpVerifyDisks()
446
  result = SubmitOpCode(op, opts=opts, cl=cl)
447
  if not isinstance(result, (list, tuple)) or len(result) != 3:
448
449
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")

450
  bad_nodes, instances, missing = result
451

452
  retcode = constants.EXIT_SUCCESS
453

454
455
456
  if bad_nodes:
    for node, text in bad_nodes.items():
      ToStdout("Error gathering data on node %s: %s",
457
               node, utils.SafeEncode(text[-400:]))
458
      retcode |= 1
459
      ToStdout("You need to fix these nodes first before fixing instances")
460

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

  if missing:
475
476
    (vg_name, ) = cl.QueryConfigValues(["volume_group_name"])

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

491
    ToStdout("You need to run replace_disks for all the above"
492
             " instances, if this message persist after fixing nodes.")
493
    retcode |= 1
494
495
496
497

  return retcode


498
499
500
501
502
503
504
505
506
507
508
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)
509
  SubmitOpCode(op, opts=opts)
510
511


512
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
513
514
515
516
517
518
519
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.

520
521
522
523
524
525
  @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
526
  """
527
528
529
530
531
532
533
534
535
  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
536
537


Iustin Pop's avatar
Iustin Pop committed
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
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
556
557
558
def SearchTags(opts, args):
  """Searches the tags on all the cluster.

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


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

608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
  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
630
631
632
633
634
635
636
637
638
639
  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

640
641
642
643
644
645
646
647
648
  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,
649
                                    new_confd_hmac_key,
Michael Hanselmann's avatar
Michael Hanselmann committed
650
651
652
                                    new_cds,
                                    rapi_cert_pem=rapi_cert_pem,
                                    cds=cds)
653
654
655
656

    files_to_copy = []

    if new_cluster_cert:
657
      files_to_copy.append(constants.NODED_CERT_FILE)
658
659
660
661

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

662
663
    if new_confd_hmac_key:
      files_to_copy.append(constants.CONFD_HMAC_KEY)
664

Michael Hanselmann's avatar
Michael Hanselmann committed
665
666
667
    if new_cds or cds:
      files_to_copy.append(constants.CLUSTER_DOMAIN_SECRET_FILE)

668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
    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,
690
                      opts.new_confd_hmac_key,
Michael Hanselmann's avatar
Michael Hanselmann committed
691
692
                      opts.new_cluster_domain_secret,
                      opts.cluster_domain_secret,
693
694
695
                      opts.force)


696
697
698
def SetClusterParams(opts, args):
  """Modify the cluster.

699
700
701
702
703
  @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
704
705

  """
706
  if not (not opts.lvm_storage or opts.vg_name or
707
          not opts.drbd_storage or opts.drbd_helper or
708
          opts.enabled_hypervisors or opts.hvparams or
709
          opts.beparams or opts.nicparams or
710
          opts.candidate_pool_size is not None or
711
          opts.uid_pool is not None or
712
713
          opts.maintain_node_health is not None or
          opts.add_uids is not None or
714
          opts.remove_uids is not None or
715
          opts.default_iallocator is not None or
716
717
          opts.reserved_lvs is not None or
          opts.prealloc_wipe_disks is not None):
718
    ToStderr("Please give at least one of the parameters.")
719
720
721
722
    return 1

  vg_name = opts.vg_name
  if not opts.lvm_storage and opts.vg_name:
723
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
724
    return 1
725
726
727

  if not opts.lvm_storage:
    vg_name = ""
728

729
730
731
732
733
734
735
736
  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 = ""

737
738
739
740
  hvlist = opts.enabled_hypervisors
  if hvlist is not None:
    hvlist = hvlist.split(",")

741
742
  # a list of (name, dict) we can pass directly to dict() (or [])
  hvparams = dict(opts.hvparams)
Iustin Pop's avatar
Iustin Pop committed
743
  for hv_params in hvparams.values():
744
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
745
746

  beparams = opts.beparams
747
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
748

749
750
751
  nicparams = opts.nicparams
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)

752

753
754
  mnh = opts.maintain_node_health

755
756
757
758
  uid_pool = opts.uid_pool
  if uid_pool is not None:
    uid_pool = uidpool.ParseUidPool(uid_pool)

759
760
761
762
763
764
765
766
  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)

767
768
769
770
771
772
  if opts.reserved_lvs is not None:
    if opts.reserved_lvs == "":
      opts.reserved_lvs = []
    else:
      opts.reserved_lvs = utils.UnescapeAndSplit(opts.reserved_lvs, sep=",")

773
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
774
                                  drbd_helper=drbd_helper,
775
776
                                  enabled_hypervisors=hvlist,
                                  hvparams=hvparams,
777
                                  os_hvp=None,
778
                                  beparams=beparams,
779
                                  nicparams=nicparams,
780
                                  candidate_pool_size=opts.candidate_pool_size,
781
                                  maintain_node_health=mnh,
782
783
                                  uid_pool=uid_pool,
                                  add_uids=add_uids,
784
                                  remove_uids=remove_uids,
785
                                  default_iallocator=opts.default_iallocator,
786
                                  prealloc_wipe_disks=opts.prealloc_wipe_disks,
787
                                  reserved_lvs=opts.reserved_lvs)
788
  SubmitOpCode(op, opts=opts)
789
790
791
  return 0


792
793
794
def QueueOps(opts, args):
  """Queue operations.

795
796
797
798
799
800
  @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

801
802
803
804
805
806
807
808
809
  """
  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]:
810
      val = "set"
811
    else:
812
813
      val = "unset"
    ToStdout("The drain flag is %s" % val)
814
  else:
815
816
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
                               errors.ECODE_INVAL)
817

818
819
  return 0

820

821
822
823
824
825
826
827
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))


828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
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)
843
    ToStdout("The watcher is no longer paused.")
844
845
846

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

849
850
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
    _ShowWatcherPause(result)
851
852
853

  elif command == "info":
    result = client.QueryConfigValues(["watcher_pause"])
854
    _ShowWatcherPause(result[0])
855
856

  else:
857
858
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
                               errors.ECODE_INVAL)
859
860
861
862

  return 0


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

959

960
961
962
963
964
965
#: dictionary with aliases for commands
aliases = {
  'masterfailover': 'master-failover',
}


966
967
968
def Main():
  return GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER},
                     aliases=aliases)