From 6756e05b0e4015dfea47c7e4b57da841f55cebd9 Mon Sep 17 00:00:00 2001 From: Iramis Valentin <85186645+git-ival@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:30:24 -0500 Subject: [PATCH 1/3] fix create_RTBs.js and update usage of util functions --- k6/generic/generic_utils.js | 5 + k6/projects/project_utils.js | 12 +- k6/rancher/rancher_users_utils.js | 14 +- k6/rancher/rancher_utils.js | 23 ++- k6/rbac/create_RTBs.js | 230 ++++++++++++++++++----------- k6/rbac/rbac_utils.js | 23 +-- k6/tests/load_projects_rbac.js | 6 +- k6/vai/filter_change_configmaps.js | 2 +- 8 files changed, 194 insertions(+), 121 deletions(-) diff --git a/k6/generic/generic_utils.js b/k6/generic/generic_utils.js index b4e4ac325..76c17fb4e 100644 --- a/k6/generic/generic_utils.js +++ b/k6/generic/generic_utils.js @@ -137,3 +137,8 @@ export function getPathBasename(filePath) { // Extract the substring after the last slash return filePath.substring(lastSlashIndex + 1); } + +export function getRandomElements(arr, count) { + const shuffled = [...arr].sort(() => 0.5 - Math.random()); + return shuffled.slice(0, count); +} diff --git a/k6/projects/project_utils.js b/k6/projects/project_utils.js index 3918ac5cc..9fe505902 100644 --- a/k6/projects/project_utils.js +++ b/k6/projects/project_utils.js @@ -13,16 +13,22 @@ export const putProjectTag = { url: `/v3/projects/` } export function cleanupMatchingProjects(baseUrl, cookies, namePrefix) { - let res = http.get(`${baseUrl}/${baseProjectsPath}`, { cookies: cookies }) + let res = http.get(`${baseUrl}/${normanProjectsPath}`, { + headers: { + accept: "application/json", + 'content-type': "application/json", + }, + cookies: cookies }) check(res, { '/v1/management.cattle.io.projects returns status 200': (r) => r.status === 200, }) - JSON.parse(res.body)["data"].filter(r => r["spec"]["displayName"].startsWith(namePrefix)).forEach(r => { + JSON.parse(res.body)["data"].filter(r => r["name"].startsWith(namePrefix)).forEach(r => { res = http.del(`${baseUrl}/${normanProjectsPath}/${r["id"]}`, { cookies: cookies }) check(res, { 'DELETE /v3/projects returns status 200': (r) => r.status === 200, }) }) + return res } export function getProject(baseUrl, cookies, id) { @@ -52,7 +58,7 @@ export function getNormanProjects(baseUrl, cookies) { return { res: res, projectArray: projectArray } } -export function createNormanProject(baseUrl, body, cookies) { +export function createNormanProject(baseUrl, cookies, body) { const res = http.post( `${baseUrl}/${normanProjectsPath}`, body, diff --git a/k6/rancher/rancher_users_utils.js b/k6/rancher/rancher_users_utils.js index b2c738635..96bba2eeb 100644 --- a/k6/rancher/rancher_users_utils.js +++ b/k6/rancher/rancher_users_utils.js @@ -52,7 +52,7 @@ export function setUserPreferences(baseUrl, cookies, userId, userPreferences) { export function getPrincipalIds(baseUrl, cookies) { const response = http.get( `${baseUrl}/v1/management.cattle.io.users`, - { cookies: cookies } + { headers: { accept: 'application/json' }, cookies: cookies } ) if (response.status !== 200) { fail('could not list users') @@ -64,7 +64,7 @@ export function getPrincipalIds(baseUrl, cookies) { export function getPrincipalById(baseUrl, cookies, id) { const response = http.get( `${baseUrl}/v3/principals/${id}`, - { cookies: cookies } + { headers: { accept: 'application/json' }, cookies: cookies } ) if (response.status !== 200) { fail('could not get principal by ID') @@ -82,7 +82,7 @@ export function getNormanUsers(baseUrl, params = null) { export function getCurrentUserPrincipal(baseUrl, cookies) { const response = http.get( `${baseUrl}/v3/principals?me=true`, - { cookies: cookies } + { headers: { accept: 'application/json' }, cookies: cookies } ) if (response.status !== 200) { fail('could not get current User\'s Principal') @@ -93,7 +93,7 @@ export function getCurrentUserPrincipal(baseUrl, cookies) { export function getCurrentUserPrincipalId(baseUrl, cookies) { const response = http.get( `${baseUrl}/v3/users?me=true`, - { cookies: cookies } + { headers: { accept: 'application/json' }, cookies: cookies } ) if (response.status !== 200) { fail('could not get my user') @@ -104,7 +104,7 @@ export function getCurrentUserPrincipalId(baseUrl, cookies) { export function getClusterIds(baseUrl, cookies) { const response = http.get( `${baseUrl}/v1/management.cattle.io.clusters`, - { cookies: cookies } + { headers: { accept: 'application/json' }, cookies: cookies } ) if (response.status !== 200) { fail('could not list clusters') @@ -146,7 +146,7 @@ export function getUsers(baseUrl, cookies) { } export function getUser(baseUrl, cookies, userId) { - const res = http.get(`${baseUrl}/v1/management.cattle.io.users/${userId}`, { cookies: cookies }) + const res = http.get(`${baseUrl}/v1/management.cattle.io.users/${userId}`, { headers: { accept: 'application/json' }, cookies: cookies }) check(res, { '/v1/management.cattle.io.users/ returns status 200': (r) => r.status === 200 || r.status === 204, }) @@ -154,7 +154,7 @@ export function getUser(baseUrl, cookies, userId) { } export function deleteUser(baseUrl, cookies, userId) { - const res = http.del(`${baseUrl}/v3/users/${userId}`, { cookies: cookies }) + const res = http.del(`${baseUrl}/v3/users/${userId}`, { headers: { accept: 'application/json' }, cookies: cookies }) check(res, { 'DELETE /v3/users returns status 200': (r) => r.status === 200 || r.status === 204, }) diff --git a/k6/rancher/rancher_utils.js b/k6/rancher/rancher_utils.js index 533fef315..286adde45 100644 --- a/k6/rancher/rancher_utils.js +++ b/k6/rancher/rancher_utils.js @@ -10,18 +10,23 @@ export function getCookies(baseUrl) { } export function login(baseUrl, cookies, username, password) { + const params = { + headers: { + accept: 'application/json', + 'content-type': 'application/json; charset=UTF-8', + }, + } + + // Only add cookies if they exist and are not empty + if (cookies && Object.keys(cookies).length > 0) { + console.log("Using cookies: ", cookies) + params.cookies = cookies + } const response = http.post( `${baseUrl}/v3-public/localProviders/local?action=login`, JSON.stringify({ "description": "UI session", "responseType": "cookie", "username": username, "password": password }), - { - headers: { - accept: 'application/json', - 'content-type': 'application/json; charset=UTF-8', - }, - cookies: { cookies }, - } + params ) - console.log("POST login: ", response.status); check(response, { 'login works': (r) => r.status === 200 || r.status === 401, @@ -384,6 +389,8 @@ export function logout(baseUrl, cookies) { check(response, { 'logging out works': (r) => r.status === 200, }) + + return response } export function generateAuthorizationHeader(token) { diff --git a/k6/rbac/create_RTBs.js b/k6/rbac/create_RTBs.js index 330055db1..2d9bcb9b3 100644 --- a/k6/rbac/create_RTBs.js +++ b/k6/rbac/create_RTBs.js @@ -1,18 +1,28 @@ import { check, fail, sleep } from 'k6'; import exec from 'k6/execution'; import http from 'k6/http'; -import { randomUUID } from 'k6/crypto'; +// import { randomUUID } from 'k6/crypto'; import { Trend } from 'k6/metrics'; +import { vu as metaVU } from 'k6/execution' +import * as k6Util from "../generic/k6_utils.js"; import { - getCookies, login, logout, deleteProjectsByPrefix, createProject, - listProjects, getProjectById, getRandomElements + getCookies, login, logout } from "../rancher/rancher_utils.js"; +import { getRandomElements } from "../generic/generic_utils.js"; +import { getClusterIds, getCurrentUserPrincipalId, getPrincipalIds } from "../rancher/rancher_users_utils.js"; +import { + createNormanProject as createProject, + getProjects as listProjects, + getProject as getProjectById, + cleanupMatchingProjects as deleteProjectsByPrefix +} from "../projects/project_utils.js"; import { createUser, listUsers, listRoles, listRoleTemplates, listRoleBindings, listClusterRoles, listClusterRoleBindings, listCRTBs, listPRTBs, deleteRoleTemplatesByPrefix, deleteUsersByPrefix, createRoleTemplate, createPRTB, createCRTB, - deletePRTBsByDescriptionLabel, deleteCRTBsByDescriptionLabel + deletePRTBsByDescriptionLabel, deleteCRTBsByDescriptionLabel, + createGlobalRoleBinding } from './rbac_utils.js'; // Parameters @@ -21,12 +31,17 @@ const projectCount = Number(__ENV.PROJECT_COUNT) || 10 const userCount = Number(__ENV.USER_COUNT) || 10 const testUserPassword = __ENV.TEST_USER_PASSWORD const userPrefix = __ENV.USER_PREFIX || 'test-user' +const projectsPrefix = "rtbs-test" +const projectRoleTemplatePrefix = "Dartboard PRTB" +const clusterRoleTemplatePrefix = "Dartboard CRTB" // Option setting const baseUrl = __ENV.BASE_URL const username = __ENV.USERNAME const password = __ENV.PASSWORD +export const handleSummary = k6Util.customHandleSummary; + // Option setting export const options = { insecureSkipTLSVerify: true, @@ -68,7 +83,7 @@ const numPRTBsTrend = new Trend('num_prtbs'); // Test functions, in order of execution export function setup() { // log in - if (!login(baseUrl, {}, username, password)) { + if (!login(baseUrl, null, username, password)) { fail(`could not login to cluster`) } const cookies = getCookies(baseUrl) @@ -76,88 +91,86 @@ export function setup() { // delete leftovers, if any cleanup(cookies) - let clusterIds = getClusterIds(cookies) + let clusterIds = getClusterIds(baseUrl, cookies) let clusterId = getRandomElements(clusterIds, 1)[0] console.log(`Utilizing Cluster with the ID ${clusterId}`) - let myId = getMyId(cookies) + let myId = getCurrentUserPrincipalId(baseUrl, cookies) // Create Projects, and Users - for (let numProjects = 0; numProjects < projectCount; numProjects++) { - let res = createProject(baseUrl, cookies, `Test Project ${numProjects + 1}`, clusterId, myId) + for (let projectNum = 0; projectNum < projectCount; projectNum++) { + const projectName = `${projectsPrefix}-vu${metaVU.idInInstance}-${projectNum.toString().padStart(4, '0')}` + const projectBody = JSON.stringify({ + type: "project", + name: projectName, + description: `Load test project ${projectNum + 1}`, + clusterId: "local", + creatorId: myId, + }) + let res = createProject(baseUrl, cookies, projectBody, clusterId, myId) if (res.status !== 201) { console.log("create project status: ", res.status) fail("Failed to create all expected Projects") } } + // Store created users with their credentials for later use + let createdUsers = [] for (let numUsers = 0; numUsers < userCount; numUsers++) { - const userName = `${userPrefix}-${randomUUID()}`; - let res = createUser(baseUrl, cookies, `Test User ${numUsers + 1}`, userName, testUserPassword) + const userName = `${userPrefix}-${crypto.randomUUID()}`; + let res = createUser(baseUrl, cookies, `Dartboard Test User ${numUsers + 1}`, userName, testUserPassword) if (res.status !== 201) { console.log("create user status: ", res.status) fail("Failed to create all expected Users") } + + const userData = JSON.parse(res.body) + createdUsers.push({ + id: userData.id, + username: userName + }) + + // Add GlobalRoleBinding so user can log in + const userId = userData.id + res = createGlobalRoleBinding(baseUrl, { cookies: cookies }, userId, "user") + if (res.status !== 201 && res.status !== 204) { + console.log("create globalrolebinding status: ", res.status) + fail("Failed to create GlobalRoleBinding for user") + } } - let projectsRes = listProjects(baseUrl, cookies) + // Wait for GlobalRoleBindings to propagate before continuing + console.log("Waiting for GlobalRoleBindings to propagate...") + sleep(5) + + let {res: projectsRes, projectArray} = listProjects(baseUrl, cookies) if (projectsRes.status !== 200) { fail("Failed to retrieve Projects") } - let usersRes = listUsers(baseUrl, cookies) - if (usersRes.status !== 200) { - fail("Failed to retrieve Users") - } - let projects = JSON.parse(projectsRes.body)["data"].filter(p => ("displayName" in p["spec"]) && p["spec"]["displayName"].startsWith("Test ")) - let users = JSON.parse(usersRes.body)["data"].filter(p => ("name" in p) && p["name"].startsWith("Test ")) + let projects = projectArray.filter(p => ("spec" in p) && ("displayName" in p["spec"]) && p["spec"]["displayName"].startsWith(projectsPrefix)) + + console.log(`Found ${projects.length} projects matching prefix "${projectsPrefix}"`) + console.log(`Created ${createdUsers.length} users`) + + if (projects.length === 0) { + fail(`No projects found matching prefix "${projectsPrefix}"`) + } + if (createdUsers.length === 0) { + fail(`No users were created`) + } updateRBACNumbers(cookies) // return data that remains constant throughout the test return { cookies: cookies, - principalIds: getPrincipalIds(cookies), + principalIds: getPrincipalIds(baseUrl, cookies), myId: myId, // clusterIds: clusterIds, clusterId: clusterId, projects: projects, - users: users, - } -} - -function getPrincipalIds(cookies) { - const response = http.get( - `${baseUrl}/v1/management.cattle.io.users`, - { cookies: cookies } - ) - if (response.status !== 200) { - fail('could not list users') - } - const users = JSON.parse(response.body).data - return users.filter(u => u["username"] != null).map(u => u["principalIds"][0]) -} - -function getMyId(cookies) { - const response = http.get( - `${baseUrl}/v3/users?me=true`, - { cookies: cookies } - ) - if (response.status !== 200) { - fail('could not get my user') + users: createdUsers, // Use the users we just created with known credentials } - return JSON.parse(response.body).data[0].principalIds[0] -} - -function getClusterIds(cookies) { - const response = http.get( - `${baseUrl}/v1/management.cattle.io.clusters`, - { cookies: cookies } - ) - if (response.status !== 200) { - fail('could not list clusters') - } - const clusters = JSON.parse(response.body).data - return clusters.map(c => c["id"]) } // updates count for each of the relevant RBAC metrics @@ -191,14 +204,21 @@ function updateRBACNumbers(cookies) { } function cleanup(cookies) { - let success = false - let projectsDeleted = deleteProjectsByPrefix(baseUrl, cookies, "Dartboard ") - let usersDeleted = deleteUsersByPrefix(baseUrl, cookies, "Dartboard ") - let prtbsDeleted = deletePRTBsByDescriptionLabel(baseUrl, cookies) - let crtbsDeleted = deleteCRTBsByDescriptionLabel(baseUrl, cookies) - let roleTemplatesDeleted = deleteRoleTemplatesByPrefix(baseUrl, cookies, "Dartboard ") + console.log("Cleaning up Projects, Users, Role Templates, CRTBs, and PRTBs with description label 'Dartboard' or name starting with test prefixes") + let projectsDeleted = deleteProjectsByPrefix(baseUrl, cookies, projectsPrefix) + // Use "Dartboard" prefix to match the description format "Dartboard Test X" + let usersDeleted = deleteUsersByPrefix(baseUrl, cookies, "Dartboard") + let prtbsDeleted = deletePRTBsByDescriptionLabel(baseUrl, cookies, "Dartboard") + let crtbsDeleted = deleteCRTBsByDescriptionLabel(baseUrl, cookies, "Dartboard") + let roleTemplatesDeleted = deleteRoleTemplatesByPrefix(baseUrl, cookies, "Dartboard") if (!projectsDeleted || !usersDeleted || !roleTemplatesDeleted || !prtbsDeleted || !crtbsDeleted) { + console.log("Projects deleted status: ", projectsDeleted) + console.log("Users deleted status: ", usersDeleted) + console.log("Role Templates deleted status: ", roleTemplatesDeleted) + console.log("PRTBs deleted status: ", prtbsDeleted) + console.log("CRTBs deleted status: ", crtbsDeleted) + // Don't fail on cleanup issues, just log them fail("failed to delete all objects created by test") } } @@ -275,6 +295,7 @@ function createProjectExpectFail(baseUrl, cookies, name, clusterId, userPrincipa let projectData = JSON.parse(res.body) if (!checkOK || projectData.length > 0) { + console.log("\nResponse post-verify project creation: ", JSON.stringify(res, null, 2), "\n") fail("Status check failed or received unexpected Project data") } @@ -282,12 +303,27 @@ function createProjectExpectFail(baseUrl, cookies, name, clusterId, userPrincipa } export function createPRTBs(data) { - const i = exec.scenario.iterationInTest + const iterationIndex = __ITER % data.projects.length + const project = data.projects[iterationIndex] + + if (!project) { + console.log(`No project found at index ${iterationIndex}, projects length: ${data.projects.length}`) + return + } + + // Use modulo to get user index, ensuring we don't go out of bounds + const userIndex = __ITER % data.users.length + let user = data.users[userIndex] + + if (!user) { + console.log(`No user found at index ${userIndex}, users length: ${data.users.length}`) + return + } let projectRoleTemplate = { "type": "roleTemplate", - "name": `Dartboard PRTB ${i}`, - "description": `Dartboard Test Project RT ${i}`, + "name": `${projectRoleTemplatePrefix} ${iterationIndex}`, + "description": `Dartboard Test Project RT ${iterationIndex}`, "rules": [ { "apiGroups": [ @@ -318,27 +354,30 @@ export function createPRTBs(data) { } let roleTemplateId = JSON.parse(res.body).id - let user = data.users[i] - res = createPRTB(baseUrl, data.cookies, data.projects[i].id, roleTemplateId, user.id) + const projectId = project.id.replace("/", ":") - if (res.status !== 201) { - console.log("\nResponse: ", JSON.stringify(res, null, 2), "\n") - fail("Failed to create PRTB") - } + res = createPRTB(baseUrl, data.cookies, projectId, roleTemplateId, user.id) + check(res, { + 'PRTB post returns 201 (created)': (r) => r.status === 201, + }) // log in as user - if (!login(baseUrl, {}, user.username, testUserPassword)) { - fail(`could not login to cluster as ${user.username}`) + let loginRes = login(baseUrl, {}, user.username, testUserPassword) + if (loginRes.status !== 200) { + console.warn(`could not login to cluster as ${user.username}, status: ${loginRes.status}`) + return // Don't fail, just skip verification for this iteration } const cookies = getCookies(baseUrl) // updateRBACNumbers with admin cookies updateRBACNumbers(data.cookies) - - getProjectById(baseUrl, cookies, data.projects[i].id.replace("/", ":")) + + // verify permissions with user cookies + getProjectById(baseUrl, cookies, data.projects[iterationIndex].id.replace("/", ":")) listProjects(baseUrl, cookies) - createProjectExpectFail(baseUrl, cookies, `Test Create Project Should Fail ${i}`, data.clusterId, user.id) + const projectName = `${projectsPrefix}-should-fail-vu${metaVU.idInInstance}-${iterationIndex.toString().padStart(4, '0')}` + createProjectExpectFail(baseUrl, cookies, projectName, data.clusterId, user.id) res = logout(baseUrl, cookies); if (res.status !== 200) { @@ -348,12 +387,21 @@ export function createPRTBs(data) { } export function createCRTBs(data) { - const i = exec.scenario.iterationInTest + const iterationIndex = __ITER % data.users.length + + // Use modulo to get user index + const userIndex = __ITER % data.users.length + let user = data.users[userIndex] + + if (!user) { + console.log(`No user found at index ${userIndex}, users length: ${data.users.length}`) + return + } let clusterRoleTemplate = { "type": "roleTemplate", - "name": `Dartboard CRTB ${i}`, - "description": `Dartboard Test Cluster RT ${i}`, + "name": `${clusterRoleTemplatePrefix} ${iterationIndex}`, + "description": `Dartboard Test Cluster RT ${iterationIndex}`, "rules": [ { "apiGroups": [ @@ -384,27 +432,31 @@ export function createCRTBs(data) { } let roleTemplateId = JSON.parse(res.body).id - let user = data.users[i] res = createCRTB(baseUrl, data.cookies, data.clusterId, roleTemplateId, user.id) + check(res, { + 'CRTB post returns 201 (created)': (r) => r.status === 201, + }) - if (res.status !== 201) { - console.log("\nResponse: ", JSON.stringify(res, null, 2), "\n") - fail("Failed to create CRTB") - } - - // log in as user - if (!login(baseUrl, {}, user.username, testUserPassword)) { - fail(`could not login to cluster as ${user.username}`) + // log in as user + let loginRes = login(baseUrl, {}, user.username, testUserPassword) + if (loginRes.status !== 200) { + console.warn(`could not login to cluster as ${user.username}, status: ${loginRes.status}`) + return // Don't fail, just skip verification for this iteration } const cookies = getCookies(baseUrl) // updateRBACNumbers with admin cookies updateRBACNumbers(data.cookies) - getProjectById(baseUrl, cookies, data.projects[i].id.replace("/", ":")) + // Get a project index safely + const projectIndex = __ITER % data.projects.length + + // verify permissions with user cookies + getProjectById(baseUrl, cookies, data.projects[projectIndex].id.replace("/", ":")) listProjects(baseUrl, cookies) - createProjectExpectFail(baseUrl, cookies, `Test Create Project Should Fail ${i}`, data.clusterId, user.id) + const projectName = `${projectsPrefix}-should-fail-vu${metaVU.idInInstance}-${iterationIndex.toString().padStart(4, '0')}` + createProjectExpectFail(baseUrl, cookies, projectName, data.clusterId, user.id) res = logout(baseUrl, cookies); if (res.status !== 200) { diff --git a/k6/rbac/rbac_utils.js b/k6/rbac/rbac_utils.js index 5262918b8..2189e8f30 100644 --- a/k6/rbac/rbac_utils.js +++ b/k6/rbac/rbac_utils.js @@ -4,7 +4,7 @@ import http from 'k6/http'; /* Usernames are prefixed with "user", password defaults to "useruseruser" if not set */ -export function createUser(baseUrl, cookies, displayName, userName, password = "useruseruser") { +export function createUser(baseUrl, cookies, displayName, userName = "test-user", password = "useruseruser") { const res = http.post(`${baseUrl}/v3/users`, JSON.stringify({ "type": "user", @@ -13,9 +13,12 @@ export function createUser(baseUrl, cookies, displayName, userName, password = " "enabled": true, "mustChangePassword": false, "password": password, - "username": `user-${userName}` + "username": `${userName}` }), - { cookies: cookies } + { + headers: { accept: "application/json", "content-type": "application/json" }, + cookies: cookies + } ) check(res, { @@ -54,7 +57,7 @@ export function listUsers(baseUrl, cookies) { fail("Status check failed or did not receive list of Users data") } - return res + return { res: res, usersData: usersData } } export function listRoleTemplates(baseUrl, cookies) { @@ -332,13 +335,13 @@ export function createCRTB(baseUrl, cookies, clusterId, roleTemplateId, userId) export function deleteUsersByPrefix(baseUrl, cookies, prefix = "Dartboard ") { let deletedAll = true - let res = listUsers(baseUrl, cookies) + let {res: res, usersData: usersData} = listUsers(baseUrl, cookies) if (res.status !== 200) { console.log("list users status: ", res.status) return false } - JSON.parse(res.body)["data"].filter(r => ("description" in r) && r["description"].startsWith(prefix)).forEach(r => { + usersData.filter(r => ("description" in r) && r["description"].startsWith(prefix)).forEach(r => { res = http.del(`${baseUrl}/v3/users/${r["id"]}`, { cookies: cookies }) if (res.status !== 200 && res.status !== 204) { @@ -403,7 +406,7 @@ export function deleteRoleTemplatesByPrefix(baseUrl, cookies, prefix = "Dartboar return deletedAll } -export function deleteCRTBsByDescriptionLabel(baseUrl, cookies, label = { "description": "Dartboard" }) { +export function deleteCRTBsByDescriptionLabel(baseUrl, cookies, label = "Dartboard") { let deletedAll = true let res = listCRTBs(baseUrl, cookies) @@ -412,7 +415,7 @@ export function deleteCRTBsByDescriptionLabel(baseUrl, cookies, label = { "descr return false } - JSON.parse(res.body)["data"].filter(r => ("labels" in r) && ("description" in r["labels"]) && r["labels"].description == label["description"]).forEach(r => { + JSON.parse(res.body)["data"].filter(r => ("labels" in r) && ("description" in r["labels"]) && r["labels"].description == label).forEach(r => { res = http.del(`${baseUrl}/v3/clusterroletemplatebindings/${r["id"]}`, { cookies: cookies }) @@ -429,7 +432,7 @@ export function deleteCRTBsByDescriptionLabel(baseUrl, cookies, label = { "descr return deletedAll } -export function deletePRTBsByDescriptionLabel(baseUrl, cookies, label = { "description": "Dartboard" }) { +export function deletePRTBsByDescriptionLabel(baseUrl, cookies, label = "Dartboard") { let deletedAll = true let res = listPRTBs(baseUrl, cookies) @@ -438,7 +441,7 @@ export function deletePRTBsByDescriptionLabel(baseUrl, cookies, label = { "descr return false } - JSON.parse(res.body)["data"].filter(r => ("labels" in r) && ("description" in r["labels"]) && r["labels"].description == label["description"]).forEach(r => { + JSON.parse(res.body)["data"].filter(r => ("labels" in r) && ("description" in r["labels"]) && r["labels"].description == label).forEach(r => { res = http.del(`${baseUrl}/v3/projectroletemplatebindings/${r["id"]}`, { cookies: cookies }) diff --git a/k6/tests/load_projects_rbac.js b/k6/tests/load_projects_rbac.js index 0cd9993de..60f21cbbf 100644 --- a/k6/tests/load_projects_rbac.js +++ b/k6/tests/load_projects_rbac.js @@ -403,9 +403,9 @@ function getProjectWithRetry(baseUrl, cookies, projectId, maxRetries = 5) { return {} } -function createNormanProjectWithRetry(baseUrl, projectBody, cookies, maxRetries = 5) { +function createNormanProjectWithRetry(baseUrl, cookies, projectBody, maxRetries = 5) { for (let retry = 0; retry < maxRetries; retry++) { - const res = projectUtil.createNormanProject(baseUrl, projectBody, cookies) + const res = projectUtil.createNormanProject(baseUrl, cookies, projectBody) if (res.status === 201) { return res } @@ -435,7 +435,7 @@ function createProjectsAndNamespaces(data, startIndex, endIndex) { }) console.log(`Creating project ${i + 1}/${projectCount}: ${projectName}`) - const projectRes = createNormanProjectWithRetry(baseUrl, projectBody, data.adminCookies, 5) + const projectRes = createNormanProjectWithRetry(baseUrl, data.adminCookies, projectBody, 5) if (projectRes.status === 201) { const projectData = JSON.parse(projectRes.body) diff --git a/k6/vai/filter_change_configmaps.js b/k6/vai/filter_change_configmaps.js index 70b66de01..a870099ca 100644 --- a/k6/vai/filter_change_configmaps.js +++ b/k6/vai/filter_change_configmaps.js @@ -239,7 +239,7 @@ export function setup() { containerDefaultResourceLimit: {}, namespaceDefaultResourceQuota: {} }) - let projRes = projectsUtil.createNormanProject(baseUrl, projectBody, cookies) + let projRes = projectsUtil.createNormanProject(baseUrl, cookies, projectBody) if (projRes.status !== 201) { fail("Dartboard test project not created") } From 07740a0ae0650bc99e7049889c7dc847b1e373ad Mon Sep 17 00:00:00 2001 From: Iramis Valentin <85186645+git-ival@users.noreply.github.com> Date: Wed, 18 Feb 2026 19:19:55 -0500 Subject: [PATCH 2/3] resolve copilot feedback + add expectedstatuses to expected reqs to avoid false negatives on 'http_req_failed' metric --- k6/generic/generic_utils.js | 7 +++++- k6/projects/project_utils.js | 2 +- k6/rancher/rancher_utils.js | 28 +++++++++++++++++++++++- k6/rbac/create_RTBs.js | 41 ++++++++++++++++++++++++------------ k6/rbac/rbac_utils.js | 2 +- 5 files changed, 63 insertions(+), 17 deletions(-) diff --git a/k6/generic/generic_utils.js b/k6/generic/generic_utils.js index 76c17fb4e..7adf3f223 100644 --- a/k6/generic/generic_utils.js +++ b/k6/generic/generic_utils.js @@ -139,6 +139,11 @@ export function getPathBasename(filePath) { } export function getRandomElements(arr, count) { - const shuffled = [...arr].sort(() => 0.5 - Math.random()); + const shuffled = [...arr]; + for (let i = shuffled.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; + } return shuffled.slice(0, count); } + diff --git a/k6/projects/project_utils.js b/k6/projects/project_utils.js index 9fe505902..b640960de 100644 --- a/k6/projects/project_utils.js +++ b/k6/projects/project_utils.js @@ -20,7 +20,7 @@ export function cleanupMatchingProjects(baseUrl, cookies, namePrefix) { }, cookies: cookies }) check(res, { - '/v1/management.cattle.io.projects returns status 200': (r) => r.status === 200, + 'GET /v3/projects returns status 200': (r) => r.status === 200, }) JSON.parse(res.body)["data"].filter(r => r["name"].startsWith(namePrefix)).forEach(r => { res = http.del(`${baseUrl}/${normanProjectsPath}/${r["id"]}`, { cookies: cookies }) diff --git a/k6/rancher/rancher_utils.js b/k6/rancher/rancher_utils.js index 286adde45..5f56c7f60 100644 --- a/k6/rancher/rancher_utils.js +++ b/k6/rancher/rancher_utils.js @@ -19,7 +19,7 @@ export function login(baseUrl, cookies, username, password) { // Only add cookies if they exist and are not empty if (cookies && Object.keys(cookies).length > 0) { - console.log("Using cookies: ", cookies) + console.log("Using cookies, count:", Object.keys(cookies).length) params.cookies = cookies } const response = http.post( @@ -377,6 +377,32 @@ export function createImportedCluster(baseUrl, cookies, name) { }) } +export function getProvisioningClusters(baseUrl, cookies) { + const response = http.get(`${baseUrl}/v1/provisioning.cattle.io.clusters?pagesize=100000&exclude=metadata.managedFields`, { + headers: { + accept: 'application/json', + }, + cookies: cookies, + }) + check(response, { + 'GET v1/provisioning.cattle.io.clusters returns status 200': (r) => r.status === 200, + }) + return response +} + +export function getManagementClusters(baseUrl, cookies) { + const response = http.get(`${baseUrl}/v1/management.cattle.io.clusters?pagesize=100000&exclude=metadata.managedFields`, { + headers: { + accept: 'application/json', + }, + cookies: cookies, + }) + check(response, { + 'GET v1/management.cattle.io.clusters returns status 200': (r) => r.status === 200, + }) + return response +} + export function logout(baseUrl, cookies) { const response = http.post(`${baseUrl}/v3/tokens?action=logout`, '{}', { headers: { diff --git a/k6/rbac/create_RTBs.js b/k6/rbac/create_RTBs.js index 2d9bcb9b3..13329f622 100644 --- a/k6/rbac/create_RTBs.js +++ b/k6/rbac/create_RTBs.js @@ -1,12 +1,10 @@ import { check, fail, sleep } from 'k6'; -import exec from 'k6/execution'; import http from 'k6/http'; -// import { randomUUID } from 'k6/crypto'; import { Trend } from 'k6/metrics'; import { vu as metaVU } from 'k6/execution' import * as k6Util from "../generic/k6_utils.js"; import { - getCookies, login, logout + getCookies, login, logout, getManagementClusters } from "../rancher/rancher_utils.js"; import { getRandomElements } from "../generic/generic_utils.js"; import { getClusterIds, getCurrentUserPrincipalId, getPrincipalIds } from "../rancher/rancher_users_utils.js"; @@ -17,7 +15,7 @@ import { cleanupMatchingProjects as deleteProjectsByPrefix } from "../projects/project_utils.js"; import { - createUser, listUsers, listRoles, listRoleTemplates, + createUser, listRoles, listRoleTemplates, listRoleBindings, listClusterRoles, listClusterRoleBindings, listCRTBs, listPRTBs, deleteRoleTemplatesByPrefix, deleteUsersByPrefix, createRoleTemplate, createPRTB, createCRTB, @@ -83,7 +81,7 @@ const numPRTBsTrend = new Trend('num_prtbs'); // Test functions, in order of execution export function setup() { // log in - if (!login(baseUrl, null, username, password)) { + if (login(baseUrl, null, username, password).status !== 200) { fail(`could not login to cluster`) } const cookies = getCookies(baseUrl) @@ -94,6 +92,15 @@ export function setup() { let clusterIds = getClusterIds(baseUrl, cookies) let clusterId = getRandomElements(clusterIds, 1)[0] console.log(`Utilizing Cluster with the ID ${clusterId}`) + + const clustersResponse = getManagementClusters(baseUrl, cookies) + if (clustersResponse.status === 200) { + const clusters = JSON.parse(clustersResponse.body).data + const cluster = clusters.find(c => c.id === clusterId) + if (cluster) { + console.log(`Selected Cluster Name: ${cluster.spec.displayName}`) + } + } let myId = getCurrentUserPrincipalId(baseUrl, cookies) // Create Projects, and Users @@ -106,7 +113,7 @@ export function setup() { clusterId: "local", creatorId: myId, }) - let res = createProject(baseUrl, cookies, projectBody, clusterId, myId) + let res = createProject(baseUrl, cookies, projectBody) if (res.status !== 201) { console.log("create project status: ", res.status) fail("Failed to create all expected Projects") @@ -218,7 +225,7 @@ function cleanup(cookies) { console.log("Role Templates deleted status: ", roleTemplatesDeleted) console.log("PRTBs deleted status: ", prtbsDeleted) console.log("CRTBs deleted status: ", crtbsDeleted) - // Don't fail on cleanup issues, just log them + // Fail the test if any cleanup operation did not complete successfully fail("failed to delete all objects created by test") } } @@ -234,7 +241,11 @@ function createUserExpectFail(baseUrl, cookies, name, password = "useruseruser") "password": password, "username": `user-${userName}` }), - { cookies: cookies } + { + cookies: cookies, + // prevent k6 from marking non-2xx responses as failed requests for this request, since we're explicitly testing for failure + responseCallback: http.expectedStatuses(401, 403) + } ) let checkOK = check(res, { @@ -285,7 +296,11 @@ function createProjectExpectFail(baseUrl, cookies, name, clusterId, userPrincipa } } }), - { cookies: cookies } + { + cookies: cookies, + // prevent k6 from marking non-2xx responses as failed requests for this request, since we're explicitly testing for failure + responseCallback: http.expectedStatuses(401, 403) + } ) let checkOK = check(res, { @@ -364,8 +379,8 @@ export function createPRTBs(data) { // log in as user let loginRes = login(baseUrl, {}, user.username, testUserPassword) - if (loginRes.status !== 200) { - console.warn(`could not login to cluster as ${user.username}, status: ${loginRes.status}`) + if (!loginRes || loginRes.status !== 200) { + console.warn(`could not login to cluster as ${user.username}, status: ${loginRes ? loginRes.status : 'unknown'}`) return // Don't fail, just skip verification for this iteration } const cookies = getCookies(baseUrl) @@ -440,8 +455,8 @@ export function createCRTBs(data) { // log in as user let loginRes = login(baseUrl, {}, user.username, testUserPassword) - if (loginRes.status !== 200) { - console.warn(`could not login to cluster as ${user.username}, status: ${loginRes.status}`) + if (!loginRes || loginRes.status !== 200) { + console.warn(`could not login to cluster as ${user.username}, status: ${loginRes ? loginRes.status : 'unknown'}`) return // Don't fail, just skip verification for this iteration } const cookies = getCookies(baseUrl) diff --git a/k6/rbac/rbac_utils.js b/k6/rbac/rbac_utils.js index 2189e8f30..4bb72c2f4 100644 --- a/k6/rbac/rbac_utils.js +++ b/k6/rbac/rbac_utils.js @@ -2,7 +2,7 @@ import { check, fail } from 'k6'; import http from 'k6/http'; /* - Usernames are prefixed with "user", password defaults to "useruseruser" if not set + Username defaults to "test-user" and password defaults to "useruseruser" if not set */ export function createUser(baseUrl, cookies, displayName, userName = "test-user", password = "useruseruser") { const res = http.post(`${baseUrl}/v3/users`, From f7438f8d419682617a91da06c7ef1c7c849b5d04 Mon Sep 17 00:00:00 2001 From: Iramis Valentin <85186645+git-ival@users.noreply.github.com> Date: Tue, 24 Feb 2026 18:10:06 -0500 Subject: [PATCH 3/3] update prefixes, update function params, update .gitignore --- .gitignore | 11 ++++++----- k6/rbac/create_RTBs.js | 27 ++++++++++++++------------- k6/rbac/rbac_utils.js | 8 ++++---- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index 2dde72219..c30dbf8f1 100644 --- a/.gitignore +++ b/.gitignore @@ -16,11 +16,12 @@ tofu/main/*/*config # Others scripts/utils/*.txt -scripts/utils/export-metrics/metrics* -scripts/utils/export-metrics/chunks_head -scripts/utils/export-metrics/wal -scripts/utils/export-metrics/queries.active -scripts/utils/export-metrics/*.tar.gz +chunks_head +wal +queries.active +*.tar +*.tar.gz +.env # Binaries /dartboard diff --git a/k6/rbac/create_RTBs.js b/k6/rbac/create_RTBs.js index 13329f622..7ae5f823e 100644 --- a/k6/rbac/create_RTBs.js +++ b/k6/rbac/create_RTBs.js @@ -28,10 +28,11 @@ const vus = __ENV.VUS || 5 const projectCount = Number(__ENV.PROJECT_COUNT) || 10 const userCount = Number(__ENV.USER_COUNT) || 10 const testUserPassword = __ENV.TEST_USER_PASSWORD -const userPrefix = __ENV.USER_PREFIX || 'test-user' -const projectsPrefix = "rtbs-test" -const projectRoleTemplatePrefix = "Dartboard PRTB" -const clusterRoleTemplatePrefix = "Dartboard CRTB" +const testPrefix = "create-RTBs" +const userPrefix = `${testPrefix}-${__ENV.USER_PREFIX || 'test-user'}` +const projectsPrefix = `${testPrefix}-rtbs-test` +const projectRoleTemplatePrefix = `${testPrefix}-Dartboard-PRTB` +const clusterRoleTemplatePrefix = `${testPrefix}-Dartboard-CRTB` // Option setting const baseUrl = __ENV.BASE_URL @@ -124,7 +125,7 @@ export function setup() { let createdUsers = [] for (let numUsers = 0; numUsers < userCount; numUsers++) { const userName = `${userPrefix}-${crypto.randomUUID()}`; - let res = createUser(baseUrl, cookies, `Dartboard Test User ${numUsers + 1}`, userName, testUserPassword) + let res = createUser(baseUrl, cookies, `Dartboard Test User ${numUsers + 1}`, userPrefix, userName, testUserPassword) if (res.status !== 201) { console.log("create user status: ", res.status) fail("Failed to create all expected Users") @@ -211,13 +212,13 @@ function updateRBACNumbers(cookies) { } function cleanup(cookies) { - console.log("Cleaning up Projects, Users, Role Templates, CRTBs, and PRTBs with description label 'Dartboard' or name starting with test prefixes") + console.log(`Cleaning up Projects, Users, Role Templates, CRTBs, and PRTBs with description label starting with test prefixes`) let projectsDeleted = deleteProjectsByPrefix(baseUrl, cookies, projectsPrefix) // Use "Dartboard" prefix to match the description format "Dartboard Test X" - let usersDeleted = deleteUsersByPrefix(baseUrl, cookies, "Dartboard") - let prtbsDeleted = deletePRTBsByDescriptionLabel(baseUrl, cookies, "Dartboard") - let crtbsDeleted = deleteCRTBsByDescriptionLabel(baseUrl, cookies, "Dartboard") - let roleTemplatesDeleted = deleteRoleTemplatesByPrefix(baseUrl, cookies, "Dartboard") + let usersDeleted = deleteUsersByPrefix(baseUrl, cookies, userPrefix) + let prtbsDeleted = deletePRTBsByDescriptionLabel(baseUrl, cookies, projectRoleTemplatePrefix) + let crtbsDeleted = deleteCRTBsByDescriptionLabel(baseUrl, cookies, clusterRoleTemplatePrefix) + let roleTemplatesDeleted = deleteRoleTemplatesByPrefix(baseUrl, cookies, projectRoleTemplatePrefix) if (!projectsDeleted || !usersDeleted || !roleTemplatesDeleted || !prtbsDeleted || !crtbsDeleted) { console.log("Projects deleted status: ", projectsDeleted) @@ -338,7 +339,7 @@ export function createPRTBs(data) { let projectRoleTemplate = { "type": "roleTemplate", "name": `${projectRoleTemplatePrefix} ${iterationIndex}`, - "description": `Dartboard Test Project RT ${iterationIndex}`, + "description": `${projectRoleTemplatePrefix} ${iterationIndex}`, "rules": [ { "apiGroups": [ @@ -372,7 +373,7 @@ export function createPRTBs(data) { const projectId = project.id.replace("/", ":") - res = createPRTB(baseUrl, data.cookies, projectId, roleTemplateId, user.id) + res = createPRTB(baseUrl, data.cookies, projectId, roleTemplateId, user.id, projectRoleTemplatePrefix) check(res, { 'PRTB post returns 201 (created)': (r) => r.status === 201, }) @@ -416,7 +417,7 @@ export function createCRTBs(data) { let clusterRoleTemplate = { "type": "roleTemplate", "name": `${clusterRoleTemplatePrefix} ${iterationIndex}`, - "description": `Dartboard Test Cluster RT ${iterationIndex}`, + "description": `${clusterRoleTemplatePrefix} ${iterationIndex}`, "rules": [ { "apiGroups": [ diff --git a/k6/rbac/rbac_utils.js b/k6/rbac/rbac_utils.js index 4bb72c2f4..3c6ae7037 100644 --- a/k6/rbac/rbac_utils.js +++ b/k6/rbac/rbac_utils.js @@ -4,12 +4,12 @@ import http from 'k6/http'; /* Username defaults to "test-user" and password defaults to "useruseruser" if not set */ -export function createUser(baseUrl, cookies, displayName, userName = "test-user", password = "useruseruser") { +export function createUser(baseUrl, cookies, displayName, description = `Dartboard ${displayName}`, userName = "test-user", password = "useruseruser") { const res = http.post(`${baseUrl}/v3/users`, JSON.stringify({ "type": "user", "name": displayName, - "description": `Dartboard ${displayName}`, + "description": description, "enabled": true, "mustChangePassword": false, "password": password, @@ -277,13 +277,13 @@ export function createGlobalRoleBinding(baseUrl, params, userId, roles = ["user" } -export function createPRTB(baseUrl, cookies, projectId, roleTemplateId, userId) { +export function createPRTB(baseUrl, cookies, projectId, roleTemplateId, userId, description = "Dartboard") { const res = http.post( `${baseUrl}/v3/projectroletemplatebindings`, JSON.stringify({ "type": "projectroletemplatebinding", "labels": { - "description": "Dartboard" + "description": description }, "roleTemplateId": roleTemplateId, "userPrincipalId": `local://${userId}`,