This is an automated email from the ASF dual-hosted git repository.
Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git
The following commit(s) were added to refs/heads/master by this push:
new 91bd8d0c18 [type:refactor] enable external ingress adapters for Motan
asset handoff (#7498)
91bd8d0c18 is described below
commit 91bd8d0c18fae8a1b297a3a752ab3282643bc7f9
Author: cloudbase-ai <[email protected]>
AuthorDate: Fri Oct 9 12:06:07 2026 +0800
[type:refactor] enable external ingress adapters for Motan asset handoff
(#7498)
* chore: remove stale Motan distribution ownership metadata
Signed-off-by: cloudbase-ai <[email protected]>
* feat: support external ingress plugin definitions without core coupling
Signed-off-by: cloudbase-ai <[email protected]>
---------
Signed-off-by: cloudbase-ai <[email protected]>
Co-authored-by: cloudbase-ai
<[email protected]>
---
.../src/main/release-docs/LICENSE | 4 -
.../main/release-docs/licenses/LICENSE-motan.txt | 16 ----
.../apache/shenyu/k8s/parser/IngressParser.java | 45 +++++++++-
.../shenyu/k8s/parser/IngressPluginDefinition.java | 99 ++++++++++++++++++++++
.../shenyu/k8s/reconciler/IngressReconciler.java | 40 +++++++--
.../apache/shenyu/k8s/IngressReconcilerTest.java | 81 ++++++++++++++++++
.../shenyu/k8s/parser/IngressParserTest.java | 57 +++++++++++++
.../k8s/IngressControllerConfiguration.java | 11 ++-
8 files changed, 322 insertions(+), 31 deletions(-)
diff --git a/shenyu-dist/shenyu-bootstrap-dist/src/main/release-docs/LICENSE
b/shenyu-dist/shenyu-bootstrap-dist/src/main/release-docs/LICENSE
index ae16c477a8..11374b4279 100644
--- a/shenyu-dist/shenyu-bootstrap-dist/src/main/release-docs/LICENSE
+++ b/shenyu-dist/shenyu-bootstrap-dist/src/main/release-docs/LICENSE
@@ -635,10 +635,6 @@ The text of each license is the standard Apache 2.0
license.
jsonschema-module-jackson 4.38.0:
https://github.com/victools/jsonschema-generator, Apache 2.0
jsonschema-module-swagger-2 4.38.0:
https://github.com/victools/jsonschema-generator, Apache 2.0
json-schema-validator 2.0.0:
https://github.com/networknt/json-schema-validator, Apache 2.0
- motan-core 1.2.1: https://github.com/weibocom/motan, Apache 2.0
- motan-registry-zookeeper 1.2.1: https://github.com/weibocom/motan, Apache
2.0
- motan-springsupport 1.2.1: https://github.com/weibocom/motan, Apache 2.0
- motan-transport-netty4 1.2.1: https://github.com/weibocom/motan, Apache 2.0
swagger-annotations-jakarta 2.2.38:
https://github.com/swagger-api/swagger-core, Apache 2.0
dubbo-dependencies-zookeeper 3.2.14: https://github.com/apache/dubbo,
Apache 2.0
kafka-clients 3.9.2: https://kafka.apache.org, Apache 2.0
diff --git
a/shenyu-dist/shenyu-bootstrap-dist/src/main/release-docs/licenses/LICENSE-motan.txt
b/shenyu-dist/shenyu-bootstrap-dist/src/main/release-docs/licenses/LICENSE-motan.txt
deleted file mode 100644
index 6ebeddc98d..0000000000
---
a/shenyu-dist/shenyu-bootstrap-dist/src/main/release-docs/licenses/LICENSE-motan.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-motan License
----------------
-
-Copyright 2009-2016 Weibo, Inc.
-
-All files licensed under the Apache License, Version 2.0 (the "License");
-you may not use these files 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.
diff --git
a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/IngressParser.java
b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/IngressParser.java
index c4cdbcc663..40ac04b8ec 100644
---
a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/IngressParser.java
+++
b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/IngressParser.java
@@ -32,9 +32,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
/**
* Parser of Ingress.
@@ -47,6 +49,8 @@ public class IngressParser implements
K8sResourceListParser<V1Ingress> {
private final Lister<V1Endpoints> endpointsLister;
+ private final List<IngressPluginDefinition> pluginDefinitions;
+
/**
* IngressParser Constructor.
*
@@ -54,8 +58,21 @@ public class IngressParser implements
K8sResourceListParser<V1Ingress> {
* @param endpointsInformer endpointsInformer
*/
public IngressParser(final SharedIndexInformer<V1Service> serviceInformer,
final SharedIndexInformer<V1Endpoints> endpointsInformer) {
+ this(serviceInformer, endpointsInformer, Collections.emptyList());
+ }
+
+ /**
+ * IngressParser Constructor.
+ *
+ * @param serviceInformer serviceInformer
+ * @param endpointsInformer endpointsInformer
+ * @param pluginDefinitions external plugin definitions
+ */
+ public IngressParser(final SharedIndexInformer<V1Service> serviceInformer,
final SharedIndexInformer<V1Endpoints> endpointsInformer,
+ final List<IngressPluginDefinition>
pluginDefinitions) {
this.serviceLister = new Lister<>(serviceInformer.getIndexer());
this.endpointsLister = new Lister<>(endpointsInformer.getIndexer());
+ this.pluginDefinitions = Objects.isNull(pluginDefinitions) ?
Collections.emptyList() : new ArrayList<>(pluginDefinitions);
}
/**
@@ -73,11 +90,14 @@ public class IngressParser implements
K8sResourceListParser<V1Ingress> {
boolean brpcEnabled = getBooleanAnnotation(ingress,
IngressConstants.PLUGIN_BRPC_ENABLED);
boolean grpcEnabled = getBooleanAnnotation(ingress,
IngressConstants.PLUGIN_GRPC_ENABLED);
boolean sofaEnabled = getBooleanAnnotation(ingress,
IngressConstants.PLUGIN_SOFA_ENABLED);
+ Optional<IngressPluginDefinition> pluginDefinition =
findPluginDefinition(ingress);
- if (!dubboEnabled && !webSocketEnabled && !brpcEnabled && !grpcEnabled
&& !sofaEnabled) {
+ if (!dubboEnabled && !webSocketEnabled && !brpcEnabled && !grpcEnabled
&& !sofaEnabled && !pluginDefinition.isPresent()) {
contextPathParse(ingress, shenyuMemoryConfigList, coreV1Api);
}
- if (dubboEnabled) {
+ if (pluginDefinition.isPresent()) {
+ shenyuMemoryConfigList.add(pluginDefinition.get().parse(ingress,
coreV1Api, serviceLister, endpointsLister));
+ } else if (dubboEnabled) {
DubboIngressParser dubboIngressParser = new
DubboIngressParser(serviceLister, endpointsLister);
shenyuMemoryConfigList.add(dubboIngressParser.parse(ingress,
coreV1Api));
} else if (webSocketEnabled) {
@@ -96,6 +116,27 @@ public class IngressParser implements
K8sResourceListParser<V1Ingress> {
return shenyuMemoryConfigList;
}
+ /**
+ * Find external plugin definition for the ingress.
+ *
+ * @param ingress ingress
+ * @return first matching definition
+ */
+ public Optional<IngressPluginDefinition> findPluginDefinition(final
V1Ingress ingress) {
+ return pluginDefinitions.stream().filter(definition ->
definition.matchesIngress(ingress)).findFirst();
+ }
+
+ /**
+ * Find external plugin-owned metadata paths for the ingress.
+ *
+ * @param definition plugin definition
+ * @param ingress ingress
+ * @return metadata paths owned by the definition
+ */
+ public List<String> findMetadataPaths(final IngressPluginDefinition
definition, final V1Ingress ingress) {
+ return definition.metadataPaths(ingress, serviceLister,
endpointsLister);
+ }
+
private boolean getBooleanAnnotation(final V1Ingress ingress, final String
annotationKey) {
Map<String, String> annotations =
Objects.isNull(ingress.getMetadata()) ? null :
ingress.getMetadata().getAnnotations();
String annotationValue =
MapUtils.emptyIfNull(annotations).get(annotationKey);
diff --git
a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/IngressPluginDefinition.java
b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/IngressPluginDefinition.java
new file mode 100644
index 0000000000..97d5ed4312
--- /dev/null
+++
b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/IngressPluginDefinition.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.k8s.parser;
+
+import io.kubernetes.client.extended.controller.reconciler.Request;
+import io.kubernetes.client.informer.cache.Lister;
+import io.kubernetes.client.openapi.apis.CoreV1Api;
+import io.kubernetes.client.openapi.models.V1Endpoints;
+import io.kubernetes.client.openapi.models.V1Ingress;
+import io.kubernetes.client.openapi.models.V1Service;
+import org.apache.shenyu.common.dto.PluginData;
+import org.apache.shenyu.k8s.common.ShenyuMemoryConfig;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * External ingress plugin definition.
+ *
+ * <p>When more than one external definition matches an ingress, the first
+ * definition in the injected list wins. Spring wiring uses ordered stream
+ * semantics, so extensions can use {@code @Order} to make overlap
deterministic.
+ */
+public interface IngressPluginDefinition {
+
+ /**
+ * Whether this definition owns the ingress.
+ *
+ * @param ingress ingress
+ * @return true when this definition owns the ingress
+ */
+ boolean matchesIngress(V1Ingress ingress);
+
+ /**
+ * Plugin name used for selector cache and repository operations.
+ *
+ * @return plugin name
+ */
+ String pluginName();
+
+ /**
+ * Metadata path used when deleting metadata for selectors.
+ *
+ * @param ingress ingress
+ * @return metadata path, or empty when no metadata deletion path is needed
+ */
+ String contextPath(V1Ingress ingress);
+
+ /**
+ * Metadata paths owned by this ingress definition.
+ *
+ * @param ingress ingress
+ * @param serviceLister service lister
+ * @param endpointsLister endpoints lister
+ * @return metadata paths owned by this ingress
+ */
+ default List<String> metadataPaths(final V1Ingress ingress, final
Lister<V1Service> serviceLister,
+ final Lister<V1Endpoints>
endpointsLister) {
+ String path = contextPath(ingress);
+ return Objects.isNull(path) || path.isEmpty() ?
Collections.emptyList() : Collections.singletonList(path);
+ }
+
+ /**
+ * Build plugin data for this ingress.
+ *
+ * @param ingress ingress
+ * @param request reconcile request
+ * @param endpointsLister endpoints lister
+ * @return plugin data
+ */
+ PluginData pluginData(V1Ingress ingress, Request request,
Lister<V1Endpoints> endpointsLister);
+
+ /**
+ * Parse ingress config for this plugin.
+ *
+ * @param ingress ingress
+ * @param coreV1Api core v1 api
+ * @param serviceLister service lister
+ * @param endpointsLister endpoints lister
+ * @return memory config
+ */
+ ShenyuMemoryConfig parse(V1Ingress ingress, CoreV1Api coreV1Api,
Lister<V1Service> serviceLister, Lister<V1Endpoints> endpointsLister);
+}
diff --git
a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/reconciler/IngressReconciler.java
b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/reconciler/IngressReconciler.java
index 7d2a91bec8..fb51ab847c 100644
---
a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/reconciler/IngressReconciler.java
+++
b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/reconciler/IngressReconciler.java
@@ -60,6 +60,7 @@ import org.apache.shenyu.k8s.common.IngressConstants;
import org.apache.shenyu.k8s.common.IngressUtils;
import org.apache.shenyu.k8s.common.ServiceIngressRelation;
import org.apache.shenyu.k8s.common.ShenyuMemoryConfig;
+import org.apache.shenyu.k8s.parser.IngressPluginDefinition;
import org.apache.shenyu.k8s.parser.IngressParser;
import org.apache.shenyu.k8s.repository.ShenyuCacheRepository;
import org.slf4j.Logger;
@@ -70,6 +71,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -166,7 +168,7 @@ public class IngressReconciler implements Reconciler {
return new Result(false);
}
Map<String, String> annotations = getAnnotations(v1Ingress);
- enablePluginsBasedOnAnnotations(annotations, request);
+ enablePluginsBasedOnAnnotations(v1Ingress, annotations, request);
if (!checkIngressClass(v1Ingress)) {
LOG.info("IngressClass is not match {}", request);
@@ -208,8 +210,14 @@ public class IngressReconciler implements Reconciler {
return new Result(false);
}
- private void enablePluginsBasedOnAnnotations(final Map<String, String>
annotations, final Request request) {
- if
(Objects.equals(annotations.get(IngressConstants.PLUGIN_DUBBO_ENABLED),
"true")) {
+ private void enablePluginsBasedOnAnnotations(final V1Ingress ingress,
final Map<String, String> annotations, final Request request) {
+ Optional<IngressPluginDefinition> pluginDefinition =
ingressParser.findPluginDefinition(ingress);
+ if (pluginDefinition.isPresent()) {
+ PluginData pluginData = pluginDefinition.get().pluginData(ingress,
request, ingressParser.getEndpointsLister());
+ if (Objects.nonNull(pluginData)) {
+ shenyuCacheRepository.saveOrUpdatePluginData(pluginData);
+ }
+ } else if
(Objects.equals(annotations.get(IngressConstants.PLUGIN_DUBBO_ENABLED),
"true")) {
String zookeeperUrl = getZookeeperUrl(annotations, request);
enablePlugin(shenyuCacheRepository, PluginEnum.DUBBO,
zookeeperUrl);
} else if
(Objects.equals(annotations.get(IngressConstants.PLUGIN_WEB_SOCKET_ENABLED),
"true")) {
@@ -225,7 +233,12 @@ public class IngressReconciler implements Reconciler {
private void doDeleteConfigByIngress(final Request request, final
V1Ingress oldIngress) {
final Map<String, String> annotations = getAnnotations(oldIngress);
List<String> selectorList = new ArrayList<>();
- if
(Objects.equals(annotations.get(IngressConstants.PLUGIN_DUBBO_ENABLED),
"true")) {
+ Optional<IngressPluginDefinition> pluginDefinition =
ingressParser.findPluginDefinition(oldIngress);
+ if (pluginDefinition.isPresent()) {
+ List<String> metadataPaths =
ingressParser.findMetadataPaths(pluginDefinition.get(), oldIngress);
+ selectorList = deleteSelectorByIngressName(request.getNamespace(),
request.getName(), pluginDefinition.get().pluginName(),
+ metadataPaths);
+ } else if
(Objects.equals(annotations.get(IngressConstants.PLUGIN_DUBBO_ENABLED),
"true")) {
selectorList = deleteSelectorByIngressName(request.getNamespace(),
request.getName(), PluginEnum.DUBBO.getName(),
annotations.get(IngressConstants.PLUGIN_DUBBO_CONTEXT_PATH));
} else if
(Objects.equals(annotations.get(IngressConstants.PLUGIN_WEB_SOCKET_ENABLED),
"true")) {
@@ -239,7 +252,9 @@ public class IngressReconciler implements Reconciler {
selectorList = deleteSelectorByIngressName(request.getNamespace(),
request.getName(), PluginEnum.DIVIDE.getName(), "");
}
if (Objects.nonNull(selectorList) && !selectorList.isEmpty()) {
- if
(Objects.equals(annotations.get(IngressConstants.PLUGIN_DUBBO_ENABLED),
"true")) {
+ if (pluginDefinition.isPresent()) {
+
IngressSelectorCache.getInstance().remove(request.getNamespace(),
request.getName(), pluginDefinition.get().pluginName());
+ } else if
(Objects.equals(annotations.get(IngressConstants.PLUGIN_DUBBO_ENABLED),
"true")) {
IngressSelectorCache.getInstance().remove(request.getNamespace(),
request.getName(), PluginEnum.DUBBO.getName());
} else if
(Objects.equals(annotations.get(IngressConstants.PLUGIN_WEB_SOCKET_ENABLED),
"true")) {
IngressSelectorCache.getInstance().remove(request.getNamespace(),
request.getName(), PluginEnum.WEB_SOCKET.getName());
@@ -369,18 +384,27 @@ public class IngressReconciler implements Reconciler {
private List<String> deleteSelectorByIngressName(final String namespace,
final String name,
final String pluginName,
final String path) {
+ List<String> metadataPaths = Objects.isNull(path) || path.isEmpty() ?
Collections.emptyList() : Collections.singletonList(path);
+ return deleteSelectorByIngressName(namespace, name, pluginName,
metadataPaths);
+ }
+
+ private List<String> deleteSelectorByIngressName(final String namespace,
final String name,
+ final String pluginName,
final List<String> paths) {
final List<String> selectorList =
IngressSelectorCache.getInstance().get(namespace, name, pluginName);
if (Objects.nonNull(selectorList) && !selectorList.isEmpty()) {
+ Set<String> metadataPaths = new
LinkedHashSet<>(CollectionUtils.emptyIfNull(paths));
for (String selectorId : selectorList) {
List<RuleData> ruleList =
shenyuCacheRepository.findRuleDataList(selectorId);
// To avoid ConcurrentModificationException, copy the ruleId
to list
List<String> ruleIdList = new ArrayList<>();
ruleList.forEach(rule -> ruleIdList.add(rule.getId()));
- for (String id : ruleIdList) {
+ for (String path : metadataPaths) {
MetaData metaData =
shenyuCacheRepository.findMetaData(path);
if (Objects.nonNull(metaData)) {
shenyuCacheRepository.deleteMetaData(metaData);
}
+ }
+ for (String id : ruleIdList) {
shenyuCacheRepository.deleteRuleData(pluginName,
selectorId, id);
}
shenyuCacheRepository.deleteSelectorData(pluginName,
selectorId);
@@ -714,6 +738,10 @@ public class IngressReconciler implements Reconciler {
}
private String getPluginName(final V1Ingress ingress) {
+ Optional<IngressPluginDefinition> pluginDefinition =
ingressParser.findPluginDefinition(ingress);
+ if (pluginDefinition.isPresent()) {
+ return pluginDefinition.get().pluginName();
+ }
Map<String, String> annotations = getAnnotations(ingress);
String pluginName;
String pluginDubboEnabled =
annotations.get(IngressConstants.PLUGIN_DUBBO_ENABLED);
diff --git
a/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/IngressReconcilerTest.java
b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/IngressReconcilerTest.java
index 15bd299f13..2471a635d2 100644
---
a/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/IngressReconcilerTest.java
+++
b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/IngressReconcilerTest.java
@@ -21,7 +21,9 @@ import
io.kubernetes.client.extended.controller.reconciler.Request;
import io.kubernetes.client.extended.controller.reconciler.Result;
import io.kubernetes.client.informer.SharedIndexInformer;
import io.kubernetes.client.informer.cache.Indexer;
+import io.kubernetes.client.informer.cache.Lister;
import io.kubernetes.client.openapi.ApiClient;
+import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.models.CoreV1EndpointPort;
import io.kubernetes.client.openapi.models.V1EndpointAddress;
import io.kubernetes.client.openapi.models.V1EndpointSubsetBuilder;
@@ -36,13 +38,18 @@ import io.kubernetes.client.openapi.models.V1Secret;
import io.kubernetes.client.openapi.models.V1Service;
import org.apache.shenyu.common.config.ssl.ShenyuSniAsyncMapping;
import org.apache.shenyu.common.dto.PluginData;
+import org.apache.shenyu.common.dto.RuleData;
import org.apache.shenyu.common.dto.SelectorData;
import org.apache.shenyu.common.enums.PluginEnum;
+import org.apache.shenyu.common.enums.PluginRoleEnum;
import org.apache.shenyu.k8s.cache.IngressCache;
import org.apache.shenyu.k8s.cache.IngressSelectorCache;
import org.apache.shenyu.k8s.cache.ServiceIngressCache;
+import org.apache.shenyu.k8s.common.IngressConfiguration;
import org.apache.shenyu.k8s.common.IngressConstants;
import org.apache.shenyu.k8s.common.ServiceIngressRelation;
+import org.apache.shenyu.k8s.common.ShenyuMemoryConfig;
+import org.apache.shenyu.k8s.parser.IngressPluginDefinition;
import org.apache.shenyu.k8s.parser.IngressParser;
import org.apache.shenyu.k8s.reconciler.IngressReconciler;
import org.apache.shenyu.k8s.repository.ShenyuCacheRepository;
@@ -233,6 +240,33 @@ public final class IngressReconcilerTest {
assertTrue(ServiceIngressCache.getInstance().getIngressName(NAMESPACE,
SERVICE_NAME).isEmpty());
}
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testExternalPluginReconcileAndDeleteUseExternalPluginName() {
+ IngressParser parser = new IngressParser(mockInformer(),
mockInformer(), Collections.singletonList(new FakeIngressPluginDefinition()));
+ ingressReconciler = new IngressReconciler(mockIngressInformer(),
mockInformer(), shenyuCacheRepository,
+ new ShenyuSniAsyncMapping(), parser, mock(ApiClient.class));
+ Map<String, String> annotations = shenyuAnnotations(new HashMap<>());
+ annotations.put("shenyu.apache.org/plugin-fake-enabled", "true");
+ mockIngress(annotations, "/fake", "Exact", null);
+
+ Result result = ingressReconciler.reconcile(new Request(NAMESPACE,
INGRESS_NAME));
+
+ assertEquals(new Result(false), result);
+ ArgumentCaptor<PluginData> pluginCaptor =
ArgumentCaptor.forClass(PluginData.class);
+ verify(shenyuCacheRepository,
atLeastOnce()).saveOrUpdatePluginData(pluginCaptor.capture());
+ assertTrue(pluginCaptor.getAllValues().stream().anyMatch(pluginData ->
"fake".equals(pluginData.getName())));
+ assertNotNull(IngressSelectorCache.getInstance().get(NAMESPACE,
INGRESS_NAME, "fake"));
+ assertNull(IngressSelectorCache.getInstance().get(NAMESPACE,
INGRESS_NAME, PluginEnum.DIVIDE.getName()));
+
+
when(shenyuCacheRepository.findRuleDataList(anyString())).thenReturn(Collections.singletonList(RuleData.builder().id("rule-id").build()));
+ when(ingressIndexer.getByKey(NAMESPACE + "/" +
INGRESS_NAME)).thenReturn(null);
+ ingressReconciler.reconcile(new Request(NAMESPACE, INGRESS_NAME));
+
+ verify(shenyuCacheRepository,
atLeastOnce()).deleteSelectorData(eq("fake"), anyString());
+ assertNull(IngressSelectorCache.getInstance().get(NAMESPACE,
INGRESS_NAME, "fake"));
+ }
+
private Map<String, String> shenyuAnnotations(final Map<String, String>
annotations) {
Map<String, String> allAnnotations = new HashMap<>();
allAnnotations.put(IngressConstants.K8S_INGRESS_CLASS_ANNOTATION_KEY,
IngressConstants.SHENYU_INGRESS_CLASS);
@@ -258,4 +292,51 @@ public final class IngressReconcilerTest {
.build();
when(ingressIndexer.getByKey(NAMESPACE + "/" +
INGRESS_NAME)).thenReturn(ingress);
}
+
+ @SuppressWarnings("unchecked")
+ private SharedIndexInformer<V1Ingress> mockIngressInformer() {
+ SharedIndexInformer<V1Ingress> ingressInformer =
mock(SharedIndexInformer.class);
+ ingressIndexer = mock(Indexer.class);
+ when(ingressInformer.getIndexer()).thenReturn(ingressIndexer);
+ return ingressInformer;
+ }
+
+ @SuppressWarnings("unchecked")
+ private SharedIndexInformer mockInformer() {
+ SharedIndexInformer informer = mock(SharedIndexInformer.class);
+ when(informer.getIndexer()).thenReturn(mock(Indexer.class));
+ return informer;
+ }
+
+ private static final class FakeIngressPluginDefinition implements
IngressPluginDefinition {
+
+ @Override
+ public boolean matchesIngress(final V1Ingress ingress) {
+ return
"true".equals(ingress.getMetadata().getAnnotations().get("shenyu.apache.org/plugin-fake-enabled"));
+ }
+
+ @Override
+ public String pluginName() {
+ return "fake";
+ }
+
+ @Override
+ public String contextPath(final V1Ingress ingress) {
+ return "/fake";
+ }
+
+ @Override
+ public PluginData pluginData(final V1Ingress ingress, final Request
request, final Lister<V1Endpoints> endpointsLister) {
+ return
PluginData.builder().id("9000").name("fake").config("{}").role(PluginRoleEnum.SYS.getName()).enabled(true).sort(9000).build();
+ }
+
+ @Override
+ public ShenyuMemoryConfig parse(final V1Ingress ingress, final
CoreV1Api coreV1Api,
+ final Lister<V1Service> serviceLister,
final Lister<V1Endpoints> endpointsLister) {
+ ShenyuMemoryConfig config = new ShenyuMemoryConfig();
+ SelectorData selectorData =
SelectorData.builder().pluginName("fake").name("fake").enabled(true).build();
+ config.setRouteConfigList(Collections.singletonList(new
IngressConfiguration(selectorData, Collections.emptyList(),
Collections.emptyList())));
+ return config;
+ }
+ }
}
diff --git
a/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/IngressParserTest.java
b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/IngressParserTest.java
index 707a087359..1ce651bdb8 100644
---
a/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/IngressParserTest.java
+++
b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/IngressParserTest.java
@@ -17,8 +17,10 @@
package org.apache.shenyu.k8s.parser;
+import io.kubernetes.client.extended.controller.reconciler.Request;
import io.kubernetes.client.informer.SharedIndexInformer;
import io.kubernetes.client.informer.cache.Indexer;
+import io.kubernetes.client.informer.cache.Lister;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.models.V1EndpointAddress;
import io.kubernetes.client.openapi.models.V1EndpointSubsetBuilder;
@@ -30,8 +32,10 @@ import io.kubernetes.client.openapi.models.V1IngressBuilder;
import io.kubernetes.client.openapi.models.V1IngressRule;
import io.kubernetes.client.openapi.models.V1IngressRuleBuilder;
import io.kubernetes.client.openapi.models.V1Service;
+import org.apache.shenyu.common.dto.PluginData;
import org.apache.shenyu.common.dto.SelectorData;
import org.apache.shenyu.common.enums.PluginEnum;
+import org.apache.shenyu.common.enums.PluginRoleEnum;
import org.apache.shenyu.k8s.common.IngressConfiguration;
import org.apache.shenyu.k8s.common.IngressConstants;
import org.apache.shenyu.k8s.common.ShenyuMemoryConfig;
@@ -39,6 +43,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -145,6 +150,19 @@ public class IngressParserTest {
assertEquals(Arrays.asList(PluginEnum.SOFA.getName()),
pluginNames(configs));
}
+ @Test
+ public void testDispatchToExternalDefinitionWhenMatched() {
+ ingressParser = new IngressParser(mockInformer(), mockInformer(),
Collections.singletonList(new FakeIngressPluginDefinition()));
+ Map<String, String> annotations = new HashMap<>();
+ annotations.put("shenyu.apache.org/plugin-fake-enabled", "true");
+ annotations.put(IngressConstants.PLUGIN_DUBBO_ENABLED, "true");
+
+ List<ShenyuMemoryConfig> configs = parse(annotations, "Exact");
+
+ assertEquals(1, configs.size());
+ assertEquals(Collections.singletonList("fake"), pluginNames(configs));
+ }
+
@Test
public void testDispatchToDivideWhenAllPluginAnnotationsDisabled() {
Map<String, String> annotations = new HashMap<>();
@@ -192,4 +210,43 @@ public class IngressParserTest {
.map(SelectorData::getPluginName)
.collect(Collectors.toList());
}
+
+ @SuppressWarnings("unchecked")
+ private SharedIndexInformer mockInformer() {
+ SharedIndexInformer informer = mock(SharedIndexInformer.class);
+ when(informer.getIndexer()).thenReturn(mock(Indexer.class));
+ return informer;
+ }
+
+ private static final class FakeIngressPluginDefinition implements
IngressPluginDefinition {
+
+ @Override
+ public boolean matchesIngress(final V1Ingress ingress) {
+ return
"true".equals(ingress.getMetadata().getAnnotations().get("shenyu.apache.org/plugin-fake-enabled"));
+ }
+
+ @Override
+ public String pluginName() {
+ return "fake";
+ }
+
+ @Override
+ public String contextPath(final V1Ingress ingress) {
+ return "/fake";
+ }
+
+ @Override
+ public PluginData pluginData(final V1Ingress ingress, final Request
request, final Lister<V1Endpoints> endpointsLister) {
+ return
PluginData.builder().id("9000").name("fake").config("{}").role(PluginRoleEnum.SYS.getName()).enabled(true).sort(9000).build();
+ }
+
+ @Override
+ public ShenyuMemoryConfig parse(final V1Ingress ingress, final
CoreV1Api coreV1Api,
+ final Lister<V1Service> serviceLister,
final Lister<V1Endpoints> endpointsLister) {
+ ShenyuMemoryConfig config = new ShenyuMemoryConfig();
+ SelectorData selectorData =
SelectorData.builder().pluginName("fake").name("fake").enabled(true).build();
+ config.setRouteConfigList(Collections.singletonList(new
IngressConfiguration(selectorData, Collections.emptyList(),
Collections.emptyList())));
+ return config;
+ }
+ }
}
diff --git
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-k8s/src/main/java/org/apache/shenyu/springboot/starter/k8s/IngressControllerConfiguration.java
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-k8s/src/main/java/org/apache/shenyu/springboot/starter/k8s/IngressControllerConfiguration.java
index fe16e76d8b..7548b9d524 100644
---
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-k8s/src/main/java/org/apache/shenyu/springboot/starter/k8s/IngressControllerConfiguration.java
+++
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-k8s/src/main/java/org/apache/shenyu/springboot/starter/k8s/IngressControllerConfiguration.java
@@ -39,6 +39,7 @@ import org.apache.commons.collections4.MapUtils;
import org.apache.shenyu.common.config.NettyHttpProperties;
import org.apache.shenyu.common.config.ssl.ShenyuSniAsyncMapping;
import org.apache.shenyu.common.exception.ShenyuException;
+import org.apache.shenyu.k8s.parser.IngressPluginDefinition;
import org.apache.shenyu.k8s.parser.IngressParser;
import org.apache.shenyu.k8s.reconciler.EndpointsReconciler;
import org.apache.shenyu.k8s.reconciler.IngressReconciler;
@@ -56,10 +57,11 @@ import reactor.netty.tcp.TcpSslContextSpec;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.time.Duration;
-
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.stream.Collectors;
import java.util.concurrent.Executors;
/**
@@ -182,11 +184,14 @@ public class IngressControllerConfiguration {
*
* @param serviceInformer serviceInformer
* @param endpointsInformer endpointsInformer
+ * @param pluginDefinitions external plugin definitions
* @return IngressParser
*/
@Bean
- public IngressParser ingressParser(final SharedIndexInformer<V1Service>
serviceInformer, final SharedIndexInformer<V1Endpoints> endpointsInformer) {
- return new IngressParser(serviceInformer, endpointsInformer);
+ public IngressParser ingressParser(final SharedIndexInformer<V1Service>
serviceInformer, final SharedIndexInformer<V1Endpoints> endpointsInformer,
+ final
ObjectProvider<IngressPluginDefinition> pluginDefinitions) {
+ List<IngressPluginDefinition> orderedDefinitions =
pluginDefinitions.orderedStream().collect(Collectors.toList());
+ return new IngressParser(serviceInformer, endpointsInformer,
orderedDefinitions);
}
/**