├── .github └── settings.yml ├── .gitignore ├── .gitmodules ├── .mvn └── wrapper │ ├── MavenWrapperDownloader.java │ └── maven-wrapper.properties ├── .vscode └── settings.json ├── DEVELOPER_README.md ├── Dockerfile ├── LICENSE ├── README.md ├── architecture.png ├── generator.iml ├── mvnw ├── mvnw.cmd ├── pom.xml ├── postman ├── AcaPy_Load_Generator.postman_collection.json ├── AcaPy_Load_Generator.postman_environment.json └── Readme.md ├── scripts ├── download-reports.sh └── install-vm.sh ├── setup ├── .env.example ├── agents │ ├── docker-compose-agents-debugging.yml │ ├── docker-compose-agents.yml │ ├── docker-compose-issuer-verifier-walletdb.yml │ ├── nginx-issuer-verifier-1.conf │ ├── nginx-issuer-verifier-10.conf │ ├── nginx-issuer-verifier-15.conf │ ├── nginx-issuer-verifier-20.conf │ └── nginx-issuer-verifier-5.conf ├── dashboard │ ├── docker-compose-dashboards.yml │ ├── grafana │ │ ├── dashboards │ │ │ ├── acapy-logs.json │ │ │ ├── docker-container-metrics.json │ │ │ ├── host-metrics.json │ │ │ ├── issuer-verifier-postgres-metrics.json │ │ │ ├── jvm-micrometer_rev9.json │ │ │ ├── test-results-old.json │ │ │ └── test-results.json │ │ ├── grafana-provisioning │ │ │ ├── dashboards │ │ │ │ └── default.yaml │ │ │ └── datasources │ │ │ │ ├── loki.yaml │ │ │ │ └── prometheus.yaml │ │ └── grafana.ini │ ├── loki │ │ └── local-config.yaml │ └── prometheus │ │ └── prometheus.yml ├── load-generator │ ├── docker-compose-load-generator.yml │ └── load-generator.env └── manage.sh └── src ├── main ├── kotlin │ └── com │ │ └── bka │ │ └── ssi │ │ └── generator │ │ ├── GeneratorApplication.kt │ │ ├── agents │ │ └── acapy │ │ │ ├── AcaPyAriesClient.kt │ │ │ ├── AcaPyOkHttpInterceptor.kt │ │ │ ├── AcaPyPostgresWallet.kt │ │ │ └── AcaPyPublisher.kt │ │ ├── api │ │ ├── AcaPyWebhookController.kt │ │ └── InfoController.kt │ │ ├── application │ │ ├── logger │ │ │ ├── AriesClientLogger.kt │ │ │ ├── AriesEventLogger.kt │ │ │ ├── ErrorLogger.kt │ │ │ ├── HttpRequestLogger.kt │ │ │ └── WalletLogger.kt │ │ ├── testflows │ │ │ ├── ConnectionFlow.kt │ │ │ ├── CredentialIssuanceFlow.kt │ │ │ ├── EmptyFlow.kt │ │ │ ├── FullFlow.kt │ │ │ ├── IssuerFlow.kt │ │ │ ├── ProofRequestFlow.kt │ │ │ └── TestFlow.kt │ │ └── testrunners │ │ │ ├── ConstantLoadTestRunner.kt │ │ │ ├── IncreasingLoadTestRunner.kt │ │ │ ├── MaxParallelIterationsTestRunner.kt │ │ │ └── TestRunner.kt │ │ ├── config │ │ ├── AcaPyConfig.kt │ │ └── SwaggerConfig.kt │ │ └── domain │ │ ├── objects │ │ ├── ConnectionInvitationDo.kt │ │ ├── ConnectionRecordDo.kt │ │ ├── CredentialDefinitionDo.kt │ │ ├── CredentialDo.kt │ │ ├── CredentialExchangeRecordDo.kt │ │ ├── CredentialRevocationRegistryRecordDo.kt │ │ ├── ProofExchangeRecordDo.kt │ │ ├── ProofRequestDo.kt │ │ └── SchemaDo.kt │ │ └── services │ │ ├── IAriesClient.kt │ │ ├── IAriesObserver.kt │ │ ├── IHttpRequestObserver.kt │ │ └── IWallet.kt └── resources │ ├── application-docker.yml │ ├── application.yml │ ├── logback-spring-docker.xml │ └── logback-spring.xml └── test └── kotlin └── com └── bka └── ssi └── generator └── GeneratorApplicationTests.kt /.github/settings.yml: -------------------------------------------------------------------------------- 1 | # 2 | # SPDX-License-Identifier: Apache-2.0 3 | # 4 | 5 | repository: 6 | name: aries-cloudagent-loadgenerator 7 | description: aries-cloudagent-loadgenerator 8 | homepage: https://wiki.hyperledger.org/display/aries 9 | default_branch: main 10 | has_downloads: false 11 | has_issues: true 12 | has_projects: true 13 | has_wiki: false 14 | archived: false 15 | private: false 16 | allow_squash_merge: true 17 | allow_merge_commit: false 18 | allow_rebase_merge: true 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/kotlin 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=kotlin 3 | 4 | ### Kotlin ### 5 | # Compiled class file 6 | *.class 7 | 8 | # Log file 9 | *.log 10 | 11 | # BlueJ files 12 | *.ctxt 13 | 14 | # Mobile Tools for Java (J2ME) 15 | .mtj.tmp/ 16 | 17 | # Package Files # 18 | *.jar 19 | *.war 20 | *.nar 21 | *.ear 22 | *.zip 23 | *.tar.gz 24 | *.rar 25 | 26 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 27 | hs_err_pid* 28 | 29 | # End of https://www.toptal.com/developers/gitignore/api/kotlin 30 | 31 | 32 | # Created by https://www.toptal.com/developers/gitignore/api/intellij 33 | # Edit at https://www.toptal.com/developers/gitignore?templates=intellij 34 | 35 | ### Intellij ### 36 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 37 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 38 | 39 | # User-specific stuff 40 | .idea 41 | 42 | # CMake 43 | cmake-build-*/ 44 | 45 | # File-based project format 46 | *.iws 47 | 48 | # IntelliJ 49 | out/ 50 | 51 | # mpeltonen/sbt-idea plugin 52 | .idea_modules/ 53 | 54 | ### Maven ### 55 | target/ 56 | pom.xml.tag 57 | pom.xml.releaseBackup 58 | pom.xml.versionsBackup 59 | pom.xml.next 60 | release.properties 61 | dependency-reduced-pom.xml 62 | buildNumber.properties 63 | .mvn/timing.properties 64 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar 65 | .mvn/wrapper/maven-wrapper.jar 66 | 67 | ### Maven Patch ### 68 | # Eclipse m2e generated files 69 | # Eclipse Core 70 | .project 71 | # JDT-specific (Eclipse Java Development Tools) 72 | .classpath 73 | 74 | ### Configuration Files ### 75 | .env 76 | 77 | # End of https://www.toptal.com/developers/gitignore/api/maven 78 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "setup/von-network"] 2 | path = setup/von-network 3 | url = https://github.com/bcgov/von-network.git 4 | [submodule "setup/agents/indy-tails-server"] 5 | path = setup/agents/indy-tails-server 6 | url = https://github.com/bcgov/indy-tails-server 7 | [submodule "setup/agents/patroni"] 8 | path = setup/agents/patroni 9 | url = https://github.com/zalando/patroni 10 | [submodule "setup/agents/acapy"] 11 | path = setup/agents/acapy 12 | url = https://github.com/hyperledger/aries-cloudagent-python 13 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | import java.net.*; 17 | import java.io.*; 18 | import java.nio.channels.*; 19 | import java.util.Properties; 20 | 21 | public class MavenWrapperDownloader { 22 | 23 | private static final String WRAPPER_VERSION = "0.5.6"; 24 | /** 25 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 26 | */ 27 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 28 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 29 | 30 | /** 31 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 32 | * use instead of the default one. 33 | */ 34 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 35 | ".mvn/wrapper/maven-wrapper.properties"; 36 | 37 | /** 38 | * Path where the maven-wrapper.jar will be saved to. 39 | */ 40 | private static final String MAVEN_WRAPPER_JAR_PATH = 41 | ".mvn/wrapper/maven-wrapper.jar"; 42 | 43 | /** 44 | * Name of the property which should be used to override the default download url for the wrapper. 45 | */ 46 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 47 | 48 | public static void main(String args[]) { 49 | System.out.println("- Downloader started"); 50 | File baseDirectory = new File(args[0]); 51 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 52 | 53 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 54 | // wrapperUrl parameter. 55 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 56 | String url = DEFAULT_DOWNLOAD_URL; 57 | if(mavenWrapperPropertyFile.exists()) { 58 | FileInputStream mavenWrapperPropertyFileInputStream = null; 59 | try { 60 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 61 | Properties mavenWrapperProperties = new Properties(); 62 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 63 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 64 | } catch (IOException e) { 65 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 66 | } finally { 67 | try { 68 | if(mavenWrapperPropertyFileInputStream != null) { 69 | mavenWrapperPropertyFileInputStream.close(); 70 | } 71 | } catch (IOException e) { 72 | // Ignore ... 73 | } 74 | } 75 | } 76 | System.out.println("- Downloading from: " + url); 77 | 78 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 79 | if(!outputFile.getParentFile().exists()) { 80 | if(!outputFile.getParentFile().mkdirs()) { 81 | System.out.println( 82 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 83 | } 84 | } 85 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 86 | try { 87 | downloadFileFromURL(url, outputFile); 88 | System.out.println("Done"); 89 | System.exit(0); 90 | } catch (Throwable e) { 91 | System.out.println("- Error downloading"); 92 | e.printStackTrace(); 93 | System.exit(1); 94 | } 95 | } 96 | 97 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 98 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 99 | String username = System.getenv("MVNW_USERNAME"); 100 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 101 | Authenticator.setDefault(new Authenticator() { 102 | @Override 103 | protected PasswordAuthentication getPasswordAuthentication() { 104 | return new PasswordAuthentication(username, password); 105 | } 106 | }); 107 | } 108 | URL website = new URL(urlString); 109 | ReadableByteChannel rbc; 110 | rbc = Channels.newChannel(website.openStream()); 111 | FileOutputStream fos = new FileOutputStream(destination); 112 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 113 | fos.close(); 114 | rbc.close(); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "java.configuration.updateBuildConfiguration": "automatic" 3 | } 4 | -------------------------------------------------------------------------------- /DEVELOPER_README.md: -------------------------------------------------------------------------------- 1 | # Developer Readme for the "Aries Cloud Agent Load Generator" 2 | 3 | This readme outlines how the load generator can be started from the IDE during development. It is written for developers 4 | who want to extend/adopt the code of the load generator. 5 | 6 | ## Technology 7 | 8 | The Load Generator is a Spring Boot application written in Kotlin. 9 | 10 | ## Startup a Test Environment 11 | 12 | To start the environment without the Load Generator container, run: 13 | 14 | ``` 15 | ./setup/manage.sh debug 16 | ``` 17 | 18 | ## Configuration via application.yml 19 | 20 | If the load generator is started via the IDE it is configured 21 | via [application.yml](./src/main/resources/application.yml). 22 | 23 | Two things need to be chosen by setting `active: true`: 24 | 25 | 1. **Test Runner** (each Test Runner generates the load differently) 26 | 2. **Test Flow** (each Test Flow corresponds to a different test scenario) 27 | 28 | ``` 29 | test-runners: 30 | max-parallel-iterations-runner: 31 | active: false 32 | number-of-total-iterations: 100 33 | number-of-parallel-iterations: 5 34 | constant-load-runner: 35 | active: true 36 | number-of-total-iterations: 100 37 | number-of-iterations-per-minute: 30 38 | thread-pool-size: 4 39 | 40 | test-flows: 41 | full-flow: 42 | active: true 43 | use-revocable-credentials: true 44 | revocation-registry-size: 500 45 | check-non-revoked: true 46 | issuer-flow: 47 | active: false 48 | use-revocable-credentials: true 49 | revocation-registry-size: 500 50 | connection-request-flow: 51 | active: false 52 | credential-issuance-flow: 53 | active: false 54 | use-revocable-credentials: true 55 | revocation-registry-size: 500 56 | proof-request-flow: 57 | active: false 58 | check-non-revoked: true 59 | ``` 60 | 61 | Only one Test Runner and one Test Flow should be set to `active: true` at a time. The Test Flow will **automatically be 62 | executed** by the Test Runner once the application is started. 63 | 64 | ## Grafana Configuration 65 | 66 | The data sources, as well as dashboards, are provisioned automatically when running the Grafana container. 67 | 68 | Data sources are configured manually 69 | in [./setup/grafana/grafana-provisioning/datasources/](./setup/grafana/grafana-provisioning/datasources/) using YAML 70 | files. Dashboards are configured in [./setup/grafana/dashboards](./setup/grafana/dashboards) using JSON files. 71 | Dashboards can be created via the Grafana Web UI and exported as JSON afterwards. 72 | 73 | ## E2E Test Cases 74 | 75 | - Start whole system with multitenancy disabled 76 | - Start whole system with multitenancy enabled 77 | - Down the whole system using `manage.sh down` 78 | - Restart the whole system using `manage.sh restart` 79 | - Start whole system with postgres cluster 80 | - Start whole system without the load generator using `manage.sh debug` as well as `SYSTEM_LOAD_GENERATOR=false` and 81 | start the load generator using `./mvnw spring-boot:run` 82 | - Start whole system with the load generator using `manage.sh debug` as well as `SYSTEM_LOAD_GENERATOR=true` 83 | - Run it on MacOS 84 | - Run it on Linux 85 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM maven:3.8.4-openjdk-17-slim AS MAVEN_BUILD 2 | 3 | WORKDIR /build/ 4 | 5 | COPY pom.xml mvnw mvnw.cmd /build/ 6 | 7 | RUN mvn -N io.takari:maven:wrapper 8 | 9 | COPY src /build/src/ 10 | 11 | RUN mvn package -Dmaven.test.skip=true 12 | 13 | FROM registry.access.redhat.com/ubi8/openjdk-11 14 | 15 | WORKDIR /app 16 | 17 | COPY --from=MAVEN_BUILD /build/target/generator-0.0.1-SNAPSHOT.jar /app/ 18 | 19 | EXPOSE 8080 20 | 21 | ENTRYPOINT ["java", "-Dspring.profiles.active=docker","-jar", "generator-0.0.1-SNAPSHOT.jar"] 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Aries Cloud Agent Load Generator 2 | 3 | Aries Cloud Agent Load Generator has been replaced by [Aries Akrida](https://github.com/hyperledger/aries-akrida). 4 | -------------------------------------------------------------------------------- /architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperledger-aries/aries-cloudagent-loadgenerator/ff8d19e62372ea7a8d0ca1f9b03492f1a435a192/architecture.png -------------------------------------------------------------------------------- /generator.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 32 | 37 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | fi 118 | 119 | if [ -z "$JAVA_HOME" ]; then 120 | javaExecutable="`which javac`" 121 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 122 | # readlink(1) is not available as standard on Solaris 10. 123 | readLink=`which readlink` 124 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 125 | if $darwin ; then 126 | javaHome="`dirname \"$javaExecutable\"`" 127 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 128 | else 129 | javaExecutable="`readlink -f \"$javaExecutable\"`" 130 | fi 131 | javaHome="`dirname \"$javaExecutable\"`" 132 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 133 | JAVA_HOME="$javaHome" 134 | export JAVA_HOME 135 | fi 136 | fi 137 | fi 138 | 139 | if [ -z "$JAVACMD" ] ; then 140 | if [ -n "$JAVA_HOME" ] ; then 141 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 142 | # IBM's JDK on AIX uses strange locations for the executables 143 | JAVACMD="$JAVA_HOME/jre/sh/java" 144 | else 145 | JAVACMD="$JAVA_HOME/bin/java" 146 | fi 147 | else 148 | JAVACMD="`which java`" 149 | fi 150 | fi 151 | 152 | if [ ! -x "$JAVACMD" ] ; then 153 | echo "Error: JAVA_HOME is not defined correctly." >&2 154 | echo " We cannot execute $JAVACMD" >&2 155 | exit 1 156 | fi 157 | 158 | if [ -z "$JAVA_HOME" ] ; then 159 | echo "Warning: JAVA_HOME environment variable is not set." 160 | fi 161 | 162 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 163 | 164 | # traverses directory structure from process work directory to filesystem root 165 | # first directory with .mvn subdirectory is considered project base directory 166 | find_maven_basedir() { 167 | 168 | if [ -z "$1" ] 169 | then 170 | echo "Path not specified to find_maven_basedir" 171 | return 1 172 | fi 173 | 174 | basedir="$1" 175 | wdir="$1" 176 | while [ "$wdir" != '/' ] ; do 177 | if [ -d "$wdir"/.mvn ] ; then 178 | basedir=$wdir 179 | break 180 | fi 181 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 182 | if [ -d "${wdir}" ]; then 183 | wdir=`cd "$wdir/.."; pwd` 184 | fi 185 | # end of workaround 186 | done 187 | echo "${basedir}" 188 | } 189 | 190 | # concatenates all lines of a file 191 | concat_lines() { 192 | if [ -f "$1" ]; then 193 | echo "$(tr -s '\n' ' ' < "$1")" 194 | fi 195 | } 196 | 197 | BASE_DIR=`find_maven_basedir "$(pwd)"` 198 | if [ -z "$BASE_DIR" ]; then 199 | exit 1; 200 | fi 201 | 202 | ########################################################################################## 203 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 204 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 205 | ########################################################################################## 206 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 207 | if [ "$MVNW_VERBOSE" = true ]; then 208 | echo "Found .mvn/wrapper/maven-wrapper.jar" 209 | fi 210 | else 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 213 | fi 214 | if [ -n "$MVNW_REPOURL" ]; then 215 | jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 216 | else 217 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 218 | fi 219 | while IFS="=" read key value; do 220 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 221 | esac 222 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 223 | if [ "$MVNW_VERBOSE" = true ]; then 224 | echo "Downloading from: $jarUrl" 225 | fi 226 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 227 | if $cygwin; then 228 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 229 | fi 230 | 231 | if command -v wget > /dev/null; then 232 | if [ "$MVNW_VERBOSE" = true ]; then 233 | echo "Found wget ... using wget" 234 | fi 235 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 236 | wget "$jarUrl" -O "$wrapperJarPath" 237 | else 238 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" 239 | fi 240 | elif command -v curl > /dev/null; then 241 | if [ "$MVNW_VERBOSE" = true ]; then 242 | echo "Found curl ... using curl" 243 | fi 244 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 245 | curl -o "$wrapperJarPath" "$jarUrl" -f 246 | else 247 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 248 | fi 249 | 250 | else 251 | if [ "$MVNW_VERBOSE" = true ]; then 252 | echo "Falling back to using Java to download" 253 | fi 254 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 255 | # For Cygwin, switch paths to Windows format before running javac 256 | if $cygwin; then 257 | javaClass=`cygpath --path --windows "$javaClass"` 258 | fi 259 | if [ -e "$javaClass" ]; then 260 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 261 | if [ "$MVNW_VERBOSE" = true ]; then 262 | echo " - Compiling MavenWrapperDownloader.java ..." 263 | fi 264 | # Compiling the Java class 265 | ("$JAVA_HOME/bin/javac" "$javaClass") 266 | fi 267 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 268 | # Running the downloader 269 | if [ "$MVNW_VERBOSE" = true ]; then 270 | echo " - Running MavenWrapperDownloader.java ..." 271 | fi 272 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 273 | fi 274 | fi 275 | fi 276 | fi 277 | ########################################################################################## 278 | # End of extension 279 | ########################################################################################## 280 | 281 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 282 | if [ "$MVNW_VERBOSE" = true ]; then 283 | echo $MAVEN_PROJECTBASEDIR 284 | fi 285 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 286 | 287 | # For Cygwin, switch paths to Windows format before running java 288 | if $cygwin; then 289 | [ -n "$M2_HOME" ] && 290 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 291 | [ -n "$JAVA_HOME" ] && 292 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 293 | [ -n "$CLASSPATH" ] && 294 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 295 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 296 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 297 | fi 298 | 299 | # Provide a "standardized" way to retrieve the CLI args that will 300 | # work with both Windows and non-Windows executions. 301 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 302 | export MAVEN_CMD_LINE_ARGS 303 | 304 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 305 | 306 | exec "$JAVACMD" \ 307 | $MAVEN_OPTS \ 308 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 309 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 310 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 311 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 124 | 125 | FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 162 | if ERRORLEVEL 1 goto error 163 | goto end 164 | 165 | :error 166 | set ERROR_CODE=1 167 | 168 | :end 169 | @endlocal & set ERROR_CODE=%ERROR_CODE% 170 | 171 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 172 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 173 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 174 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 175 | :skipRcPost 176 | 177 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 178 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 179 | 180 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 181 | 182 | exit /B %ERROR_CODE% 183 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.6.2 9 | 10 | 11 | com.bka.ssi 12 | generator 13 | 0.0.1-SNAPSHOT 14 | generator 15 | Load Generator for Aca-Py 16 | 17 | 11 18 | 1.6.10 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-web 24 | 25 | 26 | com.fasterxml.jackson.module 27 | jackson-module-kotlin 28 | 29 | 30 | org.jetbrains.kotlin 31 | kotlin-reflect 32 | 33 | 34 | org.jetbrains.kotlin 35 | kotlin-stdlib-jdk8 36 | 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-devtools 41 | runtime 42 | true 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-test 47 | test 48 | 49 | 50 | io.springfox 51 | springfox-swagger2 52 | 2.9.2 53 | 54 | 55 | io.springfox 56 | springfox-swagger-ui 57 | 2.9.2 58 | 59 | 60 | network.idu.acapy 61 | aries-client-python 62 | 0.7.19 63 | 64 | 65 | 66 | com.squareup.okhttp3 67 | okhttp 68 | 4.9.0 69 | 70 | 71 | org.springframework.boot 72 | spring-boot-starter-jdbc 73 | 74 | 75 | org.postgresql 76 | postgresql 77 | runtime 78 | 79 | 80 | org.slf4j 81 | slf4j-api 82 | 1.7.33 83 | 84 | 85 | ch.qos.logback 86 | logback-classic 87 | 1.3.12 88 | 89 | 90 | com.github.loki4j 91 | loki-logback-appender 92 | 1.3.1 93 | 94 | 95 | org.springframework.boot 96 | spring-boot-starter-actuator 97 | 98 | 99 | io.micrometer 100 | micrometer-registry-prometheus 101 | runtime 102 | 103 | 104 | 105 | 106 | ${project.basedir}/src/main/kotlin 107 | ${project.basedir}/src/test/kotlin 108 | 109 | 110 | org.springframework.boot 111 | spring-boot-maven-plugin 112 | 113 | 114 | org.jetbrains.kotlin 115 | kotlin-maven-plugin 116 | 117 | 118 | -Xjsr305=strict 119 | 120 | 121 | spring 122 | 123 | 124 | 125 | 126 | org.jetbrains.kotlin 127 | kotlin-maven-allopen 128 | ${kotlin.version} 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /postman/AcaPy_Load_Generator.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "c0d978a5-8eb6-4c70-a093-3562956e286d", 4 | "name": "AcaPy Load Generator", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "Test Local Setup (Build Connection, Issue Credential, Requests Proof)", 10 | "item": [ 11 | { 12 | "name": "Register Schema (Issuer-Verifier)", 13 | "event": [ 14 | { 15 | "listen": "test", 16 | "script": { 17 | "exec": [ 18 | "pm.test(\"Status code is 200\", function () {", 19 | " pm.response.to.have.status(200);", 20 | "});", 21 | "", 22 | "var jsonData = JSON.parse(responseBody);", 23 | "pm.environment.set(\"name-schemaId\", jsonData.schema_id);" 24 | ], 25 | "type": "text/javascript" 26 | } 27 | } 28 | ], 29 | "request": { 30 | "method": "POST", 31 | "header": [ 32 | { 33 | "key": "Content-Type", 34 | "value": "application/json" 35 | } 36 | ], 37 | "body": { 38 | "mode": "raw", 39 | "raw": "{\n \"attributes\": [\n \"first name\",\n \"last name\"\n ],\n \"schema_name\": \"name\",\n \"schema_version\": \"1.{{$timestamp}}\"\n}" 40 | }, 41 | "url": { 42 | "raw": "{{baseUrlIssuerVerifier}}/schemas", 43 | "host": [ 44 | "{{baseUrlIssuerVerifier}}" 45 | ], 46 | "path": [ 47 | "schemas" 48 | ] 49 | } 50 | }, 51 | "response": [] 52 | }, 53 | { 54 | "name": "Register Credential Definition (Issuer-Verifier)", 55 | "event": [ 56 | { 57 | "listen": "test", 58 | "script": { 59 | "exec": [ 60 | "pm.test(\"Status code is 200\", function () {", 61 | " pm.response.to.have.status(200);", 62 | "});", 63 | "", 64 | "var jsonData = JSON.parse(responseBody);", 65 | "pm.environment.set(\"name-credentialDefinitionId\", jsonData.credential_definition_id);" 66 | ], 67 | "type": "text/javascript" 68 | } 69 | } 70 | ], 71 | "request": { 72 | "method": "POST", 73 | "header": [ 74 | { 75 | "key": "Content-Type", 76 | "value": "application/json" 77 | } 78 | ], 79 | "body": { 80 | "mode": "raw", 81 | "raw": "{\n \"revocation_registry_size\": 1000,\n \"schema_id\": \"{{name-schemaId}}\",\n \"support_revocation\": true,\n \"tag\": \"{{$timestamp}}\"\n}" 82 | }, 83 | "url": { 84 | "raw": "{{baseUrlIssuerVerifier}}/credential-definitions", 85 | "host": [ 86 | "{{baseUrlIssuerVerifier}}" 87 | ], 88 | "path": [ 89 | "credential-definitions" 90 | ] 91 | } 92 | }, 93 | "response": [] 94 | }, 95 | { 96 | "name": "Create connection invitation (Issuer-Verifier)", 97 | "event": [ 98 | { 99 | "listen": "test", 100 | "script": { 101 | "exec": [ 102 | "pm.test(\"Status code is 200\", function () {", 103 | " pm.response.to.have.status(200);", 104 | "});", 105 | "", 106 | "var jsonData = JSON.parse(responseBody);", 107 | "pm.environment.set(\"connection-invitation\", JSON.stringify(jsonData.invitation));", 108 | "pm.environment.set(\"connectionId\", jsonData.connection_id);" 109 | ], 110 | "type": "text/javascript" 111 | } 112 | } 113 | ], 114 | "request": { 115 | "method": "POST", 116 | "header": [ 117 | { 118 | "key": "Content-Type", 119 | "value": "application/json" 120 | } 121 | ], 122 | "body": { 123 | "mode": "raw", 124 | "raw": "" 125 | }, 126 | "url": { 127 | "raw": "{{baseUrlIssuerVerifier}}/connections/create-invitation?alias=holder-acapy&auto_accept=true&multi_use=false&public=false", 128 | "host": [ 129 | "{{baseUrlIssuerVerifier}}" 130 | ], 131 | "path": [ 132 | "connections", 133 | "create-invitation" 134 | ], 135 | "query": [ 136 | { 137 | "key": "alias", 138 | "value": "holder-acapy", 139 | "description": "Alias" 140 | }, 141 | { 142 | "key": "auto_accept", 143 | "value": "true", 144 | "description": "Auto-accept connection (defaults to configuration)" 145 | }, 146 | { 147 | "key": "multi_use", 148 | "value": "false", 149 | "description": "Create invitation for multiple use (default false)" 150 | }, 151 | { 152 | "key": "public", 153 | "value": "false", 154 | "description": "Create invitation from public DID (default false)" 155 | } 156 | ] 157 | } 158 | }, 159 | "response": [] 160 | }, 161 | { 162 | "name": "Receive connection invitation (Holder)", 163 | "event": [ 164 | { 165 | "listen": "test", 166 | "script": { 167 | "exec": [ 168 | "pm.test(\"Status code is 200\", function () {", 169 | " pm.response.to.have.status(200);", 170 | "});", 171 | "", 172 | "setTimeout(function(){}, 5000);" 173 | ], 174 | "type": "text/javascript" 175 | } 176 | } 177 | ], 178 | "request": { 179 | "method": "POST", 180 | "header": [ 181 | { 182 | "key": "Content-Type", 183 | "value": "application/json" 184 | } 185 | ], 186 | "body": { 187 | "mode": "raw", 188 | "raw": "{{connection-invitation}}" 189 | }, 190 | "url": { 191 | "raw": "{{baseUrlHolder}}/connections/receive-invitation?alias=issuer-verifier-acapy&auto_accept=true", 192 | "host": [ 193 | "{{baseUrlHolder}}" 194 | ], 195 | "path": [ 196 | "connections", 197 | "receive-invitation" 198 | ], 199 | "query": [ 200 | { 201 | "key": "alias", 202 | "value": "issuer-verifier-acapy", 203 | "description": "Alias" 204 | }, 205 | { 206 | "key": "auto_accept", 207 | "value": "true", 208 | "description": "Auto-accept connection (defaults to configuration)" 209 | }, 210 | { 211 | "key": "mediation_id", 212 | "value": "3fa85f64-5717-4562-b3fc-2c963f66afa6", 213 | "description": "Identifier for active mediation record to be used", 214 | "disabled": true 215 | } 216 | ] 217 | } 218 | }, 219 | "response": [] 220 | }, 221 | { 222 | "name": "Issue Credential to Holder (Issuer-Verifier)", 223 | "event": [ 224 | { 225 | "listen": "test", 226 | "script": { 227 | "exec": [ 228 | "pm.test(\"Status code is 200\", function () {", 229 | " pm.response.to.have.status(200);", 230 | "});", 231 | "", 232 | "setTimeout(function(){}, 5000);" 233 | ], 234 | "type": "text/javascript" 235 | } 236 | } 237 | ], 238 | "request": { 239 | "method": "POST", 240 | "header": [ 241 | { 242 | "key": "Content-Type", 243 | "value": "application/json" 244 | } 245 | ], 246 | "body": { 247 | "mode": "raw", 248 | "raw": "{\n \"connection_id\": \"{{connectionId}}\",\n \"comment\": \"name credential offer\",\n \"cred_def_id\": \"{{name-credentialDefinitionId}}\",\n \"credential_proposal\": {\n \"attributes\": [\n {\n \"name\": \"first name\",\n \"value\": \"Holder\",\n \"mime-type\": \"text/plain\"\n },\n {\n \"name\": \"last name\",\n \"value\": \"Mustermann\",\n \"mime-type\": \"text/plain\"\n }\n ],\n \"@type\": \"issue-credential/1.0/credential-preview\"\n }\n}" 249 | }, 250 | "url": { 251 | "raw": "{{baseUrlIssuerVerifier}}/issue-credential/send", 252 | "host": [ 253 | "{{baseUrlIssuerVerifier}}" 254 | ], 255 | "path": [ 256 | "issue-credential", 257 | "send" 258 | ] 259 | } 260 | }, 261 | "response": [] 262 | }, 263 | { 264 | "name": "Send Proof Request to Holder (Issuer-Verifier)", 265 | "event": [ 266 | { 267 | "listen": "test", 268 | "script": { 269 | "exec": [ 270 | "pm.test(\"Status code is 200\", function () {", 271 | " pm.response.to.have.status(200);", 272 | "});", 273 | "", 274 | "var jsonData = JSON.parse(responseBody);", 275 | "pm.environment.set(\"presentationExchangeId\", jsonData.presentation_exchange_id);", 276 | "", 277 | "setTimeout(function(){}, 5000);" 278 | ], 279 | "type": "text/javascript" 280 | } 281 | } 282 | ], 283 | "request": { 284 | "method": "POST", 285 | "header": [ 286 | { 287 | "key": "Content-Type", 288 | "value": "application/json" 289 | } 290 | ], 291 | "body": { 292 | "mode": "raw", 293 | "raw": "{\n \"comment\": \"name credential proof request\",\n \"connection_id\": \"{{connectionId}}\",\n \"proof_request\": {\n \"name\": \"Proof request\",\n \"non_revoked\": {\n \"from\": {{$timestamp}},\n \"to\": {{$timestamp}}\n },\n \"requested_attributes\": {\n \"nameCredential\": {\n \"names\": [\n \"first name\",\n \"last name\"\n ],\n \"restrictions\": [\n {\n \"cred_def_id\": \"{{name-credentialDefinitionId}}\"\n }\n ]\n }\n },\n \"requested_predicates\": {},\n \"version\": \"1.0\"\n },\n \"trace\": false\n}" 294 | }, 295 | "url": { 296 | "raw": "{{baseUrlIssuerVerifier}}/present-proof/send-request", 297 | "host": [ 298 | "{{baseUrlIssuerVerifier}}" 299 | ], 300 | "path": [ 301 | "present-proof", 302 | "send-request" 303 | ] 304 | } 305 | }, 306 | "response": [] 307 | }, 308 | { 309 | "name": "Verify received proof presentation (Issuer-Verifier)", 310 | "event": [ 311 | { 312 | "listen": "test", 313 | "script": { 314 | "exec": [ 315 | "pm.test(\"Status code is 200\", function () {", 316 | " pm.response.to.have.status(200);", 317 | "});", 318 | "", 319 | "var jsonData = JSON.parse(responseBody);", 320 | "pm.test(\"Proof presentation is verified\", function () {", 321 | " pm.expect(jsonData.verified).to.eql(\"true\");", 322 | "});" 323 | ], 324 | "type": "text/javascript" 325 | } 326 | } 327 | ], 328 | "request": { 329 | "method": "GET", 330 | "header": [], 331 | "url": { 332 | "raw": "{{baseUrlIssuerVerifier}}/present-proof/records/{{presentationExchangeId}}", 333 | "host": [ 334 | "{{baseUrlIssuerVerifier}}" 335 | ], 336 | "path": [ 337 | "present-proof", 338 | "records", 339 | "{{presentationExchangeId}}" 340 | ] 341 | } 342 | }, 343 | "response": [] 344 | } 345 | ] 346 | } 347 | ] 348 | } -------------------------------------------------------------------------------- /postman/AcaPy_Load_Generator.postman_environment.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "11205d7f-a1e0-40f2-bc94-984802d9be8e", 3 | "name": "AcaPy_Load_Generator", 4 | "values": [ 5 | { 6 | "key": "baseUrlIssuerVerifier", 7 | "value": "http://localhost:10000", 8 | "enabled": true 9 | }, 10 | { 11 | "key": "baseUrlHolder", 12 | "value": "http://localhost:10010", 13 | "enabled": true 14 | }, 15 | { 16 | "key": "name-schemaId", 17 | "value": "", 18 | "enabled": true 19 | }, 20 | { 21 | "key": "name-credentialDefinitionId", 22 | "value": "", 23 | "enabled": true 24 | }, 25 | { 26 | "key": "connection-invitation", 27 | "value": "", 28 | "enabled": true 29 | }, 30 | { 31 | "key": "connectionId", 32 | "value": "", 33 | "enabled": true 34 | }, 35 | { 36 | "key": "presentationExchangeId", 37 | "value": "", 38 | "enabled": true 39 | } 40 | ], 41 | "_postman_variable_scope": "environment", 42 | "_postman_exported_at": "2021-12-17T13:14:32.503Z", 43 | "_postman_exported_using": "Postman/9.3.1" 44 | } 45 | -------------------------------------------------------------------------------- /postman/Readme.md: -------------------------------------------------------------------------------- 1 | # Postman Load Generator 2 | 3 | This folder offers a Postman collection and environment configuration. The 4 | collection includes a variety of requests to test the load test setup. 5 | 6 | ## Usage 7 | 1. install [Postman]() 8 | 1. import [AcaPy_Load_Generator.postman_collection.json](./AcaPy_Load_Generator.postman_collection.json) 9 | 1. import [AcaPy_Load_Generator.postman_environment.json](./AcaPy_Load_Generator.postman_environment.json) 10 | 1. choose the Postman environment `Acapy_Load_Generator` 11 | 1. select the Postman collection `Acapy Load Generator` and click on `Run` to 12 | automatically execute all requests 13 | 14 | ## Load Generator 15 | 16 | ### Test Local Setup 17 | This folder includes a handful of requests to test if the local load test 18 | setup works as expected and if all components are able to communicate with 19 | each other. For a variety of actions are run including: 20 | - creating a connection between the Issuer-Verifier and Holder agent 21 | - register a schema and credential definition (for a revocable credential) on 22 | the ledger 23 | - issue a credential 24 | - request a credential proof with a non-revocation check 25 | 26 | -------------------------------------------------------------------------------- /scripts/download-reports.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | FROM_TIMESTAMP=$1 4 | TO_TIMESTAMP=$2 5 | 6 | REPORT_FOLDER=./reports 7 | 8 | rm -rf $REPORT_FOLDER 9 | mkdir $REPORT_FOLDER 10 | 11 | MAX_DOWNLOAD_INTERVAL_IN_HOURS=6 12 | MAX_DOWNLOAD_INTERVAL_IN_MS=$(expr $MAX_DOWNLOAD_INTERVAL_IN_HOURS \* 1000 \* 60 \* 60) 13 | 14 | FROM_TIMESTAMPS=() 15 | TO_TIMESTAMPS=() 16 | FILE_POSTFIX=() 17 | 18 | FROM_TIMESTAMPS+=($FROM_TIMESTAMP) 19 | 20 | while [ $(expr $TO_TIMESTAMP - ${FROM_TIMESTAMPS[${#FROM_TIMESTAMPS[@]} - 1]}) -gt $MAX_DOWNLOAD_INTERVAL_IN_MS ] 21 | do 22 | TIMESTAMP=$(expr ${FROM_TIMESTAMPS[${#FROM_TIMESTAMPS[@]} - 1]} + $MAX_DOWNLOAD_INTERVAL_IN_MS) 23 | TO_TIMESTAMPS+=($TIMESTAMP) 24 | FROM_TIMESTAMPS+=($TIMESTAMP) 25 | FILE_POSTFIX+=("$(expr $(expr ${#FROM_TIMESTAMPS[@]} - 2) \* $MAX_DOWNLOAD_INTERVAL_IN_HOURS)-$(expr $(expr ${#FROM_TIMESTAMPS[@]} - 1) \* $MAX_DOWNLOAD_INTERVAL_IN_HOURS)") 26 | done 27 | 28 | TO_TIMESTAMPS+=($TO_TIMESTAMP) 29 | FILE_POSTFIX+=("$(expr $(expr ${#FROM_TIMESTAMPS[@]} - 1) \* $MAX_DOWNLOAD_INTERVAL_IN_HOURS)-END") 30 | 31 | echo Download split into ${#FILE_POSTFIX[@]} parts... 32 | 33 | for i in $(seq 0 $(expr ${#FILE_POSTFIX[@]} - 1)) 34 | do 35 | echo Downloading Part $(expr $i + 1): ${FILE_POSTFIX[$i]} hours FROM=${FROM_TIMESTAMPS[$i]} TO=${TO_TIMESTAMPS[$i]} 36 | 37 | echo ">> Exporting Test Results Dashboard..." 38 | wget -q -o /dev/null -O "$REPORT_FOLDER/report-test-results-${FILE_POSTFIX[$i]}.pdf" "http://localhost:8686/api/v5/report/0Pe9llbnz?from=${FROM_TIMESTAMPS[$i]}&to=${TO_TIMESTAMPS[$i]}" 39 | 40 | echo ">> Exporting Issuer/Verifier Postgres Metrics Dashboard..." 41 | wget -q -o /dev/null -O "$REPORT_FOLDER/report-issuer-verifier-postgres-metrics-${FILE_POSTFIX[$i]}.pdf" "http://localhost:8686/api/v5/report/5ZAfbNf7k?from=${FROM_TIMESTAMPS[$i]}&to=${TO_TIMESTAMPS[$i]}" 42 | 43 | echo ">> Exporting Full Docker Container Metrics Dashboard..." 44 | wget -q -o /dev/null -O "$REPORT_FOLDER/report-docker-container-metrics-${FILE_POSTFIX[$i]}.pdf" "http://localhost:8686/api/v5/report/pMEd7m0Mz?from=${FROM_TIMESTAMPS[$i]}&to=${TO_TIMESTAMPS[$i]}" 45 | 46 | echo ">> Exporting Host Metrics Dashboard..." 47 | wget -q -o /dev/null -O "$REPORT_FOLDER/report-host-metrics-${FILE_POSTFIX[$i]}.pdf" "http://localhost:8686/api/v5/report/rYdddlPWk?from=${FROM_TIMESTAMPS[$i]}&to=${TO_TIMESTAMPS[$i]}" 48 | 49 | done 50 | 51 | echo "Please find all downloaded dashboard in $REPORT_FOLDER" 52 | -------------------------------------------------------------------------------- /scripts/install-vm.sh: -------------------------------------------------------------------------------- 1 | echo ">> Update all dependencies" 2 | sudo apt update 3 | 4 | echo ">> Install docker if not yet installed" 5 | if [ -x "$(command -v docker)" ]; then 6 | echo "docker is already installed." 7 | else 8 | echo ">> Install docker" 9 | sudo apt install apt-transport-https ca-certificates curl software-properties-common 10 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 11 | sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable" 12 | sudo apt update 13 | apt-cache policy docker-ce 14 | sudo apt -y install docker-ce 15 | 16 | echo ">> Add current user to the docker user-group to be able to run docker without sudo" 17 | sudo usermod -aG docker ${USER} 18 | 19 | echo ">> Install Loki Docker Plugin to store all logs in Loki" 20 | docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions 21 | docker plugin ls 22 | fi 23 | 24 | echo ">> Install docker-compose if not yet installed" 25 | if [ -x "$(command -v docker-compose)" ]; then 26 | echo "docker-compose is already installed." 27 | else 28 | echo ">> Install docker-compose" 29 | sudo curl -L "https://github.com/docker/compose/releases/download/1.28.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose 30 | sudo chmod +x /usr/local/bin/docker-compose 31 | fi 32 | 33 | echo ">> Install Java 11" 34 | sudo apt -y install openjdk-11-jre-headless 35 | 36 | echo ">> Install jq" 37 | sudo apt-get -y install jq 38 | 39 | echo ">> Install ngrok" 40 | sudo snap -y install ngrok 41 | 42 | echo ">> Install screen" 43 | sudo apt -y install screen 44 | 45 | 46 | echo ">> Download node_exporter" 47 | cd ~/ 48 | wget https://github.com/prometheus/node_exporter/releases/download/v1.3.1/node_exporter-1.3.1.linux-amd64.tar.gz 49 | tar xvfz node_exporter-1.3.1.linux-amd64.tar.gz 50 | cd node_exporter-1.3.1.linux-amd64 51 | sudo mv ./node_exporter /usr/local/bin/ 52 | 53 | echo ">> Create a node_exporter user and service to run node exporter in the background" 54 | sudo useradd -rs /bin/false node_exporter 55 | sudo touch /etc/systemd/system/node_exporter.service 56 | sudo cat > /etc/systemd/system/node_exporter.service << EOF 57 | [Unit] 58 | Description=Node Exporter 59 | After=network.target 60 | 61 | [Service] 62 | User=node_exporter 63 | Group=node_exporter 64 | Type=simple 65 | ExecStart=/usr/local/bin/node_exporter --web.listen-address="172.17.0.1:9100" 66 | 67 | [Install] 68 | WantedBy=multi-user.target 69 | EOF 70 | 71 | echo ">> Start Node Exporter" 72 | sudo systemctl daemon-reload 73 | sudo systemctl start node_exporter 74 | systemctl status node_exporter 75 | 76 | echo ">> Ensure that Node Exporter is started on startup" 77 | sudo systemctl enable node_exporter 78 | 79 | 80 | 81 | echo ">> TODO" 82 | echo "1. 'sudo reboot' to reboot the VM and apply all installations" 83 | echo "3. configure the ./setup/.env" 84 | echo "4. run the Load Generator ./setup/manage.sh start" 85 | -------------------------------------------------------------------------------- /setup/.env.example: -------------------------------------------------------------------------------- 1 | # System 2 | SYSTEM_LEDGER=true 3 | SYSTEM_ISSUER_POSTGRES_DB=true 4 | SYSTEM_ISSUER_POSTGRES_DB_CLUSTER=false 5 | SYSTEM_METRICS_DASHBOARD=true 6 | SYSTEM_AGENTS=true 7 | SYSTEM_LOAD_GENERATOR=true 8 | 9 | # Scale Instances 10 | NUMBER_OF_ISSUER_VERIFIER_ACAPY_INSTANCES=1 11 | NUMBER_OF_HOLDER_ACAPY_INSTANCES=1 12 | 13 | # Multitenancy 14 | ENABLE_MULTITENANCY=false 15 | # each load generator creates a new sub wallet -> number or load genertor instances = number of sub wallets 16 | NUMBER_OF_LOAD_GENERATOR_INSTANCES_FOR_MULTITENANCY=3 17 | LEDGER_REGISTER_DID_ENDPOINT_DOCKER=http://host.docker.internal:9000/register 18 | 19 | # Docker Images 20 | ACAPY_IMAGE=bcgovimages/aries-cloudagent:py36-1.16-1_0.7.5 21 | POSTGRES_IMAGE=postgres:13.1-alpine 22 | 23 | # Ledger 24 | LEDGER_GENESIS=http://host.docker.internal:9000/genesis 25 | LEDGER_POOL_NAME=local_test_ledger 26 | LEDGER_REGISTER_DID_ENDPOINT_HOST=http://localhost:9000/register 27 | 28 | # Load Generator 29 | LOAD_GENERATOR_WEBHOOK_ENDPOINT=http://host.docker.internal:8080/acapy-webhook 30 | SERVER_TOMCAT_THREADS_MAX=200 31 | SERVER_TOMCAT_MAX_CONNECTIONS=8192 32 | 33 | # Load Generator: Test Runners 34 | TEST_RUNNERS_MAX_PARALLEL_ITERATIONS_RUNNER_ACTIVE=false 35 | TEST_RUNNERS_MAX_PARALLEL_ITERATIONS_RUNNER_NUMBER_OF_TOTAL_ITERATIONS=100 36 | TEST_RUNNERS_MAX_PARALLEL_ITERATIONS_RUNNER_NUMBER_OF_PARALLEL_ITERATIONS=5 37 | 38 | TEST_RUNNERS_CONSTANT_LOAD_RUNNER_ACTIVE=true 39 | TEST_RUNNERS_CONSTANT_LOAD_RUNNER_NUMBER_OF_TOTAL_ITERATIONS=100 40 | TEST_RUNNERS_CONSTANT_LOAD_RUNNER_NUMBER_OF_ITERATIONS_PER_MINUTE=30 41 | TEST_RUNNERS_CONSTANT_LOAD_RUNNER_THREAD_POOL_SIZE=10 42 | 43 | TEST_RUNNERS_INCREASING_LOAD_RUNNER_ACTIVE=false 44 | TEST_RUNNERS_INCREASING_LOAD_RUNNER_PEAK_DURATION_IN_MINUTES=2 45 | TEST_RUNNERS_INCREASING_LOAD_RUNNER_SLEEP_BETWEEN_PEAKS_IN_MINUTES=1 46 | TEST_RUNNERS_INCREASING_LOAD_RUNNER_INITIAL_NUMBER_OF_ITERATIONS_PER_MINUTE=150 47 | TEST_RUNNERS_INCREASING_LOAD_RUNNER_FINAL_NUMBER_OF_ITERATIONS_PER_MINUTE=200 48 | TEST_RUNNERS_INCREASING_LOAD_RUNNER_STEP_SIZE_OF_ITERATIONS_PER_MINUTE=25 49 | TEST_RUNNERS_INCREASING_LOAD_RUNNER_THREAD_POOL_SIZE=10 50 | 51 | # Load Generator: Test Flows 52 | TEST_FLOWS_EMPTY_FLOW_ACTIVE=false 53 | 54 | TEST_FLOWS_FULL_FLOW_ACTIVE=true 55 | TEST_FLOWS_FULL_FLOW_USE_REVOCABLE_CREDENTIALS=false 56 | TEST_FLOWS_FULL_FLOW_REVOCATION_REGISTRY_SIZE=3000 57 | TEST_FLOWS_FULL_FLOW_CHECK_NON_REVOKED=false 58 | TEST_FLOWS_FULL_FLOW_REVOKE_CREDENTIALS=false 59 | TEST_FLOWS_FULL_FLOW_CREDENTIAL_REVOCATION_BATCH_SIZE=5 60 | 61 | TEST_FLOWS_ISSUER_FLOW_ACTIVE=false 62 | TEST_FLOWS_ISSUER_FLOW_USE_REVOCABLE_CREDENTIALS=true 63 | TEST_FLOWS_ISSUER_FLOW_REVOCATION_REGISTRY_SIZE=3000 64 | 65 | TEST_FLOWS_CONNECTION_FLOW_ACTIVE=false 66 | 67 | TEST_FLOWS_CREDENTIAL_ISSUANCE_FLOW_ACTIVE=false 68 | TEST_FLOWS_CREDENTIAL_ISSUANCE_FLOW_USE_REVOCABLE_CREDENTIALS=true 69 | TEST_FLOWS_CREDENTIAL_ISSUANCE_FLOW_REVOCATION_REGISTRY_SIZE=3000 70 | 71 | TEST_FLOWS_PROOF_REQUEST_FLOW_ACTIVE=false 72 | TEST_FLOWS_PROOF_REQUEST_FLOW_REVOCATION_REGISTRY_SIZE=3000 73 | TEST_FLOWS_PROOF_REQUEST_FLOW_CHECK_NON_REVOKED=true 74 | 75 | # AcaPy Wallet Type ("indy", "askar", or "basic") 76 | WALLET_TYPE=askar 77 | 78 | # Issuer/Verifier Wallet DB 79 | MAX_CONNECTIONS_WALLET_DB_SINGLE_INSTANCE=100 80 | LOCK_TIMEOUT_IN_MS_WALLET_DB_SINGLE_INSTANCE=60000 81 | STATEMENT_TIMEOUT_IN_MS_WALLET_DB_SINGLE_INSTANCE=60000 82 | IDLE_IN_TRASATION_SESSION_TIMEOUT_IN_MS_WALLET_DB_SINGLE_INSTANCE=60000 83 | POSTGRES_USER=postgres 84 | POSTGRES_PASSWORD=postgres 85 | POSTGRES_DB=wallet_db 86 | 87 | # Issuer-Verifier AcaPy 88 | ISSUER_DID_SEED=Z7wnGIbb6amj4mMGmkeCER5zD75VYYgC 89 | ACAPY_TAILS_SERVER_BASE_URL=http://tails-server:6543 90 | MAX_WALLET_CONNECTIONS_PER_ACAPY=5 91 | # only usable in the context of `manage.sh debug` and requires to attach a debugger to the issuer-verifier agent 92 | ISSUER_VERIFIER_AGENT_ENABLE_DEBUGGING=false 93 | 94 | # Log Levels DEBUG or INFO 95 | ACAPY_LOG_LEVEL=INFO 96 | 97 | # Set to "true" for extensive Postgres logging (e.g. SQL statements). 98 | # Note: Expect degraded performance when set to "true" 99 | # "true" translates to the following settings in postgres.conf 100 | # log_statement_sample_rate=1.0 101 | # log_transaction_sample_rate=1.0 102 | # log_checkpoints=on 103 | # log_connections=on 104 | # log_disconnections=on 105 | # log_duration=on 106 | # log_lock_waits=0n 107 | # log_statement=all 108 | POSTGRES_LOG_DEBUG=false 109 | 110 | # AcaPy Debug Options (--debug-connections --debug-presentations --debug-credentials) 111 | ACAPY_DEBUG_OPTIONS= 112 | -------------------------------------------------------------------------------- /setup/agents/docker-compose-agents-debugging.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | 4 | issuer-verifier-acapy: 5 | image: ${ACAPY_IMAGE} 6 | container_name: issuer-verifier-acapy 7 | entrypoint: /bin/bash 8 | environment: 9 | ENABLE_PTVSD: 1 10 | command: [ 11 | "-c", 12 | "sleep 10; 13 | aca-py start \ 14 | --auto-provision 15 | --admin 0.0.0.0 10000 \ 16 | --inbound-transport http 0.0.0.0 10001 \ 17 | --outbound-transport http \ 18 | --endpoint http://issuer-verifier-acapy:10001 \ 19 | --genesis-url ${LEDGER_GENESIS} \ 20 | --ledger-pool-name ${LEDGER_POOL_NAME} \ 21 | --invite-base-url didcomm://aries_connection_invitation \ 22 | ${MULTITENANCY_SETTINGS} \ 23 | --admin-insecure-mode \ 24 | --label issuer_verifier_acapy \ 25 | --wallet-type ${WALLET_TYPE} \ 26 | --wallet-name ${POSTGRES_DB} \ 27 | --wallet-key key \ 28 | --wallet-storage-type postgres_storage \ 29 | --wallet-storage-config '{\"url\":\"host.docker.internal:5432\",\"max_connections\":${MAX_WALLET_CONNECTIONS_PER_ACAPY}}' \ 30 | --wallet-storage-creds '{\"account\":\"${POSTGRES_USER}\",\"password\":\"${POSTGRES_PASSWORD}\",\"admin_account\":\"${POSTGRES_USER}\",\"admin_password\":\"${POSTGRES_PASSWORD}\"}' \ 31 | --auto-ping-connection \ 32 | --auto-verify-presentation \ 33 | --invite-public \ 34 | --public-invites \ 35 | --invite-multi-use \ 36 | --tails-server-base-url ${ACAPY_TAILS_SERVER_BASE_URL} \ 37 | --seed ${ISSUER_DID_SEED} \ 38 | --log-level ${ACAPY_LOG_LEVEL} \ 39 | --webhook-url ${LOAD_GENERATOR_WEBHOOK_ENDPOINT} \ 40 | ${ACAPY_DEBUG_OPTIONS}" 41 | ] 42 | networks: 43 | - aries-load-test 44 | ports: 45 | - 10000:10000 # Admin API 46 | - 10001:10001 # DIDcomm 47 | - 5678:5678 # Debugger 48 | extra_hosts: 49 | - "host.docker.internal:host-gateway" 50 | logging: 51 | driver: loki 52 | options: 53 | loki-url: http://172.17.0.1:3100/loki/api/v1/push 54 | loki-max-backoff: "10s" 55 | loki-retries: "5" 56 | max-file: "5" 57 | max-size: "50m" 58 | restart: always 59 | 60 | holder-acapy: 61 | image: ${ACAPY_IMAGE} 62 | container_name: holder-acapy 63 | entrypoint: /bin/bash 64 | command: [ 65 | "-c", 66 | "sleep 10; 67 | aca-py start \ 68 | --auto-provision \ 69 | --inbound-transport http 0.0.0.0 10011 \ 70 | --outbound-transport http \ 71 | --admin 0.0.0.0 10010 \ 72 | --endpoint http://holder-acapy:10011 \ 73 | --genesis-url ${LEDGER_GENESIS} \ 74 | --ledger-pool-name $LEDGER_POOL_NAME \ 75 | --ledger-pool-name ${LEDGER_POOL_NAME} \ 76 | --admin-insecure-mode \ 77 | --label holder_acapy \ 78 | --wallet-type ${WALLET_TYPE} \ 79 | --wallet-name wallet_db \ 80 | --wallet-key key \ 81 | --wallet-storage-type default \ 82 | --auto-accept-invites \ 83 | --invite-public \ 84 | --public-invites \ 85 | --auto-accept-requests \ 86 | --auto-ping-connection \ 87 | --auto-respond-credential-offer \ 88 | --auto-respond-credential-proposal \ 89 | --auto-respond-credential-request \ 90 | --auto-respond-messages \ 91 | --auto-store-credential \ 92 | --auto-respond-presentation-request \ 93 | --auto-respond-presentation-proposal \ 94 | --auto-verify-presentation \ 95 | --log-level ${ACAPY_LOG_LEVEL} \ 96 | ${ACAPY_DEBUG_OPTIONS}" 97 | ] 98 | depends_on: 99 | - tails-server 100 | networks: 101 | - aries-load-test 102 | ports: 103 | - 10010:10010 104 | - 10011:10011 105 | extra_hosts: 106 | - "host.docker.internal:host-gateway" 107 | logging: 108 | driver: "json-file" 109 | options: 110 | max-file: "5" 111 | max-size: "50m" 112 | restart: always 113 | 114 | tails-server: 115 | container_name: tails-server 116 | build: 117 | context: ./indy-tails-server 118 | dockerfile: docker/Dockerfile.tails-server 119 | command: > 120 | tails-server 121 | --host 0.0.0.0 122 | --port 6543 123 | --storage-path /tmp/tails-files 124 | --log-level INFO 125 | 126 | volumes: 127 | - tails-server-volume:/home/indy/tails-files 128 | ports: 129 | - 6543:6543 130 | networks: 131 | - aries-load-test 132 | restart: always 133 | 134 | networks: 135 | aries-load-test: 136 | external: true 137 | name: aries-load-test 138 | 139 | volumes: 140 | tails-server-volume: 141 | -------------------------------------------------------------------------------- /setup/agents/docker-compose-agents.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | 4 | issuer-verifier-acapy: 5 | image: ${ACAPY_IMAGE} 6 | entrypoint: /bin/bash 7 | command: [ 8 | "-c", 9 | "sleep 10; 10 | aca-py start \ 11 | --auto-provision 12 | --admin 0.0.0.0 10000 \ 13 | --inbound-transport http 0.0.0.0 10001 \ 14 | --outbound-transport http \ 15 | --endpoint http://issuer-verifier-nginx:10001 \ 16 | --genesis-url ${LEDGER_GENESIS} \ 17 | --ledger-pool-name ${LEDGER_POOL_NAME} \ 18 | --invite-base-url didcomm://aries_connection_invitation \ 19 | ${MULTITENANCY_SETTINGS} \ 20 | --admin-insecure-mode \ 21 | --label issuer_verifier_acapy \ 22 | --wallet-type ${WALLET_TYPE} \ 23 | --wallet-name ${POSTGRES_DB} \ 24 | --wallet-key key \ 25 | --wallet-storage-type postgres_storage \ 26 | --wallet-storage-config '{\"url\":\"host.docker.internal:5432\",\"max_connections\":${MAX_WALLET_CONNECTIONS_PER_ACAPY}}' \ 27 | --wallet-storage-creds '{\"account\":\"${POSTGRES_USER}\",\"password\":\"${POSTGRES_PASSWORD}\",\"admin_account\":\"${POSTGRES_USER}\",\"admin_password\":\"${POSTGRES_PASSWORD}\"}' \ 28 | --auto-ping-connection \ 29 | --auto-verify-presentation \ 30 | --invite-public \ 31 | --public-invites \ 32 | --invite-multi-use \ 33 | --tails-server-base-url ${ACAPY_TAILS_SERVER_BASE_URL} \ 34 | --seed ${ISSUER_DID_SEED} \ 35 | --log-level ${ACAPY_LOG_LEVEL} \ 36 | --webhook-url ${LOAD_GENERATOR_WEBHOOK_ENDPOINT} \ 37 | ${ACAPY_DEBUG_OPTIONS}" 38 | ] 39 | networks: 40 | - aries-load-test 41 | extra_hosts: 42 | - "host.docker.internal:host-gateway" 43 | logging: 44 | driver: loki 45 | options: 46 | loki-url: http://172.17.0.1:3100/loki/api/v1/push 47 | loki-max-backoff: "10s" 48 | loki-retries: "5" 49 | max-file: "5" 50 | max-size: "50m" 51 | restart: always 52 | 53 | issuer-verifier-nginx: 54 | container_name: issuer-verifier-nginx 55 | image: nginx:latest 56 | volumes: 57 | - ./nginx-issuer-verifier-${NUMBER_OF_ISSUER_VERIFIER_ACAPY_INSTANCES}.conf:/etc/nginx/nginx.conf:ro 58 | depends_on: 59 | - issuer-verifier-acapy 60 | ports: 61 | - 10000:10000 62 | networks: 63 | - aries-load-test 64 | 65 | holder-acapy: 66 | image: ${ACAPY_IMAGE} 67 | entrypoint: /bin/bash 68 | command: [ 69 | "-c", 70 | "sleep 10; 71 | export IP=`ifconfig eth0 | grep 'inet ' | awk '{print $$2}'`; 72 | echo $$IP; 73 | aca-py start \ 74 | --auto-provision \ 75 | --inbound-transport http 0.0.0.0 10011 \ 76 | --outbound-transport http \ 77 | --admin 0.0.0.0 10010 \ 78 | --endpoint http://$$IP:10011 \ 79 | --genesis-url ${LEDGER_GENESIS} \ 80 | --ledger-pool-name $LEDGER_POOL_NAME \ 81 | --ledger-pool-name ${LEDGER_POOL_NAME} \ 82 | --admin-insecure-mode \ 83 | --label holder_acapy \ 84 | --wallet-type ${WALLET_TYPE} \ 85 | --wallet-name wallet_db \ 86 | --wallet-key key \ 87 | --wallet-storage-type default \ 88 | --auto-accept-invites \ 89 | --invite-public \ 90 | --public-invites \ 91 | --auto-accept-requests \ 92 | --auto-ping-connection \ 93 | --auto-respond-credential-offer \ 94 | --auto-respond-credential-proposal \ 95 | --auto-respond-credential-request \ 96 | --auto-respond-messages \ 97 | --auto-store-credential \ 98 | --auto-respond-presentation-request \ 99 | --auto-respond-presentation-proposal \ 100 | --auto-verify-presentation \ 101 | --log-level ${ACAPY_LOG_LEVEL} \ 102 | ${ACAPY_DEBUG_OPTIONS}" 103 | ] 104 | depends_on: 105 | - tails-server 106 | networks: 107 | - aries-load-test 108 | extra_hosts: 109 | - "host.docker.internal:host-gateway" 110 | logging: 111 | driver: "json-file" 112 | options: 113 | max-file: "5" 114 | max-size: "50m" 115 | restart: always 116 | 117 | tails-server: 118 | container_name: tails-server 119 | build: 120 | context: ./indy-tails-server 121 | dockerfile: docker/Dockerfile.tails-server 122 | command: > 123 | tails-server 124 | --host 0.0.0.0 125 | --port 6543 126 | --storage-path /tmp/tails-files 127 | --log-level INFO 128 | 129 | volumes: 130 | - tails-server-volume:/home/indy/tails-files 131 | ports: 132 | - 6543:6543 133 | networks: 134 | - aries-load-test 135 | restart: always 136 | 137 | networks: 138 | aries-load-test: 139 | external: true 140 | name: aries-load-test 141 | 142 | volumes: 143 | tails-server-volume: 144 | -------------------------------------------------------------------------------- /setup/agents/docker-compose-issuer-verifier-walletdb.yml: -------------------------------------------------------------------------------- 1 | # docker compose file for running a 3-node PostgreSQL cluster 2 | # with 3-node etcd cluster as the DCS and one haproxy node 3 | version: "3.7" 4 | 5 | services: 6 | etcd1: &etcd 7 | image: postgres-cluster-node 8 | networks: [ aries-load-test ] 9 | environment: 10 | ETCD_LISTEN_PEER_URLS: http://0.0.0.0:2380 11 | ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379 12 | ETCD_INITIAL_CLUSTER: etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380 13 | ETCD_INITIAL_CLUSTER_STATE: new 14 | ETCD_INITIAL_CLUSTER_TOKEN: tutorial 15 | POSTGRES_DB: postgres 16 | PATRONI_SUPERUSER_USERNAME: ${POSTGRES_USER} 17 | PATRONI_SUPERUSER_PASSWORD: ${POSTGRES_PASSWORD} 18 | PATRONI_RESTAPI_USERNAME: admin 19 | PATRONI_RESTAPI_PASSWORD: admin 20 | PATRONI_REPLICATION_USERNAME: replicator 21 | PATRONI_REPLICATION_PASSWORD: replicate 22 | PATRONI_admin_PASSWORD: admin 23 | PATRONI_admin_OPTIONS: createdb,createrole 24 | container_name: postgres-cluster-etcd1 25 | hostname: etcd1 26 | command: etcd -name etcd1 -initial-advertise-peer-urls http://etcd1:2380 27 | profiles: [ "cluster" ] 28 | 29 | etcd2: 30 | <<: *etcd 31 | container_name: postgres-cluster-etcd2 32 | hostname: etcd2 33 | command: etcd -name etcd2 -initial-advertise-peer-urls http://etcd2:2380 34 | profiles: [ "cluster" ] 35 | 36 | etcd3: 37 | <<: *etcd 38 | container_name: postgres-cluster-etcd3 39 | hostname: etcd3 40 | command: etcd -name etcd3 -initial-advertise-peer-urls http://etcd3:2380 41 | profiles: [ "cluster" ] 42 | 43 | haproxy: 44 | image: postgres-cluster-node 45 | networks: [ aries-load-test ] 46 | hostname: haproxy 47 | container_name: issuer-verifier-wallet-db 48 | ports: 49 | - "5432:5000" 50 | - "5001:5001" 51 | command: haproxy 52 | environment: &haproxy_env 53 | ETCDCTL_ENDPOINTS: http://etcd1:2379,http://etcd2:2379,http://etcd3:2379 54 | PATRONI_ETCD3_HOSTS: "'etcd1:2379','etcd2:2379','etcd3:2379'" 55 | PATRONI_SCOPE: ssiwalletdb 56 | profiles: [ "cluster" ] 57 | 58 | postgres-node-1: 59 | image: postgres-cluster-node 60 | networks: [ aries-load-test ] 61 | ports: 62 | - "5433:5432" 63 | hostname: patroni1 64 | container_name: postgres-node-1 65 | environment: 66 | <<: *haproxy_env 67 | PATRONI_NAME: patroni1 68 | profiles: [ "cluster" ] 69 | 70 | postgres-node-2: 71 | image: postgres-cluster-node 72 | networks: [ aries-load-test ] 73 | ports: 74 | - "5434:5432" 75 | hostname: patroni2 76 | container_name: postgres-node-2 77 | environment: 78 | <<: *haproxy_env 79 | PATRONI_NAME: patroni2 80 | profiles: [ "cluster" ] 81 | 82 | postgres-node-3: 83 | image: postgres-cluster-node 84 | networks: [ aries-load-test ] 85 | ports: 86 | - "5435:5432" 87 | hostname: patroni3 88 | container_name: postgres-node-3 89 | environment: 90 | <<: *haproxy_env 91 | PATRONI_NAME: patroni3 92 | profiles: [ "cluster" ] 93 | 94 | issuer-verifier-wallet-db: 95 | container_name: issuer-verifier-wallet-db 96 | image: ${POSTGRES_IMAGE} 97 | volumes: 98 | - issuer-verifier-wallet-db-volume:/var/lib/postgresql/data 99 | # - "$PWD/postgresql.conf:/etc/postgresql/postgresql.conf" 100 | command: [ "postgres","-p", "5000", 101 | "-c", "idle_in_transaction_session_timeout=${IDLE_IN_TRASATION_SESSION_TIMEOUT_IN_MS_WALLET_DB_SINGLE_INSTANCE}", 102 | "-c", "lock_timeout=${LOCK_TIMEOUT_IN_MS_WALLET_DB_SINGLE_INSTANCE}", 103 | "-c", "log_checkpoints=${POSTGRES_LOGGING_TOGGLE}", 104 | "-c", "log_connections=${POSTGRES_LOGGING_TOGGLE}", 105 | "-c", "log_disconnections=${POSTGRES_LOGGING_TOGGLE}", 106 | "-c", "log_duration=${POSTGRES_LOGGING_TOGGLE}", 107 | "-c", "log_lock_waits=${POSTGRES_LOGGING_TOGGLE}", 108 | "-c", "log_min_error_statement=ERROR", 109 | "-c", "log_statement_sample_rate=${POSTGRES_LOGGING_SAMPLE_RATE}", 110 | "-c", "log_transaction_sample_rate=${POSTGRES_LOGGING_SAMPLE_RATE}", 111 | "-c", "log_statement_sample_rate=${POSTGRES_LOGGING_SAMPLE_RATE}", 112 | "-c", "log_transaction_sample_rate=${POSTGRES_LOGGING_SAMPLE_RATE}", 113 | "-c", "max_connections=${MAX_CONNECTIONS_WALLET_DB_SINGLE_INSTANCE}", 114 | "-c", "statement_timeout=${STATEMENT_TIMEOUT_IN_MS_WALLET_DB_SINGLE_INSTANCE}" ] 115 | environment: 116 | POSTGRES_DB: postgres 117 | POSTGRES_USER: ${POSTGRES_USER} 118 | POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} 119 | networks: 120 | - aries-load-test 121 | logging: 122 | driver: "json-file" 123 | options: 124 | max-file: "5" 125 | max-size: "50m" 126 | ports: 127 | - 5432:5000 128 | restart: always 129 | profiles: [ "single-instance" ] 130 | 131 | networks: 132 | aries-load-test: 133 | external: true 134 | name: aries-load-test 135 | 136 | # TODO add volumes for cluster 137 | volumes: 138 | issuer-verifier-wallet-db-volume: 139 | -------------------------------------------------------------------------------- /setup/agents/nginx-issuer-verifier-1.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | 3 | events { 4 | worker_connections 1024; 5 | } 6 | 7 | http { 8 | 9 | upstream admin-endpoint { 10 | server agents_issuer-verifier-acapy_1:10000; 11 | } 12 | 13 | upstream didcomm-endpoint { 14 | server agents_issuer-verifier-acapy_1:10001; 15 | } 16 | 17 | server { 18 | listen 10000; 19 | location / { 20 | proxy_pass http://admin-endpoint; 21 | } 22 | } 23 | 24 | server { 25 | listen 10001; 26 | location / { 27 | proxy_pass http://didcomm-endpoint; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /setup/agents/nginx-issuer-verifier-10.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | 3 | events { 4 | worker_connections 1024; 5 | } 6 | 7 | http { 8 | 9 | upstream admin-endpoint { 10 | server agents_issuer-verifier-acapy_1:10000; 11 | server agents_issuer-verifier-acapy_2:10000; 12 | server agents_issuer-verifier-acapy_3:10000; 13 | server agents_issuer-verifier-acapy_4:10000; 14 | server agents_issuer-verifier-acapy_5:10000; 15 | server agents_issuer-verifier-acapy_6:10000; 16 | server agents_issuer-verifier-acapy_7:10000; 17 | server agents_issuer-verifier-acapy_8:10000; 18 | server agents_issuer-verifier-acapy_9:10000; 19 | server agents_issuer-verifier-acapy_10:10000; 20 | } 21 | 22 | upstream didcomm-endpoint { 23 | server agents_issuer-verifier-acapy_1:10001; 24 | server agents_issuer-verifier-acapy_2:10001; 25 | server agents_issuer-verifier-acapy_3:10001; 26 | server agents_issuer-verifier-acapy_4:10001; 27 | server agents_issuer-verifier-acapy_5:10001; 28 | server agents_issuer-verifier-acapy_6:10001; 29 | server agents_issuer-verifier-acapy_7:10001; 30 | server agents_issuer-verifier-acapy_8:10001; 31 | server agents_issuer-verifier-acapy_9:10001; 32 | server agents_issuer-verifier-acapy_10:10001; 33 | } 34 | 35 | server { 36 | listen 10000; 37 | location / { 38 | proxy_pass http://admin-endpoint; 39 | } 40 | } 41 | 42 | server { 43 | listen 10001; 44 | location / { 45 | proxy_pass http://didcomm-endpoint; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /setup/agents/nginx-issuer-verifier-15.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | 3 | events { 4 | worker_connections 1024; 5 | } 6 | 7 | http { 8 | 9 | upstream admin-endpoint { 10 | server agents_issuer-verifier-acapy_1:10000; 11 | server agents_issuer-verifier-acapy_2:10000; 12 | server agents_issuer-verifier-acapy_3:10000; 13 | server agents_issuer-verifier-acapy_4:10000; 14 | server agents_issuer-verifier-acapy_5:10000; 15 | server agents_issuer-verifier-acapy_6:10000; 16 | server agents_issuer-verifier-acapy_7:10000; 17 | server agents_issuer-verifier-acapy_8:10000; 18 | server agents_issuer-verifier-acapy_9:10000; 19 | server agents_issuer-verifier-acapy_10:10000; 20 | server agents_issuer-verifier-acapy_11:10000; 21 | server agents_issuer-verifier-acapy_12:10000; 22 | server agents_issuer-verifier-acapy_13:10000; 23 | server agents_issuer-verifier-acapy_14:10000; 24 | server agents_issuer-verifier-acapy_15:10000; 25 | } 26 | 27 | upstream didcomm-endpoint { 28 | server agents_issuer-verifier-acapy_1:10001; 29 | server agents_issuer-verifier-acapy_2:10001; 30 | server agents_issuer-verifier-acapy_3:10001; 31 | server agents_issuer-verifier-acapy_4:10001; 32 | server agents_issuer-verifier-acapy_5:10001; 33 | server agents_issuer-verifier-acapy_6:10001; 34 | server agents_issuer-verifier-acapy_7:10001; 35 | server agents_issuer-verifier-acapy_8:10001; 36 | server agents_issuer-verifier-acapy_9:10001; 37 | server agents_issuer-verifier-acapy_10:10001; 38 | server agents_issuer-verifier-acapy_11:10001; 39 | server agents_issuer-verifier-acapy_12:10001; 40 | server agents_issuer-verifier-acapy_13:10001; 41 | server agents_issuer-verifier-acapy_14:10001; 42 | server agents_issuer-verifier-acapy_15:10001; 43 | } 44 | 45 | server { 46 | listen 10000; 47 | location / { 48 | proxy_pass http://admin-endpoint; 49 | } 50 | } 51 | 52 | server { 53 | listen 10001; 54 | location / { 55 | proxy_pass http://didcomm-endpoint; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /setup/agents/nginx-issuer-verifier-20.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | 3 | events { 4 | worker_connections 1024; 5 | } 6 | 7 | http { 8 | 9 | upstream admin-endpoint { 10 | server agents_issuer-verifier-acapy_1:10000; 11 | server agents_issuer-verifier-acapy_2:10000; 12 | server agents_issuer-verifier-acapy_3:10000; 13 | server agents_issuer-verifier-acapy_4:10000; 14 | server agents_issuer-verifier-acapy_5:10000; 15 | server agents_issuer-verifier-acapy_6:10000; 16 | server agents_issuer-verifier-acapy_7:10000; 17 | server agents_issuer-verifier-acapy_8:10000; 18 | server agents_issuer-verifier-acapy_9:10000; 19 | server agents_issuer-verifier-acapy_10:10000; 20 | server agents_issuer-verifier-acapy_11:10000; 21 | server agents_issuer-verifier-acapy_12:10000; 22 | server agents_issuer-verifier-acapy_13:10000; 23 | server agents_issuer-verifier-acapy_14:10000; 24 | server agents_issuer-verifier-acapy_15:10000; 25 | server agents_issuer-verifier-acapy_16:10000; 26 | server agents_issuer-verifier-acapy_17:10000; 27 | server agents_issuer-verifier-acapy_18:10000; 28 | server agents_issuer-verifier-acapy_19:10000; 29 | server agents_issuer-verifier-acapy_20:10000; 30 | } 31 | 32 | upstream didcomm-endpoint { 33 | server agents_issuer-verifier-acapy_1:10001; 34 | server agents_issuer-verifier-acapy_2:10001; 35 | server agents_issuer-verifier-acapy_3:10001; 36 | server agents_issuer-verifier-acapy_4:10001; 37 | server agents_issuer-verifier-acapy_5:10001; 38 | server agents_issuer-verifier-acapy_6:10001; 39 | server agents_issuer-verifier-acapy_7:10001; 40 | server agents_issuer-verifier-acapy_8:10001; 41 | server agents_issuer-verifier-acapy_9:10001; 42 | server agents_issuer-verifier-acapy_10:10001; 43 | server agents_issuer-verifier-acapy_11:10001; 44 | server agents_issuer-verifier-acapy_12:10001; 45 | server agents_issuer-verifier-acapy_13:10001; 46 | server agents_issuer-verifier-acapy_14:10001; 47 | server agents_issuer-verifier-acapy_15:10001; 48 | server agents_issuer-verifier-acapy_16:10001; 49 | server agents_issuer-verifier-acapy_17:10001; 50 | server agents_issuer-verifier-acapy_18:10001; 51 | server agents_issuer-verifier-acapy_19:10001; 52 | server agents_issuer-verifier-acapy_20:10001; 53 | } 54 | 55 | server { 56 | listen 10000; 57 | location / { 58 | proxy_pass http://admin-endpoint; 59 | } 60 | } 61 | 62 | server { 63 | listen 10001; 64 | location / { 65 | proxy_pass http://didcomm-endpoint; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /setup/agents/nginx-issuer-verifier-5.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | 3 | events { 4 | worker_connections 1024; 5 | } 6 | 7 | http { 8 | 9 | upstream admin-endpoint { 10 | server agents_issuer-verifier-acapy_1:10000; 11 | server agents_issuer-verifier-acapy_2:10000; 12 | server agents_issuer-verifier-acapy_3:10000; 13 | server agents_issuer-verifier-acapy_4:10000; 14 | server agents_issuer-verifier-acapy_5:10000; 15 | } 16 | 17 | upstream didcomm-endpoint { 18 | server agents_issuer-verifier-acapy_1:10001; 19 | server agents_issuer-verifier-acapy_2:10001; 20 | server agents_issuer-verifier-acapy_3:10001; 21 | server agents_issuer-verifier-acapy_4:10001; 22 | server agents_issuer-verifier-acapy_5:10001; 23 | } 24 | 25 | server { 26 | listen 10000; 27 | location / { 28 | proxy_pass http://admin-endpoint; 29 | } 30 | } 31 | 32 | server { 33 | listen 10001; 34 | location / { 35 | proxy_pass http://didcomm-endpoint; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /setup/dashboard/docker-compose-dashboards.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | 4 | grafana: 5 | container_name: grafana 6 | image: grafana/grafana:latest 7 | ports: 8 | - 3000:3000 9 | environment: 10 | GF_RENDERING_SERVER_URL: http://grafana-image-renderer:8081/render 11 | GF_RENDERING_CALLBACK_URL: http://grafana:3000/ 12 | GF_LOG_FILTERS: rendering:debug 13 | volumes: 14 | - grafana-volume:/var/lib/grafana 15 | - ./grafana/grafana-provisioning:/etc/grafana/provisioning 16 | - ./grafana/grafana.ini:/etc/grafana/grafana.ini 17 | - ./grafana/dashboards:/var/lib/grafana/dashboards 18 | depends_on: 19 | - grafana-image-renderer 20 | - loki 21 | networks: 22 | - aries-load-test 23 | 24 | # https://grafana.com/grafana/plugins/grafana-image-renderer/#installation 25 | grafana-image-renderer: 26 | container_name: grafana-image-renderer 27 | image: grafana/grafana-image-renderer:latest 28 | ports: 29 | - 8081 30 | networks: 31 | - aries-load-test 32 | 33 | # https://github.com/IzakMarais/reporter 34 | grafana-pdf-exporter: 35 | container_name: grafana-pdf-exporter 36 | image: izakmarais/grafana-reporter 37 | entrypoint: "/usr/local/bin/grafana-reporter -ip grafana:3000 -grid-layout=1" 38 | ports: 39 | - 8686:8686 40 | depends_on: 41 | - grafana 42 | - grafana-image-renderer 43 | networks: 44 | - aries-load-test 45 | 46 | loki: 47 | container_name: loki 48 | image: grafana/loki:2.4.2 49 | ports: 50 | - 3100:3100 51 | command: -config.file=/etc/loki/local-config.yaml 52 | volumes: 53 | - loki-volume:/data/loki 54 | - ./loki/local-config.yaml:/etc/loki/local-config.yaml 55 | networks: 56 | - aries-load-test 57 | 58 | prometheus: 59 | container_name: prometheus 60 | image: prom/prometheus:v2.33.1 61 | ports: 62 | - 9090:9090 63 | command: 64 | - --config.file=/etc/prometheus/prometheus.yml 65 | volumes: 66 | - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro 67 | depends_on: 68 | - cadvisor 69 | networks: 70 | - aries-load-test 71 | # https://stackoverflow.com/a/67158212/898 72 | extra_hosts: 73 | - "host.docker.internal:host-gateway" 74 | 75 | cadvisor: 76 | image: gcr.io/cadvisor/cadvisor:v0.39.3 77 | container_name: cadvisor 78 | command: 79 | - '-port=9091' 80 | ports: 81 | - 9091:9091 82 | volumes: 83 | - /:/rootfs:ro 84 | - /var/run:/var/run:ro 85 | - /sys:/sys:ro 86 | - /var/lib/docker/:/var/lib/docker:ro 87 | - /var/run/docker.sock:/var/run/docker.sock:ro 88 | networks: 89 | - aries-load-test 90 | 91 | issuer-verifier-postgres-exporter-postgres-single-instance: 92 | container_name: issuer-verifier-postgres-exporter 93 | image: quay.io/prometheuscommunity/postgres-exporter 94 | environment: 95 | - DATA_SOURCE_NAME=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@issuer-verifier-wallet-db:5000/postgres?sslmode=disable 96 | networks: 97 | - aries-load-test 98 | ports: 99 | - 9187:9187 100 | logging: 101 | driver: "json-file" 102 | options: 103 | max-file: "5" 104 | max-size: "50m" 105 | profiles: [ "postgres-single-instance" ] 106 | 107 | issuer-verifier-postgres-exporter-node-1: 108 | container_name: issuer-verifier-postgres-exporter-node-1 109 | image: quay.io/prometheuscommunity/postgres-exporter 110 | environment: 111 | - DATA_SOURCE_NAME=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres-node-1:5432/postgres?sslmode=disable 112 | networks: 113 | - aries-load-test 114 | ports: 115 | - 9187:9187 116 | logging: 117 | driver: "json-file" 118 | options: 119 | max-file: "5" 120 | max-size: "50m" 121 | profiles: [ "postgres-cluster" ] 122 | 123 | issuer-verifier-postgres-exporter-node-2: 124 | container_name: issuer-verifier-postgres-exporter-node-2 125 | image: quay.io/prometheuscommunity/postgres-exporter 126 | environment: 127 | - DATA_SOURCE_NAME=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres-node-2:5432/postgres?sslmode=disable 128 | networks: 129 | - aries-load-test 130 | ports: 131 | - 9188:9187 132 | logging: 133 | driver: "json-file" 134 | options: 135 | max-file: "5" 136 | max-size: "50m" 137 | profiles: [ "postgres-cluster" ] 138 | 139 | issuer-verifier-postgres-exporter-node-3: 140 | container_name: issuer-verifier-postgres-exporter-node-3 141 | image: quay.io/prometheuscommunity/postgres-exporter 142 | environment: 143 | - DATA_SOURCE_NAME=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres-node-2:5432/postgres?sslmode=disable 144 | networks: 145 | - aries-load-test 146 | ports: 147 | - 9189:9187 148 | logging: 149 | driver: "json-file" 150 | options: 151 | max-file: "5" 152 | max-size: "50m" 153 | profiles: [ "postgres-cluster" ] 154 | 155 | dozzle: 156 | container_name: dozzle 157 | image: amir20/dozzle:latest 158 | environment: 159 | - DOZZLE_BASE=/dozzle 160 | - DOZZLE_LEVEL=DEBUG 161 | - DOZZLE_USERNAME=view 162 | - DOZZLE_PASSWORD=8R1uVAwRn3eyjKPj1A4iZggWTay9IOa5kEYFyaoM 163 | - DOZZLE_KEY=7zocTTANMOOH3eZVXmD5DShm4ja1vQmfoHqZRh3a 164 | - DOZZLE_NO_ANALYTICS=true 165 | networks: 166 | - aries-load-test 167 | volumes: 168 | - /var/run/docker.sock:/var/run/docker.sock 169 | ports: 170 | - 9999:8080 171 | 172 | networks: 173 | aries-load-test: 174 | external: false 175 | name: aries-load-test 176 | 177 | volumes: 178 | grafana-volume: 179 | loki-volume: 180 | -------------------------------------------------------------------------------- /setup/dashboard/grafana/grafana-provisioning/dashboards/default.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | providers: 4 | - name: Default 5 | folder: Load Generator 6 | folderUid: R49fdDfnk 7 | type: file 8 | options: 9 | path: /var/lib/grafana/dashboards 10 | -------------------------------------------------------------------------------- /setup/dashboard/grafana/grafana-provisioning/datasources/loki.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | datasources: 4 | - name: Loki 5 | type: loki 6 | access: proxy 7 | url: http://loki:3100 8 | isDefault: true 9 | jsonData: 10 | maxLines: 1000 11 | -------------------------------------------------------------------------------- /setup/dashboard/grafana/grafana-provisioning/datasources/prometheus.yaml: -------------------------------------------------------------------------------- 1 | # config file version 2 | apiVersion: 1 3 | 4 | # list of datasources that should be deleted from the database 5 | deleteDatasources: 6 | - name: Prometheus 7 | orgId: 1 8 | 9 | # list of datasources to insert/update depending 10 | # whats available in the database 11 | datasources: 12 | # name of the datasource. Required 13 | - name: Prometheus 14 | # datasource type. Required 15 | type: prometheus 16 | # access mode. direct or proxy. Required 17 | access: proxy 18 | # org id. will default to orgId 1 if not specified 19 | orgId: 1 20 | # url 21 | url: http://prometheus:9090 22 | # mark as default datasource. Max one per org 23 | isDefault: false 24 | # fields that will be converted to json and stored in json_data 25 | jsonData: 26 | graphiteVersion: "1.1" 27 | tlsAuth: false 28 | tlsAuthWithCACert: false 29 | maxLines: 1000 30 | version: 1 31 | # allow users to edit datasources from the UI. 32 | editable: true 33 | 34 | -------------------------------------------------------------------------------- /setup/dashboard/grafana/grafana.ini: -------------------------------------------------------------------------------- 1 | [auth.anonymous] 2 | enabled = true 3 | org_role = Admin 4 | 5 | [dataproxy] 6 | timeout = 300 7 | -------------------------------------------------------------------------------- /setup/dashboard/loki/local-config.yaml: -------------------------------------------------------------------------------- 1 | auth_enabled: false 2 | 3 | server: 4 | http_listen_port: 3100 5 | grpc_listen_port: 9096 6 | 7 | common: 8 | path_prefix: /tmp/loki 9 | storage: 10 | filesystem: 11 | chunks_directory: /tmp/loki/chunks 12 | rules_directory: /tmp/loki/rules 13 | replication_factor: 1 14 | ring: 15 | instance_addr: 127.0.0.1 16 | kvstore: 17 | store: inmemory 18 | 19 | schema_config: 20 | configs: 21 | - from: 2020-10-24 22 | store: boltdb-shipper 23 | object_store: filesystem 24 | schema: v11 25 | index: 26 | prefix: index_ 27 | period: 24h 28 | 29 | querier: 30 | engine: 31 | timeout: 5m 32 | query_timeout: 5m 33 | 34 | ruler: 35 | alertmanager_url: http://localhost:9093 36 | 37 | # To avoid "Too many outstanding request warning." (https://github.com/grafana/loki/issues/4613) 38 | query_scheduler: 39 | max_outstanding_requests_per_tenant: 2048 40 | 41 | # To avoid "Too many outstanding request warning." (https://github.com/grafana/loki/issues/4613) 42 | query_range: 43 | parallelise_shardable_queries: false 44 | -------------------------------------------------------------------------------- /setup/dashboard/prometheus/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 15s # By default, scrape targets every 15 seconds. 3 | evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. 4 | # scrape_timeout is set to the global default (10s). 5 | 6 | # Attach these labels to any time series or alerts when communicating with 7 | # external systems (federation, remote storage, Alertmanager). 8 | # external_labels: 9 | # monitor: 'codelab-monitor' 10 | 11 | # A scrape configuration containing exactly one endpoint to scrape: 12 | scrape_configs: 13 | - job_name: 'docker' 14 | static_configs: 15 | - targets: [ 'host.docker.internal:9323' ] 16 | 17 | - job_name: 'cadvisor' 18 | scrape_interval: 5s 19 | static_configs: 20 | - targets: [ 'cadvisor:9091' ] 21 | 22 | - job_name: 'node-export' 23 | static_configs: 24 | - targets: [ 'host.docker.internal:9100' ] 25 | 26 | - job_name: 'issuer-verifier-postgres-exporter' 27 | static_configs: 28 | - targets: [ 'host.docker.internal:9187', 'host.docker.internal:9188', 'host.docker.internal:9189' ] 29 | 30 | - job_name: 'spring' 31 | scrape_interval: 15s 32 | metrics_path: '/actuator/prometheus' 33 | static_configs: 34 | - targets: [ 'host.docker.internal:8080' ] 35 | -------------------------------------------------------------------------------- /setup/load-generator/docker-compose-load-generator.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | 4 | load-generator-1: 5 | container_name: load-generator-1 6 | build: ../../ 7 | environment: 8 | - ISSUERVERIFIER_MULTITENANCY_WEBHOOKENDPOINTURL=http://load-generator-1:8080/acapy-webhook 9 | - ISSUERVERIFIER_MULTITENANCY_SUBWALLETNAME=load-generator-1 10 | env_file: 11 | - load-generator.env 12 | networks: 13 | - aries-load-test 14 | ports: 15 | - 8080:8080 16 | extra_hosts: 17 | - "host.docker.internal:host-gateway" 18 | logging: 19 | driver: "json-file" 20 | options: 21 | max-file: "5" 22 | max-size: "50m" 23 | profiles: [ "1", "2", "3", "4", "5", "9" ] 24 | 25 | load-generator-2: 26 | container_name: load-generator-2 27 | build: ../../ 28 | environment: 29 | - ISSUERVERIFIER_MULTITENANCY_WEBHOOKENDPOINTURL=http://load-generator-2:8080/acapy-webhook 30 | - ISSUERVERIFIER_MULTITENANCY_SUBWALLETNAME=load-generator-2 31 | env_file: 32 | - load-generator.env 33 | networks: 34 | - aries-load-test 35 | extra_hosts: 36 | - "host.docker.internal:host-gateway" 37 | logging: 38 | driver: "json-file" 39 | options: 40 | max-file: "5" 41 | max-size: "50m" 42 | profiles: [ "2", "3", "4", "5", "9" ] 43 | 44 | load-generator-3: 45 | container_name: load-generator-3 46 | build: ../../ 47 | environment: 48 | - ISSUERVERIFIER_MULTITENANCY_WEBHOOKENDPOINTURL=http://load-generator-3:8080/acapy-webhook 49 | - ISSUERVERIFIER_MULTITENANCY_SUBWALLETNAME=load-generator-3 50 | env_file: 51 | - load-generator.env 52 | networks: 53 | - aries-load-test 54 | extra_hosts: 55 | - "host.docker.internal:host-gateway" 56 | logging: 57 | driver: "json-file" 58 | options: 59 | max-file: "5" 60 | max-size: "50m" 61 | profiles: [ "3", "4", "5", "9" ] 62 | 63 | load-generator-4: 64 | container_name: load-generator-4 65 | build: ../../ 66 | environment: 67 | - ISSUERVERIFIER_MULTITENANCY_WEBHOOKENDPOINTURL=http://load-generator-4:8080/acapy-webhook 68 | - ISSUERVERIFIER_MULTITENANCY_SUBWALLETNAME=load-generator-4 69 | env_file: 70 | - load-generator.env 71 | networks: 72 | - aries-load-test 73 | extra_hosts: 74 | - "host.docker.internal:host-gateway" 75 | logging: 76 | driver: "json-file" 77 | options: 78 | max-file: "5" 79 | max-size: "50m" 80 | profiles: [ "4", "5", "9" ] 81 | 82 | load-generator-5: 83 | container_name: load-generator-5 84 | build: ../../ 85 | environment: 86 | - ISSUERVERIFIER_MULTITENANCY_WEBHOOKENDPOINTURL=http://load-generator-5:8080/acapy-webhook 87 | - ISSUERVERIFIER_MULTITENANCY_SUBWALLETNAME=load-generator-5 88 | env_file: 89 | - load-generator.env 90 | networks: 91 | - aries-load-test 92 | extra_hosts: 93 | - "host.docker.internal:host-gateway" 94 | logging: 95 | driver: "json-file" 96 | options: 97 | max-file: "5" 98 | max-size: "50m" 99 | profiles: [ "5", "9" ] 100 | 101 | load-generator-6: 102 | container_name: load-generator-6 103 | build: ../../ 104 | environment: 105 | - ISSUERVERIFIER_MULTITENANCY_WEBHOOKENDPOINTURL=http://load-generator-6:8080/acapy-webhook 106 | - ISSUERVERIFIER_MULTITENANCY_SUBWALLETNAME=load-generator-6 107 | env_file: 108 | - load-generator.env 109 | networks: 110 | - aries-load-test 111 | extra_hosts: 112 | - "host.docker.internal:host-gateway" 113 | logging: 114 | driver: "json-file" 115 | options: 116 | max-file: "5" 117 | max-size: "50m" 118 | profiles: [ "9" ] 119 | 120 | load-generator-7: 121 | container_name: load-generator-7 122 | build: ../../ 123 | environment: 124 | - ISSUERVERIFIER_MULTITENANCY_WEBHOOKENDPOINTURL=http://load-generator-7:8080/acapy-webhook 125 | - ISSUERVERIFIER_MULTITENANCY_SUBWALLETNAME=load-generator-7 126 | env_file: 127 | - load-generator.env 128 | networks: 129 | - aries-load-test 130 | extra_hosts: 131 | - "host.docker.internal:host-gateway" 132 | logging: 133 | driver: "json-file" 134 | options: 135 | max-file: "5" 136 | max-size: "50m" 137 | profiles: [ "9" ] 138 | 139 | load-generator-8: 140 | container_name: load-generator-8 141 | build: ../../ 142 | environment: 143 | - ISSUERVERIFIER_MULTITENANCY_WEBHOOKENDPOINTURL=http://load-generator-8:8080/acapy-webhook 144 | - ISSUERVERIFIER_MULTITENANCY_SUBWALLETNAME=load-generator-8 145 | env_file: 146 | - load-generator.env 147 | networks: 148 | - aries-load-test 149 | extra_hosts: 150 | - "host.docker.internal:host-gateway" 151 | logging: 152 | driver: "json-file" 153 | options: 154 | max-file: "5" 155 | max-size: "50m" 156 | profiles: [ "9" ] 157 | 158 | load-generator-9: 159 | container_name: load-generator-9 160 | build: ../../ 161 | environment: 162 | - ISSUERVERIFIER_MULTITENANCY_WEBHOOKENDPOINTURL=http://load-generator-9:8080/acapy-webhook 163 | - ISSUERVERIFIER_MULTITENANCY_SUBWALLETNAME=load-generator-9 164 | env_file: 165 | - load-generator.env 166 | networks: 167 | - aries-load-test 168 | extra_hosts: 169 | - "host.docker.internal:host-gateway" 170 | logging: 171 | driver: "json-file" 172 | options: 173 | max-file: "5" 174 | max-size: "50m" 175 | profiles: [ "9" ] 176 | 177 | networks: 178 | aries-load-test: 179 | external: true 180 | name: aries-load-test 181 | -------------------------------------------------------------------------------- /setup/load-generator/load-generator.env: -------------------------------------------------------------------------------- 1 | SPRING_DATASOURCE_USERNAME=${POSTGRES_USER} 2 | SPRING_DATASOURCE_PASSWORD=${POSTGRES_PASSWORD} 3 | ISSUERVERIFIER_ACAPY_URL=${ISSUER_VERIFIER_ACAPY_URL} 4 | ISSUERVERIFIER_MULTITENANCY_ENABLED=${ENABLE_MULTITENANCY} 5 | ISSUERVERIFIER_MULTITENANCY_WALLETTYPE=${WALLET_TYPE} 6 | ISSUERVERIFIER_MULTITENANCY_REGISTERDIDENDPOINT=${LEDGER_REGISTER_DID_ENDPOINT_DOCKER} 7 | HOLDER_ACAPY_URLS=${HOLDER_ACAPY_URLS} 8 | TESTRUNNERS_MAXPARALLELITERATIONSRUNNER_ACTIVE=${TEST_RUNNERS_MAX_PARALLEL_ITERATIONS_RUNNER_ACTIVE} 9 | TESTRUNNERS_MAXPARALLELITERATIONSRUNNER_NUMBEROFTOTALITERATIONS=${TEST_RUNNERS_MAX_PARALLEL_ITERATIONS_RUNNER_NUMBER_OF_TOTAL_ITERATIONS} 10 | TESTRUNNERS_MAXPARALLELITERATIONSRUNNER_NUMBEROFPARALLELITERATIONS=${TEST_RUNNERS_MAX_PARALLEL_ITERATIONS_RUNNER_NUMBER_OF_PARALLEL_ITERATIONS} 11 | TESTRUNNERS_CONSTANTLOADRUNNER_ACTIVE=${TEST_RUNNERS_CONSTANT_LOAD_RUNNER_ACTIVE} 12 | TESTRUNNERS_CONSTANTLOADRUNNER_NUMBEROFTOTALITERATIONS=${TEST_RUNNERS_CONSTANT_LOAD_RUNNER_NUMBER_OF_TOTAL_ITERATIONS} 13 | TESTRUNNERS_CONSTANTLOADRUNNER_NUMBEROFITERATIONSPERMINUTE=${TEST_RUNNERS_CONSTANT_LOAD_RUNNER_NUMBER_OF_ITERATIONS_PER_MINUTE} 14 | TESTRUNNERS_CONSTANTLOADRUNNER_THREADPOOLSIZE=${TEST_RUNNERS_CONSTANT_LOAD_RUNNER_THREAD_POOL_SIZE} 15 | TESTRUNNERS_INCREASINGLOADRUNNER_ACTIVE=${TEST_RUNNERS_INCREASING_LOAD_RUNNER_ACTIVE} 16 | TESTRUNNERS_INCREASINGLOADRUNNER_PEAKDURATIONINMINUTES=${TEST_RUNNERS_INCREASING_LOAD_RUNNER_PEAK_DURATION_IN_MINUTES} 17 | TESTRUNNERS_INCREASINGLOADRUNNER_SLEEPBETWEENPEAKSINMINUTES=${TEST_RUNNERS_INCREASING_LOAD_RUNNER_SLEEP_BETWEEN_PEAKS_IN_MINUTES} 18 | TESTRUNNERS_INCREASINGLOADRUNNER_INITIALNUMBEROFITERATIONSPERMINUTE=${TEST_RUNNERS_INCREASING_LOAD_RUNNER_INITIAL_NUMBER_OF_ITERATIONS_PER_MINUTE} 19 | TESTRUNNERS_INCREASINGLOADRUNNER_FINALNUMBEROFITERATIONSPERMINUTE=${TEST_RUNNERS_INCREASING_LOAD_RUNNER_FINAL_NUMBER_OF_ITERATIONS_PER_MINUTE} 20 | TESTRUNNERS_INCREASINGLOADRUNNER_STEPSIZEOFITERATIONSPERMINUTE=${TEST_RUNNERS_INCREASING_LOAD_RUNNER_STEP_SIZE_OF_ITERATIONS_PER_MINUTE} 21 | TESTRUNNERS_INCREASINGLOADRUNNER_THREADPOOLSIZE=${TEST_RUNNERS_INCREASING_LOAD_RUNNER_THREAD_POOL_SIZE} 22 | TESTFLOWS_EMPTYFLOW_ACTIVE=${TEST_FLOWS_EMPTY_FLOW_ACTIVE} 23 | TESTFLOWS_FULLFLOW_ACTIVE=${TEST_FLOWS_FULL_FLOW_ACTIVE} 24 | TESTFLOWS_FULLFLOW_USEREVOCABLECREDENTIALS=${TEST_FLOWS_FULL_FLOW_USE_REVOCABLE_CREDENTIALS} 25 | TESTFLOWS_FULLFLOW_REVOCATIONREGISTRYSIZE=${TEST_FLOWS_FULL_FLOW_REVOCATION_REGISTRY_SIZE} 26 | TESTFLOWS_FULLFLOW_CHECKNONREVOKED=${TEST_FLOWS_FULL_FLOW_CHECK_NON_REVOKED} 27 | TESTFLOWS_FULLFLOW_REVOKECREDENTIALS=${TEST_FLOWS_FULL_FLOW_REVOKE_CREDENTIALS} 28 | TESTFLOWS_FULLFLOW_CREDENTIALREVOCATIONBATCHSIZE=${TEST_FLOWS_FULL_FLOW_CREDENTIAL_REVOCATION_BATCH_SIZE} 29 | TESTFLOWS_ISSUERFLOW_ACTIVE=${TEST_FLOWS_ISSUER_FLOW_ACTIVE} 30 | TESTFLOWS_ISSUERFLOW_USEREVOCABLECREDENTIALS=${TEST_FLOWS_ISSUER_FLOW_USE_REVOCABLE_CREDENTIALS} 31 | TESTFLOWS_ISSUERFLOW_REVOCATIONREGISTRYSIZE=${TEST_FLOWS_ISSUER_FLOW_REVOCATION_REGISTRY_SIZE} 32 | TESTFLOWS_CONNECTIONFLOW_ACTIVE=${TEST_FLOWS_CONNECTION_FLOW_ACTIVE} 33 | TESTFLOWS_CREDENTIALISSUANCEFLOW_ACTIVE=${TEST_FLOWS_CREDENTIAL_ISSUANCE_FLOW_ACTIVE} 34 | TESTFLOWS_CREDENTIALISSUANCEFLOW_USEREVOCABLECREDENTIALS=${TEST_FLOWS_CREDENTIAL_ISSUANCE_FLOW_USE_REVOCABLE_CREDENTIALS} 35 | TESTFLOWS_CREDENTIALISSUANCEFLOW_REVOCATIONREGISTRYSIZE=${TEST_FLOWS_CREDENTIAL_ISSUANCE_FLOW_REVOCATION_REGISTRY_SIZE} 36 | TESTFLOWS_PROOFREQUESTFLOW_ACTIVE=${TEST_FLOWS_PROOF_REQUEST_FLOW_ACTIVE} 37 | TESTFLOWS_PROOFREQUESTFLOW_REVOCATIONREGISTRYSIZE=${TEST_FLOWS_PROOF_REQUEST_FLOW_REVOCATION_REGISTRY_SIZE} 38 | TESTFLOWS_PROOFREQUESTFLOW_CHECKNONREVOKED=${TEST_FLOWS_PROOF_REQUEST_FLOW_CHECK_NON_REVOKED} 39 | SERVER_TOMCAT_THREADS_MAX=${SERVER_TOMCAT_THREADS_MAX} 40 | SERVER_TOMCAT_MAXCONNECTIONS=${SERVER_TOMCAT_MAX_CONNECTIONS} 41 | -------------------------------------------------------------------------------- /setup/manage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export MSYS_NO_PATHCONV=1 4 | set -e 5 | 6 | 7 | # getting script path 8 | SCRIPT_HOME="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 9 | 10 | # export environment variables from .env 11 | export $(grep -v '^#' $SCRIPT_HOME/.env | xargs) 12 | 13 | # ignore orphans warning 14 | export COMPOSE_IGNORE_ORPHANS=True 15 | 16 | # load all required git submodules 17 | git submodule update --init --recursive 18 | 19 | # ================================================================================================================= 20 | # Usage: 21 | # ----------------------------------------------------------------------------------------------------------------- 22 | usage() { 23 | cat <<-EOF 24 | 25 | Usage: $0 [command] 26 | 27 | Commands: 28 | 29 | start - Creates the application containers from the built images 30 | and starts the services based on the docker-compose files 31 | and configuration supplied in the .env. 32 | 33 | debug - Starts all containers but the load generator. 34 | Only one Issuer/Verifier AcaPy (optionally with a debugger enabled) 35 | and only one Holder AcaPy will be started. Can be used for running 36 | the load generator via the IDE as well as to debug the Issuer/Verifier 37 | AcaPy. The load generator can also be started using `./mvnw spring-boot:run`. 38 | 39 | restart - First, "down" is executed. Then, "start" is run. 40 | 41 | down - Brings down the services and removes the volumes (storage) and containers. 42 | 43 | EOF 44 | exit 1 45 | } 46 | 47 | toLower() { 48 | echo $(echo ${@} | tr '[:upper:]' '[:lower:]') 49 | } 50 | 51 | pushd ${SCRIPT_HOME} >/dev/null 52 | COMMAND=$(toLower ${1}) 53 | shift || COMMAND=usage 54 | 55 | function initEnv() { 56 | for arg in "$@"; do 57 | # Remove recognized arguments from the list after processing. 58 | shift 59 | case "$arg" in 60 | *=*) 61 | export "${arg}" 62 | ;; 63 | --ledger) 64 | LOG_LEDGER=1 65 | ;; 66 | --acapy) 67 | LOG_ACAPY=1 68 | ;; 69 | *) 70 | # If not recognized, save it for later procesing ... 71 | set -- "$@" "$arg" 72 | ;; 73 | esac 74 | done 75 | } 76 | 77 | function configurePostgresLoggingFlags() { 78 | if [ "${POSTGRES_LOG_DEBUG}" = true ]; then 79 | export POSTGRES_LOGGING_STATEMENT="all" 80 | export POSTGRES_LOGGING_TOGGLE="on" 81 | export POSTGRES_LOGGING_SAMPLE_RATE="1.0" 82 | else 83 | export POSTGRES_LOGGING_STATEMENT="none" 84 | export POSTGRES_LOGGING_TOGGLE="off" 85 | export POSTGRES_LOGGING_SAMPLE_RATE="0" 86 | fi 87 | } 88 | 89 | 90 | function configureMultitenancyFlags() { 91 | if [ "${ENABLE_MULTITENANCY}" = true ]; then 92 | export MULTITENANCY_SETTINGS="--multitenant --multitenant-admin --jwt-secret SECRET" 93 | else 94 | export MULTITENANCY_SETTINGS="" 95 | fi 96 | } 97 | 98 | function startLoadGenerator() { 99 | echo "Starting Load Generator ..." 100 | if [ "${ENABLE_MULTITENANCY}" = true ]; then 101 | docker-compose -f ./load-generator/docker-compose-load-generator.yml --profile ${NUMBER_OF_LOAD_GENERATOR_INSTANCES_FOR_MULTITENANCY} up -d --build 102 | else 103 | docker-compose -f ./load-generator/docker-compose-load-generator.yml --profile 1 up -d --build 104 | fi 105 | } 106 | 107 | function startDashboard() { 108 | echo "Starting dashboard and logging containers ..." 109 | 110 | if [ "${SYSTEM_ISSUER_POSTGRES_DB}" = true ]; then 111 | if [ "${SYSTEM_ISSUER_POSTGRES_DB_CLUSTER}" = true ]; then 112 | docker-compose -f ./dashboard/docker-compose-dashboards.yml --profile postgres-cluster up -d 113 | else 114 | docker-compose -f ./dashboard/docker-compose-dashboards.yml --profile postgres-single-instance up -d 115 | fi 116 | else 117 | docker-compose -f ./dashboard/docker-compose-dashboards.yml up -d 118 | fi 119 | } 120 | 121 | function startIndyNetwork() { 122 | echo "Starting the VON Network ..." 123 | ./von-network/manage build 124 | ./von-network/manage start --wait 125 | 126 | DID_REGISTRATION_RESULT="Not ready" 127 | while [[ $DID_REGISTRATION_RESULT == *"Not ready"* ]]; do 128 | echo "Waiting for the ledger to start... (sleeping 10 seconds)" 129 | sleep 10 130 | 131 | echo "Registering issuer DID..." 132 | DID_REGISTRATION_RESULT=`curl -d "{\"role\": \"ENDORSER\", \"seed\":\"$ISSUER_DID_SEED\"}" -H "Content-Type: application/json" -X POST $LEDGER_REGISTER_DID_ENDPOINT_HOST` 133 | echo $DID_REGISTRATION_RESULT 134 | done 135 | } 136 | 137 | function startAgents() { 138 | configureMultitenancyFlags 139 | 140 | docker-compose -f ./agents/docker-compose-agents.yml up -d issuer-verifier-acapy 141 | 142 | echo "Provisioning AcaPys... (sleeping 15 seconds)" 143 | sleep 15 144 | 145 | echo "Starting all AcaPy related docker containers ..." 146 | docker-compose -f ./agents/docker-compose-agents.yml up -d --scale issuer-verifier-acapy=$NUMBER_OF_ISSUER_VERIFIER_ACAPY_INSTANCES --scale holder-acapy=$NUMBER_OF_HOLDER_ACAPY_INSTANCES 147 | 148 | echo "Waiting for all the agents to start... (sleeping 15 seconds)" 149 | sleep 15 150 | 151 | export HOLDER_ACAPY_URLS="http://`docker network inspect aries-load-test | jq '.[].Containers | to_entries[].value | select(.Name|test("^agents_holder-acapy_.")) | .IPv4Address' -r | paste -sd, - | sed 's/\/[0-9]*/:10010/g' | sed 's/,/, http:\/\//g'`" 152 | } 153 | 154 | function startPostgresSingleInstance() { 155 | configurePostgresLoggingFlags 156 | 157 | docker-compose -f ./agents/docker-compose-issuer-verifier-walletdb.yml --profile single-instance up -d; 158 | 159 | echo "Starting Issuer Wallet DB as single instance... (sleeping 15 seconds)" 160 | sleep 15 161 | } 162 | 163 | function startPostgresCluster() { 164 | # cluster is built using Patroni technologies: https://github.com/zalando/patroni 165 | # details about toy environment using Docker: https://github.com/zalando/patroni/tree/master/docker 166 | 167 | cd "$SCRIPT_HOME/agents/patroni"; 168 | docker build -t postgres-cluster-node --build-arg PG_MAJOR=13 .; 169 | 170 | cd $SCRIPT_HOME; 171 | docker-compose -f ./agents/docker-compose-issuer-verifier-walletdb.yml --profile cluster up -d; 172 | 173 | echo "Starting Postgres HA Cluster using Patroni... (sleeping 45 seconds)" 174 | sleep 45 175 | } 176 | 177 | function buildAcaPyDebugImage() { 178 | docker build -t acapy-debug -f ./agents/acapy/docker/Dockerfile.run ./agents/acapy/ 179 | } 180 | 181 | function startAll() { 182 | if [ "${SYSTEM_LEDGER}" = true ]; then 183 | startIndyNetwork 184 | fi 185 | 186 | if [ "${SYSTEM_METRICS_DASHBOARD}" = true ]; then 187 | startDashboard 188 | fi 189 | 190 | if [ "${SYSTEM_ISSUER_POSTGRES_DB}" = true ]; then 191 | if [ "${SYSTEM_ISSUER_POSTGRES_DB_CLUSTER}" = true ]; then 192 | startPostgresCluster 193 | else 194 | startPostgresSingleInstance 195 | fi 196 | fi 197 | 198 | if [ "${SYSTEM_AGENTS}" = true ]; then 199 | startAgents 200 | fi 201 | 202 | if [ "${SYSTEM_LOAD_GENERATOR}" = true ]; then 203 | export ISSUER_VERIFIER_ACAPY_URL=http://issuer-verifier-nginx:10000 204 | startLoadGenerator 205 | fi 206 | } 207 | 208 | function debug() { 209 | if [ "${SYSTEM_LEDGER}" = true ]; then 210 | startIndyNetwork 211 | fi 212 | 213 | if [ "${SYSTEM_METRICS_DASHBOARD}" = true ]; then 214 | startDashboard 215 | fi 216 | 217 | if [ "${SYSTEM_ISSUER_POSTGRES_DB}" = true ]; then 218 | if [ "${SYSTEM_ISSUER_POSTGRES_DB_CLUSTER}" = true ]; then 219 | startPostgresCluster 220 | else 221 | startPostgresSingleInstance 222 | fi 223 | fi 224 | 225 | if [ "${SYSTEM_AGENTS}" = true ]; then 226 | configureMultitenancyFlags 227 | 228 | if [ "${ISSUER_VERIFIER_AGENT_ENABLE_DEBUGGING}" = true ]; then 229 | buildAcaPyDebugImage 230 | export ACAPY_IMAGE=acapy-debug 231 | fi 232 | 233 | docker-compose -f ./agents/docker-compose-agents-debugging.yml up -d 234 | fi 235 | } 236 | 237 | function downAll() { 238 | echo "Stopping the VON Network and deleting ledger data ..." 239 | ./von-network/manage down 240 | 241 | echo "Stopping load generator ..." 242 | docker-compose -f ./load-generator/docker-compose-load-generator.yml down -v 243 | 244 | echo "Stopping and removing any running AcaPy containers as well as volumes ..." 245 | docker-compose -f ./agents/docker-compose-agents.yml down -v 246 | 247 | echo "Stopping and removing any Wallet-DB containers as well as volumes ..." 248 | docker-compose -f ./agents/docker-compose-issuer-verifier-walletdb.yml down -v 249 | 250 | echo "Stopping and removing dashboard and logging containers as well as volumes ..." 251 | docker-compose -f ./dashboard/docker-compose-dashboards.yml down -v 252 | } 253 | 254 | case "${COMMAND}" in 255 | start) 256 | startAll 257 | ;; 258 | debug) 259 | debug 260 | ;; 261 | restart) 262 | downAll 263 | startAll 264 | ;; 265 | down) 266 | downAll 267 | ;; 268 | *) 269 | usage 270 | ;; 271 | esac 272 | 273 | popd >/dev/null 274 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/GeneratorApplication.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator 2 | 3 | import com.bka.ssi.generator.application.testrunners.TestRunner 4 | import org.springframework.boot.CommandLineRunner 5 | import org.springframework.boot.autoconfigure.SpringBootApplication 6 | import org.springframework.boot.runApplication 7 | import org.springframework.context.annotation.Bean 8 | import org.springframework.scheduling.annotation.EnableScheduling 9 | 10 | @SpringBootApplication 11 | @EnableScheduling 12 | class GeneratorApplication { 13 | 14 | @Bean 15 | fun runTest(testRunner: TestRunner): CommandLineRunner { 16 | return CommandLineRunner { _ -> 17 | testRunner.run() 18 | } 19 | } 20 | 21 | } 22 | 23 | fun main(args: Array) { 24 | runApplication(*args) 25 | } 26 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/agents/acapy/AcaPyAriesClient.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.agents.acapy 2 | 3 | import com.bka.ssi.generator.application.logger.AriesClientLogger 4 | import com.bka.ssi.generator.application.logger.ErrorLogger 5 | import com.bka.ssi.generator.domain.objects.* 6 | import com.bka.ssi.generator.domain.services.IAriesClient 7 | import com.google.gson.Gson 8 | import com.google.gson.JsonObject 9 | import org.hyperledger.aries.AriesClient 10 | import org.hyperledger.aries.api.connection.CreateInvitationParams 11 | import org.hyperledger.aries.api.connection.CreateInvitationRequest 12 | import org.hyperledger.aries.api.connection.ReceiveInvitationRequest 13 | import org.hyperledger.aries.api.credential_definition.CredentialDefinition 14 | import org.hyperledger.aries.api.credentials.CredentialAttributes 15 | import org.hyperledger.aries.api.credentials.CredentialPreview 16 | import org.hyperledger.aries.api.issue_credential_v1.V1CredentialProposalRequest 17 | import org.hyperledger.aries.api.present_proof.PresentProofRequest 18 | import org.hyperledger.aries.api.revocation.RevokeRequest 19 | import org.hyperledger.aries.api.schema.SchemaSendRequest 20 | import java.util.* 21 | 22 | 23 | class AcaPyAriesClient( 24 | private val acaPy: AriesClient, 25 | private val ariesClientLogger: AriesClientLogger 26 | ) : IAriesClient { 27 | 28 | override fun getPublicDid(): String? { 29 | val did = acaPy.walletDidPublic().orElse(null) ?: return null 30 | 31 | return did.did 32 | } 33 | 34 | override fun createSchemaAndCredentialDefinition( 35 | schemaDo: SchemaDo, 36 | revocable: Boolean, 37 | revocationRegistrySize: Int 38 | ): CredentialDefinitionDo { 39 | val schemaSendResponse = acaPy.schemas( 40 | SchemaSendRequest.builder() 41 | .attributes(schemaDo.attributes) 42 | .schemaName(schemaDo.name) 43 | .schemaVersion(schemaDo.version) 44 | .build() 45 | ) 46 | 47 | val credentialDefinitionResponse = acaPy.credentialDefinitionsCreate( 48 | CredentialDefinition.CredentialDefinitionRequest.builder() 49 | .schemaId(schemaSendResponse.get().schemaId) 50 | .supportRevocation(revocable) 51 | .revocationRegistrySize(revocationRegistrySize) 52 | .tag("1.0") 53 | .build() 54 | ) 55 | 56 | return CredentialDefinitionDo(credentialDefinitionResponse.get().credentialDefinitionId) 57 | } 58 | 59 | override fun createConnectionInvitation(alias: String): ConnectionInvitationDo { 60 | val createInvitationRequest = acaPy.connectionsCreateInvitation( 61 | CreateInvitationRequest.builder().build(), 62 | CreateInvitationParams( 63 | alias, 64 | true, 65 | false, 66 | false 67 | ) 68 | ) 69 | 70 | return ConnectionInvitationDo( 71 | createInvitationRequest.get().invitation.atType, 72 | createInvitationRequest.get().invitation.atId, 73 | createInvitationRequest.get().invitation.recipientKeys, 74 | createInvitationRequest.get().invitation.serviceEndpoint, 75 | createInvitationRequest.get().invitation.label 76 | ) 77 | } 78 | 79 | override fun receiveConnectionInvitation(connectionInvitationDo: ConnectionInvitationDo) { 80 | val connectionRecord = acaPy.connectionsReceiveInvitation( 81 | ReceiveInvitationRequest.builder() 82 | .type(connectionInvitationDo.type) 83 | .id(connectionInvitationDo.id) 84 | .recipientKeys(connectionInvitationDo.recipientKeys) 85 | .serviceEndpoint(connectionInvitationDo.serviceEndpoint) 86 | .label(connectionInvitationDo.label) 87 | .build(), 88 | null 89 | ) 90 | } 91 | 92 | override fun issueCredentialToConnection(connectionId: String, credentialDo: CredentialDo) { 93 | val credentialExchange = acaPy.issueCredentialSend( 94 | V1CredentialProposalRequest( 95 | true, 96 | true, 97 | "Credential Offer", 98 | connectionId, 99 | credentialDo.credentialDefinitionId, 100 | CredentialPreview( 101 | credentialDo.claims.map { CredentialAttributes(it.key, it.value) } 102 | ), 103 | null, 104 | null, 105 | null, 106 | null, 107 | null, 108 | false 109 | ) 110 | ) 111 | } 112 | 113 | private fun revokeCredential( 114 | credentialRevocationRegistryRecord: CredentialRevocationRegistryRecordDo, 115 | publish: Boolean 116 | ) { 117 | val credentialRevocation = acaPy.revocationRevoke( 118 | RevokeRequest.builder() 119 | .credRevId(credentialRevocationRegistryRecord.credentialRevocationRegistryIndex) 120 | .revRegId(credentialRevocationRegistryRecord.credentialRevocationRegistryId) 121 | .publish(publish) 122 | .notify(false) 123 | .build() 124 | ) 125 | } 126 | 127 | override fun revokeCredentialWithoutPublishing(credentialRevocationRegistryRecord: CredentialRevocationRegistryRecordDo) { 128 | revokeCredential(credentialRevocationRegistryRecord, false) 129 | } 130 | 131 | override fun revokeCredentialAndPublishRevocations(credentialRevocationRegistryRecord: CredentialRevocationRegistryRecordDo) { 132 | val trackingId = UUID.randomUUID().toString() 133 | 134 | ariesClientLogger.startPublishRevokedCredentials(trackingId) 135 | revokeCredential(credentialRevocationRegistryRecord, true) 136 | ariesClientLogger.stopPublishRevokedCredentials(trackingId) 137 | } 138 | 139 | override fun sendProofRequestToConnection( 140 | connectionId: String, 141 | proofRequestDo: ProofRequestDo, 142 | checkNonRevoked: Boolean, 143 | comment: ProofExchangeCommentDo 144 | ) { 145 | val proofRequestBuilder = PresentProofRequest.ProofRequest.builder() 146 | .name(comment.toString()) 147 | .requestedAttributes( 148 | proofRequestDo.requestedCredentials.mapIndexed { index: Int, credentialRequestDo: CredentialRequestDo -> 149 | "${index}_credential" to PresentProofRequest.ProofRequest.ProofRequestedAttributes.builder() 150 | .names(credentialRequestDo.claims) 151 | .restriction( 152 | Gson().fromJson( 153 | "{\"cred_def_id\": \"${credentialRequestDo.credentialDefinitionIdRestriction}\"}", 154 | JsonObject::class.java 155 | ) 156 | ) 157 | .restriction( 158 | Gson().fromJson( 159 | "{\"attr::${credentialRequestDo.attributeValueRestriction.attributeName}::value\": \"${credentialRequestDo.attributeValueRestriction.attributeValue}\"}", 160 | JsonObject::class.java 161 | ) 162 | ) 163 | .build() 164 | }.toMap() 165 | ) 166 | .version("1.0") 167 | 168 | if (checkNonRevoked) { 169 | proofRequestBuilder.nonRevoked( 170 | PresentProofRequest.ProofRequest.ProofNonRevoked( 171 | proofRequestDo.nonRevokedFrom, 172 | proofRequestDo.nonRevokedTo 173 | ) 174 | ) 175 | } 176 | 177 | val proofRequest = proofRequestBuilder.build() 178 | 179 | val presentationExchangeRecord = acaPy.presentProofSendRequest( 180 | PresentProofRequest( 181 | connectionId, 182 | proofRequest, 183 | false, 184 | "Proof Request" 185 | ) 186 | ) 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/agents/acapy/AcaPyOkHttpInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.agents.acapy 2 | 3 | import com.bka.ssi.generator.application.logger.ErrorLogger 4 | import com.bka.ssi.generator.domain.services.IHttpRequestObserver 5 | import okhttp3.Interceptor 6 | import okhttp3.Response 7 | 8 | class AcaPyRequestLoggerInterceptor( 9 | private val ariesClientType: String, 10 | private val handler: IHttpRequestObserver, 11 | private val errorLogger: ErrorLogger 12 | ) : Interceptor { 13 | 14 | override fun intercept(chain: Interceptor.Chain): Response { 15 | val request = chain.request() 16 | 17 | val t1 = System.nanoTime() 18 | val response = chain.proceed(request) 19 | val t2 = System.nanoTime() 20 | 21 | val durationInMs = (t2 - t1) / 1e6 22 | 23 | handler.logHttpRequest(request.method, request.url.encodedPath, response.code, durationInMs) 24 | 25 | if (response.code != 200 && response.code != 201) { 26 | errorLogger.reportAriesClientHttpRequestError( 27 | ariesClientType = "AcaPy.$ariesClientType", 28 | httpMethod = request.method, 29 | uri = request.url.encodedPath, 30 | httpResponseCode = response.code, 31 | durationInMs = durationInMs, 32 | appErrorCode = "n/a", 33 | message = response.body?.string() ?: "" 34 | ) 35 | } 36 | 37 | return response 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/agents/acapy/AcaPyPostgresWallet.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.agents.acapy 2 | 3 | import com.bka.ssi.generator.domain.services.IWallet 4 | import org.slf4j.Logger 5 | import org.slf4j.LoggerFactory 6 | import org.springframework.beans.factory.annotation.Value 7 | import org.springframework.jdbc.core.JdbcTemplate 8 | import org.springframework.stereotype.Service 9 | 10 | 11 | @Service 12 | class AcaPyPostgresWallet( 13 | val jdbcTemplate: JdbcTemplate, 14 | @Value("\${issuer-verifier.wallet-db-name}") val walletDbName: String 15 | ) : IWallet { 16 | var logger: Logger = LoggerFactory.getLogger(AcaPyPostgresWallet::class.java) 17 | 18 | override fun walletDatabaseSizeInBytes(): Int? { 19 | val size = jdbcTemplate.queryForObject( 20 | "SELECT PG_DATABASE_SIZE('$walletDbName')", 21 | Int::class.java 22 | ) 23 | 24 | return size 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/agents/acapy/AcaPyPublisher.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.agents.acapy 2 | 3 | import com.bka.ssi.generator.application.logger.ErrorLogger 4 | import com.bka.ssi.generator.domain.objects.ConnectionRecordDo 5 | import com.bka.ssi.generator.domain.objects.CredentialExchangeRecordDo 6 | import com.bka.ssi.generator.domain.objects.ProofExchangeCommentDo 7 | import com.bka.ssi.generator.domain.objects.ProofExchangeRecordDo 8 | import com.bka.ssi.generator.domain.services.IAriesObserver 9 | import org.hyperledger.aries.api.connection.ConnectionRecord 10 | import org.hyperledger.aries.api.issue_credential_v1.V1CredentialExchange 11 | import org.hyperledger.aries.api.message.ProblemReport 12 | import org.hyperledger.aries.api.present_proof.PresentationExchangeRecord 13 | import org.hyperledger.aries.api.present_proof.PresentationExchangeState 14 | import org.hyperledger.aries.webhook.EventHandler 15 | import org.springframework.stereotype.Service 16 | import java.text.SimpleDateFormat 17 | 18 | 19 | @Service 20 | class AcaPyPublisher( 21 | private val handlers: List, 22 | private val errorLogger: ErrorLogger 23 | ) : EventHandler() { 24 | 25 | private fun dateStringToMilliseconds(date: String): Long { 26 | val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS") 27 | return dateFormat.parse(date.dropLast(4)).getTime() 28 | } 29 | 30 | override fun handleConnection(connection: ConnectionRecord) { 31 | if (connection.errorMsg != null) { 32 | errorLogger.reportAriesEventError("AcaPyPublisher.handleConnection: ${connection.errorMsg}") 33 | return 34 | } 35 | 36 | handlers.forEach { 37 | it.handleConnectionRecord( 38 | ConnectionRecordDo( 39 | connection.connectionId, 40 | dateStringToMilliseconds(connection.updatedAt), 41 | connection.state.toString(), 42 | connection.stateIsActive() 43 | ) 44 | ) 45 | } 46 | } 47 | 48 | override fun handleProof(proof: PresentationExchangeRecord) { 49 | if (proof.errorMsg != null) { 50 | errorLogger.reportAriesEventError("AcaPyPublisher.handleProof: ${proof.errorMsg}") 51 | return 52 | } 53 | 54 | handlers.forEach { 55 | it.handleProofRequestRecord( 56 | ProofExchangeRecordDo( 57 | proof.presentationExchangeId, 58 | dateStringToMilliseconds(proof.updatedAt), 59 | proof.connectionId, 60 | proof.state.toString(), 61 | proof.state == PresentationExchangeState.VERIFIED, 62 | proof.isVerified, 63 | ProofExchangeCommentDo(proof.presentationRequest.name) 64 | ) 65 | ) 66 | } 67 | } 68 | 69 | override fun handleCredential(credential: V1CredentialExchange) { 70 | if (credential.errorMsg != null) { 71 | errorLogger.reportAriesEventError("AcaPyPublisher.handleCredential: ${credential.errorMsg}") 72 | return 73 | } 74 | 75 | handlers.forEach { 76 | it.handleCredentialExchangeRecord( 77 | CredentialExchangeRecordDo( 78 | credential.credentialExchangeId, 79 | credential.credentialOfferDict.credentialPreview.attributes[0].value, 80 | credential.connectionId, 81 | dateStringToMilliseconds(credential.updatedAt), 82 | credential.state.toString(), 83 | credential.stateIsCredentialAcked(), 84 | credential.revocRegId, 85 | credential.revocationId 86 | ) 87 | ) 88 | } 89 | } 90 | 91 | override fun handleProblemReport(report: ProblemReport?) { 92 | errorLogger.reportAriesEventError("AcaPyPublisher.handleProblemReport: ${report.toString()}") 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/api/AcaPyWebhookController.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.api 2 | 3 | import com.bka.ssi.generator.agents.acapy.AcaPyPublisher 4 | import org.slf4j.Logger 5 | import org.slf4j.LoggerFactory 6 | import org.springframework.web.bind.annotation.* 7 | import springfox.documentation.annotations.ApiIgnore 8 | 9 | @RestController 10 | @ApiIgnore 11 | @RequestMapping("/acapy-webhook") 12 | class AcaPyWebhookController( 13 | private val handler: AcaPyPublisher 14 | ) { 15 | 16 | var logger: Logger = LoggerFactory.getLogger(AcaPyWebhookController::class.java) 17 | 18 | @PostMapping("/topic/{topic}") 19 | fun ariesEvent(@PathVariable topic: String?, @RequestBody message: String?) { 20 | handler.handleEvent(topic, message) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/api/InfoController.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * * Copyright 2022 Bundesrepublik Deutschland 4 | * * 5 | * * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * * you may not use this file except in compliance with the License. 7 | * * You may obtain a copy of the License at 8 | * * 9 | * * http://www.apache.org/licenses/LICENSE-2.0 10 | * * 11 | * * Unless required by applicable law or agreed to in writing, software 12 | * * distributed under the License is distributed on an "AS IS" BASIS, 13 | * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * * See the License for the specific language governing permissions and 15 | * * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.bka.ssi.generator.api 20 | 21 | import com.bka.ssi.generator.domain.services.IAriesClient 22 | import io.swagger.annotations.Api 23 | import org.slf4j.Logger 24 | import org.slf4j.LoggerFactory 25 | import org.springframework.beans.factory.annotation.Qualifier 26 | import org.springframework.web.bind.annotation.GetMapping 27 | import org.springframework.web.bind.annotation.RequestMapping 28 | import org.springframework.web.bind.annotation.RestController 29 | 30 | @RestController() 31 | @Api(tags = ["info"]) 32 | @RequestMapping("/info") 33 | class InfoController( 34 | @Qualifier("IssuerVerifier") private val issuerVerifierAcaPy: IAriesClient 35 | ) { 36 | 37 | var logger: Logger = LoggerFactory.getLogger(InfoController::class.java) 38 | 39 | @GetMapping("/public-did") 40 | private fun getDid(): String? { 41 | return issuerVerifierAcaPy.getPublicDid() 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/application/logger/AriesClientLogger.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.application.logger 2 | 3 | import org.slf4j.Logger 4 | import org.slf4j.LoggerFactory 5 | import org.springframework.stereotype.Component 6 | import java.time.Instant 7 | 8 | @Component 9 | class AriesClientLogger() { 10 | var logger: Logger = LoggerFactory.getLogger(AriesClientLogger::class.java) 11 | 12 | fun startPublishRevokedCredentials(trackingId: String) { 13 | logger.info( 14 | "type=publish_credential_revocations_started trackingId=${trackingId} time=${ 15 | Instant.now().toEpochMilli() 16 | }" 17 | ) 18 | } 19 | 20 | fun stopPublishRevokedCredentials(trackingId: String) { 21 | logger.info( 22 | "type=publish_credential_revocations_stopped trackingId=${trackingId} time=${ 23 | Instant.now().toEpochMilli() 24 | }" 25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/application/logger/AriesEventLogger.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * * Copyright 2022 Bundesrepublik Deutschland 4 | * * 5 | * * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * * you may not use this file except in compliance with the License. 7 | * * You may obtain a copy of the License at 8 | * * 9 | * * http://www.apache.org/licenses/LICENSE-2.0 10 | * * 11 | * * Unless required by applicable law or agreed to in writing, software 12 | * * distributed under the License is distributed on an "AS IS" BASIS, 13 | * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * * See the License for the specific language governing permissions and 15 | * * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.bka.ssi.generator.application.logger 20 | 21 | import com.bka.ssi.generator.domain.objects.ConnectionRecordDo 22 | import com.bka.ssi.generator.domain.objects.CredentialExchangeRecordDo 23 | import com.bka.ssi.generator.domain.objects.ProofExchangeRecordDo 24 | import com.bka.ssi.generator.domain.services.IAriesObserver 25 | import org.slf4j.Logger 26 | import org.slf4j.LoggerFactory 27 | import org.springframework.stereotype.Service 28 | 29 | @Service 30 | class AriesEventLogger : IAriesObserver { 31 | var logger: Logger = LoggerFactory.getLogger(AriesEventLogger::class.java) 32 | 33 | override fun handleConnectionRecord(connectionRecord: ConnectionRecordDo) { 34 | logger.info( 35 | "type=connectionRecord time=${connectionRecord.time} connectionId=${connectionRecord.connectionId} state=${connectionRecord.state}" 36 | ) 37 | } 38 | 39 | override fun handleCredentialExchangeRecord( 40 | credentialExchangeRecord: CredentialExchangeRecordDo 41 | ) { 42 | logger.info( 43 | "type=credentialRecord time=${credentialExchangeRecord.time} credentialExchangeId=${credentialExchangeRecord.id} connectionId=${credentialExchangeRecord.connectionId} state=${credentialExchangeRecord.state}" 44 | ) 45 | 46 | if (credentialExchangeRecord.issued && credentialExchangeRecord.revocationRegistryId != null && credentialExchangeRecord.revocationRegistryIndex != null) { 47 | logger.info( 48 | "type=credentialRevocationMetadata time=${credentialExchangeRecord.time} credentialExchangeId=${credentialExchangeRecord.id} revocationRegistryId=${credentialExchangeRecord.revocationRegistryId} revocationIndex=${credentialExchangeRecord.revocationRegistryIndex}" 49 | ) 50 | } 51 | } 52 | 53 | override fun handleProofRequestRecord(proofExchangeRecord: ProofExchangeRecordDo) { 54 | logger.info( 55 | "type=presentationRecord time=${proofExchangeRecord.time} presentationExchangeId=${proofExchangeRecord.id} connectionId=${proofExchangeRecord.connectionId} state=${proofExchangeRecord.state} valid=${proofExchangeRecord.isValid} comment=${proofExchangeRecord.comment}" 56 | ) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/application/logger/ErrorLogger.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.application.logger 2 | 3 | import org.slf4j.Logger 4 | import org.slf4j.LoggerFactory 5 | import org.springframework.stereotype.Component 6 | import java.time.Instant 7 | 8 | @Component 9 | class ErrorLogger() { 10 | var logger: Logger = LoggerFactory.getLogger(ErrorLogger::class.java) 11 | 12 | fun reportTestFlowError(message: String) { 13 | logger.error( 14 | "type=test_flow_error message=\"$message\" time=${ 15 | Instant.now().toEpochMilli() 16 | }" 17 | ) 18 | } 19 | 20 | fun reportTestRunnerError(message: String) { 21 | logger.error( 22 | "type=test_runner_error message=\"$message\" time=${ 23 | Instant.now().toEpochMilli() 24 | }" 25 | ) 26 | } 27 | 28 | fun reportAriesEventError(message: String) { 29 | logger.error( 30 | "type=aries_event_error message=\"$message\" time=${ 31 | Instant.now().toEpochMilli() 32 | }" 33 | ) 34 | } 35 | 36 | fun reportAriesClientHttpRequestError( 37 | ariesClientType: String, 38 | httpMethod: String, 39 | uri: String, 40 | httpResponseCode: Int, 41 | durationInMs: Double, 42 | appErrorCode: String, 43 | message: String 44 | ) { 45 | val reformattedMessage = message.replace("\"", "'") 46 | 47 | logger.error( 48 | "type=aries_client_error ariesClientType=$ariesClientType httpMethod=$httpMethod uri=$uri httpCode=${httpResponseCode} durationInMs=${durationInMs} appErrorCode=$appErrorCode message=\"$reformattedMessage\" time=${ 49 | Instant.now().toEpochMilli() 50 | }" 51 | ) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/application/logger/HttpRequestLogger.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.application.logger 2 | 3 | import com.bka.ssi.generator.domain.services.IHttpRequestObserver 4 | import org.slf4j.Logger 5 | import org.slf4j.LoggerFactory 6 | import org.springframework.stereotype.Service 7 | 8 | @Service 9 | class HttpRequestLogger : IHttpRequestObserver { 10 | var logger: Logger = LoggerFactory.getLogger(HttpRequestLogger::class.java) 11 | 12 | override fun logHttpRequest( 13 | httpMethod: String, 14 | urlPath: String, 15 | httpResponseCode: Int, 16 | durationInMs: Double 17 | ) { 18 | logger.info("type=http_request request=${httpMethod}${urlPath} httpCode=${httpResponseCode} durationInMs=${durationInMs}") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/application/logger/WalletLogger.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.application.logger 2 | 3 | import com.bka.ssi.generator.domain.services.IWallet 4 | import org.slf4j.Logger 5 | import org.slf4j.LoggerFactory 6 | import org.springframework.scheduling.annotation.Scheduled 7 | import org.springframework.stereotype.Component 8 | import java.time.Instant 9 | 10 | @Component 11 | class WalletLogger(var wallet: IWallet) { 12 | var logger: Logger = LoggerFactory.getLogger(WalletLogger::class.java) 13 | 14 | @Scheduled(fixedRate = 10000) 15 | fun reportCurrentTime() { 16 | logger.info( 17 | "type=database_size sizeInBytes=${wallet.walletDatabaseSizeInBytes()} time=${ 18 | Instant.now().toEpochMilli() 19 | }" 20 | ) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/application/testflows/ConnectionFlow.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.application.testflows 2 | 3 | import com.bka.ssi.generator.application.testrunners.TestRunner 4 | import com.bka.ssi.generator.domain.objects.ConnectionRecordDo 5 | import com.bka.ssi.generator.domain.objects.CredentialExchangeRecordDo 6 | import com.bka.ssi.generator.domain.objects.ProofExchangeRecordDo 7 | import com.bka.ssi.generator.domain.services.IAriesClient 8 | import org.springframework.beans.factory.annotation.Qualifier 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty 10 | import org.springframework.stereotype.Service 11 | 12 | 13 | @Service 14 | @ConditionalOnProperty( 15 | name = ["test-flows.connection-flow.active"], 16 | matchIfMissing = false 17 | ) 18 | class ConnectionFlow( 19 | @Qualifier("IssuerVerifier") private val issuerVerifierAriesClient: IAriesClient, 20 | @Qualifier("Holder") holderAriesClients: List, 21 | ) : TestFlow(holderAriesClients) { 22 | 23 | protected companion object { 24 | var testRunner: TestRunner? = null 25 | } 26 | 27 | override fun initialize(testRunner: TestRunner) { 28 | logger.info("Initializing ConnectionFlow...") 29 | 30 | Companion.testRunner = testRunner 31 | 32 | testRunner.finishedInitialization() 33 | } 34 | 35 | override fun startIteration() { 36 | val connectionInvitation = issuerVerifierAriesClient.createConnectionInvitation("holder-acapy") 37 | 38 | nextHolderClient().receiveConnectionInvitation(connectionInvitation) 39 | } 40 | 41 | override fun handleConnectionRecord(connectionRecord: ConnectionRecordDo) { 42 | if (!connectionRecord.active) { 43 | return 44 | } 45 | 46 | logger.info("Established new connection") 47 | 48 | testRunner?.finishedIteration() 49 | } 50 | 51 | override fun handleCredentialExchangeRecord(credentialExchangeRecord: CredentialExchangeRecordDo) { 52 | throw NotImplementedError("The connection flow does not handle credential exchange records.") 53 | } 54 | 55 | override fun handleProofRequestRecord(proofExchangeRecord: ProofExchangeRecordDo) { 56 | throw NotImplementedError("The connection flow does not handle proof exchange records.") 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/application/testflows/CredentialIssuanceFlow.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.application.testflows 2 | 3 | import com.bka.ssi.generator.application.testrunners.TestRunner 4 | import com.bka.ssi.generator.domain.objects.* 5 | import com.bka.ssi.generator.domain.services.IAriesClient 6 | import org.springframework.beans.factory.annotation.Qualifier 7 | import org.springframework.beans.factory.annotation.Value 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty 9 | import org.springframework.stereotype.Service 10 | 11 | 12 | @Service 13 | @ConditionalOnProperty( 14 | name = ["test-flows.credential-issuance-flow.active"], 15 | matchIfMissing = false 16 | ) 17 | class CredentialIssuanceFlow( 18 | @Qualifier("IssuerVerifier") private val issuerVerifierAriesClient: IAriesClient, 19 | @Qualifier("Holder") holderAriesClients: List, 20 | @Value("\${test-flows.credential-issuance-flow.use-revocable-credentials}") private val useRevocableCredentials: Boolean, 21 | @Value("\${test-flows.credential-issuance-flow.revocation-registry-size}") private val revocationRegistrySize: Int, 22 | ) : TestFlow(holderAriesClients) { 23 | 24 | protected companion object { 25 | var credentialDefinitionId = "" 26 | var connectionId = "" 27 | var testRunner: TestRunner? = null 28 | } 29 | 30 | override fun initialize(testRunner: TestRunner) { 31 | logger.info("Initializing CredentialIssuanceFlow...") 32 | logger.info("use-revocable-credentials: $useRevocableCredentials") 33 | logger.info("revocation-registry-size: $revocationRegistrySize") 34 | 35 | Companion.testRunner = testRunner 36 | 37 | val credentialDefinition = issuerVerifierAriesClient.createSchemaAndCredentialDefinition( 38 | SchemaDo( 39 | listOf("first name", "last name"), 40 | "name", 41 | "1.0" 42 | ), 43 | useRevocableCredentials, 44 | revocationRegistrySize 45 | ) 46 | credentialDefinitionId = credentialDefinition.id 47 | 48 | initiateConnection() 49 | } 50 | 51 | override fun startIteration() { 52 | issueCredentialToConnection() 53 | logger.info("Sent Credential Offer") 54 | } 55 | 56 | private fun issueCredentialToConnection() { 57 | issuerVerifierAriesClient.issueCredentialToConnection( 58 | connectionId, 59 | CredentialDo( 60 | credentialDefinitionId, 61 | mapOf( 62 | "first name" to "Holder", 63 | "last name" to "Mustermann" 64 | ) 65 | ) 66 | ) 67 | } 68 | 69 | private fun initiateConnection() { 70 | val connectionInvitation = issuerVerifierAriesClient.createConnectionInvitation("holder-acapy") 71 | 72 | nextHolderClient().receiveConnectionInvitation(connectionInvitation) 73 | } 74 | 75 | override fun handleConnectionRecord(connectionRecord: ConnectionRecordDo) { 76 | if (!connectionRecord.active) { 77 | return 78 | } 79 | 80 | logger.info("Established new connection") 81 | 82 | connectionId = connectionRecord.connectionId 83 | 84 | testRunner?.finishedInitialization() 85 | } 86 | 87 | override fun handleCredentialExchangeRecord(credentialExchangeRecord: CredentialExchangeRecordDo) { 88 | if (!credentialExchangeRecord.issued) { 89 | return 90 | } 91 | 92 | logger.info("Issued credential") 93 | 94 | testRunner?.finishedIteration() 95 | } 96 | 97 | override fun handleProofRequestRecord(proofExchangeRecord: ProofExchangeRecordDo) { 98 | throw NotImplementedError("The issuer flow does not handle proof exchange records.") 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/application/testflows/EmptyFlow.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.application.testflows 2 | 3 | import com.bka.ssi.generator.application.testrunners.TestRunner 4 | import com.bka.ssi.generator.domain.objects.ConnectionRecordDo 5 | import com.bka.ssi.generator.domain.objects.CredentialExchangeRecordDo 6 | import com.bka.ssi.generator.domain.objects.ProofExchangeRecordDo 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty 8 | import org.springframework.stereotype.Service 9 | 10 | /** 11 | * This flow is used to debug the Load Generator. 12 | * (e.g. to better understand the behaviour of the ScheduledThreadPool) 13 | */ 14 | @Service 15 | @ConditionalOnProperty( 16 | name = ["test-flows.empty-flow.active"], 17 | matchIfMissing = false 18 | ) 19 | class EmptyFlow( 20 | ) : TestFlow( 21 | emptyList() 22 | ) { 23 | 24 | protected companion object { 25 | var testRunner: TestRunner? = null 26 | } 27 | 28 | override fun initialize(testRunner: TestRunner) { 29 | logger.info("Initializing EmptyFlow...") 30 | 31 | Companion.testRunner = testRunner 32 | 33 | testRunner.finishedInitialization() 34 | } 35 | 36 | override fun startIteration() { 37 | logger.info("Iteration started.") 38 | 39 | Thread.sleep(2000) 40 | 41 | logger.info("Iteration finished.") 42 | testRunner?.finishedIteration() 43 | } 44 | 45 | override fun handleConnectionRecord(connectionRecord: ConnectionRecordDo) { 46 | throw NotImplementedError("Not supported by Empty Flow.") 47 | } 48 | 49 | override fun handleCredentialExchangeRecord(credentialExchangeRecord: CredentialExchangeRecordDo) { 50 | throw NotImplementedError("Not supported by Empty Flow.") 51 | } 52 | 53 | override fun handleProofRequestRecord(proofExchangeRecord: ProofExchangeRecordDo) { 54 | throw NotImplementedError("Not supported by Empty Flow.") 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/application/testflows/FullFlow.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.application.testflows 2 | 3 | import com.bka.ssi.generator.application.logger.ErrorLogger 4 | import com.bka.ssi.generator.application.testrunners.TestRunner 5 | import com.bka.ssi.generator.domain.objects.* 6 | import com.bka.ssi.generator.domain.services.IAriesClient 7 | import org.springframework.beans.factory.annotation.Qualifier 8 | import org.springframework.beans.factory.annotation.Value 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty 10 | import org.springframework.stereotype.Service 11 | import java.time.Instant 12 | import java.util.* 13 | 14 | 15 | @Service 16 | @ConditionalOnProperty( 17 | name = ["test-flows.full-flow.active"], 18 | matchIfMissing = false 19 | ) 20 | class FullFlow( 21 | @Qualifier("IssuerVerifier") private val issuerVerifierAriesClient: IAriesClient, 22 | @Qualifier("Holder") holderAriesClients: List, 23 | @Value("\${test-flows.full-flow.use-revocable-credentials}") private val useRevocableCredentials: Boolean, 24 | @Value("\${test-flows.full-flow.revocation-registry-size}") private val revocationRegistrySize: Int, 25 | @Value("\${test-flows.full-flow.check-non-revoked}") private val checkNonRevoked: Boolean, 26 | @Value("\${test-flows.full-flow.revoke-credentials}") private val revokeCredentials: Boolean, 27 | @Value("\${test-flows.full-flow.credential-revocation-batch-size}") private val credentialRevocationBatchSize: Int, 28 | private val errorLogger: ErrorLogger, 29 | ) : TestFlow( 30 | holderAriesClients 31 | ) { 32 | 33 | protected companion object { 34 | const val SESSION_ID_CREDENTIAL_ATTRIBUTE_NAME = "sessionId" 35 | 36 | var numberOfBatchedCredentialRevocations = 0 37 | var credentialDefinitionId = "" 38 | var testRunner: TestRunner? = null 39 | } 40 | 41 | override fun initialize(testRunner: TestRunner) { 42 | logger.info("Initializing FullFlow...") 43 | logger.info("use-revocable-credentials: $useRevocableCredentials") 44 | logger.info("revocation-registry-size: $revocationRegistrySize") 45 | logger.info("check-non-revoked: $checkNonRevoked") 46 | logger.info("revoke-credentials: $revokeCredentials") 47 | logger.info("credential-revocation-batch-size: $credentialRevocationBatchSize") 48 | 49 | Companion.testRunner = testRunner 50 | 51 | val credentialDefinition = issuerVerifierAriesClient.createSchemaAndCredentialDefinition( 52 | SchemaDo( 53 | listOf(SESSION_ID_CREDENTIAL_ATTRIBUTE_NAME), 54 | "test-credential", 55 | "1.${Date().time}" 56 | ), 57 | useRevocableCredentials, 58 | revocationRegistrySize 59 | ) 60 | credentialDefinitionId = credentialDefinition.id 61 | 62 | testRunner.finishedInitialization() 63 | } 64 | 65 | override fun startIteration() { 66 | initiateConnection() 67 | } 68 | 69 | private fun initiateConnection() { 70 | val connectionInvitation = issuerVerifierAriesClient.createConnectionInvitation("holder-acapy") 71 | 72 | nextHolderClient().receiveConnectionInvitation(connectionInvitation) 73 | } 74 | 75 | override fun handleConnectionRecord(connectionRecord: ConnectionRecordDo) { 76 | if (!connectionRecord.active) { 77 | return 78 | } 79 | 80 | issuerVerifierAriesClient.issueCredentialToConnection( 81 | connectionRecord.connectionId, 82 | CredentialDo( 83 | credentialDefinitionId, 84 | mapOf( 85 | SESSION_ID_CREDENTIAL_ATTRIBUTE_NAME to UUID.randomUUID().toString() 86 | ) 87 | ) 88 | ) 89 | 90 | logger.info("Issued credential to new connection") 91 | } 92 | 93 | override fun handleCredentialExchangeRecord(credentialExchangeRecord: CredentialExchangeRecordDo) { 94 | if (!credentialExchangeRecord.issued) { 95 | return 96 | } 97 | 98 | sendProofRequestToConnection( 99 | credentialExchangeRecord.sessionId, 100 | credentialExchangeRecord.connectionId, 101 | ProofExchangeCommentDo( 102 | true, 103 | credentialExchangeRecord.sessionId, 104 | credentialExchangeRecord.revocationRegistryId, 105 | credentialExchangeRecord.revocationRegistryIndex 106 | ) 107 | ) 108 | 109 | logger.info("Sent proof request") 110 | } 111 | 112 | private fun sendProofRequestToConnection(sessionId: String, connectionId: String, comment: ProofExchangeCommentDo) { 113 | issuerVerifierAriesClient.sendProofRequestToConnection( 114 | connectionId, 115 | ProofRequestDo( 116 | Instant.now().toEpochMilli(), 117 | Instant.now().toEpochMilli(), 118 | listOf( 119 | CredentialRequestDo( 120 | listOf(SESSION_ID_CREDENTIAL_ATTRIBUTE_NAME), 121 | credentialDefinitionId, 122 | AttributeValueRestrictionDo( 123 | SESSION_ID_CREDENTIAL_ATTRIBUTE_NAME, 124 | sessionId 125 | ) 126 | ) 127 | ) 128 | ), 129 | true, 130 | comment 131 | ) 132 | } 133 | 134 | override fun handleProofRequestRecord(proofExchangeRecord: ProofExchangeRecordDo) { 135 | if (!proofExchangeRecord.isVerified) { 136 | return 137 | } 138 | 139 | if (proofExchangeRecord.comment.shouldBeValid) { 140 | if (!proofExchangeRecord.isValid) { 141 | logger.error("Received invalid proof presentation but expected a valid proof presentation") 142 | return 143 | } 144 | 145 | logger.info("Received valid proof presentation") 146 | 147 | if (!revokeCredentials) { 148 | testRunner?.finishedIteration() 149 | return 150 | } 151 | 152 | revokeCredential( 153 | proofExchangeRecord.connectionId, 154 | proofExchangeRecord.comment.sessionId, 155 | proofExchangeRecord.comment.revocationRegistryId, 156 | proofExchangeRecord.comment.revocationRegistryIndex 157 | ) 158 | } 159 | 160 | if (!proofExchangeRecord.comment.shouldBeValid) { 161 | if (proofExchangeRecord.isValid) { 162 | logger.error("Credential has not been revoked") 163 | return 164 | } 165 | 166 | logger.info("Credential has been successfully revoked") 167 | testRunner?.finishedIteration() 168 | } 169 | } 170 | 171 | private fun revokeCredential( 172 | connectionId: String, 173 | sessionId: String, 174 | revocationRegistryId: String?, 175 | revocationRegistryIndex: String? 176 | ) { 177 | if (revocationRegistryId == null || revocationRegistryIndex == null) { 178 | errorLogger.reportTestFlowError("Tried to revoke a credential but revocationRegistryId and/or revocationRegistryIndex is missing.") 179 | return 180 | } 181 | 182 | val publishRevocations = ++numberOfBatchedCredentialRevocations >= credentialRevocationBatchSize 183 | if (publishRevocations) { 184 | revokeCredentialAndPublishRevocationsAndTestCredentialIsRevoked( 185 | connectionId, 186 | sessionId, 187 | revocationRegistryId, 188 | revocationRegistryIndex 189 | ) 190 | return 191 | } 192 | 193 | issuerVerifierAriesClient.revokeCredentialWithoutPublishing( 194 | CredentialRevocationRegistryRecordDo( 195 | revocationRegistryId, 196 | revocationRegistryIndex 197 | ) 198 | ) 199 | 200 | testRunner?.finishedIteration() 201 | } 202 | 203 | private fun revokeCredentialAndPublishRevocationsAndTestCredentialIsRevoked( 204 | connectionId: String, 205 | sessionId: String, 206 | revocationRegistryId: String, 207 | revocationRegistryIndex: String 208 | ) { 209 | // Reset the revocation counter to ensure that other threads do not attempt to publish revocations too, 210 | // while the current revocation publishing is still being processed. This may result in many parallel 211 | // revocation publication processes that will block each other. 212 | numberOfBatchedCredentialRevocations = 0 213 | 214 | issuerVerifierAriesClient.revokeCredentialAndPublishRevocations( 215 | CredentialRevocationRegistryRecordDo( 216 | revocationRegistryId, 217 | revocationRegistryIndex 218 | ) 219 | ) 220 | 221 | sendProofRequestToConnection( 222 | sessionId, 223 | connectionId, 224 | ProofExchangeCommentDo( 225 | false, 226 | sessionId, 227 | revocationRegistryId, 228 | revocationRegistryIndex 229 | ) 230 | ) 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/application/testflows/IssuerFlow.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.application.testflows 2 | 3 | import com.bka.ssi.generator.application.testrunners.TestRunner 4 | import com.bka.ssi.generator.domain.objects.* 5 | import com.bka.ssi.generator.domain.services.IAriesClient 6 | import org.springframework.beans.factory.annotation.Qualifier 7 | import org.springframework.beans.factory.annotation.Value 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty 9 | import org.springframework.stereotype.Service 10 | 11 | 12 | @Service 13 | @ConditionalOnProperty( 14 | name = ["test-flows.issuer-flow.active"], 15 | matchIfMissing = false 16 | ) 17 | class IssuerFlow( 18 | @Qualifier("IssuerVerifier") private val issuerVerifierAriesClient: IAriesClient, 19 | @Qualifier("Holder") holderAriesClients: List, 20 | @Value("\${test-flows.issuer-flow.use-revocable-credentials}") private val useRevocableCredentials: Boolean, 21 | @Value("\${test-flows.issuer-flow.revocation-registry-size}") private val revocationRegistrySize: Int, 22 | ) : TestFlow(holderAriesClients) { 23 | 24 | protected companion object { 25 | var credentialDefinitionId = "" 26 | var testRunner: TestRunner? = null 27 | } 28 | 29 | override fun initialize(testRunner: TestRunner) { 30 | logger.info("Initializing IssuerFlow...") 31 | logger.info("use-revocable-credentials: $useRevocableCredentials") 32 | logger.info("revocation-registry-size: $revocationRegistrySize") 33 | 34 | Companion.testRunner = testRunner 35 | 36 | val credentialDefinition = issuerVerifierAriesClient.createSchemaAndCredentialDefinition( 37 | SchemaDo( 38 | listOf("first name", "last name"), 39 | "name", 40 | "1.0" 41 | ), 42 | useRevocableCredentials, 43 | revocationRegistrySize 44 | ) 45 | credentialDefinitionId = credentialDefinition.id 46 | 47 | testRunner.finishedInitialization() 48 | } 49 | 50 | override fun startIteration() { 51 | initiateConnection() 52 | } 53 | 54 | private fun initiateConnection() { 55 | val connectionInvitation = issuerVerifierAriesClient.createConnectionInvitation("holder-acapy") 56 | 57 | nextHolderClient().receiveConnectionInvitation(connectionInvitation) 58 | } 59 | 60 | override fun handleConnectionRecord(connectionRecord: ConnectionRecordDo) { 61 | if (!connectionRecord.active) { 62 | return 63 | } 64 | 65 | logger.info("Established new connection") 66 | 67 | issuerVerifierAriesClient.issueCredentialToConnection( 68 | connectionRecord.connectionId, 69 | CredentialDo( 70 | credentialDefinitionId, 71 | mapOf( 72 | "first name" to "Holder", 73 | "last name" to "Mustermann" 74 | ) 75 | ) 76 | ) 77 | 78 | } 79 | 80 | override fun handleCredentialExchangeRecord(credentialExchangeRecord: CredentialExchangeRecordDo) { 81 | if (!credentialExchangeRecord.issued) { 82 | return 83 | } 84 | 85 | logger.info("Issued credential") 86 | 87 | testRunner?.finishedIteration() 88 | } 89 | 90 | override fun handleProofRequestRecord(proofExchangeRecord: ProofExchangeRecordDo) { 91 | throw NotImplementedError("The issuer flow does not handle proof exchange records.") 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/application/testflows/ProofRequestFlow.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.application.testflows 2 | 3 | import com.bka.ssi.generator.application.testrunners.TestRunner 4 | import com.bka.ssi.generator.domain.objects.* 5 | import com.bka.ssi.generator.domain.services.IAriesClient 6 | import org.springframework.beans.factory.annotation.Qualifier 7 | import org.springframework.beans.factory.annotation.Value 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty 9 | import org.springframework.stereotype.Service 10 | import java.time.Instant 11 | 12 | 13 | @Service 14 | @ConditionalOnProperty( 15 | name = ["test-flows.proof-request-flow.active"], 16 | matchIfMissing = false 17 | ) 18 | class ProofRequestFlow( 19 | @Qualifier("IssuerVerifier") private val issuerVerifierAriesClient: IAriesClient, 20 | @Qualifier("Holder") holderAriesClients: List, 21 | @Value("\${test-flows.proof-request-flow.revocation-registry-size}") private val revocationRegistrySize: Int, 22 | @Value("\${test-flows.proof-request-flow.check-non-revoked}") private val checkNonRevoked: Boolean, 23 | ) : TestFlow(holderAriesClients) { 24 | 25 | protected companion object { 26 | var credentialDefinitionId = "" 27 | var connectionId = "" 28 | var testRunner: TestRunner? = null 29 | } 30 | 31 | override fun initialize(testRunner: TestRunner) { 32 | logger.info("Initializing ProofRequestFlow...") 33 | logger.info("revocation-registry-size: $revocationRegistrySize") 34 | logger.info("check-non-revoked: $checkNonRevoked") 35 | 36 | Companion.testRunner = testRunner 37 | 38 | val credentialDefinition = issuerVerifierAriesClient.createSchemaAndCredentialDefinition( 39 | SchemaDo( 40 | listOf("first name", "last name"), 41 | "name", 42 | "1.0" 43 | ), 44 | checkNonRevoked, 45 | revocationRegistrySize 46 | ) 47 | credentialDefinitionId = credentialDefinition.id 48 | 49 | initiateConnection() 50 | } 51 | 52 | override fun startIteration() { 53 | sendProofRequestToConnection() 54 | logger.info("Sent proof request") 55 | } 56 | 57 | private fun sendProofRequestToConnection() { 58 | issuerVerifierAriesClient.sendProofRequestToConnection( 59 | connectionId, 60 | ProofRequestDo( 61 | Instant.now().toEpochMilli(), 62 | Instant.now().toEpochMilli(), 63 | listOf( 64 | CredentialRequestDo( 65 | listOf("first name", "last name"), 66 | credentialDefinitionId, 67 | AttributeValueRestrictionDo( 68 | "first name", 69 | "bob" 70 | ) 71 | ) 72 | ) 73 | ), 74 | checkNonRevoked, 75 | ProofExchangeCommentDo(false, "credential", null, null) 76 | ) 77 | } 78 | 79 | private fun initiateConnection() { 80 | val connectionInvitation = issuerVerifierAriesClient.createConnectionInvitation("holder-acapy") 81 | 82 | nextHolderClient().receiveConnectionInvitation(connectionInvitation) 83 | } 84 | 85 | override fun handleConnectionRecord(connectionRecord: ConnectionRecordDo) { 86 | if (!connectionRecord.active) { 87 | return 88 | } 89 | 90 | logger.info("Established new connection") 91 | connectionId = connectionRecord.connectionId 92 | 93 | issuerVerifierAriesClient.issueCredentialToConnection( 94 | connectionRecord.connectionId, 95 | CredentialDo( 96 | credentialDefinitionId, 97 | mapOf( 98 | "first name" to "Holder", 99 | "last name" to "Mustermann" 100 | ) 101 | ) 102 | ) 103 | } 104 | 105 | override fun handleCredentialExchangeRecord(credentialExchangeRecord: CredentialExchangeRecordDo) { 106 | if (!credentialExchangeRecord.issued) { 107 | return 108 | } 109 | 110 | testRunner?.finishedInitialization() 111 | } 112 | 113 | override fun handleProofRequestRecord(proofExchangeRecord: ProofExchangeRecordDo) { 114 | if (!proofExchangeRecord.isVerified) { 115 | return 116 | } 117 | 118 | if (!proofExchangeRecord.isVerified) { 119 | logger.error("Received invalid proof presentation but expected a valid proof presentation") 120 | return 121 | } 122 | 123 | logger.info("Received valid proof presentation") 124 | 125 | testRunner?.finishedIteration() 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/application/testflows/TestFlow.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.application.testflows 2 | 3 | import com.bka.ssi.generator.application.testrunners.TestRunner 4 | import com.bka.ssi.generator.domain.services.IAriesClient 5 | import com.bka.ssi.generator.domain.services.IAriesObserver 6 | import org.slf4j.Logger 7 | import org.slf4j.LoggerFactory 8 | 9 | abstract class TestFlow( 10 | private var holderAriesClients: List 11 | ) : IAriesObserver { 12 | 13 | private companion object { 14 | var indexOfNextHolder = 0 15 | } 16 | 17 | protected var logger: Logger = LoggerFactory.getLogger(TestFlow::class.java) 18 | 19 | abstract fun initialize(testRunner: TestRunner) 20 | abstract fun startIteration() 21 | 22 | protected fun nextHolderClient(): IAriesClient { 23 | val holderClient = holderAriesClients[indexOfNextHolder] 24 | 25 | indexOfNextHolder++ 26 | if (indexOfNextHolder == holderAriesClients.size) indexOfNextHolder = 0 27 | 28 | return holderClient 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/application/testrunners/ConstantLoadTestRunner.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.application.testrunners 2 | 3 | import com.bka.ssi.generator.application.logger.ErrorLogger 4 | import com.bka.ssi.generator.application.testflows.TestFlow 5 | import org.springframework.beans.factory.annotation.Value 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty 7 | import org.springframework.stereotype.Service 8 | import java.util.concurrent.ExecutorService 9 | import java.util.concurrent.Executors 10 | import java.util.concurrent.ScheduledFuture 11 | import java.util.concurrent.TimeUnit 12 | 13 | 14 | @Service 15 | @ConditionalOnProperty( 16 | name = ["test-runners.constant-load-runner.active"], 17 | matchIfMissing = false 18 | ) 19 | class ConstantLoadTestRunner( 20 | private val testFlow: TestFlow, 21 | private val errorLogger: ErrorLogger, 22 | @Value("\${test-runners.constant-load-runner.number-of-total-iterations}") val numberOfTotalIterations: Int, 23 | @Value("\${test-runners.constant-load-runner.number-of-iterations-per-minute}") val numberOfIterationsPerMinute: Int, 24 | @Value("\${test-runners.constant-load-runner.thread-pool-size}") val threadPoolSize: Int 25 | ) : TestRunner( 26 | ) { 27 | 28 | lateinit var loadScheduler: ScheduledFuture<*> 29 | lateinit var fixedThreadPoolExecutor: ExecutorService 30 | 31 | protected companion object { 32 | var numberOfIterationsStarted = 0 33 | var numberOfIterationsFinished = 0 34 | } 35 | 36 | override fun run() { 37 | testFlow.initialize(this) 38 | } 39 | 40 | override fun finishedInitialization() { 41 | logger.info("Starting ConstantLoadTestRunner...") 42 | logger.info("Number of Iterations: $numberOfTotalIterations") 43 | logger.info("Number of Iterations per Minute: $numberOfIterationsPerMinute") 44 | logger.info("Thread pool size: $threadPoolSize") 45 | logger.info("Expected running duration in minutes: ${numberOfTotalIterations / numberOfIterationsPerMinute}") 46 | 47 | fixedThreadPoolExecutor = Executors.newFixedThreadPool(threadPoolSize) 48 | 49 | val executor = Executors.newScheduledThreadPool(4) 50 | loadScheduler = executor.scheduleAtFixedRate( 51 | Runnable { 52 | try { 53 | fixedThreadPoolExecutor.submit { startNewIteration() } 54 | } catch (exception: Exception) { 55 | errorLogger.reportTestRunnerError("The 'loadScheduler' of the 'ConstantLoadTestRunner' caught an error: ${exception.message} [${exception.printStackTrace()}]") 56 | exception.printStackTrace(); 57 | } 58 | }, 59 | 0, 60 | 60000L / numberOfIterationsPerMinute, 61 | TimeUnit.MILLISECONDS 62 | ) 63 | } 64 | 65 | private fun startNewIteration() { 66 | numberOfIterationsStarted++ 67 | logger.info("Started $numberOfIterationsStarted of $numberOfTotalIterations iterations") 68 | testFlow.startIteration() 69 | } 70 | 71 | override fun finishedIteration() { 72 | numberOfIterationsFinished++ 73 | logger.info("Finished ${numberOfIterationsFinished} of $numberOfTotalIterations iterations") 74 | 75 | if (numberOfIterationsFinished >= numberOfTotalIterations) { 76 | loadScheduler.cancel(true) 77 | fixedThreadPoolExecutor.shutdownNow() 78 | } 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/application/testrunners/IncreasingLoadTestRunner.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.application.testrunners 2 | 3 | import com.bka.ssi.generator.application.logger.ErrorLogger 4 | import com.bka.ssi.generator.application.testflows.TestFlow 5 | import org.springframework.beans.factory.annotation.Value 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty 7 | import org.springframework.stereotype.Service 8 | import java.util.concurrent.ExecutorService 9 | import java.util.concurrent.Executors 10 | import java.util.concurrent.ScheduledFuture 11 | import java.util.concurrent.TimeUnit 12 | 13 | 14 | @Service 15 | @ConditionalOnProperty( 16 | name = ["test-runners.increasing-load-runner.active"], 17 | matchIfMissing = false 18 | ) 19 | class IncreasingLoadTestRunner( 20 | private val testFlow: TestFlow, 21 | private val errorLogger: ErrorLogger, 22 | @Value("\${test-runners.increasing-load-runner.peak-duration-in-minutes}") val peakDurationInMinutes: Long, 23 | @Value("\${test-runners.increasing-load-runner.sleep-between-peaks-in-minutes}") val sleepBetweenPeaksInMinutes: Long, 24 | @Value("\${test-runners.increasing-load-runner.initial-number-of-iterations-per-minute}") val initialNumberOfIterationsPerMinute: Int, 25 | @Value("\${test-runners.increasing-load-runner.final-number-of-iterations-per-minute}") val finalNumberOfIterationsPerMinute: Int, 26 | @Value("\${test-runners.increasing-load-runner.step-size-of-iterations-per-minute}") val stepSizeOfIterationsPerMinute: Int, 27 | @Value("\${test-runners.increasing-load-runner.thread-pool-size}") val threadPoolSize: Int 28 | ) : TestRunner( 29 | ) { 30 | 31 | lateinit var loadScheduler: ScheduledFuture<*> 32 | lateinit var startScheduler: ScheduledFuture<*> 33 | lateinit var killScheduler: ScheduledFuture<*> 34 | lateinit var fixedThreadPoolExecutor: ExecutorService 35 | 36 | protected companion object { 37 | var numberOfIterationsStartedInCurrentPeak = 0 38 | var numberOfIterationsFinishedInCurrentPeak = 0 39 | var expectedNumberOfIterationsInCurrentPeak = 0L 40 | var totalNumberOfPeaks = 0 41 | var totalNumberOfPeaksStarted = 0 42 | } 43 | 44 | override fun run() { 45 | testFlow.initialize(this) 46 | } 47 | 48 | override fun finishedInitialization() { 49 | logger.info("Starting IncreasingLoadTestRunner...") 50 | logger.info("Peak duration in minutes: $peakDurationInMinutes") 51 | logger.info("Sleep between peaks in minutes: $sleepBetweenPeaksInMinutes") 52 | logger.info("Initial number of iterations per minute: $initialNumberOfIterationsPerMinute") 53 | logger.info("Final number of iterations per minute: $finalNumberOfIterationsPerMinute") 54 | logger.info("Step size of iterations per minute: $stepSizeOfIterationsPerMinute") 55 | logger.info("Thread pool size: $threadPoolSize") 56 | 57 | totalNumberOfPeaks = 58 | ((finalNumberOfIterationsPerMinute - initialNumberOfIterationsPerMinute) / stepSizeOfIterationsPerMinute) + 1 59 | 60 | 61 | val numberOfMinutesSleepingBetweenPeaks = totalNumberOfPeaks * sleepBetweenPeaksInMinutes 62 | val numberOfMinutesExecutingLoad = totalNumberOfPeaks * peakDurationInMinutes 63 | logger.info("Test will finish in ${numberOfMinutesSleepingBetweenPeaks + numberOfMinutesExecutingLoad} minutes") 64 | 65 | fixedThreadPoolExecutor = Executors.newFixedThreadPool(threadPoolSize) 66 | 67 | val startExecutor = Executors.newScheduledThreadPool(4) 68 | startScheduler = startExecutor.scheduleWithFixedDelay( 69 | Runnable { 70 | try { 71 | startNewPeakLoad() 72 | } catch (exception: Exception) { 73 | errorLogger.reportTestRunnerError("The 'startScheduler' of the 'IncreasingLoadTestRunner' caught an error: ${exception.message} [${exception.printStackTrace()}]") 74 | exception.printStackTrace(); 75 | } 76 | }, 77 | 0, 78 | peakDurationInMinutes + sleepBetweenPeaksInMinutes, 79 | TimeUnit.MINUTES 80 | ) 81 | 82 | val killExecutor = Executors.newScheduledThreadPool(4) 83 | killScheduler = killExecutor.scheduleWithFixedDelay( 84 | Runnable { 85 | try { 86 | killCurrentPeakLoad() 87 | } catch (exception: Exception) { 88 | errorLogger.reportTestRunnerError("The 'killScheduler' of the 'IncreasingLoadTestRunner' caught an error: ${exception.message} [${exception.printStackTrace()}]") 89 | exception.printStackTrace(); 90 | } 91 | }, 92 | peakDurationInMinutes, 93 | peakDurationInMinutes + sleepBetweenPeaksInMinutes, 94 | TimeUnit.MINUTES 95 | ) 96 | } 97 | 98 | private fun startNewPeakLoad() { 99 | if (totalNumberOfPeaksStarted >= totalNumberOfPeaks) { 100 | startScheduler.cancel(true) 101 | return 102 | } 103 | 104 | val currentNumberOfIterationsPerMinute = 105 | initialNumberOfIterationsPerMinute + stepSizeOfIterationsPerMinute * totalNumberOfPeaksStarted 106 | 107 | numberOfIterationsStartedInCurrentPeak = 0 108 | numberOfIterationsFinishedInCurrentPeak = 0 109 | expectedNumberOfIterationsInCurrentPeak = currentNumberOfIterationsPerMinute * peakDurationInMinutes 110 | 111 | val loadExecutor = Executors.newScheduledThreadPool(4) 112 | loadScheduler = loadExecutor.scheduleAtFixedRate( 113 | Runnable { 114 | try { 115 | fixedThreadPoolExecutor.submit { startNewIteration() } 116 | } catch (exception: Exception) { 117 | errorLogger.reportTestRunnerError("The 'loadScheduler' of the 'IncreasingLoadTestRunner' caught an error: ${exception.message} [${exception.printStackTrace()}]") 118 | exception.printStackTrace(); 119 | } 120 | }, 121 | 0, 122 | 60000L / currentNumberOfIterationsPerMinute, 123 | TimeUnit.MILLISECONDS 124 | ) 125 | 126 | totalNumberOfPeaksStarted++ 127 | } 128 | 129 | private fun killCurrentPeakLoad() { 130 | loadScheduler.cancel(true) 131 | 132 | if (totalNumberOfPeaksStarted >= totalNumberOfPeaks) { 133 | killScheduler.cancel(true) 134 | fixedThreadPoolExecutor.shutdownNow() 135 | } 136 | } 137 | 138 | private fun startNewIteration() { 139 | numberOfIterationsStartedInCurrentPeak++ 140 | logger.info("Started $numberOfIterationsStartedInCurrentPeak of $expectedNumberOfIterationsInCurrentPeak iterations") 141 | testFlow.startIteration() 142 | } 143 | 144 | override fun finishedIteration() { 145 | numberOfIterationsFinishedInCurrentPeak++ 146 | logger.info("Finished $numberOfIterationsFinishedInCurrentPeak of $expectedNumberOfIterationsInCurrentPeak iterations") 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/application/testrunners/MaxParallelIterationsTestRunner.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.application.testrunners 2 | 3 | import com.bka.ssi.generator.application.testflows.TestFlow 4 | import org.springframework.beans.factory.annotation.Value 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty 6 | import org.springframework.stereotype.Service 7 | 8 | @Service 9 | @ConditionalOnProperty( 10 | name = ["test-runners.max-parallel-iterations-runner.active"], 11 | matchIfMissing = false 12 | ) 13 | class MaxParallelIterationsTestRunner( 14 | private val testFlow: TestFlow, 15 | @Value("\${test-runners.max-parallel-iterations-runner.number-of-total-iterations}") val numberOfTotalIterations: Int, 16 | @Value("\${test-runners.max-parallel-iterations-runner.number-of-parallel-iterations}") val numberOfParallelIterations: Int, 17 | ) : TestRunner( 18 | ) { 19 | 20 | protected companion object { 21 | var numberOfIterationsStarted = 0 22 | var numberOfIterationsFinished = 0 23 | } 24 | 25 | override fun run() { 26 | testFlow.initialize(this) 27 | } 28 | 29 | override fun finishedInitialization() { 30 | logger.info("Starting MaxParallelIterationsTestRunner...") 31 | logger.info("Number of Iterations: $numberOfTotalIterations") 32 | logger.info("Number of Parallel Iterations: $numberOfParallelIterations") 33 | 34 | for (i in 0 until numberOfParallelIterations) { 35 | numberOfIterationsStarted++ 36 | logger.info("Started $numberOfIterationsStarted of $numberOfTotalIterations iterations") 37 | testFlow.startIteration() 38 | } 39 | } 40 | 41 | override fun finishedIteration() { 42 | numberOfIterationsFinished++ 43 | logger.info("Finished ${numberOfIterationsFinished} of $numberOfTotalIterations iterations") 44 | 45 | if (numberOfIterationsFinished < numberOfTotalIterations) { 46 | testFlow.startIteration() 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/application/testrunners/TestRunner.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.application.testrunners 2 | 3 | import org.slf4j.Logger 4 | import org.slf4j.LoggerFactory 5 | 6 | abstract class TestRunner { 7 | 8 | protected var logger: Logger = LoggerFactory.getLogger(TestRunner::class.java) 9 | 10 | abstract fun run() 11 | abstract fun finishedInitialization() 12 | abstract fun finishedIteration() 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/config/AcaPyConfig.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * * Copyright 2022 Bundesrepublik Deutschland 4 | * * 5 | * * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * * you may not use this file except in compliance with the License. 7 | * * You may obtain a copy of the License at 8 | * * 9 | * * http://www.apache.org/licenses/LICENSE-2.0 10 | * * 11 | * * Unless required by applicable law or agreed to in writing, software 12 | * * distributed under the License is distributed on an "AS IS" BASIS, 13 | * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * * See the License for the specific language governing permissions and 15 | * * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.bka.ssi.generator.config 20 | 21 | import com.bka.ssi.generator.agents.acapy.AcaPyAriesClient 22 | import com.bka.ssi.generator.agents.acapy.AcaPyRequestLoggerInterceptor 23 | import com.bka.ssi.generator.application.logger.AriesClientLogger 24 | import com.bka.ssi.generator.application.logger.ErrorLogger 25 | import com.bka.ssi.generator.domain.services.IAriesClient 26 | import com.bka.ssi.generator.domain.services.IHttpRequestObserver 27 | import okhttp3.OkHttpClient 28 | import org.hyperledger.acy_py.generated.model.DID 29 | import org.hyperledger.acy_py.generated.model.DIDCreate 30 | import org.hyperledger.acy_py.generated.model.DIDCreateOptions 31 | import org.hyperledger.aries.AriesClient 32 | import org.hyperledger.aries.api.multitenancy.* 33 | import org.slf4j.Logger 34 | import org.slf4j.LoggerFactory 35 | import org.springframework.beans.factory.annotation.Value 36 | import org.springframework.context.annotation.Bean 37 | import org.springframework.context.annotation.Configuration 38 | import org.springframework.web.client.RestTemplate 39 | import org.springframework.web.client.postForObject 40 | import java.util.* 41 | import java.util.concurrent.TimeUnit 42 | 43 | @Configuration 44 | class AcaPyConfig( 45 | private val errorLogger: ErrorLogger, 46 | private val ariesClientLogger: AriesClientLogger, 47 | @Value("\${issuer-verifier.acapy.api-key}") private val issuerVerifierAcaPyApiKey: String?, 48 | @Value("\${issuer-verifier.acapy.url}") private val issuerVerifierAcaPyUrl: String?, 49 | @Value("\${issuer-verifier.acapy.http-timeout-in-seconds}") private val issuerVerifierAcaPyHttpTimeoutInSeconds: Long, 50 | @Value("\${issuer-verifier.multitenancy.enabled}") private val issuerVerifierMultitenancyEnabled: Boolean, 51 | @Value("\${issuer-verifier.multitenancy.wallet-type}") private val issuerVerifierMultitenancyWalletType: String, 52 | @Value("\${issuer-verifier.multitenancy.register-did-endpoint}") private val registerDidEndpoint: String, 53 | @Value("\${issuer-verifier.multitenancy.webhook-endpoint-url}") private val webhookEndpointUrl: String, 54 | @Value("\${issuer-verifier.multitenancy.sub-wallet-name}") private val subWalletName: String, 55 | @Value("\${holder.acapy.api-key}") private val holderAcaPyApiKey: String?, 56 | @Value("\${holder.acapy.urls}") private val holderAcaPyUrls: Array, 57 | @Value("\${holder.acapy.http-timeout-in-seconds}") private val holderAcapyHttpTimeoutInSeconds: Long 58 | ) { 59 | var logger: Logger = LoggerFactory.getLogger(AcaPyConfig::class.java) 60 | 61 | @Bean(name = ["IssuerVerifier"]) 62 | fun issuerVerifierAriesClient( 63 | handler: IHttpRequestObserver, 64 | errorLogger: ErrorLogger 65 | ): IAriesClient? { 66 | if (issuerVerifierAcaPyUrl == null) { 67 | logger.error("Unable to establish connection to Issuer/Verifier AcaPy. Issuer/Verifier AcaPy URL not configured.") 68 | return null 69 | } 70 | 71 | if (issuerVerifierMultitenancyEnabled) { 72 | return issuerVerifierClientWithMultitenancyEnabled(issuerVerifierAcaPyUrl, handler) 73 | } 74 | 75 | return issuerVerifierClientWithMultitenancyDisabled(issuerVerifierAcaPyUrl, handler) 76 | } 77 | 78 | private fun issuerVerifierClientWithMultitenancyDisabled( 79 | issuerVerifierAcaPyUrl: String, 80 | handler: IHttpRequestObserver 81 | ): IAriesClient { 82 | val issuerVerifierAcaPyClient = 83 | buildAcaPyAriesClient( 84 | AcaPyRequestLoggerInterceptor("IssuerVerifier", handler, errorLogger), 85 | issuerVerifierAcaPyUrl, 86 | issuerVerifierAcaPyHttpTimeoutInSeconds, 87 | issuerVerifierAcaPyApiKey, 88 | null 89 | ) 90 | 91 | return AcaPyAriesClient(issuerVerifierAcaPyClient, ariesClientLogger) 92 | } 93 | 94 | private fun issuerVerifierClientWithMultitenancyEnabled( 95 | issuerVerifierAcaPyUrl: String, 96 | handler: IHttpRequestObserver, 97 | ): IAriesClient { 98 | val baseWalletAriesClient = 99 | buildAcaPyAriesClient( 100 | AcaPyRequestLoggerInterceptor("IssuerVerifierBaseWallet", handler, errorLogger), 101 | issuerVerifierAcaPyUrl, 102 | issuerVerifierAcaPyHttpTimeoutInSeconds, 103 | issuerVerifierAcaPyApiKey, 104 | null 105 | ) 106 | 107 | val subWalletToken = createNewSubWallet(baseWalletAriesClient) 108 | 109 | val subWalletIssuerVerifierAcaPyClient = 110 | buildAcaPyAriesClient( 111 | AcaPyRequestLoggerInterceptor("IssuerVerifierSubWallet", handler, errorLogger), 112 | issuerVerifierAcaPyUrl, 113 | issuerVerifierAcaPyHttpTimeoutInSeconds, 114 | issuerVerifierAcaPyApiKey, 115 | subWalletToken 116 | ) 117 | 118 | createAndRegisterNewPublicDid(subWalletIssuerVerifierAcaPyClient) 119 | 120 | return AcaPyAriesClient(subWalletIssuerVerifierAcaPyClient, ariesClientLogger) 121 | } 122 | 123 | private fun createNewSubWallet(baseWalletAriesClient: AriesClient): String { 124 | val createWalletRequestBuilder = CreateWalletRequest.builder() 125 | .keyManagementMode(KeyManagementMode.MANAGED) 126 | .walletDispatchType(WalletDispatchType.DEFAULT) 127 | .walletKey("key") 128 | .walletName(subWalletName) 129 | .walletWebhookUrls(listOf(webhookEndpointUrl)) 130 | 131 | 132 | when (issuerVerifierMultitenancyWalletType) { 133 | "askar" -> createWalletRequestBuilder.walletType(WalletType.ASKAR) 134 | "indy" -> createWalletRequestBuilder.walletType(WalletType.INDY) 135 | else -> logger.error("Unable to create sub wallet as wallet type $issuerVerifierMultitenancyWalletType is unknown.") 136 | } 137 | 138 | var walletRecord: Optional? = null 139 | 140 | try { 141 | walletRecord = baseWalletAriesClient.multitenancyWalletCreate( 142 | createWalletRequestBuilder.build() 143 | ) 144 | } catch (e: Exception) { 145 | } 146 | 147 | if (walletRecord == null || walletRecord.isEmpty) { 148 | val errorMessage = 149 | "Unable to create a new sub wallet. Has the multitenancy mode been enabled for the AcaPy?" 150 | logger.error(errorMessage) 151 | throw Exception(errorMessage) 152 | } 153 | 154 | logger.info("Created Issuer/Verifier sub wallet with the name '$subWalletName' that is reporting to '$webhookEndpointUrl'.") 155 | 156 | return walletRecord.get().token 157 | } 158 | 159 | private fun createAndRegisterNewPublicDid(ariesClient: AriesClient) { 160 | val newDid = createNewDid(ariesClient) 161 | publishDidOnLedger(newDid.did, newDid.verkey) 162 | makeDidPublicDid(ariesClient, newDid.did) 163 | } 164 | 165 | private fun createNewDid(ariesClient: AriesClient): DID { 166 | val did = ariesClient.walletDidCreate( 167 | DIDCreate.builder() 168 | .method(DIDCreate.MethodEnum.SOV) 169 | .options( 170 | DIDCreateOptions.builder() 171 | .keyType(DIDCreateOptions.KeyTypeEnum.ED25519) 172 | .build() 173 | ) 174 | .build() 175 | ) 176 | 177 | if (did.isEmpty) { 178 | logger.error("Unable to create a new DID.") 179 | } 180 | 181 | return did.get() 182 | } 183 | 184 | private fun publishDidOnLedger(did: String, verKey: String) { 185 | val rest = RestTemplate() 186 | 187 | val didRegistration = object { 188 | val did = did 189 | val verkey = verKey 190 | val role = "ENDORSER" 191 | } 192 | 193 | rest.postForObject(registerDidEndpoint, didRegistration) 194 | } 195 | 196 | private fun makeDidPublicDid(ariesClient: AriesClient, did: String) { 197 | val result = ariesClient.walletDidPublic(did) 198 | 199 | if (result.isEmpty) { 200 | logger.error("Unable to make DID a public DID.") 201 | } 202 | } 203 | 204 | 205 | @Bean(name = ["Holder"]) 206 | fun holderAriesClient( 207 | handler: IHttpRequestObserver 208 | ): List { 209 | val holderAcaPyClients = mutableListOf() 210 | 211 | if (holderAcaPyUrls.isEmpty()) { 212 | logger.error("Unable to establish connection to Holder AcaPy. Holder AcaPy URL not configured.") 213 | return holderAcaPyClients 214 | } 215 | 216 | logger.info("Using ${holderAcaPyUrls.size} Holder Agents") 217 | holderAcaPyUrls.forEach { 218 | logger.info("Using Holder Agent: $it") 219 | val holderAcaPyClient = 220 | buildAcaPyAriesClient(AcaPyRequestLoggerInterceptor("Holder", handler, errorLogger), it, holderAcapyHttpTimeoutInSeconds, holderAcaPyApiKey, null) 221 | 222 | holderAcaPyClients.add( 223 | AcaPyAriesClient(holderAcaPyClient, ariesClientLogger) 224 | ) 225 | } 226 | 227 | return holderAcaPyClients 228 | } 229 | 230 | private fun buildAcaPyAriesClient( 231 | acaPyOkHttpInterceptor: AcaPyRequestLoggerInterceptor, 232 | acaPyUrl: String, 233 | acaPyHttpTimeoutInSeconds: Long, 234 | acaPyApiKey: String?, 235 | bearerToken: String?, 236 | ): AriesClient { 237 | val okHttpClient = OkHttpClient.Builder() 238 | .addInterceptor(acaPyOkHttpInterceptor) 239 | .writeTimeout(acaPyHttpTimeoutInSeconds, TimeUnit.SECONDS) 240 | .readTimeout(acaPyHttpTimeoutInSeconds, TimeUnit.SECONDS) 241 | .connectTimeout(acaPyHttpTimeoutInSeconds, TimeUnit.SECONDS) 242 | .callTimeout(acaPyHttpTimeoutInSeconds, TimeUnit.SECONDS) 243 | .build() 244 | 245 | return AriesClient.builder() 246 | .url(acaPyUrl) 247 | .apiKey(acaPyApiKey) 248 | .client(okHttpClient) 249 | .bearerToken(bearerToken) 250 | .build() 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/config/SwaggerConfig.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.config 2 | 3 | import org.springframework.boot.actuate.autoconfigure.endpoint.web.CorsEndpointProperties 4 | import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties 5 | import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType 6 | import org.springframework.boot.actuate.endpoint.ExposableEndpoint 7 | import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver 8 | import org.springframework.boot.actuate.endpoint.web.EndpointMapping 9 | import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes 10 | import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier 11 | import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier 12 | import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier 13 | import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping 14 | import org.springframework.context.annotation.Bean 15 | import org.springframework.context.annotation.Configuration 16 | import org.springframework.core.env.Environment 17 | import org.springframework.util.StringUtils 18 | import springfox.documentation.builders.ApiInfoBuilder 19 | import springfox.documentation.builders.PathSelectors 20 | import springfox.documentation.builders.RequestHandlerSelectors 21 | import springfox.documentation.service.ApiInfo 22 | import springfox.documentation.service.Tag 23 | import springfox.documentation.spi.DocumentationType 24 | import springfox.documentation.spring.web.plugins.Docket 25 | import springfox.documentation.swagger2.annotations.EnableSwagger2 26 | import java.util.* 27 | 28 | 29 | /** 30 | * Build a Swagger2 configuration file 31 | */ 32 | @Configuration 33 | @EnableSwagger2 34 | class SwaggerConfig { 35 | 36 | private val apiPackage = "com.bka.ssi.generator.api" 37 | private val title = "API Documentation for AcaPy Load Generator" 38 | private val description = "This is an automatically generated RESTfull API documentation and UI." 39 | private val version = "v1.0" 40 | 41 | @Bean 42 | fun createRestApi(): Docket { 43 | return Docket(DocumentationType.SWAGGER_2) 44 | .apiInfo(apiInfo()) 45 | .tags(Tag("info", "Endpoints to get information")) 46 | .select() 47 | .apis(RequestHandlerSelectors.basePackage(apiPackage)) 48 | .paths(PathSelectors.any()) 49 | .build() 50 | } 51 | 52 | private fun apiInfo(): ApiInfo { 53 | return ApiInfoBuilder() 54 | .title(title) 55 | .description(description) 56 | .version(version) 57 | .build() 58 | } 59 | 60 | // This bean resolves problem with actuator and swagger 61 | // https://github.com/springfox/springfox/issues/3462#issuecomment-1010721223 62 | @Bean 63 | fun webEndpointServletHandlerMapping( 64 | webEndpointsSupplier: WebEndpointsSupplier, 65 | servletEndpointsSupplier: ServletEndpointsSupplier, 66 | controllerEndpointsSupplier: ControllerEndpointsSupplier, 67 | endpointMediaTypes: EndpointMediaTypes?, 68 | corsProperties: CorsEndpointProperties, 69 | webEndpointProperties: WebEndpointProperties, 70 | environment: Environment 71 | ): WebMvcEndpointHandlerMapping? { 72 | val allEndpoints: MutableList?> = ArrayList() 73 | val webEndpoints = webEndpointsSupplier.endpoints 74 | allEndpoints.addAll(webEndpoints) 75 | allEndpoints.addAll(servletEndpointsSupplier.endpoints) 76 | allEndpoints.addAll(controllerEndpointsSupplier.endpoints) 77 | val basePath = webEndpointProperties.basePath 78 | val endpointMapping = EndpointMapping(basePath) 79 | val shouldRegisterLinksMapping = shouldRegisterLinksMapping(webEndpointProperties, environment, basePath) 80 | return WebMvcEndpointHandlerMapping( 81 | endpointMapping, 82 | webEndpoints, 83 | endpointMediaTypes, 84 | corsProperties.toCorsConfiguration(), 85 | EndpointLinksResolver(allEndpoints, basePath), 86 | shouldRegisterLinksMapping, 87 | null 88 | ) 89 | } 90 | 91 | private fun shouldRegisterLinksMapping( 92 | webEndpointProperties: WebEndpointProperties, 93 | environment: Environment, 94 | basePath: String 95 | ): Boolean { 96 | return webEndpointProperties.discovery.isEnabled && (StringUtils.hasText(basePath) || ManagementPortType.get( 97 | environment 98 | ) == ManagementPortType.DIFFERENT) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/domain/objects/ConnectionInvitationDo.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.domain.objects 2 | 3 | data class ConnectionInvitationDo( 4 | var type: String, 5 | var id: String, 6 | var recipientKeys: List, 7 | var serviceEndpoint: String, 8 | var label: String, 9 | ) 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/domain/objects/ConnectionRecordDo.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.domain.objects 2 | 3 | class ConnectionRecordDo( 4 | val connectionId: String, 5 | val time: Long, 6 | val state: String, 7 | val active: Boolean 8 | ) { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/domain/objects/CredentialDefinitionDo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * * Copyright 2022 Bundesrepublik Deutschland 4 | * * 5 | * * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * * you may not use this file except in compliance with the License. 7 | * * You may obtain a copy of the License at 8 | * * 9 | * * http://www.apache.org/licenses/LICENSE-2.0 10 | * * 11 | * * Unless required by applicable law or agreed to in writing, software 12 | * * distributed under the License is distributed on an "AS IS" BASIS, 13 | * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * * See the License for the specific language governing permissions and 15 | * * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.bka.ssi.generator.domain.objects 20 | 21 | class CredentialDefinitionDo( 22 | val id: String 23 | ) { 24 | } 25 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/domain/objects/CredentialDo.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.domain.objects 2 | 3 | class CredentialDo( 4 | val credentialDefinitionId: String, 5 | val claims: Map 6 | ) { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/domain/objects/CredentialExchangeRecordDo.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.domain.objects 2 | 3 | class CredentialExchangeRecordDo( 4 | val id: String, 5 | val sessionId: String, 6 | val connectionId: String, 7 | val time: Long, 8 | val state: String, 9 | val issued: Boolean, 10 | val revocationRegistryId: String?, 11 | val revocationRegistryIndex: String? 12 | ) { 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/domain/objects/CredentialRevocationRegistryRecordDo.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.domain.objects 2 | 3 | data class CredentialRevocationRegistryRecordDo( 4 | var credentialRevocationRegistryId: String, 5 | var credentialRevocationRegistryIndex: String, 6 | ) 7 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/domain/objects/ProofExchangeRecordDo.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.domain.objects 2 | 3 | class ProofExchangeRecordDo( 4 | val id: String, 5 | val time: Long, 6 | val connectionId: String, 7 | val state: String, 8 | val isVerified: Boolean, 9 | val isValid: Boolean, 10 | val comment: ProofExchangeCommentDo 11 | ) { 12 | } 13 | 14 | class ProofExchangeCommentDo { 15 | val shouldBeValid: Boolean 16 | val sessionId: String 17 | val revocationRegistryId: String? 18 | val revocationRegistryIndex: String? 19 | 20 | constructor(comment: String) { 21 | shouldBeValid = Regex("^${EXPECTED_TO_BE_VALID}.*").matches(comment) 22 | sessionId = Regex("${SESSION_ID_PREFIX}: ([^ ]*) ").find(comment)!!.groupValues[1] 23 | revocationRegistryId = 24 | Regex("${REVOCATION_REGISTRY_ID_PREFIX}: ([^ ]*) ").find(comment)?.groupValues?.get(1) 25 | revocationRegistryIndex = 26 | Regex("${REVOCATION_REGISTRY_INDEX_PREFIX}: (\\d+)").find(comment)?.groupValues?.get(1) 27 | } 28 | 29 | constructor( 30 | shouldBeValid: Boolean, 31 | sessionId: String, 32 | revocationRegistryId: String?, 33 | revocationRegistryIndex: String? 34 | ) { 35 | this.shouldBeValid = shouldBeValid 36 | this.sessionId = sessionId 37 | this.revocationRegistryId = revocationRegistryId 38 | this.revocationRegistryIndex = revocationRegistryIndex 39 | 40 | } 41 | 42 | companion object { 43 | private const val EXPECTED_TO_BE_VALID = "Expected to be valid" 44 | private const val EXPECTED_TO_BE_INVALID = "Expected to be invalid" 45 | private const val SESSION_ID_PREFIX = "sessionId" 46 | private const val REVOCATION_REGISTRY_ID_PREFIX = "revocationRegistryId" 47 | private const val REVOCATION_REGISTRY_INDEX_PREFIX = "revocationRegistryIndex" 48 | 49 | } 50 | 51 | override fun toString(): String { 52 | var comment = "" 53 | 54 | if (shouldBeValid) { 55 | comment += EXPECTED_TO_BE_VALID 56 | } else { 57 | comment += EXPECTED_TO_BE_INVALID 58 | } 59 | 60 | comment += " (${SESSION_ID_PREFIX}: ${sessionId} ${REVOCATION_REGISTRY_ID_PREFIX}: ${revocationRegistryId} ${REVOCATION_REGISTRY_INDEX_PREFIX}: ${revocationRegistryIndex})" 61 | 62 | return comment 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/domain/objects/ProofRequestDo.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.domain.objects 2 | 3 | class ProofRequestDo( 4 | val nonRevokedFrom: Long, 5 | val nonRevokedTo: Long, 6 | val requestedCredentials: List 7 | ) { 8 | } 9 | 10 | class CredentialRequestDo( 11 | val claims: List, 12 | val credentialDefinitionIdRestriction: String, 13 | val attributeValueRestriction: AttributeValueRestrictionDo 14 | ) 15 | 16 | class AttributeValueRestrictionDo( 17 | val attributeName: String, 18 | val attributeValue: String 19 | ) 20 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/domain/objects/SchemaDo.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.domain.objects 2 | 3 | class SchemaDo( 4 | val attributes: List, 5 | val name: String, 6 | val version: String 7 | ) { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/domain/services/IAriesClient.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.domain.services 2 | 3 | import com.bka.ssi.generator.domain.objects.* 4 | 5 | interface IAriesClient { 6 | fun getPublicDid(): String? 7 | 8 | fun createSchemaAndCredentialDefinition( 9 | schemaDo: SchemaDo, revocable: Boolean, 10 | revocationRegistrySize: Int 11 | ): CredentialDefinitionDo 12 | 13 | fun createConnectionInvitation(alias: String): ConnectionInvitationDo 14 | fun receiveConnectionInvitation(connectionInvitationDo: ConnectionInvitationDo) 15 | fun issueCredentialToConnection(connectionId: String, credentialDo: CredentialDo) 16 | fun revokeCredentialWithoutPublishing( 17 | credentialRevocationRegistryRecord: CredentialRevocationRegistryRecordDo 18 | ) 19 | 20 | fun revokeCredentialAndPublishRevocations( 21 | credentialRevocationRegistryRecord: CredentialRevocationRegistryRecordDo 22 | ) 23 | 24 | fun sendProofRequestToConnection( 25 | connectionId: String, 26 | proofRequestDo: ProofRequestDo, 27 | checkNonRevoked: Boolean, 28 | comment: ProofExchangeCommentDo 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/domain/services/IAriesObserver.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.domain.services 2 | 3 | import com.bka.ssi.generator.domain.objects.ConnectionRecordDo 4 | import com.bka.ssi.generator.domain.objects.CredentialExchangeRecordDo 5 | import com.bka.ssi.generator.domain.objects.ProofExchangeRecordDo 6 | 7 | interface IAriesObserver { 8 | fun handleConnectionRecord(connectionRecord: ConnectionRecordDo) 9 | fun handleCredentialExchangeRecord(credentialExchangeRecord: CredentialExchangeRecordDo) 10 | fun handleProofRequestRecord(proofExchangeRecord: ProofExchangeRecordDo) 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/domain/services/IHttpRequestObserver.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.domain.services 2 | 3 | interface IHttpRequestObserver { 4 | fun logHttpRequest(httpMethod: String, urlPath: String, httpResponseCode: Int, durationInMs: Double) 5 | } 6 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bka/ssi/generator/domain/services/IWallet.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator.domain.services 2 | 3 | interface IWallet { 4 | fun walletDatabaseSizeInBytes(): Int? 5 | } 6 | -------------------------------------------------------------------------------- /src/main/resources/application-docker.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | url: jdbc:postgresql://issuer-verifier-wallet-db:5000/ 4 | 5 | logging: 6 | config: classpath:logback-spring-docker.xml 7 | 8 | issuer-verifier: 9 | acapy: 10 | url: 'http://issuer-verifier-acapy:10000' 11 | multitenancy: 12 | register-did-endpoint: http://von_webserver_1:9000/register 13 | webhook-endpoint-url: http://load-generator-1:8080/acapy-webhook 14 | 15 | holder: 16 | acapy: 17 | urls: 'http://holder-acapy:10010' 18 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | tomcat: 3 | threads: 4 | max: 200 # Maximum amount of worker threads. 5 | max-connections: 8192 # default size is 8192 6 | 7 | spring: 8 | application: 9 | name: spring-load-generator 10 | datasource: 11 | url: jdbc:postgresql://localhost:5432/ 12 | username: postgres 13 | password: postgres 14 | mvc: 15 | pathmatch: 16 | matching-strategy: ant_path_matcher # required for the Swagger UI to work 17 | 18 | logging: 19 | config: classpath:logback-spring.xml 20 | 21 | management: 22 | endpoints: 23 | web: 24 | exposure: 25 | include: "*" 26 | 27 | issuer-verifier: 28 | acapy: 29 | api-key: 30 | url: 'http://localhost:10000' 31 | http-timeout-in-seconds: 60 32 | multitenancy: 33 | enabled: false 34 | wallet-type: askar 35 | register-did-endpoint: http://localhost:9000/register 36 | webhook-endpoint-url: http://host.docker.internal:8080/acapy-webhook 37 | sub-wallet-name: host.docker.internal 38 | wallet-db-name: wallet_db 39 | 40 | holder: 41 | acapy: 42 | api-key: 43 | urls: 'http://localhost:10010' 44 | http-timeout-in-seconds: 60 45 | 46 | test-runners: 47 | max-parallel-iterations-runner: 48 | active: false 49 | number-of-total-iterations: 100 50 | number-of-parallel-iterations: 5 51 | constant-load-runner: 52 | active: true 53 | number-of-total-iterations: 100 54 | number-of-iterations-per-minute: 30 55 | thread-pool-size: 4 56 | increasing-load-runner: 57 | active: false 58 | peak-duration-in-minutes: 2 59 | sleep-between-peaks-in-minutes: 1 60 | initial-number-of-iterations-per-minute: 150 61 | final-number-of-iterations-per-minute: 500 62 | step-size-of-iterations-per-minute: 25 63 | thread-pool-size: 4 64 | 65 | test-flows: 66 | empty-flow: 67 | active: false 68 | full-flow: 69 | active: true 70 | use-revocable-credentials: true 71 | revocation-registry-size: 500 72 | check-non-revoked: true 73 | revoke-credentials: true 74 | credential-revocation-batch-size: 5 75 | issuer-flow: 76 | active: false 77 | use-revocable-credentials: true 78 | revocation-registry-size: 500 79 | connection-flow: 80 | active: false 81 | credential-issuance-flow: 82 | active: false 83 | use-revocable-credentials: true 84 | revocation-registry-size: 500 85 | proof-request-flow: 86 | active: false 87 | check-non-revoked: true 88 | revocation-registry-size: 500 89 | -------------------------------------------------------------------------------- /src/main/resources/logback-spring-docker.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | http://loki:3100/loki/api/v1/push 6 | 7 | 8 | 11 | 12 | class=%logger{20} thread=%thread | %msg %ex 13 | 14 | true 15 | 16 | 17 | 18 | 19 | 20 | %-5relative %-5level %logger{35} - %msg%n 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | http://localhost:3100/loki/api/v1/push 6 | 7 | 8 | 11 | 12 | class=%logger{20} thread=%thread | %msg %ex 13 | 14 | true 15 | 16 | 17 | 18 | 19 | 20 | %-5relative %-5level %logger{35} - %msg%n 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/test/kotlin/com/bka/ssi/generator/GeneratorApplicationTests.kt: -------------------------------------------------------------------------------- 1 | package com.bka.ssi.generator 2 | 3 | import org.junit.jupiter.api.Test 4 | import org.springframework.boot.test.context.SpringBootTest 5 | 6 | @SpringBootTest 7 | class GeneratorApplicationTests { 8 | 9 | @Test 10 | fun contextLoads() { 11 | } 12 | 13 | } 14 | --------------------------------------------------------------------------------