Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions nutkit/frontend/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def __init__(self, backend, uri, auth_token, user_agent=None,
connection_timeout_ms=None, fetch_size=None,
max_tx_retry_time_ms=None, encrypted=None,
trusted_certificates=None, liveness_check_timeout_ms=None,
max_connection_lifetime_ms=None,
max_connection_pool_size=None,
connection_acquisition_timeout_ms=None,
notifications_min_severity=None,
Expand Down Expand Up @@ -58,6 +59,7 @@ def __init__(self, backend, uri, auth_token, user_agent=None,
fetchSize=fetch_size, maxTxRetryTimeMs=max_tx_retry_time_ms,
encrypted=encrypted, trustedCertificates=trusted_certificates,
liveness_check_timeout_ms=liveness_check_timeout_ms,
max_connection_lifetime_ms=max_connection_lifetime_ms,
max_connection_pool_size=max_connection_pool_size,
connection_acquisition_timeout_ms=connection_acquisition_timeout_ms, # noqa: E501
notifications_min_severity=notifications_min_severity,
Expand Down
7 changes: 7 additions & 0 deletions nutkit/protocol/feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ class Feature(Enum):
# The driver offers a method for driver objects to report if they were
# configured with a or without encryption.
API_DRIVER_IS_ENCRYPTED = "Feature:API:Driver.IsEncrypted"
# The driver supports setting a custom max connection lifetime
API_DRIVER_MAX_CONNECTION_LIFETIME = \
"Feature:API:Driver:MaxConnectionLifetime"
# The driver supports notification filters configuration.
API_DRIVER_NOTIFICATIONS_CONFIG = "Feature:API:Driver:NotificationsConfig"
# The driver offers a method for checking if the provided authentication
Expand Down Expand Up @@ -169,6 +172,10 @@ class Feature(Enum):
# sending BEGIN but pipelines the RUN and PULL right afterwards and
# consumes three messages after that. This saves 2 full round-trips.
OPT_EXECUTE_QUERY_PIPELINING = "Optimization:ExecuteQueryPipelining"
# The driver implements a cache to match users to their most recently
# resolved home database, routing requests with no set database to this
# cached database if all open connections have an SSR connection hint.
OPT_HOME_DB_CACHE = "Optimization:HomeDatabaseCache"
# The home db cache for optimistic home db resolution treats the principal
# in basic auth the exact same way it treats impersonated users.
OPT_HOME_DB_CACHE_BASIC_PRINCIPAL_IS_IMP_USER = \
Expand Down
5 changes: 4 additions & 1 deletion nutkit/protocol/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ def __init__(
domainNameResolverRegistered=False, connectionTimeoutMs=None,
fetchSize=None, maxTxRetryTimeMs=None,
encrypted=None, trustedCertificates=None,
liveness_check_timeout_ms=None, max_connection_pool_size=None,
liveness_check_timeout_ms=None, max_connection_lifetime_ms=None,
max_connection_pool_size=None,
connection_acquisition_timeout_ms=None,
notifications_min_severity=None,
notifications_disabled_categories=None,
Expand All @@ -92,6 +93,8 @@ def __init__(
self.fetchSize = fetchSize
self.maxTxRetryTimeMs = maxTxRetryTimeMs
self.livenessCheckTimeoutMs = liveness_check_timeout_ms
if max_connection_lifetime_ms is not None:
self.maxConnectionLifetimeMs = max_connection_lifetime_ms
self.maxConnectionPoolSize = max_connection_pool_size
self.connectionAcquisitionTimeoutMs = connection_acquisition_timeout_ms
assert (client_certificate is None
Expand Down
34 changes: 34 additions & 0 deletions tests/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import os
import re
import socket
import time
import unittest
import warnings
from contextlib import contextmanager
Expand All @@ -24,6 +25,7 @@

from nutkit import protocol
from nutkit.backend import Backend
from nutkit.frontend import FakeTime


def get_backend_host_and_port():
Expand Down Expand Up @@ -255,3 +257,35 @@ class Potential(enum.Enum):
NO = 0.0
MAYBE = 0.5
# CAN_YOU_REPEAT_THE_QUESTION = "?"


class TimeoutManager:
def __init__(
self, test_case: TestkitTestCase, timeout_ms: int,
use_real_timers: bool = False
):
self._timeout_ms = timeout_ms
self._fake_time = None
if test_case.driver_supports_features(
protocol.Feature.BACKEND_MOCK_TIME
) and not use_real_timers:
self._fake_time = FakeTime(test_case._backend)

def __enter__(self):
if self._fake_time:
self._fake_time.__enter__()
return self

def __exit__(self, exc_type, exc_val, exc_tb):
if self._fake_time:
self._fake_time.__exit__(exc_type, exc_val, exc_tb)

def tick_to_before_timeout(self):
if self._fake_time:
self._fake_time.tick(self._timeout_ms - 1)

def tick_to_after_timeout(self):
if self._fake_time:
self._fake_time.tick(self._timeout_ms + 1)
else:
time.sleep(self._timeout_ms / 1000)
34 changes: 1 addition & 33 deletions tests/stub/driver_parameters/test_liveness_check.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import time
from contextlib import contextmanager

import nutkit.protocol as types
Expand All @@ -9,42 +8,11 @@
from tests.shared import (
driver_feature,
TestkitTestCase,
TimeoutManager,
)
from tests.stub.shared import StubServer


class TimeoutManager:
def __init__(
self, test_case: TestkitTestCase, timeout_ms: int,
use_real_timers: bool = False
):
self._timeout_ms = timeout_ms
self._fake_time = None
if test_case.driver_supports_features(
types.Feature.BACKEND_MOCK_TIME
) and not use_real_timers:
self._fake_time = FakeTime(test_case._backend)

def __enter__(self):
if self._fake_time:
self._fake_time.__enter__()
return self

def __exit__(self, exc_type, exc_val, exc_tb):
if self._fake_time:
self._fake_time.__exit__(exc_type, exc_val, exc_tb)

def tick_to_before_timeout(self):
if self._fake_time:
self._fake_time.tick(self._timeout_ms - 1)

def tick_to_after_timeout(self):
if self._fake_time:
self._fake_time.tick(self._timeout_ms + 1)
else:
time.sleep(self._timeout_ms / 1000)


class TestLivenessCheck(TestkitTestCase):
required_features = (
types.Feature.BOLT_5_4,
Expand Down
19 changes: 19 additions & 0 deletions tests/stub/homedb/scripts/mixed/reader_5x7_keep_warm.script
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
!: BOLT 5.7

A: HELLO {"{}": "*"}
A: LOGON {"{}": "*"}
*: RESET

C: RUN {"U": "*"} {} {"db": "homedb1", "[bookmarks]": {"[]": "*"}, "mode": "r"}
S: SUCCESS {"fields": ["n"]}
{{
C: PULL {"n": {"Z": "*"}, "[qid]": -1}
S: RECORD [1]
----
C: DISCARD {"n": {"Z": "*"}, "[qid]": -1}
}}
S: SUCCESS {}
*: RESET


?: GOODBYE
29 changes: 29 additions & 0 deletions tests/stub/homedb/scripts/mixed/reader_5x8_keep_warm.script
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
!: BOLT 5.8

A: HELLO {"{}": "*"}
A: LOGON {"{}": "*"}
*: RESET

C: RUN {"U": "*"} {} {"db": "homedb1", "[bookmarks]": {"[]": "*"}, "mode": "r", "imp_user": "user2"}
S: SUCCESS {"fields": ["n"]}
{{
C: PULL {"n": {"Z": "*"}, "[qid]": -1}
S: RECORD [1]
----
C: DISCARD {"n": {"Z": "*"}, "[qid]": -1}
}}
S: SUCCESS {}
*: RESET

C: RUN {"U": "*"} {} {"[bookmarks]": {"[]": "*"}, "mode": "r"}
S: SUCCESS {"fields": ["n"]}
{{
C: PULL {"n": {"Z": "*"}, "[qid]": -1}
S: RECORD [1]
----
C: DISCARD {"n": {"Z": "*"}, "[qid]": -1}
}}
S: SUCCESS {}
*: RESET

?: GOODBYE
20 changes: 20 additions & 0 deletions tests/stub/homedb/scripts/mixed/reader_5x8_ssr.script
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
!: BOLT 5.8

A: HELLO {"{}": "*"}
A: LOGON {"{}": "*"}
*: RESET

{+
C: RUN {"U": "*"} {} {"db": "homedb1", "[bookmarks]": {"[]": "*"}, "mode": "r"}
S: SUCCESS {"fields": ["n"]}
{{
C: PULL {"n": {"Z": "*"}, "[qid]": -1}
S: RECORD [1]
----
C: DISCARD {"n": {"Z": "*"}, "[qid]": -1}
}}
S: SUCCESS {}
*: RESET
+}

?: GOODBYE
19 changes: 19 additions & 0 deletions tests/stub/homedb/scripts/mixed/router_5x8.script
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
!: BOLT 5.8
!: ALLOW CONCURRENT

A: HELLO {"{}": "*"}
A: LOGON {"{}": "*"}
*: RESET


# explicit resolution (1st connection)
C: ROUTE "*" "*" "*"
S: SUCCESS { "rt": { "ttl": 1000000, "db": "homedb1", "servers": [{"addresses": ["#HOST#:9000"], "role": "ROUTE"}, {"addresses": ["#HOST#:9010"], "role": "READ"}, {"addresses": ["#HOST#:9020"], "role": "WRITE"}]}}
*: RESET

# explicit resolution (fallback connection)
C: ROUTE "*" "*" "*"
S: SUCCESS { "rt": { "ttl": 1000000, "db": "homedb2", "servers": [{"addresses": ["#HOST#:9000"], "role": "ROUTE"}, {"addresses": ["#HOST#:9010"], "role": "READ"}, {"addresses": ["#HOST#:#WRITER_2#"], "role": "WRITE"}]}}
*: RESET

?: GOODBYE
10 changes: 10 additions & 0 deletions tests/stub/homedb/scripts/mixed/router_keep_warm.script
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
!: BOLT #BOLT_VERSION#
!: ALLOW CONCURRENT

A: HELLO {"{}": "*"}
A: LOGON {"{}": "*"}
*: RESET

C: ROUTE {"{}": "*"} [] {"[db]": null#IMPERSONATED_USER#}
S: SUCCESS { "rt": { "ttl": 1000000, "db": "homedb1", "servers": [{"addresses": ["#HOST#:9000"], "role": "ROUTE"}, {"addresses": ["#HOST#:9010"], "role": "READ"}, {"addresses": [], "role": "WRITE"}]}}
*: RESET
19 changes: 19 additions & 0 deletions tests/stub/homedb/scripts/mixed/writer_no_ssr.script
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
!: BOLT #BOLT_VERSION#
#EXTRA_BANG_LINES#

#HELLO_MESSAGE#
A: LOGON {"{}": "*"}
*: RESET

C: RUN {"U": "*"} {} {"db": "homedb2", "[bookmarks]": {"[]": "*"}, "[mode]": "w"}
S: SUCCESS {"fields": ["n"]}
{{
C: PULL {"n": {"Z": "*"}, "[qid]": -1}
S: RECORD [1]
----
C: DISCARD {"n": {"Z": "*"}, "[qid]": -1}
}}
S: SUCCESS {}
*: RESET

?: GOODBYE
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
!: BOLT #BOLT_VERSION#
#EXTRA_BANG_LINES#

#HELLO_MESSAGE#
A: LOGON {"{}": "*"}
*: RESET

?: GOODBYE
Loading