Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Opamp client api #1481

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
8 changes: 8 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "opamp-client/opamp-spec"]
path = opamp-client/opamp-spec
url = [email protected]:open-telemetry/opamp-spec.git
31 changes: 31 additions & 0 deletions opamp-client/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
id("otel.java-conventions")
id("com.google.protobuf") version "0.9.4"
}

description = "Client implementation of the OpAMP spec."
Expand All @@ -9,3 +10,33 @@ java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

sourceSets {
main {
proto {
srcDir("opamp-spec/proto")
}
}
}

tasks.withType<JavaCompile>().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")
}
1 change: 1 addition & 0 deletions opamp-client/opamp-spec
Submodule opamp-spec added at e74e4f
Original file line number Diff line number Diff line change
@@ -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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see some of opamp-go Client's methods here, e.g. SetAgentDescription. Just wanted to confirm this is deliberate and not accidentally missing.

Copy link
Contributor Author

@LikeTheSalad LikeTheSalad Oct 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, for context, I'm currently moving the PoC implementation created here which had only the bare minimum apis/functionality for our use-case to work. So we should keep on adding more methods in the future. The SetAgentDescription one seems to be pretty straightforward so I think we could add it right away to account for the use-case of changing the description after the client has started.

An interesting note on the client initialization method used in the PoC is that it relies on the builder pattern. So, setting the agent description before starting the client is done in the client builder, as you can see here. There are 2 main reasons for this, the first one being that it's less error-prone as users wouldn't need to remember setting the agent description before starting the client, which is something I noticed is mentioned here in the Go impl, and the second reason is that using the builder pattern to solve these issues is a common practice in Java and makes it easier to adjust/extend initialization-related processes in the future. It's also the pattern used when initializing the OTel Java SDK so I think that's a bonus in terms of consistency across Java-related projects in OTel.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just added SetAgentDescription.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I think you are free to deviate from opamp-go and use whatever is the most idiomatic approach for Java. Builder pattern is less common in Go world. Struct of options is typically what Go uses, e.g. the standard library http server: https://pkg.go.dev/net/http#hdr-Servers


/**
* 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.
*
* <p>This method does not wait until the connection to the Server is established and will likely
* return before the connection attempts are even made.
*
* <p>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);
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume more callbacks will be added later, e.g. OnOpampConnectionSettings, OnCommand, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, I think those should be added as soon as we add support for those functionalities (unless we see a reason to add them beforehand with noop functionality).

Original file line number Diff line number Diff line change
@@ -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();
}
}
Loading