Skip to content

Commit 2e0217f

Browse files
authored
feat: Support managing proxy servers (higress-group#572)
1 parent ee1df83 commit 2e0217f

File tree

27 files changed

+1228
-128
lines changed

27 files changed

+1228
-128
lines changed

backend/console/src/main/java/com/alibaba/higress/console/config/SdkConfig.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.alibaba.higress.sdk.model.wasmplugin.WasmPluginServiceConfig;
2727
import com.alibaba.higress.sdk.service.DomainService;
2828
import com.alibaba.higress.sdk.service.HigressServiceProvider;
29+
import com.alibaba.higress.sdk.service.ProxyServerService;
2930
import com.alibaba.higress.sdk.service.RouteService;
3031
import com.alibaba.higress.sdk.service.ServiceService;
3132
import com.alibaba.higress.sdk.service.ServiceSourceService;
@@ -121,6 +122,11 @@ public ServiceSourceService serviceSourceService() {
121122
return serviceProvider.serviceSourceService();
122123
}
123124

125+
@Bean
126+
public ProxyServerService proxyServerService() {
127+
return serviceProvider.proxyServerService();
128+
}
129+
124130
@Bean
125131
public TlsCertificateService tlsCertificateService() {
126132
return serviceProvider.tlsCertificateService();
@@ -160,5 +166,4 @@ public McpServerService mcpServerService() {
160166
public McpServerHelper mcpServerHelper() {
161167
return new McpServerHelper();
162168
}
163-
164169
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Copyright (c) 2022-2025 Alibaba Group Holding Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
package com.alibaba.higress.console.controller;
14+
15+
import javax.annotation.Resource;
16+
import javax.validation.constraints.NotBlank;
17+
18+
import org.springdoc.api.annotations.ParameterObject;
19+
import org.springframework.http.ResponseEntity;
20+
import org.springframework.web.bind.annotation.DeleteMapping;
21+
import org.springframework.web.bind.annotation.GetMapping;
22+
import org.springframework.web.bind.annotation.PathVariable;
23+
import org.springframework.web.bind.annotation.PostMapping;
24+
import org.springframework.web.bind.annotation.PutMapping;
25+
import org.springframework.web.bind.annotation.RequestBody;
26+
import org.springframework.web.bind.annotation.RequestMapping;
27+
import org.springframework.web.bind.annotation.RestController;
28+
29+
import com.alibaba.higress.console.controller.dto.PaginatedResponse;
30+
import com.alibaba.higress.console.controller.dto.Response;
31+
import com.alibaba.higress.console.controller.util.ControllerUtil;
32+
import com.alibaba.higress.sdk.constant.HigressConstants;
33+
import com.alibaba.higress.sdk.exception.ValidationException;
34+
import com.alibaba.higress.sdk.model.CommonPageQuery;
35+
import com.alibaba.higress.sdk.model.PaginatedResult;
36+
import com.alibaba.higress.sdk.model.ProxyServer;
37+
import com.alibaba.higress.sdk.service.ProxyServerService;
38+
39+
import io.swagger.v3.oas.annotations.Operation;
40+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
41+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
42+
import io.swagger.v3.oas.annotations.tags.Tag;
43+
44+
@RestController("ProxyServerController")
45+
@RequestMapping("/v1/proxy-servers")
46+
@Tag(name = "Proxy Server APIs")
47+
public class ProxyServerController {
48+
49+
@Resource
50+
private ProxyServerService proxyServerService;
51+
52+
@GetMapping
53+
@Operation(summary = "List proxy servers")
54+
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Proxy servers listed successfully"),
55+
@ApiResponse(responseCode = "500", description = "Internal server error")})
56+
public ResponseEntity<PaginatedResponse<ProxyServer>> list(@ParameterObject CommonPageQuery query) {
57+
PaginatedResult<ProxyServer> result = proxyServerService.list(query);
58+
return ControllerUtil.buildResponseEntity(result);
59+
}
60+
61+
@PostMapping
62+
@Operation(summary = "Add a new proxy server")
63+
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Proxy server added successfully"),
64+
@ApiResponse(responseCode = "400", description = "Proxy server data is not valid"),
65+
@ApiResponse(responseCode = "409", description = "Proxy server already existed with the same name."),
66+
@ApiResponse(responseCode = "500", description = "Internal server error")})
67+
public ResponseEntity<Response<ProxyServer>> add(@RequestBody ProxyServer proxyServer) {
68+
if (!proxyServer.isValid()) {
69+
throw new ValidationException("proxyServer body is not valid.");
70+
}
71+
if (proxyServer.getName().endsWith(HigressConstants.INTERNAL_RESOURCE_NAME_SUFFIX)) {
72+
throw new ValidationException("Adding an internal proxy server is not allowed.");
73+
}
74+
ProxyServer finalProxyServer = proxyServerService.add(proxyServer);
75+
return ControllerUtil.buildResponseEntity(finalProxyServer);
76+
}
77+
78+
@PutMapping("/{name}")
79+
@Operation(summary = "Update an existed proxy server")
80+
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Proxy server updated successfully"),
81+
@ApiResponse(responseCode = "400", description = "Proxy server data is not valid"),
82+
@ApiResponse(responseCode = "409", description = "Proxy server trying to add already existed"),
83+
@ApiResponse(responseCode = "500", description = "Internal server error")})
84+
public ResponseEntity<Response<ProxyServer>> addOrUpdate(@PathVariable("name") @NotBlank String name,
85+
@RequestBody ProxyServer proxyServer) {
86+
proxyServer.setName(name);
87+
if (!proxyServer.isValid()) {
88+
throw new ValidationException("proxyServer body is not valid.");
89+
}
90+
if (proxyServer.getName().endsWith(HigressConstants.INTERNAL_RESOURCE_NAME_SUFFIX)) {
91+
throw new ValidationException("Updating an internal proxy server is not allowed.");
92+
}
93+
ProxyServer finalProxyServer = proxyServerService.addOrUpdate(proxyServer);
94+
return ControllerUtil.buildResponseEntity(finalProxyServer);
95+
}
96+
97+
@DeleteMapping("/{name}")
98+
@Operation(summary = "Delete a proxy server")
99+
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Proxy server deleted successfully"),
100+
@ApiResponse(responseCode = "400", description = "Deleting an internal proxy server is not allowed."),
101+
@ApiResponse(responseCode = "500", description = "Internal server error")})
102+
public ResponseEntity<Response<ProxyServer>> delete(@PathVariable("name") @NotBlank String name) {
103+
if (name.endsWith(HigressConstants.INTERNAL_RESOURCE_NAME_SUFFIX)) {
104+
throw new ValidationException("Deleting an internal proxy server is not allowed.");
105+
}
106+
proxyServerService.delete(name);
107+
return ResponseEntity.noContent().build();
108+
}
109+
110+
@GetMapping("/{name}")
111+
@Operation(summary = "Get proxy server by name")
112+
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Proxy server found"),
113+
@ApiResponse(responseCode = "404", description = "Proxy server not found"),
114+
@ApiResponse(responseCode = "500", description = "Internal server error")})
115+
public ResponseEntity<Response<ProxyServer>> query(@PathVariable("name") @NotBlank String name) {
116+
ProxyServer server = proxyServerService.query(name);
117+
return ControllerUtil.buildResponseEntity(server);
118+
}
119+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright (c) 2022-2025 Alibaba Group Holding Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
package com.alibaba.higress.sdk.model;
14+
15+
import java.beans.Transient;
16+
import java.util.regex.Pattern;
17+
18+
import org.apache.commons.lang3.StringUtils;
19+
20+
import com.alibaba.higress.sdk.service.kubernetes.crd.mcp.V1McpBridge;
21+
import com.alibaba.higress.sdk.util.ValidateUtil;
22+
23+
import io.swagger.v3.oas.annotations.media.Schema;
24+
import lombok.AllArgsConstructor;
25+
import lombok.Builder;
26+
import lombok.Data;
27+
import lombok.NoArgsConstructor;
28+
29+
@Data
30+
@Builder
31+
@NoArgsConstructor
32+
@AllArgsConstructor
33+
@Schema(description = "Proxy Server")
34+
public class ProxyServer {
35+
36+
private static final Pattern NAME_PATTERN = Pattern.compile("^[a-z][a-z0-9-_.]{0,62}$");
37+
38+
@Schema(description = "Proxy server type", allowableValues = {V1McpBridge.PROXY_TYPE_HTTP})
39+
private String type;
40+
41+
@Schema(description = "Proxy server name")
42+
private String name;
43+
44+
@Schema(description = "Proxy server version. Required when updating.")
45+
private String version;
46+
47+
@Schema(description = "Proxy server address, can be IP or domain name")
48+
private String serverAddress;
49+
50+
@Schema(description = "Proxy server port")
51+
private Integer serverPort;
52+
53+
@Schema(description = "Proxy server connect timeout in milliseconds, default is 1200")
54+
private Integer connectTimeout;
55+
56+
@Transient
57+
public boolean isValid() {
58+
if (StringUtils.isBlank(name) || !NAME_PATTERN.matcher(name).matches()) {
59+
return false;
60+
}
61+
if (StringUtils.isBlank(type)) {
62+
return false;
63+
}
64+
if (StringUtils.isBlank(serverAddress)
65+
|| !ValidateUtil.checkIpAddress(serverAddress) && !ValidateUtil.checkDomain(serverAddress)) {
66+
return false;
67+
}
68+
if (!ValidateUtil.checkPort(serverPort)) {
69+
return false;
70+
}
71+
if (connectTimeout != null && connectTimeout < 0) {
72+
return false;
73+
}
74+
return true;
75+
}
76+
}

backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ServiceSource.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
*/
1313
package com.alibaba.higress.sdk.model;
1414

15+
import java.beans.Transient;
1516
import java.util.HashMap;
1617
import java.util.List;
1718
import java.util.Map;
19+
import java.util.Set;
1820

1921
import org.apache.commons.collections4.CollectionUtils;
2022
import org.apache.commons.collections4.MapUtils;
@@ -24,6 +26,7 @@
2426
import com.alibaba.higress.sdk.service.kubernetes.crd.mcp.V1McpBridge;
2527
import com.alibaba.higress.sdk.util.ValidateUtil;
2628
import com.google.common.base.Splitter;
29+
import com.google.common.collect.ImmutableSet;
2730

2831
import io.swagger.v3.oas.annotations.media.Schema;
2932
import lombok.AllArgsConstructor;
@@ -40,6 +43,9 @@ public class ServiceSource implements VersionedDto {
4043

4144
private static final Map<String, ServiceSourceValidator> VALIDATORS = new HashMap<>();
4245

46+
private static final Set<String> PROXY_SUPPORTED_REGISTRY_TYPES =
47+
ImmutableSet.of(V1McpBridge.REGISTRY_TYPE_STATIC, V1McpBridge.REGISTRY_TYPE_DNS);
48+
4349
static {
4450
VALIDATORS.put(V1McpBridge.REGISTRY_TYPE_NACOS, new NacosServiceSourceValidator());
4551
VALIDATORS.put(V1McpBridge.REGISTRY_TYPE_NACOS2, new NacosServiceSourceValidator());
@@ -78,6 +84,9 @@ public class ServiceSource implements VersionedDto {
7884
@Schema(description = "Service source SNI. Used in static and dns types when TLS is enabled.")
7985
private String sni;
8086

87+
@Schema(description = "Proxy server name. Only supported in static and dns types.")
88+
private String proxyName;
89+
8190
@Schema(description = "Service source extra properties, depending on the type.\n"
8291
+ "For nacos/nacos2/nacos3: nacosGroups, nacosNamespaceId\n"
8392
+ "For MCP supported types (e.g. nacos3): enableMCPServer, mcpServerBaseUrl, mcpServerExportDomains\n"
@@ -87,6 +96,7 @@ public class ServiceSource implements VersionedDto {
8796
@Schema(description = "Service source authentication config")
8897
private ServiceSourceAuthN authN;
8998

99+
@Transient
90100
public boolean isValid() {
91101
if (StringUtils.isAnyBlank(this.name, this.type, this.getDomain())) {
92102
return false;
@@ -109,6 +119,10 @@ public boolean isValid() {
109119
return false;
110120
}
111121

122+
if (StringUtils.isNotEmpty(proxyName) && !PROXY_SUPPORTED_REGISTRY_TYPES.contains(this.getType())) {
123+
return false;
124+
}
125+
112126
return true;
113127
}
114128

@@ -138,12 +152,12 @@ private boolean validateMcpConfigs() {
138152
if (!(rawExportDomains instanceof List)) {
139153
return false;
140154
}
141-
List<?> exportDomains = (List<?>) rawExportDomains;
155+
List<?> exportDomains = (List<?>)rawExportDomains;
142156
for (Object rawExportDomain : exportDomains) {
143157
if (!(rawExportDomain instanceof String)) {
144158
return false;
145159
}
146-
String exportDomain = (String) rawExportDomain;
160+
String exportDomain = (String)rawExportDomain;
147161
if (!ValidateUtil.checkDomain(exportDomain)) {
148162
return false;
149163
}
@@ -154,7 +168,7 @@ private boolean validateMcpConfigs() {
154168
if (!(rawServerBaseUrl instanceof String)) {
155169
return false;
156170
}
157-
String serverBaseUrl= (String) rawServerBaseUrl;
171+
String serverBaseUrl = (String)rawServerBaseUrl;
158172
if (!ValidateUtil.checkUrlPath(serverBaseUrl)) {
159173
return false;
160174
}

backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/LlmProvider.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ public class LlmProvider {
3636
private String type;
3737
@Schema(description = "Provider protocol", ref = "LlmProviderProtocol")
3838
private String protocol;
39+
@Schema(description = "Proxy server name")
40+
private String proxyName;
3941
@Schema(description = "Tokens used to request the provider")
4042
private List<String> tokens;
4143
@Schema(description = "Token fail-over configuration")

backend/sdk/src/main/java/com/alibaba/higress/sdk/service/HigressServiceProvider.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ static HigressServiceProvider create(HigressServiceConfig config) throws IOExcep
4343

4444
ServiceSourceService serviceSourceService();
4545

46+
ProxyServerService proxyServerService();
47+
4648
TlsCertificateService tlsCertificateService();
4749

4850
WasmPluginService wasmPluginService();
@@ -56,5 +58,4 @@ static HigressServiceProvider create(HigressServiceConfig config) throws IOExcep
5658
LlmProviderService llmProviderService();
5759

5860
McpServerService mcpServerService();
59-
6061
}

backend/sdk/src/main/java/com/alibaba/higress/sdk/service/HigressServiceProviderImpl.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class HigressServiceProviderImpl implements HigressServiceProvider {
3939
private final RouteService routeService;
4040
private final ServiceService serviceService;
4141
private final ServiceSourceService serviceSourceService;
42+
private final ProxyServerService proxyServerService;
4243
private final TlsCertificateService tlsCertificateService;
4344
private final WasmPluginService wasmPluginService;
4445
private final WasmPluginInstanceService wasmPluginInstanceService;
@@ -56,6 +57,7 @@ class HigressServiceProviderImpl implements HigressServiceProvider {
5657
serviceService = new ServiceServiceByApiServerImpl(kubernetesClientService, kubernetesModelConverter);
5758
}
5859
serviceSourceService = new ServiceSourceServiceImpl(kubernetesClientService, kubernetesModelConverter);
60+
proxyServerService = new ProxyServerServiceImpl(kubernetesClientService, kubernetesModelConverter);
5961
tlsCertificateService = new TlsCertificateServiceImpl(kubernetesClientService, kubernetesModelConverter);
6062
wasmPluginService = new WasmPluginServiceImpl(kubernetesClientService, kubernetesModelConverter,
6163
config.getWasmPluginServiceConfig());
@@ -104,6 +106,11 @@ public ServiceSourceService serviceSourceService() {
104106
return serviceSourceService;
105107
}
106108

109+
@Override
110+
public ProxyServerService proxyServerService() {
111+
return proxyServerService;
112+
}
113+
107114
@Override
108115
public TlsCertificateService tlsCertificateService() {
109116
return tlsCertificateService;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright (c) 2022-2025 Alibaba Group Holding Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
package com.alibaba.higress.sdk.service;
14+
15+
import com.alibaba.higress.sdk.model.CommonPageQuery;
16+
import com.alibaba.higress.sdk.model.PaginatedResult;
17+
import com.alibaba.higress.sdk.model.ProxyServer;
18+
19+
public interface ProxyServerService {
20+
21+
PaginatedResult<ProxyServer> list(CommonPageQuery query);
22+
23+
ProxyServer addOrUpdate(ProxyServer serviceSource);
24+
25+
ProxyServer add(ProxyServer serviceSource);
26+
27+
void delete(String name);
28+
29+
ProxyServer query(String name);
30+
}

0 commit comments

Comments
 (0)