EDCSample

メモ

EDC/Samples の内容を紹介する。

動作確認環境

本プロジェクトに沿って動作確認した際の環境情報を以下に載せる。

1
2
3
4
5
6
7
8
9
10
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=22.04
DISTRIB_CODENAME=jammy
DISTRIB_DESCRIPTION="Ubuntu 22.04.4 LTS"

$ java -version
openjdk version "17.0.12" 2024-07-16
OpenJDK Runtime Environment (build 17.0.12+7-Ubuntu-1ubuntu222.04)
OpenJDK 64-Bit Server VM (build 17.0.12+7-Ubuntu-1ubuntu222.04, mixed mode, sharing)

EDCで利用しているJDKに合わせてJDK17を用いている。

またdobachi/EDCSampleAnsibleに環境準備などのAnsible Playbookを置く。

README

EDC/Samples/README には目的、必要事項(準備)、スコープの説明がある。

本プロジェクトの目的はオンボーディングの支援。 初心者をナビゲートしやすいような順番でサンプルが構成されており、ステップバイステップで学べる。

必要事項(準備)

このプロジェクトでは、基本的にEclipse Dataspace Componentで用いられる用語は理解している前提で説明が進められる。 そのため、EDC/documentation は読んでいることが望ましい。 また、EDC/YTの動画も参考になる。

また、関連ツールとして、Git、Gradle、Java、HTTPあたりは押さえておきたい、とされている。

動作確認する場合は、Java11+がインストールされていることが必要。

スコープ(Scopes)

サンプル群は、Scopeと呼ばれるグループで区分されている。 何を学びたいか、で分かれている。 ひとまず、初心者であればbasicが良いとのこと。

ざっと以下に解説する。

Scope名 説明
Basic EDC Frameworkを使い始めるための基礎。セットアップ、起動、拡張の作成
Transfer データ転送について
Advanced 高度な例

Basicスコープの概要

EDC/Samples/Basic/READMEには以下のサンプルが載っている。

サンプル名 説明
Basic Sample 1 コネクタを起動するのに必要なことを学ぶ
Basic Sample 2 拡張機能の作成、拡張機能の使い方を学ぶ
Basic Sample 3 プロパティファイルを用いて設定値を扱う方法を学ぶ

Basic/Sample1

EDC/Samples/Basic/Basic1/READMEには以下の通り説明されている。

Runtimeとビルドファイルで構成されている。 ビルドファイルは、build.gradle.ktsである。

このサンプルでは、EDCのBaseRuntimeを用いている。

また、EDC/Samples/Basic/Basic1/build.gradle.ktsを見ることでプロジェクトが依存するものがわかる。

basic/basic-01-basic-connector/build.gradle.kts:22

1
2
3
4
dependencies {
implementation(libs.edc.boot)
implementation(libs.edc.connector.core)
}

READMEの通り、Sampleプロジェクトのrootでコンパイル実行すれば良い。

1
2
./gradlew clean basic:basic-01-basic-connector:build
java -jar basic/basic-01-basic-connector/build/libs/basic-connector.jar

特に何があるわけではない。コンソールに起動のメッセージが出力されるはず。

Basic/Sample2

EDC/Samples/Basic/Basic2/README には以下の通り説明されている。

ServiceExtensionsrc/main/resources/META-INF/services ディレクトリ以下のプラグインファイルで構成される。 この例では HealthEndpointExtension というServiceExtensionを実装。

1
2
3
4
5
6
7
8
9
10
public class HealthEndpointExtension implements ServiceExtension {

@Inject
WebService webService;

@Override
public void initialize(ServiceExtensionContext context) {
webService.registerResource(new HealthApiController(context.getMonitor()));
}
}

上記の通り、 ServiceExtension を拡張。また、中ではWebServiceをインジェクとしている。(DIのインジェクトについてはインターネットの情報を参考) initialize メソッド内で、先にインジェクとした webService にリソースを登録している。

org.eclipse.edc.web.spi.WebService インターフェースは、例えば org.eclipse.edc.web.jersey.JerseyRestService として実装されている。 Jersey はシンプルなRESTfulサービスのフレームワークである。

上記で登録されているコントローラ HealthApiController は以下の通り。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
@Path("/")
public class HealthApiController {

private final Monitor monitor;

public HealthApiController(Monitor monitor) {
this.monitor = monitor;
}

@GET
@Path("health")
public String checkHealth() {
monitor.info("Received a health request");
return "{\"response\":\"I'm alive!\"}";
}
}

特別なことはないが、上記の通り、 checkHealth メソッドを叩かれるとメッセージを残す。 実行の仕方は以下の通り。

1
2
./gradlew clean basic:basic-02-health-endpoint:build
java -jar basic/basic-02-health-endpoint/build/libs/connector-health.jar

http://localhost:8181/api/health にアクセスすればブラウザ上でメッセージが表示されることに加え、コンソールにもメッセージが生じる。

Basic/Sample3

EDC/Samples/Basic/Basic3/README に以下のような説明がある。

設定をくくりだした ConfigurationExtension がある。 org.eclipse.edc.configuration.filesystem.FsConfigurationExtension がデフォルトの設定ファイル取り扱いのクラス。 このサンプルでは、Gradleの依存関係記述にて、 以下を指定することでJarファイルを含めるようにする。

basic/basic-03-configuration/build.gradle.kts:29

1
2
3
(snip)
implementation(libs.edc.configuration.filesystem)
(snip)

ここでは /etc/eclipse/EDC/config.properties という設定ファイルを作り利用する例を示す。 ポート番号を9191に変更する例。

1
2
sudo mkdir -p /etc/eclipse/EDC
sudo sh -c 'echo "web.http.port=9191" > /etc/eclipse/EDC/config.properties'

プロパティファイルを作ったので、それを利用するよう引数で指定して実行する。

1
2
./gradlew clean basic:basic-03-configuration:build
java -Dedc.fs.config=/etc/eclipse/EDC/config.properties -jar basic/basic-03-configuration/build/libs/filesystem-config-connector.jar

起動時に以下のようなログが見えるはず。

1
2
INFO 2024-08-26T16:16:38.946828558 HTTP context 'default' listening on port 9191
DEBUG 2024-08-26T16:16:38.986329567 Port mappings: {alias='default', port=9191, path='/api'}

ちなみに、設定ファイルのデフォルトは以下の通り。

/home/dobachi/.gradle/caches/modules-2/files-2.1/org.eclipse.edc/configuration-filesystem/0.8.1/7741c1f790be0f02a2da844cd05edb469a5d095b/configuration-filesystem-0.8.1-sources.jar!/org/eclipse/edc/configuration/filesystem/FsConfigurationExtension.java:67

1
var configLocation = propOrEnv(FS_CONFIG, "dataspaceconnector-configuration.properties");

独自のプロパティの利用

本サンプルのREADMEに書いてある通り、先程のプロパティファイルに以下を追記。

1
edc.samples.basic.03.logprefix=MyLogPrefix

ログのプリフィックス文言を決めるためのプロパティを定義。

続いて、Sample2でも使用した org.eclipse.edc.extension.health.HealthEndpointExtension を改変。 プロパティ名を定義し、 org.eclipse.edc.spi.system.SettingResolver#getSetting(java.lang.String, java.lang.String) メソッドを用いてプロパティの値を取れるようにした。

org/eclipse/edc/extension/health/HealthEndpointExtension.java:22

1
2
3
4
5
6
7
8
9
10
11
12
13
public class HealthEndpointExtension implements ServiceExtension {

@Inject
WebService webService;

private static final String LOG_PREFIX_SETTING = "edc.samples.basic.03.logprefix"; // this constant is new

@Override
public void initialize(ServiceExtensionContext context) {
var logPrefix = context.getSetting(LOG_PREFIX_SETTING, "health"); //this line is new
webService.registerResource(new HealthApiController(context.getMonitor(), logPrefix));
}
}

http://localhsot:9191/health にアクセスすると、コンソールに以下のログが出る。

1
INFO 2024-08-26T16:25:55.233519477 health :: Received a health request

Transferスコープの概要

2個のコネクタ間でデータをやり取りする。プロバイダとコンシューマ。

Transfer/Sample0(準備)

EDC/Samples/Transfer/Transfer0/READMEの通り、準備をする。 なお、このサンプルでは簡単化のために同一マシン上で、プロバイダとコンシューマを起動するが、本来は別々のところで起動するものである。

プロジェクトrootで以下を実行。

1
./gradlew transfer:transfer-00-prerequisites:connector:build

結果、ビルドされたJARファイルがここに配置される。 transfer/transfer-00-prerequisites/connector/build/libs/connector.jar

プロバイダとコンシューマは設定が異なるのみであり、JARは共通。 PATH配下の通り。

transfer/transfer-00-prerequisites/resources/configuration/consumer-configuration.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
edc.participant.id=consumer
edc.dsp.callback.address=http://localhost:29194/protocol
web.http.port=29191
web.http.path=/api
web.http.management.port=29193
web.http.management.path=/management
web.http.protocol.port=29194
web.http.protocol.path=/protocol
edc.transfer.proxy.token.signer.privatekey.alias=private-key
edc.transfer.proxy.token.verifier.publickey.alias=public-key
web.http.public.port=29291
web.http.public.path=/public
web.http.control.port=29192
web.http.control.path=/control

transfer/transfer-00-prerequisites/resources/configuration/provider-configuration.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
edc.participant.id=provider
edc.dsp.callback.address=http://localhost:19194/protocol
web.http.port=19191
web.http.path=/api
web.http.management.port=19193
web.http.management.path=/management
web.http.protocol.port=19194
web.http.protocol.path=/protocol
edc.transfer.proxy.token.signer.privatekey.alias=private-key
edc.transfer.proxy.token.verifier.publickey.alias=public-key
web.http.public.port=19291
web.http.public.path=/public
web.http.control.port=19192
web.http.control.path=/control
edc.dataplane.api.public.baseurl=http://localhost:19291/public

用いるポートを変えているのと、プロバイダ側にはデータプレーンのプロパティがあることがわかる。

プロバイダ実行:

1
java -Dedc.keystore=transfer/transfer-00-prerequisites/resources/certs/cert.pfx -Dedc.keystore.password=123456 -Dedc.fs.config=transfer/transfer-00-prerequisites/resources/configuration/provider-configuration.properties -jar transfer/transfer-00-prerequisites/connector/build/libs/connector.jar

キーやキーストアの設定、プロパティファイルのPATHを渡すのみ。

コンシューマ起動:

1
java -Dedc.keystore=transfer/transfer-00-prerequisites/resources/certs/cert.pfx -Dedc.keystore.password=123456 -Dedc.fs.config=transfer/transfer-00-prerequisites/resources/configuration/consumer-configuration.properties -jar transfer/transfer-00-prerequisites/connector/build/libs/connector.jar

プロバイダと同等。

Transfer/Sample1(コントラクト・ネゴシエーション)

EDC/Samples/Transfer/Transfer1/README の通り以下のステップで進める。

  • プロバイダのアセット(共有される対象のデータ)を作成
  • プロバイダでアクセスポリシーを作成
  • プロバイダでコントラクト定義を作成
  • その後、コンシューマからコントラクト・ネゴシエーション

まずプロバイダでアセットを作成。

1
2
3
curl -d @transfer/transfer-01-negotiation/resources/create-asset.json \
-H 'content-type: application/json' http://localhost:19193/management/v3/assets \
-s | jq

curlコマンドの-dataオプションに @ でファイルを渡しをしている。中身は以下の通り。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"@context": {
"@vocab": "https://w3id.org/edc/v0.0.1/ns/"
},
"@id": "assetId",
"properties": {
"name": "product description",
"contenttype": "application/json"
},
"dataAddress": {
"type": "HttpData",
"name": "Test asset",
"baseUrl": "https://jsonplaceholder.typicode.com/users",
"proxyPath": "true"
}
}

ポリシ定義。

1
2
3
curl -d @transfer/transfer-01-negotiation/resources/create-policy.json \
-H 'content-type: application/json' http://localhost:19193/management/v3/policydefinitions \
-s | jq

渡しているファイルの中身は以下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"@context": {
"@vocab": "https://w3id.org/edc/v0.0.1/ns/",
"odrl": "http://www.w3.org/ns/odrl/2/"
},
"@id": "aPolicy",
"policy": {
"@context": "http://www.w3.org/ns/odrl.jsonld",
"@type": "Set",
"permission": [],
"prohibition": [],
"obligation": []
}
}

ポリシの中身は空に見える。 ちなみにレスポンスは以下のような感じ。

1
2
3
4
5
6
7
8
9
10
{
"@type": "IdResponse",
"@id": "aPolicy",
"createdAt": 1724662296875,
"@context": {
"@vocab": "https://w3id.org/edc/v0.0.1/ns/",
"edc": "https://w3id.org/edc/v0.0.1/ns/",
"odrl": "http://www.w3.org/ns/odrl/2/"
}
}

コントラクト定義を作成。

1
2
3
curl -d @transfer/transfer-01-negotiation/resources/create-contract-definition.json \
-H 'content-type: application/json' http://localhost:19193/management/v3/contractdefinitions \
-s | jq

渡しているファイルの中身は以下。

1
2
3
4
5
6
7
8
9
{
"@context": {
"@vocab": "https://w3id.org/edc/v0.0.1/ns/"
},
"@id": "1",
"accessPolicyId": "aPolicy",
"contractPolicyId": "aPolicy",
"assetsSelector": []
}

アクセスポリシやコントラクトポリシのIDを渡している。

コンシューマからカタログ情報を取得する。

1
2
3
curl -X POST "http://localhost:29193/management/v3/catalog/request" \
-H 'Content-Type: application/json' \
-d @transfer/transfer-01-negotiation/resources/fetch-catalog.json -s | jq

得られるカタログ。

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
{
"@id": "4ce1bc3d-ce1e-4308-b03e-b6596958a69d",
"@type": "dcat:Catalog",
"dcat:dataset": {
"@id": "assetId",
"@type": "dcat:Dataset",
"odrl:hasPolicy": {
"@id": "MQ==:YXNzZXRJZA==:Y2ZmODc1NmYtYTRjMC00NzMxLWJlNTItM2M2ZTVlMGI2YzA3",
"@type": "odrl:Offer",
"odrl:permission": [],
"odrl:prohibition": [],
"odrl:obligation": []
},
"dcat:distribution": [
{
"@type": "dcat:Distribution",
"dct:format": {
"@id": "HttpData-PULL"
},
"dcat:accessService": {
"@id": "bb73ab52-bd72-448d-9629-8a21624b0577",
"@type": "dcat:DataService",
"dcat:endpointDescription": "dspace:connector",
"dcat:endpointUrl": "http://localhost:19194/protocol",
"dct:terms": "dspace:connector",
"dct:endpointUrl": "http://localhost:19194/protocol"
}
},
{
"@type": "dcat:Distribution",
"dct:format": {
"@id": "HttpData-PUSH"
},
"dcat:accessService": {
"@id": "bb73ab52-bd72-448d-9629-8a21624b0577",
"@type": "dcat:DataService",
"dcat:endpointDescription": "dspace:connector",
"dcat:endpointUrl": "http://localhost:19194/protocol",
"dct:terms": "dspace:connector",
"dct:endpointUrl": "http://localhost:19194/protocol"
}
}
],
"name": "product description",
"id": "assetId",
"contenttype": "application/json"
},
"dcat:distribution": [],
"dcat:service": {
"@id": "bb73ab52-bd72-448d-9629-8a21624b0577",
"@type": "dcat:DataService",
"dcat:endpointDescription": "dspace:connector",
"dcat:endpointUrl": "http://localhost:19194/protocol",
"dct:terms": "dspace:connector",
"dct:endpointUrl": "http://localhost:19194/protocol"
},
"dspace:participantId": "provider",
"participantId": "provider",
"@context": {
"@vocab": "https://w3id.org/edc/v0.0.1/ns/",
"edc": "https://w3id.org/edc/v0.0.1/ns/",
"dcat": "http://www.w3.org/ns/dcat#",
"dct": "http://purl.org/dc/terms/",
"odrl": "http://www.w3.org/ns/odrl/2/",
"dspace": "https://w3id.org/dspace/v0.8/"
}
}

上記の通り、DCATで定義されている。ODRLで定義されたポリシのIDが示される、など。 他にもHttpDataでデータ共有されることや、サービス情報が載っている。

ここからコンシューマからプロバイダに向けてコントラクト・ネゴシエーションを行う。大まかな流れは以下。

  • コンシューマがコントラクトオファを送る。
  • プロバイダが自身のオファと照らし合わせ、届いたオファを検証する。
  • プロバイダがアグリメントか、リジェクションを送る。
  • 検証が成功していれば、アグリメントを保存する。

negotiate-contract.json のうち、NaN となっている箇所を、先程のカタログ情報から拾って埋める。 具体的には、以下のIDで埋める。

1
2
"odrl:hasPolicy": {
"@id": "MQ==:YXNzZXRJZA==:Y2ZmODc1NmYtYTRjMC00NzMxLWJlNTItM2M2ZTVlMGI2YzA3",

コントラクト・ネゴシエーション実行。

1
2
3
curl -d @transfer/transfer-01-negotiation/resources/negotiate-contract.json \
-X POST -H 'content-type: application/json' http://localhost:29193/management/v3/contractnegotiations \
-s | jq

以下のようなレスポンスが得られる。コントラクト・ネゴシエーション中のIDである。

1
2
3
4
5
6
7
8
9
10
{
"@type": "IdResponse",
"@id": "4515e87b-5bd0-4c19-aa92-1968b65005e5",
"createdAt": 1724664162792,
"@context": {
"@vocab": "https://w3id.org/edc/v0.0.1/ns/",
"edc": "https://w3id.org/edc/v0.0.1/ns/",
"odrl": "http://www.w3.org/ns/odrl/2/"
}
}

コントラクト・ネゴシエーションはプロバイダとコンシューマのそれぞれで非同期的に進む。 最終的に両者がconfirmedかdeclinedになったら終了。

コンシューマでの状態確認。先程得られたIDを使う。

1
2
3
curl -X GET "http://localhost:29193/management/v3/contractnegotiations/4515e87b-5bd0-4c19-aa92-1968b65005e5" \
--header 'Content-Type: application/json' \
-s | jq

以下のように、Finalized担ったことがわかる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"@type": "ContractNegotiation",
"@id": "4515e87b-5bd0-4c19-aa92-1968b65005e5",
"type": "CONSUMER",
"protocol": "dataspace-protocol-http",
"state": "FINALIZED",
"counterPartyId": "provider",
"counterPartyAddress": "http://localhost:19194/protocol",
"callbackAddresses": [],
"createdAt": 1724664162792,
"contractAgreementId": "74ed3714-3380-4888-93f9-71b948d09553",
"@context": {
"@vocab": "https://w3id.org/edc/v0.0.1/ns/",
"edc": "https://w3id.org/edc/v0.0.1/ns/",
"odrl": "http://www.w3.org/ns/odrl/2/"
}
}

Transfer/Sample2(コンシューマPull)

EDC/Samples/Transfer/Transfer2/README に記載の通り、以下のステップで進める。

  • コンシューマからファイル転送(の手続き)を始める。
  • プロバイダが EndpointDataReference をコンシューマに送る。
  • コンシューマがエンドポイントを使ってデータをフェッチする。

転送開始。 前の章で得られた、コントラクト・アグリメントのIDを transfer/transfer-02-consumer-pull/resources/start-transfer.json に埋め込む。

1
2
3
4
curl -X POST "http://localhost:29193/management/v3/transferprocesses" \
-H "Content-Type: application/json" \
-d @transfer/transfer-02-consumer-pull/resources/start-transfer.json \
-s | jq

結果は以下の通り。 TransferProcess が生成された。

1
2
3
4
5
6
7
8
9
10
{
"@type": "IdResponse",
"@id": "0778ef5a-0999-42fb-ad16-6572ca1c04d6",
"createdAt": 1724665640700,
"@context": {
"@vocab": "https://w3id.org/edc/v0.0.1/ns/",
"edc": "https://w3id.org/edc/v0.0.1/ns/",
"odrl": "http://www.w3.org/ns/odrl/2/"
}
}

状態を確認する。先程得られたTransferProcessのIDを利用。

1
2
curl http://localhost:29193/management/v3/transferprocesses/0778ef5a-0999-42fb-ad16-6572ca1c04d6 \
-s | jq

結果の例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"@id": "0778ef5a-0999-42fb-ad16-6572ca1c04d6",
"@type": "TransferProcess",
"state": "STARTED",
"stateTimestamp": 1724665641835,
"type": "CONSUMER",
"callbackAddresses": [],
"correlationId": "f0837ef5-999c-41dc-89b9-74d9337ecb5f",
"assetId": "assetId",
"contractId": "74ed3714-3380-4888-93f9-71b948d09553",
"transferType": "HttpData-PULL",
"@context": {
"@vocab": "https://w3id.org/edc/v0.0.1/ns/",
"edc": "https://w3id.org/edc/v0.0.1/ns/",
"odrl": "http://www.w3.org/ns/odrl/2/"
}
}

statestarted 担っていることがわかる。

DTRがコンシューマに送られているはず、さらにキャッシュされているはずなので、それを取得する。

1
2
curl http://localhost:29193/management/v3/edrs/0778ef5a-0999-42fb-ad16-6572ca1c04d6/dataaddress \
-s | jq

結果の例。

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"@type": "DataAddress",
"type": "https://w3id.org/idsa/v4.1/HTTP",
"endpoint": "http://localhost:19291/public",
"authType": "bearer",
"endpointType": "https://w3id.org/idsa/v4.1/HTTP",
"authorization": "eyJraWQiOiJwdWJsaWMta2V5IiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJwcm92aWRlciIsImF1ZCI6ImNvbnN1bWVyIiwic3ViIjoicHJvdmlkZXIiLCJpYXQiOjE3MjQ2NjU2NDE3NzEsImp0aSI6ImJjNmQ1YzIzLTExMWUtNGZkZi1hMjMwLTJkZjk3NmE3NTFlOSJ9.dYBBx2z2XCjg1TgbEPiDgb2KYdUlNMd7702P4t0NgwYKhDemAKF64qGpLsU63huQ2WMb9Co4sC1euopyY-48F3UvfjK0ulKODlrfVGO-7xvSNs4qX2HVC9JyLGGbdks0wQOkv9oA7AcKxD11yTjcfbtLc-DUoOF4w4RTpI2MATSa-ETvKo_22FxMrIHgnsLOCHtjVLazJchFm4bAhVa7mRfHykkTpIIEaPuqOJpdtKcX1YUAZloFaI1ZinfXNtHjvollVC9Mjb4H12Gh1B7tLxgZ0AbP0izFeOHnQleQ09ZThajwF-xpnPQ5P5fsNiFqthW2A7Gu5-PLGT6tp2lYIg",
"@context": {
"@vocab": "https://w3id.org/edc/v0.0.1/ns/",
"edc": "https://w3id.org/edc/v0.0.1/ns/",
"odrl": "http://www.w3.org/ns/odrl/2/"
}
}

上記の通り、エンドポイントURLと認証キーがわかる。 データを取得する。

1
2
3
curl --location --request GET 'http://localhost:19291/public/' \
--header 'Authorization: eyJraWQiOiJwdWJsaWMta2V5IiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJwcm92aWRlciIsImF1ZCI6ImNvbnN1bWVyIiwic3ViIjoicHJvdmlkZXIiLCJpYXQiOjE3MjQ2NjU2NDE3NzEsImp0aSI6ImJjNmQ1YzIzLTExMWUtNGZkZi1hMjMwLTJkZjk3NmE3NTFlOSJ9.dYBBx2z2XCjg1TgbEPiDgb2KYdUlNMd7702P4t0NgwYKhDemAKF64qGpLsU63huQ2WMb9Co4sC1euopyY-48F3UvfjK0ulKODlrfVGO-7xvSNs4qX2HVC9JyLGGbdks0wQOkv9oA7AcKxD11yTjcfbtLc-DUoOF4w4RTpI2MATSa-ETvKo_22FxMrIHgnsLOCHtjVLazJchFm4bAhVa7mRfHykkTpIIEaPuqOJpdtKcX1YUAZloFaI1ZinfXNtHjvollVC9Mjb4H12Gh1B7tLxgZ0AbP0izFeOHnQleQ09ZThajwF-xpnPQ5P5fsNiFqthW2A7Gu5-PLGT6tp2lYIg' \
-s | jq

以下のような結果が得られる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[                                                               
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz",
"address": {
"street": "Kulas Light",
"suite": "Apt. 556",
"city": "Gwenborough",
"zipcode": "92998-3874",
"geo": {
"lat": "-37.3159",
"lng": "81.1496"
}
},
(snip)

https://jsonplaceholder.typicode.com/users にあるデータをコネクタのコントラクトを通じて得られた。

Pullするときに、以下のようにするとユーザIDを指定できる。

http://localhost:19291/public/1

Transfer/Sample3(プロバイダPush)

EDC/Samples/Transfer/Transfer3/README に記載の通り、今度はプロバイダからPushする例である。 大まかな流れは以下の通り。

  • ファイル転送
    • コンシューマからファイル転送(の手続き)を始める。
    • プロバイダのコントロールプレーンが実際のデータのアドレスを取得し、DataRequest をもとに DataFlowRequest を作成する。
  • プロバイダデータプレーンが実際のデータソースからデータを取得。
  • プロバイダデータプレーンがコンシューマサービスにデータを送る。

まずコンシューマ側のバックエンドのロガーを起動する。これがPush先になると見られる。今回はDockerで起動する。

1
2
docker build -t http-request-logger util/http-request-logger
docker run -p 4000:4000 http-request-logger

続いて以下のファイルを編集した上でTransferProcessを開始する。 transfer/transfer-03-provider-push/resources/start-transfer.json なお、変更するのはコントラクト・アグリメントIDである。上記のケースでは、 74ed3714-3380-4888-93f9-71b948d09553 である。

1
2
3
4
curl -X POST "http://localhost:29193/management/v3/transferprocesses" \
-H "Content-Type: application/json" \
-d @transfer/transfer-03-provider-push/resources/start-transfer.json \
-s | jq

なお、 start-transfer.json で指定している dataDestination にHttpProxy以外のものを指定しているのがポイントのようだ。

transfer/transfer-03-provider-push/resources/start-transfer.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"@context": {
"@vocab": "https://w3id.org/edc/v0.0.1/ns/"
},
"@type": "TransferRequestDto",
"connectorId": "provider",
"counterPartyAddress": "http://localhost:19194/protocol",
"contractId": "74ed3714-3380-4888-93f9-71b948d09553",
"assetId": "assetId",
"protocol": "dataspace-protocol-http",
"transferType": "HttpData-PUSH",
"dataDestination": {
"type": "HttpData",
"baseUrl": "http://localhost:4000/api/consumer/store"
}
}

結果の例。

1
2
3
4
5
6
7
8
9
10
{
"@type": "IdResponse",
"@id": "105477c9-dbff-4577-adb5-2e860f9be585",
"createdAt": 1724678897615,
"@context": {
"@vocab": "https://w3id.org/edc/v0.0.1/ns/",
"edc": "https://w3id.org/edc/v0.0.1/ns/",
"odrl": "http://www.w3.org/ns/odrl/2/"
}
}

結果確認。

1
2
curl http://localhost:29193/management/v3/transferprocesses/105477c9-dbff-4577-adb5-2e860f9be585 \
-s | jq

結果の例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  "@id": "105477c9-dbff-4577-adb5-2e860f9be585",
"@type": "TransferProcess",
"state": "COMPLETED",
"stateTimestamp": 1724678900583,
"type": "CONSUMER",
"callbackAddresses": [],
"correlationId": "657b63b0-920d-412c-9c09-edb48057e717",
"assetId": "assetId",
"contractId": "74ed3714-3380-4888-93f9-71b948d09553",
"transferType": "HttpData-PUSH",
"dataDestination": {
"@type": "DataAddress",
"type": "HttpData",
"baseUrl": "http://localhost:4000/api/consumer/store"
},
"@context": {
"@vocab": "https://w3id.org/edc/v0.0.1/ns/",
"edc": "https://w3id.org/edc/v0.0.1/ns/",
"odrl": "http://www.w3.org/ns/odrl/2/"
}
}

Completed である。

ロガーのコンソールに以下の表示。

1
2
3
4
5
6
7
8
9
10
11
12
13
Incoming request                                                                         
Method: POST
Path: /api/consumer/store
Body:
[
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz",
"address": {
"street": "Kulas Light",
(snip)

これ、Push先のエンドポイントはどうやって知るのか。カタログ?

Transfer/Sample4

EDC/Samples/Transfer/Transfer4/README の通り、コンシューマで転送完了に反応する機能を追加する。

モジュール構成は以下。

  • consumer-with-listener: コンシューマ(コネクタ)のイベント・コンシューマ拡張
  • listener: イベントを処理する TransferProcessListener の実装

ドキュメントでは、 TransferProcessListener の実装として org.eclipse.edc.sample.extension.listener.TransferProcessStartedListener を紹介。

エントリポイントは org.eclipse.edc.sample.extension.listener.TransferProcessStartedListenerExtension である。

org/eclipse/edc/sample/extension/listener/TransferProcessStartedListenerExtension.java:21

1
2
3
4
5
6
7
8
9
10
public class TransferProcessStartedListenerExtension implements ServiceExtension {

@Override
public void initialize(ServiceExtensionContext context) {
var transferProcessObservable = context.getService(TransferProcessObservable.class);
var monitor = context.getMonitor();
transferProcessObservable.registerListener(new TransferProcessStartedListener(monitor));
}

}

上記の通り、 org.eclipse.edc.sample.extension.listener.TransferProcessStartedListener をリスナとして登録している。

ポイントは以下。

org/eclipse/edc/sample/extension/listener/TransferProcessStartedListener.java:34

1
2
3
4
5
@Override
public void preStarted(final TransferProcess process) {
monitor.debug("TransferProcessStartedListener received STARTED event");
// do something meaningful before transfer start
}

START イベントを受け取ったときに、ログに出力する。

このサンプルのビルドと実行をするのだが、その前に前の章まで使用していたコンシューマを止める。

その上で、以下を実行して、Sample4のコンシューマを起動する。

1
2
./gradlew transfer:transfer-04-event-consumer:consumer-with-listener:build
java -Dedc.keystore=transfer/transfer-00-prerequisites/resources/certs/cert.pfx -Dedc.keystore.password=123456 -Dedc.fs.config=transfer/transfer-00-prerequisites/resources/configuration/consumer-configuration.properties -jar transfer/transfer-04-event-consumer/consumer-with-listener/build/libs/connector.jar

続いて、新しいコントラクト・ネゴシエーション。

1
2
3
curl -d @transfer/transfer-01-negotiation/resources/negotiate-contract.json \
-X POST -H 'content-type: application/json' http://localhost:29193/management/v3/contractnegotiations \
-s | jq

結果例。

1
2
3
4
5
6
7
8
9
10
{
"@type": "IdResponse",
"@id": "fe8c4f16-283c-4d74-a440-715e8371d228",
"createdAt": 1724766448685,
"@context": {
"@vocab": "https://w3id.org/edc/v0.0.1/ns/",
"edc": "https://w3id.org/edc/v0.0.1/ns/",
"odrl": "http://www.w3.org/ns/odrl/2/"
}
}

先の例と同様にコントラクト・アグリメントIDを取得する。 直前の結果から、コントラクト・ネゴシエーションIDを抜き出して下記のコマンドに入れるのを忘れずに。

1
2
3
curl -X GET "http://localhost:29193/management/v3/contractnegotiations/fe8c4f16-283c-4d74-a440-715e8371d228" \
--header 'Content-Type: application/json' \
-s | jq

結果例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"@type": "ContractNegotiation",
"@id": "fe8c4f16-283c-4d74-a440-715e8371d228",
"type": "CONSUMER",
"protocol": "dataspace-protocol-http",
"state": "FINALIZED",
"counterPartyId": "provider",
"counterPartyAddress": "http://localhost:19194/protocol",
"callbackAddresses": [],
"createdAt": 1724766448685,
"contractAgreementId": "8e96f6c5-cb0e-4f06-beb0-ac553ddb160e",
"@context": {
"@vocab": "https://w3id.org/edc/v0.0.1/ns/",
"edc": "https://w3id.org/edc/v0.0.1/ns/",
"odrl": "http://www.w3.org/ns/odrl/2/"
}
}

コントラクト・アグリメントIDは 8e96f6c5-cb0e-4f06-beb0-ac553ddb160e である。

つづいて、 transfer/transfer-02-consumer-pull/resources/start-transfer.json の今コントラクト・アグリメントIDを書き換えつつ、ファイル転送を開始する。 該当箇所は以下。

1
"contractId": "8e96f6c5-cb0e-4f06-beb0-ac553ddb160e",

実行コマンドは以下。

1
2
3
4
curl -X POST "http://localhost:29193/management/v3/transferprocesses" \
-H "Content-Type: application/json" \
-d @transfer/transfer-02-consumer-pull/resources/start-transfer.json \
-s | jq

実行結果例。

1
2
3
4
5
6
7
8
9
10
{
"@type": "IdResponse",
"@id": "b4dff4b1-dfb2-470e-a765-7bd549a1e2b6",
"createdAt": 1724766768403,
"@context": {
"@vocab": "https://w3id.org/edc/v0.0.1/ns/",
"edc": "https://w3id.org/edc/v0.0.1/ns/",
"odrl": "http://www.w3.org/ns/odrl/2/"
}
}

先ほど起動したコンシューマのコンソールログを見ると

1
DEBUG 2024-08-27T22:52:49.930105146 TransferProcessStartedListener received STARTED event

のようなメッセージが見つかるはず。

Transfer/Sample5

(このサンプルはAzureとAWSの環境が必要なのと手違いの被害が大きいことからメモだけとする)

EDC/Samples/Transfer/Transfer5/README に記載の通り、これまで実行したサンプルに対し、通常使うであろう機能を足していく。 この例では、具体的には「Azureのストレージから読んで、AWSのストレージに書く」というのを実現する。

なお、環境構成には、Terraformが使われている。 transfer/transfer-05-file-transfer-cloud/terraform にもろもろ格納されている。 中身を見ると、割といろいろとセットアップしているのがわかる。念の為、潰しても良いクリーンな環境で試すのが良い。 一応6章にて環境をクリーンナップする手順が記載されている。

READMEにある通り、クライアントIDなどはVault内に保持するようになっている。 依存関係にもVaultがある。

transfer/transfer-05-file-transfer-cloud/cloud-transfer-consumer/build.gradle.kts:31

1
2
3
(snip)
implementation(libs.edc.vault.azure)
(snip)

今サンプルのメインの一つは、 org.eclipse.edc.sample.extension.transfer.CloudTransferExtension である。

org.eclipse.edc.sample.extension.transfer.CloudTransferExtension#registerDataEntries メソッドがアセットを定義する。

1
2
3
4
5
6
7
8
9
10
public void registerDataEntries() {
var dataAddress = DataAddress.Builder.newInstance()
.type("AzureStorage")
.property("account", "<storage-account-name>")
.property("container", "src-container")
.property("blobname", "test-document.txt")
.keyName("<storage-account-name>-key1")
.build();
var asset = Asset.Builder.newInstance().id("1").dataAddress(dataAddress).build();
assetIndex.create(asset);

なお、org.eclipse.edc.connector.controlplane.policy.spi.store.PolicyDefinitionStoreorg.eclipse.edc.connector.controlplane.contract.spi.offer.store.ContractDefinitionStore があり、それぞれポリシとコントラクトを保持する。今回のサンプルではインメモリで保持する実装を利用。

このあとはいつもどおり、コントラクト・ネゴシエーションしてコントラクト・アグリメントIDを取得し、実際のデータ転送。 ほぼこれまで通りだが、dataDestinationでAWS S3を指定しているところがポイント。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
curl --location --request POST 'http://localhost:9192/management/v3/transferprocesses' \
--header 'X-API-Key: password' \
--header 'Content-Type: application/json' \
--data-raw '
{
"counterPartyAddress": "http://localhost:8282/protocol",
"protocol": "dataspace-protocol-http",
"connectorId": "consumer",
"assetId": "1",
"contractId": "<ContractAgreementId>",
"dataDestination": {
"type": "AmazonS3",
"region": "us-east-1",
"bucketName": "<Unique bucket name>"
},
"transferType": {
"contentType": "application/octet-stream",
"isFinite": true
}
}'

参考

レポジトリ内

EDCレポジトリ

外部

共有