Test Failed
Pull Request — master (#202)
by Vinicius
08:16
created

kytos.core.db   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 133
Duplicated Lines 0 %

Test Coverage

Coverage 91.07%

Importance

Changes 0
Metric Value
eloc 92
dl 0
loc 133
ccs 51
cts 56
cp 0.9107
rs 10
c 0
b 0
f 0
wmc 14

4 Functions

Rating   Name   Duplication   Size   Complexity  
A _mongo_conn_wait() 0 15 3
A _log_pymongo_thread_traceback() 0 12 3
A mongo_client() 0 38 1
A db_conn_wait() 0 11 2

1 Method

Rating   Name   Duplication   Size   Complexity  
A Mongo.bootstrap_index() 0 20 5
1
"""DB client."""
2
3 2
import logging
4 2
import os
5 2
import sys
6 2
import time
7 2
from typing import Optional
8
9 2
import pymongo.helpers
10 2
from pymongo import MongoClient
11 2
from pymongo.errors import AutoReconnect, OperationFailure
12
13 2
from kytos.core.exceptions import KytosDBInitException
14
15 2
LOG = logging.getLogger(__name__)
16
17
18 2
def _log_pymongo_thread_traceback() -> None:
19
    """Log pymongo thread traceback, originally it printed out to sys.stderr
20
    poluting it when handling certain asynchronous errors that can happen,
21
    with this patched function it logs to LOG.error instead."""
22 2
    if sys.stderr:
23 2
        einfo = sys.exc_info()
24 2
        try:
25 2
            LOG.error(einfo)
26
        except IOError:
27
            pass
28
        finally:
29 2
            del einfo
30
31
32 2
if hasattr(pymongo.helpers, "_handle_exception"):
33 2
    pymongo.helpers._handle_exception = _log_pymongo_thread_traceback
34
35
36 2
def mongo_client(
37
    host_seeds=os.environ.get(
38
        "MONGO_HOST_SEEDS", "mongo1:27017,mongo2:27018,mongo3:27019"
39
    ),
40
    username=os.environ.get("MONGO_USERNAME") or "invalid_user",
41
    password=os.environ.get("MONGO_PASSWORD") or "invalid_password",
42
    database=os.environ.get("MONGO_DBNAME") or "napps",
43
    connect=False,
44
    retrywrites=True,
45
    retryreads=True,
46
    readpreference="primaryPreferred",
47
    readconcernlevel="majority",
48
    maxpoolsize=int(os.environ.get("MONGO_MAX_POOLSIZE") or 300),
49
    minpoolsize=int(os.environ.get("MONGO_MIN_POOLSIZE") or 30),
50
    serverselectiontimeoutms=30000,
51
    **kwargs,
52
) -> MongoClient:
53
    """Instantiate a MongoClient instance. MongoClient is thread-safe
54
    and has connection-pooling built in.
55
56
    NApps are supposed to use the Mongo class that wraps a MongoClient. NApps
57
    might use a new MongoClient instance for exceptional cases where a NApp
58
    needs to parametrize differently.
59
    """
60 2
    return MongoClient(
61
        host_seeds.split(","),
62
        username=username,
63
        password=password,
64
        connect=False,
65
        authsource=database,
66
        retrywrites=retrywrites,
67
        retryreads=retryreads,
68
        readpreference=readpreference,
69
        maxpoolsize=maxpoolsize,
70
        minpoolsize=minpoolsize,
71
        readconcernlevel=readconcernlevel,
72
        serverselectiontimeoutms=serverselectiontimeoutms,
73
        **kwargs,
74
    )
75
76
77 2
class Mongo:
78
    """MongoClient instance for NApps."""
79
80 2
    client = mongo_client(connect=False)
81 2
    db_name = os.environ.get("MONGO_DBNAME") or "napps"
82
83 2
    @classmethod
84 2
    def bootstrap_index(
85
        cls,
86
        collection: str,
87
        index: str,
88
        direction: int,
89
        **kwargs,
90
    ) -> Optional[str]:
91
        """Bootstrap index."""
92 2
        db = cls.client[cls.db_name]
93 2
        indexes = set()
94
95 2
        for value in db[collection].index_information().values():
96
            if "key" in value and isinstance(value["key"], list):
97
                indexes.add(value["key"][0])
98
99 2
        if (index, direction) not in indexes:
100 2
            return db[collection].create_index([(index, direction)], **kwargs)
101
102
        return None
103
104
105 2
def _mongo_conn_wait(mongo_client=mongo_client, retries=10,
106
                     timeout_ms=10000) -> None:
107
    """Try to run 'hello' command on MongoDB and wait for it with retries."""
108 2
    try:
109 2
        client = mongo_client(maxpoolsize=6, minpoolsize=3)
110 2
        LOG.info("Trying to run 'hello' command on MongoDB...")
111 2
        client.db.command("hello")
112 2
        LOG.info("Ran 'hello' command on MongoDB successfully. It's ready!")
113 2
    except (OperationFailure, AutoReconnect) as exc:
114 2
        retries -= 1
115 2
        if retries > 0:
116 2
            time.sleep(max(timeout_ms / 1000, 1))
117 2
            return _mongo_conn_wait(mongo_client, retries, timeout_ms)
118 2
        LOG.error("Maximum retries reached when waiting for MongoDB")
119 2
        raise KytosDBInitException(str(exc), exc)
120
121
122 2
def db_conn_wait(db_backend="mongodb", retries=12, timeout_ms=10000) -> None:
123
    """DB conn wait."""
124 2
    try:
125 2
        LOG.info("Starting DB connection")
126 2
        conn_wait_funcs = {"mongodb": _mongo_conn_wait}
127 2
        return conn_wait_funcs[db_backend](retries=retries,
128
                                           timeout_ms=timeout_ms)
129 2
    except KeyError:
130 2
        client_names = ",".join(list(conn_wait_funcs.keys()))
0 ignored issues
show
introduced by
The variable conn_wait_funcs does not seem to be defined for all execution paths.
Loading history...
131 2
        raise KytosDBInitException(
132
            f"DB backend '{db_backend}' isn't supported."
133
            f" Current supported databases: {client_names}"
134
        )
135