diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index faf15e96d..66eaceb81 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,6 +17,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + submodules: true - name: Set up JDK for running Gradle uses: actions/setup-java@v4 @@ -46,6 +48,8 @@ jobs: fail-fast: false steps: - uses: actions/checkout@v4 + with: + submodules: true - id: setup-test-java name: Set up JDK ${{ matrix.test-java-version }} for running tests @@ -78,6 +82,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + submodules: true - name: Set up JDK for running Gradle uses: actions/setup-java@v4 @@ -131,6 +137,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + submodules: true - name: Set up JDK for running Gradle uses: actions/setup-java@v4 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..1db07bfa3 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "opamp-client/opamp-spec"] + path = opamp-client/opamp-spec + url = git@github.com:open-telemetry/opamp-spec.git diff --git a/opamp-client/build.gradle.kts b/opamp-client/build.gradle.kts index c094faf03..1716d4051 100644 --- a/opamp-client/build.gradle.kts +++ b/opamp-client/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("otel.java-conventions") + id("com.google.protobuf") version "0.9.4" } description = "Client implementation of the OpAMP spec." @@ -9,3 +10,33 @@ java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } + +sourceSets { + main { + proto { + srcDir("opamp-spec/proto") + } + } +} + +tasks.withType().configureEach { + with(options) { + // Suppressing warnings about the usage of deprecated methods. + // This is needed because the Protobuf plugin (com.google.protobuf) generates code that uses deprecated methods. + compilerArgs.add("-Xlint:-deprecation") + } +} + +val protobufVersion = "4.28.2" + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:$protobufVersion" + } +} + +dependencies { + implementation("com.google.protobuf:protobuf-java:$protobufVersion") + annotationProcessor("com.google.auto.value:auto-value") + compileOnly("com.google.auto.value:auto-value-annotations") +} diff --git a/opamp-client/opamp-spec b/opamp-client/opamp-spec new file mode 160000 index 000000000..e74e4fc49 --- /dev/null +++ b/opamp-client/opamp-spec @@ -0,0 +1 @@ +Subproject commit e74e4fc498fd1dc06e2e7368966c1ce87ee3fa9b diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClient.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClient.java new file mode 100644 index 000000000..32ed77656 --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClient.java @@ -0,0 +1,101 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal; + +import io.opentelemetry.opamp.client.internal.response.MessageData; +import opamp.proto.Opamp; + +public interface OpampClient { + + /** + * Starts the client and begin attempts to connect to the Server. Once connection is established + * the client will attempt to maintain it by reconnecting if the connection is lost. All failed + * connection attempts will be reported via {@link Callbacks#onConnectFailed(OpampClient, + * Throwable)} callback. + * + *

This method does not wait until the connection to the Server is established and will likely + * return before the connection attempts are even made. + * + *

This method may be called only once. + * + * @param callbacks The Callback to which the Client will notify about any Server requests and + * responses. + */ + void start(Callbacks callbacks); + + /** + * Stops the client. May be called only after {@link #start(Callbacks)}. May be called only once. + * After this call returns successfully it is guaranteed that no callbacks will be called. Once + * stopped, the client cannot be started again. + */ + void stop(); + + /** + * Sets attributes of the Agent. The attributes will be included in the next status report sent to + * the Server. When called after {@link #start(Callbacks)}, the attributes will be included in the + * next outgoing status report. This is typically used by Agents which allow their + * AgentDescription to change dynamically while the OpAMPClient is started. May be also called + * from {@link Callbacks#onMessage(OpampClient, MessageData)}. + * + * @param agentDescription The new agent description. + */ + void setAgentDescription(Opamp.AgentDescription agentDescription); + + /** + * Sets the current remote config status which will be sent in the next agent to server request. + * + * @param remoteConfigStatus The new remote config status. + */ + void setRemoteConfigStatus(Opamp.RemoteConfigStatus remoteConfigStatus); + + interface Callbacks { + /** + * Called when the connection is successfully established to the Server. May be called after + * {@link #start(Callbacks)} is called and every time a connection is established to the Server. + * For WebSocket clients this is called after the handshake is completed without any error. For + * HTTP clients this is called for any request if the response status is OK. + * + * @param client The relevant {@link OpampClient} instance. + */ + void onConnect(OpampClient client); + + /** + * Called when the connection to the Server cannot be established. May be called after {@link + * #start(Callbacks)} is called and tries to connect to the Server. May also be called if the + * connection is lost and reconnection attempt fails. + * + * @param client The relevant {@link OpampClient} instance. + * @param throwable The exception. + */ + void onConnectFailed(OpampClient client, Throwable throwable); + + /** + * Called when the Server reports an error in response to some previously sent request. Useful + * for logging purposes. The Agent should not attempt to process the error by reconnecting or + * retrying previous operations. The client handles the ErrorResponse_UNAVAILABLE case + * internally by performing retries as necessary. + * + * @param client The relevant {@link OpampClient} instance. + * @param errorResponse The error returned by the Server. + */ + void onErrorResponse(OpampClient client, Opamp.ServerErrorResponse errorResponse); + + /** + * Called when the Agent receives a message that needs processing. See {@link MessageData} + * definition for the data that may be available for processing. During onMessage execution the + * {@link OpampClient} functions that change the status of the client may be called, e.g. if + * RemoteConfig is processed then {@link + * #setRemoteConfigStatus(opamp.proto.Opamp.RemoteConfigStatus)} should be called to reflect the + * processing result. These functions may also be called after onMessage returns. This is + * advisable if processing can take a long time. In that case returning quickly is preferable to + * avoid blocking the {@link OpampClient}. + * + * @param client The relevant {@link OpampClient} instance. + * @param messageData The server response data that needs processing. + */ + void onMessage(OpampClient client, MessageData messageData); + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/MessageData.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/MessageData.java new file mode 100644 index 000000000..0cd4b0fdf --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/MessageData.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.response; + +import com.google.auto.value.AutoValue; +import io.opentelemetry.opamp.client.internal.OpampClient; +import javax.annotation.Nullable; +import opamp.proto.Opamp; + +/** + * Data class provided in {@link OpampClient.Callbacks#onMessage(OpampClient, MessageData)} with + * Server's provided status changes. + */ +@AutoValue +public abstract class MessageData { + @Nullable + public abstract Opamp.AgentRemoteConfig getRemoteConfig(); + + public static Builder builder() { + return new AutoValue_MessageData.Builder(); + } + + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder setRemoteConfig(Opamp.AgentRemoteConfig remoteConfig); + + public abstract MessageData build(); + } +}