Skip to content

Commit

Permalink
#621 Added metric to circuit breaker entries
Browse files Browse the repository at this point in the history
  • Loading branch information
mcweba committed Dec 18, 2024
1 parent 87ae63d commit 6435599
Show file tree
Hide file tree
Showing 11 changed files with 127 additions and 58 deletions.
2 changes: 2 additions & 0 deletions gateleen-queue/README_queue.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ The result will be a json object with the circuit information like the example b
"status": "closed",
"info": {
"failRatio": 15,
"metric": "server-tests",
"circuit": "/playground/server/tests/(.*)"
}
}
Expand All @@ -323,6 +324,7 @@ The result will be a json object with the information of all circuits like the e
"myCircuitHash": {
"infos": {
"failRatio": 15,
"metric": "server-tests",
"circuit": "/playground/server/tests/(.*)"
},
"status": "closed"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ private void handleGetCircuitStatus(Message<JsonObject> message) {

private void handleCloseCircuit(Message<JsonObject> message) {
String circuitHash = message.body().getJsonObject(PAYLOAD).getString(CIRCUIT_HASH);
PatternAndCircuitHash patternAndCircuitHash = new PatternAndCircuitHash(null, circuitHash);
PatternAndCircuitHash patternAndCircuitHash = new PatternAndCircuitHash(null, circuitHash, null);
storage.closeCircuit(patternAndCircuitHash).onComplete(event -> {
if (event.failed()) {
message.reply(new JsonObject().put(STATUS, ERROR).put(MESSAGE, event.cause().getMessage()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class RedisQueueCircuitBreakerStorage implements QueueCircuitBreakerStora
public static final String FIELD_STATE = "state";
public static final String FIELD_FAILRATIO = "failRatio";
public static final String FIELD_CIRCUIT = "circuit";
public static final String FIELD_METRICNAME = "metric";

private final LuaScriptState openCircuitLuaScriptState;
private final LuaScriptState closeCircuitLuaScriptState;
Expand Down Expand Up @@ -99,7 +100,7 @@ public Future<QueueCircuitState> getQueueCircuitState(String circuitHash) {
public Future<JsonObject> getQueueCircuitInformation(String circuitHash) {
Promise<JsonObject> promise = Promise.promise();
redisProvider.redis().onSuccess(redisAPI -> redisAPI.hmget(Arrays.asList(buildInfosKey(circuitHash), FIELD_STATE,
FIELD_FAILRATIO, FIELD_CIRCUIT), event -> {
FIELD_FAILRATIO, FIELD_CIRCUIT, FIELD_METRICNAME), event -> {
if (event.failed()) {
promise.fail(event.cause());
} else {
Expand All @@ -108,6 +109,7 @@ public Future<JsonObject> getQueueCircuitInformation(String circuitHash) {
QueueCircuitState.CLOSED);
String failRatioStr = Objects.toString(event.result().get(1), null);
String circuit = Objects.toString(event.result().get(2), null);
String metric = Objects.toString(event.result().get(3), null);
JsonObject result = new JsonObject();
result.put("status", state.name().toLowerCase());
JsonObject info = new JsonObject();
Expand All @@ -117,6 +119,9 @@ public Future<JsonObject> getQueueCircuitInformation(String circuitHash) {
if (circuit != null) {
info.put(FIELD_CIRCUIT, circuit);
}
if (StringUtils.isNotEmptyTrimmed(metric)) {
info.put(FIELD_METRICNAME, metric);
}
result.put("info", info);
promise.complete(result);
} catch (Exception e) {
Expand Down Expand Up @@ -156,6 +161,7 @@ public Future<UpdateStatisticsResult> updateStatistics(PatternAndCircuitHash pat
List<String> arguments = Arrays.asList(
uniqueRequestID,
patternAndCircuitHash.getPattern().pattern(),
patternAndCircuitHash.getMetricName() != null ? patternAndCircuitHash.getMetricName() : "",
patternAndCircuitHash.getCircuitHash(),
String.valueOf(timestamp),
String.valueOf(errorThresholdPercentage),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.swisspush.gateleen.queue.queuing.circuitbreaker.util;

import javax.annotation.Nullable;
import java.util.Objects;
import java.util.regex.Pattern;

/**
Expand All @@ -11,10 +13,12 @@
public class PatternAndCircuitHash {
private final Pattern pattern;
private final String circuitHash;
private final String metricName;

public PatternAndCircuitHash(Pattern pattern, String circuitHash) {
public PatternAndCircuitHash(@Nullable Pattern pattern, String circuitHash, @Nullable String metricName) {
this.pattern = pattern;
this.circuitHash = circuitHash;
this.metricName = metricName;
}

public Pattern getPattern() {
Expand All @@ -25,21 +29,27 @@ public String getCircuitHash() {
return circuitHash;
}

public String getMetricName() {
return metricName;
}

@Override
public String toString() {
return "url pattern: " + pattern.pattern() + " circuit hash: " + circuitHash;
return "PatternAndCircuitHash{" +
"pattern=" + pattern +
", circuitHash='" + circuitHash + '\'' +
", metricName='" + metricName + '\'' +
'}';
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

PatternAndCircuitHash that = (PatternAndCircuitHash) o;

if (!pattern.pattern().equals(that.pattern.pattern())) return false;
return circuitHash.equals(that.circuitHash);

return Objects.equals(pattern != null ? pattern.pattern() : null,
that.pattern != null ? that.pattern.pattern() : null) &&
circuitHash.equals(that.circuitHash);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ private PatternAndCircuitHash getPatternAndCircuitHashFromRule(Rule rule){
try {
Pattern pattern = Pattern.compile(rule.getUrlPattern());
String circuitHash = HashCodeGenerator.createHashCode(rule.getUrlPattern());
return new PatternAndCircuitHash(pattern, circuitHash);
return new PatternAndCircuitHash(pattern, circuitHash, rule.getMetricName());
} catch (Exception e) {
log.error("Could not compile the regex:{} to a pattern.", rule.getUrlPattern());
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
local stateField = "state"
local failRatioField = "failRatio"
local circuitField = "circuit"
local metricField = "metric"

local allCircuitsKey = KEYS[1]

local circuitInfoKeyPrefix = ARGV[1]
local circuitInfoKeySuffix = ARGV[2]

local function getCircuitInfos(circuit)
return redis.call('hmget',circuitInfoKeyPrefix..circuit..circuitInfoKeySuffix,stateField,circuitField,failRatioField)
return redis.call('hmget',circuitInfoKeyPrefix..circuit..circuitInfoKeySuffix,stateField,circuitField,metricField,failRatioField)
end

local function string_not_empty(s)
Expand All @@ -29,7 +30,10 @@ for k, circuit in ipairs(allCircuits) do
result[circuit].infos.circuit = fields[2]
end
if string_not_empty(fields[3]) then
result[circuit].infos.failRatio = tonumber(fields[3])
result[circuit].infos.metric = fields[3]
end
if string_not_empty(fields[4]) then
result[circuit].infos.failRatio = tonumber(fields[4])
end
end

Expand Down
16 changes: 10 additions & 6 deletions gateleen-queue/src/main/resources/circuitbreaker_update.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
local stateField = "state"
local failRatioField = "failRatio"
local circuitField = "circuit"
local metricField = "metric"
local circuitInfoKey = KEYS[1]
local circuitSuccessKey = KEYS[2]
local circuitFailureKey = KEYS[3]
Expand All @@ -10,12 +11,13 @@ local allCircuitsKey = KEYS[6]

local requestID = ARGV[1]
local circuit = ARGV[2]
local circuitHash = ARGV[3]
local requestTS = tonumber(ARGV[4])
local errorThresholdPercentage = tonumber(ARGV[5])
local entriesMaxAgeMS = tonumber(ARGV[6])
local minQueueSampleCount = tonumber(ARGV[7])
local maxQueueSampleCount = tonumber(ARGV[8])
local metric = ARGV[3]
local circuitHash = ARGV[4]
local requestTS = tonumber(ARGV[5])
local errorThresholdPercentage = tonumber(ARGV[6])
local entriesMaxAgeMS = tonumber(ARGV[7])
local minQueueSampleCount = tonumber(ARGV[8])
local maxQueueSampleCount = tonumber(ARGV[9])

local return_value = "OK"
local minScore = requestTS - entriesMaxAgeMS
Expand All @@ -24,6 +26,8 @@ local minScore = requestTS - entriesMaxAgeMS
redis.call('zadd',circuitKeyToUpdate,requestTS,requestID)
-- write circuit pattern to infos
redis.call('hsetnx',circuitInfoKey, circuitField, circuit)
-- write metric to infos
redis.call('hsetnx',circuitInfoKey, metricField, metric)
-- add circuit to all circuits set
redis.call('sadd',allCircuitsKey,circuitHash)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public void testGetQueueCircuitInformationSuccess(TestContext context) {
JsonObject info = new JsonObject();
info.put("failRatio", 99);
info.put("circuit", "/path/of/circuit");
info.put("metric", "my-metric-1");
result.put("info", info);

Mockito.when(queueCircuitBreakerStorage.getQueueCircuitInformation(anyString()))
Expand All @@ -97,6 +98,34 @@ public void testGetQueueCircuitInformationSuccess(TestContext context) {
context.assertEquals(QueueCircuitState.HALF_OPEN.name(), payload.getString(STATUS));
context.assertEquals(99, payload.getJsonObject("info").getInteger("failRatio"));
context.assertEquals("/path/of/circuit", payload.getJsonObject("info").getString("circuit"));
context.assertEquals("my-metric-1", payload.getJsonObject("info").getString("metric"));
async.complete();
});
}

@Test
public void testGetQueueCircuitInformationSuccessWithoutMetricName(TestContext context) {
Async async = context.async();

JsonObject result = new JsonObject();
result.put("status", QueueCircuitState.HALF_OPEN.name());
JsonObject info = new JsonObject();
info.put("failRatio", 99);
info.put("circuit", "/path/of/circuit");
result.put("info", info);

Mockito.when(queueCircuitBreakerStorage.getQueueCircuitInformation(anyString()))
.thenReturn(Future.succeededFuture(result));

vertx.eventBus().request(HTTP_REQUEST_API_ADDRESS, QueueCircuitBreakerAPI.buildGetCircuitInformationOperation("someCircuit"),
(Handler<AsyncResult<Message<JsonObject>>>) reply -> {
JsonObject replyBody = reply.result().body();
context.assertEquals(OK, replyBody.getString(STATUS));
JsonObject payload = replyBody.getJsonObject(VALUE);
context.assertEquals(QueueCircuitState.HALF_OPEN.name(), payload.getString(STATUS));
context.assertEquals(99, payload.getJsonObject("info").getInteger("failRatio"));
context.assertEquals("/path/of/circuit", payload.getJsonObject("info").getString("circuit"));
context.assertNull(payload.getJsonObject("info").getString("metric"));
async.complete();
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ public void testHandleQueuedRequest(TestContext context) {
HttpRequest req = new HttpRequest(HttpMethod.PUT, "/playground/circuitBreaker/test", MultiMap.caseInsensitiveMultiMap(), null);

Mockito.when(ruleToCircuitMapping.getCircuitFromRequestUri(anyString()))
.thenReturn(new PatternAndCircuitHash(Pattern.compile("/someCircuit"), "someCircuitHash"));
.thenReturn(new PatternAndCircuitHash(Pattern.compile("/someCircuit"), "someCircuitHash", "my-metric-1"));

Mockito.when(queueCircuitBreakerStorage.getQueueCircuitState(any(PatternAndCircuitHash.class)))
.thenReturn(Future.succeededFuture(QueueCircuitState.CLOSED));
Expand Down Expand Up @@ -206,7 +206,7 @@ public void testHandleQueuedRequestCallsLockQueueWhenCircuitIsOpen(TestContext c
HttpRequest req = new HttpRequest(HttpMethod.PUT, "/playground/circuitBreaker/test", MultiMap.caseInsensitiveMultiMap(), null);

Mockito.when(ruleToCircuitMapping.getCircuitFromRequestUri(anyString()))
.thenReturn(new PatternAndCircuitHash(Pattern.compile("/someCircuit"), "someCircuitHash"));
.thenReturn(new PatternAndCircuitHash(Pattern.compile("/someCircuit"), "someCircuitHash", "my-metric-1"));

Mockito.when(queueCircuitBreakerStorage.getQueueCircuitState(any(PatternAndCircuitHash.class)))
.thenReturn(Future.succeededFuture(QueueCircuitState.OPEN));
Expand All @@ -229,7 +229,7 @@ public void testHandleQueuedRequestDoesNotCallLockQueueWhenCircuitIsNotOpen(Test
HttpRequest req = new HttpRequest(HttpMethod.PUT, "/playground/circuitBreaker/test", MultiMap.caseInsensitiveMultiMap(), null);

Mockito.when(ruleToCircuitMapping.getCircuitFromRequestUri(anyString()))
.thenReturn(new PatternAndCircuitHash(Pattern.compile("/someCircuit"), "someCircuitHash"));
.thenReturn(new PatternAndCircuitHash(Pattern.compile("/someCircuit"), "someCircuitHash", "my-metric-1"));

Mockito.when(queueCircuitBreakerStorage.getQueueCircuitState(any(PatternAndCircuitHash.class)))
.thenReturn(Future.succeededFuture(QueueCircuitState.CLOSED));
Expand Down Expand Up @@ -274,7 +274,7 @@ public void testUpdateStatistics(TestContext context) {
HttpRequest req = new HttpRequest(HttpMethod.PUT, "/playground/circuitBreaker/test", MultiMap.caseInsensitiveMultiMap(), null);

Mockito.when(ruleToCircuitMapping.getCircuitFromRequestUri(anyString()))
.thenReturn(new PatternAndCircuitHash(Pattern.compile("/someCircuit"), "someCircuitHash"));
.thenReturn(new PatternAndCircuitHash(Pattern.compile("/someCircuit"), "someCircuitHash", "my-metric-1"));

Mockito.when(queueCircuitBreakerStorage.updateStatistics(any(PatternAndCircuitHash.class),
anyString(), anyLong(), anyInt(), anyLong(), anyLong(), anyLong(), any(QueueResponseType.class)))
Expand Down Expand Up @@ -311,7 +311,7 @@ public void testUpdateStatisticsTriggersQueueLock(TestContext context) {
HttpRequest req = new HttpRequest(HttpMethod.PUT, "/playground/circuitBreaker/test", MultiMap.caseInsensitiveMultiMap(), null);

Mockito.when(ruleToCircuitMapping.getCircuitFromRequestUri(anyString()))
.thenReturn(new PatternAndCircuitHash(Pattern.compile("/someCircuit"), "someCircuitHash"));
.thenReturn(new PatternAndCircuitHash(Pattern.compile("/someCircuit"), "someCircuitHash", "my-metric-1"));

Mockito.when(queueCircuitBreakerStorage.updateStatistics(any(PatternAndCircuitHash.class),
anyString(), anyLong(), anyInt(), anyLong(), anyLong(), anyLong(), any(QueueResponseType.class)))
Expand All @@ -334,7 +334,7 @@ public void testQueueLock(TestContext context) {
HttpRequest req = new HttpRequest(HttpMethod.PUT, "/playground/circuitBreaker/test", MultiMap.caseInsensitiveMultiMap(), null);

Mockito.when(ruleToCircuitMapping.getCircuitFromRequestUri(anyString()))
.thenReturn(new PatternAndCircuitHash(Pattern.compile("/someCircuit"), "someCircuitHash"));
.thenReturn(new PatternAndCircuitHash(Pattern.compile("/someCircuit"), "someCircuitHash", "my-metric-1"));

Mockito.when(queueCircuitBreakerStorage.lockQueue(anyString(), any(PatternAndCircuitHash.class)))
.thenReturn(Future.succeededFuture());
Expand All @@ -360,7 +360,7 @@ public void testQueueLockFailingRedisques(TestContext context) {
HttpRequest req = new HttpRequest(HttpMethod.PUT, "/playground/circuitBreaker/test", MultiMap.caseInsensitiveMultiMap(), null);

Mockito.when(ruleToCircuitMapping.getCircuitFromRequestUri(anyString()))
.thenReturn(new PatternAndCircuitHash(Pattern.compile("/someCircuit"), "someCircuitHash"));
.thenReturn(new PatternAndCircuitHash(Pattern.compile("/someCircuit"), "someCircuitHash", "my-metric-1"));

Mockito.when(queueCircuitBreakerStorage.lockQueue(anyString(), any(PatternAndCircuitHash.class)))
.thenReturn(Future.succeededFuture());
Expand All @@ -387,7 +387,7 @@ public void testQueueLockFailingStorage(TestContext context) {
HttpRequest req = new HttpRequest(HttpMethod.PUT, "/playground/circuitBreaker/test", MultiMap.caseInsensitiveMultiMap(), null);

Mockito.when(ruleToCircuitMapping.getCircuitFromRequestUri(anyString()))
.thenReturn(new PatternAndCircuitHash(Pattern.compile("/someCircuit"), "someCircuitHash"));
.thenReturn(new PatternAndCircuitHash(Pattern.compile("/someCircuit"), "someCircuitHash", "my-metric-1"));

Mockito.when(queueCircuitBreakerStorage.lockQueue(anyString(), any(PatternAndCircuitHash.class)))
.thenReturn(Future.failedFuture("queue could not be locked"));
Expand Down Expand Up @@ -544,7 +544,7 @@ public void testCloseCircuit(TestContext context) {
HttpRequest req = new HttpRequest(HttpMethod.PUT, "/playground/circuitBreaker/test", MultiMap.caseInsensitiveMultiMap(), null);

Mockito.when(ruleToCircuitMapping.getCircuitFromRequestUri(anyString()))
.thenReturn(new PatternAndCircuitHash(Pattern.compile("/someCircuit"), "someCircuitHash"));
.thenReturn(new PatternAndCircuitHash(Pattern.compile("/someCircuit"), "someCircuitHash", "my-metric-1"));

Mockito.when(queueCircuitBreakerStorage.closeCircuit(any(PatternAndCircuitHash.class)))
.thenReturn(Future.succeededFuture());
Expand All @@ -563,7 +563,7 @@ public void testCloseCircuitFailingStorage(TestContext context) {
HttpRequest req = new HttpRequest(HttpMethod.PUT, "/playground/circuitBreaker/test", MultiMap.caseInsensitiveMultiMap(), null);

Mockito.when(ruleToCircuitMapping.getCircuitFromRequestUri(anyString()))
.thenReturn(new PatternAndCircuitHash(Pattern.compile("/someCircuit"), "someCircuitHash"));
.thenReturn(new PatternAndCircuitHash(Pattern.compile("/someCircuit"), "someCircuitHash", "my-metric-1"));

Mockito.when(queueCircuitBreakerStorage.closeCircuit(any(PatternAndCircuitHash.class)))
.thenReturn(Future.failedFuture("unable to close circuit"));
Expand Down Expand Up @@ -614,7 +614,7 @@ public void testReOpenCircuit(TestContext context) {
HttpRequest req = new HttpRequest(HttpMethod.PUT, "/playground/circuitBreaker/test", MultiMap.caseInsensitiveMultiMap(), null);

Mockito.when(ruleToCircuitMapping.getCircuitFromRequestUri(anyString()))
.thenReturn(new PatternAndCircuitHash(Pattern.compile("/someCircuit"), "someCircuitHash"));
.thenReturn(new PatternAndCircuitHash(Pattern.compile("/someCircuit"), "someCircuitHash", "my-metric-1"));

Mockito.when(queueCircuitBreakerStorage.reOpenCircuit(any(PatternAndCircuitHash.class)))
.thenReturn(Future.succeededFuture());
Expand All @@ -633,7 +633,7 @@ public void testReOpenCircuitFailingStorage(TestContext context) {
HttpRequest req = new HttpRequest(HttpMethod.PUT, "/playground/circuitBreaker/test", MultiMap.caseInsensitiveMultiMap(), null);

Mockito.when(ruleToCircuitMapping.getCircuitFromRequestUri(anyString()))
.thenReturn(new PatternAndCircuitHash(Pattern.compile("/someCircuit"), "someCircuitHash"));
.thenReturn(new PatternAndCircuitHash(Pattern.compile("/someCircuit"), "someCircuitHash", "my-metric-1"));

Mockito.when(queueCircuitBreakerStorage.reOpenCircuit(any(PatternAndCircuitHash.class)))
.thenReturn(Future.failedFuture("unable to re-open circuit"));
Expand Down
Loading

0 comments on commit 6435599

Please sign in to comment.