diff --git a/k6/generic/create_secrets_with_labels.js b/k6/generic/create_secrets_with_labels.js new file mode 100644 index 00000000..4b60b358 --- /dev/null +++ b/k6/generic/create_secrets_with_labels.js @@ -0,0 +1,88 @@ +import encoding from 'k6/encoding'; +import exec from 'k6/execution'; +import { createSecretsWithLabels, createStorageClasses } from '../generic/generic_utils.js'; +import { login, getCookies } from '../rancher/rancher_utils.js'; +import {fail} from 'k6'; +import {loadKubeconfig} from '../generic/k8s.js' +import * as k8s from '../generic/k8s.js' +import { customHandleSummary } from '../generic/k6_utils.js'; + +// Parameters +const namespace = "" +const token = __ENV.TOKEN +const secretCount = Number(__ENV.SECRET_COUNT) +const secretData = encoding.b64encode("a".repeat(10*1024)) +const key = __ENV.KEY || "blue" +const value = __ENV.VALUE || "green" +const clusterId = __ENV.CLUSTER || "local" +const vus = __ENV.VUS || 2 + +// Option setting +const kubeconfig = loadKubeconfig(__ENV.KUBECONFIG, __ENV.CONTEXT) +const baseUrl = kubeconfig["url"].replace(":6443", "") +const username = __ENV.USERNAME +const password = __ENV.PASSWORD + +export const handleSummary = customHandleSummary; + +export const options = { + insecureSkipTLSVerify: true, + tlsAuth: [ + { + cert: kubeconfig["cert"], + key: kubeconfig["key"], + }, + ], + + setupTimeout: '8h', + + scenarios: { + createResourceSecretsWithLabels: { + executor: 'shared-iterations', + exec: 'createResourceSecretsWithLabels', + vus: vus, + iterations: secretCount, + maxDuration: '1h', + }, + }, + thresholds: { + http_req_failed: ['rate<=0.01'], // http errors should be less than 1% + http_req_duration: ['p(99)<=500'], // 99% of requests should be below 500ms + checks: ['rate>0.99'], // the rate of successful checks should be higher than 99% + } +}; + +export function setup() { + // if session cookie was specified, save it + if (token) { + return { R_SESS: token } + } + + // if credentials were specified, log in + if (username && password) { + const res = http.post(`${baseUrl}/v3-public/localProviders/local?action=login`, JSON.stringify({ + "description": "UI session", + "responseType": "cookie", + "username": username, + "password": password + })) + + check(res, { + 'logging in returns status 200': (r) => r.status === 200, + }) + + pause() + + const cookies = http.cookieJar().cookiesForURL(res.url) + + return cookies + } + + return {} +} + +// create storage classes +export function createResourceSecretsWithLabels(cookies) { + const iter = exec.scenario.iterationInTest + createSecretsWithLabels(baseUrl, cookies, secretData, clusterId, namespace, iter, key, value) +} \ No newline at end of file diff --git a/k6/generic/create_storage_classes.js b/k6/generic/create_storage_classes.js new file mode 100644 index 00000000..836aec74 --- /dev/null +++ b/k6/generic/create_storage_classes.js @@ -0,0 +1,85 @@ +import encoding from 'k6/encoding'; +import exec from 'k6/execution'; +import { createStorageClasses } from '../generic/generic_utils.js'; +import { login, getCookies } from '../rancher/rancher_utils.js'; +import {fail} from 'k6'; +import * as k8s from '../generic/k8s.js' +import {loadKubeconfig} from '../generic/k8s.js' +import { customHandleSummary } from '../generic/k6_utils.js'; + +// Parameters +//const namespace = "longhorn-system" +const token = __ENV.TOKEN +const storageClassCount =Number(__ENV.STORAGECLASS_COUNT) +const clusterId = "local" +const vus = __ENV.VUS || 2 + +// Option setting +const kubeconfig = loadKubeconfig(__ENV.KUBECONFIG, __ENV.CONTEXT) +const baseUrl = kubeconfig["url"].replace(":6443", "") +const username = __ENV.USERNAME +const password = __ENV.PASSWORD + +export const handleSummary = customHandleSummary; + +export const options = { + insecureSkipTLSVerify: true, + tlsAuth: [ + { + cert: kubeconfig["cert"], + key: kubeconfig["key"], + }, + ], + + setupTimeout: '8h', + + scenarios: { + createResourcesStorageClasses: { + executor: 'shared-iterations', + exec: 'createResourcesStorageClasses', + vus: vus, + iterations: storageClassCount, + maxDuration: '1h', + }, + }, + thresholds: { + http_req_failed: ['rate<=0.01'], // http errors should be less than 1% + http_req_duration: ['p(99)<=500'], // 99% of requests should be below 500ms + checks: ['rate>0.99'], // the rate of successful checks should be higher than 99% + } +}; + +export function setup() { + // if session cookie was specified, save it + if (token) { + return { R_SESS: token } + } + + // if credentials were specified, log in + if (username && password) { + const res = http.post(`${baseUrl}/v3-public/localProviders/local?action=login`, JSON.stringify({ + "description": "UI session", + "responseType": "cookie", + "username": username, + "password": password + })) + + check(res, { + 'logging in returns status 200': (r) => r.status === 200, + }) + + pause() + + const cookies = http.cookieJar().cookiesForURL(res.url) + + return cookies + } + + return {} +} + +// create storage classes +export function createResourcesStorageClasses(cookies) { + const iter = exec.scenario.iterationInTest + createStorageClasses(baseUrl, cookies, clusterId, iter) +} diff --git a/k6/generic/generic_utils.js b/k6/generic/generic_utils.js index b4e4ac32..28459fea 100644 --- a/k6/generic/generic_utils.js +++ b/k6/generic/generic_utils.js @@ -31,7 +31,7 @@ export function createConfigMaps(baseUrl, cookies, data, clusterId, namespace, i // Required params: baseurl, cookies, data, clusterId, namespace, iter export function createSecrets(baseUrl, cookies, data, clusterId, namespace, iter) { - const name = `test-secrets-${iter}` + const name = `test-secret-${iter}` const url = clusterId === "local"? `${baseUrl}/v1/secrets` : @@ -58,6 +58,38 @@ export function createSecrets(baseUrl, cookies, data, clusterId, namespace, iter }) } +// Create Secrets with Labels +// Required params: baseurl, coookies, data, cluster, namespace, iter +export function createSecretsWithLabels(baseUrl, cookies, data, clusterId, namespace, iter, key, value) { + const name = `test-secret-${iter}` + const key_1 = key + const value_1 = value + + const url = clusterId === "local"? + `${baseUrl}/v1/secrets` : + `${baseUrl}/k8s/clusters/${clusterId}/v1/secrets` + + const res = http.post(`${url}`, + JSON.stringify({ + "metadata": { + "name": name, + "namespace": namespace, + "labels": {[key_1]:value_1} + }, + "data": {"data": data}, + "type": "opaque" + }), + { cookies: cookies } + ) + + sleep(0.1) + if (res.status != 201) { + console.log(res) + } + check(res, { + '/v1/secrets returns status 201': (r) => r.status === 201, + }) +} // Required params: baseurl, cookies, clusterId, namespace, iter export function createDeployments(baseUrl, cookies, clusterId, namespace, iter) { @@ -137,3 +169,34 @@ export function getPathBasename(filePath) { // Extract the substring after the last slash return filePath.substring(lastSlashIndex + 1); } + + +export function createStorageClasses(baseUrl, cookies, clusterId, iter){ + + const name = `test-storage-class-${iter}` + + const url = clusterId === "local"? + `${baseUrl}/v1/storage.k8s.io.storageclasses` : + `${baseUrl}/k8s/clusters/${clusterId}/v1/storage.k8s.io.storageclasses` + + + const res = http.post( `${url}`, JSON.stringify({ + "type": "storage.k8s.io.storageclass", + "metadata": {"name": name}, + "parameters": { "numberOfReplicas": "3", "staleReplicaTimeout": "2880" }, + "provisioner": "driver.longhorn.io", + "allowVolumeExpansion": true, + "reclaimPolicy": "Delete", + "volumeBindingMode": "Immediate" + }), + {cookies: cookies} + ) + + sleep(0.1) + if (res.status != 201) { + console.log(res) + } + check(res, { + '/v1/storage.k8s.io.storageclasses returns status 201': (r) => r.status === 201, + }) +} diff --git a/k6/generic/k8s.js b/k6/generic/k8s.js index c90da682..27998194 100644 --- a/k6/generic/k8s.js +++ b/k6/generic/k8s.js @@ -7,7 +7,7 @@ import { URL } from '../lib/url-1.0.0.js'; const timeout = '3600s' -function loadKubeconfig(file, contextName) { +export function loadKubeconfig(file, contextName) { const config = YAML.load(open(file)); console.debug(`Loading kubeconfig from '${file}' using context '${contextName}'.`); diff --git a/k6/vai/load_steve_k8s_pagination_filter_sort.js b/k6/vai/load_steve_k8s_pagination_filter_sort.js new file mode 100644 index 00000000..988f15dd --- /dev/null +++ b/k6/vai/load_steve_k8s_pagination_filter_sort.js @@ -0,0 +1,165 @@ +import { check, fail, sleep } from 'k6'; +import * as k8s from '../generic/k8s.js' +import { login, getCookies } from '../rancher/rancher_utils.js'; +import http from 'k6/http'; +import { customHandleSummary } from '../generic/k6_utils.js'; +import {loadKubeconfig} from '../generic/k8s.js' + +// Parameters +const vus = __ENV.VUS +const perVuIterations = __ENV.PER_VU_ITERATIONS +const token = __ENV.TOKEN +const kubeconfig = loadKubeconfig(__ENV.KUBECONFIG, __ENV.CONTEXT) +const baseUrl = kubeconfig["url"].replace(":6443", "") +const username = __ENV.USERNAME +const password = __ENV.PASSWORD +const clusterId = __ENV.CLUSTER || "local" +const paginationStyle = __ENV.PAGINATION_STYLE || "k8s" +const pauseSeconds = parseFloat(__ENV.PAUSE_SECONDS || 0.0) + +const key = __ENV.KEY || "blue" +const value = __ENV.VALUE || "green" + +export const handleSummary = customHandleSummary; + +// Option setting +export const options = { + insecureSkipTLSVerify: true, + + scenarios: { + list : { + executor: 'per-vu-iterations', + exec: 'list', + vus: vus, + iterations: perVuIterations, + maxDuration: '24h', + } + }, + thresholds: { + checks: ['rate>0.99'] + } +} + +// Simulate a pause after a click - on average pauseSeconds, +/- a random quantity up to 50% +function pause() { + sleep(pauseSeconds + (Math.random() - 0.5) * 2 * pauseSeconds / 2) +} + +export function setup() { + // if session cookie was specified, save it + if (token) { + return { R_SESS: token } + } + + // if credentials were specified, log in + if (username && password) { + const res = http.post(`${baseUrl}/v3-public/localProviders/local?action=login`, JSON.stringify({ + "description": "UI session", + "responseType": "cookie", + "username": username, + "password": password + })) + + check(res, { + 'logging in returns status 200': (r) => r.status === 200, + }) + + pause() + + const cookies = http.cookieJar().cookiesForURL(res.url) + + return cookies + } + + return {} +} + +export function list(cookies, filters = "") { + if (paginationStyle === "k8s") { + listFilterSort(cookies) + } + else if (paginationStyle === "steve") { + listFilterSortVai(cookies) + } + else { + fail("Invalid PAGINATION_STYLE value: " + paginationStyle) + } +} + +export function listFilterSort(cookies) { + const url = clusterId === "local"? + `${baseUrl}/v1/secrets` : + `${baseUrl}/k8s/clusters/${clusterId}/v1/secrets` + + let revision = null + let continueToken = null + while (true) { + const fullUrl = url + "?limit=100" + "&sort=metadata.name&filter=metadata.labels." + key + "=" + value + + (revision != null ? "&revision=" + revision : "") + + (continueToken != null ? "&continue=" + continueToken : "") + + const res = http.get(fullUrl, {cookies: cookies}) + + check(res, { + '/v1/secrets returns status 200': (r) => r.status === 200, + }) + + try { + const body = JSON.parse(res.body) + if (body === undefined || body.continue === undefined) { + break + } + if (revision == null) { + revision = body.revision + } + continueToken = body.continue + } + catch (e){ + if (e instanceof SyntaxError) { + fail("Response body does not parse as JSON: " + res.body) + } + throw e + } + } + + pause() +} + + +export function listFilterSortVai(cookies) { + const url = clusterId === "local"? + `${baseUrl}/v1/secrets` : + `${baseUrl}/k8s/clusters/${clusterId}/v1/secrets` + + let i = 1 + let revision = null + while (true) { + const fullUrl = url + "?pagesize=100&page=" + i + "&sort=metadata.name&filter=metadata.labels." + key + "=" + value + + (revision != null ? "&revision=" + revision : "") + + const res = http.get(fullUrl, {cookies: cookies}) + + check(res, { + '/v1/secrets returns status 200': (r) => r.status === 200, + }) + + try { + const body = JSON.parse(res.body) + if (body === undefined || body.data === undefined || body.data.length === 0) { + break + } + if (revision == null) { + revision = body.revision + } + i = i + 1 + } + catch (e){ + if (e instanceof SyntaxError) { + fail("Response body does not parse as JSON: " + res.body) + } + throw e + } + } + + pause() +} diff --git a/k6/vai/load_steve_k8s_pagination_storage_classes.js b/k6/vai/load_steve_k8s_pagination_storage_classes.js new file mode 100644 index 00000000..7ed99f1e --- /dev/null +++ b/k6/vai/load_steve_k8s_pagination_storage_classes.js @@ -0,0 +1,163 @@ +import { check, fail, sleep } from 'k6'; +import * as k8s from '../generic/k8s.js' +import { login, getCookies } from '../rancher/rancher_utils.js'; +import http from 'k6/http'; +import { customHandleSummary } from '../generic/k6_utils.js'; +import {loadKubeconfig} from '../generic/k8s.js' + + +// Parameters +const vus = __ENV.VUS +const perVuIterations = __ENV.PER_VU_ITERATIONS +const token = __ENV.TOKEN +const kubeconfig = loadKubeconfig(__ENV.KUBECONFIG, __ENV.CONTEXT) +const baseUrl = kubeconfig["url"].replace(":6443", "") +const username = __ENV.USERNAME +const password = __ENV.PASSWORD +const clusterId = __ENV.CLUSTER || "local" +const paginationStyle = __ENV.PAGINATION_STYLE || "k8s" +const pauseSeconds = parseFloat(__ENV.PAUSE_SECONDS || 0.0) + +export const handleSummary = customHandleSummary; + +// Option setting +export const options = { + insecureSkipTLSVerify: true, + + scenarios: { + list : { + executor: 'per-vu-iterations', + exec: 'list', + vus: vus, + iterations: perVuIterations, + maxDuration: '24h', + } + }, + thresholds: { + checks: ['rate>0.99'] + } +} + +// Simulate a pause after a click - on average pauseSeconds, +/- a random quantity up to 50% +function pause() { + sleep(pauseSeconds + (Math.random() - 0.5) * 2 * pauseSeconds / 2) +} + +export function setup() { + // if session cookie was specified, save it + if (token) { + return { R_SESS: token } + } + + // if credentials were specified, log in + if (username && password) { + const res = http.post(`${baseUrl}/v3-public/localProviders/local?action=login`, JSON.stringify({ + "description": "UI session", + "responseType": "cookie", + "username": username, + "password": password + })) + + check(res, { + 'logging in returns status 200': (r) => r.status === 200, + }) + + pause() + + const cookies = http.cookieJar().cookiesForURL(res.url) + + return cookies + } + + return {} +} + +export function list(cookies, filters = "") { + if (paginationStyle === "k8s") { + listStorageClasses(cookies) + } + else if (paginationStyle === "steve") { + listStorageClassesVai(cookies) + } + else { + fail("Invalid PAGINATION_STYLE value: " + paginationStyle) + } +} + +export function listStorageClasses(cookies) { + const url = clusterId === "local"? + `${baseUrl}/v1/storage.k8s.io.storageclasses` : + `${baseUrl}/k8s/clusters/${clusterId}/v1/storage.k8s.io.storageclasses` + + let revision = null + let continueToken = null + while (true) { + const fullUrl = url + "?limit=100" + + (revision != null ? "&revision=" + revision : "") + + (continueToken != null ? "&continue=" + continueToken : "") + + const res = http.get(fullUrl, {cookies: cookies}) + + check(res, { + '/v1/storage.k8s.io.storageclasses returns status 200': (r) => r.status === 200, + }) + + try { + const body = JSON.parse(res.body) + if (body === undefined || body.continue === undefined) { + break + } + if (revision == null) { + revision = body.revision + } + continueToken = body.continue + } + catch (e){ + if (e instanceof SyntaxError) { + fail("Response body does not parse as JSON: " + res.body) + } + throw e + } + } + + pause() +} + +export function listStorageClassesVai(cookies) { + const url = clusterId === "local"? + `${baseUrl}/v1/storage.k8s.io.storageclasses` : + `${baseUrl}/k8s/clusters/${clusterId}/v1/storage.k8s.io.storageclasses` + + let i = 1 + let revision = null + while (true) { + const fullUrl = url + "?pagesize=100&page=" + i + + (revision != null ? "&revision=" + revision : "") + + const res = http.get(fullUrl, {cookies: cookies}) + + check(res, { + '/v1/storage.k8s.io.storageclasses returns status 200': (r) => r.status === 200, + }) + + try { + const body = JSON.parse(res.body) + if (body === undefined || body.data === undefined || body.data.length === 0) { + break + } + if (revision == null) { + revision = body.revision + } + i = i + 1 + } + catch (e){ + if (e instanceof SyntaxError) { + fail("Response body does not parse as JSON: " + res.body) + } + throw e + } + } + + pause() +} +