Skip to content

Commit 3603a80

Browse files
willmostlyprakhar10
andcommitted
Use MVEL for rule evaluation
Co-Authored-By: Prakhar Sapre <[email protected]>
1 parent 2add137 commit 3603a80

File tree

10 files changed

+296
-267
lines changed

10 files changed

+296
-267
lines changed

docs/routing-rules.md

Lines changed: 5 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -296,8 +296,8 @@ actions:
296296
```
297297

298298
This can difficult to maintain with more rules. To have better control over the
299-
execution of rules, we can use rule priorities and composite rules. Overall,
300-
priorities, composite rules, and other constructs that MVEL support allows
299+
execution of rules, we can use rule priorities. Overall,
300+
priorities and other constructs that MVEL support allows
301301
you to express your routing logic.
302302

303303
#### Rule priority
@@ -328,99 +328,12 @@ that the first rule (priority 0) is fired before the second rule (priority 1).
328328
Thus `routingGroup` is set to `etl` and then to `etl-special`, so the
329329
`routingGroup` is always `etl-special` in the end.
330330

331-
More specific rules must be set to a lesser priority so they are evaluated last
332-
to set a `routingGroup`. To further control the execution of rules, for example
333-
to have only one rule fire, you can use composite rules.
334-
335-
##### Composite rules
336-
337-
First, please refer to the [easy-rule composite rules documentation](https://github.com/j-easy/easy-rules/wiki/defining-rules#composite-rules).
338-
339-
The preceding section covers how to control the order of rule execution using
340-
priorities. In addition, you can configure evaluation so that only the first
341-
rule matched fires (the highest priority one) and the rest is ignored. You can
342-
use `ActivationRuleGroup` to achieve this:
343-
344-
```yaml
345-
---
346-
name: "airflow rule group"
347-
description: "routing rules for query from airflow"
348-
compositeRuleType: "ActivationRuleGroup"
349-
composingRules:
350-
- name: "airflow special"
351-
description: "if query from airflow with special label, route to etl-special group"
352-
priority: 0
353-
condition: 'request.getHeader("X-Trino-Source") == "airflow" && request.getHeader("X-Trino-Client-Tags") contains "label=special"'
354-
actions:
355-
- 'result.put("routingGroup", "etl-special")'
356-
- name: "airflow"
357-
description: "if query from airflow, route to etl group"
358-
priority: 1
359-
condition: 'request.getHeader("X-Trino-Source") == "airflow"'
360-
actions:
361-
- 'result.put("routingGroup", "etl")'
362-
```
363-
364-
Note that the priorities have switched. The more specific rule has a higher
365-
priority, since it should fire first. A query coming from airflow with special
366-
label is matched to the "airflow special" rule first, since it's higher
367-
priority, and the second rule is ignored. A query coming from airflow with no
368-
labels does not match the first rule, and is then tested and matched to the
369-
second rule.
370-
371-
You can also use `ConditionalRuleGroup` and `ActivationRuleGroup` to implement
372-
an if/else workflow. The following logic in pseudocode:
373-
374-
```text
375-
if source == "airflow":
376-
if clientTags["label"] == "foo":
377-
return "etl-foo"
378-
else if clientTags["label"] = "bar":
379-
return "etl-bar"
380-
else
381-
return "etl"
382-
```
383-
384-
This logic can be implemented with the following rules:
385-
386-
```yaml
387-
name: "airflow rule group"
388-
description: "routing rules for query from airflow"
389-
compositeRuleType: "ConditionalRuleGroup"
390-
composingRules:
391-
- name: "main condition"
392-
description: "source is airflow"
393-
priority: 0 # rule with the highest priority acts as main condition
394-
condition: 'request.getHeader("X-Trino-Source") == "airflow"'
395-
actions:
396-
- ""
397-
- name: "airflow subrules"
398-
compositeRuleType: "ActivationRuleGroup" # use ActivationRuleGroup to simulate if/else
399-
composingRules:
400-
- name: "label foo"
401-
description: "label client tag is foo"
402-
priority: 0
403-
condition: 'request.getHeader("X-Trino-Client-Tags") contains "label=foo"'
404-
actions:
405-
- 'result.put("routingGroup", "etl-foo")'
406-
- name: "label bar"
407-
description: "label client tag is bar"
408-
priority: 0
409-
condition: 'request.getHeader("X-Trino-Client-Tags") contains "label=bar"'
410-
actions:
411-
- 'result.put("routingGroup", "etl-bar")'
412-
- name: "airflow default"
413-
description: "airflow queries default to etl"
414-
condition: "true"
415-
actions:
416-
- 'result.put("routingGroup", "etl")'
417-
```
331+
More specific rules must be set to a higher priority so they are evaluated last
332+
to set a `routingGroup`.
418333

419334
##### If statements (MVEL Flow Control)
420335

421-
In the preceding section you see how `ConditionalRuleGroup` and
422-
`ActivationRuleGroup` are used to implement an `if/else` workflow. You can
423-
use MVEL support for `if` statements and other flow control. The following logic
336+
You can use MVEL support for `if` statements and other flow control. The following logic
424337
in pseudocode:
425338

426339
```text

gateway-ha/pom.xml

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
<frontend.pnpmRegistryURL>https://registry.npmmirror.com</frontend.pnpmRegistryURL>
2222

2323
<!-- dependency versions -->
24-
<dep.jeasy.version>4.1.0</dep.jeasy.version>
2524
<dep.mockito.version>5.14.2</dep.mockito.version>
2625
<dep.okhttp3.version>4.12.0</dep.okhttp3.version>
2726
<dep.trino.version>462</dep.trino.version>
@@ -253,21 +252,9 @@
253252
</dependency>
254253

255254
<dependency>
256-
<groupId>org.jeasy</groupId>
257-
<artifactId>easy-rules-core</artifactId>
258-
<version>${dep.jeasy.version}</version>
259-
</dependency>
260-
261-
<dependency>
262-
<groupId>org.jeasy</groupId>
263-
<artifactId>easy-rules-mvel</artifactId>
264-
<version>${dep.jeasy.version}</version>
265-
</dependency>
266-
267-
<dependency>
268-
<groupId>org.jeasy</groupId>
269-
<artifactId>easy-rules-support</artifactId>
270-
<version>${dep.jeasy.version}</version>
255+
<groupId>org.mvel</groupId>
256+
<artifactId>mvel2</artifactId>
257+
<version>2.5.2.Final</version>
271258
</dependency>
272259

273260
<dependency>
@@ -290,13 +277,6 @@
290277
<scope>runtime</scope>
291278
</dependency>
292279

293-
<dependency>
294-
<groupId>org.mvel</groupId>
295-
<artifactId>mvel2</artifactId>
296-
<version>2.5.2.Final</version>
297-
<scope>runtime</scope>
298-
</dependency>
299-
300280
<dependency>
301281
<groupId>org.postgresql</groupId>
302282
<artifactId>postgresql</artifactId>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package io.trino.gateway.ha.router;
15+
16+
import io.trino.gateway.ha.config.RequestAnalyzerConfig;
17+
18+
import java.io.IOException;
19+
import java.nio.file.Files;
20+
import java.nio.file.Path;
21+
import java.nio.file.Paths;
22+
import java.nio.file.attribute.BasicFileAttributes;
23+
import java.util.List;
24+
25+
public class MVELFileRoutingGroupSelector
26+
extends RulesRoutingGroupSelector
27+
{
28+
Path rulesPath;
29+
30+
MVELFileRoutingGroupSelector(String rulesPath, RequestAnalyzerConfig requestAnalyzerConfig)
31+
{
32+
super(requestAnalyzerConfig);
33+
this.rulesPath = Paths.get(rulesPath);
34+
35+
List<RoutingRule> ruleList = readRulesFromPath(this.rulesPath, MVELRoutingRule.class);
36+
setRules(ruleList);
37+
}
38+
39+
@Override
40+
void reloadRules(long lastUpdatedTimeMillis)
41+
{
42+
try {
43+
BasicFileAttributes attr = Files.readAttributes(this.rulesPath, BasicFileAttributes.class);
44+
if (attr.lastModifiedTime().toMillis() > lastUpdatedTimeMillis) {
45+
synchronized (this) {
46+
if (attr.lastModifiedTime().toMillis() > lastUpdatedTimeMillis) {
47+
List<RoutingRule> ruleList = readRulesFromPath(this.rulesPath, MVELRoutingRule.class);
48+
setRules(ruleList);
49+
}
50+
}
51+
}
52+
}
53+
catch (IOException e) {
54+
throw new RuntimeException("Could not access rules file", e);
55+
}
56+
}
57+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package io.trino.gateway.ha.router;
15+
16+
import com.fasterxml.jackson.annotation.JsonCreator;
17+
import com.fasterxml.jackson.annotation.JsonProperty;
18+
19+
import java.io.Serializable;
20+
import java.util.List;
21+
import java.util.Map;
22+
23+
import static com.google.common.collect.ImmutableList.toImmutableList;
24+
import static org.mvel2.MVEL.compileExpression;
25+
import static org.mvel2.MVEL.executeExpression;
26+
27+
public class MVELRoutingRule
28+
extends RoutingRule
29+
{
30+
@JsonCreator
31+
public MVELRoutingRule(
32+
@JsonProperty("name") String name,
33+
@JsonProperty("description") String description,
34+
@JsonProperty("priority") Integer priority,
35+
@JsonProperty("condition") Serializable condition,
36+
@JsonProperty("actions") List<Serializable> actions)
37+
{
38+
super(
39+
name,
40+
description,
41+
priority,
42+
condition instanceof String stringCondition ? compileExpression(stringCondition) : condition,
43+
actions.stream().map(action -> {
44+
if (action instanceof String stringAction) {
45+
return compileExpression(stringAction);
46+
}
47+
else {
48+
return action;
49+
}
50+
}).collect(toImmutableList()));
51+
}
52+
53+
@Override
54+
public void evaluate(Map<String, Object> variables)
55+
{
56+
if ((boolean) executeExpression(condition, variables)) {
57+
actions.forEach(action -> executeExpression(action, variables));
58+
}
59+
}
60+
}

gateway-ha/src/main/java/io/trino/gateway/ha/router/RoutingGroupSelector.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ static RoutingGroupSelector byRoutingGroupHeader()
3939
*/
4040
static RoutingGroupSelector byRoutingRulesEngine(String rulesConfigPath, RequestAnalyzerConfig requestAnalyzerConfig)
4141
{
42-
return new RuleReloadingRoutingGroupSelector(rulesConfigPath, requestAnalyzerConfig);
42+
return new MVELFileRoutingGroupSelector(rulesConfigPath, requestAnalyzerConfig);
4343
}
4444

4545
/**
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package io.trino.gateway.ha.router;
15+
16+
import com.fasterxml.jackson.annotation.JsonCreator;
17+
import com.fasterxml.jackson.annotation.JsonProperty;
18+
19+
import java.io.Serializable;
20+
import java.util.List;
21+
import java.util.Map;
22+
23+
import static java.util.Objects.requireNonNull;
24+
import static java.util.Objects.requireNonNullElse;
25+
26+
public abstract class RoutingRule
27+
implements Comparable<RoutingRule>
28+
{
29+
String name;
30+
String description;
31+
Integer priority;
32+
Serializable condition;
33+
List<Serializable> actions;
34+
35+
@JsonCreator
36+
public RoutingRule(
37+
@JsonProperty("name") String name,
38+
@JsonProperty("description") String description,
39+
@JsonProperty("priority") Integer priority,
40+
@JsonProperty("condition") Serializable condition,
41+
@JsonProperty("actions") List<Serializable> actions)
42+
{
43+
this.name = requireNonNull(name, "name is null");
44+
this.description = requireNonNullElse(description, "");
45+
this.priority = requireNonNullElse(priority, 0);
46+
this.condition = requireNonNull(condition, "condition is null");
47+
this.actions = requireNonNull(actions, "actions is null");
48+
}
49+
50+
public abstract void evaluate(Map<String, Object> variables);
51+
52+
@Override
53+
public int compareTo(RoutingRule o)
54+
{
55+
if (o == null) {
56+
return 1;
57+
}
58+
return priority.compareTo(o.priority);
59+
}
60+
}

0 commit comments

Comments
 (0)