diff --git a/docs/root/source/contributing/cross-project-policies/code-of-conduct.md b/docs/root/source/contributing/cross-project-policies/code-of-conduct.md index 5667f9d..729131e 100644 --- a/docs/root/source/contributing/cross-project-policies/code-of-conduct.md +++ b/docs/root/source/contributing/cross-project-policies/code-of-conduct.md @@ -42,7 +42,7 @@ This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Instances of abusive, harassing, or otherwise unacceptable behavior directed at yourself or another community member may be -reported by contacting a project maintainer at [contact@planetmint.com](mailto:contact@planetmint.com). All +reported by contacting a project maintainer at [mail@planetmint.io](mailto:contact@planetmint.io). All complaints will be reviewed and investigated and will result in a response that is appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an diff --git a/planetmint/backend/__init__.py b/planetmint/backend/__init__.py index 1468dc7..4f3e4fd 100644 --- a/planetmint/backend/__init__.py +++ b/planetmint/backend/__init__.py @@ -13,4 +13,4 @@ configuration or the ``PLANETMINT_DATABASE_BACKEND`` environment variable. # Include the backend interfaces from planetmint.backend import schema, query, convert # noqa -from planetmint.backend.connection import connect, Connection +from planetmint.backend.connection import Connection diff --git a/planetmint/backend/connection.py b/planetmint/backend/connection.py index 41f91af..494e86d 100644 --- a/planetmint/backend/connection.py +++ b/planetmint/backend/connection.py @@ -94,9 +94,8 @@ class Connection(metaclass=DBSingleton): from and implements this class. """ - def __init__(self, host=None, port=None, dbname=None, - connection_timeout=None, max_tries=None, - **kwargs): + def __init__(self, host: str =None, port: int = None, login: str = None, password: str = None, backend: str = None, + connection_timeout: int = None, max_tries: int = None, **kwargs): """Create a new :class:`~.Connection` instance. Args: host (str): the host to connect to. @@ -110,23 +109,102 @@ class Connection(metaclass=DBSingleton): **kwargs: arbitrary keyword arguments provided by the configuration's ``database`` settings """ - dbconf = Config().get()['database'] - self.host = host or dbconf['host'] - self.port = port or dbconf['port'] - self.dbname = dbname or dbconf['name'] - self.connection_timeout = connection_timeout if connection_timeout is not None \ - else dbconf['connection_timeout'] + self.connection_timeout = connection_timeout if connection_timeout is not None else Config().get()["database"] self.max_tries = max_tries if max_tries is not None else dbconf['max_tries'] self.max_tries_counter = range(self.max_tries) if self.max_tries != 0 else repeat(0) - self._conn = None + self.conn = None - @property - def conn(self): - if self._conn is None: - self.connect() - return self._conn + try: + backend = backend + if not backend and kwargs and kwargs.get("backend"): + backend = kwargs["backend"] + + if backend and backend != Config().get()["database"]["backend"]: + Config().init_config(backend) + else: + backend = Config().get()["database"]["backend"] + except KeyError: + logger.info("Backend {} not supported".format(backend)) + raise ConfigurationError + + if(self.conn is None): + try: + self.connect(host=host, port=port, login=login, password=password, backend=backend, kwargs=kwargs) + except tarantool.error.NetworkError as network_err: + print(f"Host {host}:{port} can't be reached.\n{network_err}") + raise network_err + + def connect(self, host: str = None, port: int = None, login: str = None, password: str = None, backend: str = None, **kwargs): + """Try to connect to the database. + Raises: + :exc:`~ConnectionError`: If the connection to the database + fails. + """ + for attempt in self.max_tries_counter: + if (self.conn is None): + try: + modulepath, _, class_name = BACKENDS[backend].rpartition('.') + Class = getattr(import_module(modulepath), class_name) + self.conn = Class(host=host, port=port, login=login, password=password, kwargs=kwargs) + break + except ConnectionError as exc: + logger.warning('Attempt %s/%s. Connection to %s:%s failed after %sms.', + attempt, self.max_tries if self.max_tries != 0 else '∞', + host, port, self.connection_timeout) + if attempt == self.max_tries: + logger.critical('Cannot connect to the Database. Giving up.') + raise ConnectionError() from exc + else: + break + return self.conn + + def close(self): + """Try to close connection to database. + Raises: + :exc:`~ConnectionError`: If the closing connection to the database + fails. + """ + for attempt in self.max_tries_counter: + if (self.conn is not None): + try: + self.conn.close() + self.conn = None + break + except ConnectionError as exc: + logger.warning('Attempt %s/%s. Close Connection to %s:%s failed after %sms.', + attempt, self.max_tries if self.max_tries != 0 else '∞', + self.conn.host, self.conn.port, self.connection_timeout) + if attempt == self.max_tries: + logger.critical('Cannot close connection to the Database. Giving up.') + raise ConnectionError() from exc + else: + break + + +class DBConnection(): + + def __init__(self, host: str = None, port: int = None, login: str = None, password: str = None, **kwargs): + """Create a new :class:`~.DBConnection` instance. + Args: + host (str): the host to connect to. + port (int): the port to connect to. + dbname (str): the name of the database to use. + connection_timeout (int, optional): the milliseconds to wait + until timing out the database connection attempt. + Defaults to 5000ms. + max_tries (int, optional): how many tries before giving up, + if 0 then try forever. Defaults to 3. + **kwargs: arbitrary keyword arguments provided by the + configuration's ``database`` settings + """ + dbconf = Config().get()['database'] + + self.host = host or dbconf["host"] if not kwargs.get("host") else kwargs["host"] + self.port = port or dbconf['port'] if not kwargs.get("port") else kwargs["port"] + self.login = login or dbconf['login'] if not kwargs.get("login") else kwargs["login"] + self.password = password or dbconf['password'] if not kwargs.get("password") else kwargs["password"] def run(self, query): """Run a query. @@ -149,18 +227,12 @@ class Connection(metaclass=DBSingleton): :exc:`~ConnectionError`: If the connection to the database fails. """ + raise NotImplementedError() - attempt = 0 - for i in self.max_tries_counter: - attempt += 1 - try: - self._conn = self._connect() - except ConnectionError as exc: - logger.warning('Attempt %s/%s. Connection to %s:%s failed after %sms.', - attempt, self.max_tries if self.max_tries != 0 else '∞', - self.host, self.port, self.connection_timeout) - if attempt == self.max_tries: - logger.critical('Cannot connect to the Database. Giving up.') - raise ConnectionError() from exc - else: - break + def close(self): + """Try to close connection to the database. + Raises: + :exc:`~ConnectionError`: If the connection to the database + fails. + """ + raise NotImplementedError() diff --git a/planetmint/backend/localmongodb/connection.py b/planetmint/backend/localmongodb/connection.py index 1216010..788d0a2 100644 --- a/planetmint/backend/localmongodb/connection.py +++ b/planetmint/backend/localmongodb/connection.py @@ -13,15 +13,13 @@ from planetmint.backend.exceptions import (DuplicateKeyError, ConnectionError) from planetmint.transactions.common.exceptions import ConfigurationError from planetmint.utils import Lazy -from planetmint.backend.connection import Connection +from planetmint.backend.connection import DBConnection, _kwargs_parser logger = logging.getLogger(__name__) -class LocalMongoDBConnection(Connection): +class LocalMongoDBConnection(DBConnection): - def __init__(self, replicaset=None, ssl=None, login=None, password=None, - ca_cert=None, certfile=None, keyfile=None, - keyfile_passphrase=None, crlfile=None, **kwargs): + def __init__(self, host: str =None, port: int = None, login: str = None, password: str = None, **kwargs): """Create a new Connection instance. Args: @@ -31,16 +29,23 @@ class LocalMongoDBConnection(Connection): configuration's ``database`` settings """ - super().__init__(**kwargs) - self.replicaset = replicaset or Config().get()['database']['replicaset'] - self.ssl = ssl if ssl is not None else Config().get()['database']['ssl'] - self.login = login or Config().get()['database']['login'] - self.password = password or Config().get()['database']['password'] - self.ca_cert = ca_cert or Config().get()['database']['ca_cert'] - self.certfile = certfile or Config().get()['database']['certfile'] - self.keyfile = keyfile or Config().get()['database']['keyfile'] - self.keyfile_passphrase = keyfile_passphrase or Config().get()['database']['keyfile_passphrase'] - self.crlfile = crlfile or Config().get()['database']['crlfile'] + super().__init__(host=host, port=port, login=login, password=password, **kwargs) + + dbconf = Config().get()['database'] + self.dbname = _kwargs_parser(key="name", kwargs=kwargs) or dbconf['name'] + self.replicaset = _kwargs_parser(key="replicaset", kwargs=kwargs) or dbconf['replicaset'] + self.ssl = _kwargs_parser(key="ssl", kwargs=kwargs) or dbconf['ssl'] + + self.ca_cert = _kwargs_parser(key="ca_cert", kwargs=kwargs) or dbconf['ca_cert'] + self.certfile = _kwargs_parser(key="certfile", kwargs=kwargs) or dbconf['certfile'] + self.keyfile = _kwargs_parser(key="keyfile", kwargs=kwargs) or dbconf['keyfile'] + self.keyfile_passphrase = _kwargs_parser(key="keyfile_passphrase", kwargs=kwargs) or dbconf[ + 'keyfile_passphrase'] + self.crlfile = _kwargs_parser(key="crlfile", kwargs=kwargs) or dbconf['crlfile'] + self.max_tries = _kwargs_parser(key="max_tries", kwargs=kwargs) + self.connection_timeout = _kwargs_parser(key="connection_timeout", kwargs=kwargs) + self.conn = self.connect() + if not self.ssl: self.ssl = False if not self.keyfile_passphrase: @@ -77,7 +82,7 @@ class LocalMongoDBConnection(Connection): print(f'DETAILS: {exc.details}') raise OperationError from exc - def _connect(self): + def connect(self): """Try to connect to the database. Raises: @@ -127,11 +132,18 @@ class LocalMongoDBConnection(Connection): except (pymongo.errors.ConnectionFailure, pymongo.errors.OperationFailure) as exc: - logger.info('Exception in _connect(): {}'.format(exc)) + logger.info('Exception in connect(): {}'.format(exc)) raise ConnectionError(str(exc)) from exc except pymongo.errors.ConfigurationError as exc: raise ConfigurationError from exc + def close(self): + try: + self.conn.close() + self.conn = None + except Exception as exc: + logger.info('Exception in planetmint.backend.localmongodb.close(): {}'.format(exc)) + raise ConnectionError(str(exc)) from exc MONGO_OPTS = { 'socketTimeoutMS': 20000, diff --git a/planetmint/backend/schema.py b/planetmint/backend/schema.py index 7204ea8..7135a0b 100644 --- a/planetmint/backend/schema.py +++ b/planetmint/backend/schema.py @@ -9,7 +9,7 @@ from functools import singledispatch import logging from planetmint.config import Config -from planetmint.backend.connection import connect +from planetmint.backend.connection import Connection from planetmint.transactions.common.exceptions import ValidationError from planetmint.transactions.common.utils import ( validate_all_values_for_key_in_obj, validate_all_values_for_key_in_list) @@ -83,7 +83,8 @@ def init_database(connection=None, dbname=None): configuration. """ - connection = connection or connect() + connection = connection or Connection().conn + print("=========================================", connection.__class__, "=========================================================") dbname = dbname or Config().get()['database']['name'] create_database(connection, dbname) diff --git a/planetmint/backend/tarantool/connection.py b/planetmint/backend/tarantool/connection.py index cc6ba8d..e421a85 100644 --- a/planetmint/backend/tarantool/connection.py +++ b/planetmint/backend/tarantool/connection.py @@ -9,27 +9,27 @@ import tarantool from planetmint.config import Config from planetmint.transactions.common.exceptions import ConfigurationError from planetmint.utils import Lazy -from planetmint.backend.connection import Connection +from planetmint.backend.connection import DBConnection logger = logging.getLogger(__name__) -class TarantoolDBConnection(Connection): +class TarantoolDBConnection(DBConnection): def __init__( self, host: str = "localhost", port: int = 3303, - user: str = None, + login: str = None, password: str = None, **kwargs, ): try: - super().__init__(**kwargs) - self.host = host - self.port = port - # TODO add user support later on - self.init_path = Config().get()["database"]["init_config"]["absolute_path"] - self.drop_path = Config().get()["database"]["drop_config"]["absolute_path"] + super().__init__(host=host, port=port, login=login, password=password, **kwargs) + + dbconf = Config().get()["database"] + self.init_path = dbconf["init_config"]["absolute_path"] + self.drop_path = dbconf["drop_config"]["absolute_path"] + self.conn = self.connect() self.SPACE_NAMES = [ "abci_chains", "assets", @@ -60,9 +60,17 @@ class TarantoolDBConnection(Connection): f.close() return "".join(execute).encode() - def _connect(self): + def connect(self): return tarantool.connect(host=self.host, port=self.port) + def close(self): + try: + self.conn.close() + self.conn = None + except Exception as exc: + logger.info('Exception in planetmint.backend.tarantool.close(): {}'.format(exc)) + raise ConnectionError(str(exc)) from exc + def get_space(self, space_name: str): return self.conn.space(space_name) diff --git a/planetmint/commands/planetmint.py b/planetmint/commands/planetmint.py index f3da72d..e0827d8 100644 --- a/planetmint/commands/planetmint.py +++ b/planetmint/commands/planetmint.py @@ -264,8 +264,8 @@ def run_drop(args): if response != 'y': return - from planetmint.backend.connection import connect - conn = connect() + from planetmint.backend.connection import Connection + conn = Connection().conn try: schema.drop_database(conn) except DatabaseDoesNotExist: diff --git a/planetmint/lib.py b/planetmint/lib.py index c8f1e05..b93162d 100644 --- a/planetmint/lib.py +++ b/planetmint/lib.py @@ -10,6 +10,7 @@ MongoDB. import logging from collections import namedtuple from uuid import uuid4 +from planetmint.backend.connection import Connection import rapidjson @@ -74,7 +75,7 @@ class Planetmint(object): self.validation = config_utils.load_validation_plugin(validationPlugin) else: self.validation = BaseValidationRules - self.connection = connection if connection is not None else planetmint.backend.connect() + self.connection = connection if connection is not None else Connection().conn def post_transaction(self, transaction, mode): """Submit a valid transaction to the mempool.""" diff --git a/tests/backend/tarantool/conftest.py b/tests/backend/tarantool/conftest.py index 83cad05..e197499 100644 --- a/tests/backend/tarantool/conftest.py +++ b/tests/backend/tarantool/conftest.py @@ -1,5 +1,5 @@ import pytest -from planetmint.backend.connection import connect +from planetmint.backend.connection import Connection # @@ -27,5 +27,5 @@ from planetmint.backend.connection import connect @pytest.fixture def db_conn(): - conn = connect() + conn = Connection().conn return conn diff --git a/tests/backend/test_connection.py b/tests/backend/test_connection.py index e2d8a85..e7b09b4 100644 --- a/tests/backend/test_connection.py +++ b/tests/backend/test_connection.py @@ -8,9 +8,9 @@ import pytest def test_get_connection_raises_a_configuration_error(monkeypatch): from planetmint.transactions.common.exceptions import ConfigurationError - from planetmint.backend.connection import connect + from planetmint.backend.connection import Connection with pytest.raises(ConfigurationError): - connect('localhost', '1337', 'mydb', 'password', 'msaccess') + Connection().connect('localhost', '1337', 'mydb', 'password', 'msaccess') with pytest.raises(ConfigurationError): # We need to force a misconfiguration here @@ -18,4 +18,4 @@ def test_get_connection_raises_a_configuration_error(monkeypatch): {'catsandra': 'planetmint.backend.meowmeow.Catsandra'}) - connect('localhost', '1337', 'mydb', 'password', 'catsandra') + Connection().connect('localhost', '1337', 'mydb', 'password', 'catsandra') diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index e3c4563..8490487 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -14,6 +14,7 @@ import pytest from planetmint.config import Config from planetmint import ValidatorElection from planetmint.commands.planetmint import run_election_show +from planetmint.backend.connection import Connection from planetmint.transactions.types.elections.election import Election from planetmint.lib import Block from planetmint.transactions.types.elections.chain_migration_election import ChainMigrationElection @@ -93,9 +94,7 @@ def test__run_init(mocker): init_db_mock = mocker.patch( 'planetmint.backend.tarantool.connection.TarantoolDBConnection.init_database') - from planetmint.backend.connection import connect - - conn = connect() + conn = Connection().conn conn.init_database() init_db_mock.assert_called_once_with() diff --git a/tests/conftest.py b/tests/conftest.py index 3fc445d..dff6bc8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,7 +18,7 @@ import codecs from collections import namedtuple from logging import getLogger from logging.config import dictConfig -from planetmint.backend.connection import connect +from planetmint.backend.connection import Connection from planetmint.backend.tarantool.connection import TarantoolDBConnection import pytest @@ -127,7 +127,7 @@ def _setup_database(_configure_planetmint): # TODO Here is located setup databa print('Initializing test db') dbname = Config().get()['database']['name'] - conn = connect() + conn = Connection().conn _drop_db(conn, dbname) schema.init_database(conn, dbname) @@ -136,7 +136,7 @@ def _setup_database(_configure_planetmint): # TODO Here is located setup databa yield print('Deleting `{}` database'.format(dbname)) - conn = connect() + conn = Connection().conn _drop_db(conn, dbname) print('Finished deleting `{}`'.format(dbname)) @@ -148,7 +148,7 @@ def _bdb(_setup_database, _configure_planetmint): from planetmint.models import Transaction from .utils import flush_db from planetmint.config import Config - conn = connect() + conn = Connection().conn yield dbname = Config().get()['database']['name'] flush_db(conn, dbname) @@ -389,7 +389,7 @@ def db_name(db_config): @pytest.fixture def db_conn(): - return connect() + return Connection().conn @pytest.fixture diff --git a/tests/tendermint/test_fastquery.py b/tests/tendermint/test_fastquery.py index aaa21d9..9e440e3 100644 --- a/tests/tendermint/test_fastquery.py +++ b/tests/tendermint/test_fastquery.py @@ -93,7 +93,7 @@ def test_filter_unspent_outputs(b, user_pk, user_sk): def test_outputs_query_key_order(b, user_pk, user_sk, user2_pk, user2_sk): from planetmint import backend - from planetmint.backend.connection import connect + from planetmint.backend.connection import Connection from planetmint.backend import query tx1 = Create.generate([user_pk], @@ -119,7 +119,7 @@ def test_outputs_query_key_order(b, user_pk, user_sk, user2_pk, user2_sk): # clean the transaction, metdata and asset collection # conn = connect() - connection = connect() + connection = Connection().conn # conn.run(conn.collection('transactions').delete_many({})) # conn.run(conn.collection('metadata').delete_many({})) # conn.run(conn.collection('assets').delete_many({})) diff --git a/tests/test_core.py b/tests/test_core.py index 621b90e..fe6d11b 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -68,15 +68,16 @@ def test_bigchain_class_default_initialization(config): def test_bigchain_class_initialization_with_parameters(): from planetmint import Planetmint - from planetmint.backend import connect + from planetmint.backend import Connection from planetmint.validation import BaseValidationRules + init_db_kwargs = { 'backend': 'localmongodb', 'host': 'this_is_the_db_host', 'port': 12345, 'name': 'this_is_the_db_name', } - connection = connect(**init_db_kwargs) + connection = Connection(**init_db_kwargs).conn planet = Planetmint(connection=connection) assert planet.connection == connection assert planet.connection.host == init_db_kwargs['host']