Skip to content

Commit

Permalink
Store routing rules in database
Browse files Browse the repository at this point in the history
Add API methods for creating and deleting rules.
  • Loading branch information
willmostly committed Feb 28, 2025
1 parent 2a59d1c commit 995bcea
Show file tree
Hide file tree
Showing 39 changed files with 1,012 additions and 155 deletions.
1 change: 1 addition & 0 deletions gateway-ha/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,7 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-Djol.skipHotspotSAAttach=true</argLine>
<!-- This is needed because we call main() in test setups. Otherwise Guice singleton won't work. -->
<reuseForks>false</reuseForks>
</configuration>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
import io.trino.gateway.ha.resource.PublicResource;
import io.trino.gateway.ha.resource.TrinoResource;
import io.trino.gateway.ha.router.ForRouter;
import io.trino.gateway.ha.router.RoutingRulesManager;
import io.trino.gateway.ha.router.ForwardingRoutingRulesManager;
import io.trino.gateway.ha.security.AuthorizedExceptionMapper;
import io.trino.gateway.proxyserver.ForProxy;
import io.trino.gateway.proxyserver.ProxyRequestHandler;
Expand Down Expand Up @@ -145,7 +145,7 @@ public void configure(Binder binder)
jaxrsBinder(binder).bind(AuthorizedExceptionMapper.class);
binder.bind(ProxyHandlerStats.class).in(Scopes.SINGLETON);
newExporter(binder).export(ProxyHandlerStats.class).withGeneratedName();
binder.bind(RoutingRulesManager.class);
binder.bind(ForwardingRoutingRulesManager.class);
}

private static void addManagedApps(HaGatewayConfiguration configuration, Binder binder)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,9 @@ public enum RulesType
* The service URL can implement dynamic rule changes.
*/
EXTERNAL,

/**
* Routing rules stored in the database.
*/
DB
}
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,7 @@ public RoutingGroupSelector getRoutingGroupSelector(@ForRouter HttpClient httpCl
if (routingRulesConfig.isRulesEngineEnabled()) {
try {
return switch (routingRulesConfig.getRulesType()) {
case FILE -> RoutingGroupSelector.byRoutingRulesEngine(
routingRulesConfig.getRulesConfigPath(),
routingRulesConfig.getRulesRefreshPeriod(),
configuration.getRequestAnalyzerConfig());
case DB, FILE -> RoutingGroupSelector.byRoutingRulesEngine(configuration);
case EXTERNAL -> {
RulesExternalConfiguration rulesExternalConfiguration = routingRulesConfig.getRulesExternalConfiguration();
yield RoutingGroupSelector.byRoutingExternal(httpClient, rulesExternalConfiguration, configuration.getRequestAnalyzerConfig());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.trino.gateway.ha.domain;
package io.trino.gateway.ha.persistence.dao;

import com.google.common.collect.ImmutableList;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.jdbi.v3.core.mapper.reflect.ColumnName;

import java.util.List;

import static java.util.Objects.requireNonNull;
import static java.util.Objects.requireNonNullElse;

/**
* RoutingRules
Expand All @@ -28,19 +28,22 @@
* @param priority priority of the routing rule. Higher number represents higher priority. If two rules have same priority then order of execution is not guaranteed.
* @param actions actions of the routing rule
* @param condition condition of the routing rule
* @param routingRuleEngine the engine used for rule evaluation
*/

public record RoutingRule(
String name,
String description,
Integer priority,
List<String> actions,
String condition)
@JsonProperty("name") @ColumnName("name") String name,
@JsonProperty("description") @ColumnName("description") String description,
@JsonProperty("priority") @ColumnName("priority") Integer priority,
// "conditionExpression" is used as a column name because "condition" is a reserved word in MySQL
@JsonProperty("condition") @ColumnName("conditionExpression") String condition,
@JsonProperty("actions") @ColumnName("actions") List<String> actions,
@JsonProperty("routingRuleEngine") @ColumnName("routingRuleEngine") RoutingRuleEngine routingRuleEngine)
{
public RoutingRule {
requireNonNull(name, "name is null");
description = requireNonNullElse(description, "");
priority = requireNonNullElse(priority, 0);
actions = ImmutableList.copyOf(actions);
requireNonNull(condition, "condition is null");
public RoutingRule
{
requireNonNull(name);
requireNonNull(condition);
requireNonNull(actions);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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.persistence.dao;

public enum RoutingRuleEngine
{
MVEL
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* 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.persistence.dao;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.jdbi.v3.core.mapper.RowMapper;
import org.jdbi.v3.core.statement.StatementContext;
import org.jdbi.v3.sqlobject.statement.SqlQuery;
import org.jdbi.v3.sqlobject.statement.SqlUpdate;
import org.jdbi.v3.sqlobject.statement.UseRowMapper;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Optional;

public interface RoutingRulesDao
{
@SqlQuery("SELECT * FROM routing_rules")
List<RoutingRule> getAll();

// "conditionExpression" is used as a column name because "condition" is a reserved word in MySQL
@SqlUpdate("""
INSERT INTO routing_rules (name, description, priority, conditionExpression, actions, routingRuleEngine)
VALUES (:name, :description, :priority, :condition, :actions, :routingRuleEngine)
""")
void create(String name, String description, Integer priority, String condition, List<String> actions, RoutingRuleEngine routingRuleEngine);

@SqlUpdate("""
UPDATE routing_rules
SET description = :description, priority = :priority, conditionExpression = :condition, actions = :actions, routingRuleEngine = :routingRuleEngine
WHERE name = :name
""")
void update(String name, String description, Integer priority, String condition, List<String> actions, RoutingRuleEngine routingRuleEngine);

@SqlQuery("SELECT * FROM routing_rules")
@UseRowMapper(RoutingRulesStringToListMapper.class)
List<RoutingRule> getAllNoListSupport();

@SqlUpdate("""
INSERT INTO routing_rules (name, description, priority, conditionExpression, actions, routingRuleEngine)
VALUES (:name, :description, :priority, :condition, :actions, :routingRuleEngine)
""")
void createNoListSupport(String name, String description, Integer priority, String condition, String actions, RoutingRuleEngine routingRuleEngine);

@SqlUpdate("""
UPDATE routing_rules
SET description = :description, priority = :priority, conditionExpression = :condition, actions = :actions, routingRuleEngine = :routingRuleEngine
WHERE name = :name
""")
void updateNoListSupport(String name, String description, Integer priority, String condition, String actions, RoutingRuleEngine routingRuleEngine);

@SqlUpdate("""
DELETE FROM routing_rules
WHERE name = :name
"""
)
void delete(String name);

class RoutingRulesStringToListMapper
implements RowMapper<RoutingRule>
{
ObjectMapper objectMapper = new ObjectMapper();
TypeReference<List<String>> actionsTypeReference = new TypeReference<List<String>>() {};

@Override
public RoutingRule map(ResultSet rs, StatementContext ctx)
throws SQLException
{
try {
return new RoutingRule(
rs.getString("name"),
rs.getString("description"),
rs.getInt("priority"),
rs.getString("conditionExpression"),
objectMapper.readValue(rs.getString("actions"), actionsTypeReference),
RoutingRuleEngine.valueOf(Optional.ofNullable(rs.getString("routingRuleEngine")).orElse("MVEL")));
}
catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import io.trino.gateway.ha.config.ProxyBackendConfiguration;
import io.trino.gateway.ha.config.UIConfiguration;
import io.trino.gateway.ha.domain.Result;
import io.trino.gateway.ha.domain.RoutingRule;
import io.trino.gateway.ha.domain.TableData;
import io.trino.gateway.ha.domain.request.GlobalPropertyRequest;
import io.trino.gateway.ha.domain.request.QueryDistributionRequest;
Expand All @@ -32,17 +31,21 @@
import io.trino.gateway.ha.domain.request.SelectorsRequest;
import io.trino.gateway.ha.domain.response.BackendResponse;
import io.trino.gateway.ha.domain.response.DistributionResponse;
import io.trino.gateway.ha.persistence.dao.RoutingRule;
import io.trino.gateway.ha.router.BackendStateManager;
import io.trino.gateway.ha.router.ForwardingRoutingRulesManager;
import io.trino.gateway.ha.router.GatewayBackendManager;
import io.trino.gateway.ha.router.HaGatewayManager;
import io.trino.gateway.ha.router.IRoutingRulesManager;
import io.trino.gateway.ha.router.QueryHistoryManager;
import io.trino.gateway.ha.router.ResourceGroupsManager;
import io.trino.gateway.ha.router.RoutingRulesManager;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
Expand Down Expand Up @@ -75,15 +78,15 @@ public class GatewayWebAppResource
private final ResourceGroupsManager resourceGroupsManager;
// TODO Avoid putting mutable objects in fields
private final UIConfiguration uiConfiguration;
private final RoutingRulesManager routingRulesManager;
private final IRoutingRulesManager routingRulesManager;

@Inject
public GatewayWebAppResource(
GatewayBackendManager gatewayBackendManager,
QueryHistoryManager queryHistoryManager,
BackendStateManager backendStateManager,
ResourceGroupsManager resourceGroupsManager,
RoutingRulesManager routingRulesManager,
ForwardingRoutingRulesManager routingRulesManager,
HaGatewayConfiguration configuration)
{
this.gatewayBackendManager = requireNonNull(gatewayBackendManager, "gatewayBackendManager is null");
Expand Down Expand Up @@ -450,6 +453,17 @@ public Response getRoutingRules()
return Response.ok(Result.ok(routingRulesList)).build();
}

@DELETE
@RolesAllowed("ADMIN")
@Produces(MediaType.APPLICATION_JSON)
@Path("/deleteRoutingRule/{name}")
public Response deleteRoutingRules(@PathParam("name") String name)
throws IOException
{
routingRulesManager.deleteRoutingRule(name);
return Response.ok(routingRulesManager.getRoutingRules()).build();
}

@POST
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
Expand All @@ -462,6 +476,16 @@ public Response updateRoutingRules(RoutingRule routingRule)
return Response.ok(Result.ok(routingRulesList)).build();
}

@POST
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
@Path("/createRoutingRule")
public Response createRoutingRule(RoutingRule routingRule)
{
routingRulesManager.createRoutingRule(routingRule);
return Response.ok(routingRulesManager.getRoutingRules()).build();
}

@GET
@RolesAllowed("ADMIN")
@Produces(MediaType.APPLICATION_JSON)
Expand Down
Loading

0 comments on commit 995bcea

Please sign in to comment.