UNPKG

testarmada-magellan

Version:

Massively parallel automated testing

137 lines (113 loc) 4.11 kB
"use strict"; const _ = require("lodash"); const settings = require("./settings"); const portUtil = require("./util/port_util"); const logger = require("./logger"); // Create a worker allocator for MAX_WORKERS workers. Note that the allocator // is not obliged to honor the creation of MAX_WORKERS, just some number of workers // between 0 and MAX_WORKERS. class Allocator { constructor(MAX_WORKERS, opts) { _.assign(this, { setTimeout, checkPorts: portUtil.checkPorts, getNextPort: portUtil.getNextPort, debug: settings.debug }, opts); logger.debug("Worker Allocator starting."); logger.debug("Port allocation range from: " + settings.BASE_PORT_START + " to " + (settings.BASE_PORT_START + settings.BASE_PORT_RANGE - 1) + " with " + settings.BASE_PORT_SPACING + " ports available to each worker."); /* istanbul ignore if */ if (settings.BASE_PORT_SPACING === 1) { logger.warn("Only one port is available per worker"); logger.warn("Increase --base_port_spacing to allocate more ports per worker if needed"); } this.initializeWorkers(MAX_WORKERS); } setup() { return Promise.resolve(); } teardown(err) { if (err) { // something is happening before this step, // we pass this error down return Promise.reject(err); } return Promise.resolve(); } initializeWorkers(numWorkers) { this.workers = []; for (let i = 1; i < numWorkers + 1; i++) { this.workers.push({ index: i, occupied: false, portOffset: undefined }); } } get(callback) { let attempts = 0; // Poll the worker allocator until we have a known-good port, then run this test const poll = () => { this._get((worker) => { attempts++; if (worker) { return callback(null, worker); } else if (attempts > settings.MAX_ALLOCATION_ATTEMPTS) { const errorMessage = "Couldn't allocate a worker after" + ` ${settings.MAX_ALLOCATION_ATTEMPTS} attempts`; return callback(errorMessage); } else { // If we didn't get a worker, try again this.setTimeout(poll, settings.WORKER_START_DELAY); } }); }; this.setTimeout(poll, settings.WORKER_START_DELAY); } _get(callback) { const availableWorker = _.find(this.workers, (e) => !e.occupied); if (availableWorker) { // occupy this worker while we test if we can use it availableWorker.occupied = true; const portOffset = this.getNextPort(); // Standard Magellan port convention: // let n = settings.BASE_PORT_SPACING - 1; // portOffset : selenium server // portOffset + 1 : pre-assigned for mocking (available for application to use) // ... // portOffset + n : available for application to use const desiredPortLabels = ["selenium port"]; const desiredPorts = []; // if BASE_PORT_SPACING is the default of 3, we'll check 3 ports for (let i = 0; i < settings.BASE_PORT_SPACING; i++) { desiredPorts.push(portOffset + i); } this.checkPorts(desiredPorts, (statuses) => { if (_.every(statuses, (status) => status.available)) { availableWorker.portOffset = portOffset; availableWorker.occupied = true; return callback(availableWorker); } else { // Print a message that ports are not available, show which ones in the range availableWorker.occupied = false; logger.warn("Detected port contention while spinning up worker: "); statuses.forEach((status, portIndex) => { if (!status.available) { logger.warn(" in use: #: " + status.port + " purpose: " + (desiredPortLabels[portIndex] ? desiredPortLabels[portIndex] : "generic")); } }); return callback(undefined); } }); } else { return callback(undefined); } } release(worker) { worker.occupied = false; } } module.exports = Allocator;