Commit 4ea8552d authored by Renato Figueiro Maia's avatar Renato Figueiro Maia
Browse files

Testes iniciais da API REST do SGA.

[SOMA-6916][SOMA-6857]
parent 51e0b66c
{
"name": "sga-test",
"version": "0.1.0",
"description": "SGA Daemon Test Suite",
"main": "sga_test_spec.js",
"scripts": {
"test": "jasmine"
},
"repository": {
"type": "git",
"url": "https://git.tecgraf.puc-rio.br/csbase-dev/sgarest-daemon.git"
},
"keywords": [
"soma",
"sga",
"test"
],
"author": "Renato Maia",
"license": "ISC",
"devDependencies": {
"express": "^4.17.1",
"frisby": "^2.1.3",
"jasmine": "^3.6.3",
"jasmine-expect": "^5.0.0"
}
}
"use strict";
const events = require('events');
const frisby = require("frisby");
const utils = require('./utils');
describe("Started SGA", function() {
afterEach(utils.clearAllResources);
it('should terminate on shutdown', async () => {
const config = utils.fillSgaConfig();
const mock = new utils.RegistryService(config);
const sga = new utils.SgaDaemon(config);
await mock.start();
await sga.start();
const [req] = await events.once(mock.events, "register");
frisby.get(req.body.actions.shutdown).expect("status", 200);
const [exitVal] = await sga.terminated();
expect(exitVal).toEqual(0);
});
});
{
"spec_dir": "spec",
"spec_files": [
"**/*[sS]pec.js"
],
"helpers": [
"helpers/**/*.js",
"../node_modules/jasmine-expect/index.js"
],
"stopSpecOnExpectationFailure": false,
"random": true
}
"use strict";
const child_process = require('child_process');
const events = require('events');
const express = require('express');
const fs = require('fs');
const os = require('os');
const path = require('path');
const util = require('util');
const testDir = fs.mkdtempSync(path.join(os.tmpdir(), "sga_test-"));
const sgaConfig = {
platform: "machine",
sgad_bind_addr: "127.0.0.1",
sgad_host: "localhost",
sgad_port: 65432,
sga_name: "SgaForUnitTest",
status_interval_s: 3,
exec_polling_interval_s: 2,
register_retry_s: 1,
project_root_dir: path.join(testDir, "projects"),
algorithm_root_dir: path.join(testDir, "algorithms"),
runtime_data_dir: path.join(testDir, "runtime"),
sandbox_root_dir: path.join(testDir, "sandbox"),
driver: "sga.driver.posix",
resources: [
"some resource",
"another resource",
"yet another resource"
],
extra_config: {
some_config: "some configuration",
another_config: "another configuration",
yet_another_config: "yet another configuration"
}
};
function fillSgaConfig(values) {
if (!values) {
values = {};
}
values.__proto__ = sgaConfig;
return values;
}
function writeAsLua(stream, value, prefix = "") {
switch (typeof value) {
case "number":
stream.write(value.toString());
break;
case "string":
stream.write(JSON.stringify(value));
break;
case "object":
const newPrefix = prefix + " ";
stream.write("{\n");
if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
stream.write(newPrefix);
writeAsLua(stream, value[i], newPrefix);
stream.write(",\n");
}
} else {
for (let property in value) {
stream.write(newPrefix);
stream.write(`${property} = `);
writeAsLua(stream, value[property], newPrefix);
stream.write(",\n");
}
}
stream.write(prefix);
stream.write("}");
break;
default:
throw new TypeError("unable to write as Lua value " + value);
}
}
let sgaDaemons = new Set();
let registryMocks = new Set();
class SgaDaemon {
constructor(config) {
this.config = config;
}
async start() {
if (this.process) {
throw new Error("already started");
}
const configPath = path.join(testDir, "sgad.cfg");
const configFile = fs.createWriteStream(configPath);
for (let property in this.config) {
configFile.write(`${property} = `);
writeAsLua(configFile, this.config[property]);
configFile.write("\n");
}
configFile.end();
await events.once(configFile, "finish");
configFile.close();
const sgaOutput = fs.createWriteStream(path.join(testDir, "sgad.out"),
{ flags: "a" });
await events.once(sgaOutput, "open");
// TODO: find a better way to find the SGA deploy to be tested.
const sgaHome = "/home/maia/.luaenv/versions/sga"
const sgaLuaVer = "5.2"
const sgaLuaBin = `${sgaHome}/bin/lua`
const sgaLuaPath = `${sgaHome}/share/lua/${sgaLuaVer}/?.lua;${sgaHome}/share/lua/${sgaLuaVer}/?/init.lua`
const sgaCPath = `${sgaHome}/lib/lua/${sgaLuaVer}/?.so`
const sgaRocksSetup = `package.path = '../?.lua;${sgaLuaPath};'..package.path
package.cpath = '${sgaCPath};'..package.cpath`
this.process = child_process.spawn(sgaLuaBin,
["-e", sgaRocksSetup, "../sgad.lua", configPath],
{ stdio: [ "ignore", sgaOutput, sgaOutput ] });
this.process.on('exit', (exitVal, signalName) => {
sgaDaemons.delete(this);
this.process = undefined;
this.config = undefined;
this.exitVal = exitVal;
this.signalName = signalName;
});
sgaDaemons.add(this);
sgaOutput.close();
}
terminate() {
if (this.process) {
this.process.kill();
return true;
}
return false;
}
terminated() {
if (this.process) {
return events.once(this.process, "exit");
}
return new Promise(res => res([this.exitVal, this.signalName]));
}
}
const NS_PER_SEC = 1e9;
const registryEndpoints = [
{
httpMethod: "post",
endpoint: "/v1/sga",
funcName: "register"
},
{
httpMethod: "post",
endpoint: "/mock/completion",
funcName: "completion"
},
{
httpMethod: "post",
endpoint: "/mock/status",
funcName: "status"
},
{
httpMethod: "get",
endpoint: "/mock/heartbeat",
funcName: "heartbeat"
}
];
class RegistryService {
constructor(sgaConfig, heartbeatInteval = .1, port = 0) {
this.sgaConfig = sgaConfig;
this.listenPort = port;
this.regReply = { actions: {} };
this.timestamps = {};
this.events = new events.EventEmitter();
this.restApp = express();
this.restApp.use(express.json()); // for parsing application/json
for (let i = 0; i < registryEndpoints.length; i++) {
const spec = registryEndpoints[i];
const funcName = spec.funcName;
this.restApp[spec.httpMethod](spec.endpoint, (req, res) => {
this.events.emit(funcName, req, res);
const method = this[funcName];
return method.call(this, req, res);
})
this.regReply.actions[funcName] = { uri: spec.endpoint };
}
this.regReply.actions.heartbeat.interval_s = heartbeatInteval;
}
async start() {
if (!this.socket) {
this.socket = this.restApp.listen(this.listenPort);
await events.once(this.socket, "listening");
registryMocks.add(this);
this.socket.on("close", () => registryMocks.delete(this));
if (this.listenPort == 0) {
this.listenPort = this.socket.address().port;
}
const baseUri = `http://localhost:${this.listenPort}`;
if (!("csbase_server" in this.sgaConfig)) {
this.sgaConfig.csbase_server = baseUri;
}
}
}
async stop() {
if (this.socket) {
this.socket.close();
await events.once(this.socket, "close");
this.socket = undefined;
}
}
expectInterval(name, value) {
const now = process.hrtime.bigint();
if (this.timestamps[name]) {
const delay = now - this.timestamps[name];
expect(delay).toBeGreaterThan(value * NS_PER_SEC);
expect(delay).toBeLessThan((value + .5) * NS_PER_SEC);
}
this.timestamps[name] = now;
}
register(req, res) {
const body = req.body;
expect(body.name).toBe(this.sgaConfig.sga_name);
expect(body.type).toBe("machine");
expect(body.platform).toBe(this.sgaConfig.platform);
expect(body.project_root_dir).toBe(this.sgaConfig.project_root_dir);
expect(body.algorithm_root_dir).toBe(this.sgaConfig.algorithm_root_dir);
expect(body.sandbox_root_dir).toBe(this.sgaConfig.sandbox_root_dir);
expect(body.actions.path).toBeString();
expect(body.actions.paths).toBeString();
expect(body.actions.job).toBeString();
expect(body.actions.shutdown).toBeString();
expect(body.resources).toEqual(jasmine.objectContaining(this.sgaConfig.resources));
expect(body.extra_config).toEqual(jasmine.objectContaining(this.sgaConfig.extra_config));
this.timestamps.heartbeat = process.hrtime.bigint();
res.status(201).json(this.regReply);
}
heartbeat(req, res) {
expect(this.timestamps.heartbeat).toBeDefined();
this.expectInterval("heartbeat", this.regReply.actions.heartbeat.interval_s);
res.status(200).end();
}
status(req, res) {
this.expectInterval("status_update", this.sgaConfig.status_interval_s);
res.status(200).end();
}
completion(req, res) {
res.status(200).end();
}
}
async function clearAllResources() {
for (let sga of sgaDaemons) {
if (sga.terminate()) {
await sga.terminated();
}
}
for (let mock of registryMocks) {
await mock.stop();
}
}
const waitMsec = (delay) => new Promise((resolve) => setTimeout(resolve, delay));
exports.waitMsec = waitMsec
exports.fillSgaConfig = fillSgaConfig;
exports.SgaDaemon = SgaDaemon;
exports.RegistryService = RegistryService;
exports.clearAllResources = clearAllResources;
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment