From 5584de59b0020cae8addc85049300433318637c6 Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Fri, 3 Mar 2017 11:36:50 +0100 Subject: [PATCH 01/10] Make ValidationError a superclass of all validation errors and use it --- bigchaindb/common/exceptions.py | 106 +++++++++++++------------ bigchaindb/core.py | 32 +------- bigchaindb/models.py | 71 +++++------------ bigchaindb/pipelines/block.py | 14 ++-- bigchaindb/pipelines/vote.py | 10 ++- bigchaindb/web/views/transactions.py | 27 +------ tests/assets/test_digital_assets.py | 5 +- tests/db/test_bigchain_api.py | 21 ++--- tests/pipelines/test_block_creation.py | 27 +++---- tests/pipelines/test_vote.py | 15 +++- tests/test_models.py | 8 +- tests/web/test_transactions.py | 10 +-- 12 files changed, 140 insertions(+), 206 deletions(-) diff --git a/bigchaindb/common/exceptions.py b/bigchaindb/common/exceptions.py index 60340492..4b95f84b 100644 --- a/bigchaindb/common/exceptions.py +++ b/bigchaindb/common/exceptions.py @@ -7,40 +7,6 @@ class ConfigurationError(BigchainDBError): """Raised when there is a problem with server configuration""" -class OperationError(BigchainDBError): - """Raised when an operation cannot go through""" - - -class TransactionDoesNotExist(BigchainDBError): - """Raised if the transaction is not in the database""" - - -class TransactionOwnerError(BigchainDBError): - """Raised if a user tries to transfer a transaction they don't own""" - - -class DoubleSpend(BigchainDBError): - """Raised if a double spend is found""" - - -class ValidationError(BigchainDBError): - """Raised if there was an error in validation""" - - -class InvalidHash(ValidationError): - """Raised if there was an error checking the hash for a particular - operation""" - - -class SchemaValidationError(ValidationError): - """Raised if there was any error validating an object's schema""" - - -class InvalidSignature(BigchainDBError): - """Raised if there was an error checking the signature for a particular - operation""" - - class DatabaseAlreadyExists(BigchainDBError): """Raised when trying to create the database but the db is already there""" @@ -49,6 +15,18 @@ class DatabaseDoesNotExist(BigchainDBError): """Raised when trying to delete the database but the db is not there""" +class StartupError(BigchainDBError): + """Raised when there is an error starting up the system""" + + +class GenesisBlockAlreadyExistsError(BigchainDBError): + """Raised when trying to create the already existing genesis block""" + + +class CyclicBlockchainError(BigchainDBError): + """Raised when there is a cycle in the blockchain""" + + class KeypairNotFoundException(BigchainDBError): """Raised if operation cannot proceed because the keypair was not given""" @@ -58,34 +36,64 @@ class KeypairMismatchException(BigchainDBError): current owner(s)""" -class StartupError(BigchainDBError): - """Raised when there is an error starting up the system""" +class OperationError(BigchainDBError): + """Raised when an operation cannot go through""" -class ImproperVoteError(BigchainDBError): +################################################################################ +# Validation errors + + +class ValidationError(BigchainDBError): + """Raised if there was an error in validation""" + + +class DoubleSpend(ValidationError): + """Raised if a double spend is found""" + + +class InvalidHash(ValidationError): + """Raised if there was an error checking the hash for a particular + operation""" + + +class SchemaValidationError(ValidationError): + """Raised if there was any error validating an object's schema""" + + +class InvalidSignature(ValidationError): + """Raised if there was an error checking the signature for a particular + operation""" + + +class ImproperVoteError(ValidationError): """Raised if a vote is not constructed correctly, or signed incorrectly""" -class MultipleVotesError(BigchainDBError): +class MultipleVotesError(ValidationError): """Raised if a voter has voted more than once""" -class GenesisBlockAlreadyExistsError(BigchainDBError): - """Raised when trying to create the already existing genesis block""" - - -class CyclicBlockchainError(BigchainDBError): - """Raised when there is a cycle in the blockchain""" - - -class TransactionNotInValidBlock(BigchainDBError): +class TransactionNotInValidBlock(ValidationError): """Raised when a transfer transaction is attempting to fulfill the outputs of a transaction that is in an invalid or undecided block""" -class AssetIdMismatch(BigchainDBError): +class AssetIdMismatch(ValidationError): """Raised when multiple transaction inputs related to different assets""" -class AmountError(BigchainDBError): +class AmountError(ValidationError): """Raised when there is a problem with a transaction's output amounts""" + + +class TransactionDoesNotExist(ValidationError): + """Raised if the transaction is not in the database""" + + +class TransactionOwnerError(ValidationError): + """Raised if a user tries to transfer a transaction they don't own""" + + +class SybilError(ValidationError): + """If a block or vote comes from an unidentifiable node""" diff --git a/bigchaindb/core.py b/bigchaindb/core.py index 9f93d47a..084df928 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -162,31 +162,6 @@ class Bigchain(object): return self.consensus.validate_transaction(self, transaction) - def is_valid_transaction(self, transaction): - """Check whether a transaction is valid or invalid. - - Similar to :meth:`~bigchaindb.Bigchain.validate_transaction` - but never raises an exception. It returns :obj:`False` if - the transaction is invalid. - - Args: - transaction (:Class:`~bigchaindb.models.Transaction`): transaction - to check. - - Returns: - The :class:`~bigchaindb.models.Transaction` instance if valid, - otherwise :obj:`False`. - """ - - try: - return self.validate_transaction(transaction) - except (ValueError, exceptions.OperationError, - exceptions.TransactionDoesNotExist, - exceptions.TransactionOwnerError, exceptions.DoubleSpend, - exceptions.InvalidHash, exceptions.InvalidSignature, - exceptions.TransactionNotInValidBlock, exceptions.AmountError): - return False - def is_new_transaction(self, txid, exclude_block_id=None): """ Return True if the transaction does not exist in any @@ -386,10 +361,9 @@ class Bigchain(object): if self.get_transaction(transaction['id']): num_valid_transactions += 1 if num_valid_transactions > 1: - raise exceptions.DoubleSpend(('`{}` was spent more than' - ' once. There is a problem' - ' with the chain') - .format(txid)) + raise exceptions.BigchainDBCritical( + '`{}` was spent more than once. There is a problem' + ' with the chain'.format(txid)) if num_valid_transactions: return Transaction.from_dict(transactions[0]) diff --git a/bigchaindb/models.py b/bigchaindb/models.py index ee7efe8f..fd71f98d 100644 --- a/bigchaindb/models.py +++ b/bigchaindb/models.py @@ -1,9 +1,9 @@ from bigchaindb.common.crypto import hash_data, PublicKey, PrivateKey from bigchaindb.common.exceptions import (InvalidHash, InvalidSignature, - OperationError, DoubleSpend, - TransactionDoesNotExist, + DoubleSpend, TransactionDoesNotExist, TransactionNotInValidBlock, - AssetIdMismatch, AmountError) + AssetIdMismatch, AmountError, + SybilError, ValidationError) from bigchaindb.common.transaction import Transaction from bigchaindb.common.utils import gen_timestamp, serialize from bigchaindb.common.schema import validate_transaction_schema @@ -22,19 +22,10 @@ class Transaction(Transaction): invalid. Raises: - OperationError: if the transaction operation is not supported - TransactionDoesNotExist: if the input of the transaction is not - found - TransactionNotInValidBlock: if the input of the transaction is not - in a valid block - TransactionOwnerError: if the new transaction is using an input it - doesn't own - DoubleSpend: if the transaction is a double spend - InvalidHash: if the hash of the transaction is wrong - InvalidSignature: if the signature of the transaction is wrong + ValidationError: If the transaction is invalid """ if len(self.inputs) == 0: - raise ValueError('Transaction contains no inputs') + raise ValidationError('Transaction contains no inputs') input_conditions = [] inputs_defined = all([input_.fulfills for input_ in self.inputs]) @@ -46,20 +37,20 @@ class Transaction(Transaction): if self.operation in (Transaction.CREATE, Transaction.GENESIS): # validate asset if self.asset['data'] is not None and not isinstance(self.asset['data'], dict): - raise TypeError(('`asset.data` must be a dict instance or ' - 'None for `CREATE` transactions')) + raise ValidationError(('`asset.data` must be a dict instance or ' + 'None for `CREATE` transactions')) # validate inputs if inputs_defined: - raise ValueError('A CREATE operation has no inputs') + raise ValidationError('A CREATE operation has no inputs') elif self.operation == Transaction.TRANSFER: # validate asset if not isinstance(self.asset['id'], str): - raise ValueError(('`asset.id` must be a string for ' - '`TRANSFER` transations')) + raise ValidationError('`asset.id` must be a string for ' + '`TRANSFER` transations') # check inputs if not inputs_defined: - raise ValueError('Only `CREATE` transactions can have null ' - 'inputs') + raise ValidationError('Only `CREATE` transactions can have ' + 'null inputs') # store the inputs so that we can check if the asset ids match input_txs = [] @@ -116,8 +107,8 @@ class Transaction(Transaction): else: allowed_operations = ', '.join(Transaction.ALLOWED_OPERATIONS) - raise TypeError('`operation`: `{}` must be either {}.' - .format(self.operation, allowed_operations)) + raise ValidationError('`operation`: `{}` must be either {}.' + .format(self.operation, allowed_operations)) if not self.inputs_valid(input_conditions): raise InvalidSignature('Transaction signature is invalid.') @@ -205,18 +196,8 @@ class Block(object): raised. Raises: - OperationError: If a non-federation node signed the Block. - InvalidSignature: If a Block's signature is invalid or if the - block contains a transaction with an invalid signature. - OperationError: if the transaction operation is not supported - TransactionDoesNotExist: if the input of the transaction is not - found - TransactionNotInValidBlock: if the input of the transaction is not - in a valid block - TransactionOwnerError: if the new transaction is using an input it - doesn't own - DoubleSpend: if the transaction is a double spend - InvalidHash: if the hash of the transaction is wrong + ValidationError: If the block or any transaction in the block does + not validate """ self._validate_block(bigchain) @@ -232,13 +213,12 @@ class Block(object): object. Raises: - OperationError: If a non-federation node signed the Block. - InvalidSignature: If a Block's signature is invalid. + ValidationError: If there is a problem with the block """ # Check if the block was created by a federation node possible_voters = (bigchain.nodes_except_me + [bigchain.me]) if self.node_pubkey not in possible_voters: - raise OperationError('Only federation nodes can create blocks') + raise SybilError('Only federation nodes can create blocks') # Check that the signature is valid if not self.is_signature_valid(): @@ -251,16 +231,7 @@ class Block(object): bigchain (Bigchain): an instantiated bigchaindb.Bigchain object. Raises: - OperationError: if the transaction operation is not supported - TransactionDoesNotExist: if the input of the transaction is not - found - TransactionNotInValidBlock: if the input of the transaction is not - in a valid block - TransactionOwnerError: if the new transaction is using an input it - doesn't own - DoubleSpend: if the transaction is a double spend - InvalidHash: if the hash of the transaction is wrong - InvalidSignature: if the signature of the transaction is wrong + ValidationError: If an invalid transaction is found """ for tx in self.transactions: # If a transaction is not valid, `validate_transactions` will @@ -341,10 +312,10 @@ class Block(object): dict: The Block as a dict. Raises: - OperationError: If the Block doesn't contain any transactions. + ValueError: If the Block doesn't contain any transactions. """ if len(self.transactions) == 0: - raise OperationError('Empty block creation is not allowed') + raise ValueError('Empty block creation is not allowed') block = { 'timestamp': self.timestamp, diff --git a/bigchaindb/pipelines/block.py b/bigchaindb/pipelines/block.py index 1f2e9017..2a43686a 100644 --- a/bigchaindb/pipelines/block.py +++ b/bigchaindb/pipelines/block.py @@ -13,8 +13,7 @@ import bigchaindb from bigchaindb import backend from bigchaindb.backend.changefeed import ChangeFeed from bigchaindb.models import Transaction -from bigchaindb.common.exceptions import (SchemaValidationError, InvalidHash, - InvalidSignature, AmountError) +from bigchaindb.common.exceptions import ValidationError from bigchaindb import Bigchain @@ -63,8 +62,7 @@ class BlockPipeline: """ try: tx = Transaction.from_dict(tx) - except (SchemaValidationError, InvalidHash, InvalidSignature, - AmountError): + except ValidationError: return None # If transaction is in any VALID or UNDECIDED block we @@ -74,12 +72,14 @@ class BlockPipeline: return None # If transaction is not valid it should not be included - if not self.bigchain.is_valid_transaction(tx): + try: + tx.validate(self.bigchain) + return tx + except ValidationError as e: + # todo: log self.bigchain.delete_transaction(tx.id) return None - return tx - def create(self, tx, timeout=False): """Create a block. diff --git a/bigchaindb/pipelines/vote.py b/bigchaindb/pipelines/vote.py index da28cb30..088c4eac 100644 --- a/bigchaindb/pipelines/vote.py +++ b/bigchaindb/pipelines/vote.py @@ -61,7 +61,7 @@ class Vote: return block['id'], [self.invalid_dummy_tx] try: block._validate_block(self.bigchain) - except (exceptions.OperationError, exceptions.InvalidSignature): + except exceptions.ValidationError: # XXX: if a block is invalid we should skip the `validate_tx` # step, but since we are in a pipeline we cannot just jump to # another function. Hackish solution: generate an invalid @@ -105,7 +105,13 @@ class Vote: if not new: return False, block_id, num_tx - valid = bool(self.bigchain.is_valid_transaction(tx)) + try: + tx.validate(self.bigchain) + valid = True + except exceptions.ValidationError: + # TODO: log + valid = False + return valid, block_id, num_tx def vote(self, tx_validity, block_id, num_tx): diff --git a/bigchaindb/web/views/transactions.py b/bigchaindb/web/views/transactions.py index 7acaa279..925aed7a 100644 --- a/bigchaindb/web/views/transactions.py +++ b/bigchaindb/web/views/transactions.py @@ -9,20 +9,7 @@ import logging from flask import current_app, request from flask_restful import Resource, reqparse - -from bigchaindb.common.exceptions import ( - AmountError, - DoubleSpend, - InvalidHash, - InvalidSignature, - SchemaValidationError, - OperationError, - TransactionDoesNotExist, - TransactionOwnerError, - TransactionNotInValidBlock, - ValidationError, -) - +from bigchaindb.common.exceptions import SchemaValidationError, ValidationError from bigchaindb.models import Transaction from bigchaindb.web.views.base import make_error from bigchaindb.web.views import parameters @@ -84,7 +71,7 @@ class TransactionListApi(Resource): message='Invalid transaction schema: {}'.format( e.__cause__.message) ) - except (ValidationError, InvalidSignature) as e: + except ValidationError as e: return make_error( 400, 'Invalid transaction ({}): {}'.format(type(e).__name__, e) @@ -93,15 +80,7 @@ class TransactionListApi(Resource): with pool() as bigchain: try: bigchain.validate_transaction(tx_obj) - except (ValueError, - OperationError, - TransactionDoesNotExist, - TransactionOwnerError, - DoubleSpend, - InvalidHash, - InvalidSignature, - TransactionNotInValidBlock, - AmountError) as e: + except ValidationError as e: return make_error( 400, 'Invalid transaction ({}): {}'.format(type(e).__name__, e) diff --git a/tests/assets/test_digital_assets.py b/tests/assets/test_digital_assets.py index 1dc4764f..d44bc52c 100644 --- a/tests/assets/test_digital_assets.py +++ b/tests/assets/test_digital_assets.py @@ -1,3 +1,4 @@ +from bigchaindb.common.exceptions import ValidationError import pytest import random @@ -26,7 +27,7 @@ def test_validate_bad_asset_creation(b, user_pk): tx.asset['data'] = 'a' tx_signed = tx.sign([b.me_private]) - with pytest.raises(TypeError): + with pytest.raises(ValidationError): b.validate_transaction(tx_signed) @@ -108,4 +109,4 @@ def test_create_valid_divisible_asset(b, user_pk, user_sk): tx = Transaction.create([user_pk], [([user_pk], 2)]) tx_signed = tx.sign([user_sk]) - assert b.is_valid_transaction(tx_signed) + tx_signed.validate(b) diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index 96779e60..1f71862a 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -3,6 +3,8 @@ from time import sleep import pytest from unittest.mock import patch +from bigchaindb.common.exceptions import ValidationError + pytestmark = pytest.mark.bdb @@ -565,14 +567,14 @@ class TestTransactionValidation(object): # Manipulate input so that it has a `fulfills` defined even # though it shouldn't have one create_tx.inputs[0].fulfills = TransactionLink('abc', 0) - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValidationError) as excinfo: b.validate_transaction(create_tx) assert excinfo.value.args[0] == 'A CREATE operation has no inputs' def test_transfer_operation_no_inputs(self, b, user_pk, signed_transfer_tx): signed_transfer_tx.inputs[0].fulfills = None - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValidationError) as excinfo: b.validate_transaction(signed_transfer_tx) assert excinfo.value.args[0] == 'Only `CREATE` transactions can have null inputs' @@ -741,7 +743,7 @@ class TestBlockValidation(object): b.validate_block(block) def test_invalid_node_pubkey(self, b): - from bigchaindb.common.exceptions import OperationError + from bigchaindb.common.exceptions import SybilError from bigchaindb.common import crypto # blocks can only be created by a federation node @@ -758,8 +760,8 @@ class TestBlockValidation(object): # from a non federation node block = block.sign(tmp_sk) - # check that validate_block raises an OperationError - with pytest.raises(OperationError): + # check that validate_block raises an SybilError + with pytest.raises(SybilError): b.validate_block(block) @@ -778,7 +780,7 @@ class TestMultipleInputs(object): tx = tx.sign([user_sk]) # validate transaction - assert b.is_valid_transaction(tx) == tx + tx.validate(b) assert len(tx.inputs) == 1 assert len(tx.outputs) == 1 @@ -800,7 +802,7 @@ class TestMultipleInputs(object): asset_id=input_tx.id) tx = tx.sign([user_sk]) - assert b.is_valid_transaction(tx) == tx + tx.validate(b) assert len(tx.inputs) == 1 assert len(tx.outputs) == 1 @@ -832,7 +834,7 @@ class TestMultipleInputs(object): transfer_tx = transfer_tx.sign([user_sk, user2_sk]) # validate transaction - assert b.is_valid_transaction(transfer_tx) == transfer_tx + transfer_tx.validate(b) assert len(transfer_tx.inputs) == 1 assert len(transfer_tx.outputs) == 1 @@ -865,7 +867,7 @@ class TestMultipleInputs(object): asset_id=tx_input.id) tx = tx.sign([user_sk, user2_sk]) - assert b.is_valid_transaction(tx) == tx + tx.validate(b) assert len(tx.inputs) == 1 assert len(tx.outputs) == 1 @@ -1219,7 +1221,6 @@ def test_cant_spend_same_input_twice_in_tx(b, genesis_block): tx_transfer = Transaction.transfer(dup_inputs, [([b.me], 200)], asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([b.me_private]) - assert b.is_valid_transaction(tx_transfer_signed) is False with pytest.raises(DoubleSpend): tx_transfer_signed.validate(b) diff --git a/tests/pipelines/test_block_creation.py b/tests/pipelines/test_block_creation.py index 2991f3cf..de829167 100644 --- a/tests/pipelines/test_block_creation.py +++ b/tests/pipelines/test_block_creation.py @@ -46,28 +46,19 @@ def test_validate_transaction_handles_exceptions(b, signed_create_tx): """ from bigchaindb.pipelines.block import BlockPipeline block_maker = BlockPipeline() + from bigchaindb.common.exceptions import ValidationError - # Test SchemaValidationError tx_dict = signed_create_tx.to_dict() - tx_dict['invalid_key'] = 'schema validation gonna getcha!' - assert block_maker.validate_tx(tx_dict) is None - # Test InvalidHash - tx_dict = signed_create_tx.to_dict() - tx_dict['id'] = 'a' * 64 - assert block_maker.validate_tx(tx_dict) is None + with patch('bigchaindb.models.Transaction.validate') as validate: + # Assert that validationerror gets caught + validate.side_effect = ValidationError() + assert block_maker.validate_tx(tx_dict) is None - # Test InvalidSignature when we pass a bad fulfillment - tx_dict = signed_create_tx.to_dict() - tx_dict['inputs'][0]['fulfillment'] = 'cf:0:aaaaaaaaaaaaaaaaaaaaaaaaa' - assert block_maker.validate_tx(tx_dict) is None - - # Test AmountError - signed_create_tx.outputs[0].amount = 0 - tx_dict = signed_create_tx.to_dict() - # set the correct value back so that we can continue using it - signed_create_tx.outputs[0].amount = 1 - assert block_maker.validate_tx(tx_dict) is None + # Assert that another error doesnt + validate.side_effect = IOError() + with pytest.raises(IOError): + block_maker.validate_tx(tx_dict) def test_create_block(b, user_pk): diff --git a/tests/pipelines/test_vote.py b/tests/pipelines/test_vote.py index 20beac1e..aaa184f0 100644 --- a/tests/pipelines/test_vote.py +++ b/tests/pipelines/test_vote.py @@ -135,10 +135,17 @@ def test_vote_validate_transaction(b): validation = vote_obj.validate_tx(tx, 123, 1) assert validation == (True, 123, 1) - # NOTE: Submit unsigned transaction to `validate_tx` yields `False`. - tx = Transaction.create([b.me], [([b.me], 1)]) - validation = vote_obj.validate_tx(tx, 456, 10) - assert validation == (False, 456, 10) + with patch('bigchaindb.models.Transaction.validate') as validate: + # Assert that validationerror gets caught + validate.side_effect = ValidationError() + validation = vote_obj.validate_tx(tx, 456, 10) + assert validation == (False, 456, 10) + + # Assert that another error doesnt + validate.side_effect = IOError() + with pytest.raises(IOError): + validation = vote_obj.validate_tx(tx, 456, 10) + @pytest.mark.genesis diff --git a/tests/test_models.py b/tests/test_models.py index 8de3a6c2..e3252f9b 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,4 +1,5 @@ from pytest import raises +from bigchaindb.common.exceptions import ValidationError class TestTransactionModel(object): @@ -8,12 +9,12 @@ class TestTransactionModel(object): tx = Transaction.create([b.me], [([b.me], 1)]) tx.operation = 'something invalid' - with raises(TypeError): + with raises(ValidationError): tx.validate(b) tx.operation = 'CREATE' tx.inputs = [] - with raises(ValueError): + with raises(ValidationError): tx.validate(b) @@ -61,11 +62,10 @@ class TestBlockModel(object): assert block.to_dict() == expected def test_block_invalid_serializaton(self): - from bigchaindb.common.exceptions import OperationError from bigchaindb.models import Block block = Block([]) - with raises(OperationError): + with raises(ValueError): block.to_dict() def test_block_deserialization(self, b): diff --git a/tests/web/test_transactions.py b/tests/web/test_transactions.py index 71f4f0e9..cf4105e9 100644 --- a/tests/web/test_transactions.py +++ b/tests/web/test_transactions.py @@ -1,4 +1,3 @@ -import builtins import json from unittest.mock import patch @@ -113,18 +112,15 @@ def test_post_create_transaction_with_invalid_schema(client, caplog): ('DoubleSpend', 'Nope! It is gone now!'), ('InvalidHash', 'Do not smoke that!'), ('InvalidSignature', 'Falsche Unterschrift!'), - ('OperationError', 'Create and transfer!'), + ('ValidationError', 'Create and transfer!'), ('TransactionDoesNotExist', 'Hallucinations?'), ('TransactionOwnerError', 'Not yours!'), ('TransactionNotInValidBlock', 'Wait, maybe?'), - ('ValueError', '?'), + ('ValidationError', '?'), )) def test_post_invalid_transaction(client, exc, msg, monkeypatch, caplog): from bigchaindb.common import exceptions - try: - exc_cls = getattr(exceptions, exc) - except AttributeError: - exc_cls = getattr(builtins, 'ValueError') + exc_cls = getattr(exceptions, exc) def mock_validation(self_, tx): raise exc_cls(msg) From 59e21bfa4dba107e189f5bfb9aab998fcccff06b Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Fri, 3 Mar 2017 11:52:12 +0100 Subject: [PATCH 02/10] fix test, log tx validation errors and document ValidationError --- bigchaindb/common/exceptions.py | 5 +++++ bigchaindb/pipelines/block.py | 2 +- bigchaindb/pipelines/vote.py | 4 ++-- tests/pipelines/test_vote.py | 3 +-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/bigchaindb/common/exceptions.py b/bigchaindb/common/exceptions.py index 4b95f84b..76513010 100644 --- a/bigchaindb/common/exceptions.py +++ b/bigchaindb/common/exceptions.py @@ -42,6 +42,11 @@ class OperationError(BigchainDBError): ################################################################################ # Validation errors +# +# All validation errors (which are handleable errors, not faults) should +# subclass ValidationError. However, where possible they should also have their +# own distinct type to differentiate them from other validation errors, +# especially for the purposes of testing. class ValidationError(BigchainDBError): diff --git a/bigchaindb/pipelines/block.py b/bigchaindb/pipelines/block.py index 2a43686a..b1d6cdee 100644 --- a/bigchaindb/pipelines/block.py +++ b/bigchaindb/pipelines/block.py @@ -76,7 +76,7 @@ class BlockPipeline: tx.validate(self.bigchain) return tx except ValidationError as e: - # todo: log + logger.warning('Invalid tx: %s' % e) self.bigchain.delete_transaction(tx.id) return None diff --git a/bigchaindb/pipelines/vote.py b/bigchaindb/pipelines/vote.py index 088c4eac..0431e20b 100644 --- a/bigchaindb/pipelines/vote.py +++ b/bigchaindb/pipelines/vote.py @@ -108,8 +108,8 @@ class Vote: try: tx.validate(self.bigchain) valid = True - except exceptions.ValidationError: - # TODO: log + except exceptions.ValidationError as e: + logger.warning('Invalid tx: %s' % e) valid = False return valid, block_id, num_tx diff --git a/tests/pipelines/test_vote.py b/tests/pipelines/test_vote.py index aaa184f0..fa167d17 100644 --- a/tests/pipelines/test_vote.py +++ b/tests/pipelines/test_vote.py @@ -128,7 +128,7 @@ def test_validate_block_with_invalid_signature(b): @pytest.mark.genesis def test_vote_validate_transaction(b): from bigchaindb.pipelines import vote - from bigchaindb.models import Transaction + from bigchaindb.common.exceptions import ValidationError tx = dummy_tx(b) vote_obj = vote.Vote() @@ -147,7 +147,6 @@ def test_vote_validate_transaction(b): validation = vote_obj.validate_tx(tx, 456, 10) - @pytest.mark.genesis def test_vote_accumulates_transactions(b): from bigchaindb.pipelines import vote From 3346fcb47b993c6d6fb88a20792fdb8b0fa47202 Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Fri, 3 Mar 2017 13:48:52 +0100 Subject: [PATCH 03/10] break BigchainDBCritical into CriticalDoubleSpend and CriticalDoubleInclusion and add test --- bigchaindb/backend/exceptions.py | 8 ++++-- bigchaindb/core.py | 4 +-- tests/db/test_bigchain_api.py | 43 +++++++++++++++++++++++++++++--- 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/bigchaindb/backend/exceptions.py b/bigchaindb/backend/exceptions.py index 3b712b08..a5eff242 100644 --- a/bigchaindb/backend/exceptions.py +++ b/bigchaindb/backend/exceptions.py @@ -17,5 +17,9 @@ class DuplicateKeyError(OperationError): """Exception raised when an insert fails because the key is not unique""" -class BigchainDBCritical(Exception): - """Unhandleable error that requires attention""" +class CriticalDoubleSpend(BigchainDBError): + """Data integrity error that requires attention""" + + +class CriticalDoubleInclusion(BigchainDBError): + """Data integrity error that requires attention""" diff --git a/bigchaindb/core.py b/bigchaindb/core.py index 084df928..c0da6177 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -308,7 +308,7 @@ class Bigchain(object): if list(validity.values()).count(Bigchain.BLOCK_VALID) > 1: block_ids = str([block for block in validity if validity[block] == Bigchain.BLOCK_VALID]) - raise backend_exceptions.BigchainDBCritical( + raise backend_exceptions.CriticalDoubleInclusion( 'Transaction {tx} is present in ' 'multiple valid blocks: {block_ids}' .format(tx=txid, block_ids=block_ids)) @@ -361,7 +361,7 @@ class Bigchain(object): if self.get_transaction(transaction['id']): num_valid_transactions += 1 if num_valid_transactions > 1: - raise exceptions.BigchainDBCritical( + raise backend_exceptions.CriticalDoubleSpend( '`{}` was spent more than once. There is a problem' ' with the chain'.format(txid)) diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index 1f71862a..f56c9e5a 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -93,7 +93,7 @@ class TestBigchainApi(object): @pytest.mark.genesis def test_get_spent_with_double_inclusion_detected(self, b, monkeypatch): - from bigchaindb.backend.exceptions import BigchainDBCritical + from bigchaindb.backend.exceptions import CriticalDoubleInclusion from bigchaindb.models import Transaction tx = Transaction.create([b.me], [([b.me], 1)]) @@ -123,12 +123,47 @@ class TestBigchainApi(object): vote = b.vote(block3.id, b.get_last_voted_block().id, True) b.write_vote(vote) - with pytest.raises(BigchainDBCritical): + with pytest.raises(CriticalDoubleInclusion): + b.get_spent(tx.id, 0) + + @pytest.mark.genesis + def test_get_spent_with_double_spend_detected(self, b, monkeypatch): + from bigchaindb.backend.exceptions import CriticalDoubleSpend + from bigchaindb.models import Transaction + + tx = Transaction.create([b.me], [([b.me], 1)]) + tx = tx.sign([b.me_private]) + + monkeypatch.setattr('time.time', lambda: 1000000000) + block1 = b.create_block([tx]) + b.write_block(block1) + + monkeypatch.setattr('time.time', lambda: 1000000020) + transfer_tx = Transaction.transfer(tx.to_inputs(), [([b.me], 1)], + asset_id=tx.id) + transfer_tx = transfer_tx.sign([b.me_private]) + block2 = b.create_block([transfer_tx]) + b.write_block(block2) + + monkeypatch.setattr('time.time', lambda: 1000000030) + transfer_tx2 = Transaction.transfer(tx.to_inputs(), [([b.me], 2)], + asset_id=tx.id) + transfer_tx2 = transfer_tx2.sign([b.me_private]) + block3 = b.create_block([transfer_tx2]) + b.write_block(block3) + + # Vote both block2 and block3 valid + vote = b.vote(block2.id, b.get_last_voted_block().id, True) + b.write_vote(vote) + vote = b.vote(block3.id, b.get_last_voted_block().id, True) + b.write_vote(vote) + + with pytest.raises(CriticalDoubleSpend): b.get_spent(tx.id, 0) @pytest.mark.genesis def test_get_block_status_for_tx_with_double_inclusion(self, b, monkeypatch): - from bigchaindb.backend.exceptions import BigchainDBCritical + from bigchaindb.backend.exceptions import CriticalDoubleInclusion from bigchaindb.models import Transaction tx = Transaction.create([b.me], [([b.me], 1)]) @@ -148,7 +183,7 @@ class TestBigchainApi(object): vote = b.vote(block2.id, b.get_last_voted_block().id, True) b.write_vote(vote) - with pytest.raises(BigchainDBCritical): + with pytest.raises(CriticalDoubleInclusion): b.get_blocks_status_containing_tx(tx.id) @pytest.mark.genesis From 352627b83ab6549e5951ab6934440b59fe9721e4 Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Mon, 6 Mar 2017 12:12:04 +0100 Subject: [PATCH 04/10] add test that asset id is a string --- tests/db/test_bigchain_api.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index f56c9e5a..d2cc82eb 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -1313,3 +1313,10 @@ def test_is_new_transaction(b, genesis_block): # Tx is new because it's only found in an invalid block assert b.is_new_transaction(tx.id) assert b.is_new_transaction(tx.id, exclude_block_id=block.id) + + +def test_validate_asset_id_string(signed_transfer_tx): + from bigchaindb.common.exceptions import ValidationError + signed_transfer_tx.asset['id'] = 1 + with pytest.raises(ValidationError): + signed_transfer_tx.validate(None) From e5dd5c665ba707fc40e9369889f92cff59f8761b Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Tue, 7 Mar 2017 13:15:31 +0100 Subject: [PATCH 05/10] address vrde's comments, reshuffle some exceptions around --- bigchaindb/backend/exceptions.py | 8 -------- bigchaindb/common/exceptions.py | 4 ++-- bigchaindb/core.py | 6 +++--- bigchaindb/exceptions.py | 8 ++++++++ bigchaindb/models.py | 6 +++--- tests/db/test_bigchain_api.py | 6 +++--- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/bigchaindb/backend/exceptions.py b/bigchaindb/backend/exceptions.py index a5eff242..017e19e4 100644 --- a/bigchaindb/backend/exceptions.py +++ b/bigchaindb/backend/exceptions.py @@ -15,11 +15,3 @@ class OperationError(BackendError): class DuplicateKeyError(OperationError): """Exception raised when an insert fails because the key is not unique""" - - -class CriticalDoubleSpend(BigchainDBError): - """Data integrity error that requires attention""" - - -class CriticalDoubleInclusion(BigchainDBError): - """Data integrity error that requires attention""" diff --git a/bigchaindb/common/exceptions.py b/bigchaindb/common/exceptions.py index 76513010..5cffda7c 100644 --- a/bigchaindb/common/exceptions.py +++ b/bigchaindb/common/exceptions.py @@ -92,8 +92,8 @@ class AmountError(ValidationError): """Raised when there is a problem with a transaction's output amounts""" -class TransactionDoesNotExist(ValidationError): - """Raised if the transaction is not in the database""" +class InputDoesNotExist(ValidationError): + """Raised if a transaction input does not exist""" class TransactionOwnerError(ValidationError): diff --git a/bigchaindb/core.py b/bigchaindb/core.py index c0da6177..283969e3 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -4,6 +4,7 @@ import collections from time import time from itertools import compress +from bigchaindb import exceptions as core_exceptions from bigchaindb.common import crypto, exceptions from bigchaindb.common.utils import gen_timestamp, serialize from bigchaindb.common.transaction import TransactionLink @@ -11,7 +12,6 @@ from bigchaindb.common.transaction import TransactionLink import bigchaindb from bigchaindb import backend, config_utils, utils -from bigchaindb.backend import exceptions as backend_exceptions from bigchaindb.consensus import BaseConsensusRules from bigchaindb.models import Block, Transaction @@ -308,7 +308,7 @@ class Bigchain(object): if list(validity.values()).count(Bigchain.BLOCK_VALID) > 1: block_ids = str([block for block in validity if validity[block] == Bigchain.BLOCK_VALID]) - raise backend_exceptions.CriticalDoubleInclusion( + raise core_exceptions.CriticalDoubleInclusion( 'Transaction {tx} is present in ' 'multiple valid blocks: {block_ids}' .format(tx=txid, block_ids=block_ids)) @@ -361,7 +361,7 @@ class Bigchain(object): if self.get_transaction(transaction['id']): num_valid_transactions += 1 if num_valid_transactions > 1: - raise backend_exceptions.CriticalDoubleSpend( + raise core_exceptions.CriticalDoubleSpend( '`{}` was spent more than once. There is a problem' ' with the chain'.format(txid)) diff --git a/bigchaindb/exceptions.py b/bigchaindb/exceptions.py index d8a4cd73..336ce231 100644 --- a/bigchaindb/exceptions.py +++ b/bigchaindb/exceptions.py @@ -1,2 +1,10 @@ class BigchainDBError(Exception): """Base class for BigchainDB exceptions.""" + + +class CriticalDoubleSpend(BigchainDBError): + """Data integrity error that requires attention""" + + +class CriticalDoubleInclusion(BigchainDBError): + """Data integrity error that requires attention""" diff --git a/bigchaindb/models.py b/bigchaindb/models.py index fd71f98d..7c390967 100644 --- a/bigchaindb/models.py +++ b/bigchaindb/models.py @@ -1,6 +1,6 @@ from bigchaindb.common.crypto import hash_data, PublicKey, PrivateKey from bigchaindb.common.exceptions import (InvalidHash, InvalidSignature, - DoubleSpend, TransactionDoesNotExist, + DoubleSpend, InputDoesNotExist, TransactionNotInValidBlock, AssetIdMismatch, AmountError, SybilError, ValidationError) @@ -60,8 +60,8 @@ class Transaction(Transaction): get_transaction(input_txid, include_status=True) if input_tx is None: - raise TransactionDoesNotExist("input `{}` doesn't exist" - .format(input_txid)) + raise InputDoesNotExist("input `{}` doesn't exist" + .format(input_txid)) if status != bigchain.TX_VALID: raise TransactionNotInValidBlock( diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index d2cc82eb..b1f94b73 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -93,7 +93,7 @@ class TestBigchainApi(object): @pytest.mark.genesis def test_get_spent_with_double_inclusion_detected(self, b, monkeypatch): - from bigchaindb.backend.exceptions import CriticalDoubleInclusion + from bigchaindb.exceptions import CriticalDoubleInclusion from bigchaindb.models import Transaction tx = Transaction.create([b.me], [([b.me], 1)]) @@ -128,7 +128,7 @@ class TestBigchainApi(object): @pytest.mark.genesis def test_get_spent_with_double_spend_detected(self, b, monkeypatch): - from bigchaindb.backend.exceptions import CriticalDoubleSpend + from bigchaindb.exceptions import CriticalDoubleSpend from bigchaindb.models import Transaction tx = Transaction.create([b.me], [([b.me], 1)]) @@ -163,7 +163,7 @@ class TestBigchainApi(object): @pytest.mark.genesis def test_get_block_status_for_tx_with_double_inclusion(self, b, monkeypatch): - from bigchaindb.backend.exceptions import CriticalDoubleInclusion + from bigchaindb.exceptions import CriticalDoubleInclusion from bigchaindb.models import Transaction tx = Transaction.create([b.me], [([b.me], 1)]) From 1db8d59a880df7170c1e4cc3e889c22cad5072b8 Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Tue, 7 Mar 2017 13:24:20 +0100 Subject: [PATCH 06/10] use comma for arguments in logging calls instead of format operator --- bigchaindb/pipelines/block.py | 2 +- bigchaindb/pipelines/vote.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bigchaindb/pipelines/block.py b/bigchaindb/pipelines/block.py index b1d6cdee..fd503867 100644 --- a/bigchaindb/pipelines/block.py +++ b/bigchaindb/pipelines/block.py @@ -76,7 +76,7 @@ class BlockPipeline: tx.validate(self.bigchain) return tx except ValidationError as e: - logger.warning('Invalid tx: %s' % e) + logger.warning('Invalid tx: %s', e) self.bigchain.delete_transaction(tx.id) return None diff --git a/bigchaindb/pipelines/vote.py b/bigchaindb/pipelines/vote.py index 0431e20b..e4273470 100644 --- a/bigchaindb/pipelines/vote.py +++ b/bigchaindb/pipelines/vote.py @@ -109,7 +109,7 @@ class Vote: tx.validate(self.bigchain) valid = True except exceptions.ValidationError as e: - logger.warning('Invalid tx: %s' % e) + logger.warning('Invalid tx: %s', e) valid = False return valid, block_id, num_tx From a3fccbc599c47457abd11f954bec9a0384f4e21b Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Wed, 8 Mar 2017 13:01:52 +0100 Subject: [PATCH 07/10] change TransactionDoesNotExist to InputDoesNotExist in tests --- tests/db/test_bigchain_api.py | 8 ++++---- tests/web/test_transactions.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index b1f94b73..c39a104f 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -567,7 +567,7 @@ class TestBigchainApi(object): @pytest.mark.usefixtures('inputs') def test_non_create_input_not_found(self, b, user_pk): from cryptoconditions import Ed25519Fulfillment - from bigchaindb.common.exceptions import TransactionDoesNotExist + from bigchaindb.common.exceptions import InputDoesNotExist from bigchaindb.common.transaction import Input, TransactionLink from bigchaindb.models import Transaction from bigchaindb import Bigchain @@ -579,7 +579,7 @@ class TestBigchainApi(object): tx = Transaction.transfer([input], [([user_pk], 1)], asset_id='mock_asset_link') - with pytest.raises(TransactionDoesNotExist): + with pytest.raises(InputDoesNotExist): tx.validate(Bigchain()) def test_count_backlog(self, b, user_pk): @@ -615,11 +615,11 @@ class TestTransactionValidation(object): assert excinfo.value.args[0] == 'Only `CREATE` transactions can have null inputs' def test_non_create_input_not_found(self, b, user_pk, signed_transfer_tx): - from bigchaindb.common.exceptions import TransactionDoesNotExist + from bigchaindb.common.exceptions import InputDoesNotExist from bigchaindb.common.transaction import TransactionLink signed_transfer_tx.inputs[0].fulfills = TransactionLink('c', 0) - with pytest.raises(TransactionDoesNotExist): + with pytest.raises(InputDoesNotExist): b.validate_transaction(signed_transfer_tx) @pytest.mark.usefixtures('inputs') diff --git a/tests/web/test_transactions.py b/tests/web/test_transactions.py index cf4105e9..5533dbd0 100644 --- a/tests/web/test_transactions.py +++ b/tests/web/test_transactions.py @@ -113,7 +113,7 @@ def test_post_create_transaction_with_invalid_schema(client, caplog): ('InvalidHash', 'Do not smoke that!'), ('InvalidSignature', 'Falsche Unterschrift!'), ('ValidationError', 'Create and transfer!'), - ('TransactionDoesNotExist', 'Hallucinations?'), + ('InputDoesNotExist', 'Hallucinations?'), ('TransactionOwnerError', 'Not yours!'), ('TransactionNotInValidBlock', 'Wait, maybe?'), ('ValidationError', '?'), From 7dbd374838e2137c8db9d82143680f91b688e965 Mon Sep 17 00:00:00 2001 From: Krish Date: Thu, 9 Mar 2017 16:53:00 +0100 Subject: [PATCH 08/10] Running a single node on k8s (#1269) * Single node as a StatefulSet in k8s - uses bigchaindb/bigchaindb:0.9.1 * Updating README * rdb, mdb as stateful services * [WIP] bdb as a statefulset * [WIP] bdb w/ rdb and bdb w/ mdb backends - does not work as of now * Split mdb & bdb into separate pods + enhancements * discovery of the mongodb service by the bdb pod by using dns name. * using separate storage classes to map 2 different volumes exposed by the mongo docker container; one for /data/db (dbPath) and the other for /data/configdb (configDB). * using the `persistentVolumeReclaimPolicy: Retain` in k8s pvc. However, this seems to be unsupported in Azure and the disks still show a reclaim policy of `delete`. * mongodb container runs the `mongod` process as user `mongodb` and group `mongodb. The corresponding `uid` and `gid` for the `mongod` process is 999 and 999 respectively. When the constinaer runs on a host with a mounted disk, the writes fail, when there is no user with uid 999. To avoid this, I use the docker provided feature of --cap-add=FOWNER in k8s. This bypasses the uid and gid permission checks during writes and allows writes. Ref: https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities * Delete redundant k8s files, add cluster deletion steps. * Single node as a StatefulSet in k8s - uses bigchaindb/bigchaindb:0.9.1 * Updating README * rdb, mdb as stateful services * [WIP] bdb as a statefulset * [WIP] bdb w/ rdb and bdb w/ mdb backends - does not work as of now * Split mdb & bdb into separate pods + enhancements * discovery of the mongodb service by the bdb pod by using dns name. * using separate storage classes to map 2 different volumes exposed by the mongo docker container; one for /data/db (dbPath) and the other for /data/configdb (configDB). * using the `persistentVolumeReclaimPolicy: Retain` in k8s pvc. However, this seems to be unsupported in Azure and the disks still show a reclaim policy of `delete`. * mongodb container runs the `mongod` process as user `mongodb` and group `mongodb. The corresponding `uid` and `gid` for the `mongod` process is 999 and 999 respectively. When the constinaer runs on a host with a mounted disk, the writes fail, when there is no user with uid 999. To avoid this, I use the docker provided feature of --cap-add=FOWNER in k8s. This bypasses the uid and gid permission checks during writes and allows writes. Ref: https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities * Delete redundant k8s files, add cluster deletion steps. * Documentation: running a single node with distinct mongodb and bigchaindb pods on k8s * Updates as per @ttmc's comments --- .../node-on-kubernetes.rst | 238 ++++++++++++------ .../template-kubernetes-azure.rst | 51 +++- k8s/bigchaindb/bigchaindb-dep.yaml | 83 ++++++ k8s/deprecated.to.del/bdb-mdb-dep.yaml | 89 +++++++ k8s/deprecated.to.del/bdb-rdb-dep.yaml | 87 +++++++ k8s/{ => deprecated.to.del}/node-mdb-ss.yaml | 4 +- k8s/{ => deprecated.to.del}/node-rdb-ss.yaml | 0 k8s/deprecated.to.del/node-ss.yaml | 89 +++++++ k8s/deprecated.to.del/rethinkdb-ss.yaml | 75 ++++++ k8s/mongodb/mongo-data-configdb-pvc.yaml | 18 ++ k8s/mongodb/mongo-data-configdb-sc.yaml | 12 + k8s/mongodb/mongo-data-db-pvc.yaml | 18 ++ k8s/mongodb/mongo-data-db-sc.yaml | 12 + k8s/mongodb/mongo-ss.yaml | 76 ++++++ k8s/toolbox/Dockerfile | 12 + k8s/toolbox/README.md | 12 + 16 files changed, 793 insertions(+), 83 deletions(-) create mode 100644 k8s/bigchaindb/bigchaindb-dep.yaml create mode 100644 k8s/deprecated.to.del/bdb-mdb-dep.yaml create mode 100644 k8s/deprecated.to.del/bdb-rdb-dep.yaml rename k8s/{ => deprecated.to.del}/node-mdb-ss.yaml (97%) rename k8s/{ => deprecated.to.del}/node-rdb-ss.yaml (100%) create mode 100644 k8s/deprecated.to.del/node-ss.yaml create mode 100644 k8s/deprecated.to.del/rethinkdb-ss.yaml create mode 100644 k8s/mongodb/mongo-data-configdb-pvc.yaml create mode 100644 k8s/mongodb/mongo-data-configdb-sc.yaml create mode 100644 k8s/mongodb/mongo-data-db-pvc.yaml create mode 100644 k8s/mongodb/mongo-data-db-sc.yaml create mode 100644 k8s/mongodb/mongo-ss.yaml create mode 100644 k8s/toolbox/Dockerfile create mode 100644 k8s/toolbox/README.md diff --git a/docs/server/source/cloud-deployment-templates/node-on-kubernetes.rst b/docs/server/source/cloud-deployment-templates/node-on-kubernetes.rst index 1a8e5deb..e1ed43e7 100644 --- a/docs/server/source/cloud-deployment-templates/node-on-kubernetes.rst +++ b/docs/server/source/cloud-deployment-templates/node-on-kubernetes.rst @@ -32,18 +32,34 @@ then you can get the ``~/.kube/config`` file using: --name -Step 3: Create a StorageClass ------------------------------ +Step 3: Create Storage Classes +------------------------------ MongoDB needs somewhere to store its data persistently, outside the container where MongoDB is running. + +The official MongoDB Docker container exports two volume mounts with correct +permissions from inside the container: + + +* The directory where the mongod instance stores its data - ``/data/db``, + described at `storage.dbpath `_. + +* The directory where mongodb instance stores the metadata for a sharded + cluster - ``/data/configdb/``, described at + `sharding.configDB `_. + + Explaining how Kubernetes handles persistent volumes, and the associated terminology, is beyond the scope of this documentation; see `the Kubernetes docs about persistent volumes `_. -The first thing to do is create a Kubernetes StorageClass. +The first thing to do is create the Kubernetes storage classes. +We will accordingly create two storage classes and persistent volume claims in +Kubernetes. + **Azure.** First, you need an Azure storage account. If you deployed your Kubernetes cluster on Azure @@ -67,25 +83,26 @@ the PersistentVolumeClaim would get stuck in a "Pending" state. For future reference, the command to create a storage account is `az storage account create `_. -Create a Kubernetes Storage Class named ``slow`` -by writing a file named ``azureStorageClass.yml`` containing: -.. code:: yaml - - kind: StorageClass - apiVersion: storage.k8s.io/v1beta1 - metadata: - name: slow - provisioner: kubernetes.io/azure-disk - parameters: - skuName: Standard_LRS - location: - -and then: +Get the files ``mongo-data-db-sc.yaml`` and ``mongo-data-configdb-sc.yaml`` +from GitHub using: .. code:: bash - $ kubectl apply -f azureStorageClass.yml + $ wget https://raw.githubusercontent.com/bigchaindb/bigchaindb/master/k8s/mongodb/mongo-data-db-sc.yaml + $ wget https://raw.githubusercontent.com/bigchaindb/bigchaindb/master/k8s/mongodb/mongo-data-configdb-sc.yaml + +You may want to update the ``parameters.location`` field in both the files to +specify the location you are using in Azure. + + +Create the required StorageClass using + +.. code:: bash + + $ kubectl apply -f mongo-data-db-sc.yaml + $ kubectl apply -f mongo-data-configdb-sc.yaml + You can check if it worked using ``kubectl get storageclasses``. @@ -99,27 +116,19 @@ Kubernetes just looks for a storageAccount with the specified skuName and location. -Step 4: Create a PersistentVolumeClaim --------------------------------------- +Step 4: Create Persistent Volume Claims +--------------------------------------- -Next, you'll create a PersistentVolumeClaim named ``mongoclaim``. -Create a file named ``mongoclaim.yml`` -with the following contents: +Next, we'll create two PersistentVolumeClaim objects ``mongo-db-claim`` and +``mongo-configdb-claim``. -.. code:: yaml +Get the files ``mongo-data-db-sc.yaml`` and ``mongo-data-configdb-sc.yaml`` +from GitHub using: - kind: PersistentVolumeClaim - apiVersion: v1 - metadata: - name: mongoclaim - annotations: - volume.beta.kubernetes.io/storage-class: slow - spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 20Gi +.. code:: bash + + $ wget https://raw.githubusercontent.com/bigchaindb/bigchaindb/master/k8s/mongodb/mongo-data-db-pvc.yaml + $ wget https://raw.githubusercontent.com/bigchaindb/bigchaindb/master/k8s/mongodb/mongo-data-configdb-pvc.yaml Note how there's no explicit mention of Azure, AWS or whatever. ``ReadWriteOnce`` (RWO) means the volume can be mounted as @@ -128,67 +137,144 @@ read-write by a single Kubernetes node. by AzureDisk.) ``storage: 20Gi`` means the volume has a size of 20 `gibibytes `_. -(You can change that if you like.) -Create ``mongoclaim`` in your Kubernetes cluster: +You may want to update the ``spec.resources.requests.storage`` field in both +the files to specify a different disk size. + +Create the required PersistentVolumeClaim using: .. code:: bash - $ kubectl apply -f mongoclaim.yml + $ kubectl apply -f mongo-data-db-pvc.yaml + $ kubectl apply -f mongo-data-configdb-pvc.yaml -You can check its status using: -.. code:: bash +You can check its status using: ``kubectl get pvc -w`` - $ kubectl get pvc - -Initially, the status of ``mongoclaim`` might be "Pending" +Initially, the status of persistent volume claims might be "Pending" but it should become "Bound" fairly quickly. -.. code:: bash - $ kubectl describe pvc - Name: mongoclaim - Namespace: default - StorageClass: slow - Status: Bound - Volume: pvc-ebed81f1-fdca-11e6-abf0-000d3a27ab21 - Labels: - Capacity: 20Gi - Access Modes: RWO - No events. +Now we are ready to run MongoDB and BigchainDB on our Kubernetes cluster. +Step 5: Run MongoDB as a StatefulSet +------------------------------------ -Step 5: Deploy MongoDB & BigchainDB ------------------------------------ - -Now you can deploy MongoDB and BigchainDB to your Kubernetes cluster. -Currently, the way we do that is we create a StatefulSet with two -containers: BigchainDB and MongoDB. (In the future, we'll put them -in separate pods, and we'll ensure those pods are in different nodes.) -We expose BigchainDB's port 9984 (the HTTP API port) -and MongoDB's port 27017 using a Kubernetes Service. - -Get the file ``node-mdb-ss.yaml`` from GitHub using: +Get the file ``mongo-ss.yaml`` from GitHub using: .. code:: bash - $ wget https://raw.githubusercontent.com/bigchaindb/bigchaindb/master/k8s/node-mdb-ss.yaml + $ wget https://raw.githubusercontent.com/bigchaindb/bigchaindb/master/k8s/mongodb/mongo-ss.yaml -Take a look inside that file to see how it defines the Service -and the StatefulSet. -Note how the MongoDB container uses the ``mongoclaim`` PersistentVolumeClaim -for its ``/data`` diretory (mount path). -Create the StatefulSet and Service in your cluster using: +Note how the MongoDB container uses the ``mongo-db-claim`` and the +``mongo-configdb-claim`` PersistentVolumeClaims for its ``/data/db`` and +``/data/configdb`` diretories (mount path). Note also that we use the pod's +``securityContext.capabilities.add`` specification to add the ``FOWNER`` +capability to the container. + +That is because MongoDB container has the user ``mongodb``, with uid ``999`` +and group ``mongodb``, with gid ``999``. +When this container runs on a host with a mounted disk, the writes fail when +there is no user with uid ``999``. + +To avoid this, we use the Docker feature of ``--cap-add=FOWNER``. +This bypasses the uid and gid permission checks during writes and allows data +to be persisted to disk. +Refer to the +`Docker doc `_ +for details. + +As we gain more experience running MongoDB in testing and production, we will +tweak the ``resources.limits.cpu`` and ``resources.limits.memory``. +We will also stop exposing port ``27017`` globally and/or allow only certain +hosts to connect to the MongoDB instance in the future. + +Create the required StatefulSet using: .. code:: bash - $ kubectl apply -f node-mdb-ss.yaml + $ kubectl apply -f mongo-ss.yaml -You can check that they're working using: +You can check its status using the commands ``kubectl get statefulsets -w`` +and ``kubectl get svc -w`` + + +Step 6: Run BigchainDB as a Deployment +-------------------------------------- + +Get the file ``bigchaindb-dep.yaml`` from GitHub using: .. code:: bash - $ kubectl get services - $ kubectl get statefulsets + $ wget https://raw.githubusercontent.com/bigchaindb/bigchaindb/master/k8s/bigchaindb/bigchaindb-dep.yaml + +Note that we set the ``BIGCHAINDB_DATABASE_HOST`` to ``mdb`` which is the name +of the MongoDB service defined earlier. + +We also hardcode the ``BIGCHAINDB_KEYPAIR_PUBLIC``, +``BIGCHAINDB_KEYPAIR_PRIVATE`` and ``BIGCHAINDB_KEYRING`` for now. + +As we gain more experience running BigchainDB in testing and production, we +will tweak the ``resources.limits`` values for CPU and memory, and as richer +monitoring and probing becomes available in BigchainDB, we will tweak the +``livenessProbe`` and ``readinessProbe`` parameters. + +We also plan to specify scheduling policies for the BigchainDB deployment so +that we ensure that BigchainDB and MongoDB are running in separate nodes, and +build security around the globally exposed port ``9984``. + +Create the required Deployment using: + +.. code:: bash + + $ kubectl apply -f bigchaindb-dep.yaml + +You can check its status using the command ``kubectl get deploy -w`` + + +Step 7: Verify the BigchainDB Node Setup +---------------------------------------- + +Step 7.1: Testing Externally +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Try to access the ``:9984`` on your +browser. You must receive a json output that shows the BigchainDB server +version among other things. + +Try to access the ``:27017`` on your +browser. You must receive a message from MongoDB stating that it doesn't allow +HTTP connections to the port anymore. + + +Step 7.2: Testing Internally +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Run a container that provides utilities like ``nslookup``, ``curl`` and ``dig`` +on the cluster and query the internal DNS and IP endpoints. + +.. code:: bash + + $ kubectl run -it toolbox -- image --restart=Never --rm + +It will drop you to the shell prompt. +Now we can query for the ``mdb`` and ``bdb`` service details. + +.. code:: bash + + $ nslookup mdb + $ dig +noall +answer _mdb_port._tcp.mdb.default.svc.cluster.local SRV + $ curl -X GET http://mdb:27017 + $ curl -X GET http://bdb:9984 + +There is a generic image based on alpine:3.5 with the required utilities +hosted at Docker Hub under ``bigchaindb/toolbox``. +The corresponding Dockerfile is `here +`_. +You can use it as below to get started immediately: + +.. code:: bash + + $ kubectl run -it toolbox --image bigchaindb/toolbox --restart=Never --rm + diff --git a/docs/server/source/cloud-deployment-templates/template-kubernetes-azure.rst b/docs/server/source/cloud-deployment-templates/template-kubernetes-azure.rst index 0fe8c378..d5c9a20d 100644 --- a/docs/server/source/cloud-deployment-templates/template-kubernetes-azure.rst +++ b/docs/server/source/cloud-deployment-templates/template-kubernetes-azure.rst @@ -94,7 +94,9 @@ Finally, you can deploy an ACS using something like: $ az acs create --name \ --resource-group \ + --master-count 3 \ --agent-count 3 \ + --admin-username ubuntu \ --agent-vm-size Standard_D2_v2 \ --dns-prefix \ --ssh-key-value ~/.ssh/.pub \ @@ -113,9 +115,6 @@ go to **Resource groups** (with the blue cube icon) and click on the one you created to see all the resources in it. -Next, you can :doc:`run a BigchainDB node on your new -Kubernetes cluster `. - Optional: SSH to Your New Kubernetes Cluster Nodes -------------------------------------------------- @@ -125,11 +124,10 @@ You can SSH to one of the just-deployed Kubernetes "master" nodes .. code:: bash - $ ssh -i ~/.ssh/.pub azureuser@ + $ ssh -i ~/.ssh/.pub ubuntu@ where you can get the IP address or hostname of a master node from the Azure Portal. -Note how the default username is ``azureuser``. The "agent" nodes don't get public IP addresses or hostnames, so you can't SSH to them *directly*, @@ -141,5 +139,48 @@ the master (a bad idea), or use something like `SSH agent forwarding `_ (better). + +Optional: Set up SSH Forwarding +------------------------------- + +On the system you will use to access the cluster, run + +.. code:: bash + + $ echo -e "Host \n ForwardAgent yes" >> ~/.ssh/config + +To verify whether SSH Forwarding works properly, login to the one of the master +machines and run + +.. code:: bash + + $ echo "$SSH_AUTH_SOCK" + +If you get an empty response, SSH forwarding hasn't been set up correctly. +If you get a non-empty response, SSH forwarding should work fine and you can +try to login to one of the k8s nodes from the master. + + +Optional: Delete the Kubernetes Cluster +--------------------------------------- + +.. code:: bash + + $ az acs delete \ + --name \ + --resource-group + + +Optional: Delete the Resource Group +----------------------------------- + +CAUTION: You might end up deleting resources other than the ACS cluster. + +.. code:: bash + + $ az group delete \ + --name + + Next, you can :doc:`run a BigchainDB node on your new Kubernetes cluster `. diff --git a/k8s/bigchaindb/bigchaindb-dep.yaml b/k8s/bigchaindb/bigchaindb-dep.yaml new file mode 100644 index 00000000..7bf68f06 --- /dev/null +++ b/k8s/bigchaindb/bigchaindb-dep.yaml @@ -0,0 +1,83 @@ +############################################################### +# This config file runs bigchaindb:master as a k8s Deployment # +# and it connects to the mongodb backend on a separate pod # +############################################################### + +apiVersion: v1 +kind: Service +metadata: + name: bdb + namespace: default + labels: + name: bdb +spec: + selector: + app: bdb + ports: + - port: 9984 + targetPort: 9984 + name: bdb-port + type: LoadBalancer +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: bdb +spec: + replicas: 1 + template: + metadata: + labels: + app: bdb + spec: + terminationGracePeriodSeconds: 10 + containers: + - name: bigchaindb + image: bigchaindb/bigchaindb:master + args: + - start + env: + - name: BIGCHAINDB_DATABASE_HOST + value: mdb + - name: BIGCHAINDB_DATABASE_PORT + # TODO(Krish): remove hardcoded port + value: "27017" + - name: BIGCHAINDB_DATABASE_REPLICASET + value: bigchain-rs + - name: BIGCHAINDB_DATABASE_BACKEND + value: mongodb + - name: BIGCHAINDB_DATABASE_NAME + value: bigchain + - name: BIGCHAINDB_SERVER_BIND + value: 0.0.0.0:9984 + - name: BIGCHAINDB_KEYPAIR_PUBLIC + value: EEWUAhsk94ZUHhVw7qx9oZiXYDAWc9cRz93eMrsTG4kZ + - name: BIGCHAINDB_KEYPAIR_PRIVATE + value: 3CjmRhu718gT1Wkba3LfdqX5pfYuBdaMPLd7ENUga5dm + - name: BIGCHAINDB_BACKLOG_REASSIGN_DELAY + value: "120" + - name: BIGCHAINDB_KEYRING + value: "" + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9984 + hostPort: 9984 + name: bdb-port + protocol: TCP + resources: + limits: + cpu: 200m + memory: 768Mi + livenessProbe: + httpGet: + path: / + port: 9984 + initialDelaySeconds: 15 + timeoutSeconds: 10 + readinessProbe: + httpGet: + path: / + port: 9984 + initialDelaySeconds: 15 + timeoutSeconds: 10 + restartPolicy: Always diff --git a/k8s/deprecated.to.del/bdb-mdb-dep.yaml b/k8s/deprecated.to.del/bdb-mdb-dep.yaml new file mode 100644 index 00000000..c985b285 --- /dev/null +++ b/k8s/deprecated.to.del/bdb-mdb-dep.yaml @@ -0,0 +1,89 @@ +############################################################### +# This config file runs bigchaindb:latest and connects to the # +# mongodb backend as a service # +############################################################### + +apiVersion: v1 +kind: Service +metadata: + name: bdb-mdb-service + namespace: default + labels: + name: bdb-mdb-service +spec: + selector: + app: bdb-mdb + ports: + - port: 9984 + targetPort: 9984 + name: bdb-api + type: LoadBalancer +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: bdb-mdb +spec: + replicas: 1 + template: + metadata: + labels: + app: bdb-mdb + spec: + terminationGracePeriodSeconds: 10 + containers: + - name: bdb-mdb + image: bigchaindb/bigchaindb:latest + args: + - start + env: + - name: BIGCHAINDB_DATABASE_HOST + value: mdb-service + - name: BIGCHAINDB_DATABASE_PORT + value: "27017" + - name: BIGCHAINDB_DATABASE_REPLICASET + value: bigchain-rs + - name: BIGCHIANDB_DATABASE_BACKEND + value: mongodb + - name: BIGCHAINDB_DATABASE_NAME + value: bigchain + - name: BIGCHAINDB_SERVER_BIND + value: 0.0.0.0:9984 + - name: BIGCHAINDB_KEYPAIR_PUBLIC + value: EEWUAhsk94ZUHhVw7qx9oZiXYDAWc9cRz93eMrsTG4kZ + - name: BIGCHAINDB_KEYPAIR_PRIVATE + value: 3CjmRhu718gT1Wkba3LfdqX5pfYuBdaMPLd7ENUga5dm + - name: BIGCHAINDB_BACKLOG_REASSIGN_DELAY + value: "120" + - name: BIGCHAINDB_KEYRING + value: "" + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9984 + hostPort: 9984 + name: bdb-port + protocol: TCP + volumeMounts: + - name: bigchaindb-data + mountPath: /data + resources: + limits: + cpu: 200m + memory: 768Mi + livenessProbe: + httpGet: + path: / + port: 9984 + initialDelaySeconds: 15 + timeoutSeconds: 10 + readinessProbe: + httpGet: + path: / + port: 9984 + initialDelaySeconds: 15 + timeoutSeconds: 10 + restartPolicy: Always + volumes: + - name: bigchaindb-data + hostPath: + path: /disk/bigchaindb-data diff --git a/k8s/deprecated.to.del/bdb-rdb-dep.yaml b/k8s/deprecated.to.del/bdb-rdb-dep.yaml new file mode 100644 index 00000000..06daca43 --- /dev/null +++ b/k8s/deprecated.to.del/bdb-rdb-dep.yaml @@ -0,0 +1,87 @@ +############################################################### +# This config file runs bigchaindb:latest and connects to the # +# rethinkdb backend as a service # +############################################################### + +apiVersion: v1 +kind: Service +metadata: + name: bdb-rdb-service + namespace: default + labels: + name: bdb-rdb-service +spec: + selector: + app: bdb-rdb + ports: + - port: 9984 + targetPort: 9984 + name: bdb-rdb-api + type: LoadBalancer +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: bdb-rdb +spec: + replicas: 1 + template: + metadata: + labels: + app: bdb-rdb + spec: + terminationGracePeriodSeconds: 10 + containers: + - name: bdb-rdb + image: bigchaindb/bigchaindb:latest + args: + - start + env: + - name: BIGCHAINDB_DATABASE_HOST + value: rdb-service + - name: BIGCHAINDB_DATABASE_PORT + value: "28015" + - name: BIGCHIANDB_DATABASE_BACKEND + value: rethinkdb + - name: BIGCHAINDB_DATABASE_NAME + value: bigchain + - name: BIGCHAINDB_SERVER_BIND + value: 0.0.0.0:9984 + - name: BIGCHAINDB_KEYPAIR_PUBLIC + value: EEWUAhsk94ZUHhVw7qx9oZiXYDAWc9cRz93eMrsTG4kZ + - name: BIGCHAINDB_KEYPAIR_PRIVATE + value: 3CjmRhu718gT1Wkba3LfdqX5pfYuBdaMPLd7ENUga5dm + - name: BIGCHAINDB_BACKLOG_REASSIGN_DELAY + value: "120" + - name: BIGCHAINDB_KEYRING + value: "" + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9984 + hostPort: 9984 + name: bdb-port + protocol: TCP + volumeMounts: + - name: bigchaindb-data + mountPath: /data + resources: + limits: + cpu: 200m + memory: 768Mi + livenessProbe: + httpGet: + path: / + port: 9984 + initialDelaySeconds: 15 + timeoutSeconds: 10 + readinessProbe: + httpGet: + path: / + port: 9984 + initialDelaySeconds: 15 + timeoutSeconds: 10 + restartPolicy: Always + volumes: + - name: bigchaindb-data + hostPath: + path: /disk/bigchaindb-data diff --git a/k8s/node-mdb-ss.yaml b/k8s/deprecated.to.del/node-mdb-ss.yaml similarity index 97% rename from k8s/node-mdb-ss.yaml rename to k8s/deprecated.to.del/node-mdb-ss.yaml index 304750c2..3c126d2d 100644 --- a/k8s/node-mdb-ss.yaml +++ b/k8s/deprecated.to.del/node-mdb-ss.yaml @@ -42,8 +42,8 @@ spec: spec: terminationGracePeriodSeconds: 10 containers: - - name: bdb-server - image: bigchaindb/bigchaindb:latest + - name: bigchaindb + image: bigchaindb/bigchaindb:master args: - start env: diff --git a/k8s/node-rdb-ss.yaml b/k8s/deprecated.to.del/node-rdb-ss.yaml similarity index 100% rename from k8s/node-rdb-ss.yaml rename to k8s/deprecated.to.del/node-rdb-ss.yaml diff --git a/k8s/deprecated.to.del/node-ss.yaml b/k8s/deprecated.to.del/node-ss.yaml new file mode 100644 index 00000000..9580daf6 --- /dev/null +++ b/k8s/deprecated.to.del/node-ss.yaml @@ -0,0 +1,89 @@ +##################################################### +# This config file uses bdb v0.9.1 with bundled rdb # +##################################################### + +apiVersion: v1 +kind: Service +metadata: + name: bdb-service + namespace: default + labels: + name: bdb-service +spec: + selector: + app: bdb + ports: + - port: 9984 + targetPort: 9984 + name: bdb-http-api + - port: 8080 + targetPort: 8080 + name: bdb-rethinkdb-api + type: LoadBalancer +--- +apiVersion: apps/v1beta1 +kind: StatefulSet +metadata: + name: bdb + namespace: default +spec: + serviceName: bdb + replicas: 1 + template: + metadata: + name: bdb + labels: + app: bdb + annotations: + pod.beta.kubernetes.io/init-containers: '[ + { + "name": "bdb091-configure", + "image": "bigchaindb/bigchaindb:0.9.1", + "command": ["bigchaindb", "-y", "configure", "rethinkdb"], + "volumeMounts": [ + { + "name": "bigchaindb-data", + "mountPath": "/data" + } + ] + } + ]' + spec: + terminationGracePeriodSeconds: 10 + containers: + - name: bdb091-server + image: bigchaindb/bigchaindb:0.9.1 + args: + - -c + - /data/.bigchaindb + - start + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9984 + hostPort: 9984 + name: bdb-port + protocol: TCP + volumeMounts: + - name: bigchaindb-data + mountPath: /data + resources: + limits: + cpu: 200m + memory: 768Mi + livenessProbe: + httpGet: + path: / + port: 9984 + initialDelaySeconds: 15 + timeoutSeconds: 10 + readinessProbe: + httpGet: + path: / + port: 9984 + initialDelaySeconds: 15 + timeoutSeconds: 10 + restartPolicy: Always + volumes: + - name: bigchaindb-data + hostPath: + path: /disk/bigchaindb-data diff --git a/k8s/deprecated.to.del/rethinkdb-ss.yaml b/k8s/deprecated.to.del/rethinkdb-ss.yaml new file mode 100644 index 00000000..081a5f6c --- /dev/null +++ b/k8s/deprecated.to.del/rethinkdb-ss.yaml @@ -0,0 +1,75 @@ +#################################################### +# This config file runs rethinkdb:2.3 as a service # +#################################################### + +apiVersion: v1 +kind: Service +metadata: + name: rdb-service + namespace: default + labels: + name: rdb-service +spec: + selector: + app: rdb + ports: + - port: 8080 + targetPort: 8080 + name: rethinkdb-http-port + - port: 28015 + targetPort: 28015 + name: rethinkdb-driver-port + type: LoadBalancer +--- +apiVersion: apps/v1beta1 +kind: StatefulSet +metadata: + name: rdb + namespace: default +spec: + serviceName: rdb + replicas: 1 + template: + metadata: + name: rdb + labels: + app: rdb + spec: + terminationGracePeriodSeconds: 10 + containers: + - name: rethinkdb + image: rethinkdb:2.3 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8080 + hostPort: 8080 + name: rdb-http-port + protocol: TCP + - containerPort: 28015 + hostPort: 28015 + name: rdb-client-port + protocol: TCP + volumeMounts: + - name: rdb-data + mountPath: /data + resources: + limits: + cpu: 200m + memory: 768Mi + livenessProbe: + httpGet: + path: / + port: 8080 + initialDelaySeconds: 15 + timeoutSeconds: 10 + readinessProbe: + httpGet: + path: / + port: 8080 + initialDelaySeconds: 15 + timeoutSeconds: 10 + restartPolicy: Always + volumes: + - name: rdb-data + hostPath: + path: /disk/rdb-data diff --git a/k8s/mongodb/mongo-data-configdb-pvc.yaml b/k8s/mongodb/mongo-data-configdb-pvc.yaml new file mode 100644 index 00000000..7d3dc8a3 --- /dev/null +++ b/k8s/mongodb/mongo-data-configdb-pvc.yaml @@ -0,0 +1,18 @@ +########################################################## +# This YAML file desribes a k8s pvc for mongodb configDB # +########################################################## + +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: mongo-configdb-claim + annotations: + volume.beta.kubernetes.io/storage-class: slow-configdb +spec: + accessModes: + - ReadWriteOnce + # FIXME(Uncomment when ACS supports this!) + # persistentVolumeReclaimPolicy: Retain + resources: + requests: + storage: 20Gi diff --git a/k8s/mongodb/mongo-data-configdb-sc.yaml b/k8s/mongodb/mongo-data-configdb-sc.yaml new file mode 100644 index 00000000..b431db67 --- /dev/null +++ b/k8s/mongodb/mongo-data-configdb-sc.yaml @@ -0,0 +1,12 @@ +################################################################### +# This YAML file desribes a StorageClass for the mongodb configDB # +################################################################### + +kind: StorageClass +apiVersion: storage.k8s.io/v1beta1 +metadata: + name: slow-configdb +provisioner: kubernetes.io/azure-disk +parameters: + skuName: Standard_LRS + location: westeurope diff --git a/k8s/mongodb/mongo-data-db-pvc.yaml b/k8s/mongodb/mongo-data-db-pvc.yaml new file mode 100644 index 00000000..e9689346 --- /dev/null +++ b/k8s/mongodb/mongo-data-db-pvc.yaml @@ -0,0 +1,18 @@ +######################################################## +# This YAML file desribes a k8s pvc for mongodb dbPath # +######################################################## + +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: mongo-db-claim + annotations: + volume.beta.kubernetes.io/storage-class: slow-db +spec: + accessModes: + - ReadWriteOnce + # FIXME(Uncomment when ACS supports this!) + # persistentVolumeReclaimPolicy: Retain + resources: + requests: + storage: 20Gi diff --git a/k8s/mongodb/mongo-data-db-sc.yaml b/k8s/mongodb/mongo-data-db-sc.yaml new file mode 100644 index 00000000..f700223d --- /dev/null +++ b/k8s/mongodb/mongo-data-db-sc.yaml @@ -0,0 +1,12 @@ +################################################################# +# This YAML file desribes a StorageClass for the mongodb dbPath # +################################################################# + +kind: StorageClass +apiVersion: storage.k8s.io/v1beta1 +metadata: + name: slow-db +provisioner: kubernetes.io/azure-disk +parameters: + skuName: Standard_LRS + location: westeurope diff --git a/k8s/mongodb/mongo-ss.yaml b/k8s/mongodb/mongo-ss.yaml new file mode 100644 index 00000000..63c7d27d --- /dev/null +++ b/k8s/mongodb/mongo-ss.yaml @@ -0,0 +1,76 @@ +######################################################################## +# This YAML file desribes a StatefulSet with a service for running and # +# exposing a MongoDB service. # +# It depends on the configdb and db k8s pvc. # +######################################################################## + +apiVersion: v1 +kind: Service +metadata: + name: mdb + namespace: default + labels: + name: mdb +spec: + selector: + app: mdb + ports: + - port: 27017 + targetPort: 27017 + name: mdb-port + type: LoadBalancer +--- +apiVersion: apps/v1beta1 +kind: StatefulSet +metadata: + name: mdb + namespace: default +spec: + serviceName: mdb + replicas: 1 + template: + metadata: + name: mdb + labels: + app: mdb + spec: + terminationGracePeriodSeconds: 10 + containers: + - name: mongodb + image: mongo:3.4.1 + args: + - --replSet=bigchain-rs + securityContext: + capabilities: + add: + - FOWNER + imagePullPolicy: IfNotPresent + ports: + - containerPort: 27017 + hostPort: 27017 + name: mdb-port + protocol: TCP + volumeMounts: + - name: mdb-db + mountPath: /data/db + - name: mdb-configdb + mountPath: /data/configdb + resources: + limits: + cpu: 200m + memory: 768Mi + livenessProbe: + tcpSocket: + port: mdb-port + successThreshold: 1 + failureThreshold: 3 + periodSeconds: 15 + timeoutSeconds: 1 + restartPolicy: Always + volumes: + - name: mdb-db + persistentVolumeClaim: + claimName: mongo-db-claim + - name: mdb-configdb + persistentVolumeClaim: + claimName: mongo-configdb-claim diff --git a/k8s/toolbox/Dockerfile b/k8s/toolbox/Dockerfile new file mode 100644 index 00000000..6bcb1298 --- /dev/null +++ b/k8s/toolbox/Dockerfile @@ -0,0 +1,12 @@ +# Toolbox container for debugging +# Run as: +# docker run -it --rm --entrypoint sh krish7919/toolbox +# kubectl run -it toolbox --image krish7919/toolbox --restart=Never --rm + +FROM alpine:3.5 +MAINTAINER github.com/krish7919 +WORKDIR / + +RUN apk add --no-cache curl bind-tools + +ENTRYPOINT ["/bin/sh"] diff --git a/k8s/toolbox/README.md b/k8s/toolbox/README.md new file mode 100644 index 00000000..b9000ab1 --- /dev/null +++ b/k8s/toolbox/README.md @@ -0,0 +1,12 @@ +## Docker container with debugging tools + +* curl +* bind-utils - provides nslookup, dig + +## Build + +`docker build -t bigchaindb/toolbox .` + +## Push + +`docker push bigchaindb/toolbox` From 646859f1d68805979edecbff523f322b5a3d9f4d Mon Sep 17 00:00:00 2001 From: Troy McConaghy Date: Fri, 10 Mar 2017 11:47:58 +0100 Subject: [PATCH 09/10] revised docs re/ SSHing to nodes in a k8s cluster --- .../template-kubernetes-azure.rst | 53 ++++++++++++------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/docs/server/source/cloud-deployment-templates/template-kubernetes-azure.rst b/docs/server/source/cloud-deployment-templates/template-kubernetes-azure.rst index d5c9a20d..93cf1e08 100644 --- a/docs/server/source/cloud-deployment-templates/template-kubernetes-azure.rst +++ b/docs/server/source/cloud-deployment-templates/template-kubernetes-azure.rst @@ -127,38 +127,53 @@ You can SSH to one of the just-deployed Kubernetes "master" nodes $ ssh -i ~/.ssh/.pub ubuntu@ where you can get the IP address or hostname -of a master node from the Azure Portal. +of a master node from the Azure Portal. For example: -The "agent" nodes don't get public IP addresses or hostnames, +.. code:: bash + + $ ssh -i ~/.ssh/mykey123.pub ubuntu@mydnsprefix.westeurope.cloudapp.azure.com + +.. note:: + + All the master nodes should have the *same* IP address and hostname + (also called the Master FQDN). + +The "agent" nodes shouldn't get public IP addresses or hostnames, so you can't SSH to them *directly*, but you can first SSH to the master -and then SSH to an agent from there -(using the *private* IP address or hostname of the agent node). -To do that, you either need to copy your SSH key pair to -the master (a bad idea), -or use something like -`SSH agent forwarding `_ (better). - - -Optional: Set up SSH Forwarding -------------------------------- - -On the system you will use to access the cluster, run +and then SSH to an agent from there. +To do that, you could +copy your SSH key pair to the master (a bad idea), +or use SSH agent forwarding (better). +To do the latter, do the following on the machine you used +to SSH to the master: .. code:: bash $ echo -e "Host \n ForwardAgent yes" >> ~/.ssh/config -To verify whether SSH Forwarding works properly, login to the one of the master -machines and run +To verify that SSH agent forwarding works properly, +SSH to the one of the master nodes and do: .. code:: bash $ echo "$SSH_AUTH_SOCK" -If you get an empty response, SSH forwarding hasn't been set up correctly. -If you get a non-empty response, SSH forwarding should work fine and you can -try to login to one of the k8s nodes from the master. +If you get an empty response, +then SSH agent forwarding hasn't been set up correctly. +If you get a non-empty response, +then SSH agent forwarding should work fine +and you can SSH to one of the agent nodes (from a master) +using something like: + +.. code:: bash + + $ ssh ssh ubuntu@k8s-agent-4AC80E97-0 + +where ``k8s-agent-4AC80E97-0`` is the name +of a Kubernetes agent node in your Kubernetes cluster. +You will have to replace it by the name +of an agent node in your cluster. Optional: Delete the Kubernetes Cluster From 72ba9761d43f34b4286fe7895e744e716fe537d2 Mon Sep 17 00:00:00 2001 From: Sylvain Bellemare Date: Fri, 10 Mar 2017 14:42:27 +0100 Subject: [PATCH 10/10] Use parametrized host & port in test to support docker-based tests or different test envs --- tests/backend/mongodb/test_connection.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/backend/mongodb/test_connection.py b/tests/backend/mongodb/test_connection.py index 786b7d7b..6350a7c5 100644 --- a/tests/backend/mongodb/test_connection.py +++ b/tests/backend/mongodb/test_connection.py @@ -32,15 +32,15 @@ def mongodb_connection(): port=bigchaindb.config['database']['port']) -def test_get_connection_returns_the_correct_instance(): +def test_get_connection_returns_the_correct_instance(db_host, db_port): from bigchaindb.backend import connect from bigchaindb.backend.connection import Connection from bigchaindb.backend.mongodb.connection import MongoDBConnection config = { 'backend': 'mongodb', - 'host': 'localhost', - 'port': 27017, + 'host': db_host, + 'port': db_port, 'name': 'test', 'replicaset': 'bigchain-rs' }