Merge pull request #43 from git-ival/lastused-tokens-k6

Basic Lastused tokens k6 test
This commit is contained in:
Iramis Valentin 2025-04-14 13:17:50 -04:00 committed by GitHub
commit 3f2aed0b86
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 749 additions and 436 deletions

View File

@ -241,7 +241,7 @@ func K6run(kubeconfig, testPath string, envVars, tags map[string]string, printLo
output = os.Stdout
}
err = Exec(kubeconfig, output, "run", "k6", "--image="+k6Image, "--namespace=tester", "--rm", "--stdin", "--restart=Never", "--overrides="+string(overrideJSON))
err = Exec(kubeconfig, output, "run", "k6", "--http-debug=full", "--image="+k6Image, "--namespace=tester", "--rm", "--stdin", "--restart=Never", "--overrides="+string(overrideJSON))
if err != nil {
return err
}

View File

@ -3,6 +3,7 @@ import exec from 'k6/execution';
import http from 'k6/http';
import {Gauge} from 'k6/metrics';
import {getCookies, login} from "./rancher_utils.js";
import {getPrincipalIds, getCurrentUserPrincipalId, getClusterIds} from "./rancher_users_utils.js"
// Parameters
const projectCount = Number(__ENV.PROJECT_COUNT)
@ -52,47 +53,12 @@ export function setup() {
// return data that remains constant throughout the test
return {
cookies: cookies,
principalIds: getPrincipalIds(cookies),
myId: getMyId(cookies),
clusterIds: getClusterIds(cookies)
principalIds: getPrincipalIds(baseUrl, cookies),
myId: getCurrentUserPrincipalId(baseUrl, cookies),
clusterIds: getClusterIds(baseUrl, cookies)
}
}
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')
}
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"])
}
function cleanup(cookies) {
let res = http.get(`${baseUrl}/v1/management.cattle.io.projects`, {cookies: cookies})
check(res, {

21
k6/k6_with_env.sh Executable file
View File

@ -0,0 +1,21 @@
#!/usr/bin/env bash
env_file=${1:-"./.env"} # path to the source-able .env file containing relevant variables
test_file=${2} # path to the k6 test file to run
iters=${3:-1} # the number of iterations of the test to loop through
delay=${4:-15} # the delay between iterations, in minutes
address=${5:-"localhost:6565"} # the domain:PORT address to the API server https://grafana.com/docs/k6/latest/using-k6/k6-options/reference/#address
counter=1
sleepDuration=$((delay * 60))
while [ ${counter} -le "${iters}" ]; do
iterStart=$(date -u '+%FT%T%:z')
echo "Started iteration at: ${iterStart}"
# shellcheck disable=SC1090
source "${env_file}" && k6 run -a "${address}" --out json="${test_file%.js*}-output${counter}.json" "${test_file}"
kubectl -n cattle-system logs -l status.phase=Running -l app=rancher -c rancher --timestamps --since-time="${iterStart}" --tail=9999999 >"rancher_logs-test_${test_file%.js*}.txt"
if [[ ${iters} -ge 2 ]]; then
sleep ${sleepDuration}
fi
counter=$((counter + 1))
done

View File

@ -1,5 +1,4 @@
import {getCookies, firstLogin, createImportedCluster, logout} from "./rancher_utils.js";
import {getCookies, createImportedCluster, firstLogin, logout} from "./rancher_utils.js";
export const options = {
insecureSkipTLSVerify: true,

149
k6/rancher_users_utils.js Normal file
View File

@ -0,0 +1,149 @@
import { check, fail, sleep } from 'k6'
import http from 'k6/http'
export function getUserId(baseUrl, cookies) {
let response = http.get(`${baseUrl}/v3/users?me=true`, {
headers: {
accept: 'application/json',
},
cookies: {cookies},
})
check(response, {
'reading user details was successful': (r) => r.status === 200,
})
if (response.status !== 200) {
fail('could not query user details')
}
return JSON.parse(response.body).data[0].id
}
export function getUserPreferences(baseUrl, cookies) {
let response = http.get(`${baseUrl}/v1/userpreferences`, {
headers: {
accept: 'application/json',
},
cookies: {cookies},
})
check(response, {
'preferences can be queried': (r) => r.status === 200,
})
return JSON.parse(response.body)["data"][0]
}
export function setUserPreferences(baseUrl, cookies, userId, userPreferences) {
let response = http.put(
`${baseUrl}/v1/userpreferences/${userId}`,
JSON.stringify(userPreferences),
{
headers: {
accept: 'application/json',
'content-type': 'application/json',
},
cookies: {cookies},
}
)
check(response, {
'preferences can be set': (r) => r.status === 200,
})
return response;
}
export function getPrincipalIds(baseUrl, 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])
}
export function getPrincipalById(baseUrl, cookies, id) {
const response = http.get(
`${baseUrl}/v3/principals/${id}`,
{cookies: cookies}
)
if (response.status !== 200) {
fail('could not get principal by ID')
}
return JSON.parse(response.body).data[0]
}
export function getUsers(baseUrl, params = null) {
const response = http.get(`${baseUrl}/v3/users`, params);
console.log("GET users status: ", response.status);
check(response, {'status is 200': (r) => r.status === 200,}) || fail('could not get list of users');
return JSON.parse(response.body)["data"];
}
export function getCurrentUserPrincipal(baseUrl, cookies) {
const response = http.get(
`${baseUrl}/v3/principals?me=true`,
{cookies: cookies}
)
if (response.status !== 200) {
fail('could not get current User\'s Principal')
}
return JSON.parse(response.body).data[0]
}
export function getCurrentUserId(baseUrl, cookies) {
const response = http.get(
`${baseUrl}/v3/users?me=true`,
{cookies: cookies}
)
if (response.status !== 200) {
fail('could not get my user')
}
return JSON.parse(response.body).data[0].id
}
export function getCurrentUserPrincipalId(baseUrl, cookies) {
const response = http.get(
`${baseUrl}/v3/users?me=true`,
{cookies: cookies}
)
if (response.status !== 200) {
fail('could not get my user')
}
return JSON.parse(response.body).data[0].principalIds[0]
}
export function getClusterIds(baseUrl, 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"])
}
export function createUser(baseUrl, params = null, id) {
const res = http.post(`${baseUrl}/v3/users`,
JSON.stringify({
"type": "user",
"name": `Dartboard Test User ${id}`,
"description": `Dartboard Test User ${id}`,
"enabled": true,
"mustChangePassword": false,
"password": "useruseruser",
"username": `user-${id}`
}),
params
)
sleep(0.1)
if (res.status != 201) {
console.log("CREATE user failed:\n", JSON.stringify(res, null, 2))
}
check(res, {
'/v3/users returns status 201': (r) => r.status === 201,
})
return JSON.parse(res.body)
}

View File

@ -1,443 +1,419 @@
import { check, fail, sleep } from 'k6'
import http from 'k6/http'
import { getUserId, getUserPreferences, setUserPreferences } from "./rancher_users_utils.js"
import encoding from 'k6/encoding';
export function getCookies(baseUrl) {
const response = http.get(`${baseUrl}/`)
return http.cookieJar().cookiesForURL(response.url)
const response = http.get(`${baseUrl}/`)
return http.cookieJar().cookiesForURL(response.url)
}
export function login(baseUrl, cookies, username, password) {
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},
}
)
check(response, {
'login works': (r) => r.status === 200 || r.status === 401,
})
return response.status === 200
}
export function timestamp() {
return new Date().toISOString()
}
export function getUserId(baseUrl, cookies) {
let response = http.get(`${baseUrl}/v3/users?me=true`, {
headers: {
accept: 'application/json',
},
cookies: {cookies},
})
check(response, {
'reading user details was successful': (r) => r.status === 200,
})
if (response.status !== 200) {
fail('could not query user details')
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 },
}
)
console.log("POST login: ", response.status);
return JSON.parse(response.body).data[0].id
}
check(response, {
'login works': (r) => r.status === 200 || r.status === 401,
})
export function getUserPreferences(baseUrl, cookies) {
let response = http.get(`${baseUrl}/v1/userpreferences`, {
headers: {
accept: 'application/json',
},
cookies: {cookies},
})
check(response, {
'preferences can be queried': (r) => r.status === 200,
})
return JSON.parse(response.body)["data"][0]
}
export function setUserPreferences(baseUrl, cookies, userId, userPreferences) {
let response = http.put(
`${baseUrl}/v1/userpreferences/${userId}`,
JSON.stringify(userPreferences),
{
headers: {
accept: 'application/json',
'content-type': 'application/json',
},
cookies: {cookies},
}
)
check(response, {
'preferences can be set': (r) => r.status === 200,
})
return response;
return response.status === 200
}
export function firstLogin(baseUrl, cookies, bootstrapPassword, password) {
let response
let response
if (login(baseUrl, cookies, "admin", bootstrapPassword)){
response = http.post(
`${baseUrl}/v3/users?action=changepassword`,
JSON.stringify({"currentPassword":bootstrapPassword, "newPassword":password}),
{
headers: {
accept: 'application/json',
'content-type': 'application/json; charset=UTF-8',
},
cookies: {cookies},
}
)
check(response, {
'password can be changed': (r) => r.status === 200,
})
if (response.status !== 200) {
fail('first password change was not successful')
}
}
else {
console.warn("bootstrap password already changed")
if(!login(baseUrl, cookies, "admin", password)) {
fail('neither bootstrap nor normal passwords were accepted')
}
}
const userId = getUserId(baseUrl, cookies)
const userPreferences = getUserPreferences(baseUrl, cookies);
userPreferences["data"]["locale"] = "en-us"
setUserPreferences(baseUrl, cookies, userId, userPreferences);
response = http.get(
`${baseUrl}/v1/management.cattle.io.settings`,
{
headers: {
accept: 'application/json',
},
cookies: {cookies},
}
if (login(baseUrl, cookies, "admin", bootstrapPassword)) {
response = http.post(
`${baseUrl}/v3/users?action=changepassword`,
JSON.stringify({ "currentPassword": bootstrapPassword, "newPassword": password }),
{
headers: {
accept: 'application/json',
'content-type': 'application/json; charset=UTF-8',
},
cookies: { cookies },
}
)
check(response, {
'Settings can be queried': (r) => r.status === 200,
'password can be changed': (r) => r.status === 200,
})
const settings = JSON.parse(response.body)
if (response.status !== 200) {
fail('first password change was not successful')
}
}
else {
console.warn("bootstrap password already changed")
if (!login(baseUrl, cookies, "admin", password)) {
fail('neither bootstrap nor normal passwords were accepted')
}
}
const userId = getUserId(baseUrl, cookies)
const userPreferences = getUserPreferences(baseUrl, cookies);
const firstLoginSetting = settings.data.filter(d => d.id === "first-login")[0]
if (firstLoginSetting === undefined) {
response = http.post(
`${baseUrl}/v1/management.cattle.io.settings`,
JSON.stringify({"type":"management.cattle.io.setting","metadata":{"name":"first-login"},"value":"false"}),
{
headers: {
accept: 'application/json',
'content-type': 'application/json',
},
cookies: {cookies},
}
)
check(response, {
'First login setting can be set': (r) => r.status === 201,
})
}
else {
firstLoginSetting["value"] = "false"
response = http.put(
`${baseUrl}/v1/management.cattle.io.settings/first-login`,
JSON.stringify(firstLoginSetting),
{
headers: {
accept: 'application/json',
'content-type': 'application/json',
},
cookies: {cookies},
}
)
check(response, {
'First login setting can be changed': (r) => r.status === 200,
})
}
userPreferences["data"]["locale"] = "en-us"
setUserPreferences(baseUrl, cookies, userId, userPreferences);
const eulaSetting = settings.data.filter(d => d.id === "eula-agreed")[0]
if (eulaSetting === undefined) {
response = http.post(
`${baseUrl}/v1/management.cattle.io.settings`,
JSON.stringify({"type":"management.cattle.io.setting","metadata":{"name":"eula-agreed"},"value":timestamp(),"default":timestamp()}),
{
headers: {
accept: 'application/json',
'content-type': 'application/json',
},
cookies: {cookies},
}
)
check(response, {
'EULA setting can be set': (r) => r.status === 201,
})
}
else {
eulaSetting["value"] = timestamp()
response = http.put(
`${baseUrl}/v1/management.cattle.io.settings/eula-agreed`,
JSON.stringify(eulaSetting),
{
headers: {
accept: 'application/json',
'content-type': 'application/json',
},
cookies: {cookies},
}
)
check(response, {
'EULA setting can be changed': (r) => r.status === 200,
})
response = http.get(
`${baseUrl}/v1/management.cattle.io.settings`,
{
headers: {
accept: 'application/json',
},
cookies: { cookies },
}
)
check(response, {
'Settings can be queried': (r) => r.status === 200,
})
const settings = JSON.parse(response.body)
const telemetrySetting = settings.data.find(d => d.id === "telemetry-opt")
if (telemetrySetting === undefined) {
response = http.post(
`${baseUrl}/v1/management.cattle.io.settings/telemetry-opt`,
JSON.stringify({"type":"management.cattle.io.setting","metadata":{"name":"telemetry-opt", "value": "out"}}),
{
headers: {
accept: 'application/json',
'content-type': 'application/json',
},
cookies: {cookies},
}
)
check(response, {
'telemetry setting can be set': (r) => r.status === 201,
})
}
else {
telemetrySetting["value"] = "out"
response = http.put(
`${baseUrl}/v1/management.cattle.io.settings/telemetry-opt`,
JSON.stringify(telemetrySetting),
{
headers: {
accept: 'application/json',
'content-type': 'application/json',
},
cookies: {cookies},
}
)
check(response, {
'telemetry setting can be changed': (r) => r.status === 200,
})
}
const firstLoginSetting = settings.data.filter(d => d.id === "first-login")[0]
if (firstLoginSetting === undefined) {
response = http.post(
`${baseUrl}/v1/management.cattle.io.settings`,
JSON.stringify({ "type": "management.cattle.io.setting", "metadata": { "name": "first-login" }, "value": "false" }),
{
headers: {
accept: 'application/json',
'content-type': 'application/json',
},
cookies: { cookies },
}
)
check(response, {
'First login setting can be set': (r) => r.status === 201,
})
}
else {
firstLoginSetting["value"] = "false"
response = http.put(
`${baseUrl}/v1/management.cattle.io.settings/first-login`,
JSON.stringify(firstLoginSetting),
{
headers: {
accept: 'application/json',
'content-type': 'application/json',
},
cookies: { cookies },
}
)
check(response, {
'First login setting can be changed': (r) => r.status === 200,
})
}
const eulaSetting = settings.data.filter(d => d.id === "eula-agreed")[0]
if (eulaSetting === undefined) {
response = http.post(
`${baseUrl}/v1/management.cattle.io.settings`,
JSON.stringify({ "type": "management.cattle.io.setting", "metadata": { "name": "eula-agreed" }, "value": timestamp(), "default": timestamp() }),
{
headers: {
accept: 'application/json',
'content-type': 'application/json',
},
cookies: { cookies },
}
)
check(response, {
'EULA setting can be set': (r) => r.status === 201,
})
}
else {
eulaSetting["value"] = timestamp()
response = http.put(
`${baseUrl}/v1/management.cattle.io.settings/eula-agreed`,
JSON.stringify(eulaSetting),
{
headers: {
accept: 'application/json',
'content-type': 'application/json',
},
cookies: { cookies },
}
)
check(response, {
'EULA setting can be changed': (r) => r.status === 200,
})
}
const telemetrySetting = settings.data.find(d => d.id === "telemetry-opt")
if (telemetrySetting === undefined) {
response = http.post(
`${baseUrl}/v1/management.cattle.io.settings/telemetry-opt`,
JSON.stringify({ "type": "management.cattle.io.setting", "metadata": { "name": "telemetry-opt", "value": "out" } }),
{
headers: {
accept: 'application/json',
'content-type': 'application/json',
},
cookies: { cookies },
}
)
check(response, {
'telemetry setting can be set': (r) => r.status === 201,
})
}
else {
telemetrySetting["value"] = "out"
response = http.put(
`${baseUrl}/v1/management.cattle.io.settings/telemetry-opt`,
JSON.stringify(telemetrySetting),
{
headers: {
accept: 'application/json',
'content-type': 'application/json',
},
cookies: { cookies },
}
)
check(response, {
'telemetry setting can be changed': (r) => r.status === 200,
})
}
}
export function Date(year, month, day) {
if (typeof year !== "undefined" &&
typeof month !== "undefined" &&
typeof day !== "undefined"
) return new Date(year, month, day)
return new Date()
}
export function timestamp() {
return new Date().toISOString()
}
export function addMinutes(date, minutes) {
return new Date(date.getTime() + (minutes*60000));
}
export function createImportedCluster(baseUrl, cookies, name) {
let response
let response
const userId = getUserId(baseUrl, cookies)
const userPreferences = getUserPreferences(baseUrl, cookies);
const userId = getUserId(baseUrl, cookies)
const userPreferences = getUserPreferences(baseUrl, cookies);
userPreferences["last-visited"] = "{\"name\":\"c-cluster-product\",\"params\":{\"cluster\":\"_\",\"product\":\"manager\"}}"
userPreferences["locale"] = "en-us"
userPreferences["seen-whatsnew"] = "\"v2.7.1\""
userPreferences["seen-cluster"] = "_"
setUserPreferences(baseUrl, cookies, userId, userPreferences)
userPreferences["last-visited"] = "{\"name\":\"c-cluster-product\",\"params\":{\"cluster\":\"_\",\"product\":\"manager\"}}"
userPreferences["locale"] = "en-us"
userPreferences["seen-whatsnew"] = "\"v2.7.1\""
userPreferences["seen-cluster"] = "_"
setUserPreferences(baseUrl, cookies, userId, userPreferences)
response = http.get(`${baseUrl}/v1/catalog.cattle.io.clusterrepos`, {
headers: {
accept: 'application/json',
},
cookies: cookies,
})
check(response, {
'querying clusterrepos works': (r) => r.status === 200,
})
response = http.get(`${baseUrl}/v1/catalog.cattle.io.clusterrepos`, {
headers: {
accept: 'application/json',
},
cookies: cookies,
})
check(response, {
'querying clusterrepos works': (r) => r.status === 200,
})
response = http.get(`${baseUrl}/v1/management.cattle.io.kontainerdrivers`, {
headers: {
accept: 'application/json',
cookies: cookies,
},
})
check(response, {
'querying kontainerdrivers works': (r) => r.status === 200,
})
response = http.get(`${baseUrl}/v1/management.cattle.io.kontainerdrivers`, {
headers: {
accept: 'application/json',
cookies: cookies,
},
})
check(response, {
'querying kontainerdrivers works': (r) => r.status === 200,
})
response = http.get(
`${baseUrl}/v1/catalog.cattle.io.clusterrepos/rancher-charts?link=index`,
{
headers: {
accept: 'application/json',
},
cookies: cookies,
}
)
check(response, {
'querying rancher-charts works': (r) => r.status === 200,
})
response = http.get(
`${baseUrl}/v1/catalog.cattle.io.clusterrepos/rancher-partner-charts?link=index`,
{
headers: {
accept: 'application/json',
},
cookies: cookies,
}
)
check(response, {
'querying rancher-partners-charts works': (r) => r.status === 200,
})
response = http.get(
`${baseUrl}/v1/catalog.cattle.io.clusterrepos/rancher-rke2-charts?link=index`,
{
headers: {
accept: 'application/json',
},
cookies: cookies,
}
)
check(response, {
'querying rancher-rke2-charts works': (r) => r.status === 200,
})
response = http.get(`${baseUrl}/v3/clusterroletemplatebindings`, {
headers: {
accept: 'application/json',
},
cookies: cookies,
})
check(response, {
'querying clusterroletemplatebindings works': (r) => r.status === 200,
})
response = http.get(`${baseUrl}/v1/management.cattle.io.roletemplates`, {
headers: {
accept: 'application/json',
},
cookies: cookies,
})
check(response, {
'querying roletemplates works': (r) => r.status === 200,
})
response = http.post(
`${baseUrl}/v1/provisioning.cattle.io.clusters`,
JSON.stringify({"type":"provisioning.cattle.io.cluster","metadata":{"namespace":"fleet-default","name":name},"spec":{}}),
{
headers: {
accept: 'application/json',
'content-type': 'application/json',
},
cookies: cookies,
}
)
check(response, {
'creating an imported cluster works': (r) => r.status === 201 || r.status === 409,
})
if (response.status === 409) {
console.warn(`cluster ${name} already exists`)
response = http.get(
`${baseUrl}/v1/catalog.cattle.io.clusterrepos/rancher-charts?link=index`,
{
headers: {
accept: 'application/json',
},
cookies: cookies,
}
)
check(response, {
'querying rancher-charts works': (r) => r.status === 200,
})
response = http.get(
`${baseUrl}/v1/provisioning.cattle.io.clusters/fleet-default/${name}`,
{
headers: {
accept: 'application/json',
},
cookies: cookies,
}
)
check(response, {
'querying clusters works': (r) => r.status === 200,
})
if (!response.status === 200) {
fail(`cluster ${name} not found`)
response = http.get(
`${baseUrl}/v1/catalog.cattle.io.clusterrepos/rancher-partner-charts?link=index`,
{
headers: {
accept: 'application/json',
},
cookies: cookies,
}
)
check(response, {
'querying rancher-partners-charts works': (r) => r.status === 200,
})
response = http.get(`${baseUrl}/v1/cluster.x-k8s.io.machinedeployments`, {
headers: {
accept: 'application/json',
},
cookies: cookies,
})
check(response, {
'querying machinedeployments works': (r) => r.status === 200,
})
response = http.get(
`${baseUrl}/v1/catalog.cattle.io.clusterrepos/rancher-rke2-charts?link=index`,
{
headers: {
accept: 'application/json',
},
cookies: cookies,
}
)
check(response, {
'querying rancher-rke2-charts works': (r) => r.status === 200,
})
response = http.get(`${baseUrl}/v1/rke.cattle.io.etcdsnapshots`, {
headers: {
accept: 'application/json',
},
cookies: cookies,
})
check(response, {
'querying etcdsnapshots works': (r) => r.status === 200,
})
response = http.get(`${baseUrl}/v3/clusterroletemplatebindings`, {
headers: {
accept: 'application/json',
},
cookies: cookies,
})
check(response, {
'querying clusterroletemplatebindings works': (r) => r.status === 200,
})
response = http.get(`${baseUrl}/v1/management.cattle.io.nodetemplates`, {
headers: {
accept: 'application/json',
},
cookies: cookies,
})
check(response, {
'querying nodetemplates works': (r) => r.status === 200,
})
response = http.get(`${baseUrl}/v1/management.cattle.io.roletemplates`, {
headers: {
accept: 'application/json',
},
cookies: cookies,
})
check(response, {
'querying roletemplates works': (r) => r.status === 200,
})
response = http.get(`${baseUrl}/v1/management.cattle.io.clustertemplates`, {
headers: {
accept: 'application/json',
},
cookies: cookies,
})
check(response, {
'querying clustertemplates works': (r) => r.status === 200,
})
response = http.post(
`${baseUrl}/v1/provisioning.cattle.io.clusters`,
JSON.stringify({ "type": "provisioning.cattle.io.cluster", "metadata": { "namespace": "fleet-default", "name": name }, "spec": {} }),
{
headers: {
accept: 'application/json',
'content-type': 'application/json',
},
cookies: cookies,
}
)
response = http.get(
`${baseUrl}/v1/management.cattle.io.clustertemplaterevisions`,
{
headers: {
accept: 'application/json',
},
cookies: cookies,
}
)
check(response, {
'querying clustertemplaterevisions works': (r) => r.status === 200,
})
check(response, {
'creating an imported cluster works': (r) => r.status === 201 || r.status === 409,
})
if (response.status === 409) {
console.warn(`cluster ${name} already exists`)
}
response = http.get(
`${baseUrl}/v1/provisioning.cattle.io.clusters/fleet-default/${name}`,
{
headers: {
accept: 'application/json',
},
cookies: cookies,
}
)
check(response, {
'querying clusters works': (r) => r.status === 200,
})
if (!response.status === 200) {
fail(`cluster ${name} not found`)
}
response = http.get(`${baseUrl}/v1/cluster.x-k8s.io.machinedeployments`, {
headers: {
accept: 'application/json',
},
cookies: cookies,
})
check(response, {
'querying machinedeployments works': (r) => r.status === 200,
})
response = http.get(`${baseUrl}/v1/rke.cattle.io.etcdsnapshots`, {
headers: {
accept: 'application/json',
},
cookies: cookies,
})
check(response, {
'querying etcdsnapshots works': (r) => r.status === 200,
})
response = http.get(`${baseUrl}/v1/management.cattle.io.nodetemplates`, {
headers: {
accept: 'application/json',
},
cookies: cookies,
})
check(response, {
'querying nodetemplates works': (r) => r.status === 200,
})
response = http.get(`${baseUrl}/v1/management.cattle.io.clustertemplates`, {
headers: {
accept: 'application/json',
},
cookies: cookies,
})
check(response, {
'querying clustertemplates works': (r) => r.status === 200,
})
response = http.get(
`${baseUrl}/v1/management.cattle.io.clustertemplaterevisions`,
{
headers: {
accept: 'application/json',
},
cookies: cookies,
}
)
check(response, {
'querying clustertemplaterevisions works': (r) => r.status === 200,
})
}
export function logout(baseUrl, cookies) {
const response = http.post(`${baseUrl}/v3/tokens?action=logout`, '{}', {
headers: {
accept: 'application/json',
'content-type': 'application/json',
},
cookies: cookies
})
const response = http.post(`${baseUrl}/v3/tokens?action=logout`, '{}', {
headers: {
accept: 'application/json',
'content-type': 'application/json',
},
cookies: cookies
})
check(response, {
'logging out works': (r) => r.status === 200,
})
check(response, {
'logging out works': (r) => r.status === 200,
})
}
export function generateAuthorizationHeader(token) {
return {
headers: {
'Authorization': `Basic ${encoding.b64encode(token)}`,
}
}
}
// Retries result-returning function for up to 10 times
// until a non-409 status is returned, waiting for up to 1s
// between retries
export function retryOnConflict(f) {
for (let i = 0; i < 9; i++) {
const res = f()
if (res.status !== 409) {
return res
}
// expected conflict. Sleep a bit and retry
sleep(Math.random())
for (let i = 0; i < 9; i++) {
const res = f()
if (res.status !== 409) {
return res
}
// all previous attempts failed, try one last time
return f()
// expected conflict. Sleep a bit and retry
sleep(Math.random())
}
// all previous attempts failed, try one last time
return f()
}

202
k6/tokens.js Normal file
View File

@ -0,0 +1,202 @@
import { check, fail, sleep } from 'k6';
import exec from 'k6/execution';
import http from 'k6/http';
import { getCookies, login, generateAuthorizationHeader, addMinutes } from "./rancher_utils.js";
import { getPrincipalIds, getCurrentUserId, getClusterIds, getCurrentUserPrincipal, createUser } from "./rancher_users_utils.js"
// Parameters
const tokenCount = Number(__ENV.TOKEN_COUNT)
const vus = Number(__ENV.K6_VUS) || 1
const perVuIterations = __ENV.PER_VU_ITERATIONS
// Option setting
const baseUrl = __ENV.BASE_URL
const username = __ENV.USERNAME
const password = __ENV.PASSWORD
// Option setting
export const options = {
insecureSkipTLSVerify: true,
setupTimeout: '8h',
scenarios: {
useTokens: {
executor: 'shared-iterations',
exec: 'useTokens',
vus: vus,
iterations: perVuIterations,
maxDuration: '1h',
},
},
// thresholds: {
// checks: ['rate>0.99']
// }
thresholds: {
http_req_failed: ['rate<=0.01'], // http errors should be less than 1%
http_req_duration: ['p(99)<=500'], // 95% of requests should be below 500ms
checks: ['rate>0.99'], // the rate of successful checks should be higher than 99%
}
}
export function setup() {
// log in
if (!login(baseUrl, {}, username, password)) {
fail(`could not login into cluster`)
}
const cookies = getCookies(baseUrl)
// delete leftovers, if any
cleanup(cookies)
// return data that remains constant throughout the test
let data = {
cookies: cookies,
principalIds: getPrincipalIds(baseUrl, cookies),
myUserId: getCurrentUserId(baseUrl, cookies),
myUserPrincipal: getCurrentUserPrincipal(baseUrl, cookies),
clusterIds: getClusterIds(baseUrl, cookies),
fullTokens: []
}
let createdTokens = []
for (let i = 0; i < tokenCount; i++) {
createdTokens.push(createToken(baseUrl, data, i))
}
createdTokens.forEach(r => {
data["fullTokens"].push(r)
})
return data
}
export function daysToMilliseconds(days) {
return days * 24 * 60 * 60 * 1000;
}
function createToken(baseUrl, data, idx) {
const clusterId = data.clusterIds[idx % data.clusterIds.length]
const id = `dartboard-${idx}`
const ttl = daysToMilliseconds(90) // Default ttl from UI is 90 days, but the API uses milliseconds
const body = {
// "clusterId": clusterId,
// "current": false,
"description": `Dartboard test token ${id}`,
// "enabled": true,
// "expired": false,
// "isDerived": true,
// "userId": data.myUserId,
// "userPrincipal": data.myUserPrincipal.id,
"metadata": {},
"ttl": ttl,
"type": "token",
}
const res = http.post(
`${baseUrl}/v3/tokens`,
JSON.stringify(body),
{ cookies: data.cookies }
)
let token = JSON.parse(res.body)
check(res, {
'POST v3/tokens returns status 201': (r) => r.status === 201,
})
return JSON.parse(res.body)
}
export function deleteTokens(data) {
let tokensData = getTokens(baseUrl, data)
tokensData.filter(r => ("description" in r) && r["description"].startsWith("Dartboard test")).forEach(r => {
let res = http.del(`${baseUrl}/v3/tokens/${r["id"]}`, { cookies: data.cookies })
check(res, {
'DELETE /v3/tokens returns status 200': (r) => r.status === 200 || r.status === 204,
})
})
}
export function getTokens(data) {
let res = http.get(`${baseUrl}/v3/tokens`, { cookies: data.cookies })
check(res, {
'GET /v3/tokens returns status 200': (r) => r.status === 200 || r.status === 204,
})
return JSON.parse(res.body)["data"]
}
export function getTokensByClusterID(data, clusterId) {
const clusterIdParam = `?clusterId=${clusterId}`
const checkString = `GET /v3/tokens${clusterIdParam} returns status 200`
let res = http.get(`${baseUrl}/v3/tokens${clusterIdParam}`, { cookies: data.cookies })
check(res, {
checkString: (r) => r.status === 200 || r.status === 204,
})
}
function getUsers(baseUrl, params) {
let res = http.get(`${baseUrl}/v3/users`, params);
check(res, { 'GET users returns status 200': (r) => r.status === 200, });
return res;
}
function useToken(baseUrl, bearerToken) {
let params = {
insecureSkipTLSVerify: true,
...generateAuthorizationHeader(bearerToken)
};
let res = getUsers(baseUrl, params)
check(res, { 'auth with bearer token': (r) => r.status === 200, });
return res, params
}
export function useTokens(data) {
let tokensData = getTokens(data);
let tokens = tokensData.filter(r => ("description" in r) && r["description"].startsWith("Dartboard test"));
let lastUsedAtTS = {};
// Use the tokens for the first time
tokens.forEach(token => {
let bearerToken = data["fullTokens"].find(f => token.id == f.id).token;
useToken(baseUrl, bearerToken)
});
tokensData = getTokens(data);
tokens = tokensData.filter(r => ("description" in r) && r["description"].startsWith("Dartboard test"));
// Track lastUsedAtTS for each token
tokens.forEach(token => {
// Make sure we're storing a valid number
const timestamp = Number(token.lastUsedAtTS);
if (isNaN(timestamp)) {
console.error(`Invalid timestamp for token ${token.id}:`, token.lastUsedAtTS);
};
lastUsedAtTS[token.id] = timestamp;
});
// Utilize each token and verify that lastUsedAtTS is updated to a newer timestamp
tokens.forEach(token => {
let bearerToken = data["fullTokens"].find(f => token.id == f.id).token;
let _, params = useToken(baseUrl, bearerToken)
createUser(baseUrl, params, `${token.id}-${exec.scenario.iterationInTest}`);
});
tokensData = getTokens(data);
tokens = tokensData.filter(r => ("description" in r) && r["description"].startsWith("Dartboard test"));
check(true, {
'all tokens lastUsedAtTS are newer after utilizing them': () =>
tokens.every(r => r.lastUsedAtTS > lastUsedAtTS[r.id])
});
}
function cleanup(data) {
deleteTokens(data);
let res = getUsers(baseUrl, { cookies: data.cookies });
let users = JSON.parse(res.body)["data"];
users.filter(r => r["description"].startsWith("Dartboard Test User ")).forEach(r => {
let res = http.del(`${baseUrl}/v3/users/${r["id"]}`, { cookies: data.cookies });
check(res, {
'DELETE /v3/users returns status 200': (r) => r.status === 200 || r.status === 204,
});
})
}

View File

@ -1,5 +1,5 @@
terraform {
required_version = "1.8.2"
required_version = ">= 1.8.1"
required_providers {
aws = {
source = "hashicorp/aws"