Skip to content

Commit 4441857

Browse files
bigmontzrobsdedude
andauthored
Surface more errors on failing to initially retrieve a RT (#811)
Co-authored-by: Robsdedude <dev@rouvenbauer.de>
1 parent 8696921 commit 4441857

File tree

6 files changed

+176
-102
lines changed

6 files changed

+176
-102
lines changed

packages/bolt-connection/src/connection-provider/connection-provider-routing.js

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,15 @@ const {
4141
}
4242
} = internal
4343

44-
const UNAUTHORIZED_ERROR_CODE = 'Neo.ClientError.Security.Unauthorized'
45-
const DATABASE_NOT_FOUND_ERROR_CODE =
46-
'Neo.ClientError.Database.DatabaseNotFound'
44+
45+
const PROCEDURE_NOT_FOUND_CODE = 'Neo.ClientError.Procedure.ProcedureNotFound'
46+
const DATABASE_NOT_FOUND_CODE = 'Neo.ClientError.Database.DatabaseNotFound'
47+
const INVALID_BOOKMARK_CODE = 'Neo.ClientError.Transaction.InvalidBookmark'
48+
const INVALID_BOOKMARK_MIXTURE_CODE =
49+
'Neo.ClientError.Transaction.InvalidBookmarkMixture'
50+
const AUTHORIZATION_EXPIRED_CODE =
51+
'Neo.ClientError.Security.AuthorizationExpired'
52+
4753
const SYSTEM_DB_NAME = 'system'
4854
const DEFAULT_DB_NAME = null
4955
const DEFAULT_ROUTING_TABLE_PURGE_DELAY = int(30000)
@@ -482,14 +488,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
482488
impersonatedUser
483489
)
484490
} catch (error) {
485-
if (error && error.code === DATABASE_NOT_FOUND_ERROR_CODE) {
486-
// not finding the target database is a sign of a configuration issue
487-
throw error
488-
}
489-
this._log.warn(
490-
`unable to fetch routing table because of an error ${error}`
491-
)
492-
return null
491+
return this._handleRediscoveryError(error, currentRouter)
493492
} finally {
494493
session.close()
495494
}
@@ -532,14 +531,24 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
532531
impersonatedUser
533532
})
534533
} catch (error) {
535-
// unable to acquire connection towards the given router
536-
if (error && error.code === UNAUTHORIZED_ERROR_CODE) {
537-
// auth error and not finding system database is a sign of a configuration issue
538-
// discovery should not proceed
539-
throw error
540-
}
541-
return null
534+
return this._handleRediscoveryError(error, routerAddress)
535+
}
536+
}
537+
538+
_handleRediscoveryError(error, routerAddress) {
539+
if (_isFailFastError(error) || _isFailFastSecurityError(error)) {
540+
throw error
541+
} else if (error.code === PROCEDURE_NOT_FOUND_CODE) {
542+
// throw when getServers procedure not found because this is clearly a configuration issue
543+
throw newError(
544+
`Server at ${routerAddress.asHostPort()} can't perform routing. Make sure you are connecting to a causal cluster`,
545+
SERVICE_UNAVAILABLE
546+
)
542547
}
548+
this._log.warn(
549+
`unable to fetch routing table because of an error ${error}`
550+
)
551+
return null
543552
}
544553

545554
async _applyRoutingTableIfPossible (currentRoutingTable, newRoutingTable, onDatabaseNameResolved) {
@@ -676,3 +685,19 @@ class RoutingTableRegistry {
676685
return this
677686
}
678687
}
688+
689+
function _isFailFastError (error) {
690+
return [
691+
DATABASE_NOT_FOUND_CODE,
692+
INVALID_BOOKMARK_CODE,
693+
INVALID_BOOKMARK_MIXTURE_CODE,
694+
].includes(error.code)
695+
}
696+
697+
function _isFailFastSecurityError (error) {
698+
return error.code.startsWith('Neo.ClientError.Security.') &&
699+
![
700+
AUTHORIZATION_EXPIRED_CODE,
701+
].includes(error.code)
702+
}
703+

packages/bolt-connection/src/rediscovery/rediscovery.js

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,7 @@
1717
* limitations under the License.
1818
*/
1919
import RoutingTable from './routing-table'
20-
import { RawRoutingTable } from '../bolt'
21-
import { newError, error, Session } from 'neo4j-driver-core'
22-
23-
const { SERVICE_UNAVAILABLE } = error
24-
const PROCEDURE_NOT_FOUND_CODE = 'Neo.ClientError.Procedure.ProcedureNotFound'
25-
const DATABASE_NOT_FOUND_CODE = 'Neo.ClientError.Database.DatabaseNotFound'
20+
import { Session } from 'neo4j-driver-core'
2621

2722
export default class Rediscovery {
2823
/**
@@ -75,23 +70,7 @@ export default class Rediscovery {
7570
afterComplete: session._onComplete
7671
},
7772
onCompleted: resolve,
78-
onError: error => {
79-
if (error.code === DATABASE_NOT_FOUND_CODE) {
80-
reject(error)
81-
} else if (error.code === PROCEDURE_NOT_FOUND_CODE) {
82-
// throw when getServers procedure not found because this is clearly a configuration issue
83-
reject(
84-
newError(
85-
`Server at ${routerAddress.asHostPort()} can't perform routing. Make sure you are connecting to a causal cluster`,
86-
SERVICE_UNAVAILABLE
87-
)
88-
)
89-
} else {
90-
// return nothing when failed to connect because code higher in the callstack is still able to retry with a
91-
// different session towards a different router
92-
resolve(RawRoutingTable.ofNull())
93-
}
94-
}
73+
onError: reject,
9574
})
9675
})
9776
}

packages/bolt-connection/test/connection-provider/connection-provider-routing.test.js

Lines changed: 106 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1658,6 +1658,86 @@ describe('#unit RoutingConnectionProvider', () => {
16581658
})
16591659
}, 10000)
16601660

1661+
describe('when rediscovery.lookupRoutingTableOnRouter fails', () => {
1662+
describe.each([
1663+
'Neo.ClientError.Database.DatabaseNotFound',
1664+
'Neo.ClientError.Transaction.InvalidBookmark',
1665+
'Neo.ClientError.Transaction.InvalidBookmarkMixture',
1666+
'Neo.ClientError.Security.Forbidden',
1667+
'Neo.ClientError.Security.IWontTellYou'
1668+
])('with "%s"', errorCode => {
1669+
it('should fail without changing the error ', async () => {
1670+
const error = newError('something wrong', errorCode)
1671+
const connectionProvider = newRoutingConnectionProviderWithFakeRediscovery(
1672+
new FakeRediscovery(null, error),
1673+
newPool()
1674+
)
1675+
1676+
let completed = false
1677+
try {
1678+
await connectionProvider.acquireConnection({ accessMode: READ })
1679+
completed = true
1680+
} catch (capturedError) {
1681+
expect(capturedError).toBe(error)
1682+
}
1683+
1684+
expect(completed).toBe(false)
1685+
})
1686+
})
1687+
1688+
describe('with "Neo.ClientError.Procedure.ProcedureNotFound"', () => {
1689+
it('should fail with SERVICE_UNAVAILABLE and a message about the need for a causal cluster', async () => {
1690+
const error = newError('something wrong', 'Neo.ClientError.Procedure.ProcedureNotFound')
1691+
const connectionProvider = newRoutingConnectionProviderWithFakeRediscovery(
1692+
new FakeRediscovery(null, error),
1693+
newPool()
1694+
)
1695+
1696+
let completed = false
1697+
try {
1698+
await connectionProvider.acquireConnection({ accessMode: READ })
1699+
completed = true
1700+
} catch (capturedError) {
1701+
expect(capturedError.code).toBe(SERVICE_UNAVAILABLE)
1702+
expect(capturedError.message).toBe(
1703+
'Server at server-non-existing-seed-router:7687 can\'t '
1704+
+ 'perform routing. Make sure you are connecting to a causal cluster'
1705+
)
1706+
}
1707+
1708+
expect(completed).toBe(false)
1709+
})
1710+
})
1711+
1712+
describe.each([
1713+
'Neo.ClientError.Security.AuthorizationExpired',
1714+
'Neo.ClientError.MadeUp.Error'
1715+
])('with "%s"', errorCode => {
1716+
it('should fail with SERVICE_UNAVAILABLE and message about no routing servers available', async () => {
1717+
const error = newError('something wrong', errorCode)
1718+
const connectionProvider = newRoutingConnectionProviderWithFakeRediscovery(
1719+
new FakeRediscovery(null, error),
1720+
newPool()
1721+
)
1722+
1723+
let completed = false
1724+
try {
1725+
await connectionProvider.acquireConnection({ accessMode: READ })
1726+
completed = true
1727+
} catch (capturedError) {
1728+
expect(capturedError.code).toBe(SERVICE_UNAVAILABLE)
1729+
expect(capturedError.message).toEqual(expect.stringContaining(
1730+
'Could not perform discovery. ' +
1731+
'No routing servers available. Known routing table: '
1732+
))
1733+
}
1734+
1735+
expect(completed).toBe(false)
1736+
})
1737+
1738+
})
1739+
})
1740+
16611741
describe('multi-database', () => {
16621742
it.each(usersDataSet)('should acquire read connection from correct routing table [user=%s]', async (user) => {
16631743
const pool = newPool()
@@ -2843,13 +2923,31 @@ function newRoutingConnectionProvider (
28432923
)
28442924
}
28452925

2926+
function newRoutingConnectionProviderWithFakeRediscovery (
2927+
fakeRediscovery,
2928+
pool = null,
2929+
routerToRoutingTable = { null: {} }
2930+
) {
2931+
const seedRouter = ServerAddress.fromUrl('server-non-existing-seed-router')
2932+
return newRoutingConnectionProviderWithSeedRouter(
2933+
seedRouter,
2934+
[seedRouter],
2935+
[],
2936+
routerToRoutingTable,
2937+
pool,
2938+
null,
2939+
fakeRediscovery
2940+
)
2941+
}
2942+
28462943
function newRoutingConnectionProviderWithSeedRouter (
28472944
seedRouter,
28482945
seedRouterResolved,
28492946
routingTables,
28502947
routerToRoutingTable = { null: {} },
28512948
connectionPool = null,
2852-
routingTablePurgeDelay = null
2949+
routingTablePurgeDelay = null,
2950+
fakeRediscovery = null
28532951
) {
28542952
const pool = connectionPool || newPool()
28552953
const connectionProvider = new RoutingConnectionProvider({
@@ -2865,7 +2963,8 @@ function newRoutingConnectionProviderWithSeedRouter (
28652963
routingTables.forEach(r => {
28662964
connectionProvider._routingTableRegistry.register(r)
28672965
})
2868-
connectionProvider._rediscovery = new FakeRediscovery(routerToRoutingTable)
2966+
connectionProvider._rediscovery =
2967+
fakeRediscovery || new FakeRediscovery(routerToRoutingTable)
28692968
connectionProvider._hostNameResolver = new FakeDnsResolver(seedRouterResolved)
28702969
connectionProvider._useSeedRouter = routingTables.every(
28712970
r => r.expirationTime !== Integer.ZERO
@@ -2999,11 +3098,15 @@ class FakeConnection extends Connection {
29993098
}
30003099

30013100
class FakeRediscovery {
3002-
constructor (routerToRoutingTable) {
3101+
constructor (routerToRoutingTable, error) {
30033102
this._routerToRoutingTable = routerToRoutingTable
3103+
this._error = error
30043104
}
30053105

30063106
lookupRoutingTableOnRouter (ignored, database, router, user) {
3107+
if (this._error) {
3108+
return Promise.reject(this._error)
3109+
}
30073110
const table = this._routerToRoutingTable[database || null]
30083111
if (table) {
30093112
let routingTables = table[router.asKey()]

packages/bolt-connection/test/rediscovery/rediscovery.test.js

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -136,48 +136,6 @@ describe('#unit Rediscovery', () => {
136136
}
137137
})
138138

139-
it('should reject with PROCEDURE_NOT_FOUND_CODE when it happens ', async () => {
140-
const routerAddress = ServerAddress.fromUrl('bolt://localhost:1235')
141-
const expectedError = newError(
142-
`Server at ${routerAddress.asHostPort()} can't perform routing. Make sure you are connecting to a causal cluster`,
143-
SERVICE_UNAVAILABLE
144-
)
145-
try {
146-
const initialAddress = '127.0.0.1'
147-
const routingContext = { context: '1234 ' }
148-
149-
const connection = new FakeConnection().withRequestRoutingInformationMock(
150-
fakeOnError(newError('1de', PROCEDURE_NOT_FOUND_CODE))
151-
)
152-
await lookupRoutingTableOnRouter({
153-
initialAddress,
154-
routingContext,
155-
connection,
156-
routerAddress
157-
})
158-
159-
fail('it should fail')
160-
} catch (error) {
161-
expect(error).toEqual(expectedError)
162-
}
163-
})
164-
165-
it('should return null when it happens an unexpected error ocorrus', async () => {
166-
const initialAddress = '127.0.0.1'
167-
const routingContext = { context: '1234 ' }
168-
169-
const connection = new FakeConnection().withRequestRoutingInformationMock(
170-
fakeOnError(newError('1de', 'abc'))
171-
)
172-
const routingTable = await lookupRoutingTableOnRouter({
173-
initialAddress,
174-
routingContext,
175-
connection
176-
})
177-
178-
expect(routingTable).toEqual(null)
179-
})
180-
181139
it('should throw PROTOCOL_ERROR if the routing table is invalid', async () => {
182140
runWithClockAt(Date.now(), async () => {
183141
try {

packages/neo4j-driver/test/driver.test.js

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -398,14 +398,23 @@ describe('#integration driver', () => {
398398
)
399399
const session = driver.session()
400400

401-
await expectAsync(session.run('RETURN 1')).toBeRejectedWith(
402-
jasmine.objectContaining({
403-
code: neo4j.error.SERVICE_UNAVAILABLE,
404-
message: jasmine.stringMatching(/No routing servers available/)
405-
})
406-
)
401+
jasmine.objectContaining('')
407402

408-
await session.close()
403+
let completed = false
404+
try {
405+
await session.run('RETURN 1')
406+
completed = true
407+
} catch (error) {
408+
expect(error.code).toEqual(neo4j.error.SERVICE_UNAVAILABLE)
409+
expect(error.message).toEqual(
410+
`Server at ${sharedNeo4j.hostname}:7687 can't perform routing. ` +
411+
'Make sure you are connecting to a causal cluster'
412+
)
413+
} finally {
414+
await session.close()
415+
}
416+
417+
expect(completed).toBe(false)
409418
})
410419

411420
it('should have correct user agent', async () => {

packages/testkit-backend/src/skipped-tests/common.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,6 @@ const skippedTests = [
2121
ifEquals('neo4j.test_session_run.TestSessionRun.test_iteration_smaller_than_fetch_size'),
2222
ifEquals('neo4j.test_tx_func_run.TestTxFuncRun.test_tx_func_configuration')
2323
),
24-
skip(
25-
'Fail while enable Temporary:FastFailingDiscovery',
26-
ifEndsWith('test_should_request_rt_from_all_initial_routers_until_successful_on_authorization_expired'),
27-
ifEndsWith('test_should_request_rt_from_all_initial_routers_until_successful_on_unknown_failure'),
28-
ifEndsWith('test_should_fail_with_routing_failure_on_any_security_discovery_failure'),
29-
ifEndsWith('test_should_fail_with_routing_failure_on_invalid_bookmark_mixture_discovery_failure'),
30-
ifEndsWith('test_should_fail_with_routing_failure_on_invalid_bookmark_discovery_failure'),
31-
ifEndsWith('test_should_fail_with_routing_failure_on_forbidden_discovery_failure')
32-
),
3324
skip('Flacky because sometimes the connection is actually available',
3425
ifEndsWith('test_should_enforce_pool_size_per_cluster_member')
3526
),
@@ -108,6 +99,15 @@ const skippedTests = [
10899
'test_should_use_resolver_during_rediscovery_when_existing_routers_fail'
109100
)
110101
),
102+
skip(
103+
'Needs to implement "domain_name_resolver_fn"',
104+
ifEndsWith(
105+
'test_should_request_rt_from_all_initial_routers_until_successful_on_unknown_failure',
106+
),
107+
ifEndsWith(
108+
'test_should_request_rt_from_all_initial_routers_until_successful_on_authorization_expired'
109+
)
110+
),
111111
skip(
112112
'Needs investigation. It is only failing in the RoutingV3 case',
113113
ifEndsWith(

0 commit comments

Comments
 (0)