From 64e476894d1bec6ac8a2c3c64122307f4ab07ea4 Mon Sep 17 00:00:00 2001 From: CH3CHO Date: Thu, 17 Oct 2024 13:17:43 +0800 Subject: [PATCH 1/2] feat: Update AI console function. 1. Update the data models of Consumer, LlmProvider and AiRoute. 2. Return the original tokens instead of masked ones in ConsumersController and LlmProviderController. 3. Fix auth functions in AI route. --- .../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 ++++++- 17 files changed, 249 insertions(+), 74 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; } From 8b10d01f7f21146057f0147031d2345cbc5c250a Mon Sep 17 00:00:00 2001 From: CH3CHO Date: Sat, 19 Oct 2024 20:25:11 +0800 Subject: [PATCH 2/2] fix UT --- .../consumer/KeyAuthCredentialHandlerTest.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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);