From 5a9b31a50cf974a122801c077a64cf641c964eff Mon Sep 17 00:00:00 2001 From: rakis Date: Sat, 18 Jan 2025 09:44:52 -0500 Subject: [PATCH] feat(ur): enable config to route From-Module --- servers/ur/src/config.js | 5 ++- servers/ur/src/domain.js | 43 ++++++++++++++++++++-- servers/ur/src/domain.test.js | 68 +++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 3 deletions(-) diff --git a/servers/ur/src/config.js b/servers/ur/src/config.js index e269e13fb..d654bc9a3 100644 --- a/servers/ur/src/config.js +++ b/servers/ur/src/config.js @@ -33,6 +33,7 @@ const serverConfigSchema = z.object({ }, z.number().positive()), processToHost: stringifiedJsonSchema.nullish(), ownerToHost: stringifiedJsonSchema.nullish(), + fromModuleToHost: stringifiedJsonSchema.nullish(), hosts: z.preprocess( (arg) => (typeof arg === 'string' ? arg.split(',').map(str => str.trim()) : arg), z.array(z.string().url()) @@ -68,6 +69,7 @@ const CONFIG_ENVS = { hosts: process.env.HOSTS || ['http://127.0.0.1:3005'], processToHost: process.env.PROCESS_TO_HOST || JSON.stringify({}), ownerToHost: process.env.OWNER_TO_HOST || JSON.stringify({}), + fromModuleToHost: process.env.FROM_MODULE_TO_HOST || JSON.stringify({}), /** * default to the CU for no hassle startup in development mode, @@ -95,7 +97,8 @@ const CONFIG_ENVS = { hosts: process.env.HOSTS, processToHost: process.env.PROCESS_TO_HOST || JSON.stringify({}), ownerToHost: process.env.OWNER_TO_HOST || JSON.stringify({}), - + fromModuleToHost: process.env.FROM_MODULE_TO_HOST || JSON.stringify({}), + aoUnit: process.env.AO_UNIT, strategy: process.env.STRATEGY || 'proxy', diff --git a/servers/ur/src/domain.js b/servers/ur/src/domain.js index 1f8d3f77b..3e3ccff86 100644 --- a/servers/ur/src/domain.js +++ b/servers/ur/src/domain.js @@ -1,9 +1,9 @@ -import { defaultTo, isEmpty, complement, path } from 'ramda' +import { defaultTo, isEmpty, complement, path, propEq } from 'ramda' import { LRUCache } from 'lru-cache' const isNotEmpty = complement(isEmpty) -export function bailoutWith ({ fetch, subrouterUrl, surUrl, owners, processToHost, ownerToHost }) { +export function bailoutWith ({ fetch, subrouterUrl, surUrl, owners, processToHost, ownerToHost, fromModuleToHost }) { const processToOwnerCache = new LRUCache({ /** * 10MB @@ -15,6 +15,17 @@ export function bailoutWith ({ fetch, subrouterUrl, surUrl, owners, processToHos sizeCalculation: () => 8 }) + const fromModuleCache = new LRUCache({ + /** + * 10MB + */ + maxSize: 10_000_000, + /** + * A number is 8 bytes + */ + sizeCalculation: () => 8 + }) + async function findProcessOwner (processId) { const owner = processToOwnerCache.get(processId) if (owner) return owner @@ -32,6 +43,25 @@ export function bailoutWith ({ fetch, subrouterUrl, surUrl, owners, processToHos .catch((_e) => null) } + async function findFromModule (processId) { + const module = fromModuleCache.get(processId) + if (module) return module + + return fetch(`${surUrl}/processes/${processId}`) + .then((res) => res.json()) + .then(defaultTo({})) + .then(p => { + return p.tags.find(propEq('From-Module', 'name'))?.value + }) + .then((module) => { + if (!module) return null + + fromModuleCache.set(processId, module) + return module + }) + .catch((_e) => null) + } + return async (processId) => { /** * If a process has a specific mapping configured, @@ -47,6 +77,15 @@ export function bailoutWith ({ fetch, subrouterUrl, surUrl, owners, processToHos if (ownerToHost[owner]) return ownerToHost[owner] } + /** + * If there are fromModule -> host configured, then we lookup the + * from-module and return the specific host if found + */ + if (fromModuleToHost && isNotEmpty(fromModuleToHost)) { + const module = await findFromModule(processId) + if (fromModuleToHost[module]) return fromModuleToHost[module] + } + /** * @deprecated - this functionality is subsumed by ownerToHost * and will eventually be removed diff --git a/servers/ur/src/domain.test.js b/servers/ur/src/domain.test.js index 4bda0de1f..b614effa9 100644 --- a/servers/ur/src/domain.test.js +++ b/servers/ur/src/domain.test.js @@ -38,6 +38,74 @@ describe('domain', () => { }) }) + describe('fromModuleToHost', () => { + test('should bailout if the process fromModule is mapped to a specific host', async () => { + const fetchMock = async (url) => { + assert.equal(url, 'surUrl1/processes/process-123') + return new Response(JSON.stringify({ owner: { address: 'owner2' }, tags: [{name:'From-Module', value: 'fromModule1'}] })) + } + + const bailout = bailoutWith({ + fetch: fetchMock, + surUrl: 'surUrl1', + fromModuleToHost: { fromModule1: 'https://specific_owner.host' } + }) + + const determineHost = determineHostWith({ hosts: HOSTS, bailout }) + + const host = await determineHost({ processId: 'process-123', failoverAttempt: 0 }) + assert.equal(host, 'https://specific_owner.host') + }) + + test('should NOT bailout if the process owner is not mapped to a specific host', async () => { + const fetchMock = async (url) => { + assert.equal(url, 'surUrl1/processes/process-123') + return new Response(JSON.stringify({ owner: { address: 'owner2' }, tags: [{name:'From-Module', value: 'fromModule1'}] })) + } + + const bailout = bailoutWith({ + fetch: fetchMock, + surUrl: 'surUrl1', + fromModuleToHost: { notFromModule1: 'https://specific_owner.host' } + }) + + const determineHost = determineHostWith({ hosts: HOSTS, bailout }) + + const host = await determineHost({ processId: 'process-123', failoverAttempt: 0 }) + assert.ok(host !== 'https://specific_owner.host') + assert.ok(HOSTS.includes(host)) + }) + + test('should NOT bailout if no fromModuleToHost is provided', async () => { + const fetchMock = async (url) => { + assert.equal(url, 'surUrl1/processes/process-123') + return new Response(JSON.stringify({ owner: { address: 'owner2' }, tags: [{name:'From-Module', value: 'fromModule1'}] })) + } + + const determineHostEmptyMapping = determineHostWith({ + hosts: HOSTS, + bailout: bailoutWith({ + fetch: fetchMock, + surUrl: 'surUrl1', + fromModuleToHost: {} + }) + }) + const host = await determineHostEmptyMapping({ processId: 'process-123', failoverAttempt: 0 }) + assert.ok(HOSTS.includes(host)) + + const determineHostNoMapping = determineHostWith({ + hosts: HOSTS, + bailout: bailoutWith({ + fetch: fetchMock, + surUrl: 'surUrl1', + ownerToHost: undefined + }) + }) + const host1 = await determineHostNoMapping({ processId: 'process-123', failoverAttempt: 0 }) + assert.ok(HOSTS.includes(host1)) + }) + }) + describe('ownerToHost', () => { test('should bailout if the process owner is mapped to a specific host', async () => { const fetchMock = async (url) => {