Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show and modify routing rules from the UI #433

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions docs/gateway-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,36 @@ Will return a JSON array of active Trino cluster backends:
curl -X POST http://localhost:8080/gateway/backend/activate/trino-2
```

## Update Routing Rules
prakhar10 marked this conversation as resolved.
Show resolved Hide resolved

This API can be used to programmatically update the Routing Rules.
Rule will be updated based on the rule name.

For this feature to work well you will need to provide a shared storage for the routing rules file.

```shell
curl -X POST http://localhost:8080/webapp/updateRoutingRules \
-d '{ "name": "trino-rule",
"description": "updated rule description",
"priority": 0,
"actions": ["updated action"],
"condition": "updated condition"
}'
```
### Disable Routing Rules UI

You can set the `disablePages` config to disable pages on the UI.

The following pages are available:
- `dashboard`
- `cluster`
- `resource-group`
- `selector`
- `history`
- `routing-rules`

```yaml
uiConfiguration:
disablePages:
- 'routing-rules'
```
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import io.trino.gateway.ha.resource.LoginResource;
import io.trino.gateway.ha.resource.PublicResource;
import io.trino.gateway.ha.resource.TrinoResource;
import io.trino.gateway.ha.router.RoutingRulesManager;
import io.trino.gateway.ha.security.AuthorizedExceptionMapper;
import io.trino.gateway.proxyserver.ForProxy;
import io.trino.gateway.proxyserver.ProxyRequestHandler;
Expand Down Expand Up @@ -123,6 +124,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).in(Scopes.SINGLETON);
}

private static void addManagedApps(HaGatewayConfiguration configuration, Binder binder)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ public class HaGatewayConfiguration

private RequestAnalyzerConfig requestAnalyzerConfig = new RequestAnalyzerConfig();

private UIConfiguration uiConfiguration = new UIConfiguration();

public UIConfiguration getUiConfiguration()
{
return uiConfiguration;
}

public void setUiConfiguration(UIConfiguration uiConfiguration)
{
this.uiConfiguration = uiConfiguration;
}
prakhar10 marked this conversation as resolved.
Show resolved Hide resolved

// List of Modules with FQCN (Fully Qualified Class Name)
private List<String> modules;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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.config;

import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.List;

public class UIConfiguration
{
private List<String> disablePages;

@JsonProperty
public List<String> getDisablePages()
{
return disablePages;
}

public void setDisablePages(List<String> disablePages)
{
this.disablePages = disablePages;
}

@Override
public String toString()
{
return "UIConfiguration{" +
"disablePages=" + disablePages +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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.domain;

import java.util.List;

/**
* RoutingRules
*
* @param name name of the routing rule
* @param description description of the routing rule
* @param priority priority of the routing rule
prakhar10 marked this conversation as resolved.
Show resolved Hide resolved
* @param actions actions of the routing rule
* @param condition condition of the routing rule
*/
public record RoutingRules(
String name,
String description,
Integer priority,
List<String> actions,
String condition)
prakhar10 marked this conversation as resolved.
Show resolved Hide resolved
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,18 @@
*/
package io.trino.gateway.ha.resource;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.google.common.base.Strings;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import io.trino.gateway.ha.clustermonitor.ClusterStats;
import io.trino.gateway.ha.config.HaGatewayConfiguration;
import io.trino.gateway.ha.config.ProxyBackendConfiguration;
import io.trino.gateway.ha.config.RoutingRulesConfiguration;
import io.trino.gateway.ha.config.UIConfiguration;
import io.trino.gateway.ha.domain.Result;
import io.trino.gateway.ha.domain.RoutingRules;
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 @@ -34,8 +41,10 @@
import io.trino.gateway.ha.router.HaGatewayManager;
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.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
Expand All @@ -44,6 +53,7 @@
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.SecurityContext;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
Expand All @@ -59,6 +69,7 @@
import static java.util.Objects.requireNonNullElse;

@Path("/webapp")
@Singleton
public class GatewayWebAppResource
{
private static final LocalDateTime START_TIME = LocalDateTime.now();
Expand All @@ -67,18 +78,28 @@ public class GatewayWebAppResource
private final QueryHistoryManager queryHistoryManager;
private final BackendStateManager backendStateManager;
private final ResourceGroupsManager resourceGroupsManager;
private final RoutingRulesManager routingRulesManager;
private final ObjectMapper yamlReader;
private final RoutingRulesConfiguration routingRulesConfiguration;
private final UIConfiguration uiConfiguration;

@Inject
public GatewayWebAppResource(
GatewayBackendManager gatewayBackendManager,
QueryHistoryManager queryHistoryManager,
BackendStateManager backendStateManager,
ResourceGroupsManager resourceGroupsManager)
ResourceGroupsManager resourceGroupsManager,
RoutingRulesManager routingRulesManager,
HaGatewayConfiguration configuration)
{
this.gatewayBackendManager = requireNonNull(gatewayBackendManager, "gatewayBackendManager is null");
this.queryHistoryManager = requireNonNull(queryHistoryManager, "queryHistoryManager is null");
this.backendStateManager = requireNonNull(backendStateManager, "backendStateManager is null");
this.resourceGroupsManager = requireNonNull(resourceGroupsManager, "resourceGroupsManager is null");
this.yamlReader = new ObjectMapper(new YAMLFactory());
this.routingRulesManager = requireNonNull(routingRulesManager, "resourceGroupsManager is null");
this.routingRulesConfiguration = configuration.getRoutingRules();
this.uiConfiguration = configuration.getUiConfiguration();
}

@POST
Expand Down Expand Up @@ -423,4 +444,36 @@ public Response readExactMatchSourceSelector()
List<ResourceGroupsManager.ExactSelectorsDetail> selectorsDetailList = resourceGroupsManager.readExactMatchSourceSelector();
return Response.ok(Result.ok(selectorsDetailList)).build();
}

@GET
ebyhr marked this conversation as resolved.
Show resolved Hide resolved
@RolesAllowed("USER")
@Produces(MediaType.APPLICATION_JSON)
@Path("/getRoutingRules")
Chaho12 marked this conversation as resolved.
Show resolved Hide resolved
public Response getRoutingRules()
throws IOException
{
List<RoutingRules> routingRulesList = routingRulesManager.getRoutingRules(routingRulesConfiguration, yamlReader);
return Response.ok(Result.ok(routingRulesList)).build();
}

@POST
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
willmostly marked this conversation as resolved.
Show resolved Hide resolved
@Produces(MediaType.APPLICATION_JSON)
@Path("/updateRoutingRules")
public synchronized Response updateRoutingRules(RoutingRules routingRules)
throws IOException
{
List<RoutingRules> routingRulesList = routingRulesManager.updateRoutingRules(routingRules, routingRulesConfiguration, yamlReader);
return Response.ok(Result.ok(routingRulesList)).build();
}

@GET
@RolesAllowed("USER")
@Produces(MediaType.APPLICATION_JSON)
@Path("/getUIConfiguration")
public Response getUIConfiguration()
{
return Response.ok(Result.ok(uiConfiguration)).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* 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.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLParser;
import io.trino.gateway.ha.config.RoutingRulesConfiguration;
import io.trino.gateway.ha.domain.RoutingRules;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

import static java.nio.charset.StandardCharsets.UTF_8;

public class RoutingRulesManager
{
public List<RoutingRules> getRoutingRules(RoutingRulesConfiguration configuration, ObjectMapper yamlReader)
prakhar10 marked this conversation as resolved.
Show resolved Hide resolved
throws IOException
{
String content = null;
prakhar10 marked this conversation as resolved.
Show resolved Hide resolved
prakhar10 marked this conversation as resolved.
Show resolved Hide resolved
try {
String rulesConfigPath = configuration.getRulesConfigPath();
content = new String(Files.readAllBytes(Paths.get(rulesConfigPath)), UTF_8);
prakhar10 marked this conversation as resolved.
Show resolved Hide resolved
YAMLParser parser = new YAMLFactory().createParser(content);
List<RoutingRules> routingRulesList = new ArrayList<>();
prakhar10 marked this conversation as resolved.
Show resolved Hide resolved
while (parser.nextToken() != null) {
RoutingRules routingRules = yamlReader.readValue(parser, RoutingRules.class);
routingRulesList.add(routingRules);
}
return routingRulesList;
}
catch (IOException e) {
throw new IOException(e);
prakhar10 marked this conversation as resolved.
Show resolved Hide resolved
}
}

public List<RoutingRules> updateRoutingRules(RoutingRules routingRules, RoutingRulesConfiguration configuration, ObjectMapper yamlReader)
prakhar10 marked this conversation as resolved.
Show resolved Hide resolved
throws IOException
{
String rulesConfigPath = configuration.getRulesConfigPath();
List<RoutingRules> routingRulesList = new ArrayList<>();
try {
String content = new String(Files.readAllBytes(Paths.get(rulesConfigPath)), UTF_8);
YAMLParser parser = new YAMLFactory().createParser(content);
while (parser.nextToken() != null) {
RoutingRules routingRule = yamlReader.readValue(parser, RoutingRules.class);
routingRulesList.add(routingRule);
}
prakhar10 marked this conversation as resolved.
Show resolved Hide resolved

for (int i = 0; i < routingRulesList.size(); i++) {
if (routingRulesList.get(i).name().equals(routingRules.name())) {
routingRulesList.set(i, routingRules);
break;
}
}

ObjectMapper yamlWriter = new ObjectMapper(new YAMLFactory());
StringBuilder yamlContent = new StringBuilder();
for (RoutingRules rule : routingRulesList) {
yamlContent.append(yamlWriter.writeValueAsString(rule));
}
Files.write(Paths.get(rulesConfigPath), yamlContent.toString().getBytes(UTF_8));
prakhar10 marked this conversation as resolved.
Show resolved Hide resolved
}
catch (IOException e) {
throw new IOException(e);
prakhar10 marked this conversation as resolved.
Show resolved Hide resolved
}
return routingRulesList;
}
}
Loading