1. Overview
The control plane integrates data planes through the
Data Plane Signaling (DPS) protocol, implemented by
the data-plane-signaling modules (data-protocols/data-plane-signaling):
data-plane-signaling-core: the registration API, the signaling client, theDataFlowControllerthat drives the data planes for the transfer process state machine, and the Signaling API that receives the data planes’ notifications;data-plane-signaling-oauth2anddata-plane-signaling-oauth2-token-exchange: the authorization profilesoauth2_client_credentialsandoauth2_token_exchange(see 5. Authorization profiles).
The control plane doesn’t move data and doesn’t create data addresses: it decides which data plane handles a transfer and when, the data plane decides how.
2. Data plane registration
The life cycle of a data plane is decoupled from the one of the control plane: data planes can be added, replaced or removed at any time. A data plane registers with the control plane through the Management API:
PUT /v4/dataplanesandDELETE /v4/dataplanes/{dataplaneId};PUT /v5/participants/{participantContextId}/dataplanesandDELETE /v5/participants/{participantContextId}/dataplanes/{dataplaneId}, which register the data plane for one participant context and need the scopemanagement-api:dataplanes:write.
The request body is a plain JSON object:
{
"dataplaneId": "dataplane-1",
"endpoint": "https://dataplane.internal/api/signaling",
"transferTypes": [ "HttpData-PULL", "HttpData-PUSH" ],
"labels": [ "eu" ],
"authorization": {
"type": "oauth2_client_credentials",
"tokenEndpoint": "https://idp.internal/token",
"clientId": "control-plane",
"clientSecret": "...",
"jwksUri": "https://idp.internal/jwks"
}
}
dataplaneId: the id of the data plane, unique per participant context. Registering the same id again replaces the registration, so a data plane can register on every start.endpoint: the base URL of the data plane’s DPS API, to which the control plane sends the signals.transferTypes: the transfer types (DPS profiles) the data plane supports.labels: free-form labels that assets and transfer requests can require of the data plane (see 3.2).authorization: optional, the authorization profile with which the control plane and the data plane authenticate to each other (see 5. Authorization profiles).
A registered data plane is a logical instance: replicas of a data plane share one registration, behind one endpoint.
The control plane doesn’t check whether a data plane is available, that is the job of the infrastructure (e.g. replicas
behind a load balancer). The registrations are persisted (edc.sql.store.dataplaneinstance.datasource for the SQL
store), so that all the control plane replicas see them.
3. Data plane selection
3.1 Building the catalog
The transfer types offered for an asset in the catalog, as Distributions, come from the registered data planes of the
participant context:
- if the asset’s
dataplaneMetadatalists noprofiles, the transfer types of all the registered data planes; - otherwise, the transfer types of the data planes that support at least one of the listed profiles.
For example, if one data plane supports HttpData-PULL and another AmazonS3-PUSH, an asset without profiles is offered
with both.
3.2 Selecting a data plane for a transfer
When a transfer process is prepared (consumer) or started (provider), the control plane selects a data plane that:
- is registered for the participant context of the transfer process;
- supports the transfer type of the transfer process;
- has all the labels of the transfer process’s
dataplaneMetadata. The provider takes them from the asset, the consumer from the transfer request.
If several data planes match, the selection strategy picks one: random by default, set with
edc.dataplane.client.selector.strategy. Extensions can add strategies through the SelectionStrategyRegistry. The id of
the selected data plane is stored on the transfer process, and all later signals go to that data plane.
4. Signaling
The DataPlaneSignalingFlowController is the DataFlowController of the control plane: the transfer process state
machine calls it at each step, and it sends the corresponding DPS message to the data plane with the
DataPlaneSignalingClient. The messages and their payloads are defined by the
DPS specification; the transfer type of the
transfer process is sent as the profile, together with the labels and the metadata (properties) of its
dataplaneMetadata.
4.1 Signals sent to the data plane
| Transfer process step | Side | DPS call |
|---|---|---|
| INITIAL | consumer | POST {endpoint}/prepare |
| INITIAL | provider | POST {endpoint}/start |
| STARTED received from the provider | consumer | POST {endpoint}/{flowId}/started |
| SUSPENDING | both | POST {endpoint}/{flowId}/suspend |
| RESUMING | both | POST {endpoint}/{flowId}/resume |
| TERMINATING | both | POST {endpoint}/{flowId}/terminate |
| COMPLETED received from the counter-party | both | POST {endpoint}/{flowId}/completed |
The flowId is the id of the transfer process.
A data plane answers prepare, start and resume either synchronously, with the final state of the data flow (e.g.
STARTED) and possibly a data address, or asynchronously, with a pending state (e.g. STARTING). In the asynchronous
case, the transfer process waits in PREPARATION_REQUESTED or STARTUP_REQUESTED until the data plane notifies the
control plane.
A signal fails with ERROR_RETRY when it couldn’t be delivered (timeout, I/O error), when the data plane answers with a
5xx, or when the authorization profile couldn’t obtain a token, and the transfer process state machine sends it again
with its backoff and retry limit (edc.transfer.send.retry.*). A 4xx answer fails with FATAL_ERROR, which ends the
step: e.g. a failed prepare terminates the consumer transfer process, a failed start makes the provider terminate
it.
4.2 Notifications from the data plane
The data plane notifies the control plane through the Signaling API, a dedicated API context
(web.http.signaling.port, default 8182, and web.http.signaling.path, default /api/signaling):
POST /transfers/{transferId}/dataflow/prepared: an asynchronousprepareis done, with the data address if any;POST /transfers/{transferId}/dataflow/started: an asynchronousstartis done, with the data address if any;POST /transfers/{transferId}/dataflow/completed: the data flow is complete, the control plane completes the transfer process and notifies the counter-party;POST /transfers/{transferId}/dataflow/errored: the data flow failed for good. As defined by DPS, the transfer process doesn’t change state and the counter-party is not notified: the control plane logs the error and emits the eventtransfer.process.dataflow.errored, with the error, so that callbacks or operators can decide how to recover, e.g. by terminating the transfer process or starting a new one.
The data plane must be configured with the URL of this API: the control plane doesn’t send it in the prepare and
start messages.
4.3 The data address
The data address carries what the counter-party needs to access the data flow, e.g. an endpoint and a token. It is created and used only by the data planes; the control plane stores it and passes it on through the Dataspace Protocol:
- pull: the provider data plane returns the data address in its
startresponse (orstartednotification). The provider control plane sends it to the consumer in the DSPTransferStartMessage, and the consumer control plane hands it to its data plane withstarted. - push: the consumer data plane returns the destination as data address in its
prepareresponse (orpreparednotification). The consumer control plane sends it in the DSPTransferRequestMessage, and the provider control plane passes it to its data plane in thestartmessage.
5. Authorization profiles
The authorization object of the registration selects how the control plane authenticates its signals to the data plane,
and how it authenticates the data plane’s notifications:
oauth2_client_credentials(data-plane-signaling-oauth2): the control plane gets a token with the client credentials (tokenEndpoint,clientId,clientSecret) for its signals, and validates the tokens of the notifications againstjwksUriorjwks.oauth2_token_exchange(data-plane-signaling-oauth2-token-exchange): the control plane exchanges its workload credential (by default the Kubernetes service account token,edc.dps.oauth2.tokenexchange.subjecttokenpath) for a token attokenExchangeEndpoint; the notifications are validated against theissuerandjwksUriorjwksof the profile.
Without an authorization object the signals and the notifications are not authenticated. The control plane logs a
warning when such a data plane registers, see
Securing the Signaling API. The authorization profiles are
defined in the DPS profiles; further ones can be added by
extensions through the SignalingAuthorizationRegistry.