Skip to content

Commit

Permalink
fix sentinel bug when pattern matching on place (#636)
Browse files Browse the repository at this point in the history
  • Loading branch information
dev-mlb authored Dec 27, 2023
1 parent e8c0768 commit 6fca0b8
Show file tree
Hide file tree
Showing 6 changed files with 349 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.slf4j.LoggerFactory;

import java.lang.invoke.MethodHandles;
import java.util.Collection;

/**
* Looks at the place that has been running for the least amount of time.
Expand All @@ -28,9 +29,10 @@ public AllMaxTime(String name, String place, String timeLimit, String threshold)
* @param placeAgentStats the stats of a place that is currently processing
* @return true if any places in mobile agents are over the configured time limit, false otherwise
*/
protected boolean overTimeLimit(Protocol.PlaceAgentStats placeAgentStats) {
logger.debug("Testing timeLimit for place={}, minTime={}, timeLimit={}", place, placeAgentStats.getMinTimeInPlace(), timeLimit);
return placeAgentStats.getMinTimeInPlace() >= this.timeLimit;
protected boolean overTimeLimit(Collection<Protocol.PlaceAgentStats> placeAgentStats) {
long minTimeInPlace = placeAgentStats.stream().mapToLong(Protocol.PlaceAgentStats::getMinTimeInPlace).min().orElse(0);
logger.debug("Testing timeLimit for place={}, minTime={}, timeLimit={}", place, minTimeInPlace, timeLimit);
return minTimeInPlace >= this.timeLimit;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.slf4j.LoggerFactory;

import java.lang.invoke.MethodHandles;
import java.util.Collection;

/**
* Looks at the place that has been running for the most amount of time.
Expand All @@ -28,9 +29,10 @@ public AnyMaxTime(String name, String place, String timeLimit, String threshold)
* @param placeAgentStats the stats of a place that is currently processing
* @return true if any places in mobile agents are over the configured time limit, false otherwise
*/
protected boolean overTimeLimit(Protocol.PlaceAgentStats placeAgentStats) {
logger.debug("Testing timeLimit for place={}, maxTime={}, timeLimit={}", place, placeAgentStats.getMaxTimeInPlace(), timeLimit);
return placeAgentStats.getMaxTimeInPlace() >= this.timeLimit;
protected boolean overTimeLimit(Collection<Protocol.PlaceAgentStats> placeAgentStats) {
long maxTimeInPlace = placeAgentStats.stream().mapToLong(Protocol.PlaceAgentStats::getMaxTimeInPlace).max().orElse(0);
logger.debug("Testing timeLimit for place={}, maxTime={}, timeLimit={}", place, maxTimeInPlace, timeLimit);
return maxTimeInPlace >= this.timeLimit;
}

}
15 changes: 10 additions & 5 deletions src/main/java/emissary/core/sentinel/protocols/rules/Rule.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@

import java.lang.invoke.MethodHandles;
import java.util.Collection;
import java.util.List;
import java.util.StringJoiner;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public abstract class Rule {

Expand Down Expand Up @@ -61,7 +63,9 @@ public Rule(String name, String place, String timeLimit, String threshold) {
* @return true if conditions are met, false otherwise
*/
public boolean condition(Collection<Protocol.PlaceAgentStats> placeAgentStats) {
return placeAgentStats.stream().filter(p -> place.matcher(p.getPlace()).matches()).anyMatch(p -> overThreshold(p) && overTimeLimit(p));
List<Protocol.PlaceAgentStats> filtered =
placeAgentStats.stream().filter(p -> place.matcher(p.getPlace()).matches()).collect(Collectors.toList());
return overThreshold(filtered) && overTimeLimit(filtered);
}

/**
Expand All @@ -70,10 +74,11 @@ public boolean condition(Collection<Protocol.PlaceAgentStats> placeAgentStats) {
* @param placeAgentStats the stats of a place that is currently processing
* @return true if the number of mobile agents stuck on the place is over the threshold, false otherwise
*/
protected boolean overThreshold(Protocol.PlaceAgentStats placeAgentStats) {
protected boolean overThreshold(Collection<Protocol.PlaceAgentStats> placeAgentStats) {
int count = placeAgentStats.stream().mapToInt(Protocol.PlaceAgentStats::getCount).sum();
int poolSize = getAgentCount();
logger.debug("Testing threshold for place={}, counter={}, poolSize={}, threshold={}", place, placeAgentStats.getCount(), poolSize, threshold);
return (double) placeAgentStats.getCount() / poolSize >= this.threshold;
logger.debug("Testing threshold for place={}, counter={}, poolSize={}, threshold={}", place, count, poolSize, threshold);
return (double) count / poolSize >= this.threshold;
}

/**
Expand All @@ -95,7 +100,7 @@ protected int getAgentCount() {
* @param placeAgentStats the stats of a place that is currently processing
* @return true if the places in mobile agents are over the configured time limit, false otherwise
*/
protected abstract boolean overTimeLimit(Protocol.PlaceAgentStats placeAgentStats);
protected abstract boolean overTimeLimit(Collection<Protocol.PlaceAgentStats> placeAgentStats);

@Override
public String toString() {
Expand Down
135 changes: 133 additions & 2 deletions src/test/java/emissary/core/sentinel/protocols/ProtocolTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
import emissary.core.sentinel.protocols.rules.Rule;
import emissary.directory.DirectoryEntry;
import emissary.directory.DirectoryPlace;
import emissary.pool.AgentPool;
import emissary.test.core.junit5.UnitTest;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
Expand All @@ -40,14 +42,17 @@
class ProtocolTest extends UnitTest {

Protocol protocol;
Rule rule1 = mock(Rule.class);
Action action = mock(Action.class);
Rule rule1;
Action action;
Map<String, Sentinel.Tracker> trackers;

@BeforeEach
public void setUp() throws Exception {
super.setUp();

rule1 = mock(Rule.class);
action = mock(Action.class);

protocol = new Protocol();
protocol.action = action;
protocol.rules.put("RULE1", rule1);
Expand Down Expand Up @@ -139,4 +144,130 @@ void protocolValidJson() {
}
}

@Nested
class RunTest extends UnitTest {

final String TO_UPPER_LOWER_PATTERN = "To(?:Lower|Upper)Place";
final String TO_LOWER_PLACE = "ToLowerPlace";
final String TO_UPPER_PLACE = "ToUpperPlace";
final int DEFAULT_POOL_SIZE = 5;

Action action;
AgentPool pool;
Map<String, Sentinel.Tracker> trackers;

@BeforeEach
public void setUp() throws Exception {
super.setUp();
action = mock(Action.class);
pool = mock(AgentPool.class);
trackers = trackers();
}

@Test
void protocol1() {
Protocol protocol = new Protocol();
protocol.action = action;
protocol.rules.put("TEST_RULE1", new AllMaxTime("rule1", TO_UPPER_LOWER_PATTERN, 5, 1.0));
protocol.rules.put("TEST_RULE2", new AnyMaxTime("rule2", TO_UPPER_LOWER_PATTERN, 30, 0.2));

testProtocol(protocol, DEFAULT_POOL_SIZE, 1);
}

@Test
void protocol2() {
Protocol protocol = new Protocol();
protocol.action = action;
protocol.rules.put("TEST_RULE1", new AllMaxTime("rule1", TO_UPPER_LOWER_PATTERN, 5, 1.0));
protocol.rules.put("TEST_RULE2", new AnyMaxTime("rule2", TO_UPPER_LOWER_PATTERN, 40, 0.2));

testProtocol(protocol, DEFAULT_POOL_SIZE, 0);
}

@Test
void protocol3() {
Protocol protocol = new Protocol();
protocol.action = action;
protocol.rules.put("TEST_RULE", new AnyMaxTime("LongRunning", TO_UPPER_LOWER_PATTERN, 30, 0.01));

testProtocol(protocol, DEFAULT_POOL_SIZE, 1);
}

@Test
void protocol4() {
Protocol protocol = new Protocol();
protocol.action = action;
protocol.rules.put("TEST_RULE", new AnyMaxTime("LongRunning", TO_LOWER_PLACE, 30, 0.01));

testProtocol(protocol, DEFAULT_POOL_SIZE, 0);
}

@Test
void protocol5() {
Protocol protocol = new Protocol();
protocol.action = action;
protocol.rules.put("TEST_RULE", new AnyMaxTime("LongRunning", TO_UPPER_PLACE, 30, 0.01));

testProtocol(protocol, DEFAULT_POOL_SIZE, 1);
}

@Test
void protocol6() {
Protocol protocol = new Protocol();
protocol.action = action;
protocol.rules.put("TEST_RULE1", new AllMaxTime("rule1", TO_UPPER_LOWER_PATTERN, 5, 1.0));
protocol.rules.put("TEST_RULE2", new AnyMaxTime("rule2", TO_UPPER_LOWER_PATTERN, 30, 0.2));

testProtocol(protocol, DEFAULT_POOL_SIZE + 1, 0);
}

void testProtocol(Protocol protocol, int poolSize, int expected) {
try (MockedStatic<AgentPool> agentPool = Mockito.mockStatic(AgentPool.class)) {
agentPool.when(AgentPool::lookup).thenReturn(pool);
when(pool.getCurrentPoolSize()).thenReturn(poolSize);
protocol.run(trackers);
verify(action, times(expected)).trigger(trackers);
}
}

Map<String, Sentinel.Tracker> trackers() {
Sentinel.Tracker agent1 = new Sentinel.Tracker("MobileAgent-01");
agent1.setAgentId("Agent-1234-testing1.txt");
agent1.setDirectoryEntryKey("http://host.domain.com:8001/ToLowerPlace");
agent1.incrementTimer(1); // init
agent1.incrementTimer(5);

Sentinel.Tracker agent2 = new Sentinel.Tracker("MobileAgent-02");
agent2.setAgentId("Agent-2345-testing2.txt");
agent2.setDirectoryEntryKey("http://host.domain.com:8001/ToLowerPlace");
agent2.incrementTimer(1); // init
agent2.incrementTimer(15);

Sentinel.Tracker agent3 = new Sentinel.Tracker("MobileAgent-03");
agent3.setAgentId("Agent-3456-testing3.txt");
agent3.setDirectoryEntryKey("http://host.domain.com:8001/ToLowerPlace");
agent3.incrementTimer(1); // init
agent3.incrementTimer(9);

Sentinel.Tracker agent4 = new Sentinel.Tracker("MobileAgent-04");
agent4.setAgentId("Agent-4567-testing4.txt");
agent4.setDirectoryEntryKey("http://host.domain.com:8001/ToUpperPlace");
agent4.incrementTimer(1); // init
agent4.incrementTimer(35);

Sentinel.Tracker agent5 = new Sentinel.Tracker("MobileAgent-05");
agent5.setAgentId("Agent-5678-testing5.txt");
agent5.setDirectoryEntryKey("http://host.domain.com:8001/ToUpperPlace");
agent5.incrementTimer(1); // init
agent5.incrementTimer(7);

return Map.of(
"MobileAgent-01", agent1,
"MobileAgent-02", agent2,
"MobileAgent-03", agent3,
"MobileAgent-04", agent4,
"MobileAgent-05", agent5);
}
}

}
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
package emissary.core.sentinel.protocols.rules;

import emissary.core.sentinel.protocols.Protocol;
import emissary.pool.AgentPool;
import emissary.test.core.junit5.UnitTest;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;

import java.util.Collection;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class AllMaxTimeTest extends UnitTest {

Protocol.PlaceAgentStats placeAgentStats;
Collection<Protocol.PlaceAgentStats> placeAgentStats;

@BeforeEach
public void setUp() throws Exception {
super.setUp();
placeAgentStats = new Protocol.PlaceAgentStats("TestPlace");
Protocol.PlaceAgentStats stats = new Protocol.PlaceAgentStats("TestPlace");
placeAgentStats = List.of(stats);
for (int i = 1; i < 6; ++i) {
placeAgentStats.update(i);
stats.update(i);
}
}

Expand Down Expand Up @@ -51,4 +61,86 @@ void notOverTimeLimit() {
Rule rule = new AllMaxTime("rule1", "TestPlace", 2, 0.75);
assertFalse(rule.overTimeLimit(placeAgentStats));
}

@Nested
class ConditionTest extends UnitTest {

final String TO_UPPER_LOWER_PATTERN = "To(?:Lower|Upper)Place";
final String TO_LOWER_PLACE = "ToLowerPlace";
final String TO_UPPER_PLACE = "ToUpperPlace";
final int DEFAULT_POOL_SIZE = 5;
final int DEFAULT_TIME_LIMIT = 5;

AgentPool pool;
List<Protocol.PlaceAgentStats> stats;

@BeforeEach
public void setUp() throws Exception {
super.setUp();
pool = mock(AgentPool.class);
stats = stats();
}

@Test
void condition1() {
assertTrue(testRule(TO_UPPER_LOWER_PATTERN, DEFAULT_TIME_LIMIT, 1.0, DEFAULT_POOL_SIZE));
}

@Test
void condition2() {
assertFalse(testRule(TO_UPPER_LOWER_PATTERN, DEFAULT_TIME_LIMIT, 1.0, DEFAULT_POOL_SIZE + 1));
}

@Test
void condition3() {
assertTrue(testRule(TO_LOWER_PLACE, DEFAULT_TIME_LIMIT, 0.5, DEFAULT_POOL_SIZE));
}

@Test
void condition4() {
assertFalse(testRule(TO_UPPER_PLACE, DEFAULT_TIME_LIMIT, 0.75, DEFAULT_POOL_SIZE));
}

@Test
void condition5() {
assertFalse(testRule(TO_UPPER_LOWER_PATTERN, DEFAULT_TIME_LIMIT + 1, 1.0, DEFAULT_POOL_SIZE));
}

@Test
void condition6() {
assertFalse(testRule(TO_UPPER_LOWER_PATTERN, DEFAULT_TIME_LIMIT + 1, 0.75, DEFAULT_POOL_SIZE));
}

@Test
void condition7() {
assertTrue(testRule(TO_UPPER_LOWER_PATTERN, DEFAULT_TIME_LIMIT, 0.5, DEFAULT_POOL_SIZE));
}

@Test
void condition8() {
assertFalse(testRule(TO_LOWER_PLACE, DEFAULT_TIME_LIMIT, 1.0, DEFAULT_POOL_SIZE));
}

boolean testRule(String matcher, int time, double threshold, int poolSize) {
Rule rule = new AllMaxTime("rule", matcher, time, threshold);
try (MockedStatic<AgentPool> agentPool = Mockito.mockStatic(AgentPool.class)) {
agentPool.when(AgentPool::lookup).thenReturn(pool);
when(pool.getCurrentPoolSize()).thenReturn(poolSize);
return rule.condition(stats);
}
}

List<Protocol.PlaceAgentStats> stats() {
Protocol.PlaceAgentStats lowerStats = new Protocol.PlaceAgentStats("ToLowerPlace");
lowerStats.update(DEFAULT_TIME_LIMIT); // MobileAgent-01
lowerStats.update(DEFAULT_TIME_LIMIT + 1); // MobileAgent-02
lowerStats.update(DEFAULT_TIME_LIMIT + 4); // MobileAgent-03

Protocol.PlaceAgentStats upperStats = new Protocol.PlaceAgentStats("ToUpperPlace");
upperStats.update(DEFAULT_TIME_LIMIT); // MobileAgent-04
upperStats.update(DEFAULT_TIME_LIMIT + 3); // MobileAgent-05

return List.of(lowerStats, upperStats);
}
}
}
Loading

0 comments on commit 6fca0b8

Please sign in to comment.