Skip to content

Commit a5e056d

Browse files
TestKit backend: add custom DNS resolution hook (#622)
* TestKit backend: add custom DNS resolution hook Signed-off-by: Rouven Bauer <rouven.bauer@neo4j.com> * Fix DNS hook protocol: shouldn't send or expect port * Improve connection logging * Skip test the driver isn't compliant with * More another test skipped * Avoid backend getting stuck if TestKit fails to respond to resolver request * Add skipped test from incorect merge --------- Signed-off-by: Rouven Bauer <rouven.bauer@neo4j.com> Co-authored-by: Steve Cathcart <stephen.cathcart@neotechnology.com> Co-authored-by: Stephen Cathcart <stephen.cathcart@neo4j.com> Co-authored-by: Stephen Cathcart <stephen.m.cathcart@gmail.com>
1 parent 41d6c1b commit a5e056d

File tree

5 files changed

+114
-40
lines changed

5 files changed

+114
-40
lines changed

neo4j/driver_with_context.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ func NewDriverWithContext(target string, auth auth.TokenManager, configurers ...
217217
}
218218

219219
d.connector.Log = d.log
220+
d.connector.LogId = d.logId
220221
d.connector.RoutingContext = routingContext
221222
d.connector.Config = d.config
222223

neo4j/driver_with_context_testkit.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ func ForceRoutingTableUpdate(d DriverWithContext, database string, bookmarks []s
5656
return errorutil.WrapError(err)
5757
}
5858

59+
func RegisterDnsResolver(d DriverWithContext, hook func(address string) []string) {
60+
d.(*driverWithContext).connector.TestKitDnsResolver = hook
61+
}
62+
5963
func GetRoutingTable(d DriverWithContext, database string) (*RoutingTable, error) {
6064
driver := d.(*driverWithContext)
6165
router, ok := driver.router.(*router.Router)

neo4j/internal/bolt/hydrator.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,11 @@ import (
2424
"github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/gql"
2525
"time"
2626

27-
idb "github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/db"
28-
"github.com/neo4j/neo4j-go-driver/v5/neo4j/log"
29-
3027
"github.com/neo4j/neo4j-go-driver/v5/neo4j/db"
3128
"github.com/neo4j/neo4j-go-driver/v5/neo4j/dbtype"
29+
idb "github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/db"
3230
"github.com/neo4j/neo4j-go-driver/v5/neo4j/internal/packstream"
31+
"github.com/neo4j/neo4j-go-driver/v5/neo4j/log"
3332
)
3433

3534
const containsSystemUpdatesKey = "contains-system-updates"

neo4j/internal/connector/connector.go

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,18 @@ import (
3434
)
3535

3636
type Connector struct {
37-
SkipEncryption bool
38-
SkipVerify bool
39-
Log log.Logger
40-
RoutingContext map[string]string
41-
Network string
42-
Config *config.Config
43-
SupplyConnection func(context.Context, string) (net.Conn, error)
37+
SkipEncryption bool
38+
SkipVerify bool
39+
Log log.Logger
40+
LogId string
41+
RoutingContext map[string]string
42+
Network string
43+
Config *config.Config
44+
SupplyConnection func(context.Context, string) (net.Conn, error)
45+
TestKitDnsResolver func(string) []string
4446
}
4547

46-
func (c Connector) Connect(
48+
func (c *Connector) Connect(
4749
ctx context.Context,
4850
address string,
4951
auth *db.ReAuthToken,
@@ -138,7 +140,29 @@ func (c Connector) createConnection(ctx context.Context, address string) (net.Co
138140
dialer.KeepAlive = -1 * time.Second // Turns keep-alive off
139141
}
140142

141-
return dialer.DialContext(ctx, c.Network, address)
143+
if c.TestKitDnsResolver == nil {
144+
c.Log.Debugf(log.Driver, c.LogId, "dialing %s", address)
145+
return dialer.DialContext(ctx, c.Network, address)
146+
}
147+
148+
addresses := c.TestKitDnsResolver(address)
149+
150+
if len(addresses) == 0 {
151+
return nil, errors.New("TestKit DNS resolver returned no address")
152+
}
153+
154+
var (
155+
err error
156+
con net.Conn
157+
)
158+
for _, address := range addresses {
159+
con, err = dialer.DialContext(ctx, c.Network, address)
160+
if err == nil {
161+
c.Log.Debugf(log.Driver, c.LogId, "dialing %s", address)
162+
return con, nil
163+
}
164+
}
165+
return nil, err
142166
}
143167

144168
func (c Connector) tlsConfig(serverName string) *tls.Config {

testkit-backend/backend.go

Lines changed: 74 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/neo4j/neo4j-go-driver/v5/neo4j/notifications"
3030
"io"
3131
"math"
32+
"net"
3233
"net/url"
3334
"regexp"
3435
"strings"
@@ -52,6 +53,7 @@ type backend struct {
5253
explicitTransactions map[string]neo4j.ExplicitTransaction
5354
recordedErrors map[string]error
5455
resolvedAddresses map[string][]any
56+
dnsResolutions map[string][]any
5557
authTokenManagers map[string]auth.TokenManager
5658
resolvedGetAuthTokens map[string]neo4j.AuthToken
5759
resolvedHandleSecurityException map[string]bool
@@ -64,6 +66,7 @@ type backend struct {
6466
bookmarkManagers map[string]neo4j.BookmarkManager
6567
clientCertificateProviders map[string]auth.ClientCertificateProvider
6668
resolvedClientCertificates map[string]auth.ClientCertificate
69+
closed bool
6770
}
6871

6972
// To implement transactional functions a bit of extra state is needed on the
@@ -148,6 +151,7 @@ func newBackend(rd *bufio.Reader, wr io.Writer) *backend {
148151
explicitTransactions: make(map[string]neo4j.ExplicitTransaction),
149152
recordedErrors: make(map[string]error),
150153
resolvedAddresses: make(map[string][]any),
154+
dnsResolutions: make(map[string][]any),
151155
authTokenManagers: make(map[string]auth.TokenManager),
152156
resolvedGetAuthTokens: make(map[string]neo4j.AuthToken),
153157
resolvedHandleSecurityException: make(map[string]bool),
@@ -159,6 +163,7 @@ func newBackend(rd *bufio.Reader, wr io.Writer) *backend {
159163
consumedBookmarks: make(map[string]struct{}),
160164
clientCertificateProviders: make(map[string]auth.ClientCertificateProvider),
161165
resolvedClientCertificates: make(map[string]auth.ClientCertificate),
166+
closed: false,
162167
}
163168
}
164169

@@ -295,6 +300,7 @@ func (b *backend) process() bool {
295300
for {
296301
line, err := b.rd.ReadString('\n')
297302
if err != nil {
303+
b.closed = true
298304
return false
299305
}
300306

@@ -329,6 +335,10 @@ func (b *backend) writeResponse(name string, data any) {
329335
if err != nil {
330336
panic(err.Error())
331337
}
338+
if b.closed {
339+
fmt.Print("RES ignored because backend is closed\n")
340+
return
341+
}
332342
// Make sure that logging framework doesn't write anything inbetween here...
333343
b.wrLock.Lock()
334344
defer b.wrLock.Unlock()
@@ -443,8 +453,7 @@ func (b *backend) handleTransactionFunc(isRead bool, data map[string]any) {
443453
b.managedTransactions[txId] = tx
444454
b.writeResponse("RetryableTry", map[string]any{"id": txId})
445455
// Process all things that the client might do within the transaction
446-
for {
447-
b.process()
456+
for b.process() {
448457
switch sessionState.retryableState {
449458
case retryablePositive:
450459
// Client succeeded and wants to commit
@@ -460,6 +469,7 @@ func (b *backend) handleTransactionFunc(isRead bool, data map[string]any) {
460469
// Client did something not related to the retryable state
461470
}
462471
}
472+
return nil, nil
463473
}
464474
var err error
465475
if isRead {
@@ -482,8 +492,7 @@ func (b *backend) customAddressResolverFunction() config.ServerAddressResolver {
482492
"id": id,
483493
"address": fmt.Sprintf("%s:%s", address.Hostname(), address.Port()),
484494
})
485-
for {
486-
b.process()
495+
for b.process() {
487496
if addresses, ok := b.resolvedAddresses[id]; ok {
488497
delete(b.resolvedAddresses, id)
489498
result := make([]config.ServerAddress, len(addresses))
@@ -493,6 +502,35 @@ func (b *backend) customAddressResolverFunction() config.ServerAddressResolver {
493502
return result
494503
}
495504
}
505+
return nil
506+
}
507+
}
508+
509+
func (b *backend) dnsResolverFunction() func(address string) []string {
510+
return func(address string) []string {
511+
id := b.nextId()
512+
host, port, err := net.SplitHostPort(address)
513+
if err != nil {
514+
b.writeError(fmt.Errorf(
515+
"couldn't parse address for custom DNS resulution (probably a bug in backend): %w", err,
516+
))
517+
return nil
518+
}
519+
b.writeResponse("DomainNameResolutionRequired", map[string]string{
520+
"id": id,
521+
"name": host,
522+
})
523+
for b.process() {
524+
if addresses, ok := b.dnsResolutions[id]; ok {
525+
delete(b.dnsResolutions, id)
526+
result := make([]string, len(addresses))
527+
for i, address := range addresses {
528+
result[i] = fmt.Sprintf("%s:%s", address, port)
529+
}
530+
return result
531+
}
532+
}
533+
return nil
496534
}
497535
}
498536

@@ -533,6 +571,11 @@ func (b *backend) handleRequest(req map[string]any) {
533571
fmt.Printf("REQ: %s %s\n", name, dataJson)
534572
switch name {
535573

574+
case "DomainNameResolutionCompleted":
575+
requestId := data["requestId"].(string)
576+
addresses := data["addresses"].([]any)
577+
b.dnsResolutions[requestId] = addresses
578+
536579
case "ResolverResolutionCompleted":
537580
requestId := data["requestId"].(string)
538581
addresses := data["addresses"].([]any)
@@ -640,6 +683,11 @@ func (b *backend) handleRequest(req map[string]any) {
640683
b.writeError(err)
641684
return
642685
}
686+
687+
if data["domainNameResolverRegistered"] != nil && data["domainNameResolverRegistered"].(bool) {
688+
neo4j.RegisterDnsResolver(driver, b.dnsResolverFunction())
689+
}
690+
643691
idKey := b.nextId()
644692
b.drivers[idKey] = driver
645693
b.writeResponse("Driver", map[string]any{"id": idKey})
@@ -1150,13 +1198,13 @@ func (b *backend) handleRequest(req map[string]any) {
11501198
"id": id,
11511199
"authTokenManagerId": managerId,
11521200
})
1153-
for {
1154-
b.process()
1201+
for b.process() {
11551202
if token, ok := b.resolvedGetAuthTokens[id]; ok {
11561203
delete(b.resolvedGetAuthTokens, id)
11571204
return token
11581205
}
11591206
}
1207+
return neo4j.AuthToken{}
11601208
},
11611209
HandleSecurityExceptionFunc: func(token neo4j.AuthToken, error *db.Neo4jError) bool {
11621210
id := b.nextId()
@@ -1168,13 +1216,13 @@ func (b *backend) handleRequest(req map[string]any) {
11681216
"auth": serializeAuth(token),
11691217
"errorCode": error.Code,
11701218
})
1171-
for {
1172-
b.process()
1219+
for b.process() {
11731220
if handled, ok := b.resolvedHandleSecurityException[id]; ok {
11741221
delete(b.resolvedHandleSecurityException, id)
11751222
return handled
11761223
}
11771224
}
1225+
return false
11781226
},
11791227
}
11801228
b.authTokenManagers[managerId] = manager
@@ -1203,13 +1251,13 @@ func (b *backend) handleRequest(req map[string]any) {
12031251
"id": id,
12041252
"basicAuthTokenManagerId": managerId,
12051253
})
1206-
for {
1207-
b.process()
1254+
for b.process() {
12081255
if basicToken, ok := b.resolvedBasicTokens[id]; ok {
12091256
delete(b.resolvedBasicTokens, id)
12101257
return basicToken.token, nil
12111258
}
12121259
}
1260+
return neo4j.AuthToken{}, nil
12131261
})
12141262
b.authTokenManagers[managerId] = manager
12151263
b.writeResponse("BasicAuthTokenManager", map[string]any{"id": managerId})
@@ -1229,13 +1277,13 @@ func (b *backend) handleRequest(req map[string]any) {
12291277
"id": id,
12301278
"bearerAuthTokenManagerId": managerId,
12311279
})
1232-
for {
1233-
b.process()
1280+
for b.process() {
12341281
if bearerToken, ok := b.resolvedBearerTokens[id]; ok {
12351282
delete(b.resolvedBearerTokens, id)
12361283
return bearerToken.token, bearerToken.expiration, nil
12371284
}
12381285
}
1286+
return neo4j.AuthToken{}, nil, nil
12391287
})
12401288
b.authTokenManagers[managerId] = manager
12411289
b.writeResponse("BearerAuthTokenManager", map[string]any{"id": managerId})
@@ -1709,20 +1757,18 @@ func testSkips() map[string]string {
17091757
"stub.routing.test_routing_v3.RoutingV3.test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_run_using_tx_run": "Won't fix - only Bolt 3 affected (not officially supported by this driver): broken servers are not removed from routing table",
17101758
"stub.routing.test_routing_v3.RoutingV3.test_should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run": "Won't fix - only Bolt 3 affected (not officially supported by this driver): broken servers are not removed from routing table",
17111759

1712-
// Missing message support in testkit backend
1713-
"stub.routing.*.*.test_should_request_rt_from_all_initial_routers_until_successful_on_unknown_failure": "Add DNS resolver TestKit message and connection timeout support",
1714-
"stub.routing.*.*.test_should_request_rt_from_all_initial_routers_until_successful_on_authorization_expired": "Add DNS resolver TestKit message and connection timeout support",
1715-
17161760
// To fix/to decide whether to fix
1717-
"stub.routing.*.*.test_should_successfully_acquire_rt_when_router_ip_changes": "Backend lacks custom DNS resolution and Go driver RT discovery differs.",
1718-
"stub.routing.test_routing_v*.RoutingV*.test_should_revert_to_initial_router_if_known_router_throws_protocol_errors": "Driver always uses configured URL first and custom resolver only if that fails",
1719-
"stub.routing.test_routing_v*.RoutingV*.test_should_read_successfully_from_reachable_db_after_trying_unreachable_db": "Driver retries to fetch a routing table up to 100 times if it's empty",
1720-
"stub.routing.test_routing_v*.RoutingV*.test_should_write_successfully_after_leader_switch_using_tx_run": "Driver retries to fetch a routing table up to 100 times if it's empty",
1721-
"stub.routing.test_routing_v*.RoutingV*.test_should_fail_when_writing_without_writers_using_session_run": "Driver retries to fetch a routing table up to 100 times if it's empty",
1722-
"stub.routing.test_routing_v*.RoutingV*.test_should_accept_routing_table_without_writers_and_then_rediscover": "Driver retries to fetch a routing table up to 100 times if it's empty",
1723-
"stub.routing.test_routing_v*.RoutingV*.test_should_fail_on_routing_table_with_no_reader": "Driver retries to fetch a routing table up to 100 times if it's empty",
1724-
"stub.routing.test_routing_v*.RoutingV*.test_should_fail_discovery_when_router_fails_with_unknown_code": "Unify: other drivers have a list of fast failing errors during discover: on anything else, the driver will try the next router",
1725-
"stub.routing.test_routing_v*.RoutingV*.test_should_drop_connections_failing_liveness_check": "Liveness check error handling is not (yet) unified: https://github.com/neo-technology/drivers-adr/pull/83",
1761+
"stub.routing.*.*.test_should_successfully_acquire_rt_when_router_ip_changes": "Backend lacks custom DNS resolution and Go driver RT discovery differs.",
1762+
"stub.routing.test_routing_v*.RoutingV*.test_should_revert_to_initial_router_if_known_router_throws_protocol_errors": "Driver always uses configured URL first and custom resolver only if that fails",
1763+
"stub.routing.test_routing_v*.RoutingV*.test_should_request_rt_from_all_initial_routers_until_successful_on_authorization_expired": "Driver always uses configured URL first and custom resolver only if that fails",
1764+
"stub.routing.test_routing_v*.RoutingV*test_should_request_rt_from_all_initial_routers_until_successful_on_unknown_failure": "Driver always uses configured URL first and custom resolver only if that fails",
1765+
"stub.routing.test_routing_v*.RoutingV*.test_should_read_successfully_from_reachable_db_after_trying_unreachable_db": "Driver retries to fetch a routing table up to 100 times if it's empty",
1766+
"stub.routing.test_routing_v*.RoutingV*.test_should_write_successfully_after_leader_switch_using_tx_run": "Driver retries to fetch a routing table up to 100 times if it's empty",
1767+
"stub.routing.test_routing_v*.RoutingV*.test_should_fail_when_writing_without_writers_using_session_run": "Driver retries to fetch a routing table up to 100 times if it's empty",
1768+
"stub.routing.test_routing_v*.RoutingV*.test_should_accept_routing_table_without_writers_and_then_rediscover": "Driver retries to fetch a routing table up to 100 times if it's empty",
1769+
"stub.routing.test_routing_v*.RoutingV*.test_should_fail_on_routing_table_with_no_reader": "Driver retries to fetch a routing table up to 100 times if it's empty",
1770+
"stub.routing.test_routing_v*.RoutingV*.test_should_fail_discovery_when_router_fails_with_unknown_code": "Unify: other drivers have a list of fast failing errors during discover: on anything else, the driver will try the next router",
1771+
"stub.routing.test_routing_v*.RoutingV*.test_should_drop_connections_failing_liveness_check": "Liveness check error handling is not (yet) unified: https://github.com/neo-technology/drivers-adr/pull/83",
17261772
"stub.*.test_0_timeout": "Fixme: driver omits 0 as tx timeout value",
17271773
"stub.summary.test_summary.TestSummaryBasicInfo.test_server_info": "pending unification: should the server address be pre or post DNS resolution?",
17281774
}
@@ -1810,13 +1856,13 @@ func (b *backend) consumeBookmarks(bookmarkManagerId string) func(context.Contex
18101856
"bookmarkManagerId": bookmarkManagerId,
18111857
"bookmarks": bookmarks,
18121858
})
1813-
for {
1814-
b.process()
1859+
for b.process() {
18151860
if _, found := b.consumedBookmarks[id]; found {
18161861
delete(b.consumedBookmarks, id)
1817-
return nil
1862+
break
18181863
}
18191864
}
1865+
return nil
18201866
}
18211867
}
18221868

0 commit comments

Comments
 (0)