[stress-tests] Documentation + necessary files to perform testing

By executing tests in this normalised fashion it is easier to compare
results between different instances or different patch levels.

Roughly speaking there are two kinds of tests:

- VM tests: which summarise general performance of the instance,
  without taking DD into account
- DD tests: which simulates many logins and interactions with DD,
  while recording the session as a user would pereceive it from a
  browser

By using these we should be able to consistently compare and improve
performance.

The original dd-stress-test.tpl.jmx tests file was prepared by
Teradisk  with hardcoded instance, threadcount and duration values.

Testing should now be performed with `vm-test.sh` and `dd-test.sh`
respectively, and the template file should stay generic.
nc-nginx-test
Evilham 2023-02-27 19:38:41 +01:00
parent eb6c14958b
commit 567bfd770d
No known key found for this signature in database
GPG Key ID: AE3EE30D970886BF
11 changed files with 1677 additions and 0 deletions

1
docs/stress-tests.md Symbolic link
View File

@ -0,0 +1 @@
../stress-tests/README.md

View File

@ -63,6 +63,7 @@ nav:
- integrations.ca.md
- post-install.ca.md
- contributing.ca.md
- stress-tests.md
- security.ca.md
- project-management.md
#- upgrade.md

10
stress-tests/.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
# Potentially private files
users.csv
docs.csv
dd-stress-test.users.csv
dd-stress-test.docs.csv
# Transient files
dd-stress-test.jmx
apache-jmeter-*/
results/
vm-test.log

13
stress-tests/Pipfile Normal file
View File

@ -0,0 +1,13 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
selenium = ">=4.8"
pyyaml = "*"
[dev-packages]
[requires]
python_version = "3"

189
stress-tests/Pipfile.lock generated Normal file
View File

@ -0,0 +1,189 @@
{
"_meta": {
"hash": {
"sha256": "7668d1f96f732a37135c5c4d46a18b784dd1e38661f5e8968bb31dcc0cc6ef18"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"async-generator": {
"hashes": [
"sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b",
"sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144"
],
"markers": "python_version >= '3.5'",
"version": "==1.10"
},
"attrs": {
"hashes": [
"sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836",
"sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"
],
"markers": "python_version >= '3.6'",
"version": "==22.2.0"
},
"certifi": {
"hashes": [
"sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3",
"sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"
],
"markers": "python_version >= '3.6'",
"version": "==2022.12.7"
},
"exceptiongroup": {
"hashes": [
"sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e",
"sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"
],
"markers": "python_version < '3.11'",
"version": "==1.1.0"
},
"h11": {
"hashes": [
"sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d",
"sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"
],
"markers": "python_version >= '3.7'",
"version": "==0.14.0"
},
"idna": {
"hashes": [
"sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4",
"sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"
],
"markers": "python_version >= '3.5'",
"version": "==3.4"
},
"outcome": {
"hashes": [
"sha256:6f82bd3de45da303cf1f771ecafa1633750a358436a8bb60e06a1ceb745d2672",
"sha256:c4ab89a56575d6d38a05aa16daeaa333109c1f96167aba8901ab18b6b5e0f7f5"
],
"markers": "python_version >= '3.7'",
"version": "==1.2.0"
},
"pysocks": {
"hashes": [
"sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299",
"sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5",
"sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"
],
"version": "==1.7.1"
},
"pyyaml": {
"hashes": [
"sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf",
"sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293",
"sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b",
"sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57",
"sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b",
"sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4",
"sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07",
"sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba",
"sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9",
"sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287",
"sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513",
"sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0",
"sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782",
"sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0",
"sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92",
"sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f",
"sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2",
"sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc",
"sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1",
"sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c",
"sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86",
"sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4",
"sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c",
"sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34",
"sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b",
"sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d",
"sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c",
"sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb",
"sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7",
"sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737",
"sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3",
"sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d",
"sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358",
"sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53",
"sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78",
"sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803",
"sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a",
"sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f",
"sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
"sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
],
"index": "pypi",
"version": "==6.0"
},
"selenium": {
"hashes": [
"sha256:bd04eb41395605d9b2b65fe587f3fed21431da75512985c52772529e5e210c60",
"sha256:c48372905bffcc3b24bd55ab4683a07ee5e1f30fe918c59558ea5ee44cedf6c3"
],
"index": "pypi",
"version": "==4.8.2"
},
"sniffio": {
"hashes": [
"sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101",
"sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"
],
"markers": "python_version >= '3.7'",
"version": "==1.3.0"
},
"sortedcontainers": {
"hashes": [
"sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88",
"sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"
],
"version": "==2.4.0"
},
"trio": {
"hashes": [
"sha256:ce68f1c5400a47b137c5a4de72c7c901bd4e7a24fbdebfe9b41de8c6c04eaacf",
"sha256:f1dd0780a89bfc880c7c7994519cb53f62aacb2c25ff487001c0052bd721cdf0"
],
"markers": "python_version >= '3.7'",
"version": "==0.22.0"
},
"trio-websocket": {
"hashes": [
"sha256:5b558f6e83cc20a37c3b61202476c5295d1addf57bd65543364e0337e37ed2bc",
"sha256:a3d34de8fac26023eee701ed1e7bf4da9a8326b61a62934ec9e53b64970fd8fe"
],
"markers": "python_version >= '3.5'",
"version": "==0.9.2"
},
"urllib3": {
"extras": [
"socks"
],
"hashes": [
"sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72",
"sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==1.26.14"
},
"wsproto": {
"hashes": [
"sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065",
"sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"
],
"markers": "python_version >= '3.7'",
"version": "==1.2.0"
}
},
"develop": {}
}

54
stress-tests/README.md Normal file
View File

@ -0,0 +1,54 @@
# Stress tests
By executing tests in this normalised fashion it is easier to compare results
between different instances or different patch levels.
This documents normalised stress-testing and references files under
[`stress-tests`][st].
[st]: https://gitlab.com/DD-workspace/DD/-/tree/main/stress-tests
## VM tests
- `vm-test.sh`: generate a text file to compare CPU and other factors across
VM types, providers or instances which may affect DD performance.
We can compare the resulting lgos just with, e.g. `vim -d`.
## DD tests
Currently these tests perform logins and interact with Nextcloud, but it would
be interesting to expand them to interact with other services.
### Directory contents
This directory contains following files:
- `dd-stress-test.tpl.jmx`: template to generate [JMeter][jm] tests to execute
- `dd-tests.sh`: helper script that generates the actual test plan files and
executes them. See `./dd-tests.sh --help`
- `dd-test-selenium.sh`: this gives us an idea of how a user would perceive
DD to be behaving while under load. Called by `./dd-tests.sh` by default.
### Results
Results will be saved in a `results` directory, where each subdirectory
corresponds to a stress test executed with `dd-test.sh`.
The naming scheme for those subdirectories is: `DOMAIN_THREADCOUNT_DURATION`
Where `THREADCOUNT` and `DURATION` are the corresponding [JMeter][jm]
parameters.
Each results directory contains:
- `log`: the [JMeter][jm] log
- `results`: the [JMeter][jm] results file
- `html/index.html`: the interactive graphs for the data as produced by JMeter
- `selenium/session.html`: the session report as would be perceived by a user.
Note this requires Python3 and selenium and can be disabled by setting the
environment variable: `USE_SELENIUM=NO`.
[jm]: https://jmeter.apache.org/

View File

@ -0,0 +1,822 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Originally designed by Teradisk.
Generalised and adapted by Evilham. -->
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.5">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="NextCloud Tests" enabled="true">
<stringProp name="TestPlan.comments"></stringProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="TestPlan.user_define_classpath"></stringProp>
</TestPlan>
<hashTree>
<Arguments guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="ThreadCount" elementType="Argument">
<stringProp name="Argument.name">ThreadCount</stringProp>
<stringProp name="Argument.value">10</stringProp> <!-- TC -->
<stringProp name="Argument.metadata">=</stringProp>
<stringProp name="Argument.desc">Number of Threads</stringProp>
</elementProp>
<elementProp name="Duration" elementType="Argument">
<stringProp name="Argument.name">Duration</stringProp>
<stringProp name="Argument.value">60</stringProp> <!-- DURATION -->
<stringProp name="Argument.metadata">=</stringProp>
<stringProp name="Argument.desc">in seconds</stringProp>
</elementProp>
<elementProp name="RampUpPeriod" elementType="Argument">
<stringProp name="Argument.name">RampUpPeriod</stringProp>
<stringProp name="Argument.value">10</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<stringProp name="Argument.desc">in seconds</stringProp>
</elementProp>
<elementProp name="IdPHost" elementType="Argument">
<stringProp name="Argument.name">IdPHost</stringProp>
<stringProp name="Argument.value">https://sso.DD_DOMAIN</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<stringProp name="Argument.desc">The main URL of your SSO Instance</stringProp>
</elementProp>
<elementProp name="IdPPort" elementType="Argument">
<stringProp name="Argument.name">IdPPort</stringProp>
<stringProp name="Argument.value">80</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="IdPContext" elementType="Argument">
<stringProp name="Argument.name">IdPContext</stringProp>
<stringProp name="Argument.value">idp</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="startupDelay" elementType="Argument">
<stringProp name="Argument.name">startupDelay</stringProp>
<stringProp name="Argument.value">1</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<stringProp name="Argument.desc">in seconds</stringProp>
</elementProp>
<elementProp name="LoginSP" elementType="Argument">
<stringProp name="Argument.name">LoginSP</stringProp>
<stringProp name="Argument.value">https://nextcloud.DD_DOMAIN/apps/user_saml/saml/login</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<stringProp name="Argument.desc">Domain of registered NC using SAML</stringProp>
</elementProp>
<elementProp name="ProviderId" elementType="Argument">
<stringProp name="Argument.name">ProviderId</stringProp>
<stringProp name="Argument.value">https://nextcloud.DD_DOMAIN/apps/user_saml/saml/metadata</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<stringProp name="Argument.desc">SAML EntityId for NC</stringProp>
</elementProp>
<elementProp name="ACSUrl" elementType="Argument">
<stringProp name="Argument.name">ACSUrl</stringProp>
<stringProp name="Argument.value">https://nextcloud.DD_DOMAIN/apps/user_saml/saml/acs</stringProp>
<stringProp name="Argument.desc">SP ACS Url to post the final response/assertion back</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="NextCloud" elementType="Argument">
<stringProp name="Argument.name">NextCloud</stringProp>
<stringProp name="Argument.value">nextcloud.DD_DOMAIN</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="ClientId" elementType="Argument">
<stringProp name="Argument.name">ClientId</stringProp>
<stringProp name="Argument.value">https://nextcloud.DD_DOMAIN/apps/user_saml/saml/metadata</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
<stringProp name="TestPlan.comments">Environment Variables that will need to be updated</stringProp>
</Arguments>
<hashTree/>
<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true">
<collectionProp name="HeaderManager.headers"/>
</HeaderManager>
<hashTree/>
<CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="CSV Get Users/Passwords" enabled="true">
<stringProp name="filename">./dd-stress-test.users.csv</stringProp>
<stringProp name="fileEncoding"></stringProp>
<stringProp name="variableNames">User,Password</stringProp>
<stringProp name="delimiter">,</stringProp>
<boolProp name="quotedData">false</boolProp>
<boolProp name="recycle">true</boolProp>
<boolProp name="stopThread">false</boolProp>
<stringProp name="shareMode">shareMode.all</stringProp>
<stringProp name="TestPlan.comments">No Spaces between User, comma, and Password fields!!</stringProp>
<boolProp name="ignoreFirstLine">false</boolProp>
</CSVDataSet>
<hashTree/>
<CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="CSV list folder/documents" enabled="true">
<stringProp name="filename">./dd-stress-test.docs.csv</stringProp>
<stringProp name="fileEncoding"></stringProp>
<stringProp name="variableNames">folder,document</stringProp>
<stringProp name="delimiter">,</stringProp>
<boolProp name="quotedData">false</boolProp>
<boolProp name="recycle">true</boolProp>
<boolProp name="stopThread">false</boolProp>
<stringProp name="shareMode">shareMode.all</stringProp>
<stringProp name="TestPlan.comments">No Spaces between Folder, comma, and DocumentID fields!!</stringProp>
<boolProp name="ignoreFirstLine">false</boolProp>
</CSVDataSet>
<hashTree/>
<CookieManager guiclass="CookiePanel" testclass="CookieManager" testname="HTTP Cookie Manager" enabled="true">
<collectionProp name="CookieManager.cookies"/>
<boolProp name="CookieManager.clearEachIteration">true</boolProp>
<boolProp name="CookieManager.controlledByThreadGroup">false</boolProp>
</CookieManager>
<hashTree/>
<ConfigTestElement guiclass="HttpDefaultsGui" testclass="ConfigTestElement" testname="HTTP Request Defaults" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path"></stringProp>
<stringProp name="HTTPSampler.concurrentPool">4</stringProp>
<stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
<stringProp name="HTTPSampler.connect_timeout">120000</stringProp>
<stringProp name="HTTPSampler.response_timeout">120000</stringProp>
</ConfigTestElement>
<hashTree/>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="SAML Login Method" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">stopthread</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<intProp name="LoopController.loops">-1</intProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">${ThreadCount}</stringProp>
<stringProp name="ThreadGroup.ramp_time">${RampUpPeriod}</stringProp>
<longProp name="ThreadGroup.start_time">1500501650000</longProp>
<longProp name="ThreadGroup.end_time">1500501650000</longProp>
<boolProp name="ThreadGroup.scheduler">true</boolProp>
<stringProp name="ThreadGroup.duration">${Duration}</stringProp>
<stringProp name="ThreadGroup.delay">${StartupDelay}</stringProp>
<stringProp name="TestPlan.comments">SAML Support Login Process</stringProp>
<boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
<boolProp name="ThreadGroup.delayedStart">true</boolProp>
</ThreadGroup>
<hashTree>
<ModuleController guiclass="ModuleControllerGui" testclass="ModuleController" testname="NEXTCLOUD Login" enabled="true">
<collectionProp name="ModuleController.node_path">
<stringProp name="764597751">Test Plan</stringProp>
<stringProp name="1997185859">NextCloud Tests</stringProp>
<stringProp name="-911831893">NEXTCLOUD Login</stringProp>
</collectionProp>
<stringProp name="TestPlan.comments">SAML2 SP calls the login page for CAS IdP</stringProp>
</ModuleController>
<hashTree/>
<ModuleController guiclass="ModuleControllerGui" testclass="ModuleController" testname="POST - Login User" enabled="true">
<collectionProp name="ModuleController.node_path">
<stringProp name="764597751">Test Plan</stringProp>
<stringProp name="1997185859">NextCloud Tests</stringProp>
<stringProp name="-497736779">POST - Login User</stringProp>
</collectionProp>
<stringProp name="TestPlan.comments">Logging into CAS Idp</stringProp>
</ModuleController>
<hashTree/>
<ModuleController guiclass="ModuleControllerGui" testclass="ModuleController" testname="POST - Authorization to NEXTCLOUD" enabled="true">
<collectionProp name="ModuleController.node_path">
<stringProp name="764597751">Test Plan</stringProp>
<stringProp name="1997185859">NextCloud Tests</stringProp>
<stringProp name="329930583">POST - Authorization to NEXTCLOUD</stringProp>
</collectionProp>
<stringProp name="TestPlan.comments">Send response from login to SP for processing</stringProp>
</ModuleController>
<hashTree/>
<ModuleController guiclass="ModuleControllerGui" testclass="ModuleController" testname="GET - Document" enabled="true">
<collectionProp name="ModuleController.node_path">
<stringProp name="764597751">Test Plan</stringProp>
<stringProp name="1997185859">NextCloud Tests</stringProp>
<stringProp name="-1050525960">GET - Document</stringProp>
</collectionProp>
<stringProp name="TestPlan.comments">Send response from login to SP for processing</stringProp>
</ModuleController>
<hashTree/>
<ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report 20 users" enabled="true">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>false</xml>
<fieldNames>true</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
<sentBytes>true</sentBytes>
<url>true</url>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
<ResultCollector guiclass="RespTimeGraphVisualizer" testclass="ResultCollector" testname="Response Time Graph" enabled="true">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>false</xml>
<fieldNames>true</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
<sentBytes>true</sentBytes>
<url>true</url>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
</hashTree>
<TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="NEXTCLOUD Login" enabled="true"/>
<hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="GET SAML2 Protected Page 2" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${LoginSP}/login</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="TestPlan.comments">Calling secured SP page, that should then redirect to CAS login page</stringProp>
</HTTPSamplerProxy>
<hashTree>
<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true">
<collectionProp name="HeaderManager.headers">
<elementProp name="Sec-Fetch-Mode" elementType="Header">
<stringProp name="Header.name">Sec-Fetch-Mode</stringProp>
<stringProp name="Header.value">navigate</stringProp>
</elementProp>
<elementProp name="Sec-Fetch-Site" elementType="Header">
<stringProp name="Header.name">Sec-Fetch-Site</stringProp>
<stringProp name="Header.value">none</stringProp>
</elementProp>
<elementProp name="Accept-Language" elementType="Header">
<stringProp name="Header.name">Accept-Language</stringProp>
<stringProp name="Header.value">en-US,en;q=0.5</stringProp>
</elementProp>
<elementProp name="Sec-Fetch-User" elementType="Header">
<stringProp name="Header.name">Sec-Fetch-User</stringProp>
<stringProp name="Header.value">?1</stringProp>
</elementProp>
<elementProp name="Pragma" elementType="Header">
<stringProp name="Header.name">Pragma</stringProp>
<stringProp name="Header.value">no-cache</stringProp>
</elementProp>
<elementProp name="Accept" elementType="Header">
<stringProp name="Header.name">Accept</stringProp>
<stringProp name="Header.value">text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8</stringProp>
</elementProp>
<elementProp name="Upgrade-Insecure-Requests" elementType="Header">
<stringProp name="Header.name">Upgrade-Insecure-Requests</stringProp>
<stringProp name="Header.value">1</stringProp>
</elementProp>
<elementProp name="Cache-Control" elementType="Header">
<stringProp name="Header.name">Cache-Control</stringProp>
<stringProp name="Header.value">no-cache</stringProp>
</elementProp>
<elementProp name="Accept-Encoding" elementType="Header">
<stringProp name="Header.name">Accept-Encoding</stringProp>
<stringProp name="Header.value">gzip, deflate, br</stringProp>
</elementProp>
<elementProp name="User-Agent" elementType="Header">
<stringProp name="Header.name">User-Agent</stringProp>
<stringProp name="Header.value">Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0</stringProp>
</elementProp>
<elementProp name="Sec-Fetch-Dest" elementType="Header">
<stringProp name="Header.name">Sec-Fetch-Dest</stringProp>
<stringProp name="Header.value">document</stringProp>
</elementProp>
</collectionProp>
</HeaderManager>
<hashTree/>
<RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Extract RelayState Variable" enabled="true">
<stringProp name="RegexExtractor.useHeaders">false</stringProp>
<stringProp name="RegexExtractor.refname">RelayState</stringProp>
<stringProp name="RegexExtractor.regex">&lt;input type=&quot;hidden&quot; name=&quot;RelayState&quot; value=&quot;(.+?)&quot;</stringProp>
<stringProp name="RegexExtractor.template">$1$</stringProp>
<stringProp name="RegexExtractor.default"></stringProp>
<stringProp name="RegexExtractor.match_number">1</stringProp>
<stringProp name="Sample.scope">all</stringProp>
</RegexExtractor>
<hashTree/>
<RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Extract SAMLRequest Variable" enabled="true">
<stringProp name="RegexExtractor.useHeaders">false</stringProp>
<stringProp name="RegexExtractor.refname">SAMLResponse</stringProp>
<stringProp name="RegexExtractor.regex">&lt;input type=&quot;hidden&quot; name=&quot;SAMLResponse&quot; value=&quot;(.+?)&quot;</stringProp>
<stringProp name="RegexExtractor.template">$1$</stringProp>
<stringProp name="RegexExtractor.default"></stringProp>
<stringProp name="RegexExtractor.match_number">1</stringProp>
<stringProp name="Sample.scope">all</stringProp>
</RegexExtractor>
<hashTree/>
<RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Extract Execution Variable" enabled="true">
<stringProp name="RegexExtractor.useHeaders">false</stringProp>
<stringProp name="RegexExtractor.refname">Execution</stringProp>
<stringProp name="RegexExtractor.regex">\;execution=(.+?)&amp;amp;</stringProp>
<stringProp name="RegexExtractor.template">$1$</stringProp>
<stringProp name="RegexExtractor.default"></stringProp>
<stringProp name="RegexExtractor.match_number"></stringProp>
<stringProp name="Sample.scope">all</stringProp>
</RegexExtractor>
<hashTree/>
<RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Extract session_code Variable" enabled="true">
<stringProp name="RegexExtractor.useHeaders">false</stringProp>
<stringProp name="RegexExtractor.refname">SessionCode</stringProp>
<stringProp name="RegexExtractor.regex">session_code=(.*)&amp;amp;ex</stringProp>
<stringProp name="RegexExtractor.template">$1$</stringProp>
<stringProp name="RegexExtractor.default"></stringProp>
<stringProp name="RegexExtractor.match_number"></stringProp>
<stringProp name="Sample.scope">all</stringProp>
</RegexExtractor>
<hashTree/>
<RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Extract tab_id Variable" enabled="true">
<stringProp name="RegexExtractor.useHeaders">false</stringProp>
<stringProp name="RegexExtractor.refname">tab_id</stringProp>
<stringProp name="RegexExtractor.regex">\;tab_id=(.*)\&quot; me</stringProp>
<stringProp name="RegexExtractor.template">$1$</stringProp>
<stringProp name="RegexExtractor.default"></stringProp>
<stringProp name="RegexExtractor.match_number"></stringProp>
<stringProp name="Sample.scope">all</stringProp>
</RegexExtractor>
<hashTree/>
<RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Extract SAMLRequest Variable NC-21" enabled="true">
<stringProp name="RegexExtractor.useHeaders">URL</stringProp>
<stringProp name="RegexExtractor.refname">SAMLRequest</stringProp>
<stringProp name="RegexExtractor.regex">SAMLRequest=(.+)</stringProp>
<stringProp name="RegexExtractor.template">$1$</stringProp>
<stringProp name="RegexExtractor.default"></stringProp>
<stringProp name="RegexExtractor.match_number">1</stringProp>
<stringProp name="Sample.scope">all</stringProp>
</RegexExtractor>
<hashTree/>
</hashTree>
</hashTree>
<TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="POST - Login User" enabled="true"/>
<hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="POST User Login Credentials" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="username" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">true</boolProp>
<stringProp name="Argument.value">${User}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">username</stringProp>
<stringProp name="Argument.desc">false</stringProp>
</elementProp>
<elementProp name="password" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">true</boolProp>
<stringProp name="Argument.value">${Password}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">password</stringProp>
<stringProp name="Argument.desc">false</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${IdPHost}/auth/realms/master/login-actions/authenticate?session_code=${SessionCode}&amp;execution=${Execution}&amp;client_id=${__urlencode(${ClientId})}&amp;tab_id=${tab_id}</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="TestPlan.comments">POST Login Credentials for SAMLResponse</stringProp>
</HTTPSamplerProxy>
<hashTree>
<RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Extract RelayState Variable" enabled="true">
<stringProp name="RegexExtractor.useHeaders">unescaped</stringProp>
<stringProp name="RegexExtractor.refname">RelayState</stringProp>
<stringProp name="RegexExtractor.regex">&lt;input type=&quot;hidden&quot; name=&quot;RelayState&quot; value=&quot;(.+?)&quot;</stringProp>
<stringProp name="RegexExtractor.template">$1$</stringProp>
<stringProp name="RegexExtractor.default"></stringProp>
<stringProp name="RegexExtractor.match_number">1</stringProp>
<stringProp name="Sample.scope">all</stringProp>
</RegexExtractor>
<hashTree/>
<RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Extract SAMLResponse Variable" enabled="true">
<stringProp name="RegexExtractor.useHeaders">unescaped</stringProp>
<stringProp name="RegexExtractor.refname">SAMLResponse</stringProp>
<stringProp name="RegexExtractor.regex">&lt;input type=&quot;hidden&quot; name=&quot;SAMLResponse&quot; value=&quot;(.+?)&quot;</stringProp>
<stringProp name="RegexExtractor.template">$1$</stringProp>
<stringProp name="RegexExtractor.default"></stringProp>
<stringProp name="RegexExtractor.match_number">1</stringProp>
<stringProp name="Sample.scope">all</stringProp>
</RegexExtractor>
<hashTree/>
</hashTree>
</hashTree>
<TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="POST - Authorization to NEXTCLOUD" enabled="true"/>
<hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="POST Authorization back to SP" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="RelayState" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">true</boolProp>
<stringProp name="Argument.value">${RelayState}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">RelayState</stringProp>
</elementProp>
<elementProp name="SAMLResponse" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">true</boolProp>
<stringProp name="Argument.value">${SAMLResponse}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">SAMLResponse</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${ACSUrl}</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
</hashTree>
<TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="GET - Document" enabled="true"/>
<hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="GET - home" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="OCS-APIREQUEST" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">true</boolProp>
<stringProp name="Argument.name">OCS-APIREQUEST</stringProp>
<stringProp name="Argument.value">true</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain">${NextCloud}</stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path"></stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree>
<RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Extract Execution Variable" enabled="true">
<stringProp name="RegexExtractor.useHeaders">false</stringProp>
<stringProp name="RegexExtractor.refname">requestToken</stringProp>
<stringProp name="RegexExtractor.regex">data-requesttoken=&quot;(.+)\&quot;</stringProp>
<stringProp name="RegexExtractor.template">$1$</stringProp>
<stringProp name="RegexExtractor.default"></stringProp>
<stringProp name="RegexExtractor.match_number"></stringProp>
<stringProp name="Sample.scope">all</stringProp>
</RegexExtractor>
<hashTree/>
</hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="List - Folder Documents" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="dir" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.name">dir</stringProp>
<stringProp name="Argument.value">Documents</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain">${NextCloud}</stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/apps/files</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree>
<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header manager" enabled="true">
<collectionProp name="HeaderManager.headers">
<elementProp name="Accept" elementType="Header">
<stringProp name="Header.name">Accept</stringProp>
<stringProp name="Header.value">application/json, text/plain, */*</stringProp>
</elementProp>
<elementProp name="requesttoken" elementType="Header">
<stringProp name="Header.name">requesttoken</stringProp>
<stringProp name="Header.value">${requestToken}</stringProp>
</elementProp>
</collectionProp>
</HeaderManager>
<hashTree/>
</hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="PROFIND - File Redis PDF (viewer)" enabled="true">
<boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
<elementProp name="HTTPsampler.Arguments" elementType="Arguments">
<collectionProp name="Arguments.arguments">
<elementProp name="" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">&lt;?xml version=&quot;1.0&quot;?&gt;&#xd;
&lt;d:propfind xmlns:d=&quot;DAV:&quot; xmlns:oc=&quot;http://owncloud.org/ns&quot; xmlns:nc=&quot;http://nextcloud.org/ns&quot; xmlns:ocs=&quot;http://open-collaboration-services.org/ns&quot;&gt;&#xd;
&lt;d:prop&gt;&#xd;
&lt;d:getlastmodified /&gt;&#xd;
&lt;d:getetag /&gt;&#xd;
&lt;d:getcontenttype /&gt;&#xd;
&lt;d:resourcetype /&gt;&#xd;
&lt;oc:fileid /&gt;&#xd;
&lt;oc:permissions /&gt;&#xd;
&lt;oc:size /&gt;&#xd;
&lt;d:getcontentlength /&gt;&#xd;
&lt;d:quota-available-bytes /&gt;&#xd;
&lt;nc:has-preview /&gt;&#xd;
&lt;nc:mount-type /&gt;&#xd;
&lt;nc:is-encrypted /&gt;&#xd;
&lt;ocs:share-permissions /&gt;&#xd;
&lt;nc:share-attributes /&gt;&#xd;
&lt;oc:tags /&gt;&#xd;
&lt;oc:favorite /&gt;&#xd;
&lt;oc:owner-id /&gt;&#xd;
&lt;oc:owner-display-name /&gt;&#xd;
&lt;oc:share-types /&gt;&#xd;
&lt;oc:comments-unread /&gt;&#xd;
&lt;/d:prop&gt;&#xd;
&lt;/d:propfind&gt;</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain">${NextCloud}</stringProp>
<stringProp name="HTTPSampler.port">443</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding">UTF-8</stringProp>
<stringProp name="HTTPSampler.path">/remote.php/dav/files/${User}</stringProp>
<stringProp name="HTTPSampler.method">PROPFIND</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree>
<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true">
<collectionProp name="HeaderManager.headers">
<elementProp name="Sec-Fetch-Mode" elementType="Header">
<stringProp name="Header.name">Sec-Fetch-Mode</stringProp>
<stringProp name="Header.value">cors</stringProp>
</elementProp>
<elementProp name="Sec-Fetch-Site" elementType="Header">
<stringProp name="Header.name">Sec-Fetch-Site</stringProp>
<stringProp name="Header.value">same-origin</stringProp>
</elementProp>
<elementProp name="Accept-Language" elementType="Header">
<stringProp name="Header.name">Accept-Language</stringProp>
<stringProp name="Header.value">en-US,en;q=0.5</stringProp>
</elementProp>
<elementProp name="Origin" elementType="Header">
<stringProp name="Header.name">Origin</stringProp>
<stringProp name="Header.value">https://${NextCloud}</stringProp>
</elementProp>
<elementProp name="Accept" elementType="Header">
<stringProp name="Header.name">Accept</stringProp>
<stringProp name="Header.value">text/plain</stringProp>
</elementProp>
<elementProp name="Depth" elementType="Header">
<stringProp name="Header.name">Depth</stringProp>
<stringProp name="Header.value">0</stringProp>
</elementProp>
<elementProp name="X-Requested-With" elementType="Header">
<stringProp name="Header.name">X-Requested-With</stringProp>
<stringProp name="Header.value">XMLHttpRequest</stringProp>
</elementProp>
<elementProp name="Content-Type" elementType="Header">
<stringProp name="Header.name">Content-Type</stringProp>
<stringProp name="Header.value">text/plain;charset=UTF-8</stringProp>
</elementProp>
<elementProp name="Accept-Encoding" elementType="Header">
<stringProp name="Header.name">Accept-Encoding</stringProp>
<stringProp name="Header.value">gzip, deflate, br</stringProp>
</elementProp>
<elementProp name="User-Agent" elementType="Header">
<stringProp name="Header.name">User-Agent</stringProp>
<stringProp name="Header.value">Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0</stringProp>
</elementProp>
<elementProp name="Sec-Fetch-Dest" elementType="Header">
<stringProp name="Header.name">Sec-Fetch-Dest</stringProp>
<stringProp name="Header.value">empty</stringProp>
</elementProp>
<elementProp name="" elementType="Header">
<stringProp name="Header.name">requesttoken</stringProp>
<stringProp name="Header.value">${requestToken}</stringProp>
</elementProp>
</collectionProp>
</HeaderManager>
<hashTree/>
</hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="GET - viewer" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="file" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.name">file</stringProp>
<stringProp name="Argument.value">https://${NextCloud}/remote.php/dav/files/${User}/${document}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
</elementProp>
<elementProp name="canDownload" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.name">canDownload</stringProp>
<stringProp name="Argument.value">1</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain">${NextCloud}</stringProp>
<stringProp name="HTTPSampler.port">443</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/apps/files_pdfviewer/</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree>
<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true">
<collectionProp name="HeaderManager.headers">
<elementProp name="Sec-Fetch-Mode" elementType="Header">
<stringProp name="Header.name">Sec-Fetch-Mode</stringProp>
<stringProp name="Header.value">navigate</stringProp>
</elementProp>
<elementProp name="Sec-Fetch-Site" elementType="Header">
<stringProp name="Header.name">Sec-Fetch-Site</stringProp>
<stringProp name="Header.value">same-origin</stringProp>
</elementProp>
<elementProp name="Accept-Language" elementType="Header">
<stringProp name="Header.name">Accept-Language</stringProp>
<stringProp name="Header.value">en-US,en;q=0.5</stringProp>
</elementProp>
<elementProp name="Upgrade-Insecure-Requests" elementType="Header">
<stringProp name="Header.name">Upgrade-Insecure-Requests</stringProp>
<stringProp name="Header.value">1</stringProp>
</elementProp>
<elementProp name="Accept-Encoding" elementType="Header">
<stringProp name="Header.name">Accept-Encoding</stringProp>
<stringProp name="Header.value">gzip, deflate, br</stringProp>
</elementProp>
<elementProp name="User-Agent" elementType="Header">
<stringProp name="Header.name">User-Agent</stringProp>
<stringProp name="Header.value">Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0</stringProp>
</elementProp>
<elementProp name="Accept" elementType="Header">
<stringProp name="Header.name">Accept</stringProp>
<stringProp name="Header.value">text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8</stringProp>
</elementProp>
<elementProp name="Sec-Fetch-Dest" elementType="Header">
<stringProp name="Header.name">Sec-Fetch-Dest</stringProp>
<stringProp name="Header.value">iframe</stringProp>
</elementProp>
<elementProp name="" elementType="Header">
<stringProp name="Header.name">requesttoken</stringProp>
<stringProp name="Header.value">${requestToken}</stringProp>
</elementProp>
</collectionProp>
</HeaderManager>
<hashTree/>
</hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Open Document" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">${NextCloud}</stringProp>
<stringProp name="HTTPSampler.port">443</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding">utf-8</stringProp>
<stringProp name="HTTPSampler.path">/remote.php/dav/files/${User}/${document}</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree>
<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true">
<collectionProp name="HeaderManager.headers">
<elementProp name="Sec-Fetch-Mode" elementType="Header">
<stringProp name="Header.name">Sec-Fetch-Mode</stringProp>
<stringProp name="Header.value">cors</stringProp>
</elementProp>
<elementProp name="Sec-Fetch-Site" elementType="Header">
<stringProp name="Header.name">Sec-Fetch-Site</stringProp>
<stringProp name="Header.value">same-origin</stringProp>
</elementProp>
<elementProp name="Accept-Language" elementType="Header">
<stringProp name="Header.name">Accept-Language</stringProp>
<stringProp name="Header.value">en-US,en;q=0.5</stringProp>
</elementProp>
<elementProp name="Accept-Encoding" elementType="Header">
<stringProp name="Header.name">Accept-Encoding</stringProp>
<stringProp name="Header.value">gzip, deflate, br</stringProp>
</elementProp>
<elementProp name="User-Agent" elementType="Header">
<stringProp name="Header.name">User-Agent</stringProp>
<stringProp name="Header.value">Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0</stringProp>
</elementProp>
<elementProp name="Accept" elementType="Header">
<stringProp name="Header.name">Accept</stringProp>
<stringProp name="Header.value">*/*</stringProp>
</elementProp>
<elementProp name="Sec-Fetch-Dest" elementType="Header">
<stringProp name="Header.name">Sec-Fetch-Dest</stringProp>
<stringProp name="Header.value">empty</stringProp>
</elementProp>
<elementProp name="" elementType="Header">
<stringProp name="Header.name">requesttoken</stringProp>
<stringProp name="Header.value">${requestToken}</stringProp>
</elementProp>
</collectionProp>
</HeaderManager>
<hashTree/>
</hashTree>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>

View File

@ -0,0 +1,361 @@
#!/usr/bin/env python3
import argparse
import io
import pathlib
import random
import traceback
from datetime import datetime
import yaml
from selenium import webdriver
# We need selenium 4
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options as ChromeOptions
from typing import Any, Dict, Iterable, List
class DDSession:
driver: webdriver.Chrome
def __init__(
self,
instance: str,
users_file: str,
out_dir: pathlib.Path,
stepTimeoutSeconds: int = 20,
):
self.instance = instance
self.users_file = [
[i.strip() for i in l.split(sep=",", maxsplit=1)]
for l in users_file.split("\n")
if l
]
self.out_dir = out_dir
self.stepTimeoutSeconds = stepTimeoutSeconds
if not self.out_dir.is_dir():
self.out_dir.mkdir()
self.start_time = datetime.utcnow()
self.last_time = self.start_time
self.screenshot_counter = 0
self.data: Dict[str, Any] = dict(
instance=self.instance, start_time=self.start_time, steps=[]
)
self.reset()
def persist(self) -> None:
yaml.dump(self.data, (self.out_dir / "session.yml").open("w"))
(self.out_dir / "session.html").open("w").write(self.summarise()["html"])
def summarise(self) -> Dict[str, Any]:
return DDSession.summarise_data(self.data)
@staticmethod
def summarise_data(data) -> Dict[str, Any]:
import copy
from operator import getitem
from itertools import groupby
def summarise_item(g: List[Dict[str, Any]]) -> Dict[str, Any]:
if "substeps" in g[0]:
items = [sum((st["step_t"] for st in s["substeps"])) for s in g]
else:
items = [st["step_t"] for st in g]
return {
"max": max(items),
"count": len(items),
"average": sum(items) / len(items),
}
def summarise(d: Iterable[Dict[str, Any]]) -> Dict[str, Any]:
dict_data = dict()
for k, g in groupby(
sorted(d, key=lambda x: getitem(x, "step")),
key=lambda x: getitem(x, "step"),
):
dict_data[k] = summarise_item(list(g))
return dict_data
summary: Dict[str, Any] = dict()
summary["overview"] = summarise(data["steps"])
d = copy.deepcopy(data["steps"])
substeps = [
dict(st, step=f"{step['step']}: {st['substep']}")
for step in data["steps"]
for st in step["substeps"]
]
summary["stepbystep"] = summarise(substeps)
html = """<html>
<head><style>img { margin-bottom: 60px; }</style></head>
<body>
"""
html += f"""
<h1>{data['instance']}</h1>
<h2>{data['start_time']}</h2>
<details><summary>Summary</summary>
<pre>{yaml.dump(summary)}
</pre>
</details>
"""
for s in data["steps"]:
html += f"""<h3>{s['step']}: {s['t']} s (+ {sum((st['step_t'] for st in s['substeps']))} s)</h3>"""
for st in s["substeps"]:
html += f"""<h4>{s['step']} - {st['substep']}: {st['t']} s (+ {st['step_t']} s)</h4>"""
html += f"""<img src="{st['png']}"/>"""
html += "</body></html>"
summary["html"] = html
return summary
@property
def executed_seconds(self) -> float:
return (datetime.utcnow() - self.start_time).total_seconds()
@property
def nextcloud_url(self) -> str:
return f"https://nextcloud.{self.instance}"
def reset(self, new_driver: bool = False) -> None:
# If needed:
# https://stackoverflow.com/a/72922584
if getattr(self, "driver", None) is None or new_driver:
if getattr(self, "driver", None) is not None:
self.driver.quit()
options = ChromeOptions()
for arg in [
"--headless",
"--no-sandbox",
"--disable-infobars",
"--disable-extensions",
"--disable-dev-shm-usage",
"window-size=1400,600",
]:
options.add_argument(arg)
self.driver = webdriver.Chrome(options=options)
msg = "Restart"
else:
self.driver.delete_all_cookies()
msg = "Cleaned cache"
self.screenshot("Browser", msg)
def perform_login(self) -> None:
# Wait until Keycloak is shown
WebDriverWait(self.driver, timeout=self.stepTimeoutSeconds).until(
lambda d: d.find_elements(By.ID, "kc-form-login")
)
self.screenshot("Login", "Request")
# Fill in form data for login
(username, password) = random.choice(self.users_file)
usernameField = self.driver.find_element(By.ID, "username")
usernameField.send_keys(username)
passwordField = self.driver.find_element(By.ID, "password")
passwordField.send_keys(password)
loginButton = self.driver.find_element(By.ID, "kc-login")
self.screenshot("Login", "Form filled")
loginButton.click()
self.screenshot("Login", "Form sent")
# TODO: wait for redirection middle step and session.screenshot it
def load_nextcloud_files(self) -> None:
# Start loading
self.driver.get(self.nextcloud_url)
# Wait until Nextcloud with Megamenu is shown or a login is requested
WebDriverWait(self.driver, timeout=self.stepTimeoutSeconds).until(
lambda d: (
d.find_elements(By.ID, "kc-form-login")
+ d.find_elements(By.ID, "menu-apps-icon")
)
)
# Detect whether or not a login is needed
login_required = self.driver.find_elements(By.ID, "kc-form-login")
if login_required:
# Perform login
self.perform_login()
self.screenshot("Nextcloud", "Loading")
# Wait until Nextcloud files app has loaded
WebDriverWait(self.driver, timeout=self.stepTimeoutSeconds).until(
lambda d: d.find_elements(
By.CSS_SELECTOR, "#app-content-files td.selection"
)
)
self.screenshot("Nextcloud", "Loaded")
def open_file_in_onlyoffice(self, file_base_name: str) -> None:
# Get and click docx file
# This assumes we are on the nextcloud files app with a FILE_BASE file
window_count = len(self.driver.window_handles)
docxFileLabel = self.driver.find_element(By.PARTIAL_LINK_TEXT, file_base_name)
docxFileLabel.click()
# Switch to OnlyOffice window
WebDriverWait(self.driver, timeout=self.stepTimeoutSeconds).until(
EC.number_of_windows_to_be(window_count + 1)
)
child = self.driver.window_handles[window_count]
self.driver.switch_to.window(child)
self.screenshot("OnlyOffice", "Opening")
# Wait for OnlyOffice to start loading
oofCSS = "div.app-onlyoffice #app iframe"
WebDriverWait(self.driver, timeout=self.stepTimeoutSeconds).until(
lambda d: d.find_elements(By.CSS_SELECTOR, oofCSS)
)
self.screenshot("OnlyOffice", "Loading 1")
# Switch to its iframe
oof = WebDriverWait(self.driver, timeout=self.stepTimeoutSeconds).until(
lambda d: EC.frame_to_be_available_and_switch_to_it(
d.find_element(By.CSS_SELECTOR, oofCSS)
)(d)
)
self.screenshot("OnlyOffice", "Loading 2")
oofLoaded = lambda d: EC.element_to_be_clickable(
d.find_element(By.ID, "id-toolbar-btn-save")
)(d)
# Get the first loading screen
WebDriverWait(self.driver, timeout=self.stepTimeoutSeconds).until(
lambda d: oofLoaded(d)
or d.find_elements(By.CSS_SELECTOR, "#loading-mask div.loader-page")
)
self.screenshot("OnlyOffice", "Loading 3")
# Wait for OnlyOffice second loading phase
WebDriverWait(self.driver, timeout=self.stepTimeoutSeconds).until(
lambda d: oofLoaded(d)
or d.find_elements(By.CSS_SELECTOR, "div.asc-loadmask-title")
)
self.screenshot("OnlyOffice", "Loading 4")
# Wait a for final loading (save button clickable)
WebDriverWait(self.driver, timeout=self.stepTimeoutSeconds * 3).until(oofLoaded)
self.screenshot("OnlyOffice", "Loaded")
# Close OnlyOffice window
self.driver.close()
# And change back to the main window
self.driver.switch_to.window(self.driver.window_handles[window_count - 1])
self.screenshot("OnlyOffice", "Closed")
def screenshot(self, step: str, substep_txt: str) -> None:
# Get data
scr_now = datetime.utcnow()
scr_s = (scr_now - self.start_time).total_seconds()
step_t = (scr_now - self.last_time).total_seconds()
scr_id = self.screenshot_counter
# Upgrade values
self.last_time = scr_now
self.screenshot_counter += 1
# Constants
scr_fn = f"{scr_id:04d}.png"
scr_path = self.out_dir / scr_fn
# Get screenie
scr_img = self.driver.get_screenshot_as_png()
# Write it
open(scr_path, "wb").write(scr_img)
# Add to report
substep: Dict[str, Any] = dict(
id=scr_id, substep=substep_txt, step_t=step_t, t=scr_s, png=scr_fn
)
if self.data["steps"] and self.data["steps"][-1]["step"] == step:
self.data["steps"][-1]["substeps"].append(substep)
else:
self.data["steps"].append(dict(step=step, t=scr_s, substeps=[substep]))
def randomBool() -> bool:
return random.choice([True, False])
def main_test(
instance: str,
users_file: io.TextIOWrapper,
out_dir: pathlib.Path,
duration: int,
stepTimeoutSeconds: int = 20,
) -> DDSession:
session = DDSession(
instance=instance,
users_file=users_file.read(),
out_dir=out_dir,
stepTimeoutSeconds=20,
)
first_run = True
while session.executed_seconds < duration:
# 50% chance of requiring login reseting cookies
# First run always requires login, skip reset
if not first_run and randomBool():
# Possibly driver too
# 25% chance of cleaning all cache and reopening the browser
session.reset(new_driver=randomBool())
first_run = False
try:
# Load nextcloud files
session.load_nextcloud_files()
# Open file in OnlyOffice
session.open_file_in_onlyoffice("template_1")
except Exception as ex:
session.screenshot("Exception", str(ex))
print(traceback.format_exc())
session.persist()
return session
def main_summary(filename: io.TextIOWrapper) -> None:
data = yaml.safe_load(filename)
summary = DDSession.summarise_data(data)
html_fn = pathlib.Path(filename.name).with_suffix(".html")
open(html_fn, "w").write(summary["html"])
if __name__ == "__main__":
import pathlib
parser = argparse.ArgumentParser(
prog="DD Selenium tester", description="Run basic UI tests on DD"
)
subparsers = parser.add_subparsers(required=True)
test_parser = subparsers.add_parser("test")
test_parser.add_argument("instance")
test_parser.add_argument(
"-u",
"--users-file",
default="dd-stress-test.users.csv",
type=argparse.FileType("r"),
)
test_parser.add_argument("-d", "--duration", default=300, type=int)
test_parser.add_argument("-o", "--out-dir", default="results", type=pathlib.Path)
summary_parser = subparsers.add_parser("summary")
summary_parser.add_argument(
"filename", default="session.yml", type=argparse.FileType("r")
)
ns = parser.parse_args()
if "instance" in ns:
main_test(**vars(ns))
else:
main_summary(**vars(ns))

180
stress-tests/dd-test.sh Executable file
View File

@ -0,0 +1,180 @@
#!/bin/sh -eu
# Process inputs
DD_DOMAIN="${1:-}"
tc="${2:-}"
duration="${3:-60}"
USE_SELENIUM="${USE_SELENIUM:-YES}"
SCRIPT_PATH="$(realpath "${0}")"
SCRIPT_NAME="$(basename "${0}")"
JMETER_DEFAULT="./apache-jmeter-5.5/bin/jmeter"
full_tests() {
# Runtime: 7 tests * 5 mins / test = 35 mins
# Cool-off periods: 30s * 6 = 3 mins
# Total: 38 mins
cooloff="30"
"${SCRIPT_PATH}" "${DD_DOMAIN}" 10 300
sleep "${cooloff}"
"${SCRIPT_PATH}" "${DD_DOMAIN}" 20 300
sleep "${cooloff}"
"${SCRIPT_PATH}" "${DD_DOMAIN}" 30 300
sleep "${cooloff}"
"${SCRIPT_PATH}" "${DD_DOMAIN}" 60 300
sleep "${cooloff}"
"${SCRIPT_PATH}" "${DD_DOMAIN}" 100 300
sleep "${cooloff}"
"${SCRIPT_PATH}" "${DD_DOMAIN}" 300 300
sleep "${cooloff}"
"${SCRIPT_PATH}" "${DD_DOMAIN}" 600 300
}
help_users_file() {
cat <<-EOF
The format of the users.csv file must be:
USERNAME1,PASSWORD1
USERNAME2,PASSWORD2
...
Take care not to have any spaces between fields.
EOF
}
help_jmeter() {
cat <<-EOF
Note this scripts depends on JMeter with some plugins enabled.
You can set the JMETER environment variable to its binary path.
If this variable is unset, ${JMETER_DEFAULT}
will be used, from this script's location.
See:
https://jmeter.apache.org/download_jmeter.cgi
https://jmeter-plugins.org/install/Install/
EOF
}
help() {
cat <<-EOF
Examples:
./${SCRIPT_NAME} DD_DOMAIN THREAD_COUNT [DURATION]
or:
./${SCRIPT_NAME} --full-tests DD_DOMAIN
EOF
help_jmeter
cat <<-EOF
When using --full-tests, a pre-selected combination of
THREAD_COUNT and DURATION will be used against DD_DOMAIN.
Where DD_DOMAIN is the base domain, e.g. if your DD instance's
Nextcloud can be accessed at nextcloud.example.org, the parameter
should be "example.org".
THREAD_COUNT refers to the amount of users that will be simulated.
DURATION is the total test time time in seconds. Defaults to 60.
Note that you MUST have a users.csv file in the current directory.
By default this script runs tests with selenium and documents the
session as would be perceived by a user.
You can disable this behaviour by setting the environment variable
USE_SELENIUM=NO.
EOF
help_users_file
}
if [ "${DD_DOMAIN:-}" = "--full-tests" ]; then
shift # Consume operation argument
# Re-set global variable
DD_DOMAIN="${1:-}"
# Execute full suite
full_tests
# And exit
exit 0
elif [ "${1:-}" = "--help" ]; then
help
exit 0
elif [ -z "${DD_DOMAIN:-}" ] || [ -z "${tc:-}" ]; then
help >> /dev/stderr
exit 1
fi
USERS_FILE="$(pwd)/users.csv"
DOCS_FILE="$(pwd)/docs.csv"
# Change current path
cd "$(dirname "${SCRIPT_PATH}")"
out_dir="$(pwd)/results/${DD_DOMAIN}_${tc}_${duration}"
if [ -f "${USERS_FILE}" ]; then
cat "${USERS_FILE}" > dd-stress-test.users.csv
else
printf "ERROR: missing file\t%s\n\n" "${USERS_FILE}" >> /dev/stderr
help_users_file >> /dev/stderr
exit 2
fi
if [ -f "${DOCS_FILE}" ]; then
cat "${DOCS_FILE}" > dd-stress-test.docs.csv
else
cat > dd-stress-test.docs.csv <<-EOF
/,Readme.md
/,template.docx
/,template_1.docx
EOF
fi
JMETER="${JMETER:-${JMETER_DEFAULT}}"
# Ensure JMeter is available / bootstrap it
if [ ! -f "${JMETER}" ]; then
echo "INFO: Could not find JMeter, attempting to download it" >> /dev/stderr
curl -L 'https://dlcdn.apache.org//jmeter/binaries/apache-jmeter-5.5.tgz' | tar -xz
JMETER="${JMETER_DEFAULT}"
fi
if [ ! -f "${JMETER}" ]; then
printf "ERROR: missing JMeter\t%s\n\n" "${JMETER}" >> /dev/stderr
fi
# Ensure JMeter plugins are available / bootstrap them
JMETER_PLUGINS="$(dirname "${JMETER}")/../lib/ext/jmeter-plugins-manager-1.8.jar"
if [ ! -f "${JMETER_PLUGINS}" ]; then
echo "INFO: Could not find JMeter plugins, attempting to download them" >> /dev/stderr
curl -L 'https://jmeter-plugins.org/get/' > "${JMETER_PLUGINS}"
fi
if [ ! -f "${JMETER_PLUGINS}" ]; then
printf "ERROR: missing JMeter plugins\t%s\n\n" "${JMETER_PLUGINS}" >> /dev/stderr
fi
if [ ! -f "${JMETER}" ] || [ ! -f "${JMETER_PLUGINS}" ]; then
help_jmeter >> /dev/stderr
exit 3
fi
# Clean up out dir
rm -rf "${out_dir}"
mkdir -p "${out_dir}"
# Adapt template
sed -E \
-e "s%([^>]*)>(.*)<\!-- TC.*$%\\1>${tc}</stringProp> <\!-- TC -->%" \
-e "s%([^>]*)>(.*)<\!-- DURATION.*$%\\1>${duration}</stringProp> <\!-- DURATION -->%" \
-e "s/DD_DOMAIN/${DD_DOMAIN}/g" \
dd-stress-test.tpl.jmx > dd-stress-test.jmx
# Call Selenium test process in parallel
if [ "${USE_SELENIUM}" = "YES" ]; then
printf "\n\nRunning parallel Selenium-based tests:\t%s\tover %s seconds\n\n" "${DD_DOMAIN}" "${duration}"
python3 dd-test-selenium.py test --duration "${duration}" --out-dir "${out_dir}/selenium" "${DD_DOMAIN}" 2>&1 > "${out_dir}/selenium.log" &
fi
# Execute test
printf "\n\nAbout to test:\t%s\twith %s 'users' over %s seconds\n\n" \
"${DD_DOMAIN}" "${tc}" "${duration}"
env HEAP="-Xms2g -Xmx2g -XX:MaxMetaspaceSize=2g" "${JMETER}" -n -t dd-stress-test.jmx -l "${out_dir}/results" -e -o "${out_dir}/html"
mv jmeter.log "${out_dir}/log"
# Notify results
printf "\n\nYou can find the results at:\t%s\n\n" "${out_dir}"

View File

@ -0,0 +1,20 @@
async-generator==1.10
attrs==22.2.0
certifi==2022.12.7
exceptiongroup==1.1.0
h11==0.14.0
idna==3.4
outcome==1.2.0
pip==22.3.1
PySocks==1.7.1
PyYAML==6.0
selenium==4.8.2
setuptools==67.0.0
sniffio==1.3.0
sortedcontainers==2.4.0
sqlite3==0.0.0
trio==0.22.0
trio-websocket==0.9.2
urllib3==1.26.14
wheel==0.38.4
wsproto==1.2.0

26
stress-tests/vm-test.sh Normal file
View File

@ -0,0 +1,26 @@
#!/bin/sh -eu
LOG_FILE="${LOG_FILE:-vm-test.log}"
SYSBENCH="$(command -v sysbench2)"
# Save stderr as well
exec 2>&1
echo "$(date)" | tee "${LOG_FILE}"
if [ -z "${SYSBENCH}" ]; then
echo "Skipping: sysbench tests (try: apt install sysbench)" | \
tee -a "${LOG_FILE}"
else
printf "\n\nfileio\n\n" | tee -a "${LOG_FILE}"
"${SYSBENCH}" fileio prepare --file-test-mode=rndrw --threads=4 --time=60 | tee -a "${LOG_FILE}"
"${SYSBENCH}" fileio run --file-test-mode=rndrw --threads=4 --time=60 | tee -a "${LOG_FILE}"
printf "\n\ncpu\n\n" | tee -a "${LOG_FILE}"
"${SYSBENCH}" cpu run --threads=4 --time=60 | tee -a "${LOG_FILE}"
printf "\n\nmemory\n\n" | tee -a "${LOG_FILE}"
"${SYSBENCH}" memory run --threads=4 --time=60 | tee -a "${LOG_FILE}"
fi
# Perform basic OpenSSL tests too
printf "\n\nCPU OpenSSL\n\n" | tee -a "${LOG_FILE}"
openssl speed --seconds 60 sha256 | tee -a "${LOG_FILE}"