diff --git a/gateway-ha/pom.xml b/gateway-ha/pom.xml
index d8b56dc29..15b5af463 100644
--- a/gateway-ha/pom.xml
+++ b/gateway-ha/pom.xml
@@ -21,7 +21,6 @@
https://registry.npmmirror.com
- 4.1.0
5.14.2
4.12.0
462
@@ -253,21 +252,9 @@
- org.jeasy
- easy-rules-core
- ${dep.jeasy.version}
-
-
-
- org.jeasy
- easy-rules-mvel
- ${dep.jeasy.version}
-
-
-
- org.jeasy
- easy-rules-support
- ${dep.jeasy.version}
+ org.mvel
+ mvel2
+ 2.5.2.Final
@@ -290,13 +277,6 @@
runtime
-
- org.mvel
- mvel2
- 2.5.2.Final
- runtime
-
-
org.postgresql
postgresql
diff --git a/gateway-ha/src/main/java/io/trino/gateway/ha/router/FileReloadingMVELRoutingGroupSelector.java b/gateway-ha/src/main/java/io/trino/gateway/ha/router/FileReloadingMVELRoutingGroupSelector.java
new file mode 100644
index 000000000..94c01e9f1
--- /dev/null
+++ b/gateway-ha/src/main/java/io/trino/gateway/ha/router/FileReloadingMVELRoutingGroupSelector.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed 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 io.trino.gateway.ha.router;
+
+import io.trino.gateway.ha.config.RequestAnalyzerConfig;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.List;
+
+public class FileReloadingMVELRoutingGroupSelector
+ extends RulesRoutingGroupSelector
+{
+ Path rulesPath;
+
+ FileReloadingMVELRoutingGroupSelector(String rulesPath, RequestAnalyzerConfig requestAnalyzerConfig)
+ {
+ super(requestAnalyzerConfig);
+ this.rulesPath = Paths.get(rulesPath);
+
+ List ruleList = readRulesFromPath(this.rulesPath, MVELRoutingRule.class);
+ setRules(ruleList);
+ }
+
+ @Override
+ void reloadRules(long lastUpdatedTimeMillis)
+ {
+ try {
+ BasicFileAttributes attr = Files.readAttributes(this.rulesPath, BasicFileAttributes.class);
+ if (attr.lastModifiedTime().toMillis() > lastUpdatedTimeMillis) {
+ synchronized (this) {
+ if (attr.lastModifiedTime().toMillis() > lastUpdatedTimeMillis) {
+ List ruleList = readRulesFromPath(this.rulesPath, MVELRoutingRule.class);
+ setRules(ruleList);
+ }
+ }
+ }
+ }
+ catch (IOException e) {
+ throw new RuntimeException("Could not access rules file", e);
+ }
+ }
+}
diff --git a/gateway-ha/src/main/java/io/trino/gateway/ha/router/MVELRoutingRule.java b/gateway-ha/src/main/java/io/trino/gateway/ha/router/MVELRoutingRule.java
new file mode 100644
index 000000000..a41d3f463
--- /dev/null
+++ b/gateway-ha/src/main/java/io/trino/gateway/ha/router/MVELRoutingRule.java
@@ -0,0 +1,46 @@
+package io.trino.gateway.ha.router;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.ImmutableList;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+import static org.mvel2.MVEL.compileExpression;
+import static org.mvel2.MVEL.executeExpression;
+
+public class MVELRoutingRule
+ extends RoutingRule
+{
+ @JsonCreator
+ public MVELRoutingRule(
+ @JsonProperty("name") String name,
+ @JsonProperty("description") String description,
+ @JsonProperty("priority") Integer priority,
+ @JsonProperty("condition") Serializable condition,
+ @JsonProperty("actions") List actions)
+ {
+ super(
+ name,
+ description,
+ priority,
+ condition instanceof String stringCondition ? compileExpression(stringCondition) : condition,
+ actions.stream().map(action -> {
+ if (action instanceof String stringAction) {
+ return compileExpression(stringAction);
+ }
+ else {
+ return action;
+ }}).collect(ImmutableList.toImmutableList()));
+ }
+
+ @Override
+ public void evaluate(Map variables)
+ {
+ if ((boolean) executeExpression(condition, variables)) {
+ actions.forEach(action -> executeExpression(action, variables));
+ }
+ }
+}
diff --git a/gateway-ha/src/main/java/io/trino/gateway/ha/router/RoutingGroupSelector.java b/gateway-ha/src/main/java/io/trino/gateway/ha/router/RoutingGroupSelector.java
index ae6285e14..ecd9040c7 100644
--- a/gateway-ha/src/main/java/io/trino/gateway/ha/router/RoutingGroupSelector.java
+++ b/gateway-ha/src/main/java/io/trino/gateway/ha/router/RoutingGroupSelector.java
@@ -39,7 +39,7 @@ static RoutingGroupSelector byRoutingGroupHeader()
*/
static RoutingGroupSelector byRoutingRulesEngine(String rulesConfigPath, RequestAnalyzerConfig requestAnalyzerConfig)
{
- return new RuleReloadingRoutingGroupSelector(rulesConfigPath, requestAnalyzerConfig);
+ return new FileReloadingMVELRoutingGroupSelector(rulesConfigPath, requestAnalyzerConfig);
}
/**
diff --git a/gateway-ha/src/main/java/io/trino/gateway/ha/router/RoutingRule.java b/gateway-ha/src/main/java/io/trino/gateway/ha/router/RoutingRule.java
new file mode 100644
index 000000000..433d51223
--- /dev/null
+++ b/gateway-ha/src/main/java/io/trino/gateway/ha/router/RoutingRule.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed 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 io.trino.gateway.ha.router;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+import static java.util.Objects.requireNonNull;
+import static java.util.Objects.requireNonNullElse;
+
+public abstract class RoutingRule
+ implements Comparable
+{
+ String name;
+ String description;
+ Integer priority;
+ Serializable condition;
+ List actions;
+
+ @JsonCreator
+ public RoutingRule(
+ @JsonProperty("name") String name,
+ @JsonProperty("description") String description,
+ @JsonProperty("priority") Integer priority,
+ @JsonProperty("condition") Serializable condition,
+ @JsonProperty("actions") List actions)
+ {
+ this.name = requireNonNull(name, "name is null");
+ this.description = requireNonNullElse(description, "");
+ this.priority = requireNonNullElse(priority, 0);
+ this.condition = requireNonNull(condition, "condition is null");
+ this.actions = requireNonNull(actions, "actions is null");
+ }
+
+ public abstract void evaluate(Map variables);
+
+ @Override
+ public int compareTo(RoutingRule o)
+ {
+ if (o == null) {
+ return 1;
+ }
+ return priority.compareTo(o.priority);
+ }
+}
diff --git a/gateway-ha/src/main/java/io/trino/gateway/ha/router/RuleReloadingRoutingGroupSelector.java b/gateway-ha/src/main/java/io/trino/gateway/ha/router/RuleReloadingRoutingGroupSelector.java
deleted file mode 100644
index 4be89f39c..000000000
--- a/gateway-ha/src/main/java/io/trino/gateway/ha/router/RuleReloadingRoutingGroupSelector.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Licensed 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 io.trino.gateway.ha.router;
-
-import io.airlift.log.Logger;
-import io.trino.gateway.ha.config.RequestAnalyzerConfig;
-import jakarta.servlet.http.HttpServletRequest;
-import org.jeasy.rules.api.Facts;
-import org.jeasy.rules.api.Rules;
-import org.jeasy.rules.api.RulesEngine;
-import org.jeasy.rules.core.DefaultRulesEngine;
-import org.jeasy.rules.mvel.MVELRuleFactory;
-import org.jeasy.rules.support.reader.YamlRuleDefinitionReader;
-
-import java.io.FileReader;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.HashMap;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-public class RuleReloadingRoutingGroupSelector
- implements RoutingGroupSelector
-{
- private static final Logger log = Logger.get(RuleReloadingRoutingGroupSelector.class);
- private final RulesEngine rulesEngine = new DefaultRulesEngine();
- private final MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader());
- private final String rulesConfigPath;
- private volatile Rules rules = new Rules();
- private volatile long lastUpdatedTime;
- private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
- private final RequestAnalyzerConfig requestAnalyzerConfig;
- private final TrinoRequestUser.TrinoRequestUserProvider trinoRequestUserProvider;
-
- RuleReloadingRoutingGroupSelector(String rulesConfigPath, RequestAnalyzerConfig requestAnalyzerConfig)
- {
- this.rulesConfigPath = rulesConfigPath;
- this.requestAnalyzerConfig = requestAnalyzerConfig;
- trinoRequestUserProvider = new TrinoRequestUser.TrinoRequestUserProvider(requestAnalyzerConfig);
- try {
- rules = ruleFactory.createRules(
- new FileReader(rulesConfigPath, UTF_8));
- BasicFileAttributes attr = Files.readAttributes(Path.of(rulesConfigPath),
- BasicFileAttributes.class);
- lastUpdatedTime = attr.lastModifiedTime().toMillis();
- }
- catch (Exception e) {
- throw new RuntimeException("Error opening rules configuration file at "
- + rulesConfigPath + "\n"
- + "Using routing group header as default.", e);
- }
- }
-
- @Override
- public String findRoutingGroup(HttpServletRequest request)
- {
- try {
- BasicFileAttributes attr = Files.readAttributes(Path.of(rulesConfigPath),
- BasicFileAttributes.class);
- log.debug("File modified time: %s. lastUpdatedTime: %s", attr.lastModifiedTime(), lastUpdatedTime);
- if (attr.lastModifiedTime().toMillis() > lastUpdatedTime) {
- Lock writeLock = readWriteLock.writeLock();
- writeLock.lock();
- try {
- if (attr.lastModifiedTime().toMillis() > lastUpdatedTime) {
- // This check is performed again to prevent parsing the rules twice in case another
- // thread finds the condition true and acquires the lock before this one
- log.info("Updating rules to file modified at %s", attr.lastModifiedTime());
- rules = ruleFactory.createRules(
- new FileReader(rulesConfigPath, UTF_8));
- lastUpdatedTime = attr.lastModifiedTime().toMillis();
- }
- }
- finally {
- writeLock.unlock();
- }
- }
-
- Facts facts = new Facts();
- HashMap result = new HashMap();
-
- facts.put("request", request);
- if (requestAnalyzerConfig.isAnalyzeRequest()) {
- TrinoQueryProperties trinoQueryProperties = new TrinoQueryProperties(
- request,
- requestAnalyzerConfig.isClientsUseV2Format(),
- requestAnalyzerConfig.getMaxBodySize());
- TrinoRequestUser trinoRequestUser = trinoRequestUserProvider.getInstance(request);
- facts.put("trinoQueryProperties", trinoQueryProperties);
- facts.put("trinoRequestUser", trinoRequestUser);
- }
- facts.put("result", result);
- Lock readLock = readWriteLock.readLock();
- readLock.lock();
- try {
- rulesEngine.fire(rules, facts);
- }
- finally {
- readLock.unlock();
- }
- return result.get("routingGroup");
- }
- catch (Exception e) {
- log.error(e, "Error opening rules configuration file, using "
- + "routing group header as default.");
- // Invalid rules could lead to perf problems as every thread goes into the writeLock
- // block until the issue is resolved
- }
- return request.getHeader(ROUTING_GROUP_HEADER);
- }
-}
diff --git a/gateway-ha/src/main/java/io/trino/gateway/ha/router/RulesRoutingGroupSelector.java b/gateway-ha/src/main/java/io/trino/gateway/ha/router/RulesRoutingGroupSelector.java
new file mode 100644
index 000000000..9192f376f
--- /dev/null
+++ b/gateway-ha/src/main/java/io/trino/gateway/ha/router/RulesRoutingGroupSelector.java
@@ -0,0 +1,97 @@
+package io.trino.gateway.ha.router;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import com.fasterxml.jackson.dataformat.yaml.YAMLParser;
+import com.google.common.collect.ImmutableMap;
+import io.trino.gateway.ha.config.RequestAnalyzerConfig;
+import jakarta.servlet.http.HttpServletRequest;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Collections.sort;
+
+public abstract class RulesRoutingGroupSelector
+ implements RoutingGroupSelector
+{
+ public static final String RESULTS_ROUTING_GROUP_KEY = "routingGroup";
+
+ private List rules;
+ final boolean analyzeRequest;
+ final boolean clientsUseV2Format;
+ final int maxBodySize;
+ final TrinoRequestUser.TrinoRequestUserProvider trinoRequestUserProvider;
+ private volatile long lastUpdatedTimeMillis;
+
+ RulesRoutingGroupSelector(RequestAnalyzerConfig requestAnalyzerConfig)
+ {
+ this(new ArrayList<>(), requestAnalyzerConfig);
+ }
+
+ public RulesRoutingGroupSelector(List rules, RequestAnalyzerConfig requestAnalyzerConfig)
+ {
+ setRules(rules);
+ analyzeRequest = requestAnalyzerConfig.isAnalyzeRequest();
+ clientsUseV2Format = requestAnalyzerConfig.isClientsUseV2Format();
+ maxBodySize = requestAnalyzerConfig.getMaxBodySize();
+ trinoRequestUserProvider = new TrinoRequestUser.TrinoRequestUserProvider(requestAnalyzerConfig);
+ }
+
+ abstract void reloadRules(long lastUpdatedTimeMillis);
+
+ void setRules(List rules)
+ {
+ this.rules = new ArrayList<>(rules);
+ lastUpdatedTimeMillis = System.currentTimeMillis();
+ sort(this.rules);
+ }
+
+ // TODO: add CRUD operations for the rules
+
+ @Override
+ public String findRoutingGroup(HttpServletRequest request)
+ {
+ reloadRules(lastUpdatedTimeMillis);
+ Map result = new HashMap<>();
+ Map variables;
+ if (analyzeRequest) {
+ TrinoQueryProperties trinoQueryProperties = new TrinoQueryProperties(
+ request,
+ clientsUseV2Format,
+ maxBodySize);
+ TrinoRequestUser trinoRequestUser = trinoRequestUserProvider.getInstance(request);
+ variables = ImmutableMap.of("result", result, "request", request, "trinoQueryProperties", trinoQueryProperties, "trinoRequestUser", trinoRequestUser);
+ }
+ else {
+ variables = ImmutableMap.of("result", result, "request", request);
+ }
+
+ rules.forEach(rule -> rule.evaluate(variables));
+ return result.get(RESULTS_ROUTING_GROUP_KEY);
+ }
+
+ public static List readRulesFromPath(Path rulesPath, Class extends RoutingRule> ruleType)
+ {
+ ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory());
+ try {
+ String content = Files.readString(rulesPath, UTF_8);
+ YAMLParser parser = new YAMLFactory().createParser(content);
+ List routingRulesList = new ArrayList<>();
+ while (parser.nextToken() != null) {
+ RoutingRule routingRules = yamlReader.readValue(parser, ruleType);
+ routingRulesList.add(routingRules);
+ }
+ return routingRulesList;
+ }
+ catch (IOException e) {
+ throw new RuntimeException("Failed to read or parse routing rules configuration from path : " + rulesPath, e);
+ }
+ }
+}
diff --git a/gateway-ha/src/test/java/io/trino/gateway/ha/router/TestRoutingGroupSelector.java b/gateway-ha/src/test/java/io/trino/gateway/ha/router/TestRoutingGroupSelector.java
index 1066d19a7..abb7fe0d9 100644
--- a/gateway-ha/src/test/java/io/trino/gateway/ha/router/TestRoutingGroupSelector.java
+++ b/gateway-ha/src/test/java/io/trino/gateway/ha/router/TestRoutingGroupSelector.java
@@ -67,7 +67,6 @@ static Stream provideRoutingRuleConfigFiles()
String rulesDir = "src/test/resources/rules/";
return Stream.of(
rulesDir + "routing_rules_atomic.yml",
- rulesDir + "routing_rules_composite.yml",
rulesDir + "routing_rules_priorities.yml",
rulesDir + "routing_rules_if_statements.yml");
}