Revert "Migrate cli for tendermint integration"

This commit is contained in:
Ahmed Muawia Khan 2018-03-05 14:36:54 +01:00 committed by Sylvain Bellemare
parent 81c6975501
commit 60f2ccaead
9 changed files with 585 additions and 49 deletions

View File

@ -10,16 +10,21 @@ import json
import sys
from bigchaindb.common import crypto
from bigchaindb.common.exceptions import (DatabaseAlreadyExists,
from bigchaindb.common.exceptions import (StartupError,
DatabaseAlreadyExists,
KeypairNotFoundException,
DatabaseDoesNotExist)
import bigchaindb
from bigchaindb import backend
from bigchaindb.backend import schema
from bigchaindb.backend import query
from bigchaindb.backend.admin import (set_replicas, set_shards, add_replicas,
remove_replicas)
from bigchaindb.backend.exceptions import OperationError
from bigchaindb.commands import utils
from bigchaindb.commands.messages import (
CANNOT_START_KEYPAIR_NOT_FOUND,
RETHINKDB_STARTUP_ERROR,
)
from bigchaindb.commands.utils import (
configure_bigchaindb, start_logging_process, input_on_stderr)
@ -74,8 +79,8 @@ def run_configure(args, skip_if_exists=False):
# Patch the default configuration with the new values
conf = bigchaindb.config_utils.update(
conf,
bigchaindb.config_utils.env_config(bigchaindb.config))
conf,
bigchaindb.config_utils.env_config(bigchaindb.config))
print('Generating keypair', file=sys.stderr)
conf['keypair']['private'], conf['keypair']['public'] = \
@ -90,18 +95,15 @@ def run_configure(args, skip_if_exists=False):
if not args.yes:
for key in ('bind', ):
val = conf['server'][key]
conf['server'][key] = input_on_stderr(
'API Server {}? (default `{}`): '.format(key, val), val)
conf['server'][key] = input_on_stderr('API Server {}? (default `{}`): '.format(key, val), val)
for key in ('scheme', 'host', 'port'):
val = conf['wsserver'][key]
conf['wsserver'][key] = input_on_stderr(
'WebSocket Server {}? (default `{}`): '.format(key, val), val)
conf['wsserver'][key] = input_on_stderr('WebSocket Server {}? (default `{}`): '.format(key, val), val)
for key in database_keys:
val = conf['database'][key]
conf['database'][key] = input_on_stderr(
'Database {}? (default `{}`): '.format(key, val), val)
conf['database'][key] = input_on_stderr('Database {}? (default `{}`): '.format(key, val), val)
val = conf['backlog_reassign_delay']
conf['backlog_reassign_delay'] = input_on_stderr(
@ -115,6 +117,21 @@ def run_configure(args, skip_if_exists=False):
print('Ready to go!', file=sys.stderr)
@configure_bigchaindb
def run_export_my_pubkey(args):
"""Export this node's public key to standard output
"""
pubkey = bigchaindb.config['keypair']['public']
if pubkey is not None:
print(pubkey)
else:
sys.exit("This node's public key wasn't set anywhere "
"so it can't be exported")
# raises SystemExit exception
# message is sent to stderr
# exits with exit code 1 (signals tha an error happened)
def _run_init():
# Try to access the keypair, throws an exception if it does not exist
b = bigchaindb.Bigchain()
@ -146,8 +163,7 @@ def run_recover(b):
if block:
while block['height'] > tendermint_height:
logger.info(
'BigchainDB is ahead of tendermint, removing block %s', block['height'])
logger.info('BigchainDB is ahead of tendermint, removing block %s', block['height'])
query.delete_latest_block(b.connection)
block = b.get_latest_block()
@ -158,8 +174,7 @@ def run_drop(args):
dbname = bigchaindb.config['database']['name']
if not args.yes:
response = input_on_stderr(
'Do you want to drop `{}` database? [y/n]: '.format(dbname))
response = input_on_stderr('Do you want to drop `{}` database? [y/n]: '.format(dbname))
if response != 'y':
return
@ -168,8 +183,7 @@ def run_drop(args):
try:
schema.drop_database(conn, dbname)
except DatabaseDoesNotExist:
print("Cannot drop '{name}'. The database does not exist.".format(
name=dbname), file=sys.stderr)
print("Cannot drop '{name}'. The database does not exist.".format(name=dbname), file=sys.stderr)
@configure_bigchaindb
@ -180,7 +194,22 @@ def run_start(args):
# run_recover(BigchainDB())
logger.info('Keypair found, no need to create one on the fly.')
if args.allow_temp_keypair:
if not (bigchaindb.config['keypair']['private'] or
bigchaindb.config['keypair']['public']):
private_key, public_key = crypto.generate_key_pair()
bigchaindb.config['keypair']['private'] = private_key
bigchaindb.config['keypair']['public'] = public_key
else:
logger.warning('Keypair found, no need to create one on the fly.')
if args.start_rethinkdb:
try:
proc = utils.start_rethinkdb()
except StartupError as e:
sys.exit(RETHINKDB_STARTUP_ERROR.format(e))
logger.info('RethinkDB started with PID %s' % proc.pid)
try:
if not args.skip_initialize_database:
@ -197,6 +226,50 @@ def run_start(args):
start()
@configure_bigchaindb
def run_set_shards(args):
conn = backend.connect()
try:
set_shards(conn, shards=args.num_shards)
except OperationError as e:
sys.exit(str(e))
@configure_bigchaindb
def run_set_replicas(args):
conn = backend.connect()
try:
set_replicas(conn, replicas=args.num_replicas)
except OperationError as e:
sys.exit(str(e))
@configure_bigchaindb
def run_add_replicas(args):
# Note: This command is specific to MongoDB
conn = backend.connect()
try:
add_replicas(conn, args.replicas)
except (OperationError, NotImplementedError) as e:
sys.exit(str(e))
else:
print('Added {} to the replicaset.'.format(args.replicas))
@configure_bigchaindb
def run_remove_replicas(args):
# Note: This command is specific to MongoDB
conn = backend.connect()
try:
remove_replicas(conn, args.replicas)
except (OperationError, NotImplementedError) as e:
sys.exit(str(e))
else:
print('Removed {} from the replicaset.'.format(args.replicas))
def create_parser():
parser = argparse.ArgumentParser(
description='Control your BigchainDB node.',
@ -218,13 +291,16 @@ def create_parser():
default='localmongodb',
const='localmongodb',
nargs='?',
help='The backend to use. It can only be '
'"localmongodb", currently.')
help='The backend to use. It can be either '
'rethinkdb or mongodb.')
# parsers for showing/exporting config values
subparsers.add_parser('show-config',
help='Show the current configuration')
subparsers.add_parser('export-my-pubkey',
help="Export this node's public key")
# parser for database-level commands
subparsers.add_parser('init',
help='Init the database')
@ -236,12 +312,63 @@ def create_parser():
start_parser = subparsers.add_parser('start',
help='Start BigchainDB')
start_parser.add_argument('--dev-allow-temp-keypair',
dest='allow_temp_keypair',
action='store_true',
help='Generate a random keypair on start')
start_parser.add_argument('--dev-start-rethinkdb',
dest='start_rethinkdb',
action='store_true',
help='Run RethinkDB on start')
start_parser.add_argument('--no-init',
dest='skip_initialize_database',
default=False,
action='store_true',
help='Skip database initialization')
# parser for configuring the number of shards
sharding_parser = subparsers.add_parser('set-shards',
help='Configure number of shards')
sharding_parser.add_argument('num_shards', metavar='num_shards',
type=int, default=1,
help='Number of shards')
# parser for configuring the number of replicas
replicas_parser = subparsers.add_parser('set-replicas',
help='Configure number of replicas')
replicas_parser.add_argument('num_replicas', metavar='num_replicas',
type=int, default=1,
help='Number of replicas (i.e. the replication factor)')
# parser for adding nodes to the replica set
add_replicas_parser = subparsers.add_parser('add-replicas',
help='Add a set of nodes to the '
'replica set. This command '
'is specific to the MongoDB'
' backend.')
add_replicas_parser.add_argument('replicas', nargs='+',
type=utils.mongodb_host,
help='A list of space separated hosts to '
'add to the replicaset. Each host '
'should be in the form `host:port`.')
# parser for removing nodes from the replica set
rm_replicas_parser = subparsers.add_parser('remove-replicas',
help='Remove a set of nodes from the '
'replica set. This command '
'is specific to the MongoDB'
' backend.')
rm_replicas_parser.add_argument('replicas', nargs='+',
type=utils.mongodb_host,
help='A list of space separated hosts to '
'remove from the replicaset. Each host '
'should be in the form `host:port`.')
return parser

View File

@ -6,10 +6,16 @@ import argparse
import builtins
import functools
import multiprocessing as mp
import subprocess
import sys
import rethinkdb as r
from pymongo import uri_parser
import bigchaindb
import bigchaindb.config_utils
from bigchaindb import backend
from bigchaindb.common.exceptions import StartupError
from bigchaindb.log.setup import setup_logging
from bigchaindb.version import __version__
@ -119,6 +125,46 @@ def input_on_stderr(prompt='', default=None, convert=None):
return _convert(value, default, convert)
def start_rethinkdb():
"""Start RethinkDB as a child process and wait for it to be
available.
Raises:
:class:`~bigchaindb.common.exceptions.StartupError` if
RethinkDB cannot be started.
"""
proc = subprocess.Popen(['rethinkdb', '--bind', 'all'],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True)
dbname = bigchaindb.config['database']['name']
line = ''
for line in proc.stdout:
if line.startswith('Server ready'):
# FIXME: seems like tables are not ready when the server is ready,
# that's why we need to query RethinkDB to know the state
# of the database. This code assumes the tables are ready
# when the database is ready. This seems a valid assumption.
try:
conn = backend.connect()
# Before checking if the db is ready, we need to query
# the server to check if it contains that db
if conn.run(r.db_list().contains(dbname)):
conn.run(r.db(dbname).wait())
except (r.ReqlOpFailedError, r.ReqlDriverError) as exc:
raise StartupError('Error waiting for the database `{}` '
'to be ready'.format(dbname)) from exc
return proc
# We are here when we exhaust the stdout of the process.
# The last `line` contains info about the error.
raise StartupError(line)
def start(parser, argv, scope):
"""Utility function to execute a subcommand.
@ -160,6 +206,34 @@ def start(parser, argv, scope):
return func(args)
def mongodb_host(host):
"""Utility function that works as a type for mongodb ``host`` args.
This function validates the ``host`` args provided by to the
``add-replicas`` and ``remove-replicas`` commands and checks if each arg
is in the form "host:port"
Args:
host (str): A string containing hostname and port (e.g. "host:port")
Raises:
ArgumentTypeError: if it fails to parse the argument
"""
# check if mongodb can parse the host
try:
hostname, port = uri_parser.parse_host(host, default_port=None)
except ValueError as exc:
raise argparse.ArgumentTypeError(exc.args[0])
# we do require the port to be provided.
if port is None or hostname == '':
raise argparse.ArgumentTypeError('expected host in the form '
'`host:port`. Got `{}` instead.'
.format(host))
return host
base_parser = argparse.ArgumentParser(add_help=False, prog='bigchaindb')
base_parser.add_argument('-c', '--config',
@ -170,8 +244,7 @@ base_parser.add_argument('-c', '--config',
# the environment variables provided to configure the logger.
base_parser.add_argument('-l', '--log-level',
type=str.upper, # convert to uppercase for comparison to choices
choices=['DEBUG', 'INFO',
'WARNING', 'ERROR', 'CRITICAL'],
choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
help='Log level')
base_parser.add_argument('-y', '--yes', '--yes-please',

View File

@ -12,8 +12,11 @@ ENV PYTHONUNBUFFERED 0
ENV BIGCHAINDB_DATABASE_PORT 27017
ENV BIGCHAINDB_DATABASE_BACKEND $backend
ENV BIGCHAINDB_SERVER_BIND 0.0.0.0:9984
ENV BIGCHAINDB_WSSERVER_HOST 0.0.0.0
ENV BIGCHAINDB_WSSERVER_SCHEME ws
ENV BIGCHAINDB_WSSERVER_ADVERTISED_HOST 0.0.0.0
ENV BIGCHAINDB_WSSERVER_ADVERTISED_SCHEME ws
ENV BIGCHAINDB_START_TENDERMINT 0

View File

@ -19,6 +19,8 @@ services:
BIGCHAINDB_DATABASE_BACKEND: localmongodb
BIGCHAINDB_DATABASE_HOST: mdb
BIGCHAINDB_DATABASE_PORT: 27017
BIGCHAINDB_SERVER_BIND: 0.0.0.0:9984
BIGCHAINDB_WSSERVER_HOST: 0.0.0.0
BIGCHAINDB_START_TENDERMINT: 0
BIGCHAINDB_TENDERMINT_HOST: tendermint
BIGCHAINDB_TENDERMINT_PORT: 46657

View File

@ -47,6 +47,8 @@ def run_start_args(request):
param = getattr(request, 'param', {})
return Namespace(
config=param.get('config'),
start_rethinkdb=param.get('start_rethinkdb', False),
allow_temp_keypair=param.get('allow_temp_keypair', False),
skip_initialize_database=param.get('skip_initialize_database', False),
)

View File

View File

@ -0,0 +1,125 @@
import pytest
import rethinkdb
from unittest.mock import Mock, patch
from argparse import Namespace
@patch('bigchaindb.commands.utils.start_rethinkdb', return_value=Mock())
def test_bigchain_run_start_with_rethinkdb(mock_start_rethinkdb,
mock_run_configure,
mock_processes_start,
mock_db_init_with_existing_db,
mocked_setup_logging):
from bigchaindb import config
from bigchaindb.commands.bigchaindb import run_start
args = Namespace(start_rethinkdb=True, allow_temp_keypair=False, config=None, yes=True,
skip_initialize_database=False)
run_start(args)
mock_start_rethinkdb.assert_called_with()
mocked_setup_logging.assert_called_once_with(user_log_config=config['log'])
@patch('subprocess.Popen')
def test_start_rethinkdb_returns_a_process_when_successful(mock_popen):
from bigchaindb.commands import utils
mock_popen.return_value = Mock(stdout=[
'Listening for client driver 1234',
'Server ready'])
assert utils.start_rethinkdb() is mock_popen.return_value
@patch('subprocess.Popen')
def test_start_rethinkdb_exits_when_cannot_start(mock_popen):
from bigchaindb.common import exceptions
from bigchaindb.commands import utils
mock_popen.return_value = Mock(stdout=['Nopety nope'])
with pytest.raises(exceptions.StartupError):
utils.start_rethinkdb()
@patch('rethinkdb.ast.Table.reconfigure')
def test_set_shards(mock_reconfigure, monkeypatch, b):
from bigchaindb.commands.bigchaindb import run_set_shards
# this will mock the call to retrieve the database config
# we will set it to return one replica
def mockreturn_one_replica(self, conn):
return {'shards': [{'replicas': [1]}]}
monkeypatch.setattr(rethinkdb.RqlQuery, 'run', mockreturn_one_replica)
args = Namespace(num_shards=3, config=None)
run_set_shards(args)
mock_reconfigure.assert_called_with(replicas=1, shards=3, dry_run=False)
# this will mock the call to retrieve the database config
# we will set it to return three replica
def mockreturn_three_replicas(self, conn):
return {'shards': [{'replicas': [1, 2, 3]}]}
monkeypatch.setattr(rethinkdb.RqlQuery, 'run', mockreturn_three_replicas)
run_set_shards(args)
mock_reconfigure.assert_called_with(replicas=3, shards=3, dry_run=False)
def test_set_shards_raises_exception(monkeypatch, b):
from bigchaindb.commands.bigchaindb import run_set_shards
# test that we are correctly catching the exception
def mock_raise(*args, **kwargs):
raise rethinkdb.ReqlOpFailedError('')
def mockreturn_one_replica(self, conn):
return {'shards': [{'replicas': [1]}]}
monkeypatch.setattr(rethinkdb.RqlQuery, 'run', mockreturn_one_replica)
monkeypatch.setattr(rethinkdb.ast.Table, 'reconfigure', mock_raise)
args = Namespace(num_shards=3, config=None)
with pytest.raises(SystemExit) as exc:
run_set_shards(args)
assert exc.value.args == ('Failed to reconfigure tables.',)
@patch('rethinkdb.ast.Table.reconfigure')
def test_set_replicas(mock_reconfigure, monkeypatch, b):
from bigchaindb.commands.bigchaindb import run_set_replicas
# this will mock the call to retrieve the database config
# we will set it to return two shards
def mockreturn_two_shards(self, conn):
return {'shards': [1, 2]}
monkeypatch.setattr(rethinkdb.RqlQuery, 'run', mockreturn_two_shards)
args = Namespace(num_replicas=2, config=None)
run_set_replicas(args)
mock_reconfigure.assert_called_with(replicas=2, shards=2, dry_run=False)
# this will mock the call to retrieve the database config
# we will set it to return three shards
def mockreturn_three_shards(self, conn):
return {'shards': [1, 2, 3]}
monkeypatch.setattr(rethinkdb.RqlQuery, 'run', mockreturn_three_shards)
run_set_replicas(args)
mock_reconfigure.assert_called_with(replicas=2, shards=3, dry_run=False)
def test_set_replicas_raises_exception(monkeypatch, b):
from bigchaindb.commands.bigchaindb import run_set_replicas
# test that we are correctly catching the exception
def mock_raise(*args, **kwargs):
raise rethinkdb.ReqlOpFailedError('')
def mockreturn_two_shards(self, conn):
return {'shards': [1, 2]}
monkeypatch.setattr(rethinkdb.RqlQuery, 'run', mockreturn_two_shards)
monkeypatch.setattr(rethinkdb.ast.Table, 'reconfigure', mock_raise)
args = Namespace(num_replicas=2, config=None)
with pytest.raises(SystemExit) as exc:
run_set_replicas(args)
assert exc.value.args == ('Failed to reconfigure tables.',)

View File

@ -16,12 +16,16 @@ def test_make_sure_we_dont_remove_any_command():
assert parser.parse_args(['configure', 'localmongodb']).command
assert parser.parse_args(['configure', 'localmongodb']).command
assert parser.parse_args(['show-config']).command
assert parser.parse_args(['export-my-pubkey']).command
assert parser.parse_args(['init']).command
assert parser.parse_args(['drop']).command
assert parser.parse_args(['start']).command
assert parser.parse_args(['set-shards', '1']).command
assert parser.parse_args(['set-replicas', '1']).command
assert parser.parse_args(['add-replicas', 'localhost:27017']).command
assert parser.parse_args(['remove-replicas', 'localhost:27017']).command
@pytest.mark.tendermint
@patch('bigchaindb.commands.utils.start')
def test_main_entrypoint(mock_start):
from bigchaindb.commands.bigchaindb import main
@ -36,13 +40,12 @@ def test_bigchain_run_start(mock_run_configure,
mocked_setup_logging):
from bigchaindb import config
from bigchaindb.commands.bigchaindb import run_start
args = Namespace(config=None, yes=True,
args = Namespace(start_rethinkdb=False, allow_temp_keypair=False, config=None, yes=True,
skip_initialize_database=False)
run_start(args)
mocked_setup_logging.assert_called_once_with(user_log_config=config['log'])
@pytest.mark.tendermint
@pytest.mark.skipif(reason="BigchainDB doesn't support the automatic creation of a config file anymore")
def test_bigchain_run_start_assume_yes_create_default_config(
monkeypatch, mock_processes_start, mock_generate_key_pair,
@ -60,8 +63,7 @@ def test_bigchain_run_start_assume_yes_create_default_config(
value['return'] = newconfig
monkeypatch.setattr(config_utils, 'write_config', mock_write_config)
monkeypatch.setattr(config_utils, 'file_config',
lambda x: config_utils.set_config(expected_config))
monkeypatch.setattr(config_utils, 'file_config', lambda x: config_utils.set_config(expected_config))
monkeypatch.setattr('os.path.exists', lambda path: False)
args = Namespace(config=None, yes=True)
@ -74,7 +76,6 @@ def test_bigchain_run_start_assume_yes_create_default_config(
# TODO Please beware, that if debugging, the "-s" switch for pytest will
# interfere with capsys.
# See related issue: https://github.com/pytest-dev/pytest/issues/128
@pytest.mark.tendermint
@pytest.mark.usefixtures('ignore_local_config_file')
def test_bigchain_show_config(capsys):
from bigchaindb import config
@ -89,7 +90,45 @@ def test_bigchain_show_config(capsys):
assert output_config == config
@pytest.mark.tendermint
@pytest.mark.usefixtures('ignore_local_config_file')
def test_bigchain_export_my_pubkey_when_pubkey_set(capsys, monkeypatch):
from bigchaindb import config
from bigchaindb.commands.bigchaindb import run_export_my_pubkey
args = Namespace(config=None)
# so in run_export_my_pubkey(args) below,
# filename=args.config='dummy' is passed to autoconfigure().
# We just assume autoconfigure() works and sets
# config['keypair']['public'] correctly (tested elsewhere).
# We force-set config['keypair']['public'] using monkeypatch.
monkeypatch.setitem(config['keypair'], 'public', 'Charlie_Bucket')
_, _ = capsys.readouterr() # has the effect of clearing capsys
run_export_my_pubkey(args)
out, _ = capsys.readouterr()
lines = out.splitlines()
assert config['keypair']['public'] in lines
assert 'Charlie_Bucket' in lines
@pytest.mark.usefixtures('ignore_local_config_file')
def test_bigchain_export_my_pubkey_when_pubkey_not_set(monkeypatch):
from bigchaindb import config
from bigchaindb.commands.bigchaindb import run_export_my_pubkey
args = Namespace(config=None)
monkeypatch.setitem(config['keypair'], 'public', None)
# assert that run_export_my_pubkey(args) raises SystemExit:
with pytest.raises(SystemExit) as exc_info:
run_export_my_pubkey(args)
# exc_info is an object of class ExceptionInfo
# https://pytest.org/latest/builtin.html#_pytest._code.ExceptionInfo
assert exc_info.type == SystemExit
# exc_info.value is an object of class SystemExit
# https://docs.python.org/3/library/exceptions.html#SystemExit
assert exc_info.value.code == \
"This node's public key wasn't set anywhere so it can't be exported"
def test_bigchain_run_init_when_db_exists(mocker, capsys):
from bigchaindb.commands.bigchaindb import run_init
from bigchaindb.common.exceptions import DatabaseAlreadyExists
@ -109,7 +148,6 @@ def test_bigchain_run_init_when_db_exists(mocker, capsys):
)
@pytest.mark.tendermint
def test__run_init(mocker):
from bigchaindb.commands.bigchaindb import _run_init
bigchain_mock = mocker.patch(
@ -126,7 +164,6 @@ def test__run_init(mocker):
bigchain_mock.return_value.create_genesis_block.assert_called_once_with()
@pytest.mark.tendermint
@patch('bigchaindb.backend.schema.drop_database')
def test_drop_db_when_assumed_yes(mock_db_drop):
from bigchaindb.commands.bigchaindb import run_drop
@ -136,19 +173,16 @@ def test_drop_db_when_assumed_yes(mock_db_drop):
assert mock_db_drop.called
@pytest.mark.tendermint
@patch('bigchaindb.backend.schema.drop_database')
def test_drop_db_when_interactive_yes(mock_db_drop, monkeypatch):
from bigchaindb.commands.bigchaindb import run_drop
args = Namespace(config=None, yes=False)
monkeypatch.setattr(
'bigchaindb.commands.bigchaindb.input_on_stderr', lambda x: 'y')
monkeypatch.setattr('bigchaindb.commands.bigchaindb.input_on_stderr', lambda x: 'y')
run_drop(args)
assert mock_db_drop.called
@pytest.mark.tendermint
@patch('bigchaindb.backend.schema.drop_database')
def test_drop_db_when_db_does_not_exist(mock_db_drop, capsys):
from bigchaindb import config
@ -163,19 +197,16 @@ def test_drop_db_when_db_does_not_exist(mock_db_drop, capsys):
name=config['database']['name'])
@pytest.mark.tendermint
@patch('bigchaindb.backend.schema.drop_database')
def test_drop_db_does_not_drop_when_interactive_no(mock_db_drop, monkeypatch):
from bigchaindb.commands.bigchaindb import run_drop
args = Namespace(config=None, yes=False)
monkeypatch.setattr(
'bigchaindb.commands.bigchaindb.input_on_stderr', lambda x: 'n')
monkeypatch.setattr('bigchaindb.commands.bigchaindb.input_on_stderr', lambda x: 'n')
run_drop(args)
assert not mock_db_drop.called
@pytest.mark.tendermint
def test_run_configure_when_config_exists_and_skipping(monkeypatch):
from bigchaindb.commands.bigchaindb import run_configure
monkeypatch.setattr('os.path.exists', lambda path: True)
@ -187,7 +218,6 @@ def test_run_configure_when_config_exists_and_skipping(monkeypatch):
# TODO Beware if you are putting breakpoints in there, and using the '-s'
# switch with pytest. It will just hang. Seems related to the monkeypatching of
# input_on_stderr.
@pytest.mark.tendermint
def test_run_configure_when_config_does_not_exist(monkeypatch,
mock_write_config,
mock_generate_key_pair,
@ -200,7 +230,6 @@ def test_run_configure_when_config_does_not_exist(monkeypatch,
assert return_value is None
@pytest.mark.tendermint
def test_run_configure_when_config_does_exist(monkeypatch,
mock_write_config,
mock_generate_key_pair,
@ -213,17 +242,16 @@ def test_run_configure_when_config_does_exist(monkeypatch,
from bigchaindb.commands.bigchaindb import run_configure
monkeypatch.setattr('os.path.exists', lambda path: True)
monkeypatch.setattr('builtins.input', lambda: '\n')
monkeypatch.setattr(
'bigchaindb.config_utils.write_config', mock_write_config)
monkeypatch.setattr('bigchaindb.config_utils.write_config', mock_write_config)
args = Namespace(config='foo', yes=None)
run_configure(args)
assert value == {}
@pytest.mark.tendermint
@pytest.mark.parametrize('backend', (
'localmongodb',
'rethinkdb',
'mongodb',
))
def test_run_configure_with_backend(backend, monkeypatch, mock_write_config):
import bigchaindb
@ -251,6 +279,54 @@ def test_run_configure_with_backend(backend, monkeypatch, mock_write_config):
assert value['return'] == expected_config
@patch('bigchaindb.common.crypto.generate_key_pair',
return_value=('private_key', 'public_key'))
@pytest.mark.usefixtures('ignore_local_config_file')
def test_allow_temp_keypair_generates_one_on_the_fly(
mock_gen_keypair, mock_processes_start,
mock_db_init_with_existing_db, mocked_setup_logging):
import bigchaindb
from bigchaindb.commands.bigchaindb import run_start
bigchaindb.config['keypair'] = {'private': None, 'public': None}
args = Namespace(allow_temp_keypair=True, start_rethinkdb=False, config=None, yes=True,
skip_initialize_database=False)
run_start(args)
mocked_setup_logging.assert_called_once_with(
user_log_config=bigchaindb.config['log'])
assert bigchaindb.config['keypair']['private'] == 'private_key'
assert bigchaindb.config['keypair']['public'] == 'public_key'
@patch('bigchaindb.common.crypto.generate_key_pair',
return_value=('private_key', 'public_key'))
@pytest.mark.usefixtures('ignore_local_config_file')
def test_allow_temp_keypair_doesnt_override_if_keypair_found(mock_gen_keypair,
mock_processes_start,
mock_db_init_with_existing_db,
mocked_setup_logging):
import bigchaindb
from bigchaindb.commands.bigchaindb import run_start
# Preconditions for the test
original_private_key = bigchaindb.config['keypair']['private']
original_public_key = bigchaindb.config['keypair']['public']
assert isinstance(original_public_key, str)
assert isinstance(original_private_key, str)
args = Namespace(allow_temp_keypair=True, start_rethinkdb=False, config=None, yes=True,
skip_initialize_database=False)
run_start(args)
mocked_setup_logging.assert_called_once_with(
user_log_config=bigchaindb.config['log'])
assert bigchaindb.config['keypair']['private'] == original_private_key
assert bigchaindb.config['keypair']['public'] == original_public_key
def test_run_start_when_db_already_exists(mocker,
monkeypatch,
run_start_args,
@ -270,7 +346,6 @@ def test_run_start_when_db_already_exists(mocker,
assert mocked_start.called
@pytest.mark.tendermint
def test_run_start_when_keypair_not_found(mocker,
monkeypatch,
run_start_args,
@ -296,7 +371,33 @@ def test_run_start_when_keypair_not_found(mocker,
assert not mocked_start.called
@pytest.mark.tendermint
def test_run_start_when_start_rethinkdb_fails(mocker,
monkeypatch,
run_start_args,
mocked_setup_logging):
from bigchaindb import config
from bigchaindb.commands.bigchaindb import run_start
from bigchaindb.commands.messages import RETHINKDB_STARTUP_ERROR
from bigchaindb.common.exceptions import StartupError
run_start_args.start_rethinkdb = True
mocked_start = mocker.patch('bigchaindb.processes.start')
err_msg = 'Error starting rethinkdb.'
def mock_start_rethinkdb():
raise StartupError(err_msg)
monkeypatch.setattr(
'bigchaindb.commands.utils.start_rethinkdb', mock_start_rethinkdb)
with pytest.raises(SystemExit) as exc:
run_start(run_start_args)
mocked_setup_logging.assert_called_once_with(user_log_config=config['log'])
assert len(exc.value.args) == 1
assert exc.value.args[0] == RETHINKDB_STARTUP_ERROR.format(err_msg)
assert not mocked_start.called
@patch('argparse.ArgumentParser.parse_args')
@patch('bigchaindb.commands.utils.base_parser')
@patch('bigchaindb.commands.utils.start')
@ -323,14 +424,102 @@ def test_calling_main(start_mock, base_parser_mock, parse_args_mock,
subparsers.add_parser.assert_any_call('show-config',
help='Show the current '
'configuration')
subparsers.add_parserassert_any_call('export-my-pubkey',
help="Export this node's public "
'key')
subparsers.add_parser.assert_any_call('init', help='Init the database')
subparsers.add_parser.assert_any_call('drop', help='Drop the database')
subparsers.add_parser.assert_any_call('start', help='Start BigchainDB')
subsubparsers.add_argument.assert_any_call('--dev-start-rethinkdb',
dest='start_rethinkdb',
action='store_true',
help='Run RethinkDB on start')
subsubparsers.add_argument.assert_any_call('--dev-allow-temp-keypair',
dest='allow_temp_keypair',
action='store_true',
help='Generate a random keypair on start')
subparsers.add_parser.assert_any_call('set-shards',
help='Configure number of shards')
subsubparsers.add_argument.assert_any_call('num_shards',
metavar='num_shards',
type=int, default=1,
help='Number of shards')
subparsers.add_parser.assert_any_call('set-replicas',
help='Configure number of replicas')
subsubparsers.add_argument.assert_any_call('num_replicas',
metavar='num_replicas',
type=int, default=1,
help='Number of replicas (i.e. '
'the replication factor)')
assert start_mock.called is True
@pytest.mark.usefixtures('ignore_local_config_file')
@patch('bigchaindb.commands.bigchaindb.add_replicas')
def test_run_add_replicas(mock_add_replicas):
from bigchaindb.commands.bigchaindb import run_add_replicas
from bigchaindb.backend.exceptions import OperationError
args = Namespace(config=None, replicas=['localhost:27017'])
# test add_replicas no raises
mock_add_replicas.return_value = None
assert run_add_replicas(args) is None
assert mock_add_replicas.call_count == 1
mock_add_replicas.reset_mock()
# test add_replicas with `OperationError`
mock_add_replicas.side_effect = OperationError('err')
with pytest.raises(SystemExit) as exc:
run_add_replicas(args)
assert exc.value.args == ('err',)
assert mock_add_replicas.call_count == 1
mock_add_replicas.reset_mock()
# test add_replicas with `NotImplementedError`
mock_add_replicas.side_effect = NotImplementedError('err')
with pytest.raises(SystemExit) as exc:
run_add_replicas(args)
assert exc.value.args == ('err',)
assert mock_add_replicas.call_count == 1
mock_add_replicas.reset_mock()
@pytest.mark.usefixtures('ignore_local_config_file')
@patch('bigchaindb.commands.bigchaindb.remove_replicas')
def test_run_remove_replicas(mock_remove_replicas):
from bigchaindb.commands.bigchaindb import run_remove_replicas
from bigchaindb.backend.exceptions import OperationError
args = Namespace(config=None, replicas=['localhost:27017'])
# test add_replicas no raises
mock_remove_replicas.return_value = None
assert run_remove_replicas(args) is None
assert mock_remove_replicas.call_count == 1
mock_remove_replicas.reset_mock()
# test add_replicas with `OperationError`
mock_remove_replicas.side_effect = OperationError('err')
with pytest.raises(SystemExit) as exc:
run_remove_replicas(args)
assert exc.value.args == ('err',)
assert mock_remove_replicas.call_count == 1
mock_remove_replicas.reset_mock()
# test add_replicas with `NotImplementedError`
mock_remove_replicas.side_effect = NotImplementedError('err')
with pytest.raises(SystemExit) as exc:
run_remove_replicas(args)
assert exc.value.args == ('err',)
assert mock_remove_replicas.call_count == 1
mock_remove_replicas.reset_mock()
@pytest.mark.tendermint
@pytest.mark.bdb
def test_recover_db_from_zombie_txn(b, monkeypatch):
@ -408,7 +597,7 @@ def test_recover_db_on_start(mock_autoconfigure,
mock_start,
mocked_setup_logging):
from bigchaindb.commands.bigchaindb import run_start
args = Namespace(config=None, yes=True,
args = Namespace(start_rethinkdb=False, allow_temp_keypair=False, config=None, yes=True,
skip_initialize_database=False)
run_start(args)

View File

@ -1,5 +1,5 @@
import argparse
from argparse import Namespace
from argparse import ArgumentTypeError, Namespace
import logging
import pytest
@ -119,5 +119,20 @@ def test_start_sets_multiprocess_var_based_on_cli_args(mock_cpu_count):
scope = {'run_mp_arg_test': run_mp_arg_test}
assert utils.start(parser, ['mp_arg_test'], scope).multiprocess == 1
assert utils.start(
parser, ['mp_arg_test', '--multiprocess'], scope).multiprocess == 42
assert utils.start(parser, ['mp_arg_test', '--multiprocess'], scope).multiprocess == 42
def test_mongodb_host_type():
from bigchaindb.commands.utils import mongodb_host
# bad port provided
with pytest.raises(ArgumentTypeError):
mongodb_host('localhost:11111111111')
# no port information provided
with pytest.raises(ArgumentTypeError):
mongodb_host('localhost')
# bad host provided
with pytest.raises(ArgumentTypeError):
mongodb_host(':27017')