Connection singleton (#265)

Created a Singleton for the connection classes.
This commit is contained in:
Jürgen Eckel 2022-11-02 09:51:48 +01:00 committed by GitHub
parent e401995637
commit 713bd5267c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 400 additions and 358 deletions

59
.github/workflows/unit-test-abci.yml vendored Normal file
View File

@ -0,0 +1,59 @@
# Copyright © 2020 Interplanetary Database Association e.V.,
# Planetmint and IPDB software contributors.
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
# Code is Apache-2.0 and docs are CC-BY-4.0
name: Unit tests - with direct ABCI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Build container
run: |
docker-compose -f docker-compose.yml build --no-cache --build-arg abci_status=enable planetmint
- name: Save image
run: docker save -o planetmint.tar planetmint_planetmint
- name: Upload image
uses: actions/upload-artifact@v3
with:
name: planetmint-abci
path: planetmint.tar
retention-days: 5
test-with-abci:
runs-on: ubuntu-latest
needs: build
strategy:
matrix:
include:
- db: "Tarantool with ABCI"
host: "tarantool"
port: 3303
abci: "enabled"
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Download planetmint
uses: actions/download-artifact@v3
with:
name: planetmint-abci
- name: Load planetmint
run: docker load -i planetmint.tar
- name: Start containers
run: docker-compose -f docker-compose.yml up -d planetmint
- name: Run tests
run: docker exec planetmint_planetmint_1 pytest -v -m abci

60
.github/workflows/unit-test-no-abci.yml vendored Normal file
View File

@ -0,0 +1,60 @@
# Copyright © 2020 Interplanetary Database Association e.V.,
# Planetmint and IPDB software contributors.
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
# Code is Apache-2.0 and docs are CC-BY-4.0
name: Unit tests - with Planemint
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Build container
run: |
docker-compose -f docker-compose.yml build --no-cache planetmint
- name: Save image
run: docker save -o planetmint.tar planetmint_planetmint
- name: Upload image
uses: actions/upload-artifact@v3
with:
name: planetmint
path: planetmint.tar
retention-days: 5
test-without-abci:
runs-on: ubuntu-latest
needs: build
strategy:
matrix:
include:
- db: "Tarantool without ABCI"
host: "tarantool"
port: 3303
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Download planetmint
uses: actions/download-artifact@v3
with:
name: planetmint
- name: Load planetmint
run: docker load -i planetmint.tar
- name: Start containers
run: docker-compose -f docker-compose.yml up -d bdb
- name: Run tests
run: docker exec planetmint_planetmint_1 pytest -v --cov=planetmint --cov-report xml:htmlcov/coverage.xml
- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v3

View File

@ -1,109 +0,0 @@
# Copyright © 2020 Interplanetary Database Association e.V.,
# Planetmint and IPDB software contributors.
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
# Code is Apache-2.0 and docs are CC-BY-4.0
name: Unit tests
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- abci_enabled: "ABCI enabled"
abci: "enabled"
- abci_disabled: "ABCI disabled"
abci: "disabled"
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Build container
run: |
if [[ "${{ matrix.abci }}" == "enabled" ]]; then
docker-compose -f docker-compose.yml build --no-cache --build-arg abci_status=enable planetmint
fi
if [[ ""${{ matrix.abci }}" == "disabled"" ]]; then
docker-compose -f docker-compose.yml build --no-cache planetmint
fi
- name: Save image
run: docker save -o planetmint.tar planetmint_planetmint
- name: Upload image
uses: actions/upload-artifact@v3
with:
name: planetmint-abci-${{matrix.abci}}
path: planetmint.tar
retention-days: 5
test-with-abci:
runs-on: ubuntu-latest
needs: build
strategy:
matrix:
include:
- db: "MongoDB with ABCI"
host: "mongodb"
port: 27017
abci: "enabled"
- db: "Tarantool with ABCI"
host: "tarantool"
port: 3303
abci: "enabled"
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Download planetmint
uses: actions/download-artifact@v3
with:
name: planetmint-abci-enabled
- name: Load planetmint
run: docker load -i planetmint.tar
- name: Start containers
run: docker-compose -f docker-compose.yml up -d planetmint
- name: Run tests
run: docker exec planetmint_planetmint_1 pytest -v -m abci
test-without-abci:
runs-on: ubuntu-latest
needs: build
strategy:
matrix:
include:
- db: "MongoDB without ABCI"
host: "mongodb"
port: 27017
- db: "Tarantool without ABCI"
host: "tarantool"
port: 3303
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Download planetmint
uses: actions/download-artifact@v3
with:
name: planetmint-abci-disabled
- name: Load planetmint
run: docker load -i planetmint.tar
- name: Start containers
run: docker-compose -f docker-compose.yml up -d bdb
- name: Run tests
run: docker exec planetmint_planetmint_1 pytest -v --cov=planetmint --cov-report xml:htmlcov/coverage.xml
- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v3

View File

@ -33,6 +33,11 @@ For reference, the possible headings are:
* **Changed** moved transaction network validation to Planetmint class
* **Changed** adjusted test cases
## [1.2.2] - 2022-23-09
* **Changed** A connection singleton got introduced to avoid connection issues and unstable states.
## [1.2.1] - 2022-20-09
* **Changed** Create model now validates for CID strings for asset["data"] and metadata
* **Changed** adjusted test cases

View File

@ -34,5 +34,4 @@ COPY . /usr/src/app/
WORKDIR /usr/src/app
RUN pip install -e .[dev]
RUN pip install flask-cors
RUN pip install pynacl==1.4.0 base58==2.1.1 pyasn1==0.4.8 zenroom==2.1.0.dev1655293214 cryptography==3.4.7
RUN planetmint -y configure
RUN pip install pynacl==1.4.0 base58==2.1.1 pyasn1==0.4.8 zenroom==2.1.0.dev1655293214 cryptography==3.4.7

View File

@ -38,6 +38,34 @@ def test_zenroom_signing(
zenroomscpt = ZenroomSha256(script=fulfill_script_zencode, data=zenroom_data, keys=zen_public_keys)
print(f"zenroom is: {zenroomscpt.script}")
def test_zenroom_signing(
gen_key_zencode,
secret_key_to_private_key_zencode,
fulfill_script_zencode,
zenroom_data,
zenroom_house_assets,
condition_script_zencode,
):
biolabs = generate_keypair()
version = "2.0"
alice = json.loads(zencode_exec(gen_key_zencode).output)["keyring"]
bob = json.loads(zencode_exec(gen_key_zencode).output)["keyring"]
zen_public_keys = json.loads(
zencode_exec(secret_key_to_private_key_zencode.format("Alice"), keys=json.dumps({"keyring": alice})).output
)
zen_public_keys.update(
json.loads(
zencode_exec(secret_key_to_private_key_zencode.format("Bob"), keys=json.dumps({"keyring": bob})).output
)
)
zenroomscpt = ZenroomSha256(script=fulfill_script_zencode, data=zenroom_data, keys=zen_public_keys)
print(f"zenroom is: {zenroomscpt.script}")
# CRYPTO-CONDITIONS: generate the condition uri
condition_uri_zen = zenroomscpt.condition.serialize_uri()
print(f"\nzenroom condition URI: {condition_uri_zen}")
@ -73,6 +101,7 @@ def test_zenroom_signing(
"output": ["ok"],
"policies": {},
}
metadata = {"result": {"output": ["ok"]}}
token_creation_tx = {
"operation": "CREATE",

View File

@ -27,7 +27,7 @@ services:
restart: always
planetmint:
depends_on:
#- mongodb
- mongodb
- tendermint
- tarantool
build:
@ -124,4 +124,4 @@ services:
image: alpine
command: /bin/sh -c "./planetmint/scripts/clean.sh"
volumes:
- $PWD:/planetmint
- $PWD:/planetmint

View File

@ -4,7 +4,7 @@ Content-Type: application/json
{
"assets": "/assets/",
"blocks": "/blocks/",
"docs": "https://docs.planetmint.io/projects/server/en/v1.0.1/http-client-server-api.html",
"docs": "https://docs.planetmint.io/projects/server/en/v1.3.1/http-client-server-api.html",
"metadata": "/metadata/",
"outputs": "/outputs/",
"streamedblocks": "ws://localhost:9985/api/v1/streams/valid_blocks",

View File

@ -22,9 +22,7 @@ Content-Type: application/json
]
}
],
"metadata": {
"sequence": 0
},
"metadata": "QmZs4UHLHCUGLQr6rzbtJTjT8Sf5pw6FNuaRZ5pzguk5FV",
"operation": "CREATE",
"outputs": [
{

View File

@ -20,9 +20,7 @@ Content-Type: application/json
]
}
],
"metadata": {
"sequence": 1
},
"metadata": "QmQ9Sc3VWZ3nH5FWVVi2MNMDhyjTiKrEb6mN79qqt1Uq9s",
"operation": "TRANSFER",
"outputs": [
{
@ -60,9 +58,7 @@ Content-Type: application/json
]
}
],
"metadata": {
"sequence": 2
},
"metadata": "QmRRg9RGq3TmV6nj2SXJacPuTdwaFDGTPACJLdN368pN3t",
"operation": "TRANSFER",
"outputs": [
{

View File

@ -19,9 +19,7 @@ Content-Type: application/json
]
}
],
"metadata": {
"sequence": 0
},
"metadata": "QmZs4UHLHCUGLQr6rzbtJTjT8Sf5pw6FNuaRZ5pzguk5FV",
"operation": "CREATE",
"outputs": [
{

View File

@ -6,7 +6,7 @@ Content-Type: application/json
"v1": {
"assets": "/api/v1/assets/",
"blocks": "/api/v1/blocks/",
"docs": "https://docs.planetmint.io/projects/server/en/v1.0.1/http-client-server-api.html",
"docs": "https://docs.planetmint.io/projects/server/en/v1.2.2/http-client-server-api.html",
"metadata": "/api/v1/metadata/",
"outputs": "/api/v1/outputs/",
"streamedblocks": "ws://localhost:9985/api/v1/streams/valid_blocks",
@ -15,7 +15,7 @@ Content-Type: application/json
"validators": "/api/v1/validators"
}
},
"docs": "https://docs.planetmint.io/projects/server/en/v1.0.1/",
"docs": "https://docs.planetmint.io/projects/server/en/v1.2.2/",
"software": "Planetmint",
"version": "1.0.1"
"version": "1.3.1"
}

View File

@ -20,9 +20,7 @@ Content-Type: application/json
]
}
],
"metadata": {
"sequence": 0
},
"metadata": "QmZs4UHLHCUGLQr6rzbtJTjT8Sf5pw6FNuaRZ5pzguk5FV",
"operation": "CREATE",
"outputs": [
{

View File

@ -19,9 +19,7 @@ Content-Type: application/json
]
}
],
"metadata": {
"sequence": 0
},
"metadata": "QmZs4UHLHCUGLQr6rzbtJTjT8Sf5pw6FNuaRZ5pzguk5FV",
"operation": "CREATE",
"outputs": [
{

View File

@ -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 [mail@planetmint.io](mailto:mail@planetmint.io). 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

View File

@ -3,7 +3,6 @@ import base58
from hashlib import sha3_256
from cryptoconditions.types.zenroom import ZenroomSha256
from planetmint_driver.crypto import generate_keypair
from .helper.hosts import Hosts
from zenroom import zencode_exec
import time
@ -72,7 +71,7 @@ def test_zenroom_signing(
"output": ["ok"],
"policies": {},
}
metadata = {"result": {"output": ["ok"]}}
token_creation_tx = {
"operation": "CREATE",
"asset": {"data": {"test": "my asset"}},

View File

@ -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

View File

@ -20,92 +20,54 @@ BACKENDS = {
logger = logging.getLogger(__name__)
def connect(
host: str = None, port: int = None, login: str = None, password: str = None, backend: str = None, **kwargs
):
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
host = host or Config().get()["database"]["host"] if not kwargs.get("host") else kwargs["host"]
port = port or Config().get()["database"]["port"] if not kwargs.get("port") else kwargs["port"]
login = login or Config().get()["database"]["login"] if not kwargs.get("login") else kwargs["login"]
password = password or Config().get()["database"]["password"]
try:
if backend == "tarantool_db":
modulepath, _, class_name = BACKENDS[backend].rpartition(".")
Class = getattr(import_module(modulepath), class_name)
return Class(host=host, port=port, user=login, password=password, kwargs=kwargs)
elif backend == "localmongodb":
modulepath, _, class_name = BACKENDS[backend].rpartition(".")
Class = getattr(import_module(modulepath), class_name)
dbname = _kwargs_parser(key="name", kwargs=kwargs) or Config().get()["database"]["name"]
replicaset = _kwargs_parser(key="replicaset", kwargs=kwargs) or Config().get()["database"]["replicaset"]
ssl = _kwargs_parser(key="ssl", kwargs=kwargs) or Config().get()["database"]["ssl"]
login = (
login or Config().get()["database"]["login"]
if _kwargs_parser(key="login", kwargs=kwargs) is None
else _kwargs_parser(key="login", kwargs=kwargs) # noqa: E501
)
password = (
password or Config().get()["database"]["password"]
if _kwargs_parser(key="password", kwargs=kwargs) is None
else _kwargs_parser(key="password", kwargs=kwargs) # noqa: E501
)
ca_cert = _kwargs_parser(key="ca_cert", kwargs=kwargs) or Config().get()["database"]["ca_cert"]
certfile = _kwargs_parser(key="certfile", kwargs=kwargs) or Config().get()["database"]["certfile"]
keyfile = _kwargs_parser(key="keyfile", kwargs=kwargs) or Config().get()["database"]["keyfile"]
keyfile_passphrase = (
_kwargs_parser(key="keyfile_passphrase", kwargs=kwargs)
or Config().get()["database"]["keyfile_passphrase"]
)
crlfile = _kwargs_parser(key="crlfile", kwargs=kwargs) or Config().get()["database"]["crlfile"]
max_tries = _kwargs_parser(key="max_tries", kwargs=kwargs)
connection_timeout = _kwargs_parser(key="connection_timeout", kwargs=kwargs)
return Class(
host=host,
port=port,
dbname=dbname,
max_tries=max_tries,
connection_timeout=connection_timeout,
replicaset=replicaset,
ssl=ssl,
login=login,
password=password,
ca_cert=ca_cert,
certfile=certfile,
keyfile=keyfile,
keyfile_passphrase=keyfile_passphrase,
crlfile=crlfile,
)
except tarantool.error.NetworkError as network_err:
print(f"Host {host}:{port} can't be reached.\n{network_err}")
raise network_err
def _kwargs_parser(key, kwargs):
if kwargs.get(key):
return kwargs[key]
return None
class Connection:
class DBSingleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
try:
backend = kwargs.get("backend") if kwargs and kwargs.get("backend") else None
if backend is not None 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
modulepath, _, class_name = BACKENDS[backend].rpartition(".")
Class = getattr(import_module(modulepath), class_name)
cls._instances[cls] = super(DBSingleton, Class).__call__(*args, **kwargs)
return cls._instances[cls]
class Connection(metaclass=DBSingleton):
def __init__(self) -> None:
pass
class DBConnection(metaclass=DBSingleton):
"""Connection class interface.
All backend implementations should provide a connection class that inherits
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.
@ -119,24 +81,16 @@ class Connection:
**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.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"]
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
@property
def conn(self):
if self._conn is None:
self.connect()
return self._conn
def run(self, query):
"""Run a query.
@ -159,23 +113,12 @@ class Connection:
: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()

View File

@ -11,25 +11,13 @@ from planetmint.config import Config
from planetmint.backend.exceptions import DuplicateKeyError, OperationError, ConnectionError
from 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):
def __init__(
self,
replicaset=None,
ssl=None,
login=None,
password=None,
ca_cert=None,
certfile=None,
keyfile=None,
keyfile_passphrase=None,
crlfile=None,
**kwargs,
):
class LocalMongoDBConnection(DBConnection):
def __init__(self, host: str = None, port: int = None, login: str = None, password: str = None, **kwargs):
"""Create a new Connection instance.
Args:
@ -39,16 +27,27 @@ 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) or dbconf["connection_timeout"]
)
self.__conn = None
self.connect()
if not self.ssl:
self.ssl = False
if not self.keyfile_passphrase:
@ -56,7 +55,7 @@ class LocalMongoDBConnection(Connection):
@property
def db(self):
return self.conn[self.dbname]
return self.connect()[self.dbname]
def query(self):
return Lazy()
@ -72,10 +71,10 @@ class LocalMongoDBConnection(Connection):
def run(self, query):
try:
try:
return query.run(self.conn)
return query.run(self.connect())
except pymongo.errors.AutoReconnect:
logger.warning("Lost connection to the database, " "retrying query.")
return query.run(self.conn)
return query.run(self.connect())
except pymongo.errors.AutoReconnect as exc:
raise ConnectionError from exc
except pymongo.errors.DuplicateKeyError as exc:
@ -84,7 +83,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:
@ -95,7 +94,8 @@ class LocalMongoDBConnection(Connection):
:exc:`~ConfigurationError`: If there is a ConfigurationError while
connecting to the database.
"""
if self.__conn:
return self.__conn
try:
# FYI: the connection process might raise a
# `ServerSelectionTimeoutError`, that is a subclass of
@ -131,15 +131,23 @@ class LocalMongoDBConnection(Connection):
)
if self.login is not None:
client[self.dbname].authenticate(self.login, mechanism="MONGODB-X509")
self.__conn = client
return client
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,

View File

@ -14,7 +14,7 @@ register_query = module_dispatch_registrar(convert)
@register_query(LocalMongoDBConnection)
def prepare_asset(connection, transaction_type, transaction_id, filter_operation, asset):
if transaction_type in filter_operation:
if transaction_type not in filter_operation:
asset["id"] = transaction_id
return asset

View File

@ -63,7 +63,7 @@ INDEXES = {
def create_database(conn, dbname):
logger.info("Create database `%s`.", dbname)
# TODO: read and write concerns can be declared here
conn.conn.get_database(dbname)
conn.connect().get_database(dbname)
@register_schema(LocalMongoDBConnection)
@ -73,7 +73,7 @@ def create_tables(conn, dbname):
# TODO: read and write concerns can be declared here
try:
logger.info(f"Create `{table_name}` table.")
conn.conn[dbname].create_collection(table_name)
conn.connect()[dbname].create_collection(table_name)
except CollectionInvalid:
logger.info(f"Collection {table_name} already exists.")
create_indexes(conn, dbname, table_name, INDEXES[table_name])
@ -82,9 +82,9 @@ def create_tables(conn, dbname):
def create_indexes(conn, dbname, collection, indexes):
logger.info(f"Ensure secondary indexes for `{collection}`.")
for fields, kwargs in indexes:
conn.conn[dbname][collection].create_index(fields, **kwargs)
conn.connect()[dbname][collection].create_index(fields, **kwargs)
@register_schema(LocalMongoDBConnection)
def drop_database(conn, dbname):
conn.conn.drop_database(dbname)
conn.connect().drop_database(dbname)

View File

@ -9,7 +9,7 @@ import logging
from functools import singledispatch
from planetmint.config import Config
from planetmint.backend.connection import connect
from planetmint.backend.connection import Connection
from transactions.common.exceptions import ValidationError
from transactions.common.utils import (
validate_all_values_for_key_in_obj,
@ -134,7 +134,7 @@ def init_database(connection=None, dbname=None):
configuration.
"""
connection = connection or connect()
connection = connection or Connection()
dbname = dbname or Config().get()["database"]["name"]
create_database(connection, dbname)

View File

@ -9,27 +9,28 @@ import tarantool
from planetmint.config import Config
from transactions.common.exceptions import ConfigurationError
from planetmint.utils import Lazy
from planetmint.backend.connection import Connection
from planetmint.backend.connection import DBConnection, ConnectionError
logger = logging.getLogger(__name__)
class TarantoolDBConnection(Connection):
class TarantoolDBConnection(DBConnection):
def __init__(
self,
host: str = "localhost",
port: int = 3303,
user: str = None,
host: str = None,
port: int = 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 = None
self.connect()
self.SPACE_NAMES = [
"abci_chains",
"assets",
@ -43,10 +44,11 @@ class TarantoolDBConnection(Connection):
"inputs",
"outputs",
"keys",
"scripts",
]
except tarantool.error.NetworkError as network_err:
logger.info("Host cant be reached")
raise network_err
raise ConnectionError
except ConfigurationError:
logger.info("Exception in _connect(): {}")
raise ConfigurationError
@ -60,25 +62,43 @@ class TarantoolDBConnection(Connection):
f.close()
return "".join(execute).encode()
def _connect(self):
return tarantool.connect(host=self.host, port=self.port)
def connect(self):
if not self.__conn:
self.__conn = tarantool.connect(host=self.host, port=self.port)
return self.__conn
def close(self):
try:
if self.__conn:
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)
return self.connect().space(space_name)
def space(self, space_name: str):
return self.query().space(space_name)
def run(self, query, only_data=True):
def exec(self, query, only_data=True):
try:
return query.run(self.conn).data if only_data else query.run(self.conn)
conn = self.connect()
conn.execute(query) if only_data else conn.execute(query)
except tarantool.error.OperationalError as op_error:
raise op_error
except tarantool.error.NetworkError as net_error:
raise net_error
def get_connection(self):
return self.conn
def run(self, query, only_data=True):
try:
conn = self.connect()
return query.run(conn).data if only_data else query.run(conn)
except tarantool.error.OperationalError as op_error:
raise op_error
except tarantool.error.NetworkError as net_error:
raise net_error
def drop_database(self):
db_config = Config().get()["database"]
@ -91,6 +111,11 @@ class TarantoolDBConnection(Connection):
def run_command(self, command: str, config: dict):
from subprocess import run
try:
self.close()
except ConnectionError:
pass
print(f" commands: {command}")
host_port = "%s:%s" % (self.host, self.port)
execute_cmd = self._file_content_to_bytes(path=command)
@ -101,3 +126,20 @@ class TarantoolDBConnection(Connection):
).stderr
output = output.decode()
return output
def run_command_with_output(self, command: str):
from subprocess import run
try:
self.close()
except ConnectionError:
pass
host_port = "%s:%s" % (
Config().get()["database"]["host"],
Config().get()["database"]["port"],
)
output = run(["tarantoolctl", "connect", host_port], input=command, capture_output=True)
if output.returncode != 0:
raise Exception(f"Error while trying to execute cmd {command} on host:port {host_port}: {output.stderr}")
return output.stdout

View File

@ -258,9 +258,9 @@ def run_drop(args):
if response != "y":
return
from planetmint.backend.connection import connect
from planetmint.backend.connection import Connection
conn = connect()
conn = Connection()
try:
schema.drop_database(conn)
except DatabaseDoesNotExist:

View File

@ -4,6 +4,7 @@ import os
# from planetmint.log import DEFAULT_LOGGING_CONFIG as log_config
from planetmint.version import __version__ # noqa
from decouple import config
class Singleton(type):
@ -26,7 +27,7 @@ class Config(metaclass=Singleton):
# _base_database_localmongodb.keys() because dicts are unordered.
# I tried to configure
self.log_config = DEFAULT_LOGGING_CONFIG
db = "tarantool_db"
db = config("PLANETMINT_DATABASE_BACKEND", default="tarantool_db")
self.__private_database_keys_map = { # TODO Check if it is working after removing 'name' field
"tarantool_db": ("host", "port"),
"localmongodb": ("host", "port", "name"),
@ -95,7 +96,7 @@ class Config(metaclass=Singleton):
"tendermint": {
"host": "localhost",
"port": 26657,
"version": "v0.31.5", # look for __tm_supported_versions__
"version": "v0.34.15", # look for __tm_supported_versions__
},
"database": self.__private_database_map,
"log": {

View File

@ -8,6 +8,12 @@ MongoDB.
"""
import logging
from collections import namedtuple
from uuid import uuid4
from planetmint.backend.connection import Connection
import rapidjson
from hashlib import sha3_256
import json
import rapidjson
import requests
@ -87,7 +93,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()
def post_transaction(self, transaction, mode):
"""Submit a valid transaction to the mempool."""

View File

@ -3,7 +3,7 @@
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
# Code is Apache-2.0 and docs are CC-BY-4.0
__version__ = "1.3.0"
__version__ = "1.3.1"
__short_version__ = "1.3"
# Supported Tendermint versions

View File

@ -136,6 +136,7 @@ install_requires = [
"PyNaCl==1.4.0",
"pyasn1>=0.4.8",
"cryptography==3.4.7",
"python-decouple",
"planetmint-transactions==0.2.0",
]

View File

@ -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()
return conn

View File

@ -7,16 +7,17 @@ import pytest
def test_get_connection_raises_a_configuration_error(monkeypatch):
from planetmint.backend.connection import ConnectionError
from planetmint.backend.tarantool.connection import TarantoolDBConnection
with pytest.raises(ConnectionError):
TarantoolDBConnection("localhost", "1337", "mydb", "password")
@pytest.mark.skip(reason="we currently do not suppport mongodb.")
def test_get_connection_raises_a_configuration_error_mongodb(monkeypatch):
from planetmint.backend.localmongodb.connection import LocalMongoDBConnection
from transactions.common.exceptions import ConfigurationError
from planetmint.backend.connection import connect
with pytest.raises(ConfigurationError):
connect("localhost", "1337", "mydb", "password", "msaccess")
with pytest.raises(ConfigurationError):
# We need to force a misconfiguration here
monkeypatch.setattr(
"planetmint.backend.connection.BACKENDS", {"catsandra": "planetmint.backend.meowmeow.Catsandra"}
)
connect("localhost", "1337", "mydb", "password", "catsandra")
with pytest.raises(ConnectionError):
conn = LocalMongoDBConnection("localhost", "1337", "mydb", "password")

View File

@ -12,6 +12,7 @@ from argparse import Namespace
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.lib import Block
from transactions.types.elections.chain_migration_election import ChainMigrationElection
from tests.utils import generate_election, generate_validators
@ -102,9 +103,7 @@ def test_bigchain_show_config(capsys):
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.init_database()
init_db_mock.assert_called_once_with()

View File

@ -20,7 +20,7 @@ from ipld import marshal, multihash
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
from transactions.common import crypto
from transactions.common.transaction_mode_types import BROADCAST_TX_COMMIT
@ -118,7 +118,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()
_drop_db(conn, dbname)
schema.init_database(conn, dbname)
@ -127,7 +127,7 @@ def _setup_database(_configure_planetmint): # TODO Here is located setup databa
yield
print("Deleting `{}` database".format(dbname))
conn = connect()
conn = Connection()
_drop_db(conn, dbname)
print("Finished deleting `{}`".format(dbname))
@ -140,7 +140,7 @@ def _bdb(_setup_database, _configure_planetmint):
from .utils import flush_db
from planetmint.config import Config
conn = connect()
conn = Connection()
yield
dbname = Config().get()["database"]["name"]
flush_db(conn, dbname)
@ -387,7 +387,7 @@ def db_name(db_config):
@pytest.fixture
def db_conn():
return connect()
return Connection()
@pytest.fixture

View File

@ -91,7 +91,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], [([user_pk], 3), ([user_pk], 2), ([user_pk], 1)]).sign([user_sk])
@ -116,7 +116,7 @@ def test_outputs_query_key_order(b, user_pk, user_sk, user2_pk, user2_sk):
assert len(outputs) == 1
# clean the transaction, metdata and asset collection
connection = connect()
connection = Connection()
query.delete_transactions(connection, txn_ids=[tx1.id, tx2.id])
b.store_bulk_transactions([tx1])

View File

@ -8,6 +8,8 @@ import pytest
from planetmint.version import __tm_supported_versions__
from transactions.types.assets.create import Create
from transactions.types.assets.transfer import Transfer
from transactions.common.exceptions import ConfigurationError
from planetmint.backend.connection import Connection, ConnectionError
@pytest.fixture
@ -28,7 +30,7 @@ def config(request, monkeypatch):
"name": "bigchain",
},
"tendermint": {
"host": "localhost",
"host": "tendermint",
"port": 26657,
},
"CONFIGURED": True,
@ -48,28 +50,9 @@ def test_bigchain_class_default_initialization(config):
assert planet.validation == BaseValidationRules
def test_bigchain_class_initialization_with_parameters():
from planetmint import Planetmint
from planetmint.backend import connect
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)
planet = Planetmint(connection=connection)
assert planet.connection == connection
assert planet.connection.host == init_db_kwargs["host"]
assert planet.connection.port == init_db_kwargs["port"]
# assert planet.connection.name == init_db_kwargs['name']
assert planet.validation == BaseValidationRules
@pytest.mark.bdb
def test_get_spent_issue_1271(b, alice, bob, carol):
b.connection.close()
tx_1 = Create.generate(
[carol.public_key],
[([carol.public_key], 8)],

View File

@ -81,10 +81,40 @@ def test_get_outputs_endpoint_with_invalid_spent(client, user_pk):
assert res.status_code == 400
@pytest.mark.skip(
reason="just failing sometimes - a test to narrow down the issues of the test 'test_get_divisble_transactions_returns_500'"
)
@pytest.mark.abci
def test_get_divisble_transactions_returns_500_phase_one(b, client):
import json
import time
TX_ENDPOINT = "/api/v1/transactions"
def mine(tx_list):
b.store_bulk_transactions(tx_list)
alice_priv, alice_pub = crypto.generate_key_pair()
# bob_priv, bob_pub = crypto.generate_key_pair()
# carly_priv, carly_pub = crypto.generate_key_pair()
# time.sleep(1)
create_tx = Create.generate([alice_pub], [([alice_pub], 4)])
create_tx.sign([alice_priv])
# ATTENTION: comment out the next line and the test will never fail
res = client.post(TX_ENDPOINT, data=json.dumps(create_tx.to_dict()))
assert res.status_code == 202
mine([create_tx])
@pytest.mark.skip(
reason="this test fails with strange inconsistent tarantool error messages. sometimes, it's even passing."
)
@pytest.mark.abci
def test_get_divisble_transactions_returns_500(b, client):
from transactions.common import crypto
import json
import time
TX_ENDPOINT = "/api/v1/transactions"
@ -94,7 +124,6 @@ def test_get_divisble_transactions_returns_500(b, client):
alice_priv, alice_pub = crypto.generate_key_pair()
bob_priv, bob_pub = crypto.generate_key_pair()
carly_priv, carly_pub = crypto.generate_key_pair()
create_tx = Create.generate([alice_pub], [([alice_pub], 4)])
create_tx.sign([alice_priv])
@ -110,7 +139,6 @@ def test_get_divisble_transactions_returns_500(b, client):
res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx.to_dict()))
assert res.status_code == 202
mine([transfer_tx])
transfer_tx_carly = Transfer.generate([transfer_tx.to_inputs()[1]], [([carly_pub], 1)], asset_ids=[create_tx.id])
@ -118,7 +146,6 @@ def test_get_divisble_transactions_returns_500(b, client):
res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx_carly.to_dict()))
assert res.status_code == 202
mine([transfer_tx_carly])
asset_id = create_tx.id

View File

@ -11,6 +11,7 @@ from unittest.mock import Mock, patch
from cryptoconditions import Ed25519Sha256
from ipld import multihash, marshal
from hashlib import sha3_256
from transactions.common import crypto
from transactions.common.transaction import Transaction
from transactions.types.assets.create import Create