├── .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 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
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 |
9 | app=aries-load-generator,level=%level
10 |
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 |
9 | app=aries-load-generator,level=%level
10 |
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 |
--------------------------------------------------------------------------------