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

feat: include agent info into polling #29

Merged
merged 7 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ public AgentBuilder extend(AgentBuilder agentBuilder, ConfigProperties config) {

// Notify configuration server about this agent
NotificationManager notificationManager = NotificationManager.create();
notificationManager.sendStartNotification();
// Set up shutdown notification to configuration server
notificationManager.setUpShutdownNotification();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,10 @@ public class HttpConfigurationCallback implements FutureCallback<SimpleHttpRespo

@Override
public void completed(SimpleHttpResponse result) {
log.info(
"Fetched configuration from configuration server and received status code {}",
result.getCode());

logStatus(result);
// Publish Event
if (result.getCode() == 200) {
if (result.getCode() == 200 || result.getCode() == 201) {
String body = result.getBodyText();

try {
Expand All @@ -45,4 +43,21 @@ public void failed(Exception ex) {
public void cancelled() {
log.info("Cancelled configuration fetch");
}

private void logStatus(SimpleHttpResponse response) {
int statusCode = response.getCode();

if (statusCode == 200) {
log.info("Configuration fetched successfully");
} else if (statusCode == 201) {
log.info(
"Connection to configuration server was successfully established. Configuration fetched successfully");
} else if (statusCode == 404) {
log.error("Configuration not found on configuration server");
} else {
log.error(
"Unexpected status code: {}. Please check the configuration server connection.",
statusCode);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,27 @@

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
import org.apache.hc.core5.net.URIBuilder;
import rocks.inspectit.gepard.agent.internal.identity.model.AgentInfo;
import rocks.inspectit.gepard.commons.model.agent.Agent;

/**
* This factory should create different HTTP requests for the configuration server to fetch
* inspectit configurations.
*/
public class HttpConfigurationFactory {

public static final String X_GEPARD_SERVICE_NAME = "x-gepard-service-name";
public static final String X_GEPARD_VM_ID = "x-gepard-vm-id";
public static final String X_GEPARD_GEPARD_VERSION = "x-gepard-gepard-version";
public static final String X_GEPARD_OTEL_VERSION = "x-gepard-otel-version";
public static final String X_GEPARD_JAVA_VERSION = "x-gepard-java-version";
public static final String X_GEPARD_START_TIME = "x-gepard-start-time";
public static final String X_GEPARD_ATTRIBUTE = "x-gepard-attribute-";

private HttpConfigurationFactory() {}

/**
Expand All @@ -23,8 +34,32 @@ private HttpConfigurationFactory() {}
*/
public static SimpleHttpRequest createConfigurationRequest(String baseUrl)
throws URISyntaxException {
URI uri = new URIBuilder(baseUrl + "/agent-configuration").build();

return SimpleRequestBuilder.get(uri).build();
AgentInfo agentInfo = AgentInfo.INFO;

URI uri = new URIBuilder(baseUrl + "/agent-configuration/" + agentInfo.getAgentId()).build();

SimpleRequestBuilder requestBuilder = SimpleRequestBuilder.get(uri);

return buildRequestWithHeaders(requestBuilder, agentInfo.getAgent()).build();
}

private static SimpleRequestBuilder buildRequestWithHeaders(
SimpleRequestBuilder requestBuilder, Agent agent) {
Map<String, String> headers =
Map.of(
X_GEPARD_SERVICE_NAME, agent.getServiceName(),
X_GEPARD_VM_ID, agent.getVmId(),
X_GEPARD_GEPARD_VERSION, agent.getGepardVersion(),
X_GEPARD_OTEL_VERSION, agent.getOtelVersion(),
X_GEPARD_JAVA_VERSION, agent.getJavaVersion(),
X_GEPARD_START_TIME, agent.getStartTime().toString());

headers.forEach(requestBuilder::addHeader);
agent
.getAttributes()
.forEach((key, value) -> requestBuilder.addHeader(X_GEPARD_ATTRIBUTE + key, value));
Copy link
Member Author

Choose a reason for hiding this comment

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

Hi @EddeCCC, @binarycoded,
Quick note: I changed the headers to lower-case, since the apache http client automatically lowercases them, when sending.
According to the http-specs, header names are case-insensitive (https://stackoverflow.com/questions/5258977/are-http-headers-case-sensitive). That might be the reason.
Long Story Short: On the the other side (config-server) you still need to check for lower-cased headers.
Was pretty painful to find that out while debugging.


return requestBuilder;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Objects;
import rocks.inspectit.gepard.agent.internal.identity.model.IdentityInfo;

Expand Down Expand Up @@ -38,13 +40,16 @@ public IdentityInfo getIdentityInfo() {
* @return the SHA3-256 hashed <code>String</code>
*/
private static String hash(String input) {
String salt = generateSalt();
String saltedInput = salt + input;

MessageDigest messageDigest;
try {
messageDigest = MessageDigest.getInstance("SHA3-256");
} catch (NoSuchAlgorithmException e) {
throw new UnsupportedOperationException("SHA3-256 not supported", e);
}
byte[] bytes = messageDigest.digest(input.getBytes(StandardCharsets.UTF_8));
byte[] bytes = messageDigest.digest(saltedInput.getBytes(StandardCharsets.UTF_8));
StringBuilder hexString = new StringBuilder(2 * bytes.length);
for (byte b : bytes) {
String hex = Integer.toHexString(0xff & b);
Expand All @@ -55,4 +60,12 @@ private static String hash(String input) {
}
return hexString.toString();
}

/** Generates a secure random 128bit string/salt */
private static String generateSalt() {
byte[] salt = new byte[16];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(salt);
return Base64.getEncoder().encodeToString(salt);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,10 @@ public class NotificationManager {

private final String serverBaseUrl;

private final StartNotifier startNotifier;

private final ShutdownNotifier shutdownNotifier;

private NotificationManager(String serverBaseUrl) {
this.serverBaseUrl = serverBaseUrl;
this.startNotifier = new StartNotifier();
this.shutdownNotifier = new ShutdownNotifier();
}

Expand All @@ -35,22 +32,6 @@ public static NotificationManager create() {
return new NotificationManager(url);
}

/**
* Sends a message to the configuration server, to notify it about this agent starting, if a
* configuration server url was provided.
*
* @return true, if the notification was executed successfully
*/
public boolean sendStartNotification() {
boolean successful = false;
if (serverBaseUrl.isEmpty()) log.info("No configuration server url was provided");
else {
log.info("Sending start notification to configuration server with url: {}", serverBaseUrl);
successful = startNotifier.sendNotification(serverBaseUrl);
}
return successful;
}

/**
* Sets up a shutdown notification to the configuration server, if a configuration server url was
* provided.
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,6 @@ public class NotificationFactory {

private NotificationFactory() {}

/**
* Create an HTTP post request to notify the configuration server about the starting agent and
* it's information.
*
* @param baseUrl the base url of the configuration server
* @return the HTTP post request, containing agent information
* @throws URISyntaxException invalid uri
* @throws JsonProcessingException corrupted agent information
*/
public static SimpleHttpRequest createStartNotification(String baseUrl)
throws URISyntaxException, JsonProcessingException {
String agentId = AgentInfo.INFO.getAgentId();
URI uri = new URI(baseUrl + "/connections/" + agentId);
String agentInfoString = mapper.writeValueAsString(AgentInfo.INFO.getAgent());

return SimpleRequestBuilder.post(uri)
.setBody(agentInfoString, ContentType.APPLICATION_JSON)
.setHeader("content-type", "application/json")
.build();
}

/**
* Create an HTTP put request to notify the configuration server about the shutting down agent.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@
import org.mockserver.model.HttpError;
import rocks.inspectit.gepard.agent.MockServerTestBase;
import rocks.inspectit.gepard.agent.configuration.persistence.ConfigurationPersistence;
import rocks.inspectit.gepard.agent.internal.identity.model.AgentInfo;

class HttpConfigurationPollerTest extends MockServerTestBase {

private static HttpConfigurationPoller poller;

private static ConfigurationPersistence persistence;

private static String AGENT_ID = AgentInfo.INFO.getAgentId();

@BeforeEach
void beforeEach() {
persistence = mock(ConfigurationPersistence.class);
Expand All @@ -33,7 +36,7 @@ void afterEach() {
@Test
void runDoesntLoadLocalConfigurationWhenPollingIsSuccessful() {
mockServer
.when(request().withMethod("GET").withPath("/api/v1/agent-configuration"))
.when(request().withMethod("GET").withPath("/api/v1/agent-configuration/" + AGENT_ID))
.respond(response().withStatusCode(200));
poller.run();
verify(persistence, never()).loadLocalConfiguration();
Expand All @@ -52,7 +55,7 @@ void runLoadsLocalConfigurationOnlyOnceWhenConnectionFailed() {
@Test
void configurationRequestIsSentSuccessfully() {
mockServer
.when(request().withMethod("GET").withPath("/api/v1/agent-configuration"))
.when(request().withMethod("GET").withPath("/api/v1/agent-configuration/" + AGENT_ID))
.respond(response().withStatusCode(200));

boolean successful = poller.pollConfiguration();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
import java.io.IOException;
import org.apache.commons.io.FileUtils;
import org.mockserver.client.MockServerClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.MockServerContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.utility.DockerImageName;
Expand All @@ -22,8 +20,6 @@
*/
public class ConfigurationServerMock {

private static final Logger logger = LoggerFactory.getLogger(ConfigurationServerMock.class);

private MockServerContainer server;
private MockServerClient serverClient;

Expand Down Expand Up @@ -56,7 +52,7 @@ public void configServerSetup(String config_path) throws IOException {
String body = FileUtils.readFileToString(file, "UTF-8");

serverClient
.when(request().withMethod("GET").withPath("/api/v1/agent-configuration"))
.when(request().withMethod("GET").withPath("/api/v1/agent-configuration/.*"))
.respond(response().withStatusCode(200).withBody(body));

serverClient
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,22 @@

import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.lang.reflect.Field;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import rocks.inspectit.gepard.agent.internal.identity.model.IdentityInfo;

@ExtendWith(MockitoExtension.class)
class IdentityManagerTest {

@BeforeEach
public void setup() throws NoSuchFieldException, IllegalAccessException {
Field instance = IdentityManager.class.getDeclaredField("instance");
instance.setAccessible(true);
instance.set(null, null);
}

@Test
void testCreateIdentityManagerSuccessfully() {
RuntimeMXBean mockRuntimeMXBean = mock(RuntimeMXBean.class);
Expand All @@ -33,9 +39,7 @@ void testCreateIdentityManagerSuccessfully() {

assertNotNull(identityInfo);
assertEquals("12345@mockedHostName", identityInfo.vmId());
assertEquals(
"d29aca592fc2071bcef6577d649071d4d54a8ae6cd5c0be0e51f28af2867f207",
identityInfo.agentId());
assertNotNull(identityInfo.agentId());
}
}
}
Loading
Loading