Skip to content

Commit d3c7855

Browse files
committed
Show and modify routing rules from the UI
Remove console logs and renamed api Add api documentation and changes related to PR comments
1 parent 56e33c5 commit d3c7855

File tree

9 files changed

+356
-22
lines changed

9 files changed

+356
-22
lines changed

docs/gateway-api.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,16 @@ Will return a JSON array of active Trino cluster backends:
9191
curl -X POST http://localhost:8080/gateway/backend/activate/trino-2
9292
```
9393

94+
## Update Routing Rules
95+
96+
This API can be used to programmatically update the Routing Rules.
97+
Rule will be updated based on the rule name.
98+
```shell
99+
curl -X POST http://localhost:8080/webapp/updateRoutingRules \
100+
-d '{ "name": "trino-rule",
101+
"description": "updated rule description",
102+
"priority": 0,
103+
"actions": ["updated action"],
104+
"condition": "updated condition"
105+
}'
106+
```
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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.domain;
15+
16+
import com.fasterxml.jackson.annotation.JsonProperty;
17+
18+
import java.util.List;
19+
20+
/**
21+
* RoutingRules
22+
*
23+
* @param name name of the routing rule
24+
* @param description description of the routing rule
25+
* @param priority priority of the routing rule
26+
* @param actions actions of the routing rule
27+
* @param condition condition of the routing rule
28+
*/
29+
public record RoutingRules(
30+
@JsonProperty("name") String name,
31+
@JsonProperty("description") String description,
32+
@JsonProperty("priority") Integer priority,
33+
@JsonProperty("actions") List<String> actions,
34+
@JsonProperty("condition") String condition)
35+
{
36+
}

gateway-ha/src/main/java/io/trino/gateway/ha/resource/GatewayWebAppResource.java

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,18 @@
1313
*/
1414
package io.trino.gateway.ha.resource;
1515

16+
import com.fasterxml.jackson.core.type.TypeReference;
17+
import com.fasterxml.jackson.databind.ObjectMapper;
18+
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
19+
import com.fasterxml.jackson.dataformat.yaml.YAMLParser;
1620
import com.google.common.base.Strings;
1721
import com.google.inject.Inject;
22+
import com.google.inject.Singleton;
1823
import io.trino.gateway.ha.clustermonitor.ClusterStats;
24+
import io.trino.gateway.ha.config.HaGatewayConfiguration;
1925
import io.trino.gateway.ha.config.ProxyBackendConfiguration;
2026
import io.trino.gateway.ha.domain.Result;
27+
import io.trino.gateway.ha.domain.RoutingRules;
2128
import io.trino.gateway.ha.domain.TableData;
2229
import io.trino.gateway.ha.domain.request.GlobalPropertyRequest;
2330
import io.trino.gateway.ha.domain.request.QueryDistributionRequest;
@@ -36,6 +43,7 @@
3643
import io.trino.gateway.ha.router.ResourceGroupsManager;
3744
import jakarta.annotation.security.RolesAllowed;
3845
import jakarta.ws.rs.Consumes;
46+
import jakarta.ws.rs.GET;
3947
import jakarta.ws.rs.POST;
4048
import jakarta.ws.rs.Path;
4149
import jakarta.ws.rs.Produces;
@@ -44,11 +52,15 @@
4452
import jakarta.ws.rs.core.Response;
4553
import jakarta.ws.rs.core.SecurityContext;
4654

55+
import java.io.IOException;
56+
import java.nio.file.Files;
57+
import java.nio.file.Paths;
4758
import java.time.LocalDateTime;
4859
import java.time.ZoneId;
4960
import java.time.ZoneOffset;
5061
import java.time.ZonedDateTime;
5162
import java.time.format.DateTimeFormatter;
63+
import java.util.ArrayList;
5264
import java.util.Collections;
5365
import java.util.List;
5466
import java.util.Map;
@@ -59,6 +71,7 @@
5971
import static java.util.Objects.requireNonNullElse;
6072

6173
@Path("/webapp")
74+
@Singleton
6275
public class GatewayWebAppResource
6376
{
6477
private static final LocalDateTime START_TIME = LocalDateTime.now();
@@ -67,18 +80,21 @@ public class GatewayWebAppResource
6780
private final QueryHistoryManager queryHistoryManager;
6881
private final BackendStateManager backendStateManager;
6982
private final ResourceGroupsManager resourceGroupsManager;
83+
private final HaGatewayConfiguration configuration;
7084

7185
@Inject
7286
public GatewayWebAppResource(
7387
GatewayBackendManager gatewayBackendManager,
7488
QueryHistoryManager queryHistoryManager,
7589
BackendStateManager backendStateManager,
76-
ResourceGroupsManager resourceGroupsManager)
90+
ResourceGroupsManager resourceGroupsManager,
91+
HaGatewayConfiguration configuration)
7792
{
7893
this.gatewayBackendManager = requireNonNull(gatewayBackendManager, "gatewayBackendManager is null");
7994
this.queryHistoryManager = requireNonNull(queryHistoryManager, "queryHistoryManager is null");
8095
this.backendStateManager = requireNonNull(backendStateManager, "backendStateManager is null");
8196
this.resourceGroupsManager = requireNonNull(resourceGroupsManager, "resourceGroupsManager is null");
97+
this.configuration = requireNonNull(configuration, "configuration is null");
8298
}
8399

84100
@POST
@@ -423,4 +439,62 @@ public Response readExactMatchSourceSelector()
423439
List<ResourceGroupsManager.ExactSelectorsDetail> selectorsDetailList = resourceGroupsManager.readExactMatchSourceSelector();
424440
return Response.ok(Result.ok(selectorsDetailList)).build();
425441
}
442+
443+
@GET
444+
@RolesAllowed("USER")
445+
@Produces(MediaType.APPLICATION_JSON)
446+
@Path("/getRoutingRules")
447+
public Response getRoutingRules()
448+
{
449+
try {
450+
String rulesConfigPath = configuration.getRoutingRules().getRulesConfigPath();
451+
YAMLFactory yamlFactory = new YAMLFactory();
452+
ObjectMapper yamlReader = new ObjectMapper(yamlFactory);
453+
YAMLParser yamlParser = yamlFactory.createParser(new String(Files.readAllBytes(Paths.get(rulesConfigPath))));
454+
List<RoutingRules> routingRulesList = yamlReader
455+
.readValues(yamlParser, new TypeReference<RoutingRules>() {})
456+
.readAll();
457+
return Response.ok(Result.ok(routingRulesList)).build();
458+
}
459+
catch (IOException e) {
460+
throw new RuntimeException(e);
461+
}
462+
}
463+
464+
@POST
465+
@RolesAllowed("ADMIN")
466+
@Consumes(MediaType.APPLICATION_JSON)
467+
@Produces(MediaType.APPLICATION_JSON)
468+
@Path("/updateRoutingRules")
469+
public synchronized Response updateRoutingRules(RoutingRules routingRules)
470+
{
471+
String rulesConfigPath = configuration.getRoutingRules().getRulesConfigPath();
472+
ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory());
473+
List<RoutingRules> routingRulesList = new ArrayList<>();
474+
YAMLFactory yamlFactory = new YAMLFactory();
475+
try {
476+
YAMLParser yamlParser = yamlFactory.createParser(new String(Files.readAllBytes(Paths.get(rulesConfigPath))));
477+
routingRulesList = yamlReader
478+
.readValues(yamlParser, new TypeReference<RoutingRules>() {})
479+
.readAll();
480+
481+
for (int i = 0; i < routingRulesList.size(); i++) {
482+
if (routingRulesList.get(i).name().equals(routingRules.name())) {
483+
routingRulesList.set(i, routingRules);
484+
break;
485+
}
486+
}
487+
488+
ObjectMapper yamlWriter = new ObjectMapper(new YAMLFactory());
489+
StringBuilder yamlContent = new StringBuilder();
490+
for (RoutingRules rule : routingRulesList) {
491+
yamlContent.append(yamlWriter.writeValueAsString(rule));
492+
}
493+
Files.write(Paths.get(rulesConfigPath), yamlContent.toString().getBytes());
494+
}
495+
catch (IOException e) {
496+
throw new RuntimeException(e);
497+
}
498+
return Response.ok(Result.ok(routingRulesList)).build();
499+
}
426500
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import {api} from "../base";
2+
import {RoutingRulesData} from "../../types/routing-rules";
3+
4+
export async function routingRulesApi(): Promise<any> {
5+
const response = await api.get('/webapp/getRoutingRules');
6+
return response;
7+
}
8+
9+
export async function updateRoutingRulesApi(body: Record<string, any>): Promise<RoutingRulesData> {
10+
return api.post('/webapp/updateRoutingRules', body)
11+
}

webapp/src/components/layout.tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Nav, Avatar, Layout, Dropdown, Button, Toast, Modal, Tag } from '@douyinfe/semi-ui';
2-
import { IconGithubLogo, IconDoubleChevronRight, IconDoubleChevronLeft, IconMoon, IconSun, IconMark, IconIdCard } from '@douyinfe/semi-icons';
2+
import { IconGithubLogo, IconDoubleChevronRight, IconDoubleChevronLeft, IconMoon, IconSun, IconMark, IconIdCard, IconUserSetting, IconUser } from '@douyinfe/semi-icons';
33
import styles from './layout.module.scss';
44
import { useEffect, useState } from 'react';
55
import { Link, useLocation } from "react-router-dom";
@@ -87,14 +87,19 @@ export const RootLayout = (props: {
8787
</Dropdown.Menu>
8888
}
8989
>
90-
<Avatar
91-
size="small"
92-
src={access.avatar || config.avatar}
93-
color="blue"
94-
className={styles.avatar}
95-
>
96-
{access.nickName}
97-
</Avatar>
90+
{access.roles.includes('ADMIN') ? (
91+
<Button icon={<IconUserSetting
92+
size="extra-large"
93+
color="orange"
94+
className={styles.semiIconsBell} />}>
95+
</Button>
96+
) : (
97+
<Button icon={<IconUser
98+
size="extra-large"
99+
color="blue"
100+
className={styles.semiIconsBell} />}>
101+
</Button>
102+
)}
98103
</Dropdown>
99104
</div>
100105
}

0 commit comments

Comments
 (0)