add linting

add prettier
add concurrent users upload test
add api and play for users
remove some code smell
This commit is contained in:
Florian Schade
2020-12-03 11:45:38 +01:00
parent 9ba8fcf08e
commit de34fbb995
25 changed files with 1138 additions and 535 deletions

2
.gitignore vendored
View File

@@ -11,7 +11,7 @@ node_modules/
.idea
*/yarn-error.log
yarn-error.log
# Konnectd
konnectd/assets/identifier

View File

@@ -53,4 +53,5 @@ replace (
github.com/owncloud/ocis/thumbnails => ../thumbnails
github.com/owncloud/ocis/webdav => ../webdav
google.golang.org/grpc => google.golang.org/grpc v1.26.0
github.com/cs3org/reva => ../../../cs3org/reva
)

2
tests/k6/.eslintignore Normal file
View File

@@ -0,0 +1,2 @@
node_modules
dist

14
tests/k6/.eslintrc Normal file
View File

@@ -0,0 +1,14 @@
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module"
},
"extends": [
"plugin:@typescript-eslint/recommended",
"prettier/@typescript-eslint",
"plugin:prettier/recommended"
],
"rules": {
}
}

7
tests/k6/.prettierrc Normal file
View File

@@ -0,0 +1,7 @@
{
"semi": true,
"trailingComma": "all",
"singleQuote": true,
"printWidth": 120,
"tabWidth": 4
}

View File

@@ -4,9 +4,20 @@
"main": "index.js",
"scripts": {
"clean": "rm -rf ./dist",
"lint": "eslint './src/**/*.ts' --fix",
"build": "rollup -c",
"build:w": "rollup -c -w"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{ts}": [
"eslint --fix"
]
},
"devDependencies": {
"@babel/core": "^7.9.0",
"@babel/polyfill": "^7.12.1",
@@ -18,26 +29,26 @@
"@rollup/plugin-json": "^4.0.3",
"@rollup/plugin-node-resolve": "^7.1.3",
"@rollup/pluginutils": "^4.1.0",
"@types/faker": "^5.1.4",
"@types/jest": "^25.2.1",
"@types/k6": "^0.28.2",
"@types/lodash": "^4.14.165",
"@typescript-eslint/eslint-plugin": "^2.29.0",
"@typescript-eslint/parser": "^2.29.0",
"@typescript-eslint/eslint-plugin": "^4.9.0",
"@typescript-eslint/parser": "^4.9.0",
"babel-plugin-lodash": "^3.3.4",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.11.0",
"eslint": "^7.14.0",
"eslint-config-prettier": "^6.15.0",
"eslint-plugin-jest": "^23.8.2",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-prettier": "^3.2.0",
"husky": "^4.3.0",
"jest": "^25.4.0",
"k6": "^0.0.0",
"lint-staged": "^10.1.7",
"prettier": "^2.0.5",
"prettier": "^2.2.1",
"prettier-eslint": "^12.0.0",
"rollup": "^2.7.2",
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-multi-input": "^1.1.1",
"rollup-plugin-terser": "^5.3.0",
"typescript": "^3.8.3"
"typescript": "^4.1.2"
},
"dependencies": {
"lodash": "^4.17.20",

View File

@@ -1,41 +1,52 @@
import encoding from 'k6/encoding';
import * as types from '../types';
import * as defaults from "../defaults";
import {merge} from 'lodash';
import http, {RefinedParams, RefinedResponse, RequestBody, ResponseType} from "k6/http";
import * as defaults from '../defaults';
import { merge } from 'lodash';
import http, { RefinedParams, RefinedResponse, RequestBody, ResponseType } from 'k6/http';
import { bytes } from 'k6';
export const buildHeaders = ({credential}: { credential: types.Credential }): { [key: string]: string } => {
export const buildHeaders = ({ credential }: { credential?: types.Credential }): { [key: string]: string } => {
const isOIDCGuard = (credential as types.Token).tokenType !== undefined;
const authOIDC = credential as types.Token;
const authBasic = credential as types.Account;
return {
Authorization: isOIDCGuard ? `${authOIDC.tokenType} ${authOIDC.accessToken}` : `Basic ${encoding.b64encode(`${authBasic.login}:${authBasic.password}`)}`,
}
}
...(credential && {
Authorization: isOIDCGuard
? `${authOIDC.tokenType} ${authOIDC.accessToken}`
: `Basic ${encoding.b64encode(`${authBasic.login}:${authBasic.password}`)}`,
}),
};
};
export const buildURL = ({path}: { path: string }): string => {
return [
defaults.ENV.HOST,
...path.split('/').filter(Boolean)
].join('/')
}
export const buildURL = ({ path }: { path: string }): string => {
return [defaults.ENV.HOST, ...path.split('/').filter(Boolean)].join('/');
};
export const request = ({method, path, body = {}, params = {}, credential}: {
method: 'PROPFIND' | 'PUT' | 'GET' | 'DELETE' | 'MKCOL',
path: string,
export const request = ({
method,
path,
body = {},
params = {},
credential,
}: {
method: 'PROPFIND' | 'PUT' | 'GET' | 'POST' | 'DELETE' | 'MKCOL';
path: string;
credential: types.Credential;
body?: RequestBody | null,
params?: RefinedParams<ResponseType> | null
body?: RequestBody | bytes | null;
params?: RefinedParams<ResponseType> | null;
}): RefinedResponse<ResponseType> => {
return http.request(
method,
buildURL({path}),
body,
merge({
headers: {
...buildHeaders({credential})
}
}, params)
buildURL({ path }),
body as never,
merge(
{
headers: {
...buildHeaders({ credential }),
},
},
params,
),
);
}
};

View File

@@ -1,59 +1,111 @@
import {RefinedResponse, ResponseType} from 'k6/http';
import * as api from './api'
import { RefinedResponse, ResponseType } from 'k6/http';
import * as api from './api';
import * as types from '../types';
export class Upload {
public static exec({credential, userName, path = '', asset, tags}: {
public static exec({
credential,
userName,
path = '',
asset,
tags,
}: {
credential: types.Credential;
userName: string;
asset: types.Asset;
path?: string;
tags?: types.Tags;
}): RefinedResponse<ResponseType> {
return api.request({method: 'PUT', credential, path: `/remote.php/dav/files/${userName}/${path}/${asset.name}`, params: {tags}, body: asset.bytes as any})
return api.request({
method: 'PUT',
credential,
path: `/remote.php/dav/files/${userName}/${path}/${asset.name}`,
params: { tags },
body: asset.bytes,
});
}
}
export class Download {
public static exec({credential, userName, path, tags}: {
public static exec({
credential,
userName,
path,
tags,
}: {
credential: types.Credential;
userName: string;
path: string;
tags?: types.Tags;
}): RefinedResponse<ResponseType> {
return api.request({method: 'GET', credential, path: `/remote.php/dav/files/${userName}/${path}`, params: {tags}})
return api.request({
method: 'GET',
credential,
path: `/remote.php/dav/files/${userName}/${path}`,
params: { tags },
});
}
}
export class Delete {
public static exec({credential, userName, path, tags}: {
public static exec({
credential,
userName,
path,
tags,
}: {
credential: types.Credential;
userName: string;
path: string;
tags?: types.Tags;
}): RefinedResponse<ResponseType> {
return api.request({method: 'DELETE', credential, path: `/remote.php/dav/files/${userName}/${path}`, params: {tags}})
return api.request({
method: 'DELETE',
credential,
path: `/remote.php/dav/files/${userName}/${path}`,
params: { tags },
});
}
}
export class Create {
public static exec({credential, userName, path, tags}: {
public static exec({
credential,
userName,
path,
tags,
}: {
credential: types.Credential;
userName: string;
path: string;
tags?: types.Tags;
}): RefinedResponse<ResponseType> {
return api.request({method: 'MKCOL', credential, path: `/remote.php/dav/files/${userName}/${path}`, params: {tags}})
return api.request({
method: 'MKCOL',
credential,
path: `/remote.php/dav/files/${userName}/${path}`,
params: { tags },
});
}
}
export class Propfind {
public static exec({credential, userName, path = '', tags}: {
public static exec({
credential,
userName,
path = '',
tags,
}: {
credential: types.Credential;
userName: string;
path?: string;
tags?: types.Tags;
}): RefinedResponse<ResponseType> {
return api.request({method: 'PROPFIND', credential, path: `/remote.php/dav/files/${userName}/${path}`, params: {tags}})
return api.request({
method: 'PROPFIND',
credential,
path: `/remote.php/dav/files/${userName}/${path}`,
params: { tags },
});
}
}
}

View File

@@ -1,2 +1,3 @@
export * as api from './api'
export * as dav from './dav'
export * as api from './api';
export * as dav from './dav';
export * as users from './users';

View File

@@ -0,0 +1,46 @@
import { RefinedResponse, ResponseType } from 'k6/http';
import * as api from './api';
import * as types from '../types';
export class Create {
public static exec({
userName,
password,
email,
credential,
tags,
}: {
credential: types.Credential;
userName: string;
password: string;
email: string;
tags?: types.Tags;
}): RefinedResponse<ResponseType> {
return api.request({
method: 'POST',
credential,
path: `/ocs/v1.php/cloud/users`,
params: { tags },
body: { userid: userName, password, email },
});
}
}
export class Delete {
public static exec({
userName,
credential,
tags,
}: {
credential: types.Credential;
userName: string;
tags?: types.Tags;
}): RefinedResponse<ResponseType> {
return api.request({
method: 'DELETE',
credential,
path: `/ocs/v1.php/cloud/users/${userName}`,
params: { tags },
});
}
}

View File

@@ -2,9 +2,8 @@ import * as defaults from './defaults';
import http from 'k6/http';
import queryString from 'query-string';
import * as types from './types';
import {fail} from 'k6';
import {get} from 'lodash'
import { fail } from 'k6';
import { get } from 'lodash';
export default class Factory {
private provider!: types.AuthProvider;
@@ -15,14 +14,14 @@ export default class Factory {
if (defaults.ENV.OIDC_ENABLED) {
this.provider = new OIDCProvider(account);
return
return;
}
this.provider = new AccountProvider(account);
}
public get credential(): types.Credential {
return this.provider.credential
return this.provider.credential;
}
}
@@ -46,7 +45,7 @@ class OIDCProvider implements types.AuthProvider {
private cache!: {
validTo: Date;
token: types.Token;
}
};
constructor(account: types.Account) {
this.account = account;
@@ -63,12 +62,12 @@ class OIDCProvider implements types.AuthProvider {
const offset = 5;
const d = new Date();
d.setSeconds(d.getSeconds() + token.expiresIn - offset)
d.setSeconds(d.getSeconds() + token.expiresIn - offset);
return d
return d;
})(),
token,
}
};
}
return this.cache.token;
@@ -77,18 +76,16 @@ class OIDCProvider implements types.AuthProvider {
private getContinueURI(): string {
const logonResponse = http.post(
this.logonUri,
JSON.stringify(
{
params: [this.account.login, this.account.password, '1'],
hello: {
scope: 'openid profile email',
client_id: 'phoenix',
redirect_uri: this.redirectUri,
flow: 'oidc'
},
'state': 'vp42cf'
JSON.stringify({
params: [this.account.login, this.account.password, '1'],
hello: {
scope: 'openid profile email',
client_id: 'phoenix',
redirect_uri: this.redirectUri,
flow: 'oidc',
},
),
state: 'vp42cf',
}),
{
headers: {
'Kopano-Konnect-XSRF': '1',
@@ -107,53 +104,49 @@ class OIDCProvider implements types.AuthProvider {
}
private getCode(continueURI: string): string {
const authorizeUri = `${continueURI}?${
queryString.stringify(
{
client_id: 'phoenix',
prompt: 'none',
redirect_uri: this.redirectUri,
response_mode: 'query',
response_type: 'code',
scope: 'openid profile email',
},
)
}`;
const authorizeResponse = http.get(
authorizeUri,
{
redirects: 0,
},
)
const authorizeUri = `${continueURI}?${queryString.stringify({
client_id: 'phoenix',
prompt: 'none',
redirect_uri: this.redirectUri,
response_mode: 'query',
response_type: 'code',
scope: 'openid profile email',
})}`;
const authorizeResponse = http.get(authorizeUri, {
redirects: 0,
});
const code = get(queryString.parseUrl(authorizeResponse.headers.Location), 'query.code')
const code = get(queryString.parseUrl(authorizeResponse.headers.Location), 'query.code');
if (authorizeResponse.status != 302 || !code) {
fail(continueURI);
}
return code
return code;
}
private getToken(code: string): types.Token {
const tokenResponse = http.post(
this.tokenUrl,
{
client_id: 'phoenix',
code,
redirect_uri: this.redirectUri,
grant_type: 'authorization_code'
}
)
const tokenResponse = http.post(this.tokenUrl, {
client_id: 'phoenix',
code,
redirect_uri: this.redirectUri,
grant_type: 'authorization_code',
});
const token = {
accessToken: get(tokenResponse.json(), 'access_token'),
tokenType: get(tokenResponse.json(), 'token_type'),
idToken: get(tokenResponse.json(), 'id_token'),
expiresIn: get(tokenResponse.json(), 'expires_in'),
}
};
if (tokenResponse.status != 200 || !token.accessToken || !token.tokenType || !token.idToken || !token.expiresIn) {
if (
tokenResponse.status != 200 ||
!token.accessToken ||
!token.tokenType ||
!token.idToken ||
!token.expiresIn
) {
fail(this.tokenUrl);
}

View File

@@ -9,9 +9,14 @@ export class ENV {
}
export class ACCOUNTS {
public static readonly ADMIN = 'admin';
public static readonly EINSTEIN = 'einstein';
public static readonly RICHARD = 'richard';
public static readonly ALL: { [key: string]: types.Account; } = {
public static readonly ALL: { [key: string]: types.Account } = {
admin: {
login: 'admin',
password: 'admin',
},
einstein: {
login: 'einstein',
password: 'relativity',
@@ -20,5 +25,5 @@ export class ACCOUNTS {
login: 'richard',
password: 'superfluidity',
},
}
}
};
}

View File

@@ -1,6 +1,6 @@
export * as api from './api'
export * as playbook from './playbook'
export * as auth from './auth'
export * as defaults from './defaults'
export * as types from './types'
export * as utils from './utils'
export * as api from './api';
export * as playbook from './playbook';
export * as auth from './auth';
export * as defaults from './defaults';
export * as types from './types';
export * as utils from './utils';

View File

@@ -1,141 +1,177 @@
import * as api from '../api';
import {check} from 'k6';
import { check } from 'k6';
import * as types from '../types';
import {RefinedResponse, ResponseType} from 'k6/http';
import {Play} from "./playbook";
import { RefinedResponse, ResponseType } from 'k6/http';
import { Play } from './playbook';
export class Upload extends Play {
constructor({name, metricID = 'default'}: { name?: string; metricID?: string; }) {
super({name: name || `oc_${metricID}_play_dav_upload`});
constructor({ name, metricID = 'default' }: { name?: string; metricID?: string } = {}) {
super({ name: name || `oc_${metricID}_play_dav_upload` });
}
public exec(
{credential, userName, path, asset, tags}: {
credential: types.Credential;
path?: string;
userName: string;
asset: types.Asset;
tags?: types.Tags;
}
): { response: RefinedResponse<ResponseType>; tags: types.Tags; } {
tags = {...this.tags, ...tags};
public exec({
credential,
userName,
path,
asset,
tags,
}: {
credential: types.Credential;
path?: string;
userName: string;
asset: types.Asset;
tags?: types.Tags;
}): { response: RefinedResponse<ResponseType>; tags: types.Tags } {
tags = { ...this.tags, ...tags };
const response = api.dav.Upload.exec({credential: credential as types.Credential, asset, userName, tags, path});
const response = api.dav.Upload.exec({ credential: credential, asset, userName, tags, path });
check(response, {
'upload status is 201': () => response.status === 201,
}, tags) || this.metricErrorRate.add(1, tags);
check(
response,
{
'dav upload status is 201': () => response.status === 201,
},
tags,
) || this.metricErrorRate.add(1, tags);
this.metricTrend.add(response.timings.duration, tags)
this.metricTrend.add(response.timings.duration, tags);
return {response, tags}
return { response, tags };
}
}
export class Delete extends Play {
constructor({name, metricID = 'default'}: { name?: string; metricID?: string; }) {
super({name: name || `oc_${metricID}_play_dav_delete`});
constructor({ name, metricID = 'default' }: { name?: string; metricID?: string } = {}) {
super({ name: name || `oc_${metricID}_play_dav_delete` });
}
public exec(
{credential, userName, path, tags}: {
credential: types.Credential;
path: string;
userName: string;
tags?: types.Tags;
}
): { response: RefinedResponse<ResponseType>; tags: types.Tags; } {
tags = {...this.tags, ...tags};
public exec({
credential,
userName,
path,
tags,
}: {
credential: types.Credential;
path: string;
userName: string;
tags?: types.Tags;
}): { response: RefinedResponse<ResponseType>; tags: types.Tags } {
tags = { ...this.tags, ...tags };
const response = api.dav.Delete.exec({credential: credential as types.Credential, userName, tags, path});
const response = api.dav.Delete.exec({ credential: credential, userName, tags, path });
check(response, {
'delete status is 204': () => response.status === 204,
}, tags) || this.metricErrorRate.add(1, tags);
check(
response,
{
'dav delete status is 204': () => response.status === 204,
},
tags,
) || this.metricErrorRate.add(1, tags);
this.metricTrend.add(response.timings.duration, tags)
this.metricTrend.add(response.timings.duration, tags);
return {response, tags}
return { response, tags };
}
}
export class Download extends Play {
constructor({name, metricID = 'default'}: { name?: string; metricID?: string; }) {
super({name: name || `oc_${metricID}_play_dav_download`});
constructor({ name, metricID = 'default' }: { name?: string; metricID?: string } = {}) {
super({ name: name || `oc_${metricID}_play_dav_download` });
}
public exec(
{credential, userName, path, tags}: {
credential: types.Credential;
path: string;
userName: string;
tags?: types.Tags;
}
): { response: RefinedResponse<ResponseType>; tags: types.Tags; } {
tags = {...this.tags, ...tags};
public exec({
credential,
userName,
path,
tags,
}: {
credential: types.Credential;
path: string;
userName: string;
tags?: types.Tags;
}): { response: RefinedResponse<ResponseType>; tags: types.Tags } {
tags = { ...this.tags, ...tags };
const response = api.dav.Download.exec({credential: credential as types.Credential, userName, tags, path});
const response = api.dav.Download.exec({ credential: credential, userName, tags, path });
check(response, {
'download status is 200': () => response.status === 200,
}, tags) || this.metricErrorRate.add(1, tags);
check(
response,
{
'dav download status is 200': () => response.status === 200,
},
tags,
) || this.metricErrorRate.add(1, tags);
this.metricTrend.add(response.timings.duration, tags)
this.metricTrend.add(response.timings.duration, tags);
return {response, tags}
return { response, tags };
}
}
export class Create extends Play {
constructor({name, metricID = 'default'}: { name?: string; metricID?: string; }) {
super({name: name || `oc_${metricID}_play_dav_create`});
constructor({ name, metricID = 'default' }: { name?: string; metricID?: string } = {}) {
super({ name: name || `oc_${metricID}_play_dav_create` });
}
public exec(
{credential, userName, path, tags}: {
credential: types.Credential;
path: string;
userName: string;
tags?: types.Tags;
}
): { response: RefinedResponse<ResponseType>; tags: types.Tags; } {
tags = {...this.tags, ...tags};
public exec({
credential,
userName,
path,
tags,
}: {
credential: types.Credential;
path: string;
userName: string;
tags?: types.Tags;
}): { response: RefinedResponse<ResponseType>; tags: types.Tags } {
tags = { ...this.tags, ...tags };
const response = api.dav.Create.exec({credential: credential as types.Credential, userName, tags, path});
const response = api.dav.Create.exec({ credential: credential, userName, tags, path });
check(response, {
'create status is 201': () => response.status === 201,
}, tags) || this.metricErrorRate.add(1, tags);
check(
response,
{
'dav create status is 201': () => response.status === 201,
},
tags,
) || this.metricErrorRate.add(1, tags);
this.metricTrend.add(response.timings.duration, tags)
this.metricTrend.add(response.timings.duration, tags);
return {response, tags}
return { response, tags };
}
}
export class Propfind extends Play {
constructor({name, metricID = 'default'}: { name?: string; metricID?: string; }) {
super({name: name || `oc_${metricID}_play_dav_propfind`});
constructor({ name, metricID = 'default' }: { name?: string; metricID?: string } = {}) {
super({ name: name || `oc_${metricID}_play_dav_propfind` });
}
public exec(
{credential, userName, path, tags}: {
credential: types.Credential;
path?: string;
userName: string;
tags?: types.Tags;
}
): { response: RefinedResponse<ResponseType>; tags: types.Tags; } {
tags = {...this.tags, ...tags};
public exec({
credential,
userName,
path,
tags,
}: {
credential: types.Credential;
path?: string;
userName: string;
tags?: types.Tags;
}): { response: RefinedResponse<ResponseType>; tags: types.Tags } {
tags = { ...this.tags, ...tags };
const response = api.dav.Propfind.exec({credential: credential as types.Credential, userName, tags, path});
const response = api.dav.Propfind.exec({ credential: credential, userName, tags, path });
check(response, {
'propfind status is 207': () => response.status === 207,
}, tags) || this.metricErrorRate.add(1, tags);
check(
response,
{
'dav propfind status is 207': () => response.status === 207,
},
tags,
) || this.metricErrorRate.add(1, tags);
this.metricTrend.add(response.timings.duration, tags)
this.metricTrend.add(response.timings.duration, tags);
return {response, tags}
return { response, tags };
}
}
}

View File

@@ -1 +1,2 @@
export * as dav from './dav'
export * as dav from './dav';
export * as users from './users';

View File

@@ -1,4 +1,4 @@
import {Gauge, Trend} from "k6/metrics";
import { Gauge, Trend } from 'k6/metrics';
export class Play {
public readonly name: string;
@@ -8,12 +8,12 @@ export class Play {
public readonly metricErrorRate: Gauge;
protected tags: { [key: string]: string };
constructor({name}: { name: string; }) {
constructor({ name }: { name: string }) {
this.name = name;
this.metricTrendName = `${this.name}_trend`;
this.metricErrorRateName = `${this.name}_error_rate`;
this.metricTrend = new Trend(this.metricTrendName, true);
this.metricErrorRate = new Gauge(this.metricErrorRateName);
this.tags = {play: this.name}
this.tags = { play: this.name };
}
}
}

View File

@@ -0,0 +1,73 @@
import * as api from '../api';
import { check } from 'k6';
import * as types from '../types';
import { RefinedResponse, ResponseType } from 'k6/http';
import { Play } from './playbook';
export class Create extends Play {
constructor({ name, metricID = 'default' }: { name?: string; metricID?: string } = {}) {
super({ name: name || `oc_${metricID}_play_users_create` });
}
public exec({
credential,
userName,
password,
email,
tags,
}: {
credential: types.Credential;
userName: string;
password: string;
email: string;
tags?: types.Tags;
}): { response: RefinedResponse<ResponseType>; tags: types.Tags } {
tags = { ...this.tags, ...tags };
const response = api.users.Create.exec({ credential: credential, userName, password, tags, email });
check(
response,
{
'users create status is 200': () => response.status === 200,
},
tags,
) || this.metricErrorRate.add(1, tags);
this.metricTrend.add(response.timings.duration, tags);
return { response, tags };
}
}
export class Delete extends Play {
constructor({ name, metricID = 'default' }: { name?: string; metricID?: string } = {}) {
super({ name: name || `oc_${metricID}_play_users_delete` });
}
public exec({
credential,
userName,
tags,
}: {
credential: types.Credential;
userName: string;
tags?: types.Tags;
}): { response: RefinedResponse<ResponseType>; tags: types.Tags } {
tags = { ...this.tags, ...tags };
const response = api.users.Delete.exec({ credential: credential, userName, tags });
check(
response,
{
'users delete status is 200': () => response.status === 200,
},
tags,
) || this.metricErrorRate.add(1, tags);
this.metricTrend.add(response.timings.duration, tags);
return { response, tags };
}
}

View File

@@ -1,4 +1,4 @@
import {bytes} from 'k6';
import { bytes } from 'k6';
export interface Asset {
bytes: bytes;
@@ -13,16 +13,16 @@ export interface Token {
}
export interface Account {
login: string
password: string
login: string;
password: string;
}
export type Credential = Token | Account
export type Credential = Token | Account;
export interface AuthProvider {
credential: Credential
credential: Credential;
}
export type AssetUnit = 'KB' | 'MB' | 'GB'
export type Tags = { [key: string]: string };
export type Tags = { [key: string]: string }
export declare type AssetUnit = 'KB' | 'MB' | 'GB';

View File

@@ -1,57 +1,60 @@
import {bytes} from 'k6';
import {randomBytes as k6_randomBytes} from 'k6/crypto';
import { bytes } from 'k6';
import { randomBytes as k6_randomBytes } from 'k6/crypto';
import * as defaults from './defaults';
import * as types from './types';
export const randomNumber = ({min, max}: { min: number; max: number; }): number => {
export const randomNumber = ({ min, max }: { min: number; max: number }): number => {
return Math.random() * (max - min) + min;
}
};
export const randomString = (): string => {
return Math.random().toString(20).substr(2)
}
export const randomString = ({ length = 10 }: { length?: number } = {}): string => {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
export const buildAccount = ({login = defaults.ACCOUNTS.EINSTEIN}: { login: string; }): types.Account => {
let str = '';
for (let i = 0; i < length; i++) {
str += chars.charAt(Math.floor(Math.random() * chars.length));
}
return str;
};
export const buildAccount = ({ login = defaults.ACCOUNTS.EINSTEIN }: { login: string }): types.Account => {
if (defaults.ENV.LOGIN && defaults.ENV.PASSWORD) {
return {
login: defaults.ENV.LOGIN,
password: defaults.ENV.PASSWORD,
}
};
}
return defaults.ACCOUNTS.ALL[login];
}
export const buildAsset = (
{
name = 'dummy.zip',
size = 1,
unit = 'MB',
}: {
name?: string;
size?: number;
unit?: types.AssetUnit;
}
): types.Asset => {
};
export const buildAsset = ({
name = 'dummy.zip',
size = 50,
unit = 'KB',
}: {
name?: string;
size?: number;
unit?: types.AssetUnit;
}): types.Asset => {
const gen = {
KB: (s: number): bytes => {
return k6_randomBytes(s * 1024)
return k6_randomBytes(s * 1024);
},
MB: (s: number): bytes => {
return gen.KB(s * 1024)
return gen.KB(s * 1024);
},
GB: (s: number): bytes => {
return gen.MB(s * 1024)
return gen.MB(s * 1024);
},
}
};
const fileBaseName = name.split('/').reverse()[0]
const fileName = fileBaseName.split('.')[0]
const fileExtension = fileBaseName.split('.').reverse()[0] || 'zip'
const fileBaseName = name.split('/').reverse()[0];
const fileName = fileBaseName.split('.')[0];
const fileExtension = fileBaseName.split('.').reverse()[0] || 'zip';
return {
name: `${fileName}-${__VU}-${__ITER}-${size}-${unit}-${randomString()}.${fileExtension}`,
name: `${fileName}-${__VU}-${__ITER}-${unit}-${size}-${randomString()}.${fileExtension}`,
bytes: gen[unit](size),
}
}
};
};

View File

@@ -0,0 +1,73 @@
import { Options, Threshold } from 'k6/options';
import { utils, auth, defaults, playbook, types } from '../../../../../lib';
const files: Array<{
size: number;
unit: types.AssetUnit;
}> = [
{ size: 50, unit: 'KB' },
{ size: 500, unit: 'KB' },
{ size: 5, unit: 'MB' },
{ size: 50, unit: 'MB' },
];
const adminAuthFactory = new auth.default(utils.buildAccount({ login: defaults.ACCOUNTS.ADMIN }));
const plays = {
usersCreate: new playbook.users.Create(),
usersDelete: new playbook.users.Delete(),
davUpload: new playbook.dav.Upload(),
davDelete: new playbook.dav.Delete(),
};
export const options: Options = {
insecureSkipTLSVerify: true,
iterations: 10,
vus: 10,
thresholds: files.reduce((acc: { [name: string]: Threshold[] }, c) => {
acc[`${plays.davUpload.metricTrendName}{asset:${c.unit + c.size.toString()}}`] = [];
acc[`${plays.davDelete.metricTrendName}{asset:${c.unit + c.size.toString()}}`] = [];
return acc;
}, {}),
};
export default (): void => {
const userName: string = utils.randomString();
const password: string = utils.randomString();
plays.usersCreate.exec({
userName,
password,
email: `${userName}@owncloud.com`,
credential: adminAuthFactory.credential,
});
const userAuthFactory = new auth.default({ login: userName, password });
const filesUploaded: { id: string; name: string }[] = [];
files.forEach((f) => {
const id = f.unit + f.size.toString();
const asset = utils.buildAsset({
name: `${userName}-dummy.zip`,
unit: f.unit,
size: f.size,
});
plays.davUpload.exec({
credential: userAuthFactory.credential,
asset,
userName,
tags: { asset: id },
});
filesUploaded.push({ id, name: asset.name });
});
filesUploaded.forEach((f) => {
plays.davDelete.exec({
credential: userAuthFactory.credential,
userName: userAuthFactory.account.login,
path: f.name,
tags: { asset: f.id },
});
});
plays.usersDelete.exec({ userName: userName, credential: adminAuthFactory.credential });
};

View File

@@ -1,64 +1,63 @@
import {Options} from "k6/options";
import {utils, auth, defaults, playbook} from '../../../../../../lib'
import {times} from 'lodash'
import { Options, Threshold } from 'k6/options';
import { utils, auth, defaults, playbook, types } from '../../../../../../lib';
import { times } from 'lodash';
// put 1000 files into one dir and run a 'PROPFIND' through API
const files: {
size: number;
unit: any;
}[] = times(1000, () => ({size: 1, unit: 'KB'}))
const authFactory = new auth.default(utils.buildAccount({login: defaults.ACCOUNTS.EINSTEIN}));
unit: types.AssetUnit;
}[] = times(1000, () => ({ size: 1, unit: 'KB' }));
const authFactory = new auth.default(utils.buildAccount({ login: defaults.ACCOUNTS.EINSTEIN }));
const plays = {
davUpload: new playbook.dav.Upload({}),
davPropfind: new playbook.dav.Propfind({}),
davDelete: new playbook.dav.Delete({}),
}
davUpload: new playbook.dav.Upload(),
davPropfind: new playbook.dav.Propfind(),
davDelete: new playbook.dav.Delete(),
};
export const options: Options = {
insecureSkipTLSVerify: true,
iterations: 3,
vus: 1,
thresholds: files.reduce((acc: any, c) => {
acc[`${plays.davUpload.metricTrendName}{asset:${c.unit + c.size.toString()}`] = []
acc[`${plays.davPropfind.metricTrendName}`] = []
acc[`${plays.davDelete.metricTrendName}{asset:${c.unit + c.size.toString()}`] = []
return acc
thresholds: files.reduce((acc: { [name: string]: Threshold[] }, c) => {
acc[`${plays.davUpload.metricTrendName}{asset:${c.unit + c.size.toString()}}`] = [];
acc[`${plays.davDelete.metricTrendName}{asset:${c.unit + c.size.toString()}}`] = [];
return acc;
}, {}),
};
export default (): void => {
const filesUploaded: { id: string, name: string, }[] = []
const {account, credential} = authFactory;
const filesUploaded: { id: string; name: string }[] = [];
const { account, credential } = authFactory;
files.forEach(f => {
files.forEach((f) => {
const id = f.unit + f.size.toString();
const asset = utils.buildAsset({
name: `${account.login}-dummy.zip`,
unit: f.unit as any,
unit: f.unit,
size: f.size,
})
});
plays.davUpload.exec({
credential,
asset,
userName: account.login,
tags: {asset: id},
tags: { asset: id },
});
filesUploaded.push({id, name: asset.name})
})
filesUploaded.push({ id, name: asset.name });
});
plays.davPropfind.exec({
credential,
userName: account.login,
})
});
filesUploaded.forEach(f => {
filesUploaded.forEach((f) => {
plays.davDelete.exec({
credential,
userName: account.login,
path: f.name,
tags: {asset: f.id},
tags: { asset: f.id },
});
})
}
});
};

View File

@@ -1,81 +1,81 @@
import {Options} from "k6/options";
import {utils, auth, defaults, playbook} from '../../../../../../lib'
import {times} from 'lodash'
import { Options, Threshold } from 'k6/options';
import { utils, auth, defaults, playbook, types } from '../../../../../../lib';
import { times } from 'lodash';
// Unpack standard data tarball, run PROPFIND on each dir
const files: {
size: number;
unit: any;
}[] = times(1000, () => ({size: 1, unit: 'KB'}))
const authFactory = new auth.default(utils.buildAccount({login: defaults.ACCOUNTS.EINSTEIN}));
unit: types.AssetUnit;
}[] = times(1000, () => ({ size: 1, unit: 'KB' }));
const authFactory = new auth.default(utils.buildAccount({ login: defaults.ACCOUNTS.EINSTEIN }));
const plays = {
davUpload: new playbook.dav.Upload({}),
davPropfind: new playbook.dav.Propfind({}),
davCreate: new playbook.dav.Create({}),
davDelete: new playbook.dav.Delete({}),
}
davUpload: new playbook.dav.Upload(),
davPropfind: new playbook.dav.Propfind(),
davCreate: new playbook.dav.Create(),
davDelete: new playbook.dav.Delete(),
};
export const options: Options = {
insecureSkipTLSVerify: true,
iterations: 3,
vus: 1,
thresholds: files.reduce((acc: any, c) => {
acc[`${plays.davUpload.metricTrendName}{asset:${c.unit + c.size.toString()}`] = []
acc[`${plays.davPropfind.metricTrendName}`] = []
acc[`${plays.davCreate.metricTrendName}{asset:${c.unit + c.size.toString()}`] = []
acc[`${plays.davDelete.metricTrendName}{asset:${c.unit + c.size.toString()}`] = []
return acc
thresholds: files.reduce((acc: { [name: string]: Threshold[] }, c) => {
acc[`${plays.davUpload.metricTrendName}{asset:${c.unit + c.size.toString()}}`] = [];
acc[`${plays.davCreate.metricTrendName}{asset:${c.unit + c.size.toString()}}`] = [];
acc[`${plays.davDelete.metricTrendName}{asset:${c.unit + c.size.toString()}}`] = [];
return acc;
}, {}),
};
export default (): void => {
const filesUploaded: { id: string, name: string, folder: string }[] = []
const {account, credential} = authFactory;
const filesUploaded: { id: string; name: string; folder: string }[] = [];
const { account, credential } = authFactory;
files.forEach(f => {
files.forEach((f) => {
const id = f.unit + f.size.toString();
const asset = utils.buildAsset({
name: `${account.login}-dummy.zip`,
unit: f.unit as any,
unit: f.unit,
size: f.size,
})
});
const folder = times(utils.randomNumber({min: 1, max: 10}), () => utils.randomString()).reduce((acc: string[], c) => {
acc.push(c)
const folder = times(utils.randomNumber({ min: 1, max: 10 }), () => utils.randomString())
.reduce((acc: string[], c) => {
acc.push(c);
plays.davCreate.exec({
credential,
path: acc.join('/'),
userName: account.login,
tags: {asset: id},
});
return acc
}, []).join('/')
plays.davCreate.exec({
credential,
path: acc.join('/'),
userName: account.login,
tags: { asset: id },
});
return acc;
}, [])
.join('/');
plays.davUpload.exec({
credential,
asset,
path: folder,
userName: account.login,
tags: {asset: id},
tags: { asset: id },
});
filesUploaded.push({id, name: asset.name, folder})
})
filesUploaded.push({ id, name: asset.name, folder });
});
plays.davPropfind.exec({
credential,
userName: account.login,
})
});
filesUploaded.forEach(f => {
filesUploaded.forEach((f) => {
plays.davDelete.exec({
credential,
userName: account.login,
path: f.folder.split('/')[0],
tags: {asset: f.id},
tags: { asset: f.id },
});
})
}
});
};

View File

@@ -1,72 +1,72 @@
import {Options} from "k6/options";
import {utils, auth, defaults, playbook} from '../../../../../../lib'
import {times} from 'lodash'
import { Options, Threshold } from 'k6/options';
import { utils, auth, defaults, playbook, types } from '../../../../../../lib';
import { times } from 'lodash';
// upload, download and delete of many files with several sizes and summary size of 500 MB in one directory
const files: {
size: number;
unit: any;
unit: types.AssetUnit;
}[] = [
...times(100, () => ({size: 500, unit: 'KB'})),
...times(50, () => ({size: 5, unit: 'MB'})),
...times(10, () => ({size: 25, unit: 'MB'})),
]
const authFactory = new auth.default(utils.buildAccount({login: defaults.ACCOUNTS.EINSTEIN}));
...times(100, () => ({ size: 500, unit: 'KB' as types.AssetUnit })),
...times(50, () => ({ size: 5, unit: 'MB' as types.AssetUnit })),
...times(10, () => ({ size: 25, unit: 'MB' as types.AssetUnit })),
];
const authFactory = new auth.default(utils.buildAccount({ login: defaults.ACCOUNTS.EINSTEIN }));
const plays = {
davUpload: new playbook.dav.Upload({}),
davDownload: new playbook.dav.Download({}),
davDelete: new playbook.dav.Delete({}),
}
davUpload: new playbook.dav.Upload(),
davDownload: new playbook.dav.Download(),
davDelete: new playbook.dav.Delete(),
};
export const options: Options = {
insecureSkipTLSVerify: true,
iterations: 3,
vus: 1,
thresholds: files.reduce((acc: any, c) => {
acc[`${plays.davUpload.metricTrendName}{asset:${c.unit + c.size.toString()}`] = []
acc[`${plays.davDownload.metricTrendName}{asset:${c.unit + c.size.toString()}`] = []
acc[`${plays.davDelete.metricTrendName}{asset:${c.unit + c.size.toString()}`] = []
return acc
thresholds: files.reduce((acc: { [name: string]: Threshold[] }, c) => {
acc[`${plays.davUpload.metricTrendName}{asset:${c.unit + c.size.toString()}}`] = [];
acc[`${plays.davDownload.metricTrendName}{asset:${c.unit + c.size.toString()}}`] = [];
acc[`${plays.davDelete.metricTrendName}{asset:${c.unit + c.size.toString()}}`] = [];
return acc;
}, {}),
};
export default (): void => {
const filesUploaded: { id: string, name: string, }[] = []
const {account, credential} = authFactory;
const filesUploaded: { id: string; name: string }[] = [];
const { account, credential } = authFactory;
files.forEach(f => {
files.forEach((f) => {
const id = f.unit + f.size.toString();
const asset = utils.buildAsset({
name: `${account.login}-dummy.zip`,
unit: f.unit as any,
unit: f.unit,
size: f.size,
})
});
plays.davUpload.exec({
credential,
asset,
userName: account.login,
tags: {asset: id},
tags: { asset: id },
});
filesUploaded.push({id, name: asset.name})
})
filesUploaded.push({ id, name: asset.name });
});
filesUploaded.forEach(f => {
filesUploaded.forEach((f) => {
plays.davDownload.exec({
credential,
userName: account.login,
path: f.name,
tags: {asset: f.id},
tags: { asset: f.id },
});
})
});
filesUploaded.forEach(f => {
filesUploaded.forEach((f) => {
plays.davDelete.exec({
credential,
userName: account.login,
path: f.name,
tags: {asset: f.id},
tags: { asset: f.id },
});
})
}
});
};

View File

@@ -1,5 +1,5 @@
import {Options} from "k6/options";
import {utils, auth, defaults, playbook, types} from '../../../../../../lib'
import { Options, Threshold } from 'k6/options';
import { utils, auth, defaults, playbook, types } from '../../../../../../lib';
// upload, download and delete of one file with sizes 50kb, 500kb, 5MB, 50MB, 500MB, 1GB
@@ -7,69 +7,69 @@ const files: {
size: number;
unit: types.AssetUnit;
}[] = [
{size: 50, unit: 'KB'},
{size: 500, unit: 'KB'},
{size: 5, unit: 'MB'},
{size: 50, unit: 'MB'},
{size: 500, unit: 'MB'},
{size: 1, unit: 'GB'},
]
const authFactory = new auth.default(utils.buildAccount({login: defaults.ACCOUNTS.EINSTEIN}));
{ size: 50, unit: 'KB' },
{ size: 500, unit: 'KB' },
{ size: 5, unit: 'MB' },
{ size: 50, unit: 'MB' },
{ size: 500, unit: 'MB' },
{ size: 1, unit: 'GB' },
];
const authFactory = new auth.default(utils.buildAccount({ login: defaults.ACCOUNTS.EINSTEIN }));
const plays = {
davUpload: new playbook.dav.Upload({}),
davDownload: new playbook.dav.Download({}),
davDelete: new playbook.dav.Delete({}),
}
davUpload: new playbook.dav.Upload(),
davDownload: new playbook.dav.Download(),
davDelete: new playbook.dav.Delete(),
};
export const options: Options = {
insecureSkipTLSVerify: true,
iterations: 3,
vus: 1,
thresholds: files.reduce((acc: any, c) => {
acc[`${plays.davUpload.metricTrendName}{asset:${c.unit + c.size.toString()}`] = []
acc[`${plays.davDownload.metricTrendName}{asset:${c.unit + c.size.toString()}`] = []
acc[`${plays.davDelete.metricTrendName}{asset:${c.unit + c.size.toString()}`] = []
return acc
thresholds: files.reduce((acc: { [name: string]: Threshold[] }, c) => {
acc[`${plays.davUpload.metricTrendName}{asset:${c.unit + c.size.toString()}}`] = [];
acc[`${plays.davDownload.metricTrendName}{asset:${c.unit + c.size.toString()}}`] = [];
acc[`${plays.davDelete.metricTrendName}{asset:${c.unit + c.size.toString()}}`] = [];
return acc;
}, {}),
};
export default (): void => {
const filesUploaded: { id: string, name: string, }[] = []
const {account, credential} = authFactory;
const filesUploaded: { id: string; name: string }[] = [];
const { account, credential } = authFactory;
files.forEach(f => {
files.forEach((f) => {
const id = f.unit + f.size.toString();
const asset = utils.buildAsset({
name: `${account.login}-dummy.zip`,
unit: f.unit as any,
unit: f.unit,
size: f.size,
})
});
plays.davUpload.exec({
credential,
asset,
userName: account.login,
tags: {asset: id},
tags: { asset: id },
});
filesUploaded.push({id, name: asset.name})
})
filesUploaded.push({ id, name: asset.name });
});
filesUploaded.forEach(f => {
filesUploaded.forEach((f) => {
plays.davDownload.exec({
credential,
userName: account.login,
path: f.name,
tags: {asset: f.id},
tags: { asset: f.id },
});
})
});
filesUploaded.forEach(f => {
filesUploaded.forEach((f) => {
plays.davDelete.exec({
credential,
userName: account.login,
path: f.name,
tags: {asset: f.id},
tags: { asset: f.id },
});
})
}
});
};

File diff suppressed because it is too large Load Diff