From f72eda008b5c3880ec5d0ff878b76dc5a9f69147 Mon Sep 17 00:00:00 2001
From: Abhishek Balaji Radhakrishnan
Date: Tue, 19 Sep 2023 14:27:34 -0700
Subject: [PATCH 1/9] Disallow overshadowing rules in rule chain.
This would prevent users from shooting themselves in the foot.
For example, if an interval in a rule is "large enough" and
covers an interval in subsequent rule(s), then those rules
will never run.
Implement guard rails for rules in the rule chain.
---
.../ForeverBroadcastDistributionRule.java | 12 +
.../coordinator/rules/ForeverDropRule.java | 12 +
.../coordinator/rules/ForeverLoadRule.java | 14 +
.../IntervalBroadcastDistributionRule.java | 14 +
.../coordinator/rules/IntervalDropRule.java | 14 +
.../coordinator/rules/IntervalLoadRule.java | 19 +-
.../PeriodBroadcastDistributionRule.java | 15 +
.../rules/PeriodDropBeforeRule.java | 15 +
.../coordinator/rules/PeriodDropRule.java | 15 +
.../coordinator/rules/PeriodLoadRule.java | 19 +-
.../druid/server/coordinator/rules/Rule.java | 2 +
.../druid/server/coordinator/rules/Rules.java | 3 +
.../druid/server/http/RulesResource.java | 38 +++
.../druid/server/http/RulesResourceTest.java | 287 ++++++++++++++++++
14 files changed, 474 insertions(+), 5 deletions(-)
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/ForeverBroadcastDistributionRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/ForeverBroadcastDistributionRule.java
index ef5094cbea4a..f770c686df67 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/ForeverBroadcastDistributionRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/ForeverBroadcastDistributionRule.java
@@ -56,6 +56,12 @@ public boolean appliesTo(Interval interval, DateTime referenceTimestamp)
return true;
}
+ @Override
+ public Interval getInterval(DateTime referenceTimestamp)
+ {
+ return Rules.FOREVER_INTERVAL;
+ }
+
@Override
public boolean equals(Object o)
{
@@ -75,4 +81,10 @@ public int hashCode()
{
return Objects.hash(getType());
}
+
+ @Override
+ public String toString()
+ {
+ return "ForeverBroadcastDistributionRule{}";
+ }
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/ForeverDropRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/ForeverDropRule.java
index 19479c015b91..b9c7352539ef 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/ForeverDropRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/ForeverDropRule.java
@@ -46,4 +46,16 @@ public boolean appliesTo(Interval interval, DateTime referenceTimestamp)
{
return true;
}
+
+ @Override
+ public Interval getInterval(DateTime referenceTimestamp)
+ {
+ return Rules.FOREVER_INTERVAL;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "ForeverDropRule{}";
+ }
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/ForeverLoadRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/ForeverLoadRule.java
index 35f22fa555f8..5166ed736d2c 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/ForeverLoadRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/ForeverLoadRule.java
@@ -60,4 +60,18 @@ public boolean appliesTo(Interval interval, DateTime referenceTimestamp)
return true;
}
+ @Override
+ public Interval getInterval(DateTime referenceTimestamp)
+ {
+ return Rules.FOREVER_INTERVAL;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "ForeverLoadRule{" +
+ "tieredReplicants=" + getTieredReplicants() +
+ ", useDefaultTierForNull=" + useDefaultTierForNull() +
+ '}';
+ }
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/IntervalBroadcastDistributionRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/IntervalBroadcastDistributionRule.java
index b1bf29eedd20..e0db2a01e504 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/IntervalBroadcastDistributionRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/IntervalBroadcastDistributionRule.java
@@ -65,6 +65,12 @@ public Interval getInterval()
return interval;
}
+ @Override
+ public Interval getInterval(DateTime referenceTimestamp)
+ {
+ return interval;
+ }
+
@Override
public boolean equals(Object o)
{
@@ -83,4 +89,12 @@ public int hashCode()
{
return Objects.hash(getInterval());
}
+
+ @Override
+ public String toString()
+ {
+ return "IntervalBroadcastDistributionRule{" +
+ "interval=" + interval +
+ '}';
+ }
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/IntervalDropRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/IntervalDropRule.java
index b2959e925d28..e53c06c8ea57 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/IntervalDropRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/IntervalDropRule.java
@@ -66,6 +66,12 @@ public boolean appliesTo(Interval theInterval, DateTime referenceTimestamp)
return interval.contains(theInterval);
}
+ @Override
+ public Interval getInterval(DateTime referenceTimestamp)
+ {
+ return interval;
+ }
+
@Override
public boolean equals(Object o)
{
@@ -84,4 +90,12 @@ public int hashCode()
{
return Objects.hash(interval);
}
+
+ @Override
+ public String toString()
+ {
+ return "IntervalDropRule{" +
+ "interval=" + interval +
+ '}';
+ }
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/IntervalLoadRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/IntervalLoadRule.java
index 209f5c24d1e4..a5fb46503f58 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/IntervalLoadRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/IntervalLoadRule.java
@@ -21,7 +21,6 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
-import org.apache.druid.java.util.common.logger.Logger;
import org.apache.druid.timeline.DataSegment;
import org.joda.time.DateTime;
import org.joda.time.Interval;
@@ -34,8 +33,6 @@
*/
public class IntervalLoadRule extends LoadRule
{
- private static final Logger log = new Logger(IntervalLoadRule.class);
-
private final Interval interval;
@JsonCreator
@@ -74,6 +71,12 @@ public boolean appliesTo(Interval theInterval, DateTime referenceTimestamp)
return Rules.eligibleForLoad(interval, theInterval);
}
+ @Override
+ public Interval getInterval(DateTime referenceTimestamp)
+ {
+ return interval;
+ }
+
@Override
public boolean equals(Object o)
{
@@ -95,4 +98,14 @@ public int hashCode()
{
return Objects.hash(super.hashCode(), interval);
}
+
+ @Override
+ public String toString()
+ {
+ return "IntervalLoadRule{" +
+ "interval=" + interval +
+ ", tieredReplicants=" + getTieredReplicants() +
+ ", useDefaultTierForNull=" + useDefaultTierForNull() +
+ '}';
+ }
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodBroadcastDistributionRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodBroadcastDistributionRule.java
index d48353d3e50a..a88e75fdb4e4 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodBroadcastDistributionRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodBroadcastDistributionRule.java
@@ -65,6 +65,12 @@ public boolean appliesTo(Interval interval, DateTime referenceTimestamp)
return Rules.eligibleForLoad(period, interval, referenceTimestamp, includeFuture);
}
+ @Override
+ public Interval getInterval(DateTime referenceTimestamp)
+ {
+ return new Interval(referenceTimestamp.minus(period), referenceTimestamp);
+ }
+
@JsonProperty
public Period getPeriod()
{
@@ -96,4 +102,13 @@ public int hashCode()
{
return Objects.hash(getPeriod(), isIncludeFuture());
}
+
+ @Override
+ public String toString()
+ {
+ return "PeriodBroadcastDistributionRule{" +
+ "period=" + period +
+ ", includeFuture=" + includeFuture +
+ '}';
+ }
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropBeforeRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropBeforeRule.java
index 6654b385eac3..262d00a7aae0 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropBeforeRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropBeforeRule.java
@@ -21,6 +21,7 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.druid.java.util.common.DateTimes;
import org.apache.druid.timeline.DataSegment;
import org.joda.time.DateTime;
import org.joda.time.Interval;
@@ -63,4 +64,18 @@ public boolean appliesTo(Interval theInterval, DateTime referenceTimestamp)
final DateTime periodAgo = referenceTimestamp.minus(period);
return theInterval.getEndMillis() <= periodAgo.getMillis();
}
+
+ @Override
+ public Interval getInterval(DateTime referenceTimestamp)
+ {
+ return new Interval(DateTimes.utc(Long.MIN_VALUE), referenceTimestamp.minus(period));
+ }
+
+ @Override
+ public String toString()
+ {
+ return "PeriodDropBeforeRule{" +
+ "period=" + period +
+ '}';
+ }
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropRule.java
index da17b4c2a9d9..a31ee254b676 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropRule.java
@@ -80,4 +80,19 @@ public boolean appliesTo(Interval theInterval, DateTime referenceTimestamp)
return currInterval.contains(theInterval);
}
}
+
+ @Override
+ public Interval getInterval(DateTime referenceTimestamp)
+ {
+ return new Interval(referenceTimestamp.minus(period), referenceTimestamp);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "PeriodDropRule{" +
+ "period=" + period +
+ ", includeFuture=" + includeFuture +
+ '}';
+ }
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodLoadRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodLoadRule.java
index 1d2b4e187716..d7b814c66beb 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodLoadRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodLoadRule.java
@@ -21,7 +21,6 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
-import org.apache.druid.java.util.common.logger.Logger;
import org.apache.druid.timeline.DataSegment;
import org.joda.time.DateTime;
import org.joda.time.Interval;
@@ -35,7 +34,6 @@
*/
public class PeriodLoadRule extends LoadRule
{
- private static final Logger log = new Logger(PeriodLoadRule.class);
static final boolean DEFAULT_INCLUDE_FUTURE = true;
private final Period period;
@@ -85,6 +83,12 @@ public boolean appliesTo(Interval interval, DateTime referenceTimestamp)
return Rules.eligibleForLoad(period, interval, referenceTimestamp, includeFuture);
}
+ @Override
+ public Interval getInterval(DateTime referenceTimestamp)
+ {
+ return new Interval(referenceTimestamp.minus(period), referenceTimestamp);
+ }
+
@Override
public boolean equals(Object o)
{
@@ -106,4 +110,15 @@ public int hashCode()
{
return Objects.hash(super.hashCode(), period, includeFuture);
}
+
+ @Override
+ public String toString()
+ {
+ return "PeriodLoadRule{" +
+ "period=" + period +
+ ", includeFuture=" + includeFuture +
+ ", tieredReplicants=" + getTieredReplicants() +
+ ", useDefaultTierForNull=" + useDefaultTierForNull() +
+ '}';
+ }
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/Rule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/Rule.java
index a66101d0fe14..0f1f821f8fc5 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/Rule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/Rule.java
@@ -50,4 +50,6 @@ public interface Rule
boolean appliesTo(Interval interval, DateTime referenceTimestamp);
void run(DataSegment segment, SegmentActionHandler segmentHandler);
+
+ Interval getInterval(DateTime referenceTimestamp);
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/Rules.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/Rules.java
index 9cb6094886c7..b8d63ec8a078 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/Rules.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/Rules.java
@@ -19,12 +19,15 @@
package org.apache.druid.server.coordinator.rules;
+import org.apache.druid.java.util.common.DateTimes;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.joda.time.Period;
public class Rules
{
+ public static final Interval FOREVER_INTERVAL = new Interval(DateTimes.utc(Long.MIN_VALUE), DateTimes.utc(Long.MAX_VALUE));
+
public static boolean eligibleForLoad(Interval src, Interval target)
{
return src.overlaps(target);
diff --git a/server/src/main/java/org/apache/druid/server/http/RulesResource.java b/server/src/main/java/org/apache/druid/server/http/RulesResource.java
index beb223a6cc65..3e5b5d78f38b 100644
--- a/server/src/main/java/org/apache/druid/server/http/RulesResource.java
+++ b/server/src/main/java/org/apache/druid/server/http/RulesResource.java
@@ -25,11 +25,15 @@
import org.apache.druid.audit.AuditEntry;
import org.apache.druid.audit.AuditInfo;
import org.apache.druid.audit.AuditManager;
+import org.apache.druid.error.InvalidInput;
+import org.apache.druid.java.util.common.DateTimes;
import org.apache.druid.java.util.common.Intervals;
import org.apache.druid.metadata.MetadataRuleManager;
import org.apache.druid.server.coordinator.rules.Rule;
+import org.apache.druid.server.coordinator.rules.Rules;
import org.apache.druid.server.http.security.RulesResourceFilter;
import org.apache.druid.server.http.security.StateResourceFilter;
+import org.joda.time.DateTime;
import org.joda.time.Interval;
import javax.servlet.http.HttpServletRequest;
@@ -108,6 +112,7 @@ public Response setDatasourceRules(
)
{
try {
+ validateRules(rules);
final AuditInfo auditInfo = new AuditInfo(author, comment, req.getRemoteAddr());
if (databaseRuleManager.overrideRule(dataSourceName, rules, auditInfo)) {
return Response.ok().build();
@@ -181,4 +186,37 @@ private List getRuleHistory(
return auditManager.fetchAuditHistory(AUDIT_HISTORY_TYPE, theInterval);
}
+ /**
+ * Validate rules. Throws an exception if a rule contain an interval that will overshadow another rules' interval.
+ * Rules that will be evaluated at some point are considered to be non-overshadowing.
+ * @param rules Datasource rules.
+ */
+ private void validateRules(final List rules)
+ {
+ if (rules == null) {
+ return;
+ }
+ final DateTime now = DateTimes.nowUtc();
+ for (int i = 0; i < rules.size() - 1; i++) {
+ final Rule currRule = rules.get(i);
+ final Rule nextRule = rules.get(i + 1);
+ final Interval currInterval = currRule.getInterval(now);
+ final Interval nextInterval = nextRule.getInterval(now);
+ if (currInterval.contains(nextInterval)) {
+ // If the current rule overshaows the next rule even at the intervals' boundaries, then we know that the next
+ // rule will always be a no-op. Also, a forever rule spans eternity and overshadows everything that follows it.
+ if (Rules.FOREVER_INTERVAL.equals(currInterval) ||
+ (currRule.getInterval(currInterval.getStart()).contains(nextRule.getInterval(currInterval.getStart()))
+ && currRule.getInterval(currInterval.getEnd()).contains(nextRule.getInterval(currInterval.getEnd())))) {
+ throw InvalidInput.exception(
+ "Rule[%s] has an interval that contains interval for rule[%s]. The interval[%s] also covers interval[%s].",
+ currRule,
+ nextRule,
+ currInterval,
+ nextInterval
+ );
+ }
+ }
+ }
+ }
}
diff --git a/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java b/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java
index 5b52e3d30ee9..f3b3af68ffa6 100644
--- a/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java
+++ b/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java
@@ -23,16 +23,31 @@
import org.apache.druid.audit.AuditEntry;
import org.apache.druid.audit.AuditInfo;
import org.apache.druid.audit.AuditManager;
+import org.apache.druid.error.DruidExceptionMatcher;
import org.apache.druid.java.util.common.DateTimes;
import org.apache.druid.java.util.common.Intervals;
+import org.apache.druid.java.util.common.StringUtils;
import org.apache.druid.metadata.MetadataRuleManager;
+import org.apache.druid.server.coordinator.rules.ForeverBroadcastDistributionRule;
+import org.apache.druid.server.coordinator.rules.ForeverDropRule;
+import org.apache.druid.server.coordinator.rules.ForeverLoadRule;
+import org.apache.druid.server.coordinator.rules.IntervalBroadcastDistributionRule;
+import org.apache.druid.server.coordinator.rules.IntervalLoadRule;
+import org.apache.druid.server.coordinator.rules.PeriodBroadcastDistributionRule;
+import org.apache.druid.server.coordinator.rules.PeriodDropBeforeRule;
+import org.apache.druid.server.coordinator.rules.PeriodDropRule;
+import org.apache.druid.server.coordinator.rules.PeriodLoadRule;
+import org.apache.druid.server.coordinator.rules.Rule;
import org.easymock.EasyMock;
import org.joda.time.Interval;
+import org.joda.time.Period;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Response;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -151,6 +166,278 @@ public void testGetDatasourceRuleHistoryWithWrongCount()
EasyMock.verify(auditManager);
}
+ @Test
+ public void testSetDatasourceRulesWithEffectivelyNoRule()
+ {
+ EasyMock.expect(databaseRuleManager.overrideRule(EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject()))
+ .andReturn(true).times(2);
+ EasyMock.replay(databaseRuleManager);
+
+ final RulesResource rulesResource = new RulesResource(databaseRuleManager, auditManager);
+ final Response resp1 = rulesResource.setDatasourceRules("dataSource1", null, null, null, EasyMock.createMock(HttpServletRequest.class));
+ Assert.assertEquals(200, resp1.getStatus());
+
+ final Response resp2 = rulesResource.setDatasourceRules("dataSource1", new ArrayList<>(), null, null, EasyMock.createMock(HttpServletRequest.class));
+ Assert.assertEquals(200, resp2.getStatus());
+ EasyMock.verify(databaseRuleManager);
+ }
+
+ @Test
+ public void testSetDatasourceRulesWithInvalidLoadRules()
+ {
+ EasyMock.replay(auditManager);
+
+ final RulesResource rulesResource = new RulesResource(databaseRuleManager, auditManager);
+ final HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class);
+
+ final IntervalLoadRule loadInterval1 = new IntervalLoadRule(
+ Intervals.of("2023-07-29T01:00:00Z/2023-12-30T01:00:00Z"),
+ null,
+ null
+ );
+ final IntervalLoadRule loadInterval2 = new IntervalLoadRule(
+ Intervals.of("2023-09-29T01:00:00Z/2023-10-30T01:00:00Z"),
+ null,
+ null
+ );
+ final PeriodLoadRule loadPT1H = new PeriodLoadRule(new Period("PT1H"), null, null, null);
+ final PeriodLoadRule loadP3M = new PeriodLoadRule(new Period("P3M"), null, null, null);
+ final PeriodLoadRule loadP6M = new PeriodLoadRule(new Period("P6M"), null, null, null);
+ final ForeverLoadRule loadForever = new ForeverLoadRule(null, null);
+
+ final List rules = new ArrayList<>();
+ rules.add(loadP6M);
+ rules.add(loadP3M);
+ rules.add(loadForever);
+
+ DruidExceptionMatcher.invalidInput().expectMessageContains(
+ StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", loadP6M, loadP3M)
+ ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
+
+ rules.clear();
+ rules.add(loadP3M);
+ rules.add(loadForever);
+ rules.add(loadP6M);
+
+ DruidExceptionMatcher.invalidInput().expectMessageContains(
+ StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", loadForever, loadP6M)
+ ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
+
+ rules.clear();
+ rules.add(loadForever);
+ rules.add(loadPT1H);
+ rules.add(loadInterval2);
+ rules.add(loadP6M);
+
+ DruidExceptionMatcher.invalidInput().expectMessageContains(
+ StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", loadForever, loadPT1H)
+ ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
+
+ rules.clear();
+ rules.add(loadInterval1);
+ rules.add(loadInterval2);
+ rules.add(loadP6M);
+ rules.add(loadForever);
+
+ DruidExceptionMatcher.invalidInput().expectMessageContains(
+ StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", loadInterval1, loadInterval2)
+ ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
+
+ rules.clear();
+ rules.add(loadP6M);
+ rules.add(loadInterval1);
+ rules.add(loadForever);
+ rules.add(loadInterval2);
+
+ DruidExceptionMatcher.invalidInput().expectMessageContains(
+ StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", loadForever, loadInterval2)
+ ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
+ }
+
+ @Test
+ public void testDatasourceRulesWithInvalidDropRules()
+ {
+ EasyMock.replay(auditManager);
+
+ final RulesResource rulesResource = new RulesResource(databaseRuleManager, auditManager);
+ final HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class);
+
+ final PeriodDropBeforeRule dropBeforeP3M = new PeriodDropBeforeRule(new Period("P3M"));
+ final PeriodDropBeforeRule dropBeforeP6M = new PeriodDropBeforeRule(new Period("P6M"));
+ final PeriodDropRule dropByP1M = new PeriodDropRule(new Period("P1M"), true);
+ final PeriodDropRule dropByP2M = new PeriodDropRule(new Period("P2M"), true);
+ final ForeverDropRule dropForever = new ForeverDropRule();
+
+ final List rules = new ArrayList<>();
+ rules.add(dropBeforeP3M);
+ rules.add(dropBeforeP6M);
+ rules.add(dropByP1M);
+ rules.add(dropForever);
+
+ DruidExceptionMatcher.invalidInput().expectMessageContains(
+ StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", dropBeforeP3M, dropBeforeP6M)
+ ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
+
+ rules.clear();
+ rules.add(dropByP2M);
+ rules.add(dropByP1M);
+ rules.add(dropForever);
+
+ DruidExceptionMatcher.invalidInput().expectMessageContains(
+ StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", dropByP2M, dropByP1M)
+ ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
+
+ rules.clear();
+ rules.add(dropForever);
+ rules.add(dropByP1M);
+ rules.add(dropByP2M);
+
+ DruidExceptionMatcher.invalidInput().expectMessageContains(
+ StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", dropForever, dropByP1M)
+ ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
+ }
+
+ @Test
+ public void testDatasourceRulesWithInvalidBroadcastRules()
+ {
+ EasyMock.replay(auditManager);
+
+ final RulesResource rulesResource = new RulesResource(databaseRuleManager, auditManager);
+ final HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class);
+
+ final ForeverBroadcastDistributionRule broadcastForever = new ForeverBroadcastDistributionRule();
+ final PeriodBroadcastDistributionRule broadcastPeriodPT1H = new PeriodBroadcastDistributionRule(new Period("PT1H"), true);
+ final PeriodBroadcastDistributionRule broadcastPeriodPT2H = new PeriodBroadcastDistributionRule(new Period("PT2H"), true);
+ final IntervalBroadcastDistributionRule broadcastInterval1 = new IntervalBroadcastDistributionRule(
+ Intervals.of("2000-09-29T01:00:00Z/2050-10-30T01:00:00Z")
+ );
+ final IntervalBroadcastDistributionRule broadcastInterval2 = new IntervalBroadcastDistributionRule(
+ Intervals.of("2010-09-29T01:00:00Z/2020-10-30T01:00:00Z")
+ );
+
+ final List rules = new ArrayList<>();
+ rules.add(broadcastInterval1);
+ rules.add(broadcastInterval2);
+ rules.add(broadcastForever);
+
+ DruidExceptionMatcher.invalidInput().expectMessageContains(
+ StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", broadcastInterval1, broadcastInterval2)
+ ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
+
+ rules.clear();
+ rules.add(broadcastPeriodPT2H);
+ rules.add(broadcastPeriodPT1H);
+ rules.add(broadcastForever);
+
+ DruidExceptionMatcher.invalidInput().expectMessageContains(
+ StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", broadcastPeriodPT2H, broadcastPeriodPT1H)
+ ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
+
+
+ rules.clear();
+ rules.add(broadcastPeriodPT1H);
+ rules.add(broadcastPeriodPT2H);
+ rules.add(broadcastInterval2);
+ rules.add(broadcastForever);
+ rules.add(broadcastInterval1);
+
+ DruidExceptionMatcher.invalidInput().expectMessageContains(
+ StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", broadcastForever, broadcastInterval1)
+ ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
+ }
+
+ @Test
+ public void testSetDatasourceRulesWithDifferentInvalidRules()
+ {
+ EasyMock.replay(auditManager);
+
+ final RulesResource rulesResource = new RulesResource(databaseRuleManager, auditManager);
+ final HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class);
+
+ final IntervalLoadRule loadLargeInteval = new IntervalLoadRule(
+ Intervals.of("1980-07-29T01:00:00Z/2050-12-30T01:00:00Z"),
+ null,
+ null
+ );
+ final IntervalLoadRule loadSmallInterval = new IntervalLoadRule(
+ Intervals.of("2020-09-29T01:00:00Z/2025-10-30T01:00:00Z"),
+ null,
+ null
+ );
+ final PeriodLoadRule periodPT1H = new PeriodLoadRule(new Period("PT1H"), null, null, null);
+ final PeriodLoadRule periodP3M = new PeriodLoadRule(new Period("P3M"), null, null, null);
+ final PeriodLoadRule periodP6M = new PeriodLoadRule(new Period("P6M"), null, null, null);
+ final ForeverLoadRule foreverLoadRule = new ForeverLoadRule(null, null);
+ final ForeverDropRule foreverDropRule = new ForeverDropRule();
+ final PeriodBroadcastDistributionRule broadcastPeriodPT15m = new PeriodBroadcastDistributionRule(new Period("PT15m"), true);
+
+ final List rules = new ArrayList<>();
+ rules.add(loadSmallInterval);
+ rules.add(loadLargeInteval);
+ rules.add(broadcastPeriodPT15m);
+ rules.add(periodPT1H);
+ rules.add(periodP3M);
+ rules.add(periodP6M);
+ rules.add(foreverLoadRule);
+ rules.add(foreverDropRule);
+
+ DruidExceptionMatcher.invalidInput().expectMessageContains(
+ StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", foreverLoadRule, foreverDropRule)
+ ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
+ }
+
+ @Test
+ public void testSetDatasourceRulesWithValidRules()
+ {
+ EasyMock.expect(databaseRuleManager.overrideRule(EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject()))
+ .andReturn(true).anyTimes();
+ EasyMock.replay(databaseRuleManager);
+ final RulesResource rulesResource = new RulesResource(databaseRuleManager, auditManager);
+
+ final IntervalLoadRule loadLargeInteval = new IntervalLoadRule(
+ Intervals.of("1980-07-29T01:00:00Z/2050-12-30T01:00:00Z"),
+ null,
+ null
+ );
+ final IntervalLoadRule loadSmallInterval = new IntervalLoadRule(
+ Intervals.of("2020-09-29T01:00:00Z/2025-10-30T01:00:00Z"),
+ null,
+ null
+ );
+ final PeriodLoadRule periodPT1H = new PeriodLoadRule(new Period("PT1H"), null, null, null);
+ final PeriodLoadRule periodP3M = new PeriodLoadRule(new Period("P3M"), null, null, null);
+ final PeriodLoadRule periodP6M = new PeriodLoadRule(new Period("P6M"), null, null, null);
+ final ForeverLoadRule foreverLoadRule = new ForeverLoadRule(null, null);
+ final ForeverDropRule foreverDropRule = new ForeverDropRule();
+ final PeriodBroadcastDistributionRule broadcastPeriodPT15m = new PeriodBroadcastDistributionRule(new Period("PT15m"), true);
+
+ final List rules = new ArrayList<>();
+ rules.add(loadSmallInterval);
+ rules.add(loadLargeInteval);
+ rules.add(broadcastPeriodPT15m);
+ rules.add(periodPT1H);
+ rules.add(periodP3M);
+ rules.add(periodP6M);
+ rules.add(foreverLoadRule);
+
+ final Response resp = rulesResource.setDatasourceRules("dataSource1", rules, null, null, EasyMock.createMock(HttpServletRequest.class));
+ Assert.assertEquals(200, resp.getStatus());
+ EasyMock.verify(databaseRuleManager);
+
+ rules.clear();
+ rules.add(broadcastPeriodPT15m);
+ rules.add(periodPT1H);
+ rules.add(periodP3M);
+ rules.add(periodP6M);
+ rules.add(loadSmallInterval);
+ rules.add(loadLargeInteval);
+ rules.add(foreverDropRule);
+
+ final Response resp2 = rulesResource.setDatasourceRules("dataSource1", rules, null, null, EasyMock.createMock(HttpServletRequest.class));
+ Assert.assertEquals(200, resp2.getStatus());
+ EasyMock.verify(databaseRuleManager);
+ }
+
@Test
public void testGetAllDatasourcesRuleHistoryWithCount()
{
From 91d4797cdfe9c4ef117756ef0c07f5f1245e97cf Mon Sep 17 00:00:00 2001
From: Abhishek Balaji Radhakrishnan
Date: Tue, 19 Sep 2023 21:20:58 -0700
Subject: [PATCH 2/9] Address review comments.
---
.../ForeverBroadcastDistributionRule.java | 5 ++-
.../coordinator/rules/ForeverDropRule.java | 5 ++-
.../coordinator/rules/ForeverLoadRule.java | 5 ++-
.../IntervalBroadcastDistributionRule.java | 2 +-
.../coordinator/rules/IntervalDropRule.java | 2 +-
.../coordinator/rules/IntervalLoadRule.java | 2 +-
.../PeriodBroadcastDistributionRule.java | 4 +-
.../rules/PeriodDropBeforeRule.java | 2 +-
.../coordinator/rules/PeriodDropRule.java | 2 +-
.../coordinator/rules/PeriodLoadRule.java | 2 +-
.../druid/server/coordinator/rules/Rule.java | 8 +++-
.../druid/server/coordinator/rules/Rules.java | 42 ++++++++++++++++++-
.../druid/server/http/RulesResource.java | 39 +----------------
.../druid/server/http/RulesResourceTest.java | 10 ++++-
14 files changed, 74 insertions(+), 56 deletions(-)
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/ForeverBroadcastDistributionRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/ForeverBroadcastDistributionRule.java
index f770c686df67..3fc808450ebf 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/ForeverBroadcastDistributionRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/ForeverBroadcastDistributionRule.java
@@ -21,6 +21,7 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.druid.java.util.common.Intervals;
import org.apache.druid.timeline.DataSegment;
import org.joda.time.DateTime;
import org.joda.time.Interval;
@@ -57,9 +58,9 @@ public boolean appliesTo(Interval interval, DateTime referenceTimestamp)
}
@Override
- public Interval getInterval(DateTime referenceTimestamp)
+ public Interval getEligibleInterval(DateTime referenceTimestamp)
{
- return Rules.FOREVER_INTERVAL;
+ return Intervals.ETERNITY;
}
@Override
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/ForeverDropRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/ForeverDropRule.java
index b9c7352539ef..7c5b54bf8c2d 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/ForeverDropRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/ForeverDropRule.java
@@ -20,6 +20,7 @@
package org.apache.druid.server.coordinator.rules;
import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.druid.java.util.common.Intervals;
import org.apache.druid.timeline.DataSegment;
import org.joda.time.DateTime;
import org.joda.time.Interval;
@@ -48,9 +49,9 @@ public boolean appliesTo(Interval interval, DateTime referenceTimestamp)
}
@Override
- public Interval getInterval(DateTime referenceTimestamp)
+ public Interval getEligibleInterval(DateTime referenceTimestamp)
{
- return Rules.FOREVER_INTERVAL;
+ return Intervals.ETERNITY;
}
@Override
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/ForeverLoadRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/ForeverLoadRule.java
index 5166ed736d2c..55a9a992a00f 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/ForeverLoadRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/ForeverLoadRule.java
@@ -21,6 +21,7 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.druid.java.util.common.Intervals;
import org.apache.druid.timeline.DataSegment;
import org.joda.time.DateTime;
import org.joda.time.Interval;
@@ -61,9 +62,9 @@ public boolean appliesTo(Interval interval, DateTime referenceTimestamp)
}
@Override
- public Interval getInterval(DateTime referenceTimestamp)
+ public Interval getEligibleInterval(DateTime referenceTimestamp)
{
- return Rules.FOREVER_INTERVAL;
+ return Intervals.ETERNITY;
}
@Override
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/IntervalBroadcastDistributionRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/IntervalBroadcastDistributionRule.java
index e0db2a01e504..96e7123630ad 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/IntervalBroadcastDistributionRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/IntervalBroadcastDistributionRule.java
@@ -66,7 +66,7 @@ public Interval getInterval()
}
@Override
- public Interval getInterval(DateTime referenceTimestamp)
+ public Interval getEligibleInterval(DateTime referenceTimestamp)
{
return interval;
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/IntervalDropRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/IntervalDropRule.java
index e53c06c8ea57..6ed11d49bf60 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/IntervalDropRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/IntervalDropRule.java
@@ -67,7 +67,7 @@ public boolean appliesTo(Interval theInterval, DateTime referenceTimestamp)
}
@Override
- public Interval getInterval(DateTime referenceTimestamp)
+ public Interval getEligibleInterval(DateTime referenceTimestamp)
{
return interval;
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/IntervalLoadRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/IntervalLoadRule.java
index a5fb46503f58..c66675c75fa4 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/IntervalLoadRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/IntervalLoadRule.java
@@ -72,7 +72,7 @@ public boolean appliesTo(Interval theInterval, DateTime referenceTimestamp)
}
@Override
- public Interval getInterval(DateTime referenceTimestamp)
+ public Interval getEligibleInterval(DateTime referenceTimestamp)
{
return interval;
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodBroadcastDistributionRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodBroadcastDistributionRule.java
index a88e75fdb4e4..8406776dcc13 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodBroadcastDistributionRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodBroadcastDistributionRule.java
@@ -66,9 +66,9 @@ public boolean appliesTo(Interval interval, DateTime referenceTimestamp)
}
@Override
- public Interval getInterval(DateTime referenceTimestamp)
+ public Interval getEligibleInterval(DateTime referenceTimestamp)
{
- return new Interval(referenceTimestamp.minus(period), referenceTimestamp);
+ return new Interval(period, referenceTimestamp);
}
@JsonProperty
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropBeforeRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropBeforeRule.java
index 262d00a7aae0..07e220a1dfdc 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropBeforeRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropBeforeRule.java
@@ -66,7 +66,7 @@ public boolean appliesTo(Interval theInterval, DateTime referenceTimestamp)
}
@Override
- public Interval getInterval(DateTime referenceTimestamp)
+ public Interval getEligibleInterval(DateTime referenceTimestamp)
{
return new Interval(DateTimes.utc(Long.MIN_VALUE), referenceTimestamp.minus(period));
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropRule.java
index a31ee254b676..63c519250245 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropRule.java
@@ -82,7 +82,7 @@ public boolean appliesTo(Interval theInterval, DateTime referenceTimestamp)
}
@Override
- public Interval getInterval(DateTime referenceTimestamp)
+ public Interval getEligibleInterval(DateTime referenceTimestamp)
{
return new Interval(referenceTimestamp.minus(period), referenceTimestamp);
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodLoadRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodLoadRule.java
index d7b814c66beb..8834c7c412ae 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodLoadRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodLoadRule.java
@@ -84,7 +84,7 @@ public boolean appliesTo(Interval interval, DateTime referenceTimestamp)
}
@Override
- public Interval getInterval(DateTime referenceTimestamp)
+ public Interval getEligibleInterval(DateTime referenceTimestamp)
{
return new Interval(referenceTimestamp.minus(period), referenceTimestamp);
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/Rule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/Rule.java
index 0f1f821f8fc5..2faa248a2be4 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/Rule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/Rule.java
@@ -51,5 +51,11 @@ public interface Rule
void run(DataSegment segment, SegmentActionHandler segmentHandler);
- Interval getInterval(DateTime referenceTimestamp);
+ /**
+ * Return an eligible interval from the reference timestamp. Implemntations
+ * must return a valid interval based on the rule type.
+ * @param referenceTimestamp base timestamp
+ * @return
+ */
+ Interval getEligibleInterval(DateTime referenceTimestamp);
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/Rules.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/Rules.java
index b8d63ec8a078..a25a1b363f4a 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/Rules.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/Rules.java
@@ -19,15 +19,17 @@
package org.apache.druid.server.coordinator.rules;
+import org.apache.druid.error.InvalidInput;
import org.apache.druid.java.util.common.DateTimes;
+import org.apache.druid.java.util.common.Intervals;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.joda.time.Period;
+import java.util.List;
+
public class Rules
{
- public static final Interval FOREVER_INTERVAL = new Interval(DateTimes.utc(Long.MIN_VALUE), DateTimes.utc(Long.MAX_VALUE));
-
public static boolean eligibleForLoad(Interval src, Interval target)
{
return src.overlaps(target);
@@ -46,4 +48,40 @@ public static boolean eligibleForLoad(Period period, Interval interval, DateTime
private Rules()
{
}
+
+ /**
+ * Validate rules. This method throws an exception if a rule contain an interval that
+ * will fully cover the next rules' interval in the list. Rules that will be evaluated at some point
+ * are considered to be legitimate.
+ * @param rules Datasource rules.
+ */
+ public static void validateRules(final List rules)
+ {
+ if (rules == null) {
+ return;
+ }
+ final DateTime now = DateTimes.nowUtc();
+ for (int i = 0; i < rules.size() - 1; i++) {
+ final Rule currRule = rules.get(i);
+ final Rule nextRule = rules.get(i + 1);
+ final Interval currInterval = currRule.getEligibleInterval(now);
+ final Interval nextInterval = nextRule.getEligibleInterval(now);
+ if (currInterval.contains(nextInterval)) {
+ // If the current rule has an eternity interval, it covers everything following it.
+ // Or if the current rule still covers the next rule at the current interval boundaries, then the
+ // next rule will never fire at any time, so throw an exception.
+ if (Intervals.ETERNITY.equals(currInterval) ||
+ (currRule.getEligibleInterval(currInterval.getStart()).contains(nextRule.getEligibleInterval(currInterval.getStart()))
+ && currRule.getEligibleInterval(currInterval.getEnd()).contains(nextRule.getEligibleInterval(currInterval.getEnd())))) {
+ throw InvalidInput.exception(
+ "Rule[%s] has an interval that contains interval for rule[%s]. The interval[%s] also covers interval[%s].",
+ currRule,
+ nextRule,
+ currInterval,
+ nextInterval
+ );
+ }
+ }
+ }
+ }
}
diff --git a/server/src/main/java/org/apache/druid/server/http/RulesResource.java b/server/src/main/java/org/apache/druid/server/http/RulesResource.java
index 3e5b5d78f38b..99d7d5d1caf4 100644
--- a/server/src/main/java/org/apache/druid/server/http/RulesResource.java
+++ b/server/src/main/java/org/apache/druid/server/http/RulesResource.java
@@ -25,15 +25,12 @@
import org.apache.druid.audit.AuditEntry;
import org.apache.druid.audit.AuditInfo;
import org.apache.druid.audit.AuditManager;
-import org.apache.druid.error.InvalidInput;
-import org.apache.druid.java.util.common.DateTimes;
import org.apache.druid.java.util.common.Intervals;
import org.apache.druid.metadata.MetadataRuleManager;
import org.apache.druid.server.coordinator.rules.Rule;
import org.apache.druid.server.coordinator.rules.Rules;
import org.apache.druid.server.http.security.RulesResourceFilter;
import org.apache.druid.server.http.security.StateResourceFilter;
-import org.joda.time.DateTime;
import org.joda.time.Interval;
import javax.servlet.http.HttpServletRequest;
@@ -112,7 +109,7 @@ public Response setDatasourceRules(
)
{
try {
- validateRules(rules);
+ Rules.validateRules(rules);
final AuditInfo auditInfo = new AuditInfo(author, comment, req.getRemoteAddr());
if (databaseRuleManager.overrideRule(dataSourceName, rules, auditInfo)) {
return Response.ok().build();
@@ -185,38 +182,4 @@ private List getRuleHistory(
}
return auditManager.fetchAuditHistory(AUDIT_HISTORY_TYPE, theInterval);
}
-
- /**
- * Validate rules. Throws an exception if a rule contain an interval that will overshadow another rules' interval.
- * Rules that will be evaluated at some point are considered to be non-overshadowing.
- * @param rules Datasource rules.
- */
- private void validateRules(final List rules)
- {
- if (rules == null) {
- return;
- }
- final DateTime now = DateTimes.nowUtc();
- for (int i = 0; i < rules.size() - 1; i++) {
- final Rule currRule = rules.get(i);
- final Rule nextRule = rules.get(i + 1);
- final Interval currInterval = currRule.getInterval(now);
- final Interval nextInterval = nextRule.getInterval(now);
- if (currInterval.contains(nextInterval)) {
- // If the current rule overshaows the next rule even at the intervals' boundaries, then we know that the next
- // rule will always be a no-op. Also, a forever rule spans eternity and overshadows everything that follows it.
- if (Rules.FOREVER_INTERVAL.equals(currInterval) ||
- (currRule.getInterval(currInterval.getStart()).contains(nextRule.getInterval(currInterval.getStart()))
- && currRule.getInterval(currInterval.getEnd()).contains(nextRule.getInterval(currInterval.getEnd())))) {
- throw InvalidInput.exception(
- "Rule[%s] has an interval that contains interval for rule[%s]. The interval[%s] also covers interval[%s].",
- currRule,
- nextRule,
- currInterval,
- nextInterval
- );
- }
- }
- }
- }
}
diff --git a/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java b/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java
index f3b3af68ffa6..92ea60221de3 100644
--- a/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java
+++ b/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java
@@ -233,6 +233,15 @@ public void testSetDatasourceRulesWithInvalidLoadRules()
StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", loadForever, loadPT1H)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
+ rules.clear();
+ rules.add(loadPT1H);
+ rules.add(loadPT1H);
+ rules.add(loadP6M);
+
+ DruidExceptionMatcher.invalidInput().expectMessageContains(
+ StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", loadPT1H, loadPT1H)
+ ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
+
rules.clear();
rules.add(loadInterval1);
rules.add(loadInterval2);
@@ -333,7 +342,6 @@ public void testDatasourceRulesWithInvalidBroadcastRules()
StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", broadcastPeriodPT2H, broadcastPeriodPT1H)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
-
rules.clear();
rules.add(broadcastPeriodPT1H);
rules.add(broadcastPeriodPT2H);
From 2166d6c4abc098186edc1f8d611c8525aa57de82 Mon Sep 17 00:00:00 2001
From: Abhishek Radhakrishnan
Date: Tue, 19 Sep 2023 22:48:06 -0700
Subject: [PATCH 3/9] Apply suggestions from code review
Co-authored-by: Laksh Singla
---
.../java/org/apache/druid/server/coordinator/rules/Rule.java | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/Rule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/Rule.java
index 2faa248a2be4..c34b086484c8 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/Rule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/Rule.java
@@ -52,10 +52,9 @@ public interface Rule
void run(DataSegment segment, SegmentActionHandler segmentHandler);
/**
- * Return an eligible interval from the reference timestamp. Implemntations
+ * Return an eligible interval from the reference timestamp. Implementations
* must return a valid interval based on the rule type.
* @param referenceTimestamp base timestamp
- * @return
*/
Interval getEligibleInterval(DateTime referenceTimestamp);
}
From c3edb12849a4c65b7098cefcfef449e9228c13ae Mon Sep 17 00:00:00 2001
From: Abhishek Balaji Radhakrishnan
Date: Tue, 19 Sep 2023 23:15:35 -0700
Subject: [PATCH 4/9] includeFuture change and unit test.
---
.../PeriodBroadcastDistributionRule.java | 3 +-
.../coordinator/rules/PeriodDropRule.java | 4 +-
.../coordinator/rules/PeriodLoadRule.java | 3 +-
.../druid/server/http/RulesResourceTest.java | 64 +++++++++++++++++--
4 files changed, 65 insertions(+), 9 deletions(-)
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodBroadcastDistributionRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodBroadcastDistributionRule.java
index 8406776dcc13..5de02add3316 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodBroadcastDistributionRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodBroadcastDistributionRule.java
@@ -68,7 +68,8 @@ public boolean appliesTo(Interval interval, DateTime referenceTimestamp)
@Override
public Interval getEligibleInterval(DateTime referenceTimestamp)
{
- return new Interval(period, referenceTimestamp);
+ return includeFuture ? new Interval(referenceTimestamp.minus(period), referenceTimestamp.plus(period))
+ : new Interval(referenceTimestamp.minus(period), referenceTimestamp);
}
@JsonProperty
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropRule.java
index 63c519250245..c999bb01ad96 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropRule.java
@@ -27,6 +27,7 @@
import org.joda.time.Period;
/**
+ *
*/
public class PeriodDropRule extends DropRule
{
@@ -84,7 +85,8 @@ public boolean appliesTo(Interval theInterval, DateTime referenceTimestamp)
@Override
public Interval getEligibleInterval(DateTime referenceTimestamp)
{
- return new Interval(referenceTimestamp.minus(period), referenceTimestamp);
+ return includeFuture ? new Interval(referenceTimestamp.minus(period), referenceTimestamp.plus(period))
+ : new Interval(referenceTimestamp.minus(period), referenceTimestamp);
}
@Override
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodLoadRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodLoadRule.java
index 8834c7c412ae..0481195ec0a7 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodLoadRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodLoadRule.java
@@ -86,7 +86,8 @@ public boolean appliesTo(Interval interval, DateTime referenceTimestamp)
@Override
public Interval getEligibleInterval(DateTime referenceTimestamp)
{
- return new Interval(referenceTimestamp.minus(period), referenceTimestamp);
+ return includeFuture ? new Interval(referenceTimestamp.minus(period), referenceTimestamp.plus(period))
+ : new Interval(referenceTimestamp.minus(period), referenceTimestamp);
}
@Override
diff --git a/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java b/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java
index 92ea60221de3..909557c73f9a 100644
--- a/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java
+++ b/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java
@@ -200,9 +200,10 @@ public void testSetDatasourceRulesWithInvalidLoadRules()
null,
null
);
- final PeriodLoadRule loadPT1H = new PeriodLoadRule(new Period("PT1H"), null, null, null);
- final PeriodLoadRule loadP3M = new PeriodLoadRule(new Period("P3M"), null, null, null);
- final PeriodLoadRule loadP6M = new PeriodLoadRule(new Period("P6M"), null, null, null);
+ final PeriodLoadRule loadPT1H = new PeriodLoadRule(new Period("PT1H"), true, null, null);
+ final PeriodLoadRule loadPT1HNoFuture = new PeriodLoadRule(new Period("PT1H"), false, null, null);
+ final PeriodLoadRule loadP3M = new PeriodLoadRule(new Period("P3M"), true, null, null);
+ final PeriodLoadRule loadP6M = new PeriodLoadRule(new Period("P6M"), true, null, null);
final ForeverLoadRule loadForever = new ForeverLoadRule(null, null);
final List rules = new ArrayList<>();
@@ -233,6 +234,15 @@ public void testSetDatasourceRulesWithInvalidLoadRules()
StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", loadForever, loadPT1H)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
+ rules.clear();
+ rules.add(loadPT1H);
+ rules.add(loadPT1HNoFuture);
+ rules.add(loadP6M);
+
+ DruidExceptionMatcher.invalidInput().expectMessageContains(
+ StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", loadPT1H, loadPT1HNoFuture)
+ ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
+
rules.clear();
rules.add(loadPT1H);
rules.add(loadPT1H);
@@ -274,6 +284,7 @@ public void testDatasourceRulesWithInvalidDropRules()
final PeriodDropBeforeRule dropBeforeP3M = new PeriodDropBeforeRule(new Period("P3M"));
final PeriodDropBeforeRule dropBeforeP6M = new PeriodDropBeforeRule(new Period("P6M"));
final PeriodDropRule dropByP1M = new PeriodDropRule(new Period("P1M"), true);
+ final PeriodDropRule dropByP1MNoFuture = new PeriodDropRule(new Period("P1M"), false);
final PeriodDropRule dropByP2M = new PeriodDropRule(new Period("P2M"), true);
final ForeverDropRule dropForever = new ForeverDropRule();
@@ -296,6 +307,16 @@ public void testDatasourceRulesWithInvalidDropRules()
StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", dropByP2M, dropByP1M)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
+ rules.clear();
+ rules.add(dropByP1M);
+ rules.add(dropByP1MNoFuture);
+ rules.add(dropForever);
+
+ DruidExceptionMatcher.invalidInput().expectMessageContains(
+ StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", dropByP1M, dropByP1MNoFuture)
+ ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
+
+
rules.clear();
rules.add(dropForever);
rules.add(dropByP1M);
@@ -316,6 +337,7 @@ public void testDatasourceRulesWithInvalidBroadcastRules()
final ForeverBroadcastDistributionRule broadcastForever = new ForeverBroadcastDistributionRule();
final PeriodBroadcastDistributionRule broadcastPeriodPT1H = new PeriodBroadcastDistributionRule(new Period("PT1H"), true);
+ final PeriodBroadcastDistributionRule broadcastPeriodPT1HNoFuture = new PeriodBroadcastDistributionRule(new Period("PT1H"), false);
final PeriodBroadcastDistributionRule broadcastPeriodPT2H = new PeriodBroadcastDistributionRule(new Period("PT2H"), true);
final IntervalBroadcastDistributionRule broadcastInterval1 = new IntervalBroadcastDistributionRule(
Intervals.of("2000-09-29T01:00:00Z/2050-10-30T01:00:00Z")
@@ -342,6 +364,25 @@ public void testDatasourceRulesWithInvalidBroadcastRules()
StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", broadcastPeriodPT2H, broadcastPeriodPT1H)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
+ rules.clear();
+ rules.add(broadcastPeriodPT1H);
+ rules.add(broadcastPeriodPT1H);
+ rules.add(broadcastForever);
+
+ DruidExceptionMatcher.invalidInput().expectMessageContains(
+ StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", broadcastPeriodPT1H, broadcastPeriodPT1H)
+ ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
+
+ // Same interval with and without future.
+ rules.clear();
+ rules.add(broadcastPeriodPT1H);
+ rules.add(broadcastPeriodPT1HNoFuture);
+ rules.add(broadcastForever);
+
+ DruidExceptionMatcher.invalidInput().expectMessageContains(
+ StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", broadcastPeriodPT1H, broadcastPeriodPT1HNoFuture)
+ ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
+
rules.clear();
rules.add(broadcastPeriodPT1H);
rules.add(broadcastPeriodPT2H);
@@ -412,18 +453,25 @@ public void testSetDatasourceRulesWithValidRules()
null,
null
);
- final PeriodLoadRule periodPT1H = new PeriodLoadRule(new Period("PT1H"), null, null, null);
- final PeriodLoadRule periodP3M = new PeriodLoadRule(new Period("P3M"), null, null, null);
- final PeriodLoadRule periodP6M = new PeriodLoadRule(new Period("P6M"), null, null, null);
+ final PeriodLoadRule periodPT1H = new PeriodLoadRule(new Period("PT1H"), true, null, null);
+ final PeriodLoadRule periodPT1HNoFuture = new PeriodLoadRule(new Period("PT1H"), false, null, null);
+ final PeriodLoadRule periodP3M = new PeriodLoadRule(new Period("P3M"), true, null, null);
+ final PeriodLoadRule periodP6M = new PeriodLoadRule(new Period("P6M"), true, null, null);
+ final PeriodDropRule dropByP1M = new PeriodDropRule(new Period("P1M"), true);
+ final PeriodDropRule dropByP1MNoFuture = new PeriodDropRule(new Period("P1M"), false);
final ForeverLoadRule foreverLoadRule = new ForeverLoadRule(null, null);
final ForeverDropRule foreverDropRule = new ForeverDropRule();
final PeriodBroadcastDistributionRule broadcastPeriodPT15m = new PeriodBroadcastDistributionRule(new Period("PT15m"), true);
+ final PeriodBroadcastDistributionRule broadcastPeriodPT15mNoFuture = new PeriodBroadcastDistributionRule(new Period("PT15m"), false);
final List rules = new ArrayList<>();
rules.add(loadSmallInterval);
rules.add(loadLargeInteval);
rules.add(broadcastPeriodPT15m);
+ rules.add(periodPT1HNoFuture);
rules.add(periodPT1H);
+ rules.add(dropByP1MNoFuture);
+ rules.add(dropByP1M);
rules.add(periodP3M);
rules.add(periodP6M);
rules.add(foreverLoadRule);
@@ -433,8 +481,12 @@ public void testSetDatasourceRulesWithValidRules()
EasyMock.verify(databaseRuleManager);
rules.clear();
+ rules.add(broadcastPeriodPT15mNoFuture);
rules.add(broadcastPeriodPT15m);
+ rules.add(periodPT1HNoFuture);
rules.add(periodPT1H);
+ rules.add(dropByP1MNoFuture);
+ rules.add(dropByP1M);
rules.add(periodP3M);
rules.add(periodP6M);
rules.add(loadSmallInterval);
From 20133fceb1201f02a93508a6c4aa08ba01e56c7f Mon Sep 17 00:00:00 2001
From: Abhishek Balaji Radhakrishnan
Date: Wed, 20 Sep 2023 14:23:21 -0700
Subject: [PATCH 5/9] Some test cleanup.
---
.../druid/server/coordinator/rules/Rules.java | 5 +
.../druid/server/http/RulesResourceTest.java | 107 +++++++++---------
2 files changed, 59 insertions(+), 53 deletions(-)
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/Rules.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/Rules.java
index a25a1b363f4a..c6ea531f6a66 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/Rules.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/Rules.java
@@ -25,7 +25,11 @@
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.joda.time.Period;
+import org.joda.time.base.BaseInterval;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.LinkedList;
import java.util.List;
public class Rules
@@ -60,6 +64,7 @@ public static void validateRules(final List rules)
if (rules == null) {
return;
}
+
final DateTime now = DateTimes.nowUtc();
for (int i = 0; i < rules.size() - 1; i++) {
final Rule currRule = rules.get(i);
diff --git a/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java b/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java
index 909557c73f9a..fd2234e8b34b 100644
--- a/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java
+++ b/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java
@@ -38,6 +38,7 @@
import org.apache.druid.server.coordinator.rules.PeriodDropRule;
import org.apache.druid.server.coordinator.rules.PeriodLoadRule;
import org.apache.druid.server.coordinator.rules.Rule;
+import org.apache.druid.server.coordinator.rules.Rules;
import org.easymock.EasyMock;
import org.joda.time.Interval;
import org.joda.time.Period;
@@ -281,21 +282,21 @@ public void testDatasourceRulesWithInvalidDropRules()
final RulesResource rulesResource = new RulesResource(databaseRuleManager, auditManager);
final HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class);
- final PeriodDropBeforeRule dropBeforeP3M = new PeriodDropBeforeRule(new Period("P3M"));
- final PeriodDropBeforeRule dropBeforeP6M = new PeriodDropBeforeRule(new Period("P6M"));
+ final PeriodDropBeforeRule dropBeforeByP3M = new PeriodDropBeforeRule(new Period("P3M"));
+ final PeriodDropBeforeRule dropBeforeByP6M = new PeriodDropBeforeRule(new Period("P6M"));
final PeriodDropRule dropByP1M = new PeriodDropRule(new Period("P1M"), true);
final PeriodDropRule dropByP1MNoFuture = new PeriodDropRule(new Period("P1M"), false);
final PeriodDropRule dropByP2M = new PeriodDropRule(new Period("P2M"), true);
final ForeverDropRule dropForever = new ForeverDropRule();
final List rules = new ArrayList<>();
- rules.add(dropBeforeP3M);
- rules.add(dropBeforeP6M);
+ rules.add(dropBeforeByP3M);
+ rules.add(dropBeforeByP6M);
rules.add(dropByP1M);
rules.add(dropForever);
DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", dropBeforeP3M, dropBeforeP6M)
+ StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", dropBeforeByP3M, dropBeforeByP6M)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
rules.clear();
@@ -336,9 +337,9 @@ public void testDatasourceRulesWithInvalidBroadcastRules()
final HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class);
final ForeverBroadcastDistributionRule broadcastForever = new ForeverBroadcastDistributionRule();
- final PeriodBroadcastDistributionRule broadcastPeriodPT1H = new PeriodBroadcastDistributionRule(new Period("PT1H"), true);
- final PeriodBroadcastDistributionRule broadcastPeriodPT1HNoFuture = new PeriodBroadcastDistributionRule(new Period("PT1H"), false);
- final PeriodBroadcastDistributionRule broadcastPeriodPT2H = new PeriodBroadcastDistributionRule(new Period("PT2H"), true);
+ final PeriodBroadcastDistributionRule broadcastPT1H = new PeriodBroadcastDistributionRule(new Period("PT1H"), true);
+ final PeriodBroadcastDistributionRule broadcastPT1HNoFuture = new PeriodBroadcastDistributionRule(new Period("PT1H"), false);
+ final PeriodBroadcastDistributionRule broadcastPT2H = new PeriodBroadcastDistributionRule(new Period("PT2H"), true);
final IntervalBroadcastDistributionRule broadcastInterval1 = new IntervalBroadcastDistributionRule(
Intervals.of("2000-09-29T01:00:00Z/2050-10-30T01:00:00Z")
);
@@ -356,36 +357,36 @@ public void testDatasourceRulesWithInvalidBroadcastRules()
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
rules.clear();
- rules.add(broadcastPeriodPT2H);
- rules.add(broadcastPeriodPT1H);
+ rules.add(broadcastPT2H);
+ rules.add(broadcastPT1H);
rules.add(broadcastForever);
DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", broadcastPeriodPT2H, broadcastPeriodPT1H)
+ StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", broadcastPT2H, broadcastPT1H)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
rules.clear();
- rules.add(broadcastPeriodPT1H);
- rules.add(broadcastPeriodPT1H);
+ rules.add(broadcastPT1H);
+ rules.add(broadcastPT1H);
rules.add(broadcastForever);
DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", broadcastPeriodPT1H, broadcastPeriodPT1H)
+ StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", broadcastPT1H, broadcastPT1H)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
// Same interval with and without future.
rules.clear();
- rules.add(broadcastPeriodPT1H);
- rules.add(broadcastPeriodPT1HNoFuture);
+ rules.add(broadcastPT1H);
+ rules.add(broadcastPT1HNoFuture);
rules.add(broadcastForever);
DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", broadcastPeriodPT1H, broadcastPeriodPT1HNoFuture)
+ StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", broadcastPT1H, broadcastPT1HNoFuture)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
rules.clear();
- rules.add(broadcastPeriodPT1H);
- rules.add(broadcastPeriodPT2H);
+ rules.add(broadcastPT1H);
+ rules.add(broadcastPT2H);
rules.add(broadcastInterval2);
rules.add(broadcastForever);
rules.add(broadcastInterval1);
@@ -413,25 +414,25 @@ public void testSetDatasourceRulesWithDifferentInvalidRules()
null,
null
);
- final PeriodLoadRule periodPT1H = new PeriodLoadRule(new Period("PT1H"), null, null, null);
- final PeriodLoadRule periodP3M = new PeriodLoadRule(new Period("P3M"), null, null, null);
- final PeriodLoadRule periodP6M = new PeriodLoadRule(new Period("P6M"), null, null, null);
- final ForeverLoadRule foreverLoadRule = new ForeverLoadRule(null, null);
- final ForeverDropRule foreverDropRule = new ForeverDropRule();
- final PeriodBroadcastDistributionRule broadcastPeriodPT15m = new PeriodBroadcastDistributionRule(new Period("PT15m"), true);
+ final PeriodLoadRule loadPT1H = new PeriodLoadRule(new Period("PT1H"), null, null, null);
+ final PeriodLoadRule loadP3M = new PeriodLoadRule(new Period("P3M"), null, null, null);
+ final PeriodLoadRule loadP6M = new PeriodLoadRule(new Period("P6M"), null, null, null);
+ final ForeverLoadRule loadForever = new ForeverLoadRule(null, null);
+ final ForeverDropRule dropForever = new ForeverDropRule();
+ final PeriodBroadcastDistributionRule broadcastPT15m = new PeriodBroadcastDistributionRule(new Period("PT15m"), true);
final List rules = new ArrayList<>();
rules.add(loadSmallInterval);
rules.add(loadLargeInteval);
- rules.add(broadcastPeriodPT15m);
- rules.add(periodPT1H);
- rules.add(periodP3M);
- rules.add(periodP6M);
- rules.add(foreverLoadRule);
- rules.add(foreverDropRule);
+ rules.add(broadcastPT15m);
+ rules.add(loadPT1H);
+ rules.add(loadP3M);
+ rules.add(loadP6M);
+ rules.add(loadForever);
+ rules.add(dropForever);
DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", foreverLoadRule, foreverDropRule)
+ StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", loadForever, dropForever)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
}
@@ -453,45 +454,45 @@ public void testSetDatasourceRulesWithValidRules()
null,
null
);
- final PeriodLoadRule periodPT1H = new PeriodLoadRule(new Period("PT1H"), true, null, null);
- final PeriodLoadRule periodPT1HNoFuture = new PeriodLoadRule(new Period("PT1H"), false, null, null);
- final PeriodLoadRule periodP3M = new PeriodLoadRule(new Period("P3M"), true, null, null);
- final PeriodLoadRule periodP6M = new PeriodLoadRule(new Period("P6M"), true, null, null);
+ final PeriodLoadRule loadPT1H = new PeriodLoadRule(new Period("PT1H"), true, null, null);
+ final PeriodLoadRule loadPT1HNoFuture = new PeriodLoadRule(new Period("PT1H"), false, null, null);
+ final PeriodLoadRule loadP3M = new PeriodLoadRule(new Period("P3M"), true, null, null);
+ final PeriodLoadRule loadP6M = new PeriodLoadRule(new Period("P6M"), true, null, null);
final PeriodDropRule dropByP1M = new PeriodDropRule(new Period("P1M"), true);
final PeriodDropRule dropByP1MNoFuture = new PeriodDropRule(new Period("P1M"), false);
- final ForeverLoadRule foreverLoadRule = new ForeverLoadRule(null, null);
- final ForeverDropRule foreverDropRule = new ForeverDropRule();
- final PeriodBroadcastDistributionRule broadcastPeriodPT15m = new PeriodBroadcastDistributionRule(new Period("PT15m"), true);
- final PeriodBroadcastDistributionRule broadcastPeriodPT15mNoFuture = new PeriodBroadcastDistributionRule(new Period("PT15m"), false);
+ final ForeverLoadRule loadForever = new ForeverLoadRule(null, null);
+ final ForeverDropRule dropForever = new ForeverDropRule();
+ final PeriodBroadcastDistributionRule broadcastPT15m = new PeriodBroadcastDistributionRule(new Period("PT15m"), true);
+ final PeriodBroadcastDistributionRule broadcastPT15mNoFuture = new PeriodBroadcastDistributionRule(new Period("PT15m"), false);
final List rules = new ArrayList<>();
rules.add(loadSmallInterval);
rules.add(loadLargeInteval);
- rules.add(broadcastPeriodPT15m);
- rules.add(periodPT1HNoFuture);
- rules.add(periodPT1H);
+ rules.add(broadcastPT15m);
+ rules.add(loadPT1HNoFuture);
+ rules.add(loadPT1H);
rules.add(dropByP1MNoFuture);
rules.add(dropByP1M);
- rules.add(periodP3M);
- rules.add(periodP6M);
- rules.add(foreverLoadRule);
+ rules.add(loadP3M);
+ rules.add(loadP6M);
+ rules.add(loadForever);
final Response resp = rulesResource.setDatasourceRules("dataSource1", rules, null, null, EasyMock.createMock(HttpServletRequest.class));
Assert.assertEquals(200, resp.getStatus());
EasyMock.verify(databaseRuleManager);
rules.clear();
- rules.add(broadcastPeriodPT15mNoFuture);
- rules.add(broadcastPeriodPT15m);
- rules.add(periodPT1HNoFuture);
- rules.add(periodPT1H);
+ rules.add(broadcastPT15mNoFuture);
+ rules.add(broadcastPT15m);
+ rules.add(loadPT1HNoFuture);
+ rules.add(loadPT1H);
rules.add(dropByP1MNoFuture);
rules.add(dropByP1M);
- rules.add(periodP3M);
- rules.add(periodP6M);
+ rules.add(loadP3M);
+ rules.add(loadP6M);
rules.add(loadSmallInterval);
rules.add(loadLargeInteval);
- rules.add(foreverDropRule);
+ rules.add(dropForever);
final Response resp2 = rulesResource.setDatasourceRules("dataSource1", rules, null, null, EasyMock.createMock(HttpServletRequest.class));
Assert.assertEquals(200, resp2.getStatus());
From f8975e26fcbcf993f6cc17af8f0e3f8c7347cbad Mon Sep 17 00:00:00 2001
From: Abhishek Balaji Radhakrishnan
Date: Wed, 20 Sep 2023 18:21:42 -0700
Subject: [PATCH 6/9] fix and add more tests.
---
.../druid/server/coordinator/rules/Rules.java | 46 +++----
.../druid/server/http/RulesResourceTest.java | 113 +++++++++++-------
2 files changed, 92 insertions(+), 67 deletions(-)
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/Rules.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/Rules.java
index c6ea531f6a66..540bd1842ff7 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/Rules.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/Rules.java
@@ -25,11 +25,7 @@
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.joda.time.Period;
-import org.joda.time.base.BaseInterval;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.LinkedList;
import java.util.List;
public class Rules
@@ -55,7 +51,7 @@ private Rules()
/**
* Validate rules. This method throws an exception if a rule contain an interval that
- * will fully cover the next rules' interval in the list. Rules that will be evaluated at some point
+ * fully covers another subsequent rules' interval in the list. Rules that will be evaluated at some point
* are considered to be legitimate.
* @param rules Datasource rules.
*/
@@ -66,25 +62,31 @@ public static void validateRules(final List rules)
}
final DateTime now = DateTimes.nowUtc();
- for (int i = 0; i < rules.size() - 1; i++) {
+ for (int i = 0; i < rules.size(); i++) {
final Rule currRule = rules.get(i);
- final Rule nextRule = rules.get(i + 1);
final Interval currInterval = currRule.getEligibleInterval(now);
- final Interval nextInterval = nextRule.getEligibleInterval(now);
- if (currInterval.contains(nextInterval)) {
- // If the current rule has an eternity interval, it covers everything following it.
- // Or if the current rule still covers the next rule at the current interval boundaries, then the
- // next rule will never fire at any time, so throw an exception.
- if (Intervals.ETERNITY.equals(currInterval) ||
- (currRule.getEligibleInterval(currInterval.getStart()).contains(nextRule.getEligibleInterval(currInterval.getStart()))
- && currRule.getEligibleInterval(currInterval.getEnd()).contains(nextRule.getEligibleInterval(currInterval.getEnd())))) {
- throw InvalidInput.exception(
- "Rule[%s] has an interval that contains interval for rule[%s]. The interval[%s] also covers interval[%s].",
- currRule,
- nextRule,
- currInterval,
- nextInterval
- );
+
+ for (int j = i + 1; j < rules.size(); j++) {
+ final Rule nextRule = rules.get(j);
+ final Interval nextInterval = nextRule.getEligibleInterval(now);
+ if (currInterval.contains(nextInterval)) {
+ // If the current rule has eternity, it covers everything following it.
+ // Or if the current rule still covers the next rule at the current interval boundaries, then the
+ // next rule will never fire at any time, so throw an exception.
+ if (Intervals.ETERNITY.equals(currInterval) ||
+ (currRule.getEligibleInterval(currInterval.getStart())
+ .contains(nextRule.getEligibleInterval(currInterval.getStart()))
+ && currRule.getEligibleInterval(currInterval.getEnd())
+ .contains(nextRule.getEligibleInterval(currInterval.getEnd())))) {
+ throw InvalidInput.exception(
+ "Rule[%s] has an interval that fully contains the interval for rule[%s]."
+ + " i.e., interval[%s] hides interval[%s]. Please fix the rules and retry.",
+ currRule,
+ nextRule,
+ currInterval,
+ nextInterval
+ );
+ }
}
}
}
diff --git a/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java b/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java
index fd2234e8b34b..7ec3b5acc351 100644
--- a/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java
+++ b/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java
@@ -32,13 +32,13 @@
import org.apache.druid.server.coordinator.rules.ForeverDropRule;
import org.apache.druid.server.coordinator.rules.ForeverLoadRule;
import org.apache.druid.server.coordinator.rules.IntervalBroadcastDistributionRule;
+import org.apache.druid.server.coordinator.rules.IntervalDropRule;
import org.apache.druid.server.coordinator.rules.IntervalLoadRule;
import org.apache.druid.server.coordinator.rules.PeriodBroadcastDistributionRule;
import org.apache.druid.server.coordinator.rules.PeriodDropBeforeRule;
import org.apache.druid.server.coordinator.rules.PeriodDropRule;
import org.apache.druid.server.coordinator.rules.PeriodLoadRule;
import org.apache.druid.server.coordinator.rules.Rule;
-import org.apache.druid.server.coordinator.rules.Rules;
import org.easymock.EasyMock;
import org.joda.time.Interval;
import org.joda.time.Period;
@@ -191,20 +191,20 @@ public void testSetDatasourceRulesWithInvalidLoadRules()
final RulesResource rulesResource = new RulesResource(databaseRuleManager, auditManager);
final HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class);
- final IntervalLoadRule loadInterval1 = new IntervalLoadRule(
- Intervals.of("2023-07-29T01:00:00Z/2023-12-30T01:00:00Z"),
+ final PeriodLoadRule loadPT1H = new PeriodLoadRule(new Period("PT1H"), true, null, null);
+ final PeriodLoadRule loadPT1HNoFuture = new PeriodLoadRule(new Period("PT1H"), false, null, null);
+ final PeriodLoadRule loadP3M = new PeriodLoadRule(new Period("P3M"), true, null, null);
+ final PeriodLoadRule loadP6M = new PeriodLoadRule(new Period("P6M"), true, null, null);
+ final IntervalLoadRule loadInterval2020To2023 = new IntervalLoadRule(
+ Intervals.of("2020/2023"),
null,
null
);
- final IntervalLoadRule loadInterval2 = new IntervalLoadRule(
- Intervals.of("2023-09-29T01:00:00Z/2023-10-30T01:00:00Z"),
+ final IntervalLoadRule loadInterval2021To2022 = new IntervalLoadRule(
+ Intervals.of("2021/2022"),
null,
null
);
- final PeriodLoadRule loadPT1H = new PeriodLoadRule(new Period("PT1H"), true, null, null);
- final PeriodLoadRule loadPT1HNoFuture = new PeriodLoadRule(new Period("PT1H"), false, null, null);
- final PeriodLoadRule loadP3M = new PeriodLoadRule(new Period("P3M"), true, null, null);
- final PeriodLoadRule loadP6M = new PeriodLoadRule(new Period("P6M"), true, null, null);
final ForeverLoadRule loadForever = new ForeverLoadRule(null, null);
final List rules = new ArrayList<>();
@@ -213,7 +213,7 @@ public void testSetDatasourceRulesWithInvalidLoadRules()
rules.add(loadForever);
DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", loadP6M, loadP3M)
+ StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", loadP6M, loadP3M)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
rules.clear();
@@ -222,17 +222,17 @@ public void testSetDatasourceRulesWithInvalidLoadRules()
rules.add(loadP6M);
DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", loadForever, loadP6M)
+ StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", loadForever, loadP6M)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
rules.clear();
rules.add(loadForever);
rules.add(loadPT1H);
- rules.add(loadInterval2);
+ rules.add(loadInterval2021To2022);
rules.add(loadP6M);
DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", loadForever, loadPT1H)
+ StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", loadForever, loadPT1H)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
rules.clear();
@@ -241,7 +241,7 @@ public void testSetDatasourceRulesWithInvalidLoadRules()
rules.add(loadP6M);
DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", loadPT1H, loadPT1HNoFuture)
+ StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", loadPT1H, loadPT1HNoFuture)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
rules.clear();
@@ -250,27 +250,38 @@ public void testSetDatasourceRulesWithInvalidLoadRules()
rules.add(loadP6M);
DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", loadPT1H, loadPT1H)
+ StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", loadPT1H, loadPT1H)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
rules.clear();
- rules.add(loadInterval1);
- rules.add(loadInterval2);
+ rules.add(loadInterval2020To2023);
+ rules.add(loadInterval2021To2022);
rules.add(loadP6M);
rules.add(loadForever);
DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", loadInterval1, loadInterval2)
+ StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", loadInterval2020To2023, loadInterval2021To2022)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
rules.clear();
rules.add(loadP6M);
- rules.add(loadInterval1);
+ rules.add(loadInterval2021To2022);
+ rules.add(loadP3M);
+ rules.add(loadInterval2020To2023);
+
+ DruidExceptionMatcher.invalidInput().expectMessageContains(
+ StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", loadP6M, loadP3M)
+ ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
+
+
+ rules.clear();
+ rules.add(loadP6M);
+ rules.add(loadInterval2020To2023);
rules.add(loadForever);
- rules.add(loadInterval2);
+ rules.add(loadInterval2021To2022);
DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", loadForever, loadInterval2)
+ StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", loadInterval2020To2023, loadInterval2021To2022)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
}
@@ -285,6 +296,8 @@ public void testDatasourceRulesWithInvalidDropRules()
final PeriodDropBeforeRule dropBeforeByP3M = new PeriodDropBeforeRule(new Period("P3M"));
final PeriodDropBeforeRule dropBeforeByP6M = new PeriodDropBeforeRule(new Period("P6M"));
final PeriodDropRule dropByP1M = new PeriodDropRule(new Period("P1M"), true);
+ final IntervalDropRule dropInterval2000To2020 = new IntervalDropRule(Intervals.of("2000/2020"));
+ final IntervalDropRule dropInterval2010To2020 = new IntervalDropRule(Intervals.of("2010/2020"));
final PeriodDropRule dropByP1MNoFuture = new PeriodDropRule(new Period("P1M"), false);
final PeriodDropRule dropByP2M = new PeriodDropRule(new Period("P2M"), true);
final ForeverDropRule dropForever = new ForeverDropRule();
@@ -296,7 +309,7 @@ public void testDatasourceRulesWithInvalidDropRules()
rules.add(dropForever);
DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", dropBeforeByP3M, dropBeforeByP6M)
+ StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", dropBeforeByP3M, dropBeforeByP6M)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
rules.clear();
@@ -305,7 +318,17 @@ public void testDatasourceRulesWithInvalidDropRules()
rules.add(dropForever);
DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", dropByP2M, dropByP1M)
+ StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", dropByP2M, dropByP1M)
+ ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
+
+ rules.clear();
+ rules.add(dropInterval2000To2020);
+ rules.add(dropByP1M);
+ rules.add(dropByP2M);
+ rules.add(dropInterval2010To2020);
+
+ DruidExceptionMatcher.invalidInput().expectMessageContains(
+ StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", dropInterval2000To2020, dropInterval2010To2020)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
rules.clear();
@@ -314,7 +337,7 @@ public void testDatasourceRulesWithInvalidDropRules()
rules.add(dropForever);
DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", dropByP1M, dropByP1MNoFuture)
+ StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", dropByP1M, dropByP1MNoFuture)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
@@ -324,7 +347,7 @@ public void testDatasourceRulesWithInvalidDropRules()
rules.add(dropByP2M);
DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", dropForever, dropByP1M)
+ StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", dropForever, dropByP1M)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
}
@@ -340,20 +363,20 @@ public void testDatasourceRulesWithInvalidBroadcastRules()
final PeriodBroadcastDistributionRule broadcastPT1H = new PeriodBroadcastDistributionRule(new Period("PT1H"), true);
final PeriodBroadcastDistributionRule broadcastPT1HNoFuture = new PeriodBroadcastDistributionRule(new Period("PT1H"), false);
final PeriodBroadcastDistributionRule broadcastPT2H = new PeriodBroadcastDistributionRule(new Period("PT2H"), true);
- final IntervalBroadcastDistributionRule broadcastInterval1 = new IntervalBroadcastDistributionRule(
- Intervals.of("2000-09-29T01:00:00Z/2050-10-30T01:00:00Z")
+ final IntervalBroadcastDistributionRule broadcastInterval2000To2050 = new IntervalBroadcastDistributionRule(
+ Intervals.of("2000/2050")
);
- final IntervalBroadcastDistributionRule broadcastInterval2 = new IntervalBroadcastDistributionRule(
- Intervals.of("2010-09-29T01:00:00Z/2020-10-30T01:00:00Z")
+ final IntervalBroadcastDistributionRule broadcastInterval2010To2020 = new IntervalBroadcastDistributionRule(
+ Intervals.of("2010/2020")
);
final List rules = new ArrayList<>();
- rules.add(broadcastInterval1);
- rules.add(broadcastInterval2);
+ rules.add(broadcastInterval2000To2050);
+ rules.add(broadcastInterval2010To2020);
rules.add(broadcastForever);
DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", broadcastInterval1, broadcastInterval2)
+ StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", broadcastInterval2000To2050, broadcastInterval2010To2020)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
rules.clear();
@@ -362,7 +385,7 @@ public void testDatasourceRulesWithInvalidBroadcastRules()
rules.add(broadcastForever);
DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", broadcastPT2H, broadcastPT1H)
+ StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", broadcastPT2H, broadcastPT1H)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
rules.clear();
@@ -371,7 +394,7 @@ public void testDatasourceRulesWithInvalidBroadcastRules()
rules.add(broadcastForever);
DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", broadcastPT1H, broadcastPT1H)
+ StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", broadcastPT1H, broadcastPT1H)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
// Same interval with and without future.
@@ -381,18 +404,18 @@ public void testDatasourceRulesWithInvalidBroadcastRules()
rules.add(broadcastForever);
DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", broadcastPT1H, broadcastPT1HNoFuture)
+ StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", broadcastPT1H, broadcastPT1HNoFuture)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
rules.clear();
rules.add(broadcastPT1H);
rules.add(broadcastPT2H);
- rules.add(broadcastInterval2);
+ rules.add(broadcastInterval2010To2020);
rules.add(broadcastForever);
- rules.add(broadcastInterval1);
+ rules.add(broadcastInterval2000To2050);
DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", broadcastForever, broadcastInterval1)
+ StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", broadcastForever, broadcastInterval2000To2050)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
}
@@ -404,13 +427,13 @@ public void testSetDatasourceRulesWithDifferentInvalidRules()
final RulesResource rulesResource = new RulesResource(databaseRuleManager, auditManager);
final HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class);
- final IntervalLoadRule loadLargeInteval = new IntervalLoadRule(
- Intervals.of("1980-07-29T01:00:00Z/2050-12-30T01:00:00Z"),
+ final IntervalLoadRule loadInterval1980To2050 = new IntervalLoadRule(
+ Intervals.of("1980/2050"),
null,
null
);
- final IntervalLoadRule loadSmallInterval = new IntervalLoadRule(
- Intervals.of("2020-09-29T01:00:00Z/2025-10-30T01:00:00Z"),
+ final IntervalLoadRule loadInterval2020To2025 = new IntervalLoadRule(
+ Intervals.of("2020/2025"),
null,
null
);
@@ -422,9 +445,9 @@ public void testSetDatasourceRulesWithDifferentInvalidRules()
final PeriodBroadcastDistributionRule broadcastPT15m = new PeriodBroadcastDistributionRule(new Period("PT15m"), true);
final List rules = new ArrayList<>();
- rules.add(loadSmallInterval);
- rules.add(loadLargeInteval);
+ rules.add(loadInterval2020To2025);
rules.add(broadcastPT15m);
+ rules.add(loadInterval1980To2050);
rules.add(loadPT1H);
rules.add(loadP3M);
rules.add(loadP6M);
@@ -432,7 +455,7 @@ public void testSetDatasourceRulesWithDifferentInvalidRules()
rules.add(dropForever);
DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that contains interval for rule[%s].", loadForever, dropForever)
+ StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", loadForever, dropForever)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
}
From 320cd90ccf4156fe9e53c9b784be1a9fb36fcc35 Mon Sep 17 00:00:00 2001
From: Abhishek Balaji Radhakrishnan
Date: Tue, 26 Sep 2023 22:06:29 -0700
Subject: [PATCH 7/9] Minor adjustments for consistency
---
.../druid/server/coordinator/rules/PeriodDropBeforeRule.java | 2 ++
.../apache/druid/server/coordinator/rules/PeriodDropRule.java | 4 +++-
.../apache/druid/server/coordinator/rules/PeriodLoadRule.java | 4 +++-
3 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropBeforeRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropBeforeRule.java
index 07e220a1dfdc..9b9b2c0a72aa 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropBeforeRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropBeforeRule.java
@@ -22,6 +22,7 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.druid.java.util.common.DateTimes;
+import org.apache.druid.java.util.common.JodaUtils;
import org.apache.druid.timeline.DataSegment;
import org.joda.time.DateTime;
import org.joda.time.Interval;
@@ -68,6 +69,7 @@ public boolean appliesTo(Interval theInterval, DateTime referenceTimestamp)
@Override
public Interval getEligibleInterval(DateTime referenceTimestamp)
{
+// return new Interval(DateTimes.utc(JodaUtils.MIN_INSTANT), referenceTimestamp.minus(period));
return new Interval(DateTimes.utc(Long.MIN_VALUE), referenceTimestamp.minus(period));
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropRule.java
index c999bb01ad96..47744dffb8ba 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropRule.java
@@ -21,6 +21,8 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.druid.java.util.common.DateTimes;
+import org.apache.druid.java.util.common.JodaUtils;
import org.apache.druid.timeline.DataSegment;
import org.joda.time.DateTime;
import org.joda.time.Interval;
@@ -85,7 +87,7 @@ public boolean appliesTo(Interval theInterval, DateTime referenceTimestamp)
@Override
public Interval getEligibleInterval(DateTime referenceTimestamp)
{
- return includeFuture ? new Interval(referenceTimestamp.minus(period), referenceTimestamp.plus(period))
+ return includeFuture ? new Interval(referenceTimestamp.minus(period), DateTimes.utc(JodaUtils.MAX_INSTANT))
: new Interval(referenceTimestamp.minus(period), referenceTimestamp);
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodLoadRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodLoadRule.java
index 0481195ec0a7..0e6f32f1cadb 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodLoadRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodLoadRule.java
@@ -21,6 +21,8 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.druid.java.util.common.DateTimes;
+import org.apache.druid.java.util.common.JodaUtils;
import org.apache.druid.timeline.DataSegment;
import org.joda.time.DateTime;
import org.joda.time.Interval;
@@ -86,7 +88,7 @@ public boolean appliesTo(Interval interval, DateTime referenceTimestamp)
@Override
public Interval getEligibleInterval(DateTime referenceTimestamp)
{
- return includeFuture ? new Interval(referenceTimestamp.minus(period), referenceTimestamp.plus(period))
+ return includeFuture ? new Interval(referenceTimestamp.minus(period), DateTimes.utc(JodaUtils.MAX_INSTANT))
: new Interval(referenceTimestamp.minus(period), referenceTimestamp);
}
From d729fdff375348ac617744e57bbd2d2cb9504bae Mon Sep 17 00:00:00 2001
From: Abhishek Balaji Radhakrishnan
Date: Tue, 26 Sep 2023 22:07:09 -0700
Subject: [PATCH 8/9] Add separate test classes. Do some code cleanup.
---
.../PeriodBroadcastDistributionRule.java | 4 +-
.../rules/PeriodDropBeforeRule.java | 11 +-
.../coordinator/rules/PeriodDropRule.java | 3 +-
.../coordinator/rules/PeriodLoadRule.java | 3 +-
.../druid/server/coordinator/rules/Rule.java | 6 +-
.../druid/server/coordinator/rules/Rules.java | 23 +-
.../rules/RulesEligibleIntervalTest.java | 195 ++++++++++
.../server/coordinator/rules/RulesTest.java | 149 ++++++++
.../druid/server/http/RulesResourceTest.java | 354 ++----------------
9 files changed, 408 insertions(+), 340 deletions(-)
create mode 100644 server/src/test/java/org/apache/druid/server/coordinator/rules/RulesEligibleIntervalTest.java
create mode 100644 server/src/test/java/org/apache/druid/server/coordinator/rules/RulesTest.java
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodBroadcastDistributionRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodBroadcastDistributionRule.java
index 5de02add3316..17bbf7be789b 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodBroadcastDistributionRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodBroadcastDistributionRule.java
@@ -21,6 +21,8 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.druid.java.util.common.DateTimes;
+import org.apache.druid.java.util.common.JodaUtils;
import org.apache.druid.timeline.DataSegment;
import org.joda.time.DateTime;
import org.joda.time.Interval;
@@ -68,7 +70,7 @@ public boolean appliesTo(Interval interval, DateTime referenceTimestamp)
@Override
public Interval getEligibleInterval(DateTime referenceTimestamp)
{
- return includeFuture ? new Interval(referenceTimestamp.minus(period), referenceTimestamp.plus(period))
+ return includeFuture ? new Interval(referenceTimestamp.minus(period), DateTimes.utc(JodaUtils.MAX_INSTANT))
: new Interval(referenceTimestamp.minus(period), referenceTimestamp);
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropBeforeRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropBeforeRule.java
index 9b9b2c0a72aa..7198e5af7006 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropBeforeRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropBeforeRule.java
@@ -22,7 +22,6 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.druid.java.util.common.DateTimes;
-import org.apache.druid.java.util.common.JodaUtils;
import org.apache.druid.timeline.DataSegment;
import org.joda.time.DateTime;
import org.joda.time.Interval;
@@ -69,8 +68,14 @@ public boolean appliesTo(Interval theInterval, DateTime referenceTimestamp)
@Override
public Interval getEligibleInterval(DateTime referenceTimestamp)
{
-// return new Interval(DateTimes.utc(JodaUtils.MIN_INSTANT), referenceTimestamp.minus(period));
- return new Interval(DateTimes.utc(Long.MIN_VALUE), referenceTimestamp.minus(period));
+ final DateTime end = referenceTimestamp.minus(period);
+ if (end.isBefore(DateTimes.MIN)) {
+ // We use Long.MIN_VALUE as the start here (instead of DateTimes.MIN) when end is < DateTimes.MIN because the
+ // resulting interval will be invalid where start > end. This is true for referenceTimestamp = DateTimes.MIN.
+ return new Interval(DateTimes.utc(Long.MIN_VALUE), end);
+ } else {
+ return new Interval(DateTimes.MIN, end);
+ }
}
@Override
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropRule.java
index 47744dffb8ba..e92e55a3c9a8 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodDropRule.java
@@ -22,7 +22,6 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.druid.java.util.common.DateTimes;
-import org.apache.druid.java.util.common.JodaUtils;
import org.apache.druid.timeline.DataSegment;
import org.joda.time.DateTime;
import org.joda.time.Interval;
@@ -87,7 +86,7 @@ public boolean appliesTo(Interval theInterval, DateTime referenceTimestamp)
@Override
public Interval getEligibleInterval(DateTime referenceTimestamp)
{
- return includeFuture ? new Interval(referenceTimestamp.minus(period), DateTimes.utc(JodaUtils.MAX_INSTANT))
+ return includeFuture ? new Interval(referenceTimestamp.minus(period), DateTimes.MAX)
: new Interval(referenceTimestamp.minus(period), referenceTimestamp);
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodLoadRule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodLoadRule.java
index 0e6f32f1cadb..b799a664eb45 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodLoadRule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/PeriodLoadRule.java
@@ -22,7 +22,6 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.druid.java.util.common.DateTimes;
-import org.apache.druid.java.util.common.JodaUtils;
import org.apache.druid.timeline.DataSegment;
import org.joda.time.DateTime;
import org.joda.time.Interval;
@@ -88,7 +87,7 @@ public boolean appliesTo(Interval interval, DateTime referenceTimestamp)
@Override
public Interval getEligibleInterval(DateTime referenceTimestamp)
{
- return includeFuture ? new Interval(referenceTimestamp.minus(period), DateTimes.utc(JodaUtils.MAX_INSTANT))
+ return includeFuture ? new Interval(referenceTimestamp.minus(period), DateTimes.MAX)
: new Interval(referenceTimestamp.minus(period), referenceTimestamp);
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/Rule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/Rule.java
index c34b086484c8..72c02ceb5416 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/Rule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/Rule.java
@@ -52,9 +52,9 @@ public interface Rule
void run(DataSegment segment, SegmentActionHandler segmentHandler);
/**
- * Return an eligible interval from the reference timestamp. Implementations
- * must return a valid interval based on the rule type.
- * @param referenceTimestamp base timestamp
+ * Returns the interval eligible for this rule. The interval must be computed based on the rule type
+ * optionally using {@code referenceTimestamp}. {@code referenceTimestamp} must be a timestamp
+ * between [{@link org.apache.druid.java.util.common.DateTimes.MIN}, {@link org.apache.druid.java.util.common.DateTimes.MAX}).
*/
Interval getEligibleInterval(DateTime referenceTimestamp);
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/Rules.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/Rules.java
index 540bd1842ff7..bf068de088c2 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/Rules.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/Rules.java
@@ -50,10 +50,22 @@ private Rules()
}
/**
- * Validate rules. This method throws an exception if a rule contain an interval that
- * fully covers another subsequent rules' interval in the list. Rules that will be evaluated at some point
- * are considered to be legitimate.
- * @param rules Datasource rules.
+ * Validates the given list of retention rules for a datasource (or cluster default).
+ * A rule is considered valid only if it will be evaluated at some point, i.e. its eligible interval
+ * is not fully covered by the eligible interval of any preceding rule in the list.
+ *
+ * Consider two rules r1 and r2. Assume r1 and r2's eligible intervals at the time when the rules are evaluated are
+ * i1 and i2 respectively. r1 and r2 are invalid if:
+ *
+ * - i1 is eternity. i.e., eternity fully covers i2 and any other interval that follows it. Or
+ * - i1 fully contains i2 and
+ * - r1's eligible interval at i1's start and end fully contain r2's eligible interval at i1's start and end
+ * respectively. This boundary check is used to identify rules that will fire at some point. i.e., period based rules
+ * will return distinct eligible intervals at the boundaries, whereas broadcast and interval based rules will return
+ * fixed intervals regardless of the boundary.
+ *
+ *
+ * @throws org.apache.druid.error.DruidException with error code "invalidInput" if any of the given rules is not valid.
*/
public static void validateRules(final List rules)
{
@@ -70,9 +82,6 @@ public static void validateRules(final List rules)
final Rule nextRule = rules.get(j);
final Interval nextInterval = nextRule.getEligibleInterval(now);
if (currInterval.contains(nextInterval)) {
- // If the current rule has eternity, it covers everything following it.
- // Or if the current rule still covers the next rule at the current interval boundaries, then the
- // next rule will never fire at any time, so throw an exception.
if (Intervals.ETERNITY.equals(currInterval) ||
(currRule.getEligibleInterval(currInterval.getStart())
.contains(nextRule.getEligibleInterval(currInterval.getStart()))
diff --git a/server/src/test/java/org/apache/druid/server/coordinator/rules/RulesEligibleIntervalTest.java b/server/src/test/java/org/apache/druid/server/coordinator/rules/RulesEligibleIntervalTest.java
new file mode 100644
index 000000000000..83fafbde0f34
--- /dev/null
+++ b/server/src/test/java/org/apache/druid/server/coordinator/rules/RulesEligibleIntervalTest.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.server.coordinator.rules;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.druid.java.util.common.DateTimes;
+import org.apache.druid.java.util.common.Intervals;
+import org.apache.druid.java.util.common.JodaUtils;
+import org.joda.time.DateTime;
+import org.joda.time.Interval;
+import org.joda.time.Period;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RulesEligibleIntervalTest
+{
+ private final DateTime referenceTime;
+
+ @Parameterized.Parameters
+ public static Object[] getReferenceTimestamps()
+ {
+ final DateTime now = DateTimes.nowUtc();
+ return new Object[]{
+ now,
+ now.minusYears(5),
+ now.plusYears(10),
+ DateTimes.utc(0),
+ DateTimes.utc(JodaUtils.MIN_INSTANT + 1),
+ DateTimes.utc(JodaUtils.MIN_INSTANT),
+ DateTimes.utc(JodaUtils.MAX_INSTANT - 1),
+ DateTimes.utc(JodaUtils.MAX_INSTANT)
+ };
+ }
+
+ public RulesEligibleIntervalTest(final DateTime referenceTimestamp)
+ {
+ this.referenceTime = referenceTimestamp;
+ }
+
+ @Test
+ public void testPeriodLoadEligibleInterval()
+ {
+ final Period period = new Period("P50Y");
+ final PeriodLoadRule loadPT1H = new PeriodLoadRule(
+ period,
+ true,
+ null,
+ null
+ );
+ Assert.assertEquals(
+ new Interval(this.referenceTime.minus(period), DateTimes.MAX),
+ loadPT1H.getEligibleInterval(this.referenceTime)
+ );
+ }
+
+ @Test
+ public void testPeriodLoadExcludingFutureEligibleInterval()
+ {
+ final Period period = new Period("PT1H");
+ final PeriodLoadRule rule = new PeriodLoadRule(
+ period,
+ false,
+ null,
+ null
+ );
+ Assert.assertEquals(new Interval(period, this.referenceTime), rule.getEligibleInterval(this.referenceTime));
+ }
+
+ @Test
+ public void testIntervalLoadEligibleInterval()
+ {
+ final Interval interval = Intervals.of("2000/3000");
+ final IntervalLoadRule rule = new IntervalLoadRule(
+ interval,
+ null,
+ null
+ );
+ Assert.assertEquals(interval, rule.getEligibleInterval(this.referenceTime));
+ }
+
+ @Test
+ public void testForeverLoadEligibleInterval()
+ {
+ final ForeverLoadRule rule = new ForeverLoadRule(ImmutableMap.of(), false);
+ Assert.assertEquals(Intervals.ETERNITY, rule.getEligibleInterval(this.referenceTime));
+ }
+
+ @Test
+ public void testPeriodDropEligibleInterval()
+ {
+ final Period period = new Period("P5000Y");
+ final PeriodDropRule rule = new PeriodDropRule(
+ period,
+ true
+ );
+ Assert.assertEquals(
+ new Interval(this.referenceTime.minus(period), DateTimes.utc(JodaUtils.MAX_INSTANT)),
+ rule.getEligibleInterval(this.referenceTime)
+ );
+ }
+
+ @Test
+ public void testPeriodDropExcludingFutureEligibleInterval()
+ {
+ final Period period = new Period("P50Y");
+ final PeriodDropRule rule = new PeriodDropRule(
+ period,
+ false
+ );
+ Assert.assertEquals(
+ new Interval(this.referenceTime.minus(period), this.referenceTime),
+ rule.getEligibleInterval(this.referenceTime)
+ );
+ }
+
+ @Test
+ public void testPeriodDropBeforeEligibleInterval()
+ {
+ final Period period = new Period("P50Y");
+ final PeriodDropBeforeRule rule = new PeriodDropBeforeRule(period);
+
+ if (this.referenceTime.minus(period).isBefore(DateTimes.MIN)) {
+ Assert.assertEquals(
+ new Interval(DateTimes.utc(Long.MIN_VALUE), this.referenceTime.minus(period)),
+ rule.getEligibleInterval(this.referenceTime)
+ );
+ } else {
+ Assert.assertEquals(
+ new Interval(DateTimes.MIN, this.referenceTime.minus(period)),
+ rule.getEligibleInterval(this.referenceTime)
+ );
+ }
+ }
+
+ @Test
+ public void testForeverDropEligibleInterval()
+ {
+ final ForeverDropRule rule = new ForeverDropRule();
+ Assert.assertEquals(Intervals.ETERNITY, rule.getEligibleInterval(this.referenceTime));
+ }
+
+ @Test
+ public void testPeriodBroadcastEligibleInterval()
+ {
+ final Period period = new Period("P15Y");
+ final PeriodBroadcastDistributionRule rule = new PeriodBroadcastDistributionRule(period, true);
+ Assert.assertEquals(
+ new Interval(referenceTime.minus(period), DateTimes.MAX),
+ rule.getEligibleInterval(referenceTime)
+ );
+ }
+
+ @Test
+ public void testPeriodBroadcastExcludingFutureEligibleInterval()
+ {
+ final Period period = new Period("P15Y");
+ final PeriodBroadcastDistributionRule rule = new PeriodBroadcastDistributionRule(period, false);
+ Assert.assertEquals(new Interval(period, this.referenceTime), rule.getEligibleInterval(this.referenceTime));
+ }
+
+ @Test
+ public void testForeverBroadcastEligibleInterval()
+ {
+ final ForeverBroadcastDistributionRule rule = new ForeverBroadcastDistributionRule();
+ Assert.assertEquals(Intervals.ETERNITY, rule.getEligibleInterval(this.referenceTime));
+ }
+
+ @Test
+ public void testIntervalBroadcastEligibleInterval()
+ {
+ final Interval interval = Intervals.of("1993/2070");
+ final IntervalBroadcastDistributionRule rule = new IntervalBroadcastDistributionRule(interval);
+ Assert.assertEquals(interval, rule.getEligibleInterval(this.referenceTime));
+ }
+}
diff --git a/server/src/test/java/org/apache/druid/server/coordinator/rules/RulesTest.java b/server/src/test/java/org/apache/druid/server/coordinator/rules/RulesTest.java
new file mode 100644
index 000000000000..b6bde979a302
--- /dev/null
+++ b/server/src/test/java/org/apache/druid/server/coordinator/rules/RulesTest.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.server.coordinator.rules;
+
+import org.apache.druid.error.DruidExceptionMatcher;
+import org.apache.druid.java.util.common.Intervals;
+import org.apache.druid.java.util.common.StringUtils;
+import org.joda.time.Period;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+public class RulesTest
+{
+ // Load rules
+ private static final PeriodLoadRule LOAD_PT1H = new PeriodLoadRule(
+ new Period("PT1H"), true, null, null
+ );
+ private static final PeriodLoadRule LOAD_PT1H_EXLUDE_FUTURE = new PeriodLoadRule(
+ new Period("PT1H"), false, null, null
+ );
+ private static final PeriodLoadRule LOAD_P3M = new PeriodLoadRule(
+ new Period("P3M"), true, null, null
+ );
+ private static final IntervalLoadRule LOAD_2020_2023 = new IntervalLoadRule(
+ Intervals.of("2020/2023"), null, null
+ );
+ private static final IntervalLoadRule LOAD_2021_2022 = new IntervalLoadRule(
+ Intervals.of("2021/2022"), null, null
+ );
+ private static final IntervalLoadRule LOAD_1980_2050 = new IntervalLoadRule(
+ Intervals.of("1980/2050"), null, null
+ );
+ private static final ForeverLoadRule LOAD_FOREVER = new ForeverLoadRule(null, null);
+
+ // Drop rules
+ private static final PeriodDropBeforeRule DROP_BEFORE_P3M = new PeriodDropBeforeRule(new Period("P3M"));
+ private static final PeriodDropBeforeRule DROP_BEFORE_P6M = new PeriodDropBeforeRule(new Period("P6M"));
+ private static final PeriodDropRule DROP_P1M = new PeriodDropRule(new Period("P1M"), true);
+ private static final PeriodDropRule DROP_P2M = new PeriodDropRule(new Period("P2M"), true);
+ private static final IntervalDropRule DROP_2000_2020 = new IntervalDropRule(Intervals.of("2000/2020"));
+ private static final IntervalDropRule DROP_2010_2020 = new IntervalDropRule(Intervals.of("2010/2020"));
+ private static final ForeverDropRule DROP_FOREVER = new ForeverDropRule();
+
+ // Broadcast rules
+ private static final PeriodBroadcastDistributionRule BROADCAST_PT1H = new PeriodBroadcastDistributionRule(
+ new Period("PT1H"), true
+ );
+ private static final PeriodBroadcastDistributionRule BROADCAST_PT1H_EXCLUDE_FUTURE = new PeriodBroadcastDistributionRule(
+ new Period("PT1H"), false
+ );
+ private static final PeriodBroadcastDistributionRule BROADCAST_PT2H = new PeriodBroadcastDistributionRule(
+ new Period("PT2H"), true
+ );
+ private static final IntervalBroadcastDistributionRule BROADCAST_2000_2050 = new IntervalBroadcastDistributionRule(
+ Intervals.of("2000/2050")
+ );
+ private static final IntervalBroadcastDistributionRule BROADCAST_2010_2020 = new IntervalBroadcastDistributionRule(
+ Intervals.of("2010/2020")
+ );
+ private static final ForeverBroadcastDistributionRule BROADCAST_FOREVER = new ForeverBroadcastDistributionRule();
+
+ private final List rules;
+ private final boolean isInvalid;
+ private final Rule invalidRule1;
+ private final Rule invalidRule2;
+
+ public RulesTest(final List rules, final boolean isInvalid, final Rule invalidRule1, final Rule invalidRule2)
+ {
+ this.rules = rules;
+ this.isInvalid = isInvalid;
+ this.invalidRule1 = invalidRule1;
+ this.invalidRule2 = invalidRule2;
+ }
+
+ @Parameterized.Parameters
+ public static Object[] inputsAndExpectations()
+ {
+ return new Object[][] {
+ // Invalid rules
+ {getRules(LOAD_PT1H, LOAD_2021_2022, LOAD_FOREVER, LOAD_P3M), true, LOAD_FOREVER, LOAD_P3M},
+ {getRules(LOAD_PT1H, LOAD_P3M, LOAD_PT1H_EXLUDE_FUTURE, LOAD_1980_2050), true, LOAD_PT1H, LOAD_PT1H_EXLUDE_FUTURE},
+ {getRules(LOAD_PT1H, LOAD_P3M, LOAD_2020_2023, LOAD_P3M), true, LOAD_P3M, LOAD_P3M},
+ {getRules(LOAD_2020_2023, LOAD_2021_2022, LOAD_P3M, LOAD_FOREVER), true, LOAD_2020_2023, LOAD_2021_2022},
+ {getRules(LOAD_P3M, LOAD_2021_2022, LOAD_PT1H, LOAD_2020_2023), true, LOAD_P3M, LOAD_PT1H},
+ {getRules(LOAD_P3M, LOAD_2021_2022, LOAD_FOREVER, LOAD_2020_2023), true, LOAD_FOREVER, LOAD_2020_2023},
+ {getRules(DROP_BEFORE_P3M, DROP_P1M, DROP_BEFORE_P6M, DROP_FOREVER), true, DROP_BEFORE_P3M, DROP_BEFORE_P6M},
+ {getRules(DROP_P2M, DROP_P1M, DROP_FOREVER), true, DROP_P2M, DROP_P1M},
+ {getRules(DROP_2000_2020, DROP_P1M, DROP_P2M, DROP_2010_2020), true, DROP_2000_2020, DROP_2010_2020},
+ {getRules(DROP_P1M, DROP_FOREVER, DROP_P2M), true, DROP_FOREVER, DROP_P2M},
+ {getRules(BROADCAST_2000_2050, BROADCAST_PT1H, BROADCAST_2010_2020, BROADCAST_FOREVER), true, BROADCAST_2000_2050, BROADCAST_2010_2020},
+ {getRules(BROADCAST_PT2H, BROADCAST_2000_2050, BROADCAST_PT1H, BROADCAST_FOREVER), true, BROADCAST_PT2H, BROADCAST_PT1H},
+ {getRules(BROADCAST_PT1H, BROADCAST_PT1H_EXCLUDE_FUTURE, BROADCAST_FOREVER), true, BROADCAST_PT1H, BROADCAST_PT1H_EXCLUDE_FUTURE},
+ {getRules(BROADCAST_PT1H, BROADCAST_PT2H, BROADCAST_2010_2020, BROADCAST_FOREVER, BROADCAST_2000_2050), true, BROADCAST_FOREVER, BROADCAST_2000_2050},
+ {getRules(LOAD_PT1H, LOAD_1980_2050, LOAD_P3M, LOAD_FOREVER, DROP_FOREVER), true, LOAD_FOREVER, DROP_FOREVER},
+
+ // Valid rules
+ {null, false, null, null},
+ {getRules(), false, null, null},
+ {getRules(LOAD_FOREVER), false, null, null},
+ {getRules(LOAD_PT1H_EXLUDE_FUTURE, LOAD_PT1H, LOAD_P3M, LOAD_FOREVER), false, null, null},
+ {getRules(DROP_2010_2020, DROP_2000_2020, DROP_P1M, DROP_P2M, DROP_BEFORE_P3M, DROP_FOREVER), false, null, null},
+ {getRules(BROADCAST_PT1H, BROADCAST_PT2H, BROADCAST_2010_2020, BROADCAST_2000_2050, BROADCAST_FOREVER), false, null, null},
+ {getRules(DROP_BEFORE_P6M, DROP_P1M, DROP_BEFORE_P3M, LOAD_2020_2023, DROP_FOREVER), false, null, null},
+ {getRules(DROP_2000_2020, LOAD_PT1H, BROADCAST_2000_2050, LOAD_1980_2050, LOAD_P3M, LOAD_FOREVER), false, null, null},
+ };
+ }
+
+ private static ArrayList getRules(Rule... rules)
+ {
+ return new ArrayList<>(Arrays.asList(rules));
+ }
+
+ @Test
+ public void testValidateRules()
+ {
+ if (this.isInvalid) {
+ DruidExceptionMatcher.invalidInput().expectMessageContains(
+ StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].",
+ invalidRule1, invalidRule2
+ )
+ ).assertThrowsAndMatches(() -> Rules.validateRules(rules));
+ } else {
+ Rules.validateRules(rules);
+ }
+ }
+}
diff --git a/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java b/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java
index 7ec3b5acc351..16e394ccc3c3 100644
--- a/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java
+++ b/server/src/test/java/org/apache/druid/server/http/RulesResourceTest.java
@@ -28,12 +28,8 @@
import org.apache.druid.java.util.common.Intervals;
import org.apache.druid.java.util.common.StringUtils;
import org.apache.druid.metadata.MetadataRuleManager;
-import org.apache.druid.server.coordinator.rules.ForeverBroadcastDistributionRule;
-import org.apache.druid.server.coordinator.rules.ForeverDropRule;
import org.apache.druid.server.coordinator.rules.ForeverLoadRule;
-import org.apache.druid.server.coordinator.rules.IntervalBroadcastDistributionRule;
import org.apache.druid.server.coordinator.rules.IntervalDropRule;
-import org.apache.druid.server.coordinator.rules.IntervalLoadRule;
import org.apache.druid.server.coordinator.rules.PeriodBroadcastDistributionRule;
import org.apache.druid.server.coordinator.rules.PeriodDropBeforeRule;
import org.apache.druid.server.coordinator.rules.PeriodDropRule;
@@ -57,6 +53,17 @@ public class RulesResourceTest
private MetadataRuleManager databaseRuleManager;
private AuditManager auditManager;
+ private static final PeriodLoadRule LOAD_P3M = new PeriodLoadRule(
+ new Period("P3M"), true, null, null
+ );
+ private final ForeverLoadRule LOAD_FOREVER = new ForeverLoadRule(null, null);
+ private static final PeriodDropRule DROP_P2M = new PeriodDropRule(new Period("P2M"), true);
+ private static final PeriodDropBeforeRule DROP_BEFORE_P6M = new PeriodDropBeforeRule(new Period("P6M"));
+ private static final IntervalDropRule DROP_2010_2020 = new IntervalDropRule(Intervals.of("2010/2020"));
+ private static final PeriodBroadcastDistributionRule BROADCAST_PT2H = new PeriodBroadcastDistributionRule(
+ new Period("PT2H"), true
+ );
+
@Before
public void setUp()
{
@@ -184,344 +191,47 @@ public void testSetDatasourceRulesWithEffectivelyNoRule()
}
@Test
- public void testSetDatasourceRulesWithInvalidLoadRules()
- {
- EasyMock.replay(auditManager);
-
- final RulesResource rulesResource = new RulesResource(databaseRuleManager, auditManager);
- final HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class);
-
- final PeriodLoadRule loadPT1H = new PeriodLoadRule(new Period("PT1H"), true, null, null);
- final PeriodLoadRule loadPT1HNoFuture = new PeriodLoadRule(new Period("PT1H"), false, null, null);
- final PeriodLoadRule loadP3M = new PeriodLoadRule(new Period("P3M"), true, null, null);
- final PeriodLoadRule loadP6M = new PeriodLoadRule(new Period("P6M"), true, null, null);
- final IntervalLoadRule loadInterval2020To2023 = new IntervalLoadRule(
- Intervals.of("2020/2023"),
- null,
- null
- );
- final IntervalLoadRule loadInterval2021To2022 = new IntervalLoadRule(
- Intervals.of("2021/2022"),
- null,
- null
- );
- final ForeverLoadRule loadForever = new ForeverLoadRule(null, null);
-
- final List rules = new ArrayList<>();
- rules.add(loadP6M);
- rules.add(loadP3M);
- rules.add(loadForever);
-
- DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", loadP6M, loadP3M)
- ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
-
- rules.clear();
- rules.add(loadP3M);
- rules.add(loadForever);
- rules.add(loadP6M);
-
- DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", loadForever, loadP6M)
- ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
-
- rules.clear();
- rules.add(loadForever);
- rules.add(loadPT1H);
- rules.add(loadInterval2021To2022);
- rules.add(loadP6M);
-
- DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", loadForever, loadPT1H)
- ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
-
- rules.clear();
- rules.add(loadPT1H);
- rules.add(loadPT1HNoFuture);
- rules.add(loadP6M);
-
- DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", loadPT1H, loadPT1HNoFuture)
- ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
-
- rules.clear();
- rules.add(loadPT1H);
- rules.add(loadPT1H);
- rules.add(loadP6M);
-
- DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", loadPT1H, loadPT1H)
- ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
-
- rules.clear();
- rules.add(loadInterval2020To2023);
- rules.add(loadInterval2021To2022);
- rules.add(loadP6M);
- rules.add(loadForever);
-
- DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", loadInterval2020To2023, loadInterval2021To2022)
- ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
-
- rules.clear();
- rules.add(loadP6M);
- rules.add(loadInterval2021To2022);
- rules.add(loadP3M);
- rules.add(loadInterval2020To2023);
-
- DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", loadP6M, loadP3M)
- ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
-
-
- rules.clear();
- rules.add(loadP6M);
- rules.add(loadInterval2020To2023);
- rules.add(loadForever);
- rules.add(loadInterval2021To2022);
-
- DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", loadInterval2020To2023, loadInterval2021To2022)
- ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
- }
-
- @Test
- public void testDatasourceRulesWithInvalidDropRules()
- {
- EasyMock.replay(auditManager);
-
- final RulesResource rulesResource = new RulesResource(databaseRuleManager, auditManager);
- final HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class);
-
- final PeriodDropBeforeRule dropBeforeByP3M = new PeriodDropBeforeRule(new Period("P3M"));
- final PeriodDropBeforeRule dropBeforeByP6M = new PeriodDropBeforeRule(new Period("P6M"));
- final PeriodDropRule dropByP1M = new PeriodDropRule(new Period("P1M"), true);
- final IntervalDropRule dropInterval2000To2020 = new IntervalDropRule(Intervals.of("2000/2020"));
- final IntervalDropRule dropInterval2010To2020 = new IntervalDropRule(Intervals.of("2010/2020"));
- final PeriodDropRule dropByP1MNoFuture = new PeriodDropRule(new Period("P1M"), false);
- final PeriodDropRule dropByP2M = new PeriodDropRule(new Period("P2M"), true);
- final ForeverDropRule dropForever = new ForeverDropRule();
-
- final List rules = new ArrayList<>();
- rules.add(dropBeforeByP3M);
- rules.add(dropBeforeByP6M);
- rules.add(dropByP1M);
- rules.add(dropForever);
-
- DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", dropBeforeByP3M, dropBeforeByP6M)
- ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
-
- rules.clear();
- rules.add(dropByP2M);
- rules.add(dropByP1M);
- rules.add(dropForever);
-
- DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", dropByP2M, dropByP1M)
- ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
-
- rules.clear();
- rules.add(dropInterval2000To2020);
- rules.add(dropByP1M);
- rules.add(dropByP2M);
- rules.add(dropInterval2010To2020);
-
- DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", dropInterval2000To2020, dropInterval2010To2020)
- ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
-
- rules.clear();
- rules.add(dropByP1M);
- rules.add(dropByP1MNoFuture);
- rules.add(dropForever);
-
- DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", dropByP1M, dropByP1MNoFuture)
- ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
-
-
- rules.clear();
- rules.add(dropForever);
- rules.add(dropByP1M);
- rules.add(dropByP2M);
-
- DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", dropForever, dropByP1M)
- ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
- }
-
- @Test
- public void testDatasourceRulesWithInvalidBroadcastRules()
+ public void testSetDatasourceRulesWithValidRules()
{
- EasyMock.replay(auditManager);
-
+ EasyMock.expect(databaseRuleManager.overrideRule(EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject()))
+ .andReturn(true).anyTimes();
+ EasyMock.replay(databaseRuleManager);
final RulesResource rulesResource = new RulesResource(databaseRuleManager, auditManager);
- final HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class);
-
- final ForeverBroadcastDistributionRule broadcastForever = new ForeverBroadcastDistributionRule();
- final PeriodBroadcastDistributionRule broadcastPT1H = new PeriodBroadcastDistributionRule(new Period("PT1H"), true);
- final PeriodBroadcastDistributionRule broadcastPT1HNoFuture = new PeriodBroadcastDistributionRule(new Period("PT1H"), false);
- final PeriodBroadcastDistributionRule broadcastPT2H = new PeriodBroadcastDistributionRule(new Period("PT2H"), true);
- final IntervalBroadcastDistributionRule broadcastInterval2000To2050 = new IntervalBroadcastDistributionRule(
- Intervals.of("2000/2050")
- );
- final IntervalBroadcastDistributionRule broadcastInterval2010To2020 = new IntervalBroadcastDistributionRule(
- Intervals.of("2010/2020")
- );
final List rules = new ArrayList<>();
- rules.add(broadcastInterval2000To2050);
- rules.add(broadcastInterval2010To2020);
- rules.add(broadcastForever);
-
- DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", broadcastInterval2000To2050, broadcastInterval2010To2020)
- ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
-
- rules.clear();
- rules.add(broadcastPT2H);
- rules.add(broadcastPT1H);
- rules.add(broadcastForever);
-
- DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", broadcastPT2H, broadcastPT1H)
- ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
-
- rules.clear();
- rules.add(broadcastPT1H);
- rules.add(broadcastPT1H);
- rules.add(broadcastForever);
-
- DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", broadcastPT1H, broadcastPT1H)
- ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
-
- // Same interval with and without future.
- rules.clear();
- rules.add(broadcastPT1H);
- rules.add(broadcastPT1HNoFuture);
- rules.add(broadcastForever);
+ rules.add(BROADCAST_PT2H);
+ rules.add(DROP_P2M);
+ rules.add(DROP_2010_2020);
+ rules.add(LOAD_P3M);
+ rules.add(DROP_BEFORE_P6M);
+ rules.add(LOAD_FOREVER);
- DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", broadcastPT1H, broadcastPT1HNoFuture)
- ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
-
- rules.clear();
- rules.add(broadcastPT1H);
- rules.add(broadcastPT2H);
- rules.add(broadcastInterval2010To2020);
- rules.add(broadcastForever);
- rules.add(broadcastInterval2000To2050);
-
- DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", broadcastForever, broadcastInterval2000To2050)
- ).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
+ final Response resp = rulesResource.setDatasourceRules("dataSource1", rules, null, null, EasyMock.createMock(HttpServletRequest.class));
+ Assert.assertEquals(200, resp.getStatus());
+ EasyMock.verify(databaseRuleManager);
}
@Test
- public void testSetDatasourceRulesWithDifferentInvalidRules()
+ public void testSetDatasourceRulesWithInvalidRules()
{
EasyMock.replay(auditManager);
final RulesResource rulesResource = new RulesResource(databaseRuleManager, auditManager);
final HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class);
- final IntervalLoadRule loadInterval1980To2050 = new IntervalLoadRule(
- Intervals.of("1980/2050"),
- null,
- null
- );
- final IntervalLoadRule loadInterval2020To2025 = new IntervalLoadRule(
- Intervals.of("2020/2025"),
- null,
- null
- );
- final PeriodLoadRule loadPT1H = new PeriodLoadRule(new Period("PT1H"), null, null, null);
- final PeriodLoadRule loadP3M = new PeriodLoadRule(new Period("P3M"), null, null, null);
- final PeriodLoadRule loadP6M = new PeriodLoadRule(new Period("P6M"), null, null, null);
- final ForeverLoadRule loadForever = new ForeverLoadRule(null, null);
- final ForeverDropRule dropForever = new ForeverDropRule();
- final PeriodBroadcastDistributionRule broadcastPT15m = new PeriodBroadcastDistributionRule(new Period("PT15m"), true);
-
final List rules = new ArrayList<>();
- rules.add(loadInterval2020To2025);
- rules.add(broadcastPT15m);
- rules.add(loadInterval1980To2050);
- rules.add(loadPT1H);
- rules.add(loadP3M);
- rules.add(loadP6M);
- rules.add(loadForever);
- rules.add(dropForever);
+ rules.add(DROP_P2M);
+ rules.add(LOAD_P3M);
+ rules.add(DROP_BEFORE_P6M);
+ rules.add(BROADCAST_PT2H);
+ rules.add(DROP_2010_2020);
+ rules.add(LOAD_FOREVER);
DruidExceptionMatcher.invalidInput().expectMessageContains(
- StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", loadForever, dropForever)
+ StringUtils.format("Rule[%s] has an interval that fully contains the interval for rule[%s].", DROP_P2M, BROADCAST_PT2H)
).assertThrowsAndMatches(() -> rulesResource.setDatasourceRules("dataSource1", rules, null, null, req));
}
- @Test
- public void testSetDatasourceRulesWithValidRules()
- {
- EasyMock.expect(databaseRuleManager.overrideRule(EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject()))
- .andReturn(true).anyTimes();
- EasyMock.replay(databaseRuleManager);
- final RulesResource rulesResource = new RulesResource(databaseRuleManager, auditManager);
-
- final IntervalLoadRule loadLargeInteval = new IntervalLoadRule(
- Intervals.of("1980-07-29T01:00:00Z/2050-12-30T01:00:00Z"),
- null,
- null
- );
- final IntervalLoadRule loadSmallInterval = new IntervalLoadRule(
- Intervals.of("2020-09-29T01:00:00Z/2025-10-30T01:00:00Z"),
- null,
- null
- );
- final PeriodLoadRule loadPT1H = new PeriodLoadRule(new Period("PT1H"), true, null, null);
- final PeriodLoadRule loadPT1HNoFuture = new PeriodLoadRule(new Period("PT1H"), false, null, null);
- final PeriodLoadRule loadP3M = new PeriodLoadRule(new Period("P3M"), true, null, null);
- final PeriodLoadRule loadP6M = new PeriodLoadRule(new Period("P6M"), true, null, null);
- final PeriodDropRule dropByP1M = new PeriodDropRule(new Period("P1M"), true);
- final PeriodDropRule dropByP1MNoFuture = new PeriodDropRule(new Period("P1M"), false);
- final ForeverLoadRule loadForever = new ForeverLoadRule(null, null);
- final ForeverDropRule dropForever = new ForeverDropRule();
- final PeriodBroadcastDistributionRule broadcastPT15m = new PeriodBroadcastDistributionRule(new Period("PT15m"), true);
- final PeriodBroadcastDistributionRule broadcastPT15mNoFuture = new PeriodBroadcastDistributionRule(new Period("PT15m"), false);
-
- final List rules = new ArrayList<>();
- rules.add(loadSmallInterval);
- rules.add(loadLargeInteval);
- rules.add(broadcastPT15m);
- rules.add(loadPT1HNoFuture);
- rules.add(loadPT1H);
- rules.add(dropByP1MNoFuture);
- rules.add(dropByP1M);
- rules.add(loadP3M);
- rules.add(loadP6M);
- rules.add(loadForever);
-
- final Response resp = rulesResource.setDatasourceRules("dataSource1", rules, null, null, EasyMock.createMock(HttpServletRequest.class));
- Assert.assertEquals(200, resp.getStatus());
- EasyMock.verify(databaseRuleManager);
-
- rules.clear();
- rules.add(broadcastPT15mNoFuture);
- rules.add(broadcastPT15m);
- rules.add(loadPT1HNoFuture);
- rules.add(loadPT1H);
- rules.add(dropByP1MNoFuture);
- rules.add(dropByP1M);
- rules.add(loadP3M);
- rules.add(loadP6M);
- rules.add(loadSmallInterval);
- rules.add(loadLargeInteval);
- rules.add(dropForever);
-
- final Response resp2 = rulesResource.setDatasourceRules("dataSource1", rules, null, null, EasyMock.createMock(HttpServletRequest.class));
- Assert.assertEquals(200, resp2.getStatus());
- EasyMock.verify(databaseRuleManager);
- }
-
@Test
public void testGetAllDatasourcesRuleHistoryWithCount()
{
From 966c902d297403d1852ab6152e78388d3f20de03 Mon Sep 17 00:00:00 2001
From: Abhishek Balaji Radhakrishnan
Date: Tue, 26 Sep 2023 23:03:29 -0700
Subject: [PATCH 9/9] fixup intellij inspection and update comment.
---
.../apache/druid/server/coordinator/rules/Rule.java | 4 ++--
.../apache/druid/server/coordinator/rules/Rules.java | 11 ++++++-----
2 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/Rule.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/Rule.java
index 72c02ceb5416..dfb03102fe51 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/Rule.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/Rule.java
@@ -53,8 +53,8 @@ public interface Rule
/**
* Returns the interval eligible for this rule. The interval must be computed based on the rule type
- * optionally using {@code referenceTimestamp}. {@code referenceTimestamp} must be a timestamp
- * between [{@link org.apache.druid.java.util.common.DateTimes.MIN}, {@link org.apache.druid.java.util.common.DateTimes.MAX}).
+ * optionally using {@code referenceTimestamp}. {@code referenceTimestamp} must be a {@link DateTime}
+ * between [{@link org.apache.druid.java.util.common.DateTimes#MIN}, {@link org.apache.druid.java.util.common.DateTimes#MAX}].
*/
Interval getEligibleInterval(DateTime referenceTimestamp);
}
diff --git a/server/src/main/java/org/apache/druid/server/coordinator/rules/Rules.java b/server/src/main/java/org/apache/druid/server/coordinator/rules/Rules.java
index bf068de088c2..5dc714d4f451 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/rules/Rules.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/rules/Rules.java
@@ -55,14 +55,15 @@ private Rules()
* is not fully covered by the eligible interval of any preceding rule in the list.
*
* Consider two rules r1 and r2. Assume r1 and r2's eligible intervals at the time when the rules are evaluated are
- * i1 and i2 respectively. r1 and r2 are invalid if:
+ * i1 and i2, respectively. r1 and r2 are invalid if:
*
* - i1 is eternity. i.e., eternity fully covers i2 and any other interval that follows it. Or
* - i1 fully contains i2 and
- * - r1's eligible interval at i1's start and end fully contain r2's eligible interval at i1's start and end
- * respectively. This boundary check is used to identify rules that will fire at some point. i.e., period based rules
- * will return distinct eligible intervals at the boundaries, whereas broadcast and interval based rules will return
- * fixed intervals regardless of the boundary.
+ * - r1's eligible intervals at i1's start and end fully contain r2's eligible intervals at i1's start and end,
+ * respectively. This boundary check is used to identify rules that will fire at some point. i.e., period type rules
+ * will yield distinct eligible intervals at the boundaries, whereas broadcast and interval type rules will return
+ * fixed intervals regardless of the boundary. Therefore, period rules cannot always fully contain interval
+ * rules and vice-versa.
*
*
* @throws org.apache.druid.error.DruidException with error code "invalidInput" if any of the given rules is not valid.