Skip to content

ADR 013: bolt_agent, Bolt 5.3 #547

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
May 24, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
12 changes: 11 additions & 1 deletion boltstub/bolt_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,6 @@ class Bolt5x1Protocol(Bolt5x0Protocol):
version_aliases = set()
# allow the server to negotiate other bolt versions
equivalent_versions = set()
server_agent = "Neo4j/5.5.0"

messages = {
"C": {
Expand All @@ -489,6 +488,8 @@ class Bolt5x1Protocol(Bolt5x0Protocol):
"S": Bolt5x0Protocol.messages["S"],
}

server_agent = "Neo4j/5.5.0"


class Bolt5x2Protocol(Bolt5x1Protocol):
protocol_version = (5, 2)
Expand All @@ -497,3 +498,12 @@ class Bolt5x2Protocol(Bolt5x1Protocol):
equivalent_versions = set()

server_agent = "Neo4j/5.7.0"


class Bolt5x3Protocol(Bolt5x2Protocol):
protocol_version = (5, 3)
version_aliases = set()
# allow the server to negotiate other bolt versions
equivalent_versions = set()

server_agent = "Neo4j/5.9.0"
5 changes: 3 additions & 2 deletions boltstub/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@


def load_parser():
with open(path.join(path.dirname(__file__), "grammar.lark"), "r") as fd:
grammar_path = path.join(path.dirname(__file__), "grammar.lark")
with open(grammar_path, "r", encoding="utf-8") as fd:
return lark.Lark(
fd, propagate_positions=True # , ambiguity="explicit"
)
Expand Down Expand Up @@ -1557,7 +1558,7 @@ def parse(script: str, substitutions: Optional[dict] = None) -> Script:


def parse_file(filename):
with open(filename) as fd:
with open(filename, encoding="utf-8") as fd:
try:
script = parse(fd.read())
except Exception:
Expand Down
2 changes: 2 additions & 0 deletions nutkit/protocol/feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ class Feature(Enum):
BOLT_5_1 = "Feature:Bolt:5.1"
# The driver supports Bolt protocol version 5.2
BOLT_5_2 = "Feature:Bolt:5.2"
# The driver supports Bolt protocol version 5.3
BOLT_5_3 = "Feature:Bolt:5.3"
# The driver supports patching DateTimes to use UTC for Bolt 4.3 and 4.4
BOLT_PATCH_UTC = "Feature:Bolt:Patch:UTC"
# The driver supports impersonation
Expand Down
14 changes: 14 additions & 0 deletions tests/stub/driver_parameters/scripts/v5x2/user_agent_custom.script
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
!: BOLT 5.2

C: HELLO {"[routing]": null, "user_agent": "Hello, I'm a banana 🍌!"}
S: SUCCESS {"server": "Neo4j/5.5.0", "connection_id": "bolt-1"}
C: LOGON {"{}": "*"}
S: SUCCESS {}
*: RESET
C: RUN "*" "*" "*"
S: SUCCESS {"fields": ["n"]}
C: PULL {"n": "*"}
S: RECORD [1]
SUCCESS {"type": "r"}
*: RESET
?: GOODBYE
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
!: BOLT 5.2

C: HELLO {"[routing]": null, "user_agent": {"U": "*"}}
S: SUCCESS {"server": "Neo4j/5.5.0", "connection_id": "bolt-1"}
C: LOGON {"{}": "*"}
S: SUCCESS {}
*: RESET
C: RUN "*" "*" "*"
S: SUCCESS {"fields": ["n"]}
C: PULL {"n": "*"}
S: RECORD [1]
SUCCESS {"type": "r"}
*: RESET
?: GOODBYE
14 changes: 14 additions & 0 deletions tests/stub/driver_parameters/scripts/v5x3/user_agent_custom.script
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
!: BOLT 5.3

C: HELLO {"[routing]": null, "user_agent": "Hello, I'm a banana 🍌!", "bolt_agent": {"product": {"U": "*"}, "[platform]": {"U": "*"}, "[language]": {"U": "*"}, "[language_details]": {"U": "*"}}}
S: SUCCESS {"server": "Neo4j/5.9.0", "connection_id": "bolt-1"}
C: LOGON {"{}": "*"}
S: SUCCESS {}
*: RESET
C: RUN "*" "*" "*"
S: SUCCESS {"fields": ["n"]}
C: PULL {"n": "*"}
S: RECORD [1]
SUCCESS {"type": "r"}
*: RESET
?: GOODBYE
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
!: BOLT 5.3

C: HELLO {"[routing]": null, "user_agent": {"U": "*"}, "bolt_agent": {"product": {"U": "*"}, "[platform]": {"U": "*"}, "[language]": {"U": "*"}, "[language_details]": {"U": "*"}}}
S: SUCCESS {"server": "Neo4j/5.9.0", "connection_id": "bolt-1"}
C: LOGON {"{}": "*"}
S: SUCCESS {}
*: RESET
C: RUN "*" "*" "*"
S: SUCCESS {"fields": ["n"]}
C: PULL {"n": "*"}
S: RECORD [1]
SUCCESS {"type": "r"}
*: RESET
?: GOODBYE
2 changes: 1 addition & 1 deletion tests/stub/driver_parameters/test_bookmark_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -837,7 +837,7 @@ def test_multiple_bookmark_manager(self):
)

def _start_server(self, server, script):
server.start(self.script_path(script),
server.start(self.script_path("v5x0", script),
vars_={"#HOST#": self._router.host})

def assert_begin(self, line: str, bookmarks=None):
Expand Down
123 changes: 123 additions & 0 deletions tests/stub/driver_parameters/test_client_agent_strings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import abc
import json
import re
from contextlib import contextmanager

import nutkit.protocol as types
from nutkit.frontend import Driver
from tests.shared import TestkitTestCase
from tests.stub.shared import StubServer


class _ClientAgentStringsTestBase(TestkitTestCase, abc.ABC):

@property
@abc.abstractmethod
def version_folder(self) -> str:
...

def setUp(self):
super().setUp()
self._server = StubServer(9000)
self._driver = None
self._session = None

def tearDown(self):
self._server.reset()
if self._session:
self._session.close()
if self._driver:
self._driver.close()
return super().tearDown()

def _start_server(self, script, version_folder=None, vars_=None):
if version_folder is None:
version_folder = self.version_folder
self._server.start(self.script_path(version_folder, script),
vars_=vars_)

@contextmanager
def driver(self, **kwargs):
auth = types.AuthorizationToken("basic", principal="neo4j",
credentials="pass")
uri = "bolt://%s" % self._server.address
driver = Driver(self._backend, uri, auth, **kwargs)
try:
yield driver
finally:
driver.close()

@contextmanager
def session(self, driver=None, **driver_kwargs):
if driver is None:
with self.driver(**driver_kwargs) as driver:
session = driver.session("r")
try:
yield session
finally:
session.close()
else:
session = driver.session("r")
try:
yield session
finally:
session.close()

def _session_run_return_1(self, **driver_kwargs):
with self.session(**driver_kwargs) as session:
result = session.run("RETURN 1 AS n")
list(result)

def _test_default_user_agent(self):
self._start_server("user_agent_default.script")
self._session_run_return_1()

def _test_custom_user_agent(self):
self._start_server("user_agent_custom.script")
self._session_run_return_1(user_agent="Hello, I'm a banana 🍌!")


class TestClientAgentStringsV5x2(_ClientAgentStringsTestBase):

version_folder = "v5x2"
required_features = (types.Feature.BOLT_5_2,)

def test_default_user_agent(self):
super()._test_default_user_agent()

def test_custom_user_agent(self):
super()._test_custom_user_agent()


class TestClientAgentStringsV5x3(_ClientAgentStringsTestBase):

version_folder = "v5x3"
required_features = (types.Feature.BOLT_5_3,)

def test_default_user_agent(self):
super()._test_default_user_agent()

def test_custom_user_agent(self):
super()._test_custom_user_agent()

def test_bolt_agent(self):
super()._test_default_user_agent()

hellos = self._server.get_requests("HELLO")
assert len(hellos) == 1
hello_extra = json.loads(hellos[0].split(maxsplit=1)[1])
bolt_agent = hello_extra["{}"]["bolt_agent"]["{}"]
self._assert_bolt_agent_product_conforms_format(bolt_agent["product"])

self._server.reset()
super()._test_custom_user_agent()

hellos = self._server.get_requests("HELLO")
assert len(hellos) == 1
hello_extra = json.loads(hellos[0].split(maxsplit=1)[1])
# asserts user agent is does not affect bolt agent
assert bolt_agent == hello_extra["{}"]["bolt_agent"]["{}"]

@staticmethod
def _assert_bolt_agent_product_conforms_format(bolt_agent_product):
assert re.match(r"^.+/.+$", bolt_agent_product)
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def _get_vars(self):
def _start_server(self, server, script, vars_=None):
if vars_ is None:
vars_ = self._get_vars()
server.start(self.script_path(script), vars_=vars_)
server.start(self.script_path("v5x0", script), vars_=vars_)

def test_should_work_when_every_step_is_done_in_time(self):
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def setUp(self):
# test for security reasons.
self._server = StubServer(9999)
self._server.start(
self.script_path("tx_without_commit_or_rollback.script")
self.script_path("v5x0", "tx_without_commit_or_rollback.script")
)
self._driver = None
self._sessions = []
Expand Down
4 changes: 2 additions & 2 deletions tests/stub/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,14 @@ def start(self, path=None, script=None, vars_=None):
if path:
self._last_rewritten_path = path
script_fn = os.path.basename(path)
with open(path, "r") as f:
with open(path, "r", encoding="utf-8") as f:
script = f.read()
for v in vars_:
script = script.replace(v, str(vars_[v]))
if script:
tempdir = tempfile.gettempdir()
path = os.path.join(tempdir, script_fn)
with open(path, "w") as f:
with open(path, "w", encoding="utf-8") as f:
f.write(script)
f.flush()
os.fsync(f)
Expand Down
2 changes: 1 addition & 1 deletion tests/stub/versions/scripts/v5x2_return_1.script
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
!: BOLT 5.1
!: BOLT 5.2

C: HELLO {"{}": "*"}
S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"}
Expand Down
18 changes: 18 additions & 0 deletions tests/stub/versions/scripts/v5x3_return_1.script
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
!: BOLT 5.3

C: HELLO {"{}": "*"}
S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"}
A: LOGON {"{}": "*"}
*: RESET
{?
C: RUN {"U": "*"} {"{}": "*"} {"{}": "*"}
S: SUCCESS {"fields": ["n.name"]}
{{
C: PULL {"n": {"Z": "*"}}
----
C: DISCARD {"n": {"Z": "*"}}
}}
S: SUCCESS {"type": "w"}
?}
*: RESET
?: GOODBYE
Loading