Skip to content

Commit

Permalink
add tests for AgentHealth monitoring
Browse files Browse the repository at this point in the history
  • Loading branch information
EddeCCC committed Nov 29, 2023
1 parent 77d1e45 commit 2540563
Show file tree
Hide file tree
Showing 13 changed files with 547 additions and 69 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package rocks.inspectit.ocelot.core.config.propertysources.http;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
Expand Down Expand Up @@ -31,6 +32,7 @@ public class HttpConfigurationPoller extends DynamicallyActivatableService imple
/**
* The state of the used HTTP property source configuration.
*/
@Getter
private HttpPropertySourceState currentState;

public HttpConfigurationPoller() {
Expand Down Expand Up @@ -87,4 +89,9 @@ public void updateAgentHealthState(AgentHealthState agentHealth) {
currentState.updateAgentHealthState(agentHealth);
}
}

public AgentHealthState getCurrentAgentHealthState() {
if(currentState == null) return null;
return currentState.getAgentHealth();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ public class HttpPropertySourceState {
@Getter
private boolean firstFileWriteAttemptSuccessful = true;

@Getter
private AgentHealthState agentHealth = AgentHealthState.defaultState();

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package rocks.inspectit.ocelot.core.utils;
package rocks.inspectit.ocelot.core.selfmonitoring;

import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import rocks.inspectit.ocelot.commons.models.health.AgentHealthIncident;
Expand All @@ -16,12 +16,12 @@
* As soon as incidents are put into a full queue, old incidents will be removed to create space
*/
@Component
@RequiredArgsConstructor
public class AgentHealthIncidentBuffer {

private final ApplicationContext ctx;

private final InspectitEnvironment env;
@Autowired
private ApplicationContext ctx;
@Autowired
private InspectitEnvironment env;

private final ConcurrentLinkedQueue<AgentHealthIncident> buffer = new ConcurrentLinkedQueue<>();

Expand Down Expand Up @@ -49,4 +49,8 @@ public List<AgentHealthIncident> asList() {
Collections.reverse(incidentList);
return incidentList;
}

public void clear() {
buffer.clear();
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
package rocks.inspectit.ocelot.core.selfmonitoring;

import ch.qos.logback.classic.Level;
import com.google.common.annotations.VisibleForTesting;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import rocks.inspectit.ocelot.commons.models.health.AgentHealth;
import rocks.inspectit.ocelot.commons.models.health.AgentHealthIncident;
import rocks.inspectit.ocelot.core.config.InspectitEnvironment;
import rocks.inspectit.ocelot.core.selfmonitoring.event.models.AgentHealthChangedEvent;
import rocks.inspectit.ocelot.core.utils.AgentHealthIncidentBuffer;

import javax.annotation.PostConstruct;
import java.time.Duration;
Expand All @@ -27,12 +25,16 @@
*/
@Component
@Slf4j
@RequiredArgsConstructor
public class AgentHealthManager {

private final ApplicationContext ctx;

private final ScheduledExecutorService executor;
@Autowired
private ApplicationContext ctx;
@Autowired
private ScheduledExecutorService executor;
@Autowired
private InspectitEnvironment env;
@Autowired
private AgentHealthIncidentBuffer healthIncidentBuffer;

/**
* Map of {@code eventClass -> agentHealth}, whereas the {@code agentHealth} is reset whenever an event of type
Expand All @@ -49,20 +51,25 @@ public class AgentHealthManager {

private AgentHealth lastNotifiedHealth = AgentHealth.OK;

private final InspectitEnvironment env;

private final AgentHealthIncidentBuffer healthIncidentBuffer;

@PostConstruct
@VisibleForTesting
void startHealthCheckScheduler() {
checkHealthAndSchedule();
}

public List<AgentHealthIncident> getHistory() {
public List<AgentHealthIncident> getIncidentHistory() {
return healthIncidentBuffer.asList();
}

/**
* Notifies the AgentHealthManager about an eventHealth.
* The manager determines, whether the event is invalidatable or times out.
*
* @param eventHealth health of event
* @param invalidator class, which created the invalidatable eventHealth
* @param loggerName name of the logger, who created the event
* @param message message of the event
*/
public void notifyAgentHealth(AgentHealth eventHealth, Class<?> invalidator, String loggerName, String message) {
if (invalidator == null)
handleTimeoutHealth(eventHealth, loggerName, message);
Expand All @@ -88,6 +95,11 @@ private void handleTimeoutHealth(AgentHealth eventHealth, String loggerName, Str
triggerAgentHealthChangedEvent(loggerName, fullEventMessage, isNotInfo);
}

/**
* Invalidates an invalidatable eventHealth and creates a new AgentHealthIncident
* @param eventClass class, which created the invalidatable eventHealth
* @param eventMessage message of the event
*/
public void invalidateIncident(Class<?> eventClass, String eventMessage) {
invalidatableHealth.remove(eventClass);
triggerAgentHealthChangedEvent(eventClass.getTypeName(), eventMessage);
Expand All @@ -101,7 +113,8 @@ public void invalidateIncident(Class<?> eventClass, String eventMessage) {
* <li>exists -> run until that timeout is over</li>
* </ul>
*/
private void checkHealthAndSchedule() {
@VisibleForTesting
void checkHealthAndSchedule() {
triggerAgentHealthChangedEvent(AgentHealthManager.class.getCanonicalName(), "Checking timed out agent healths");

Duration validityPeriod = env.getCurrentConfig().getSelfMonitoring().getAgentHealth().getValidityPeriod();
Expand Down Expand Up @@ -180,4 +193,20 @@ public AgentHealth getCurrentHealth() {
.orElse(AgentHealth.OK);
return AgentHealth.mostSevere(generalHealth, invHealth);
}

/**
* THIS METHOD SHOULD ONLY BE USED FOR TESTING.
* It allows to specify a custom validityPeriod, which by default has to be at least 60s.
* With customizing the period, you can reduce the amount of waiting time for tests.
*/
@VisibleForTesting
void handleTimeoutHealthTesting(AgentHealth eventHealth, String loggerName, String eventMassage, Duration validityPeriod) {
boolean isNotInfo = eventHealth.isMoreSevereOrEqualTo(AgentHealth.WARNING);

if (isNotInfo) {
generalHealthTimeouts.put(eventHealth, LocalDateTime.now().plus(validityPeriod));
}
String fullEventMessage = eventMassage + ". This status is valid for " + validityPeriod;
triggerAgentHealthChangedEvent(loggerName, fullEventMessage, isNotInfo);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class PollerWritingHealthEventListener implements HealthEventListener {

@Override
public void onAgentHealthEvent(AgentHealthChangedEvent event) {
List<AgentHealthIncident> incidentHistory = agentHealthManager.getHistory();
List<AgentHealthIncident> incidentHistory = agentHealthManager.getIncidentHistory();

AgentHealthState state = new AgentHealthState(event.getNewHealth(), event.getSource().toString(), event.getMessage(), incidentHistory);
httpConfigurationPoller.updateAgentHealthState(state);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
import rocks.inspectit.ocelot.commons.models.health.AgentHealthIncident;
import rocks.inspectit.ocelot.core.utils.AgentHealthIncidentBuffer;
import rocks.inspectit.ocelot.core.selfmonitoring.AgentHealthIncidentBuffer;

import java.util.List;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package rocks.inspectit.ocelot.core.selfmonitoring;

import org.junit.jupiter.api.BeforeEach;
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.context.ApplicationContext;
import rocks.inspectit.ocelot.commons.models.health.AgentHealth;
import rocks.inspectit.ocelot.commons.models.health.AgentHealthIncident;
import rocks.inspectit.ocelot.core.SpringTestBase;
import rocks.inspectit.ocelot.core.config.InspectitEnvironment;
import rocks.inspectit.ocelot.core.selfmonitoring.event.models.AgentHealthIncidentAddedEvent;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
public class AgentHealthIncidentBufferTest {

@InjectMocks
private AgentHealthIncidentBuffer incidentBuffer;
@Mock
private ApplicationContext ctx;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private InspectitEnvironment env;

private AgentHealthIncident incident;

@BeforeEach
void setUp() {
when(env.getCurrentConfig().getSelfMonitoring().getAgentHealth().getIncidentBufferSize()).thenReturn(2);
incident = new AgentHealthIncident("2001-01-01", AgentHealth.WARNING, this.getClass().getCanonicalName(), "Mock message", true);
}
@Test
void verifyBufferSize() {
incidentBuffer.put(incident);
incidentBuffer.put(incident);
incidentBuffer.put(incident);

verify(ctx, times(3)).publishEvent(any(AgentHealthIncidentAddedEvent.class));
}

@Test
void verifyEventPublisher() {
incidentBuffer.put(incident);
incidentBuffer.put(incident);
incidentBuffer.put(incident);

verify(ctx, times(3)).publishEvent(any(AgentHealthIncidentAddedEvent.class));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package rocks.inspectit.ocelot.core.selfmonitoring;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import rocks.inspectit.ocelot.commons.models.health.AgentHealth;
import rocks.inspectit.ocelot.core.SpringTestBase;
import rocks.inspectit.ocelot.core.config.propertysources.http.HttpConfigurationPoller;

import java.time.Duration;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
* Integration tests {@link AgentHealthManager}
*/
public class AgentHealthManagerIntTest extends SpringTestBase {

@Autowired
private AgentHealthManager healthManager;
@Autowired
private HttpConfigurationPoller configurationPoller;
@Autowired
private AgentHealthIncidentBuffer incidentBuffer;

@BeforeEach
void clearBuffer() {
incidentBuffer.clear();
}

@Nested
class InvalidatableHealth {

@Test
void verifyAgentHealthUpdating() {
AgentHealth currentHealth = configurationPoller.getCurrentAgentHealthState().getHealth();
int bufferSize = healthManager.getIncidentHistory().size();
assertEquals(currentHealth, AgentHealth.OK);
assertEquals(bufferSize, 0);

healthManager.notifyAgentHealth(AgentHealth.WARNING, this.getClass(), this.getClass().getName(), "Mock message");

currentHealth = configurationPoller.getCurrentAgentHealthState().getHealth();
bufferSize = healthManager.getIncidentHistory().size();
assertEquals(currentHealth, AgentHealth.WARNING);
assertEquals(bufferSize, 1);

healthManager.notifyAgentHealth(AgentHealth.ERROR, this.getClass(), this.getClass().getName(), "Mock message");

currentHealth = configurationPoller.getCurrentAgentHealthState().getHealth();
bufferSize = healthManager.getIncidentHistory().size();
assertEquals(currentHealth, AgentHealth.ERROR);
assertEquals(bufferSize, 2);
}

@Test
void verifyAgentHealthInvalidation() {
AgentHealth currentHealth = configurationPoller.getCurrentAgentHealthState().getHealth();
int bufferSize = healthManager.getIncidentHistory().size();
assertEquals(currentHealth, AgentHealth.OK);
assertEquals(bufferSize, 0);

healthManager.notifyAgentHealth(AgentHealth.ERROR, this.getClass(), this.getClass().getName(), "Mock message");

currentHealth = configurationPoller.getCurrentAgentHealthState().getHealth();
bufferSize = healthManager.getIncidentHistory().size();
assertEquals(currentHealth, AgentHealth.ERROR);
assertEquals(bufferSize, 1);

healthManager.invalidateIncident(this.getClass(), "Mock invalidation");

currentHealth = configurationPoller.getCurrentAgentHealthState().getHealth();
bufferSize = healthManager.getIncidentHistory().size();
assertEquals(currentHealth, AgentHealth.OK);
assertEquals(bufferSize, 2);
}
}

@Nested
class TimeoutHealth {

@Test
void verifyAgentHealthUpdating() throws InterruptedException {
AgentHealth currentHealth = configurationPoller.getCurrentAgentHealthState().getHealth();
int bufferSize = healthManager.getIncidentHistory().size();
assertEquals(currentHealth, AgentHealth.OK);
assertEquals(bufferSize, 0);

// Use custom method for testing, to reduce the validityPeriod
// This method should not be used outside of tests!
healthManager.handleTimeoutHealthTesting(AgentHealth.WARNING, this.getClass().getName(),
"Mock message", Duration.ofSeconds(10));

currentHealth = configurationPoller.getCurrentAgentHealthState().getHealth();
bufferSize = healthManager.getIncidentHistory().size();
assertEquals(currentHealth, AgentHealth.WARNING);
assertEquals(bufferSize, 1);

// wait 61s for time out (which has to be at least 60s)
Thread.sleep(61000);

currentHealth = configurationPoller.getCurrentAgentHealthState().getHealth();
bufferSize = healthManager.getIncidentHistory().size();
assertEquals(currentHealth, AgentHealth.OK);
assertEquals(bufferSize, 3);
}
}
}
Loading

0 comments on commit 2540563

Please sign in to comment.