Skip to content

Commit

Permalink
meta: use load balancer for companion in e2e tests (#4228)
Browse files Browse the repository at this point in the history
  • Loading branch information
mifi committed Feb 2, 2023
1 parent 29ad1b6 commit e966fa7
Show file tree
Hide file tree
Showing 14 changed files with 205 additions and 97 deletions.
4 changes: 2 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -526,8 +526,8 @@ module.exports = {
extends: ['plugin:cypress/recommended'],
},
{
files: ['e2e/**/*.ts', 'e2e/**/*.js', 'e2e/**/*.jsx'],
rules: { 'import/no-extraneous-dependencies': 'off', 'no-unused-expressions': 'off' },
files: ['e2e/**/*.ts', 'e2e/**/*.js', 'e2e/**/*.jsx', 'e2e/**/*.mjs'],
rules: { 'import/no-extraneous-dependencies': 'off', 'no-unused-expressions': 'off', 'no-console': 'off' },
},
],
}
19 changes: 14 additions & 5 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: lts/*

- name: Start Redis
uses: supercharge/redis-github-action@1.4.0
with:
redis-version: 7

- name: Install dependencies
run: corepack yarn install --immutable
env:
Expand All @@ -72,18 +78,21 @@ jobs:
- name: Run end-to-end browser tests
run: corepack yarn run e2e:ci
env:
COMPANION_DATADIR: ./output
COMPANION_DOMAIN: localhost:3020
COMPANION_PROTOCOL: http
COMPANION_REDIS_URL: redis://localhost:6379
COMPANION_UNSPLASH_KEY: ${{secrets.COMPANION_UNSPLASH_KEY}}
COMPANION_UNSPLASH_SECRET: ${{secrets.COMPANION_UNSPLASH_SECRET}}
COMPANION_AWS_KEY: ${{secrets.COMPANION_AWS_KEY}}
COMPANION_AWS_SECRET: ${{secrets.COMPANION_AWS_SECRET}}
COMPANION_AWS_BUCKET: ${{secrets.COMPANION_AWS_BUCKET}}
COMPANION_AWS_REGION: ${{secrets.COMPANION_AWS_REGION}}
VITE_COMPANION_URL: http://localhost:3020
VITE_TRANSLOADIT_KEY: ${{secrets.TRANSLOADIT_KEY}}
VITE_TRANSLOADIT_SECRET: ${{secrets.TRANSLOADIT_SECRET}}
VITE_TRANSLOADIT_TEMPLATE: ${{secrets.TRANSLOADIT_TEMPLATE}}
VITE_TRANSLOADIT_SERVICE_URL: ${{secrets.TRANSLOADIT_SERVICE_URL}}
COMPANION_AWS_KEY: ${{secrets.COMPANION_AWS_KEY}}
COMPANION_AWS_SECRET: ${{secrets.COMPANION_AWS_SECRET}}
COMPANION_AWS_BUCKET: ${{secrets.COMPANION_AWS_BUCKET}}
COMPANION_AWS_REGION: ${{secrets.COMPANION_AWS_REGION}}
COMPANION_AWS_DISABLE_ACL: 'true'
# https://docs.cypress.io/guides/references/advanced-installation#Binary-cache
CYPRESS_CACHE_FOLDER: ${{ steps.cypress-cache-dir-path.outputs.dir }}
- name: Upload videos in case of failure
Expand Down
5 changes: 1 addition & 4 deletions e2e/cypress.config.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import { defineConfig } from 'cypress'
// eslint-disable-next-line import/no-extraneous-dependencies
import installLogsPrinter from 'cypress-terminal-report/src/installLogsPrinter.js'

export default defineConfig({
Expand All @@ -10,8 +8,7 @@ export default defineConfig({
baseUrl: 'http://localhost:1234',
specPattern: 'cypress/integration/*.spec.ts',

// eslint-disable-next-line no-unused-vars
setupNodeEvents (on, config) {
setupNodeEvents (on) {
// implement node event listeners here
installLogsPrinter(on)
},
Expand Down
1 change: 0 additions & 1 deletion e2e/generate-test.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/usr/bin/env node
/* eslint-disable no-console, import/no-extraneous-dependencies */
import prompts from 'prompts'
import fs from 'node:fs/promises'

Expand Down
1 change: 1 addition & 0 deletions e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"cypress": "^10.0.0",
"cypress-terminal-report": "^4.1.2",
"deep-freeze": "^0.0.1",
"execa": "^6.1.0",
"parcel": "^2.0.1",
"prompts": "^2.4.2",
"react": "^18.1.0",
Expand Down
79 changes: 79 additions & 0 deletions e2e/start-companion-with-load-balancer.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env node

import { execa } from 'execa'
import http from 'node:http'
import httpProxy from 'http-proxy'

const numInstances = 3
const lbPort = 3020
const companionStartPort = 3021

// simple load balancer that will direct requests round robin between companion instances
function createLoadBalancer (baseUrls) {
const proxy = httpProxy.createProxyServer({ ws: true })

let i = 0

function getTarget () {
return baseUrls[i % baseUrls.length]
}

const server = http.createServer((req, res) => {
const target = getTarget()
// console.log('req', req.method, target, req.url)
proxy.web(req, res, { target }, (err) => {
console.error('Load balancer failed to proxy request', err.message)
res.statusCode = 500
res.end()
})
i++
})

server.on('upgrade', (req, socket, head) => {
const target = getTarget()
// console.log('upgrade', target, req.url)
proxy.ws(req, socket, head, { target }, (err) => {
console.error('Load balancer failed to proxy websocket', err.message)
console.error(err)
socket.destroy()
})
i++
})

server.listen(lbPort)
console.log('Load balancer listening', lbPort)
return server
}

const startCompanion = ({ name, port }) => execa('nodemon', [
'--watch', 'packages/@uppy/companion/src', '--exec', 'node', '-r', 'dotenv/config', './packages/@uppy/companion/src/standalone/start-server.js',
], {
cwd: new URL('../', import.meta.url),
stdio: 'inherit',
env: {
// Note: these env variables will override anything set in .env
COMPANION_PORT: port,
COMPANION_SECRET: 'development', // multi instance will not work without secret set
COMPANION_PREAUTH_SECRET: 'development', // multi instance will not work without secret set
COMPANION_ALLOW_LOCAL_URLS: 'true',
COMPANION_LOGGER_PROCESS_NAME: name,
},
})

const hosts = Array.from({ length: numInstances }, (_, index) => {
const port = companionStartPort + index
return { index, port }
})

console.log('Starting companion instances on ports', hosts.map(({ port }) => port))

const companions = hosts.map(({ index, port }) => startCompanion({ name: `companion${index}`, port }))

let loadBalancer
try {
loadBalancer = createLoadBalancer(hosts.map(({ port }) => `http://localhost:${port}`))
await Promise.all(companions)
} finally {
loadBalancer?.close()
companions.forEach((companion) => companion.kill())
}
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,11 @@
"release": "PACKAGES=$(yarn workspaces list --json) yarn workspace @uppy-dev/release interactive",
"size": "echo 'JS Bundle mingz:' && cat ./packages/uppy/dist/uppy.min.js | gzip | wc -c && echo 'CSS Bundle mingz:' && cat ./packages/uppy/dist/uppy.min.css | gzip | wc -c",
"start:companion": "bash bin/companion.sh",
"start:companion:with-loadbalancer": "e2e/start-companion-with-load-balancer.mjs",
"start": "npm-run-all --parallel watch start:companion web:start",
"e2e": "yarn build && yarn e2e:skip-build",
"e2e:skip-build": "npm-run-all --parallel watch:js:lib e2e:client start:companion e2e:cypress",
"e2e:ci": "start-server-and-test 'npm-run-all --parallel e2e:client start:companion' '1234|3020' e2e:headless",
"e2e:skip-build": "npm-run-all --parallel watch:js:lib e2e:client start:companion:with-loadbalancer e2e:cypress",
"e2e:ci": "start-server-and-test 'npm-run-all --parallel e2e:client start:companion:with-loadbalancer' '1234|3020' e2e:headless",
"e2e:client": "yarn workspace e2e client:start",
"e2e:cypress": "yarn workspace e2e cypress:open",
"e2e:headless": "yarn workspace e2e cypress:headless",
Expand Down
8 changes: 8 additions & 0 deletions packages/@uppy/companion/src/companion.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ const { getCredentialsOverrideMiddleware } = require('./server/provider/credenti
// @ts-ignore
const { version } = require('../package.json')

function setLoggerProcessName ({ loggerProcessName }) {
if (loggerProcessName != null) logger.setProcessName(loggerProcessName)
}

// intercepts grantJS' default response error when something goes
// wrong during oauth process.
const interceptGrantErrorResponse = interceptor((req, res) => {
Expand Down Expand Up @@ -51,13 +55,17 @@ const interceptGrantErrorResponse = interceptor((req, res) => {
module.exports.errors = { ProviderApiError, ProviderAuthError }
module.exports.socket = require('./server/socket')

module.exports.setLoggerProcessName = setLoggerProcessName

/**
* Entry point into initializing the Companion app.
*
* @param {object} optionsArg
* @returns {{ app: import('express').Express, emitter: any }}}
*/
module.exports.app = (optionsArg = {}) => {
setLoggerProcessName(optionsArg)

validateConfig(optionsArg)

const options = merge({}, defaultOptions, optionsArg)
Expand Down
34 changes: 19 additions & 15 deletions packages/@uppy/companion/src/server/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,25 @@ function maskMessage (msg) {
return out
}

let processName = 'companion'

exports.setProcessName = (newProcessName) => {
processName = newProcessName
}

/**
* message log
*
* @param {string | Error} arg the message or error to log
* @param {string} tag a unique tag to easily search for this message
* @param {string} level error | info | debug
* @param {string} [id] a unique id to easily trace logs tied to a request
* @param {Function} [color] function to display the log in appropriate color
* @param {object} params
* @param {string | Error} params.arg the message or error to log
* @param {string} params.tag a unique tag to easily search for this message
* @param {string} params.level error | info | debug
* @param {string} [params.traceId] a unique id to easily trace logs tied to a request
* @param {Function} [params.color] function to display the log in appropriate color
*/
const log = (arg, tag = '', level, id = '', color = (message) => message) => {
const log = ({ arg, tag = '', level, traceId = '', color = (message) => message }) => {
const time = new Date().toISOString()
const whitespace = tag && id ? ' ' : ''
const whitespace = tag && traceId ? ' ' : ''

function msgToString () {
// We don't need to log stack trace on special errors that we ourselves have produced
Expand All @@ -59,7 +66,7 @@ const log = (arg, tag = '', level, id = '', color = (message) => message) => {
const msgString = msgToString()
const masked = maskMessage(msgString)
// eslint-disable-next-line no-console
console.log(color(`companion: ${time} [${level}] ${id}${whitespace}${tag}`), color(masked))
console.log(color(`${processName}: ${time} [${level}] ${traceId}${whitespace}${tag}`), color(masked))
}

/**
Expand All @@ -70,7 +77,7 @@ const log = (arg, tag = '', level, id = '', color = (message) => message) => {
* @param {string} [traceId] a unique id to easily trace logs tied to a request
*/
exports.info = (msg, tag, traceId) => {
log(msg, tag, 'info', traceId)
log({ arg: msg, tag, level: 'info', traceId })
}

/**
Expand All @@ -81,8 +88,7 @@ exports.info = (msg, tag, traceId) => {
* @param {string} [traceId] a unique id to easily trace logs tied to a request
*/
exports.warn = (msg, tag, traceId) => {
// @ts-ignore
log(msg, tag, 'warn', traceId, chalk.bold.yellow)
log({ arg: msg, tag, level: 'warn', traceId, color: chalk.bold.yellow })
}

/**
Expand All @@ -93,8 +99,7 @@ exports.warn = (msg, tag, traceId) => {
* @param {string} [traceId] a unique id to easily trace logs tied to a request
*/
exports.error = (msg, tag, traceId) => {
// @ts-ignore
log(msg, tag, 'error', traceId, chalk.bold.red)
log({ arg: msg, tag, level: 'error', traceId, color: chalk.bold.red })
}

/**
Expand All @@ -106,7 +111,6 @@ exports.error = (msg, tag, traceId) => {
*/
exports.debug = (msg, tag, traceId) => {
if (process.env.NODE_ENV !== 'production') {
// @ts-ignore
log(msg, tag, 'debug', traceId, chalk.bold.blue)
log({ arg: msg, tag, level: 'debug', traceId, color: chalk.bold.blue })
}
}
Loading

0 comments on commit e966fa7

Please sign in to comment.