vtex
Version:
The platform for e-commerce apps
90 lines (89 loc) • 3.88 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSavedOrMostAvailableHost = void 0;
const tslib_1 = require("tslib");
const async_retry_1 = tslib_1.__importDefault(require("async-retry"));
const moment_1 = tslib_1.__importDefault(require("moment"));
const ramda_1 = require("ramda");
const conf_1 = require("./api/conf");
const errors_1 = require("./api/error/errors");
const logger_1 = tslib_1.__importDefault(require("./api/logger"));
const TTL_SAVED_HOST_HOURS = 0;
const NOT_AVAILABLE = {
hostname: undefined,
score: -1000,
stickyHint: undefined,
};
const AVAILABILITY_RETRY_OPTS = {
retries: 2,
minTimeout: 1000,
factor: 2,
};
const withTimeout = (promise, timeout) => {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new errors_1.BuilderHubTimeoutError(`Timeout of ${timeout}ms exceeded`)), timeout);
promise
.then(res => {
clearTimeout(timer);
resolve(res);
})
.catch(err => {
clearTimeout(timer);
reject(err);
});
});
};
const mapAvailability = (appId, builder, timeout) => {
return ramda_1.map(async (hintIdx) => {
const getAvailabilityWithTimeout = () => {
const availabilityP = builder.availability(appId, hintIdx);
return withTimeout(availabilityP, timeout);
};
try {
const response = (await async_retry_1.default(getAvailabilityWithTimeout, AVAILABILITY_RETRY_OPTS));
const { host: stickyHint, hostname, score } = response;
logger_1.default.debug(`Retrieved availability score ${score} from host ${hostname}`);
return { hostname, score, stickyHint };
}
catch (e) {
e.code === 'builder_hub_timeout'
? logger_1.default.debug(`Request to host at position ${hintIdx} timed out after ${timeout}ms`)
: logger_1.default.debug(`Unable to retrieve availability from host at position ${hintIdx}`);
return NOT_AVAILABLE;
}
});
};
const highestAvailability = ramda_1.reduce((acc, current) => {
const { score: scoreAcc } = acc;
const { score: scoreCurrent } = current;
return scoreCurrent > scoreAcc ? current : acc;
}, NOT_AVAILABLE);
const getMostAvailableHost = async (appId, builder, nHosts, timeout) => {
const hintsIdxArray = Array.from(new Array(nHosts), (_, idx) => idx);
const getAvailability = mapAvailability(appId, builder, timeout);
logger_1.default.debug(`Trying to retrieve builder-hub availability from ${nHosts} hosts`);
const availabilityArray = await Promise.all(getAvailability(hintsIdxArray));
const { hostname, score, stickyHint } = highestAvailability(availabilityArray);
stickyHint
? logger_1.default.debug(`Selected host ${hostname} with availability score ${score}`)
: logger_1.default.debug(`Unable to select host a priori, will use default options`);
return stickyHint;
};
exports.getSavedOrMostAvailableHost = async (appId, builder, nHosts, timeout) => {
const [appName] = appId.split('@');
if (conf_1.hasStickyHost(appName)) {
logger_1.default.debug(`Found sticky host saved locally`);
const { stickyHost, lastUpdated } = conf_1.getStickyHost(appName);
const timeElapsed = moment_1.default.duration(moment_1.default().diff(lastUpdated));
if (timeElapsed.asHours() <= TTL_SAVED_HOST_HOURS) {
return stickyHost;
}
logger_1.default.debug(`Saved sticky host has expired`);
}
logger_1.default.debug(`Finding a new sticky host`);
const newStickyHost = await getMostAvailableHost(appId, builder, nHosts, timeout);
if (newStickyHost) {
conf_1.saveStickyHost(appName, newStickyHost);
}
return newStickyHost;
};