Skip to content
New issue

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

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

Already on GitHub? # to your account

feat #1124: Create agent command for fetching details about an agent #1473

Merged
merged 1 commit into from
Jun 23, 2022
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
@@ -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