diff --git a/bigchaindb/commands/bigchain.py b/bigchaindb/commands/bigchain.py index 8a9c6888..b495b13a 100644 --- a/bigchaindb/commands/bigchain.py +++ b/bigchaindb/commands/bigchain.py @@ -207,8 +207,26 @@ def run_load(args): def run_set_shards(args): b = bigchaindb.Bigchain() - r.table('bigchain').reconfigure(shards=args.num_shards, replicas=1).run(b.conn) - r.table('backlog').reconfigure(shards=args.num_shards, replicas=1).run(b.conn) + for table in ['bigchain', 'backlog']: + # See https://www.rethinkdb.com/api/python/config/ + table_config = r.table(table).config().run(b.conn) + num_replicas = len(table_config['shards'][0]['replicas']) + try: + r.table(table).reconfigure(shards=args.num_shards, replicas=num_replicas).run(b.conn) + except r.ReqlOpFailedError as e: + logger.warn(e) + + +def run_set_replicas(args): + b = bigchaindb.Bigchain() + for table in ['bigchain', 'backlog']: + # See https://www.rethinkdb.com/api/python/config/ + table_config = r.table(table).config().run(b.conn) + num_shards = len(table_config['shards']) + try: + r.table(table).reconfigure(shards=num_shards, replicas=args.num_replicas).run(b.conn) + except r.ReqlOpFailedError as e: + logger.warn(e) def main(): @@ -255,9 +273,18 @@ def main(): 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, + 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)') + load_parser = subparsers.add_parser('load', help='Write transactions to the backlog') diff --git a/docs/source/nodes/bigchaindb-cli.md b/docs/source/nodes/bigchaindb-cli.md index ad9fd5a0..6ca22328 100644 --- a/docs/source/nodes/bigchaindb-cli.md +++ b/docs/source/nodes/bigchaindb-cli.md @@ -49,4 +49,11 @@ $ bigchaindb load -h This command is used to set the number of shards in the underlying datastore. For example, the following command will set the number of shards to four: ```text $ bigchaindb set-shards 4 -``` \ No newline at end of file +``` + +### bigchaindb set-replicas + +This command is used to set the number of replicas (of each shard) in the underlying datastore. For example, the following command will set the number of replicas to three (i.e. it will set the replication factor to three): +```text +$ bigchaindb set-replicas 3 +``` diff --git a/tests/test_commands.py b/tests/test_commands.py index 12c1350e..e803f7c9 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -4,8 +4,7 @@ from argparse import Namespace import copy import pytest - -from tests.db.conftest import setup_database +import rethinkdb @pytest.fixture @@ -227,21 +226,89 @@ def test_start_rethinkdb_exits_when_cannot_start(mock_popen): utils.start_rethinkdb() -def test_set_shards(b): - import rethinkdb as r +@patch('rethinkdb.ast.Table.reconfigure') +def test_set_shards(mock_reconfigure, monkeypatch, b): from bigchaindb.commands.bigchain import run_set_shards - # set the number of 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) + run_set_shards(args) + mock_reconfigure.assert_called_with(replicas=1, shards=3) + + # 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) + + +@patch('logging.Logger.warn') +def test_set_shards_raises_exception(mock_log, monkeypatch, b): + from bigchaindb.commands.bigchain 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) run_set_shards(args) - # retrieve table configuration - table_config = list(r.db('rethinkdb') - .table('table_config') - .filter(r.row['db'] == b.dbname) - .run(b.conn)) + assert mock_log.called - # check that the number of shards got set to the correct value - for table in table_config: - if table['name'] in ['backlog', 'bigchain']: - assert len(table['shards']) == 3 + +@patch('rethinkdb.ast.Table.reconfigure') +def test_set_replicas(mock_reconfigure, monkeypatch, b): + from bigchaindb.commands.bigchain 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) + run_set_replicas(args) + mock_reconfigure.assert_called_with(replicas=2, shards=2) + + # 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) + + +@patch('logging.Logger.warn') +def test_set_replicas_raises_exception(mock_log, monkeypatch, b): + from bigchaindb.commands.bigchain 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) + run_set_replicas(args) + + assert mock_log.called