Initialize replica set for mongodb when running bigchaindb init

This commit is contained in:
Rodolphe Marques 2016-12-21 15:53:25 +01:00
parent 83d37d699f
commit 4a14c7c8f1
2 changed files with 105 additions and 0 deletions

View File

@ -1,8 +1,10 @@
"""Utils to initialize and drop the database."""
import time
import logging
from pymongo import ASCENDING, DESCENDING
from pymongo import errors
from bigchaindb import backend
from bigchaindb.common import exceptions
@ -24,6 +26,9 @@ def create_database(conn, dbname):
# TODO: read and write concerns can be declared here
conn.conn.get_database(dbname)
# initialize the replica set
initialize_replica_set(conn)
@register_schema(MongoDBConnection)
def create_tables(conn, dbname):
@ -85,3 +90,68 @@ def create_votes_secondary_index(conn, dbname):
('node_pubkey',
ASCENDING)],
name='block_and_voter')
def initialize_replica_set(conn):
"""Initialize a replica set. If already initialized skip."""
replica_set_name = _get_replica_set_name(conn)
config = {'_id': replica_set_name,
'members': [{'_id': 0, 'host': 'localhost:27017'}]}
try:
conn.conn.admin.command('replSetInitiate', config)
except errors.OperationFailure as exc_info:
if exc_info.details['codeName'] == 'AlreadyInitialized':
logger.info('Replica set already initialized')
return
else:
raise
_wait_for_replica_set_initialization(conn)
logger.info('Initialized replica set')
def _get_replica_set_name(conn):
"""Checks if the replSet option was enabled either through the command
line option or config file.
Note:
The setting we are looking for will have a different name depending
if it was set by the config file (`replSetName`) or by command
line arguments (`replSet`).
Returns:
The replica set name if enabled.
Raise:
:exc:`~ConfigurationError`: If mongod was not started with the
replSet option.
"""
options = conn.conn.admin.command('getCmdLineOpts')
try:
repl_opts = options['parsed']['replication']
return repl_opts.get('replSetName', None) or repl_opts['replSet']
except KeyError:
raise exceptions.ConfigurationError('mongod was not started with'
' the replSet option.')
def _wait_for_replica_set_initialization(conn):
"""Wait for a replica set to finish initialization.
If a replica set is being initialized for the first time it takes some
time. Nodes need to discover each other and an election needs to take
place. During this time the database is not writable so we need to wait
before continuing with the rest of the initialization
"""
# I did not find a better way to do this for now.
# To check if the database is ready we will poll the mongodb logs until
# we find the line that says the database is ready
logger.info('Waiting for mongodb replica set initialization')
while True:
logs = conn.conn.admin.command('getLog', 'rs')['log']
for line in logs:
if 'database writes are now permitted' in line:
return
time.sleep(0.1)

View File

@ -1,4 +1,5 @@
import pytest
from unittest.mock import patch
@pytest.mark.usefixtures('setup_database')
@ -107,3 +108,37 @@ def test_drop():
schema.drop_database(conn, dbname)
assert dbname not in conn.conn.database_names()
@pytest.mark.usefixtures('setup_database')
def test_get_replica_set_name():
from pymongo.database import Database
from bigchaindb import backend
from bigchaindb.backend.mongodb.schema import _get_replica_set_name
from bigchaindb.common.exceptions import ConfigurationError
conn = backend.connect()
# no replSet option set
cmd_line_opts = {'argv': ['mongod', '--dbpath=/data'],
'ok': 1.0,
'parsed': {'storage': {'dbPath': '/data'}}}
with patch.object(Database, 'command', return_value=cmd_line_opts):
with pytest.raises(ConfigurationError):
_get_replica_set_name(conn)
# replSet option set through the command line
cmd_line_opts = {'argv': ['mongod', '--dbpath=/data', '--replSet=rs0'],
'ok': 1.0,
'parsed': {'replication': {'replSet': 'rs0'},
'storage': {'dbPath': '/data'}}}
with patch.object(Database, 'command', return_value=cmd_line_opts):
assert _get_replica_set_name(conn) == 'rs0'
# replSet option set through the config file
cmd_line_opts = {'argv': ['mongod', '--dbpath=/data', '--replSet=rs0'],
'ok': 1.0,
'parsed': {'replication': {'replSetName': 'rs0'},
'storage': {'dbPath': '/data'}}}
with patch.object(Database, 'command', return_value=cmd_line_opts):
assert _get_replica_set_name(conn) == 'rs0'