From 76c6abd956f7d271649f364d0d3f80ac06fa7853 Mon Sep 17 00:00:00 2001 From: Kent Dong Date: Mon, 21 Oct 2024 10:23:44 +0800 Subject: [PATCH] feat: Update AI console function. (#360) --- .../controller/ConsumersController.java | 13 ---- .../controller/ai/LlmProvidersController.java | 9 --- .../alibaba/higress/sdk/model/ai/AiRoute.java | 13 ++-- .../sdk/model/ai/AiRouteAuthConfig.java | 40 ++++++++++++ .../sdk/model/ai/AiRouteFallbackConfig.java | 55 ++++++++++++++++ .../sdk/model/ai/AiRouteFallbackStrategy.java | 23 +++++++ .../higress/sdk/model/ai/AiUpstream.java | 10 +++ .../higress/sdk/model/ai/LlmProvider.java | 12 ---- .../sdk/model/consumer/Credential.java | 2 - .../sdk/model/consumer/KeyAuthCredential.java | 7 -- .../service/HigressServiceProviderImpl.java | 4 +- .../ai/AbstractLlmProviderHandler.java | 2 + .../sdk/service/ai/AiRouteServiceImpl.java | 65 +++++++++++++++---- .../sdk/service/consumer/ConsumerService.java | 5 ++ .../service/consumer/ConsumerServiceImpl.java | 32 +++++++-- .../service/consumer/CredentialHandler.java | 6 +- .../consumer/KeyAuthCredentialHandler.java | 25 ++++++- .../KeyAuthCredentialHandlerTest.java | 16 ++--- 18 files changed, 257 insertions(+), 82 deletions(-) create mode 100644 backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/AiRouteAuthConfig.java create mode 100644 backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/AiRouteFallbackConfig.java create mode 100644 backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/AiRouteFallbackStrategy.java diff --git a/backend/console/src/main/java/com/alibaba/higress/console/controller/ConsumersController.java b/backend/console/src/main/java/com/alibaba/higress/console/controller/ConsumersController.java index bcd73ddb..ecd49f56 100644 --- a/backend/console/src/main/java/com/alibaba/higress/console/controller/ConsumersController.java +++ b/backend/console/src/main/java/com/alibaba/higress/console/controller/ConsumersController.java @@ -16,7 +16,6 @@ import javax.validation.ValidationException; import javax.validation.constraints.NotBlank; -import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; @@ -35,7 +34,6 @@ import com.alibaba.higress.sdk.model.CommonPageQuery; import com.alibaba.higress.sdk.model.PaginatedResult; import com.alibaba.higress.sdk.model.consumer.Consumer; -import com.alibaba.higress.sdk.model.consumer.Credential; import com.alibaba.higress.sdk.service.consumer.ConsumerService; @RestController("ConsumersController") @@ -53,9 +51,6 @@ public void setConsumerService(ConsumerService consumerService) { @GetMapping public ResponseEntity> list(CommonPageQuery query) { PaginatedResult consumers = consumerService.list(query); - if (CollectionUtils.isNotEmpty(consumers.getData())) { - consumers.getData().forEach(ConsumersController::maskSensitiveData); - } return ControllerUtil.buildResponseEntity(consumers); } @@ -63,7 +58,6 @@ public ResponseEntity> list(CommonPageQuery query) { public ResponseEntity> add(@RequestBody Consumer consumer) { consumer.validate(false); Consumer newConsumer = consumerService.addOrUpdate(consumer); - maskSensitiveData(newConsumer); return ControllerUtil.buildResponseEntity(newConsumer); } @@ -83,7 +77,6 @@ public ResponseEntity> put(@PathVariable("name") @NotBlank St } consumer.validate(true); Consumer updatedConsumer = consumerService.addOrUpdate(consumer); - maskSensitiveData(updatedConsumer); return ControllerUtil.buildResponseEntity(updatedConsumer); } @@ -92,10 +85,4 @@ public ResponseEntity> delete(@PathVariable("name") @NotBlank consumerService.delete(name); return ResponseEntity.noContent().build(); } - - private static void maskSensitiveData(Consumer consumer) { - if (consumer != null && CollectionUtils.isNotEmpty(consumer.getCredentials())) { - consumer.getCredentials().forEach(Credential::maskSensitiveData); - } - } } diff --git a/backend/console/src/main/java/com/alibaba/higress/console/controller/ai/LlmProvidersController.java b/backend/console/src/main/java/com/alibaba/higress/console/controller/ai/LlmProvidersController.java index 8c7e213a..a664a7c6 100644 --- a/backend/console/src/main/java/com/alibaba/higress/console/controller/ai/LlmProvidersController.java +++ b/backend/console/src/main/java/com/alibaba/higress/console/controller/ai/LlmProvidersController.java @@ -16,7 +16,6 @@ import javax.validation.ValidationException; import javax.validation.constraints.NotBlank; -import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; @@ -52,25 +51,18 @@ public void setLlmProviderService(LlmProviderService llmProviderService) { @GetMapping public ResponseEntity> list(CommonPageQuery query) { PaginatedResult providers = llmProviderService.list(query); - if (CollectionUtils.isNotEmpty(providers.getData())) { - providers.getData().forEach(LlmProvider::maskSensitiveData); - } return ControllerUtil.buildResponseEntity(providers); } @PostMapping public ResponseEntity> add(@RequestBody LlmProvider certificate) { LlmProvider newProvider = llmProviderService.addOrUpdate(certificate); - newProvider.maskSensitiveData(); return ControllerUtil.buildResponseEntity(newProvider); } @GetMapping(value = "/{name}") public ResponseEntity> query(@PathVariable("name") @NotBlank String name) { LlmProvider provider = llmProviderService.query(name); - if (provider != null){ - provider.maskSensitiveData(); - } return ControllerUtil.buildResponseEntity(provider); } @@ -83,7 +75,6 @@ public ResponseEntity> put(@PathVariable("name") @NotBlank throw new ValidationException("Provider name in the URL doesn't match the one in the body."); } LlmProvider updatedProvider = llmProviderService.addOrUpdate(provider); - updatedProvider.maskSensitiveData(); return ControllerUtil.buildResponseEntity(updatedProvider); } diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/AiRoute.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/AiRoute.java index 55216429..52dc66f6 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/AiRoute.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/AiRoute.java @@ -36,9 +36,8 @@ public class AiRoute { private String version; private List domains; private List upstreams; - private Boolean authEnabled; - private List consumers; - private AiUpstream fallbackUpstream; + private AiRouteAuthConfig authConfig; + private AiRouteFallbackConfig fallbackConfig; public void validate() { if (StringUtils.isBlank(name)) { @@ -47,8 +46,12 @@ public void validate() { if (CollectionUtils.isEmpty(upstreams)){ throw new ValidationException("upstreams cannot be empty."); } - if (Boolean.TRUE.equals(authEnabled) && CollectionUtils.isEmpty(consumers)){ - throw new ValidationException("consumers cannot be empty when authEnabled is true."); + upstreams.forEach(AiUpstream::validate); + if (authConfig != null){ + authConfig.validate(); + } + if (fallbackConfig != null){ + fallbackConfig.validate(); } } } diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/AiRouteAuthConfig.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/AiRouteAuthConfig.java new file mode 100644 index 00000000..6df6d9ca --- /dev/null +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/AiRouteAuthConfig.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022-2023 Alibaba Group Holding Ltd. + * + * 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 com.alibaba.higress.sdk.model.ai; + +import java.util.List; + +import org.apache.commons.collections4.CollectionUtils; + +import com.alibaba.higress.sdk.exception.ValidationException; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AiRouteAuthConfig { + + private Boolean enabled; + private List allowedConsumers; + + public void validate() { + if (Boolean.TRUE.equals(enabled) && CollectionUtils.isEmpty(allowedConsumers)) { + throw new ValidationException("allowedConsumers cannot be empty when auth is enabled."); + } + } +} diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/AiRouteFallbackConfig.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/AiRouteFallbackConfig.java new file mode 100644 index 00000000..0e704a17 --- /dev/null +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/AiRouteFallbackConfig.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2022-2023 Alibaba Group Holding Ltd. + * + * 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 com.alibaba.higress.sdk.model.ai; + +import java.util.List; + +import org.apache.commons.collections4.CollectionUtils; + +import com.alibaba.higress.sdk.exception.ValidationException; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.apache.commons.lang3.StringUtils; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AiRouteFallbackConfig { + + private Boolean enabled; + private List upstreams; + private String fallbackStrategy; + + public void validate() { + if (!Boolean.TRUE.equals(enabled)) { + return; + } + if (StringUtils.isNotEmpty(fallbackStrategy)) { + switch (fallbackStrategy) { + case AiRouteFallbackStrategy.RANDOM: + case AiRouteFallbackStrategy.SEQUENCE: + break; + default: + throw new ValidationException("unknown fallback strategy: " + fallbackStrategy); + } + } + if (CollectionUtils.isEmpty(upstreams)) { + throw new ValidationException("upstreams cannot be empty when fallback is enabled."); + } + upstreams.forEach(AiUpstream::validate); + } +} diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/AiRouteFallbackStrategy.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/AiRouteFallbackStrategy.java new file mode 100644 index 00000000..2818c1bd --- /dev/null +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/AiRouteFallbackStrategy.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022-2023 Alibaba Group Holding Ltd. + * + * 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 com.alibaba.higress.sdk.model.ai; + +public final class AiRouteFallbackStrategy { + + private AiRouteFallbackStrategy() { + } + + public static final String RANDOM = "RAND"; + + public static final String SEQUENCE = "SEQ"; +} diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/AiUpstream.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/AiUpstream.java index 597039bf..b7e7f9d7 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/AiUpstream.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/AiUpstream.java @@ -12,6 +12,10 @@ */ package com.alibaba.higress.sdk.model.ai; +import org.apache.commons.lang3.StringUtils; + +import com.alibaba.higress.sdk.exception.ValidationException; + import io.swagger.annotations.ApiModel; import lombok.AllArgsConstructor; import lombok.Builder; @@ -27,4 +31,10 @@ public class AiUpstream { private String provider; private Integer weight; + + public void validate() { + if (StringUtils.isEmpty(provider)) { + throw new ValidationException("provider cannot be null or empty."); + } + } } diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/LlmProvider.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/LlmProvider.java index bd77238b..e3d7f201 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/LlmProvider.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/LlmProvider.java @@ -15,11 +15,8 @@ import java.util.List; import java.util.Map; -import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import com.alibaba.higress.sdk.util.StringUtil; - import io.swagger.annotations.ApiModel; import lombok.AllArgsConstructor; import lombok.Builder; @@ -53,13 +50,4 @@ public void validate(boolean forUpdate) { throw new IllegalArgumentException("Unknown protocol: " + protocol); } } - - public void maskSensitiveData() { - if (CollectionUtils.isEmpty(tokens)) { - return; - } - for (int i = 0; i < tokens.size(); ++i) { - tokens.set(i, StringUtil.mask(tokens.get(i))); - } - } } diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/consumer/Credential.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/consumer/Credential.java index 4e34af0d..6a5ca489 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/consumer/Credential.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/consumer/Credential.java @@ -55,6 +55,4 @@ public void setProperty(String name, Object value) { } public void validate(boolean forUpdate) {} - - public void maskSensitiveData() {} } \ No newline at end of file diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/consumer/KeyAuthCredential.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/consumer/KeyAuthCredential.java index 7442d4ad..d77bde79 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/consumer/KeyAuthCredential.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/consumer/KeyAuthCredential.java @@ -17,7 +17,6 @@ import org.apache.commons.lang3.StringUtils; import com.alibaba.higress.sdk.exception.ValidationException; -import com.alibaba.higress.sdk.util.StringUtil; import io.swagger.annotations.ApiModel; import lombok.AllArgsConstructor; @@ -78,10 +77,4 @@ public void validate(boolean forUpdate) { throw new ValidationException("value cannot be blank."); } } - - @Override - public void maskSensitiveData() { - super.maskSensitiveData(); - this.value = StringUtil.mask(this.value); - } } diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/HigressServiceProviderImpl.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/HigressServiceProviderImpl.java index 3da7e4e2..4766a982 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/HigressServiceProviderImpl.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/HigressServiceProviderImpl.java @@ -58,8 +58,8 @@ class HigressServiceProviderImpl implements HigressServiceProvider { consumerService = new ConsumerServiceImpl(wasmPluginService, wasmPluginInstanceService); llmProviderService = new LlmProviderServiceImpl(serviceSourceService, wasmPluginService, wasmPluginInstanceService); - aiRouteService = - new AiRouteServiceImpl(kubernetesModelConverter, kubernetesClientService, routeService, llmProviderService); + aiRouteService = new AiRouteServiceImpl(kubernetesModelConverter, kubernetesClientService, routeService, + llmProviderService, consumerService); } @Override diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/ai/AbstractLlmProviderHandler.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/ai/AbstractLlmProviderHandler.java index d06cfd19..fb8fc4cb 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/ai/AbstractLlmProviderHandler.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/ai/AbstractLlmProviderHandler.java @@ -111,6 +111,8 @@ public void saveConfig(LlmProvider provider, Map configurations) List tokens = provider.getTokens(); if (CollectionUtils.isNotEmpty(tokens)) { configurations.put(PROVIDER_API_TOKENS_KEY, tokens); + } else { + configurations.remove(PROVIDER_API_TOKENS_KEY); } TokenFailoverConfig failoverConfig = provider.getTokenFailoverConfig(); diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/ai/AiRouteServiceImpl.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/ai/AiRouteServiceImpl.java index 511a5ca4..6032d71a 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/ai/AiRouteServiceImpl.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/ai/AiRouteServiceImpl.java @@ -19,6 +19,7 @@ import java.util.Map; import java.util.Optional; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; @@ -30,13 +31,18 @@ import com.alibaba.higress.sdk.model.CommonPageQuery; import com.alibaba.higress.sdk.model.PaginatedResult; import com.alibaba.higress.sdk.model.Route; +import com.alibaba.higress.sdk.model.WasmPluginInstanceScope; import com.alibaba.higress.sdk.model.ai.AiRoute; +import com.alibaba.higress.sdk.model.ai.AiRouteAuthConfig; +import com.alibaba.higress.sdk.model.ai.AiRouteFallbackConfig; +import com.alibaba.higress.sdk.model.ai.AiRouteFallbackStrategy; import com.alibaba.higress.sdk.model.ai.AiUpstream; import com.alibaba.higress.sdk.model.route.KeyedRoutePredicate; import com.alibaba.higress.sdk.model.route.RoutePredicate; import com.alibaba.higress.sdk.model.route.RoutePredicateTypeEnum; import com.alibaba.higress.sdk.model.route.UpstreamService; import com.alibaba.higress.sdk.service.RouteService; +import com.alibaba.higress.sdk.service.consumer.ConsumerService; import com.alibaba.higress.sdk.service.kubernetes.KubernetesClientService; import com.alibaba.higress.sdk.service.kubernetes.KubernetesModelConverter; import com.alibaba.higress.sdk.service.kubernetes.crd.istio.V1alpha3EnvoyFilter; @@ -62,15 +68,18 @@ public class AiRouteServiceImpl implements AiRouteService { private final LlmProviderService llmProviderService; + private final ConsumerService consumerService; + private final String routeFallbackEnvoyFilterConfig; public AiRouteServiceImpl(KubernetesModelConverter kubernetesModelConverter, KubernetesClientService kubernetesClientService, RouteService routeService, - LlmProviderService llmProviderService) { + LlmProviderService llmProviderService, ConsumerService consumerService) { this.kubernetesModelConverter = kubernetesModelConverter; this.kubernetesClientService = kubernetesClientService; this.routeService = routeService; this.llmProviderService = llmProviderService; + this.consumerService = consumerService; try { this.routeFallbackEnvoyFilterConfig = @@ -151,6 +160,7 @@ public AiRoute update(AiRoute route) { } writeAiRouteResources(route); + writeAiRouteFallbackResources(route); return kubernetesModelConverter.configMap2AiRoute(updatedConfigMap); } @@ -158,13 +168,15 @@ public AiRoute update(AiRoute route) { private void writeAiRouteResources(AiRoute aiRoute) { String routeName = aiRoute.getName() + HigressConstants.INTERNAL_RESOURCE_NAME_SUFFIX; Route route = buildRoute(routeName, aiRoute); + setUpstreams(route, aiRoute.getUpstreams()); saveRoute(route); - - writeAiRouteFallbackResources(aiRoute); + writeAuthConfigResources(routeName, aiRoute.getAuthConfig()); } private void writeAiRouteFallbackResources(AiRoute aiRoute) { - if (aiRoute.getFallbackUpstream() == null || StringUtils.isEmpty(aiRoute.getFallbackUpstream().getProvider())) { + AiRouteFallbackConfig fallbackConfig = aiRoute.getFallbackConfig(); + if (fallbackConfig == null || !Boolean.TRUE.equals(fallbackConfig.getEnabled()) + || CollectionUtils.isEmpty(fallbackConfig.getUpstreams())) { return; } @@ -178,6 +190,17 @@ private void writeAiRouteFallbackResources(AiRoute aiRoute) { fallbackHeaderPredicate.setMatchValue(originalRouteName); fallbackHeaderPredicate.setCaseSensitive(true); route.setHeaders(List.of(fallbackHeaderPredicate)); + String fallbackStrategy = fallbackConfig.getFallbackStrategy(); + List fallbackUpStreams; + if (StringUtils.isEmpty(fallbackStrategy) || AiRouteFallbackStrategy.RANDOM.equals(fallbackStrategy)) { + fallbackUpStreams = fallbackConfig.getUpstreams(); + fallbackUpStreams.forEach(upstream -> upstream.setWeight(1)); + } else if (AiRouteFallbackStrategy.SEQUENCE.equals(fallbackStrategy)) { + fallbackUpStreams = List.of(fallbackConfig.getUpstreams().get(0)); + } else { + throw new BusinessException("Unknown fallback strategy: " + fallbackStrategy); + } + setUpstreams(route, fallbackUpStreams); saveRoute(route); StringBuilder envoyFilterBuilder = new StringBuilder(routeFallbackEnvoyFilterConfig); @@ -199,6 +222,14 @@ private void writeAiRouteFallbackResources(AiRoute aiRoute) { throw new BusinessException( "Error occurs when writing the fallback EnvoyFilter for AI route: " + aiRoute.getName(), e); } + + writeAuthConfigResources(fallbackRouteName, aiRoute.getAuthConfig()); + } + + private void writeAuthConfigResources(String routeName, AiRouteAuthConfig authConfig) { + List allowedConsumers = authConfig != null && Boolean.TRUE.equals(authConfig.getEnabled()) + ? authConfig.getAllowedConsumers() : List.of(); + consumerService.updateAllowList(WasmPluginInstanceScope.ROUTE, routeName, allowedConsumers); } private Route buildRoute(String routeName, AiRoute aiRoute) { @@ -206,19 +237,25 @@ private Route buildRoute(String routeName, AiRoute aiRoute) { route.setName(routeName); route.setPath(new RoutePredicate(RoutePredicateTypeEnum.PRE.name(), "/", true)); route.setDomains(aiRoute.getDomains()); - if (aiRoute.getUpstreams() != null) { - List services = new ArrayList<>(aiRoute.getUpstreams().size()); - for (AiUpstream upstream : aiRoute.getUpstreams()) { - UpstreamService service = llmProviderService.buildUpstreamService(upstream.getProvider()); - service.setVersion(null); - service.setWeight(upstream.getWeight()); - services.add(service); - } - route.setServices(services); - } return route; } + private void setUpstreams(Route route, List upstreams) { + if (CollectionUtils.isEmpty(upstreams)) { + route.setServices(List.of()); + return; + } + + List services = new ArrayList<>(upstreams.size()); + for (AiUpstream upstream : upstreams) { + UpstreamService service = llmProviderService.buildUpstreamService(upstream.getProvider()); + service.setVersion(null); + service.setWeight(upstream.getWeight()); + services.add(service); + } + route.setServices(services); + } + private void deleteAiRouteResources(String aiRouteName) { String routeName = aiRouteName + HigressConstants.INTERNAL_RESOURCE_NAME_SUFFIX; routeService.delete(routeName); diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/consumer/ConsumerService.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/consumer/ConsumerService.java index 2ed81e03..2d7773d8 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/consumer/ConsumerService.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/consumer/ConsumerService.java @@ -12,8 +12,11 @@ */ package com.alibaba.higress.sdk.service.consumer; +import java.util.List; + import com.alibaba.higress.sdk.model.CommonPageQuery; import com.alibaba.higress.sdk.model.PaginatedResult; +import com.alibaba.higress.sdk.model.WasmPluginInstanceScope; import com.alibaba.higress.sdk.model.consumer.Consumer; public interface ConsumerService { @@ -25,4 +28,6 @@ public interface ConsumerService { Consumer query(String consumerName); void delete(String consumerName); + + void updateAllowList(WasmPluginInstanceScope scope, String target, List consumerNames); } diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/consumer/ConsumerServiceImpl.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/consumer/ConsumerServiceImpl.java index 08483ad8..303c0b78 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/consumer/ConsumerServiceImpl.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/consumer/ConsumerServiceImpl.java @@ -63,11 +63,7 @@ public Consumer addOrUpdate(Consumer consumer) { if (plugin == null) { throw new BusinessException("Plugin " + config.getPluginName() + " not found"); } - instance = new WasmPluginInstance(); - instance.setPluginName(plugin.getName()); - instance.setPluginVersion(plugin.getPluginVersion()); - instance.setInternal(true); - instance.setScope(WasmPluginInstanceScope.GLOBAL); + instance = createInstance(WasmPluginInstanceScope.GLOBAL, null, config.getPluginName()); } if (config.saveConsumer(instance, consumer)) { instancesToUpdate.add(instance); @@ -112,6 +108,18 @@ public void delete(String consumerName) { } } + @Override + public void updateAllowList(WasmPluginInstanceScope scope, String target, List consumerNames) { + for (CredentialHandler config : CREDENTIAL_HANDLERS.values()) { + WasmPluginInstance instance = wasmPluginInstanceService.query(scope, target, config.getPluginName(), true); + if (instance == null) { + instance = createInstance(scope, target, config.getPluginName()); + } + config.updateAllowList(instance, consumerNames); + wasmPluginInstanceService.addOrUpdate(instance); + } + } + private SortedMap getConsumers() { SortedMap consumers = new TreeMap<>(); for (CredentialHandler config : CREDENTIAL_HANDLERS.values()) { @@ -130,4 +138,18 @@ private SortedMap getConsumers() { } return consumers; } + + private WasmPluginInstance createInstance(WasmPluginInstanceScope scope, String target, String pluginName) { + WasmPlugin plugin = wasmPluginService.query(pluginName, null); + if (plugin == null) { + throw new BusinessException("Plugin " + pluginName + " not found"); + } + WasmPluginInstance instance = new WasmPluginInstance(); + instance.setPluginName(plugin.getName()); + instance.setPluginVersion(plugin.getPluginVersion()); + instance.setInternal(true); + instance.setScope(scope); + instance.setTarget(target); + return instance; + } } diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/consumer/CredentialHandler.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/consumer/CredentialHandler.java index 9d5ed4e2..67ba88bd 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/consumer/CredentialHandler.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/consumer/CredentialHandler.java @@ -12,11 +12,11 @@ */ package com.alibaba.higress.sdk.service.consumer; +import java.util.List; + import com.alibaba.higress.sdk.model.WasmPluginInstance; import com.alibaba.higress.sdk.model.consumer.Consumer; -import java.util.List; - interface CredentialHandler { String getType(); @@ -30,4 +30,6 @@ interface CredentialHandler { boolean saveConsumer(WasmPluginInstance instance, Consumer consumer); boolean deleteConsumer(WasmPluginInstance globalInstance, String consumerName); + + void updateAllowList(WasmPluginInstance instance, List consumerNames); } diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/consumer/KeyAuthCredentialHandler.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/consumer/KeyAuthCredentialHandler.java index 02f8063c..381e56e3 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/consumer/KeyAuthCredentialHandler.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/consumer/KeyAuthCredentialHandler.java @@ -35,7 +35,7 @@ class KeyAuthCredentialHandler implements CredentialHandler { private static final String NAME_KEY = "name"; private static final String IN_HEADER_KEY = "in_header"; private static final String IN_QUERY_KEY = "in_query"; - private static final String KEY_KEY = "key"; + private static final String KEYS_KEY = "keys"; private static final String CREDENTIAL_KEY = "credential"; private static final String ALLOW_KEY = "allow"; @@ -124,6 +124,10 @@ public boolean saveConsumer(WasmPluginInstance instance, Consumer consumer) { instance.setConfigurations(configurations); } + // TODO: Remove this after plugin upgrade. + // Add a dummy key to the global keys list because the plugin requires at least one global key. + configurations.put(KEYS_KEY, List.of("x-higress-dummy-key")); + Object consumersObj = configurations.computeIfAbsent(CONSUMERS_KEY, k -> new ArrayList<>()); List consumers; if (consumersObj instanceof List) { @@ -177,7 +181,7 @@ public boolean saveConsumer(WasmPluginInstance instance, Consumer consumer) { throw new IllegalArgumentException( "Unsupported key auth credential source: " + keyAuthCredential.getSource()); } - consumerConfig.put(KEY_KEY, List.of(key)); + consumerConfig.put(KEYS_KEY, List.of(key)); consumerConfig.put(CREDENTIAL_KEY, credential); configurations.put(CONSUMERS_KEY, consumers); @@ -211,6 +215,21 @@ public boolean deleteConsumer(WasmPluginInstance globalInstance, String consumer return deleted; } + @Override + public void updateAllowList(WasmPluginInstance instance, List consumerNames) { + Map configurations = instance.getConfigurations(); + if (MapUtils.isEmpty(configurations)) { + configurations = new HashMap<>(); + instance.setConfigurations(configurations); + } + + if (CollectionUtils.isEmpty(consumerNames)) { + configurations.remove(ALLOW_KEY); + } else { + configurations.put(ALLOW_KEY, new ArrayList<>(consumerNames)); + } + } + private KeyAuthCredential mergeExistedConfig(KeyAuthCredential keyAuthCredential, Map consumerConfig) { KeyAuthCredential existedCredential = parseCredential(consumerConfig); @@ -230,7 +249,7 @@ private static KeyAuthCredential parseCredential(Map consumerMap return null; } - Object keyObj = MapUtils.getObject(consumerMap, KEY_KEY); + Object keyObj = MapUtils.getObject(consumerMap, KEYS_KEY); if (!(keyObj instanceof List keyList) || keyList.isEmpty()) { return null; } diff --git a/backend/sdk/src/test/java/com/alibaba/higress/sdk/service/consumer/KeyAuthCredentialHandlerTest.java b/backend/sdk/src/test/java/com/alibaba/higress/sdk/service/consumer/KeyAuthCredentialHandlerTest.java index 0a9e7978..d0a403ff 100644 --- a/backend/sdk/src/test/java/com/alibaba/higress/sdk/service/consumer/KeyAuthCredentialHandlerTest.java +++ b/backend/sdk/src/test/java/com/alibaba/higress/sdk/service/consumer/KeyAuthCredentialHandlerTest.java @@ -171,7 +171,7 @@ public void saveConsumerTestFromNothingBearer() { Assertions.assertEquals(1, consumers.size()); Map consumerMap = consumers.get(0); Assertions.assertEquals("zhangsan", consumerMap.get("name")); - Assertions.assertEquals(List.of("Authorization"), consumerMap.get("key")); + Assertions.assertEquals(List.of("Authorization"), consumerMap.get("keys")); Assertions.assertEquals(true, consumerMap.get("in_header")); Assertions.assertNotEquals(Boolean.TRUE, consumerMap.get("in_query")); Assertions.assertEquals("Bearer sk-123456", consumerMap.get("credential")); @@ -191,7 +191,7 @@ public void saveConsumerTestBadConsumersHeader() { Assertions.assertEquals(1, consumers.size()); Map consumerMap = consumers.get(0); Assertions.assertEquals("zhangsan", consumerMap.get("name")); - Assertions.assertEquals(List.of("X-API-KEY"), consumerMap.get("key")); + Assertions.assertEquals(List.of("X-API-KEY"), consumerMap.get("keys")); Assertions.assertEquals(true, consumerMap.get("in_header")); Assertions.assertNotEquals(Boolean.TRUE, consumerMap.get("in_query")); Assertions.assertEquals("sk-123456", consumerMap.get("credential")); @@ -211,7 +211,7 @@ public void saveConsumerTestUpdateQuery() { Assertions.assertEquals(1, consumers.size()); Map consumerMap = consumers.get(0); Assertions.assertEquals("zhangsan", consumerMap.get("name")); - Assertions.assertEquals(List.of("token"), consumerMap.get("key")); + Assertions.assertEquals(List.of("token"), consumerMap.get("keys")); Assertions.assertEquals(true, consumerMap.get("in_query")); Assertions.assertNotEquals(Boolean.TRUE, consumerMap.get("in_header")); Assertions.assertEquals("sk-123456", consumerMap.get("credential")); @@ -230,7 +230,7 @@ public void saveConsumerTestNoUpdateEmptyData() { Assertions.assertEquals(1, consumers.size()); Map consumerMap = consumers.get(0); Assertions.assertEquals("zhangsan", consumerMap.get("name")); - Assertions.assertEquals(List.of("Authorization"), consumerMap.get("key")); + Assertions.assertEquals(List.of("Authorization"), consumerMap.get("keys")); Assertions.assertEquals(true, consumerMap.get("in_header")); Assertions.assertNotEquals(Boolean.TRUE, consumerMap.get("in_query")); Assertions.assertEquals("Bearer sk-123456", consumerMap.get("credential")); @@ -250,7 +250,7 @@ public void saveConsumerTestNoUpdatePartialData() { Assertions.assertEquals(1, consumers.size()); Map consumerMap = consumers.get(0); Assertions.assertEquals("zhangsan", consumerMap.get("name")); - Assertions.assertEquals(List.of("X-API-KEY"), consumerMap.get("key")); + Assertions.assertEquals(List.of("X-API-KEY"), consumerMap.get("keys")); Assertions.assertEquals(true, consumerMap.get("in_header")); Assertions.assertNotEquals(Boolean.TRUE, consumerMap.get("in_query")); Assertions.assertEquals("sk-123456", consumerMap.get("credential")); @@ -313,7 +313,7 @@ public void deleteConsumerTestFoundOnce() { Assertions.assertEquals(1, consumers.size()); Map consumerMap = consumers.get(0); Assertions.assertEquals("lisi", consumerMap.get("name")); - Assertions.assertEquals(List.of("Authorization"), consumerMap.get("key")); + Assertions.assertEquals(List.of("Authorization"), consumerMap.get("keys")); Assertions.assertEquals(true, consumerMap.get("in_header")); Assertions.assertNotEquals(Boolean.TRUE, consumerMap.get("in_query")); Assertions.assertEquals("Bearer sk-123456", consumerMap.get("credential")); @@ -333,7 +333,7 @@ public void deleteConsumerTestFoundTwice() { Assertions.assertEquals(1, consumers.size()); Map consumerMap = consumers.get(0); Assertions.assertEquals("lisi", consumerMap.get("name")); - Assertions.assertEquals(List.of("Authorization"), consumerMap.get("key")); + Assertions.assertEquals(List.of("Authorization"), consumerMap.get("keys")); Assertions.assertEquals(true, consumerMap.get("in_header")); Assertions.assertNotEquals(Boolean.TRUE, consumerMap.get("in_query")); Assertions.assertEquals("Bearer sk-123456", consumerMap.get("credential")); @@ -358,7 +358,7 @@ private void addConsumer(WasmPluginInstance instance, String name, boolean inHea (List>)configurations.computeIfAbsent("consumers", k -> new ArrayList<>()); Map consumer = new HashMap<>(); consumer.put("name", name); - consumer.put("key", List.of(key)); + consumer.put("keys", List.of(key)); consumer.put("credential", credential); if (inHeader) { consumer.put("in_header", true);