Skip to content

Commit

Permalink
docs: provide a multi-tenancy sample
Browse files Browse the repository at this point in the history
  • Loading branch information
ndr-brt committed Aug 4, 2023
1 parent 4ab0ced commit 8e573fe
Show file tree
Hide file tree
Showing 10 changed files with 352 additions and 0 deletions.
109 changes: 109 additions & 0 deletions samples/multi-tenancy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# 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 assets:
```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 assets:
```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
[]
```
46 changes: 46 additions & 0 deletions samples/multi-tenancy/build.gradle.kts
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)
}
9 changes: 9 additions & 0 deletions samples/multi-tenancy/config/first.properties
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
9 changes: 9 additions & 0 deletions samples/multi-tenancy/config/second.properties
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
9 changes: 9 additions & 0 deletions samples/multi-tenancy/config/third.properties
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* 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);

try (var classLoader = URLClassLoader.newInstance(classPathEntries, getSystemClassLoader())) {
var 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 | 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 RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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 samples/multi-tenancy/src/test/resources/tenants.properties
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
3 changes: 3 additions & 0 deletions samples/multi-tenancy/tenants.properties
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
2 changes: 2 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ include(":edc-extensions:dataplane-proxy:edc-dataplane-proxy-provider-core")
include(":edc-extensions:dataplane-proxy:edc-dataplane-proxy-provider-api")
include(":edc-tests:edc-dataplane-proxy-e2e")

include(":samples:multi-tenancy")


// this is needed to have access to snapshot builds of plugins
pluginManagement {
Expand Down

0 comments on commit 8e573fe

Please sign in to comment.