Skip to content

Commit

Permalink
feat #1124: Create agent command for fetching details about an agent (#…
Browse files Browse the repository at this point in the history
…1473)

Co-authored-by: Simon Braitsch <simon.braitsch@novatec-gmbh.de>
  • Loading branch information
sbraitsch and s-braitsch authored Jun 23, 2022
1 parent 94fdf8d commit 945412d
Show file tree
Hide file tree
Showing 10 changed files with 397 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package rocks.inspectit.ocelot.agentcommunication.handlers.impl;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.async.DeferredResult;
import rocks.inspectit.ocelot.agentcommunication.handlers.CommandHandler;
import rocks.inspectit.ocelot.commons.models.command.Command;
import rocks.inspectit.ocelot.commons.models.command.CommandResponse;
import rocks.inspectit.ocelot.commons.models.command.impl.EnvironmentCommand;
import rocks.inspectit.ocelot.config.model.InspectitServerSettings;

import java.time.Duration;

/**
* Handler for the Agent Environment command.
*/
@Slf4j
@Component
public class EnvironmentCommandHandler implements CommandHandler {

@Autowired
private InspectitServerSettings configuration;

/**
* Checks if the given {@link Command} is an instance of {@link EnvironmentCommand}.
*
* @param command The command which should be checked.
* @return True if the given command is an instance of {@link EnvironmentCommand}.
*/
@Override
public boolean canHandle(Command command) {
return command instanceof EnvironmentCommand;
}

/**
* Checks if the given {@link CommandResponse} is an instance of {@link EnvironmentCommand.Response}.
*
* @param response The response which should be checked.
* @return True if the given response is an instance of {@link EnvironmentCommand.Response}.
*/
@Override
public boolean canHandle(CommandResponse response) {
return response instanceof EnvironmentCommand.Response;
}

/**
* Prepares an instance of {@link DeferredResult} by setting the Timeout as well as the onTimeout function.
* This onTimeout function sets the {@link ResponseEntity} to the status REQUEST_TIMEOUT.
*
* @param agentId The id of the agent the command is meant for.
* @param command The command to be Executed.
* @return An instance of {@link DeferredResult} with a set timeout value and a set timeout function.
*/
@Override
public DeferredResult<ResponseEntity<?>> prepareResponse(String agentId, Command command) {
if (!canHandle(command)) {
throw new IllegalArgumentException("EnvironmentCommandHandler can only handle commands of type EnvironmentCommand.");
}

Duration responseTimeout = configuration.getAgentCommand().getResponseTimeout();
DeferredResult<ResponseEntity<?>> deferredResult = new DeferredResult<>(responseTimeout.toMillis());
deferredResult.onTimeout(() -> ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT).build());

return deferredResult;
}

/**
* Takes an instance of {@link CommandResponse} as well as an instance of {@link DeferredResult}.
* Sets the {@link ResponseEntity} of the {@link DeferredResult} according to the
* Environment Information received from the respective Agent."
*
* @param response The {@link CommandResponse} to be handled.
* @param result The {@link DeferredResult} the response should be written in.
*/
@Override
public void handleResponse(CommandResponse response, DeferredResult<ResponseEntity<?>> result) {
EnvironmentCommand.Response environmentResponse = (EnvironmentCommand.Response) response;
result.setResult(ResponseEntity.ok().body(environmentResponse.getEnvironment()));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import rocks.inspectit.ocelot.agentcommunication.AgentCommandDispatcher;
import rocks.inspectit.ocelot.commons.models.command.impl.EnvironmentCommand;
import rocks.inspectit.ocelot.commons.models.command.impl.ListClassesCommand;
import rocks.inspectit.ocelot.commons.models.command.impl.LogsCommand;
import rocks.inspectit.ocelot.commons.models.command.impl.PingCommand;
Expand Down Expand Up @@ -49,4 +50,10 @@ public DeferredResult<ResponseEntity<?>> listClasses(@RequestParam(value = "agen
ListClassesCommand listClassesCommand = new ListClassesCommand(query);
return commandDispatcher.dispatchCommand(agentId, listClassesCommand);
}

@GetMapping(value = "command/environment")
public DeferredResult<ResponseEntity<?>> environment(@RequestParam(value = "agent-id") String agentId) throws ExecutionException {
EnvironmentCommand environmentCommand = new EnvironmentCommand();
return commandDispatcher.dispatchCommand(agentId, environmentCommand);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package rocks.inspectit.ocelot.agentcommunication.handlers.impl;

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.ResponseEntity;
import org.springframework.web.context.request.async.DeferredResult;
import rocks.inspectit.ocelot.commons.models.command.Command;
import rocks.inspectit.ocelot.commons.models.command.impl.EnvironmentCommand;
import rocks.inspectit.ocelot.config.model.InspectitServerSettings;

import java.time.Duration;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
public class EnvironmentCommandHandlerTest {

@InjectMocks
EnvironmentCommandHandler environmentCommandHandler;

@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private InspectitServerSettings configuration;

@Nested
class PrepareResponse {

@Test
public void cantHandleCommand() {
Command command = null;
String agentId = "test-id";

try {
environmentCommandHandler.prepareResponse(agentId, command);
} catch (IllegalArgumentException e) {
assertThat(e.getMessage()).isEqualTo("EnvironmentCommandHandler can only handle commands of type EnvironmentCommand.");
}

}

@Test
public void preparesResponse() {
when(configuration.getAgentCommand().getResponseTimeout()).thenReturn(Duration.ofMinutes(1));
EnvironmentCommand command = new EnvironmentCommand();
String agentId = "test-id";

DeferredResult<ResponseEntity<?>> result = environmentCommandHandler.prepareResponse(agentId, command);

assertThat(result).isNotNull();

}

}

@Nested
class HandleResponse {

@Test
public void handlesResponse() {
EnvironmentCommand.Response mockResponse = mock(EnvironmentCommand.Response.class);
DeferredResult<ResponseEntity<?>> result = new DeferredResult<>();

environmentCommandHandler.handleResponse(mockResponse, result);

assertThat(result.getResult()).isEqualTo(ResponseEntity.ok().build());
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.AllArgsConstructor;
import lombok.Data;
import rocks.inspectit.ocelot.commons.models.command.impl.EnvironmentCommand;
import rocks.inspectit.ocelot.commons.models.command.impl.ListClassesCommand;
import rocks.inspectit.ocelot.commons.models.command.impl.PingCommand;
import rocks.inspectit.ocelot.commons.models.command.impl.LogsCommand;
Expand All @@ -20,7 +21,8 @@
@JsonSubTypes(value = {
@JsonSubTypes.Type(name = PingCommand.TYPE_IDENTIFIER, value = PingCommand.class),
@JsonSubTypes.Type(name = ListClassesCommand.TYPE_IDENTIFIER, value = ListClassesCommand.class),
@JsonSubTypes.Type(name = LogsCommand.TYPE_IDENTIFIER, value = LogsCommand.class)
@JsonSubTypes.Type(name = LogsCommand.TYPE_IDENTIFIER, value = LogsCommand.class),
@JsonSubTypes.Type(name = EnvironmentCommand.TYPE_IDENTIFIER, value = EnvironmentCommand.class)
})
public abstract class Command {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import rocks.inspectit.ocelot.commons.models.command.impl.EnvironmentCommand;
import rocks.inspectit.ocelot.commons.models.command.impl.ListClassesCommand;
import rocks.inspectit.ocelot.commons.models.command.impl.LogsCommand;
import rocks.inspectit.ocelot.commons.models.command.impl.PingCommand;
Expand All @@ -22,6 +23,7 @@
@JsonSubTypes.Type(name = PingCommand.TYPE_IDENTIFIER, value = PingCommand.Response.class),
@JsonSubTypes.Type(name = ListClassesCommand.TYPE_IDENTIFIER, value = ListClassesCommand.Response.class),
@JsonSubTypes.Type(name = LogsCommand.TYPE_IDENTIFIER, value = LogsCommand.Response.class),
@JsonSubTypes.Type(name = EnvironmentCommand.TYPE_IDENTIFIER, value = EnvironmentCommand.Response.class),
})
public abstract class CommandResponse {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package rocks.inspectit.ocelot.commons.models.command.impl;

import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import rocks.inspectit.ocelot.commons.models.command.Command;
import rocks.inspectit.ocelot.commons.models.command.CommandResponse;

import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
* Represents an Environment-Command. Environment commands are used to receive the details of a certain agent.
* These details include environment variables, system properties and JVM arguments.
*/
@Data
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class EnvironmentCommand extends Command {

/**
* Type identifier for JSON serialization.
*/
public static final String TYPE_IDENTIFIER = "environment";

/**
* Represents a response to the {@link EnvironmentCommand}.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public static class Response extends CommandResponse {
private EnvironmentDetail environment;
}

/**
* Represents the structure of the returned environment details
*/
@Data
public static class EnvironmentDetail {
@JsonPropertyOrder(alphabetic = true)
private Map<String, String> environmentVariables;
@JsonPropertyOrder(alphabetic = true)
private Properties systemProperties;
private List<String> jvmArguments;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public class HttpCommandFetcher {
/**
* Object mapper for serializing command responses.
*/
private final ObjectMapper objectMapper = new ObjectMapper().enableDefaultTyping();
private final ObjectMapper objectMapper = new ObjectMapper();

/**
* The prefix which is used for the meta information HTTP headers.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package rocks.inspectit.ocelot.core.command.handler.impl;

import org.springframework.stereotype.Component;
import rocks.inspectit.ocelot.commons.models.command.Command;
import rocks.inspectit.ocelot.commons.models.command.CommandResponse;
import rocks.inspectit.ocelot.commons.models.command.impl.EnvironmentCommand;
import rocks.inspectit.ocelot.core.command.handler.CommandExecutor;

import java.lang.management.ManagementFactory;

/**
* Executor for executing {@link EnvironmentCommand}s.
*/
@Component
public class EnvironmentCommandExecutor implements CommandExecutor {

/**
* Checks if the given {@link Command} is an instance of {@link EnvironmentCommand}.
*
* @param command The {@link Command} to be checked.
* @return True if the given {@link Command} is an instance of {@link EnvironmentCommand}.
*/
@Override
public boolean canExecute(Command command) {
return command instanceof EnvironmentCommand;
}

/**
* Executes the given {@link Command}. Throws an {@link IllegalArgumentException} if the given command is either null
* or not handled by this implementation.
* Populates an instance of {@link EnvironmentCommand.EnvironmentDetail} with the respective values from the agent.
*
* @param command The command to be executed.
* @return An instance of {@link EnvironmentCommand} with alive set to true and the id of the given command.
*/
@Override
public CommandResponse execute(Command command) {
if (!canExecute(command)) {
String exceptionMessage = "Invalid command type. Executor does not support commands of type " + command.getClass();
throw new IllegalArgumentException(exceptionMessage);
}

EnvironmentCommand.Response response = new EnvironmentCommand.Response();
response.setCommandId(command.getCommandId());

EnvironmentCommand.EnvironmentDetail body = new EnvironmentCommand.EnvironmentDetail();
body.setEnvironmentVariables(System.getenv());
body.setSystemProperties(System.getProperties());
body.setJvmArguments(ManagementFactory.getRuntimeMXBean().getInputArguments());

response.setEnvironment(body);

return response;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ protected void init() {
if (!systemCpuUsage.isPresent()) {
log.info(METRIC_UNAVAILABLE, SYSTEM_USAGE_METRIC_FULL_NAME, METH_SYS_CPU_LOAD);
}
if (!systemCpuUsage.isPresent()) {
if (!processCpuUsage.isPresent()) {
log.info(METRIC_UNAVAILABLE, PROCESS_USAGE_METRIC_FULL_NAME, METH_PROC_CPU_LOAD);
}
if (!averageLoadAvailable) {
Expand Down
Loading

0 comments on commit 945412d

Please # to comment.