update redis addon to use pipeline+http api

This commit is contained in:
Girish Ramakrishnan
2021-12-15 17:35:56 -08:00
parent 83fd3d9ab4
commit 26dc63553e
2 changed files with 58 additions and 29 deletions
+57 -28
View File
@@ -1,5 +1,7 @@
'use strict';
const { pipeline } = require('stream');
exports = module.exports = {
listServices,
getServiceStatus,
@@ -43,6 +45,7 @@ const addonConfigs = require('./addonconfigs.js'),
eventlog = require('./eventlog.js'),
fs = require('fs'),
hat = require('./hat.js'),
http = require('http'),
infra = require('./infra_version.js'),
mail = require('./mail.js'),
once = require('once'),
@@ -590,7 +593,12 @@ async function waitForContainer(containerName, tokenEnvName) {
const result = await getContainerDetails(containerName, tokenEnvName);
await promiseRetry({ times: 10, interval: 15000, debug }, async () => {
const [networkError, response] = await safe(superagent.get(`https://${result.ip}:3000/healthcheck?access_token=${result.token}`)
// temporary workaround till we move all containers to http
const url = containerName.includes('redis')
? `http://${result.ip}:3000/healthcheck?access_token=${result.token}`
: `https://${result.ip}:3000/healthcheck?access_token=${result.token}`;
const [networkError, response] = await safe(superagent.get(url)
.timeout(5000)
.disableTLSCerts()
.ok(() => true));
@@ -1266,6 +1274,51 @@ async function pipeRequestToFile(url, filename) {
});
}
async function pipeRequestToFile2(url, filename) {
assert.strictEqual(typeof url, 'string');
assert.strictEqual(typeof filename, 'string');
return new Promise((resolve, reject) => {
const writeStream = fs.createWriteStream(filename);
const request = http.request(url, { method: 'POST' }); // ClientRequest
request.setTimeout(60000, () => request.destroy(new Error('Request timedout'))); // connect OR post-connect idle timeout
request.on('error', (error) => reject(new BoxError(BoxError.NETWORK_ERROR, `Could not pipe ${url} to ${filename}: ${error.message}`))); // network error, dns error
request.on('response', (response) => {
if (response.statusCode !== 200) return reject(new BoxError(BoxError.ADDONS_ERROR, `Unexpected response code or HTTP error when piping ${url} to ${filename}: status ${response.statusCode}`));
pipeline(response, writeStream, (error) => {
if (error) return reject(new BoxError(BoxError.ADDONS_ERROR, `Error piping ${url} to ${filename}: ${error.message}`));
if (!response.complete) return reject(new BoxError(BoxError.ADDONS_ERROR, `Response not complete when piping ${url} to ${filename}`));
resolve();
});
});
request.end(); // make the request
});
}
async function pipeFileToRequest(filename, url) {
assert.strictEqual(typeof filename, 'string');
assert.strictEqual(typeof url, 'string');
return new Promise((resolve, reject) => {
const readStream = fs.createReadStream(filename);
const request = http.request(url, { method: 'POST' }); // ClientRequest
request.setTimeout(60000, () => request.destroy(new Error('Request timedout'))); // connect OR post-connect idle timeout
request.on('response', (response) => {
response.resume(); // drain the response
if (response.statusCode !== 200) return reject(new BoxError(BoxError.ADDONS_ERROR, `Unexpected response code or HTTP error when piping ${filename} to ${url}: status ${response.statusCode} complete ${response.complete}`));
resolve();
});
pipeline(readStream, request, function (error) {
if (error) return reject(new BoxError(BoxError.ADDONS_ERROR, `Error piping file ${filename} to request ${url}`));
});
});
}
async function backupMySql(app, options) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof options, 'object');
@@ -1800,8 +1853,7 @@ async function clearRedis(app, options) {
const result = await getContainerDetails('redis-' + app.id, 'CLOUDRON_REDIS_TOKEN');
const [networkError, response] = await safe(superagent.post(`https://${result.ip}:3000/clear?access_token=${result.token}`)
.disableTLSCerts()
const [networkError, response] = await safe(superagent.post(`http://${result.ip}:3000/clear?access_token=${result.token}`)
.ok(() => true));
if (networkError) throw new BoxError(BoxError.ADDONS_ERROR, `Network error clearing redis: ${networkError.message}`);
@@ -1830,9 +1882,7 @@ async function backupRedis(app, options) {
debug('Backing up redis');
const result = await getContainerDetails('redis-' + app.id, 'CLOUDRON_REDIS_TOKEN');
const url = `https://${result.ip}:3000/backup?access_token=${result.token}`;
await pipeRequestToFile(url, dumpPath('redis', app.id));
await pipeRequestToFile2(`http://${result.ip}:3000/backup?access_token=${result.token}`, dumpPath('redis', app.id));
}
async function restoreRedis(app, options) {
@@ -1842,28 +1892,7 @@ async function restoreRedis(app, options) {
debug('Restoring redis');
const result = await getContainerDetails('redis-' + app.id, 'CLOUDRON_REDIS_TOKEN');
await new Promise((resolve, reject) => {
reject = once(reject); // protect from multiple returns with streams
let input;
const newDumpLocation = dumpPath('redis', app.id);
if (fs.existsSync(newDumpLocation)) {
input = fs.createReadStream(newDumpLocation);
} else { // old location of dumps
input = fs.createReadStream(path.join(paths.APPS_DATA_DIR, app.id, 'redis/dump.rdb'));
}
input.on('error', (error) => reject(new BoxError(BoxError.FS_ERROR, `Error reading input stream when restoring redis: ${error.message}`)));
const restoreReq = request.post(`https://${result.ip}:3000/restore?access_token=${result.token}`, { json: true, rejectUnauthorized: false }, function (error, response) {
if (error) return reject(new BoxError(BoxError.ADDONS_ERROR, `Error restoring redis: ${error.message}`));
if (response.statusCode !== 200) return reject(new BoxError(BoxError.ADDONS_ERROR, `Error restoring redis. Status code: ${response.statusCode} message: ${response.body.message}`));
resolve();
});
input.pipe(restoreReq);
});
await pipeFileToRequest(dumpPath('redis', app.id), `http://${result.ip}:3000/restore?access_token=${result.token}`);
}
async function statusTurn() {