Skip to content
Snippets Groups Projects
Commit 1eeca356 authored by richard.petersen's avatar richard.petersen :sailboat:
Browse files

Add optional flag for redis configuration

parent 8db6cabe
No related branches found
No related tags found
No related merge requests found
...@@ -6,6 +6,7 @@ baseUrls: ...@@ -6,6 +6,7 @@ baseUrls:
- http://main-core-ui.main-e2e-stack.svc.cluster.local - http://main-core-ui.main-e2e-stack.svc.cluster.local
redis: redis:
enabled: true
host: main-redis-master.main-e2e-stack.svc.cluster.local host: main-redis-master.main-e2e-stack.svc.cluster.local
prefix: ${CI_COMMIT_REF_SLUG}-${OX_COMPONENT} prefix: ${CI_COMMIT_REF_SLUG}-${OX_COMPONENT}
......
...@@ -27,6 +27,7 @@ spec: ...@@ -27,6 +27,7 @@ spec:
value: "{{ .Values.logLevel }}" value: "{{ .Values.logLevel }}"
- name: APP_ROOT - name: APP_ROOT
value: "{{ .Values.appRoot }}" value: "{{ .Values.appRoot }}"
{{- if .Values.redis.enabled }}
- name: REDIS_HOST - name: REDIS_HOST
value: "{{ required "redis.host required" .Values.redis.host }}" value: "{{ required "redis.host required" .Values.redis.host }}"
- name: REDIS_PORT - name: REDIS_PORT
...@@ -37,6 +38,7 @@ spec: ...@@ -37,6 +38,7 @@ spec:
value: "{{ .Values.redis.password }}" value: "{{ .Values.redis.password }}"
- name: REDIS_PREFIX - name: REDIS_PREFIX
value: "{{ .Values.redis.prefix }}" value: "{{ .Values.redis.prefix }}"
{{- end }}
ports: ports:
- name: http - name: http
containerPort: {{ .Values.containerPort | default 8080 }} containerPort: {{ .Values.containerPort | default 8080 }}
......
...@@ -105,6 +105,7 @@ baseUrls: [] ...@@ -105,6 +105,7 @@ baseUrls: []
appRoot: '/' appRoot: '/'
redis: redis:
enabled: false
host: '' host: ''
port: 6379 port: 6379
db: 0 db: 0
......
module.exports = { module.exports = {
spec: ['integration/**/*_test.js'], spec: ['integration/**/*_test.js'],
file: ['integration/global-setup.js']
} }
\ No newline at end of file
process.env.REDIS_HOST = 'localhost'
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
"start": "node src/index.js", "start": "node src/index.js",
"dev": "nodemon index.js", "dev": "nodemon index.js",
"prepare": "husky install", "prepare": "husky install",
"test": "LOG_LEVEL=error mocha --loader=testdouble --config spec/.mocharc.cjs", "test": "LOG_LEVEL=error mocha --loader=testdouble --config spec/.mocharc.cjs --exit",
"integration": "LOG_LEVEL=error mocha --loader=testdouble --config integration/.mocharc.cjs --exit", "integration": "LOG_LEVEL=error mocha --loader=testdouble --config integration/.mocharc.cjs --exit",
"release-chart": "cd helm/core-ui-middleware/ && npx --package=@open-xchange/release-it -- release-it", "release-chart": "cd helm/core-ui-middleware/ && npx --package=@open-xchange/release-it -- release-it",
"release-app": "npx --package=@open-xchange/release-it@latest -- release-it-auto-keepachangelog", "release-app": "npx --package=@open-xchange/release-it@latest -- release-it-auto-keepachangelog",
......
import request from 'supertest'
import { generateSimpleViteManifest, mockApp, mockConfig, mockFetch } from './util.js'
import { expect } from 'chai'
import * as td from 'testdouble'
import { Response } from 'node-fetch'
import sinon from 'sinon'
const sandbox = sinon.createSandbox()
describe('Redis', function () { describe('Redis', function () {
it('first instance updates version', async function () { let app
// TODO let spy
beforeEach(async function () {
// no redis mock!!
await import('../src/create-queues.js').then(({ default: createQueues }) => createQueues())
mockConfig({ urls: ['http://ui-server/'] })
mockFetch({
'http://ui-server': {
'/manifest.json': generateSimpleViteManifest({}),
'/example.js': spy = sandbox.spy(() => {
return new Response('this is example', { headers: { 'content-type': 'application/javascript' } })
})
}
})
app = await mockApp()
})
afterEach(async function () {
td.reset()
})
it('use internal cache, when redis is disabled', async function () {
expect(spy.callCount).to.equal(0)
let response = await request(app).get('/example.js')
expect(response.statusCode).to.equal(200)
expect(spy.callCount).to.equal(1)
response = await request(app).get('/example.js')
expect(response.statusCode).to.equal(200)
expect(spy.callCount).to.equal(1)
}) })
}) })
...@@ -42,9 +42,10 @@ export function mockFetch (servers = {}) { ...@@ -42,9 +42,10 @@ export function mockFetch (servers = {}) {
}) })
} }
export function mockRedis (data = {}) { export function mockRedis (data = {}, isEnabled = true) {
const mock = { const mock = {
isReady () { return Promise.resolve() }, isReady () { return Promise.resolve() },
isEnabled () { return isEnabled },
client: new RedisMock({ data }), client: new RedisMock({ data }),
pubClient: new RedisMock(), pubClient: new RedisMock(),
subClient: new RedisMock() subClient: new RedisMock()
......
import * as redis from './redis.js'
const cache = {}
export function set (key, value) {
if (cache[key] === value) return
cache[key] = value
if (redis.isEnabled()) {
return redis.client.set(key, value)
}
}
export async function getBuffer (key) {
return get(key, { method: 'getBuffer' })
}
export async function get (key, { method = 'get' } = {}) {
if (cache[key]) return cache[key]
if (redis.isEnabled()) {
const result = await redis.client[method]?.(key)
cache[key] = result
return result
}
}
import { getQueue, subClient } from './redis.js' import * as redis from './redis.js'
import { updateVersionProcessor, registerLatestVersionListener } from './version.js' import { updateVersionProcessor, registerLatestVersionListener } from './version.js'
const { getQueue, subClient } = redis
export default function createQueues () { export default function createQueues () {
const updateVersionQueue = getQueue('update-version') if (redis.isEnabled()) {
updateVersionQueue.process(updateVersionProcessor) const updateVersionQueue = getQueue('update-version')
updateVersionQueue.add({}, { updateVersionQueue.process(updateVersionProcessor)
jobId: 'update-version-job', updateVersionQueue.add({}, {
repeat: { every: Number(process.env.CACHE_TTL) }, jobId: 'update-version-job',
removeOnComplete: true repeat: { every: Number(process.env.CACHE_TTL) },
}) removeOnComplete: true
})
}
// not a queue but though, used by redis // not a queue but though, used by redis
registerLatestVersionListener(subClient) registerLatestVersionListener(subClient)
......
...@@ -3,12 +3,10 @@ import crypto from 'crypto' ...@@ -3,12 +3,10 @@ import crypto from 'crypto'
import { config } from './config.js' import { config } from './config.js'
import { getRedisKey, isJSFile } from './util.js' import { getRedisKey, isJSFile } from './util.js'
import { getCSSDependenciesFor, getViteManifests } from './manifests.js' import { getCSSDependenciesFor, getViteManifests } from './manifests.js'
import { client } from './redis.js' import * as cache from './cache.js'
import { logger } from './logger.js' import { logger } from './logger.js'
import { NotFoundError } from './errors.js' import { NotFoundError } from './errors.js'
const fileCache = {}
export async function fetchFileWithHeadersFromBaseUrl (path, baseUrl, version) { export async function fetchFileWithHeadersFromBaseUrl (path, baseUrl, version) {
const [response, dependencies] = await Promise.all([ const [response, dependencies] = await Promise.all([
fetch(new URL(path, baseUrl)), fetch(new URL(path, baseUrl)),
...@@ -56,27 +54,17 @@ export async function fetchFileWithHeaders ({ path, version }) { ...@@ -56,27 +54,17 @@ export async function fetchFileWithHeaders ({ path, version }) {
export async function saveToCache ({ version, path, body, headers, ...rest }) { export async function saveToCache ({ version, path, body, headers, ...rest }) {
if (typeof body !== 'string' && !(body instanceof Buffer)) body = JSON.stringify(body) if (typeof body !== 'string' && !(body instanceof Buffer)) body = JSON.stringify(body)
fileCache[version] = fileCache[version] || {}
fileCache[version][path] = {
body,
headers,
...rest
}
return Promise.all([ return Promise.all([
client.set(getRedisKey({ version, name: `${path}:body` }), body), cache.set(getRedisKey({ version, name: `${path}:body` }), body),
client.set(getRedisKey({ version, name: `${path}:meta` }), JSON.stringify({ headers, ...rest })) cache.set(getRedisKey({ version, name: `${path}:meta` }), JSON.stringify({ headers, ...rest }))
]) ])
} }
export async function loadFromCache ({ version, path }) { export async function loadFromCache ({ version, path }) {
if (!fileCache[version]?.[path]) { const [body, meta = '{}'] = await Promise.all([
const [body, meta = '{}'] = await Promise.all([ cache.getBuffer(getRedisKey({ version, name: `${path}:body` })),
client.getBuffer(getRedisKey({ version, name: `${path}:body` })), cache.get(getRedisKey({ version, name: `${path}:meta` }))
client.get(getRedisKey({ version, name: `${path}:meta` })) ])
]) if (!body) return
if (!body) return return { ...JSON.parse(meta), body }
fileCache[version] = fileCache[version] || {}
fileCache[version][path] = { ...JSON.parse(meta), body }
}
return fileCache[version][path]
} }
...@@ -2,10 +2,10 @@ import fetch from 'node-fetch' ...@@ -2,10 +2,10 @@ import fetch from 'node-fetch'
import { config } from './config.js' import { config } from './config.js'
import { getRedisKey, viteManifestToDeps, viteToOxManifest } from './util.js' import { getRedisKey, viteManifestToDeps, viteToOxManifest } from './util.js'
import { logger } from './logger.js' import { logger } from './logger.js'
import { client } from './redis.js' import * as cache from './cache.js'
export async function getViteManifests ({ version }) { export async function getViteManifests ({ version }) {
const manifests = await client.get(getRedisKey({ version, name: 'viteManifests' })) const manifests = await cache.get(getRedisKey({ version, name: 'viteManifests' }))
if (manifests) return JSON.parse(manifests) if (manifests) return JSON.parse(manifests)
await config.load() await config.load()
...@@ -30,27 +30,27 @@ export async function getViteManifests ({ version }) { ...@@ -30,27 +30,27 @@ export async function getViteManifests ({ version }) {
// combine all manifests by keys. With duplicates, last wins // combine all manifests by keys. With duplicates, last wins
const newManifests = viteManifests.reduce((memo, manifest) => Object.assign(memo, manifest), {}) const newManifests = viteManifests.reduce((memo, manifest) => Object.assign(memo, manifest), {})
await client.set(getRedisKey({ version, name: 'viteManifests' }), JSON.stringify(newManifests)) await cache.set(getRedisKey({ version, name: 'viteManifests' }), JSON.stringify(newManifests))
return newManifests return newManifests
} }
export async function getOxManifests ({ version }) { export async function getOxManifests ({ version }) {
const manifests = await client.get(getRedisKey({ version, name: 'oxManifests' })) const manifests = await cache.get(getRedisKey({ version, name: 'oxManifests' }))
if (manifests) return JSON.parse(manifests) if (manifests) return JSON.parse(manifests)
const viteManifests = await getViteManifests({ version }) const viteManifests = await getViteManifests({ version })
const newManifests = viteToOxManifest(viteManifests) const newManifests = viteToOxManifest(viteManifests)
await client.set(getRedisKey({ version, name: 'oxManifests' }), JSON.stringify(newManifests)) await cache.set(getRedisKey({ version, name: 'oxManifests' }), JSON.stringify(newManifests))
return newManifests return newManifests
} }
export async function getDependencies ({ version }) { export async function getDependencies ({ version }) {
const deps = await client.get(getRedisKey({ version, name: 'dependencies' })) const deps = await cache.get(getRedisKey({ version, name: 'dependencies' }))
if (deps) return JSON.parse(deps) if (deps) return JSON.parse(deps)
const viteManifests = await getViteManifests({ version }) const viteManifests = await getViteManifests({ version })
const newDeps = viteManifestToDeps(viteManifests) const newDeps = viteManifestToDeps(viteManifests)
await client.set(getRedisKey({ version, name: 'dependencies' }), JSON.stringify(newDeps)) await cache.set(getRedisKey({ version, name: 'dependencies' }), JSON.stringify(newDeps))
return newDeps return newDeps
} }
......
...@@ -5,6 +5,14 @@ import Queue from 'bull' ...@@ -5,6 +5,14 @@ import Queue from 'bull'
const commonQueueOptions = { enableReadyCheck: false, maxRetriesPerRequest: null } const commonQueueOptions = { enableReadyCheck: false, maxRetriesPerRequest: null }
const createClient = (type, options = {}) => { const createClient = (type, options = {}) => {
if (!isEnabled()) {
return new Proxy({}, {
get () {
throw new Error('Redis is disabled. Do not use it.')
}
})
}
const client = new Redis({ const client = new Redis({
host: process.env.REDIS_HOST, host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT || 6379, port: process.env.REDIS_PORT || 6379,
...@@ -64,3 +72,7 @@ export async function closeQueue (name) { ...@@ -64,3 +72,7 @@ export async function closeQueue (name) {
} }
return queue.close() return queue.close()
} }
export function isEnabled () {
return !!process.env.REDIS_HOST
}
...@@ -7,8 +7,10 @@ import { once } from '../util.js' ...@@ -7,8 +7,10 @@ import { once } from '../util.js'
const router = Router() const router = Router()
const healthCheck = new health.HealthChecker() const healthCheck = new health.HealthChecker()
const redisReady = new health.ReadinessCheck('Redis ready', redis.isReady) if (redis.isEnabled()) {
healthCheck.registerReadinessCheck(redisReady) const redisReady = new health.ReadinessCheck('Redis ready', redis.isReady)
healthCheck.registerReadinessCheck(redisReady)
}
const startupCheck = new health.StartupCheck('check latest version', once(async function () { const startupCheck = new health.StartupCheck('check latest version', once(async function () {
await getLatestVersion() await getLatestVersion()
......
...@@ -2,7 +2,7 @@ import fetch from 'node-fetch' ...@@ -2,7 +2,7 @@ import fetch from 'node-fetch'
import { config } from './config.js' import { config } from './config.js'
import { getRedisKey, hash } from './util.js' import { getRedisKey, hash } from './util.js'
import { logger } from './logger.js' import { logger } from './logger.js'
import { client, pubClient } from './redis.js' import * as redis from './redis.js'
let latestVersion let latestVersion
...@@ -34,30 +34,40 @@ export const fetchLatestVersion = async () => { ...@@ -34,30 +34,40 @@ export const fetchLatestVersion = async () => {
export async function getLatestVersion () { export async function getLatestVersion () {
if (latestVersion) return latestVersion if (latestVersion) return latestVersion
const version = await client.get(getRedisKey({ name: 'latestVersion' })) if (redis.isEnabled()) {
if (version) return (latestVersion = version) const version = await redis.client.get(getRedisKey({ name: 'latestVersion' }))
if (version) return (latestVersion = version)
}
const newVersion = await fetchLatestVersion() const newVersion = await fetchLatestVersion()
pubClient.publish(getRedisKey({ name: 'updateLatestVersion' }), newVersion)
await client.set(getRedisKey({ name: 'latestVersion' }), newVersion) if (redis.isEnabled()) {
redis.pubClient.publish(getRedisKey({ name: 'updateLatestVersion' }), newVersion)
await redis.client.set(getRedisKey({ name: 'latestVersion' }), newVersion)
}
return (latestVersion = newVersion) return (latestVersion = newVersion)
} }
export function registerLatestVersionListener (client) { export function registerLatestVersionListener (client) {
const key = getRedisKey({ name: 'updateLatestVersion' }) if (redis.isEnabled()) {
client.subscribe(key, (errs, count) => logger.info(`Subscribed to ${key}.`)) const key = getRedisKey({ name: 'updateLatestVersion' })
client.on('message', (channel, message) => { client.subscribe(key, (errs, count) => logger.info(`Subscribed to ${key}.`))
if (channel === key) latestVersion = message client.on('message', (channel, message) => {
}) if (channel === key) latestVersion = message
})
} else {
setInterval(updateVersionProcessor, Number(process.env.CACHE_TTL))
}
} }
export async function updateVersionProcessor (job) { export async function updateVersionProcessor () {
const [storedVersion, fetchedVersion] = await Promise.all([ const [storedVersion, fetchedVersion] = await Promise.all([
getLatestVersion(), getLatestVersion(),
fetchLatestVersion() fetchLatestVersion()
]) ])
if (storedVersion === fetchedVersion) return fetchedVersion if (storedVersion === fetchedVersion) return fetchedVersion
pubClient.publish(getRedisKey({ name: 'updateLatestVersion' }), fetchedVersion) redis.pubClient.publish(getRedisKey({ name: 'updateLatestVersion' }), fetchedVersion)
await client.set(getRedisKey({ name: 'latestVersion' }), fetchedVersion) await redis.client.set(getRedisKey({ name: 'latestVersion' }), fetchedVersion)
return fetchedVersion return fetchedVersion
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment