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 ef5094cbea4a3..f770c686df67a 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 19479c015b91c..b9c7352539ef9 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 35f22fa555f88..5166ed736d2cd 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 b1bf29eedd20a..e0db2a01e504e 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 b2959e925d286..e53c06c8ea571 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 209f5c24d1e40..a5fb46503f582 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 d48353d3e50a2..a88e75fdb4e44 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 6654b385eac3b..262d00a7aae0f 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 da17b4c2a9d98..a31ee254b6767 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 1d2b4e1877167..d7b814c66bebc 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 a66101d0fe141..0f1f821f8fc5c 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 9cb6094886c72..a8bbbfa0e3a9e 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 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 beb223a6cc65e..3e5b5d78f38b7 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 5b52e3d30ee9c..f7d6e96fc9695 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,280 @@ 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); + + RulesResource rulesResource = new RulesResource(databaseRuleManager, auditManager); + Response resp1 = rulesResource.setDatasourceRules("dataSource1", null, null, null, EasyMock.createMock(HttpServletRequest.class)); + Assert.assertEquals(200, resp1.getStatus()); + + 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); + + RulesResource rulesResource = new RulesResource(databaseRuleManager, auditManager); + HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class); + + IntervalLoadRule loadInterval1 = new IntervalLoadRule( + Intervals.of("2023-07-29T01:00:00Z/2023-12-30T01:00:00Z"), + null, + null + ); + IntervalLoadRule loadInterval2 = new IntervalLoadRule( + Intervals.of("2023-09-29T01:00:00Z/2023-10-30T01:00:00Z"), + null, + null + ); + PeriodLoadRule loadPT1H = new PeriodLoadRule(new Period("PT1H"), null, null, null); + PeriodLoadRule loadP3M = new PeriodLoadRule(new Period("P3M"), null, null, null); + PeriodLoadRule loadP6M = new PeriodLoadRule(new Period("P6M"), null, null, null); + 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); + + RulesResource rulesResource = new RulesResource(databaseRuleManager, auditManager); + HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class); + + PeriodDropBeforeRule dropBeforeP3M = new PeriodDropBeforeRule(new Period("P3M")); + PeriodDropBeforeRule dropBeforeP6M = new PeriodDropBeforeRule(new Period("P6M")); + PeriodDropRule dropByP1M = new PeriodDropRule(new Period("P1M"), true); + PeriodDropRule dropByP2M = new PeriodDropRule(new Period("P2M"), true); + 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); + + RulesResource rulesResource = new RulesResource(databaseRuleManager, auditManager); + HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class); + + ForeverBroadcastDistributionRule broadcastForever = new ForeverBroadcastDistributionRule(); + PeriodBroadcastDistributionRule broadcastPeriodPT1H = new PeriodBroadcastDistributionRule(new Period("PT1H"), true); + PeriodBroadcastDistributionRule broadcastPeriodPT2H = new PeriodBroadcastDistributionRule(new Period("PT2H"), true); + IntervalBroadcastDistributionRule broadcastInterval1 = new IntervalBroadcastDistributionRule( + Intervals.of("2000-09-29T01:00:00Z/2050-10-30T01:00:00Z") + ); + 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); + + RulesResource rulesResource = new RulesResource(databaseRuleManager, auditManager); + HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class); + + IntervalLoadRule loadLargeInteval = new IntervalLoadRule( + Intervals.of("1980-07-29T01:00:00Z/2050-12-30T01:00:00Z"), + null, + null + ); + IntervalLoadRule loadSmallInterval = new IntervalLoadRule( + Intervals.of("2020-09-29T01:00:00Z/2025-10-30T01:00:00Z"), + null, + null + ); + PeriodLoadRule periodPT1H = new PeriodLoadRule(new Period("PT1H"), null, null, null); + PeriodLoadRule periodP3M = new PeriodLoadRule(new Period("P3M"), null, null, null); + PeriodLoadRule periodP6M = new PeriodLoadRule(new Period("P6M"), null, null, null); + ForeverLoadRule foreverLoadRule = new ForeverLoadRule(null, null); + ForeverDropRule foreverDropRule = new ForeverDropRule(); + PeriodBroadcastDistributionRule broadcastPeriodPT15m = new PeriodBroadcastDistributionRule(new Period("PT15m"), true); + + 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); + RulesResource rulesResource = new RulesResource(databaseRuleManager, auditManager); + + IntervalLoadRule loadLargeInteval = new IntervalLoadRule( + Intervals.of("1980-07-29T01:00:00Z/2050-12-30T01:00:00Z"), + null, + null + ); + IntervalLoadRule loadSmallInterval = new IntervalLoadRule( + Intervals.of("2020-09-29T01:00:00Z/2025-10-30T01:00:00Z"), + null, + null + ); + PeriodLoadRule periodPT1H = new PeriodLoadRule(new Period("PT1H"), null, null, null); + PeriodLoadRule periodP3M = new PeriodLoadRule(new Period("P3M"), null, null, null); + PeriodLoadRule periodP6M = new PeriodLoadRule(new Period("P6M"), null, null, null); + ForeverLoadRule foreverLoadRule = new ForeverLoadRule(null, null); + ForeverDropRule foreverDropRule = new ForeverDropRule(); + PeriodBroadcastDistributionRule broadcastPeriodPT15m = new PeriodBroadcastDistributionRule(new Period("PT15m"), true); + + 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); + + 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(foreverLoadRule); + + Response resp2 = rulesResource.setDatasourceRules("dataSource1", rules, null, null, EasyMock.createMock(HttpServletRequest.class)); + Assert.assertEquals(200, resp2.getStatus()); + EasyMock.verify(databaseRuleManager); + } + @Test public void testGetAllDatasourcesRuleHistoryWithCount() {