-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: provide a multi-tenancy sample (#691)
* docs: provide a multi-tenancy sample * fix ci
- Loading branch information
Showing
11 changed files
with
386 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
# Multi Tenancy | ||
|
||
This sample show how to create a custom runtime to run multiple EDC tenants in a single java process. | ||
|
||
## How it works | ||
|
||
In a Java Runtime, multiple "sub-runtimes" with dedicated classloader can be launched in parallel, giving object-level | ||
separation (an object instantiated by a sub-runtime cannot be accessed by another sub-runtime). | ||
|
||
## How to use | ||
|
||
The module provides an extension of the `BaseRuntime` class called `MultiTenantRuntime`. | ||
This class can be set in the `build.gradle.kts` as the main class: | ||
|
||
```kotlin | ||
application { | ||
mainClass.set("org.eclipse.tractusx.edc.samples.multitenancy.MultiTenantRuntime") | ||
} | ||
``` | ||
|
||
This runtime looks for a properties file which path can be specified with the `edc.tenants.path` property. | ||
|
||
In this file the tenants are defined through settings, e.g.: | ||
|
||
```properties | ||
edc.tenants.tenant1.edc.fs.config=/config/path | ||
edc.tenants.tenant2.edc.fs.config=/config/path | ||
edc.tenants.tenant3.edc.fs.config=/config/path | ||
``` | ||
|
||
Using this file the EDC will run with 3 tenants: `tenant1`, `tenant2` and `tenant3`, every one with their respective | ||
configuration file. | ||
Everything that stays after the tenant name in the setting key will be loaded in the tenant runtime, so *theoretically* | ||
(but not recommended) you could define all the tenants configuration in the tenants properties file: | ||
|
||
```properties | ||
edc.tenants.tenant1.web.http.port=18181 | ||
edc.tenants.tenant1.any.other.setting=value | ||
edc.tenants.tenant2.web.http.port=28181 | ||
edc.tenants.tenant3.web.http.port=38181 | ||
``` | ||
|
||
## Sample | ||
|
||
Build: | ||
|
||
```shell | ||
./gradlew :samples:multi-tenancy:build | ||
``` | ||
|
||
Run: | ||
|
||
```shell | ||
java -jar -Dedc.tenants.path=samples/multi-tenancy/tenants.properties samples/multi-tenancy/build/libs/multitenant.jar | ||
``` | ||
|
||
Create a PolicyDefinition on `first` tenant: | ||
|
||
```shell | ||
curl -X POST http://localhost:18183/management/v2/policydefinitions \ | ||
--header 'Content-Type: application/json' \ | ||
--data '{ | ||
"@context": { "@vocab": "https://w3id.org/edc/v0.0.1/ns/" }, | ||
"policy": { | ||
"@context": "http://www.w3.org/ns/odrl.jsonld", | ||
"@type": "set", | ||
"permission": [], | ||
"prohibition": [], | ||
"obligation": [] | ||
} | ||
} | ||
' | ||
``` | ||
|
||
Get `first` tenant policy definitions: | ||
|
||
```shell | ||
curl -X POST http://localhost:18183/management/v2/policydefinitions/request | ||
``` | ||
|
||
Will get a list containing the PolicyDefinition we created: | ||
|
||
```json | ||
[ | ||
{ | ||
"@id": "f48f2e27-c385-4846-b8b8-112c08bfa424", | ||
"@type": "edc:PolicyDefinition", | ||
"edc:createdAt": 1691147860257, | ||
"edc:policy": { | ||
"@id": "898fa3d6-b488-4f5f-9a41-4fb4b9229813", | ||
"@type": "odrl:Set", | ||
"odrl:permission": [], | ||
"odrl:prohibition": [], | ||
"odrl:obligation": [] | ||
}, | ||
"@context": { | ||
"dct": "https://purl.org/dc/terms/", | ||
"tx": "https://w3id.org/tractusx/v0.0.1/ns/", | ||
"edc": "https://w3id.org/edc/v0.0.1/ns/", | ||
"dcat": "https://www.w3.org/ns/dcat/", | ||
"odrl": "http://www.w3.org/ns/odrl/2/", | ||
"dspace": "https://w3id.org/dspace/v0.8/" | ||
} | ||
} | ||
] | ||
``` | ||
|
||
`second` and `third` tenants will have no policy definitions: | ||
|
||
```shell | ||
curl -X POST http://localhost:28183/management/v2/policydefinitions/request | ||
``` | ||
|
||
and | ||
|
||
```shell | ||
curl -X POST http://localhost:38183/management/v2/policydefinitions/request | ||
``` | ||
|
||
will return | ||
|
||
```json | ||
[] | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
/* | ||
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Apache License, Version 2.0 which is available at | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* Contributors: | ||
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation | ||
* | ||
*/ | ||
|
||
plugins { | ||
`java-library` | ||
id("application") | ||
id("com.github.johnrengelman.shadow") version "8.1.1" | ||
} | ||
|
||
|
||
dependencies { | ||
implementation(libs.edc.boot) | ||
implementation(project(":edc-controlplane:edc-controlplane-base")) { | ||
exclude("org.eclipse.tractusx.edc", "data-encryption") | ||
exclude(module = "ssi-miw-credential-client") | ||
exclude(module = "ssi-identity-core") | ||
exclude(module = "auth-tokenbased") | ||
} | ||
implementation(libs.edc.iam.mock) | ||
implementation(libs.edc.core.controlplane) | ||
} | ||
|
||
application { | ||
mainClass.set("org.eclipse.tractusx.edc.samples.multitenancy.MultiTenantRuntime") | ||
} | ||
|
||
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> { | ||
mergeServiceFiles() | ||
archiveFileName.set("multitenant.jar") | ||
} | ||
|
||
// do not publish | ||
edcBuild { | ||
publish.set(false) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
web.http.port=18181 | ||
web.http.path=/ | ||
web.http.protocol.port=18182 | ||
web.http.protocol.path=/protocol | ||
web.http.management.port=18183 | ||
web.http.management.path=/management | ||
web.http.control.port=18184 | ||
web.http.control.path=/control | ||
edc.dsp.callback.address=http://localhost:18182 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
web.http.port=28181 | ||
web.http.path=/ | ||
web.http.protocol.port=28182 | ||
web.http.protocol.path=/protocol | ||
web.http.management.port=28183 | ||
web.http.management.path=/management | ||
web.http.control.port=28184 | ||
web.http.control.path=/control | ||
edc.dsp.callback.address=http://localhost:28182 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
web.http.port=38181 | ||
web.http.path=/ | ||
web.http.protocol.port=38182 | ||
web.http.protocol.path=/protocol | ||
web.http.management.port=38183 | ||
web.http.management.path=/management | ||
web.http.control.port=38184 | ||
web.http.control.path=/control | ||
edc.dsp.callback.address=http://localhost:38182 |
104 changes: 104 additions & 0 deletions
104
...nancy/src/main/java/org/eclipse/tractusx/edc/samples/multitenancy/MultiTenantRuntime.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
/* | ||
* Copyright (c) 2022 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Apache License, Version 2.0 which is available at | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* Contributors: | ||
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - Initial implementation | ||
* | ||
*/ | ||
|
||
package org.eclipse.tractusx.edc.samples.multitenancy; | ||
|
||
import org.eclipse.edc.boot.system.runtime.BaseRuntime; | ||
import org.eclipse.edc.spi.EdcException; | ||
import org.eclipse.edc.spi.monitor.Monitor; | ||
import org.eclipse.edc.spi.system.configuration.Config; | ||
import org.eclipse.edc.spi.system.configuration.ConfigFactory; | ||
import org.jetbrains.annotations.NotNull; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.net.MalformedURLException; | ||
import java.net.URL; | ||
import java.net.URLClassLoader; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.util.Arrays; | ||
import java.util.Properties; | ||
|
||
import static java.lang.ClassLoader.getSystemClassLoader; | ||
|
||
public class MultiTenantRuntime extends BaseRuntime { | ||
|
||
private final @NotNull Monitor monitor = createMonitor(); | ||
|
||
public static void main(String[] args) { | ||
var runtime = new MultiTenantRuntime(); | ||
runtime.boot(); | ||
} | ||
|
||
protected void boot() { | ||
loadTenantsConfig().getConfig("edc.tenants").partition().forEach(this::bootTenant); | ||
} | ||
|
||
private void bootTenant(Config tenantConfig) { | ||
var baseProperties = System.getProperties(); | ||
tenantConfig.getRelativeEntries().forEach(System::setProperty); | ||
var classPathEntries = Arrays.stream(System.getProperty("java.class.path").split(File.pathSeparator)) | ||
.map(this::toUrl) | ||
.toArray(URL[]::new); | ||
|
||
Thread runtimeThread = null; | ||
try (var classLoader = URLClassLoader.newInstance(classPathEntries, getSystemClassLoader())) { | ||
runtimeThread = new Thread(() -> { | ||
try { | ||
Thread.currentThread().setContextClassLoader(classLoader); | ||
super.boot(); | ||
} catch (Exception e) { | ||
throw new EdcException(e); | ||
} | ||
}); | ||
|
||
monitor.info("Starting tenant " + tenantConfig.currentNode()); | ||
runtimeThread.start(); | ||
|
||
runtimeThread.join(20_000); | ||
|
||
} catch (InterruptedException e) { | ||
runtimeThread.interrupt(); | ||
throw new EdcException(e); | ||
} catch (IOException e) { | ||
throw new EdcException(e); | ||
} finally { | ||
System.setProperties(baseProperties); | ||
} | ||
} | ||
|
||
@NotNull | ||
private Config loadTenantsConfig() { | ||
var tenantsPath = System.getProperty("edc.tenants.path"); | ||
if (tenantsPath == null) { | ||
throw new EdcException("No edc.tenants.path mandatory property provided"); | ||
} | ||
try (var is = Files.newInputStream(Path.of(tenantsPath))) { | ||
var properties = new Properties(); | ||
properties.load(is); | ||
return ConfigFactory.fromProperties(properties); | ||
} catch (IOException e) { | ||
throw new EdcException(e); | ||
} | ||
} | ||
|
||
private URL toUrl(String entry) { | ||
try { | ||
return new File(entry).toURI().toURL(); | ||
} catch (MalformedURLException e) { | ||
throw new EdcException(e); | ||
} | ||
} | ||
} |
69 changes: 69 additions & 0 deletions
69
...y/src/test/java/org/eclipse/tractusx/edc/samples/multitenancy/MultiTenantRuntimeTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
/* | ||
* Copyright (c) 2022 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Apache License, Version 2.0 which is available at | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* Contributors: | ||
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - Initial implementation | ||
* | ||
*/ | ||
|
||
package org.eclipse.tractusx.edc.samples.multitenancy; | ||
|
||
import org.eclipse.edc.spi.EdcException; | ||
import org.eclipse.edc.spi.monitor.Monitor; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.junit.jupiter.api.Test; | ||
import org.mockito.ArgumentMatcher; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertThrows; | ||
import static org.mockito.ArgumentMatchers.argThat; | ||
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.never; | ||
import static org.mockito.Mockito.times; | ||
import static org.mockito.Mockito.verify; | ||
|
||
class MultiTenantRuntimeTest { | ||
|
||
private final Monitor monitor = mock(); | ||
private final MultiTenantRuntime runtime = | ||
new MultiTenantRuntime() { | ||
@Override | ||
protected @NotNull Monitor createMonitor() { | ||
return monitor; | ||
} | ||
}; | ||
|
||
@Test | ||
void throwsExceptionIfNoTenantsPropertyProvided() { | ||
assertThrows(EdcException.class, runtime::boot); | ||
verify(monitor, never()).info(argThat(connectorIsReady())); | ||
} | ||
|
||
@Test | ||
void throwsExceptionIfTenantsFileDoesNotExist() { | ||
System.setProperty("edc.tenants.path", "unexistentfile"); | ||
|
||
assertThrows(EdcException.class, runtime::boot); | ||
verify(monitor, never()).info(argThat(connectorIsReady())); | ||
} | ||
|
||
@Test | ||
void threadForEveryTenant() { | ||
System.setProperty("edc.tenants.path", "./src/test/resources/tenants.properties"); | ||
|
||
runtime.boot(); | ||
|
||
verify(monitor, times(2)).info(argThat(connectorIsReady())); | ||
} | ||
|
||
@NotNull | ||
private ArgumentMatcher<String> connectorIsReady() { | ||
return message -> message.endsWith(" ready"); | ||
} | ||
|
||
} |
10 changes: 10 additions & 0 deletions
10
samples/multi-tenancy/src/test/resources/tenants.properties
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
edc.tenants.one.edc.any=any | ||
edc.tenants.one.web.http.port=18181 | ||
edc.tenants.one.web.http.path=/api | ||
edc.tenants.one.web.http.protocol.port=18282 | ||
edc.tenants.one.web.http.protocol.path=/protocol | ||
edc.tenants.two.edc.any=any | ||
edc.tenants.two.web.http.port=28181 | ||
edc.tenants.two.web.http.path=/api | ||
edc.tenants.two.web.http.protocol.port=28282 | ||
edc.tenants.two.web.http.protocol.path=/protocol |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
edc.tenants.first.edc.fs.config=samples/multi-tenancy/config/first.properties | ||
edc.tenants.second.edc.fs.config=samples/multi-tenancy/config/second.properties | ||
edc.tenants.third.edc.fs.config=samples/multi-tenancy/config/third.properties |
Oops, something went wrong.