diff --git a/load-tests/class-scenario.js b/load-tests/class-scenario.js index cbbb71374e..f3affe08d7 100644 --- a/load-tests/class-scenario.js +++ b/load-tests/class-scenario.js @@ -1,9 +1,10 @@ import http from "k6/http"; import exec from "k6/execution"; -import { Trend } from "k6/metrics"; +import { Counter, Trend } from "k6/metrics"; import { renkuLogin } from "./oauth.js"; -import { check, fail, sleep } from "k6"; +import { check, fail, group, sleep } from "k6"; +import { uuidv4 } from "https://jslib.k6.io/k6-utils/1.2.0/index.js"; import { baseUrl, @@ -16,34 +17,49 @@ export const options = { scenarios: { lecture: { executor: "per-vu-iterations", - vus: 10, // tested up to 30 + vus: 4, // tested up to 30 iterations: 1, + maxDuration: '30m', + gracefulStop: '2m', }, }, }; // k6 custom metrics -const sessionStartupTrend = new Trend("session_startup"); +const sessionStartupDuration = new Trend("session_startup_duration", true); +const sessionCreateReqDuration = new Trend("session_create_req_duration", true); +const sessionGetReqDuration = new Trend("session_get_req_duration", true); +const sessionDeleteReqDuration = new Trend("session_delete_req_duration", true); +const requestRetries = new Counter("http_request_retries", false); -function httpRetry(httpRequest, n, logMessage) { +function DoHttpRequest(httpRequest, nRetries = 0) { let res, i = 0; - do { - sleep(i); - res = httpRequest; - console.log( - `${exec.vu.idInInstance}-vu: ${logMessage}, status: ${res.status}, retries: ${i}` - ); - i++; - } while (!(res.status >= 200 && res.status < 300) && i < n); + while (i <= nRetries) { + res = httpRequest(); + if (res.status >= 400 || res.status < 200) { + i++; + requestRetries.add(1) + sleep(i); + continue; + } + break; + }; + + generalResponseCheck(res); + return res; +} - if (res.status >= 400) { - throw new Error( - `${exec.vu.idInInstance}-vu: FAILED ${logMessage}, status: ${res.status}, retry: ${i}` +function generalResponseCheck(res) { + if ( + !check(res, { + "request succeeded with 2XX": (res) => res.status >= 200 && res.status < 300, + }) + ) { + fail( + `request at ${res.url} failed with ${res.status} and body ${res.body}` ); } - - return res; } function showProjectInfo(baseUrl, gitUrl) { @@ -52,73 +68,63 @@ function showProjectInfo(baseUrl, gitUrl) { is_delayed: false, migrate_project: false, }; - const res = http.post( - `${baseUrl}/ui-server/api/renku/project.show`, - JSON.stringify(payload), - { headers: { "Content-Type": "application/json" } } - ); - console.log(res.status); + const res = DoHttpRequest( + () => http.post( + `${baseUrl}/ui-server/api/renku/project.show`, + JSON.stringify(payload), + { headers: { "Content-Type": "application/json" } } + ) + ) if ( !check(res, { - "getting project info succeeded with 2XX": (res) => - res.status >= 200 && res.status < 300, "getting project info response has no error": (res) => res.json().error === undefined, }) ) { fail( - `getting project info failed with status ${res.status} and body ${res.body}` + `getting project info failed with error ${res.json().error}` ); } - return JSON.parse(res.body); + return res.json(); } -function forkProject(baseUrl, projectInfo) { +function forkProject(baseUrl, projectInfo, idPostfix) { const name = projectInfo.result.name; const projectPathComponents = projectInfo.result.id.split("/"); const path = projectPathComponents.pop(); const namespace_path = projectPathComponents.pop(); const id = namespace_path + "%2F" + path; - const vuIdPostfix = "-" + String(exec.vu.idInInstance); - - console.log(`${exec.vu.idInInstance}-vu: project id: ${id}`); - const payload = { id: id, - name: name + vuIdPostfix, + name: name + idPostfix, namespace_path: namespace_path, - path: path + vuIdPostfix, + path: path + idPostfix, }; - const res = httpRetry( - http.post( + const res = DoHttpRequest( + () => http.post( `${baseUrl}/ui-server/api/projects/${id}/fork`, JSON.stringify(payload), { headers: { "Content-Type": "application/json" } } ), 10, - "fork project" ); - return JSON.parse(res.body); + return res.json(); } function getCommitShas(baseUrl, projectInfo) { const id = projectInfo.id; - console.log(`${exec.vu.idInInstance}-vu: project id to fork ${id}`); - const res = httpRetry( - http.get( + const res = DoHttpRequest( + () => http.get( `${baseUrl}/ui-server/api/projects/${id}/repository/commits?ref_name=master&per_page=100&page=1` ), 10, - "get commit sha" ); - //console.log(`${exec.vu.idInInstance}-vu: commit sha request status: ${res.status}`) - return JSON.parse(res.body); } @@ -131,55 +137,53 @@ function startServer(baseUrl, forkedProject, commitShas) { serverOptions: serverOptions, }; - const res = httpRetry( - http.post( + const res = DoHttpRequest( + () => http.post( `${baseUrl}/ui-server/api/notebooks/servers`, JSON.stringify(payload), { headers: { "Content-Type": "application/json" } } ), - 10, - "start server/session" - ); - - console.log( - `${exec.vu.idInInstance}-vu: start server, status: ${res.status}` + 1, ); - - return JSON.parse(res.body); + sessionCreateReqDuration.add(res.timings.duration) + return res.json(); } function pollServerStatus(baseUrl, server) { const serverName = server.name; - console.log(`${exec.vu.idInInstance}-vu: server name: ${serverName}`); const ServerStates = { Starting: "starting", Running: "running", }; - let resBody, - counter = 0; + let resJson, res, counter = 0; do { sleep(1); - resBody = JSON.parse( - http.get(`${baseUrl}/ui-server/api/notebooks/servers/${serverName}`).body - ); + res = DoHttpRequest( + () => http.get(`${baseUrl}/ui-server/api/notebooks/servers/${serverName}`) + ) + sessionGetReqDuration.add(res.timings.duration) + resJson = res.json() counter++; } while ( - resBody.status === undefined || - resBody.status.state == ServerStates.Starting + resJson.status === undefined || + resJson.status.state == ServerStates.Starting ); - sessionStartupTrend.add(counter); + sessionStartupDuration.add(counter * 1000); - return resBody; + return resJson; } function stopServer(baseUrl, server) { const serverName = server.name; - const res = http.del( - `${baseUrl}/ui-server/api/notebooks/servers/${serverName}` - ); + const res = DoHttpRequest( + () => http.del( + `${baseUrl}/ui-server/api/notebooks/servers/${serverName}` + ), + ) + sessionDeleteReqDuration.add(res.timings.duration) return res.status; } @@ -187,58 +191,46 @@ function stopServer(baseUrl, server) { function deleteProject(baseUrl, projectInfo) { const id = projectInfo.id; - const res = httpRetry( - http.del(`${baseUrl}/ui-server/api/projects/${id}`), + const res = DoHttpRequest( + () => http.del(`${baseUrl}/ui-server/api/projects/${id}`), 10, - "delete project" ); - console.log("shuttdown"); - return res.status; } -// Test - +// Test setup export function setup() { renkuLogin(baseUrl, credentials); - const projectInfo = showProjectInfo(baseUrl, sampleGitProjectUrl); - return projectInfo; } +// Test code export default function test(projectInfo) { - const vu = exec.vu.idInInstance; - - sleep(vu); // lets VUs start in sequence + let forkedProject, commitShas, server; + const uuid = uuidv4(); - console.log(`${vu}-vu: login to renku`); renkuLogin(baseUrl, credentials); - console.log(`${vu}-vu: fork 'test' project -> 'test-${vu}'`); - const forkedProject = forkProject(baseUrl, projectInfo); - - sleep(90); // workaround - - console.log(`${vu}-vu: get latest commit hash from forked project`); - const commitShas = getCommitShas(baseUrl, forkedProject); - - console.log(`${vu}-vu: start server/session with latest commit`); - const server = startServer(baseUrl, forkedProject, commitShas); - - console.log(`${vu}-vu: wait for server to enter state 'running'`); - pollServerStatus(baseUrl, server); - console.log(`${vu}-vu: server 'running'`); + group("fork", function () { + forkedProject = forkProject(baseUrl, projectInfo, uuid); + sleep(90); // waiting for fork to complete + commitShas = getCommitShas(baseUrl, forkedProject); + }) - console.log(`${vu}-vu: let server run for 200 seconds`); - sleep(200); + group("launch session", function () { + server = startServer(baseUrl, forkedProject, commitShas); + pollServerStatus(baseUrl, server); + }) - console.log(`${vu}-vu: shutdown server`); - stopServer(baseUrl, server); + sleep(10); // simulate users being idle - console.log(`${vu}-vu: delete 'project-${vu}'`); - deleteProject(baseUrl, forkedProject); + group("shutdown server", function () { + stopServer(baseUrl, server); + }) - console.log(`${vu}-vu: test finished`); + group("remove project", function () { + deleteProject(baseUrl, forkedProject); + }) }