diff --git a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/XdsCoreService.java b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/XdsCoreService.java index 39f78b45ac..339e1ec108 100644 --- a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/XdsCoreService.java +++ b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/XdsCoreService.java @@ -45,4 +45,11 @@ public interface XdsCoreService extends BaseService { * @return XdsRoute */ XdsLoadBalanceService getLoadBalanceService(); + + /** + * get XDSFlowControlService + * + * @return XdsFlowControlService + */ + XdsFlowControlService getXdsFlowControlService(); } diff --git a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/XdsFlowControlService.java b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/XdsFlowControlService.java new file mode 100644 index 0000000000..81bd138d72 --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/XdsFlowControlService.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. All rights reserved. + * + * 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 io.sermant.core.service.xds; + +import io.sermant.core.service.xds.entity.XdsHttpFault; +import io.sermant.core.service.xds.entity.XdsInstanceCircuitBreakers; +import io.sermant.core.service.xds.entity.XdsRateLimit; +import io.sermant.core.service.xds.entity.XdsRequestCircuitBreakers; +import io.sermant.core.service.xds.entity.XdsRetryPolicy; + +import java.util.Optional; + +/** + * xDS FlowControl service + * + * @author zhp + * @since 2024-11-27 + **/ +public interface XdsFlowControlService { + /** + * get request circuit breaker information of cluster + * + * @param serviceName service name + * @param clusterName cluster name + * @return circuit breaker rules + */ + Optional getRequestCircuitBreakers(String serviceName, String clusterName); + + /** + * get instance circuit breaker information of cluster + * + * @param serviceName service name + * @param clusterName cluster name + * @return Outlier Detection rules + */ + Optional getInstanceCircuitBreakers(String serviceName, String clusterName); + + /** + * get retry policy of route name + * + * @param serviceName service name + * @param routeName route name + * @return retry policy + */ + Optional getRetryPolicy(String serviceName, String routeName); + + /** + * get rate limit of route name + * + * @param serviceName service name + * @param routeName route name + * @return rate limit rule + */ + Optional getRateLimit(String serviceName, String routeName); + + /** + * get http fault of route name + * + * @param serviceName service name + * @param routeName route name + * @return http fault rule + */ + Optional getHttpFault(String serviceName, String routeName); +} diff --git a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsAbort.java b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsAbort.java index 013340dea7..ceb76e7cdd 100644 --- a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsAbort.java +++ b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsAbort.java @@ -31,7 +31,7 @@ public class XdsAbort { /** * The percentage of requests/ operations/ connections that will be aborted with the error code */ - private float percentage; + private int percentage; public int getHttpStatus() { return httpStatus; @@ -41,11 +41,11 @@ public void setHttpStatus(int httpStatus) { this.httpStatus = httpStatus; } - public float getPercentage() { + public int getPercentage() { return percentage; } - public void setPercentage(float percentage) { + public void setPercentage(int percentage) { this.percentage = percentage; } } diff --git a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsCluster.java b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsCluster.java index 53abe91e3e..9977c24443 100644 --- a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsCluster.java +++ b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsCluster.java @@ -31,6 +31,10 @@ public class XdsCluster { private boolean isLocalityLb; + private XdsRequestCircuitBreakers requestCircuitBreakers; + + private XdsInstanceCircuitBreakers instanceCircuitBreakers; + public String getClusterName() { return clusterName; } @@ -62,4 +66,20 @@ public boolean isLocalityLb() { public void setLocalityLb(boolean localityLb) { isLocalityLb = localityLb; } + + public XdsRequestCircuitBreakers getRequestCircuitBreakers() { + return requestCircuitBreakers; + } + + public void setRequestCircuitBreakers(XdsRequestCircuitBreakers requestCircuitBreakers) { + this.requestCircuitBreakers = requestCircuitBreakers; + } + + public XdsInstanceCircuitBreakers getInstanceCircuitBreakers() { + return instanceCircuitBreakers; + } + + public void setInstanceCircuitBreakers(XdsInstanceCircuitBreakers instanceCircuitBreakers) { + this.instanceCircuitBreakers = instanceCircuitBreakers; + } } diff --git a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsDelay.java b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsDelay.java index 803bae277a..3d63f923d9 100644 --- a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsDelay.java +++ b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsDelay.java @@ -31,7 +31,7 @@ public class XdsDelay { /** * The percentage of requests on which the delay will be injected */ - private float percentage; + private int percentage; public long getFixedDelay() { return fixedDelay; @@ -41,11 +41,11 @@ public void setFixedDelay(long fixedDelay) { this.fixedDelay = fixedDelay; } - public float getPercentage() { + public int getPercentage() { return percentage; } - public void setPercentage(float percentage) { + public void setPercentage(int percentage) { this.percentage = percentage; } } diff --git a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsOutlierDetection.java b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsInstanceCircuitBreakers.java similarity index 94% rename from sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsOutlierDetection.java rename to sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsInstanceCircuitBreakers.java index 450147802e..ac2bdc70c7 100644 --- a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsOutlierDetection.java +++ b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsInstanceCircuitBreakers.java @@ -17,12 +17,13 @@ package io.sermant.core.service.xds.entity; /** - * Xds Outlier Detection information + * Circuit breaker information for downstream instance, The instance has reached the specified number of errors and will + * trigger a circuit breaker and the instance will be removed for a period of time * * @author zhp * @since 2024-11-18 */ -public class XdsOutlierDetection { +public class XdsInstanceCircuitBreakers { /** * Whether to distinguish between local source failures and external errors. When set to true, * it will detect local source failures. diff --git a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsCircuitBreakers.java b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsRequestCircuitBreakers.java similarity index 81% rename from sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsCircuitBreakers.java rename to sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsRequestCircuitBreakers.java index b1e242fc61..199656fff4 100644 --- a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsCircuitBreakers.java +++ b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsRequestCircuitBreakers.java @@ -17,12 +17,13 @@ package io.sermant.core.service.xds.entity; /** - * Xds circuit breaker information + * Circuit breaker information for downstream instance requests, When the number of active requests for an instance + * reaches the specified limit, it will trigger a circuit breaker * * @author zhp * @since 2024-11-18 */ -public class XdsCircuitBreakers { +public class XdsRequestCircuitBreakers { /** * Maximum active request count */ diff --git a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsRoute.java b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsRoute.java index 41ca560a02..77993555ab 100644 --- a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsRoute.java +++ b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsRoute.java @@ -29,6 +29,10 @@ public class XdsRoute { private XdsRouteAction routeAction; + private XdsHttpFault httpFault; + + private XdsRateLimit rateLimit; + public String getName() { return name; } @@ -52,4 +56,20 @@ public XdsRouteAction getRouteAction() { public void setRouteAction(XdsRouteAction routeAction) { this.routeAction = routeAction; } + + public XdsHttpFault getHttpFault() { + return httpFault; + } + + public void setHttpFault(XdsHttpFault httpFault) { + this.httpFault = httpFault; + } + + public XdsRateLimit getRateLimit() { + return rateLimit; + } + + public void setRateLimit(XdsRateLimit rateLimit) { + this.rateLimit = rateLimit; + } } diff --git a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsRouteAction.java b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsRouteAction.java index eb44873448..a9e7486fb2 100644 --- a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsRouteAction.java +++ b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsRouteAction.java @@ -31,6 +31,8 @@ public class XdsRouteAction { private XdsWeightedClusters weightedClusters; + private XdsRetryPolicy retryPolicy; + public String getCluster() { return cluster; } @@ -55,6 +57,14 @@ public void setWeightedClusters(XdsWeightedClusters weightedClusters) { this.weightedClusters = weightedClusters; } + public XdsRetryPolicy getRetryPolicy() { + return retryPolicy; + } + + public void setRetryPolicy(XdsRetryPolicy retryPolicy) { + this.retryPolicy = retryPolicy; + } + /** * xDS WeightedClusters * diff --git a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsServiceCluster.java b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsServiceCluster.java index d072fb99d5..342d116380 100644 --- a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsServiceCluster.java +++ b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/xds/entity/XdsServiceCluster.java @@ -18,6 +18,7 @@ import java.util.Collections; import java.util.Map; +import java.util.Optional; import java.util.Set; /** @@ -104,4 +105,38 @@ public XdsLbPolicy getBaseLbPolicyOfService() { } return xdsCluster.getLbPolicy(); } + + /** + * get XdsRequestCircuitBreakers + * + * @param clusterName cluster name + * @return XdsOutlierDetection + */ + public Optional getRequestCircuitBreakersOfCluster(String clusterName) { + if (clusters == null) { + return Optional.empty(); + } + XdsCluster xdsCluster = clusters.get(clusterName); + if (xdsCluster == null) { + return Optional.empty(); + } + return Optional.ofNullable(xdsCluster.getRequestCircuitBreakers()); + } + + /** + * get XdsInstanceCircuitBreakers + * + * @param clusterName cluster name + * @return XdsOutlierDetection + */ + public Optional getInstanceCircuitBreakersOfCluster(String clusterName) { + if (clusters == null) { + return Optional.empty(); + } + XdsCluster xdsCluster = clusters.get(clusterName); + if (xdsCluster == null) { + return Optional.empty(); + } + return Optional.ofNullable(xdsCluster.getInstanceCircuitBreakers()); + } } diff --git a/sermant-agentcore/sermant-agentcore-core/src/test/java/io/sermant/core/service/xds/entity/XdsAbortTest.java b/sermant-agentcore/sermant-agentcore-core/src/test/java/io/sermant/core/service/xds/entity/XdsAbortTest.java index 5da560ade1..e97fc1e0b1 100644 --- a/sermant-agentcore/sermant-agentcore-core/src/test/java/io/sermant/core/service/xds/entity/XdsAbortTest.java +++ b/sermant-agentcore/sermant-agentcore-core/src/test/java/io/sermant/core/service/xds/entity/XdsAbortTest.java @@ -29,9 +29,9 @@ public class XdsAbortTest { @Test public void testXdsAbort() { XdsAbort abort = new XdsAbort(); - abort.setPercentage(0.1f); + abort.setPercentage(100); abort.setHttpStatus(200); Assert.assertEquals(200, abort.getHttpStatus()); - Assert.assertEquals(0.1f, abort.getPercentage(), 0); + Assert.assertEquals(100, abort.getPercentage(), 0); } } diff --git a/sermant-agentcore/sermant-agentcore-core/src/test/java/io/sermant/core/service/xds/entity/XdsClusterTest.java b/sermant-agentcore/sermant-agentcore-core/src/test/java/io/sermant/core/service/xds/entity/XdsClusterTest.java index cded1d59b4..e7bf88b8c9 100644 --- a/sermant-agentcore/sermant-agentcore-core/src/test/java/io/sermant/core/service/xds/entity/XdsClusterTest.java +++ b/sermant-agentcore/sermant-agentcore-core/src/test/java/io/sermant/core/service/xds/entity/XdsClusterTest.java @@ -33,9 +33,15 @@ public void testXdsCluster() { cluster.setLbPolicy(XdsLbPolicy.RANDOM); cluster.setLocalityLb(true); cluster.setServiceName("serviceA"); + XdsInstanceCircuitBreakers instanceCircuitBreakers = new XdsInstanceCircuitBreakers(); + cluster.setInstanceCircuitBreakers(instanceCircuitBreakers); + XdsRequestCircuitBreakers requestCircuitBreakers = new XdsRequestCircuitBreakers(); + cluster.setRequestCircuitBreakers(requestCircuitBreakers); Assert.assertTrue(cluster.isLocalityLb()); Assert.assertEquals("outbound|8080||serviceA.default.svc.cluster.local", cluster.getClusterName()); Assert.assertEquals("serviceA", cluster.getServiceName()); Assert.assertEquals(XdsLbPolicy.RANDOM, cluster.getLbPolicy()); + Assert.assertEquals(instanceCircuitBreakers, cluster.getInstanceCircuitBreakers()); + Assert.assertEquals(requestCircuitBreakers, cluster.getRequestCircuitBreakers()); } } diff --git a/sermant-agentcore/sermant-agentcore-core/src/test/java/io/sermant/core/service/xds/entity/XdsDelayTest.java b/sermant-agentcore/sermant-agentcore-core/src/test/java/io/sermant/core/service/xds/entity/XdsDelayTest.java index 2463676ef6..47fb845ee0 100644 --- a/sermant-agentcore/sermant-agentcore-core/src/test/java/io/sermant/core/service/xds/entity/XdsDelayTest.java +++ b/sermant-agentcore/sermant-agentcore-core/src/test/java/io/sermant/core/service/xds/entity/XdsDelayTest.java @@ -29,9 +29,9 @@ public class XdsDelayTest { @Test public void testXdsDelay() { XdsDelay delay = new XdsDelay(); - delay.setPercentage(0.1f); + delay.setPercentage(100); delay.setFixedDelay(200L); Assert.assertEquals(200, delay.getFixedDelay()); - Assert.assertEquals(0.1f, delay.getPercentage(), 0); + Assert.assertEquals(100, delay.getPercentage(), 0); } } diff --git a/sermant-agentcore/sermant-agentcore-core/src/test/java/io/sermant/core/service/xds/entity/XdsInstanceCircuitBreakerTest.java b/sermant-agentcore/sermant-agentcore-core/src/test/java/io/sermant/core/service/xds/entity/XdsInstanceCircuitBreakerTest.java new file mode 100644 index 0000000000..01f90cd7dc --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-core/src/test/java/io/sermant/core/service/xds/entity/XdsInstanceCircuitBreakerTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. All rights reserved. + * + * 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 io.sermant.core.service.xds.entity; + +import org.junit.Assert; +import org.junit.Test; + +/** + * XdsInstanceCircuitBreakerTest + * + * @author zhp + * @since 2024-11-21 + **/ +public class XdsInstanceCircuitBreakerTest { + @Test + public void testXdsOutlierDetection() { + XdsInstanceCircuitBreakers xdsInstanceCircuitBreakers = initInstanceCircuitBreakers(); + Assert.assertEquals(1.0f, xdsInstanceCircuitBreakers.getFailurePercentageMinimumHosts(), 0.0f); + Assert.assertEquals(10, xdsInstanceCircuitBreakers.getConsecutiveGatewayFailure(), 0.0f); + Assert.assertEquals(10, xdsInstanceCircuitBreakers.getMaxEjectionPercent(), 0.0f); + Assert.assertEquals(20, xdsInstanceCircuitBreakers.getConsecutiveLocalOriginFailure(), 0.0f); + Assert.assertTrue(xdsInstanceCircuitBreakers.isSplitExternalLocalOriginErrors()); + Assert.assertEquals(1000L, xdsInstanceCircuitBreakers.getInterval()); + Assert.assertEquals(1000L, xdsInstanceCircuitBreakers.getBaseEjectionTime()); + Assert.assertEquals(30, xdsInstanceCircuitBreakers.getConsecutive5xxFailure()); + } + + private XdsInstanceCircuitBreakers initInstanceCircuitBreakers() { + XdsInstanceCircuitBreakers xdsInstanceCircuitBreakers = new XdsInstanceCircuitBreakers(); + xdsInstanceCircuitBreakers.setFailurePercentageMinimumHosts(1.0f); + xdsInstanceCircuitBreakers.setConsecutiveGatewayFailure(10); + xdsInstanceCircuitBreakers.setMaxEjectionPercent(10f); + xdsInstanceCircuitBreakers.setConsecutiveLocalOriginFailure(20); + xdsInstanceCircuitBreakers.setSplitExternalLocalOriginErrors(true); + xdsInstanceCircuitBreakers.setInterval(1000L); + xdsInstanceCircuitBreakers.setBaseEjectionTime(1000L); + xdsInstanceCircuitBreakers.setConsecutive5xxFailure(30); + return xdsInstanceCircuitBreakers; + } +} diff --git a/sermant-agentcore/sermant-agentcore-core/src/test/java/io/sermant/core/service/xds/entity/XdsOutlierDetectionTest.java b/sermant-agentcore/sermant-agentcore-core/src/test/java/io/sermant/core/service/xds/entity/XdsOutlierDetectionTest.java deleted file mode 100644 index c6b74ac343..0000000000 --- a/sermant-agentcore/sermant-agentcore-core/src/test/java/io/sermant/core/service/xds/entity/XdsOutlierDetectionTest.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2024-2024 Sermant Authors. All rights reserved. - * - * 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 io.sermant.core.service.xds.entity; - -import org.junit.Assert; -import org.junit.Test; - -/** - * XdsOutlierDetectionTest - * - * @author zhp - * @since 2024-11-21 - **/ -public class XdsOutlierDetectionTest { - @Test - public void testXdsOutlierDetection() { - XdsOutlierDetection xdsOutlierDetection = initOutlierDetection(); - Assert.assertEquals(1.0f, xdsOutlierDetection.getFailurePercentageMinimumHosts(), 0.0f); - Assert.assertEquals(10, xdsOutlierDetection.getConsecutiveGatewayFailure(), 0.0f); - Assert.assertEquals(10, xdsOutlierDetection.getMaxEjectionPercent(), 0.0f); - Assert.assertEquals(20, xdsOutlierDetection.getConsecutiveLocalOriginFailure(), 0.0f); - Assert.assertTrue(xdsOutlierDetection.isSplitExternalLocalOriginErrors()); - Assert.assertEquals(1000L, xdsOutlierDetection.getInterval()); - Assert.assertEquals(1000L, xdsOutlierDetection.getBaseEjectionTime()); - Assert.assertEquals(30, xdsOutlierDetection.getConsecutive5xxFailure()); - } - - private XdsOutlierDetection initOutlierDetection() { - XdsOutlierDetection xdsOutlierDetection = new XdsOutlierDetection(); - xdsOutlierDetection.setFailurePercentageMinimumHosts(1.0f); - xdsOutlierDetection.setConsecutiveGatewayFailure(10); - xdsOutlierDetection.setMaxEjectionPercent(10f); - xdsOutlierDetection.setConsecutiveLocalOriginFailure(20); - xdsOutlierDetection.setSplitExternalLocalOriginErrors(true); - xdsOutlierDetection.setInterval(1000L); - xdsOutlierDetection.setBaseEjectionTime(1000L); - xdsOutlierDetection.setConsecutive5xxFailure(30); - return xdsOutlierDetection; - } -} diff --git a/sermant-agentcore/sermant-agentcore-core/src/test/java/io/sermant/core/service/xds/entity/XdsCircuitBreakersTest.java b/sermant-agentcore/sermant-agentcore-core/src/test/java/io/sermant/core/service/xds/entity/XdsRequestCircuitBreakersTest.java similarity index 88% rename from sermant-agentcore/sermant-agentcore-core/src/test/java/io/sermant/core/service/xds/entity/XdsCircuitBreakersTest.java rename to sermant-agentcore/sermant-agentcore-core/src/test/java/io/sermant/core/service/xds/entity/XdsRequestCircuitBreakersTest.java index fd9ffb72c6..b35056526a 100644 --- a/sermant-agentcore/sermant-agentcore-core/src/test/java/io/sermant/core/service/xds/entity/XdsCircuitBreakersTest.java +++ b/sermant-agentcore/sermant-agentcore-core/src/test/java/io/sermant/core/service/xds/entity/XdsRequestCircuitBreakersTest.java @@ -25,10 +25,10 @@ * @author zhp * @since 2024-11-21 **/ -public class XdsCircuitBreakersTest { +public class XdsRequestCircuitBreakersTest { @Test public void testXdsCircuitBreakers() { - XdsCircuitBreakers circuitBreakers = new XdsCircuitBreakers(); + XdsRequestCircuitBreakers circuitBreakers = new XdsRequestCircuitBreakers(); circuitBreakers.setMaxRequests(200); Assert.assertEquals(200, circuitBreakers.getMaxRequests()); } diff --git a/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/XdsCoreServiceImpl.java b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/XdsCoreServiceImpl.java index ce2476689c..401488aee0 100644 --- a/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/XdsCoreServiceImpl.java +++ b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/XdsCoreServiceImpl.java @@ -18,12 +18,14 @@ import io.sermant.core.common.LoggerFactory; import io.sermant.core.service.xds.XdsCoreService; +import io.sermant.core.service.xds.XdsFlowControlService; import io.sermant.core.service.xds.XdsLoadBalanceService; import io.sermant.core.service.xds.XdsRouteService; import io.sermant.core.service.xds.XdsServiceDiscovery; import io.sermant.implement.service.xds.client.XdsClient; import io.sermant.implement.service.xds.discovery.XdsServiceDiscoveryImpl; import io.sermant.implement.service.xds.env.XdsConstant; +import io.sermant.implement.service.xds.flowcontrol.XdsFlowControlServiceImpl; import io.sermant.implement.service.xds.handler.CdsHandler; import io.sermant.implement.service.xds.handler.EdsHandler; import io.sermant.implement.service.xds.handler.LdsHandler; @@ -50,6 +52,8 @@ public class XdsCoreServiceImpl implements XdsCoreService { private XdsLoadBalanceService xdsLoadBalanceService; + private XdsFlowControlService xdsFlowControlService; + private XdsClient client; @Override @@ -71,6 +75,7 @@ public void start() { xdsServiceDiscovery = new XdsServiceDiscoveryImpl(edsHandler); xdsRouteService = new XdsRouteServiceImpl(); xdsLoadBalanceService = new XdsLoadBalanceServiceImpl(); + xdsFlowControlService = new XdsFlowControlServiceImpl(); } @Override @@ -96,4 +101,9 @@ public XdsRouteService getXdsRouteService() { public XdsLoadBalanceService getLoadBalanceService() { return xdsLoadBalanceService; } + + @Override + public XdsFlowControlService getXdsFlowControlService() { + return xdsFlowControlService; + } } diff --git a/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/env/XdsConstant.java b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/env/XdsConstant.java index 7d9f3ab574..0406d7c15f 100644 --- a/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/env/XdsConstant.java +++ b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/env/XdsConstant.java @@ -93,6 +93,16 @@ public class XdsConstant { */ public static final String K8S_DEFAULT_NAMESPACE = "default"; + /** + * filter name of http fault + */ + public static final String HTTP_FAULT_FILTER_NAME = "envoy.filters.http.fault"; + + /** + * filter name of http local rate limit filter + */ + public static final String LOCAL_RATE_LIMIT_FILTER_FILTER_NAME = "envoy.filters.http.local_ratelimit"; + private XdsConstant() { } } diff --git a/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/flowcontrol/XdsFlowControlServiceImpl.java b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/flowcontrol/XdsFlowControlServiceImpl.java new file mode 100644 index 0000000000..e05e244659 --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/flowcontrol/XdsFlowControlServiceImpl.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. All rights reserved. + * + * 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 io.sermant.implement.service.xds.flowcontrol; + +import io.sermant.core.service.xds.XdsFlowControlService; +import io.sermant.core.service.xds.entity.XdsHttpFault; +import io.sermant.core.service.xds.entity.XdsInstanceCircuitBreakers; +import io.sermant.core.service.xds.entity.XdsRateLimit; +import io.sermant.core.service.xds.entity.XdsRequestCircuitBreakers; +import io.sermant.core.service.xds.entity.XdsRetryPolicy; +import io.sermant.core.service.xds.entity.XdsRoute; +import io.sermant.core.service.xds.entity.XdsRouteAction; +import io.sermant.core.service.xds.entity.XdsServiceCluster; +import io.sermant.core.utils.StringUtils; +import io.sermant.implement.service.xds.cache.XdsDataCache; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * The implementation class of XdsFlowControlService + * + * @author zhp + * @since 2024-11-27 + **/ +public class XdsFlowControlServiceImpl implements XdsFlowControlService { + /** + * constructor + */ + public XdsFlowControlServiceImpl() { + } + + @Override + public Optional getRequestCircuitBreakers(String serviceName, String clusterName) { + Map serviceClusterMap = XdsDataCache.getServiceClusterMap(); + XdsServiceCluster serviceCluster = serviceClusterMap.get(serviceName); + if (serviceCluster == null) { + return Optional.empty(); + } + return serviceCluster.getRequestCircuitBreakersOfCluster(clusterName); + } + + @Override + public Optional getInstanceCircuitBreakers(String serviceName, String clusterName) { + Map serviceClusterMap = XdsDataCache.getServiceClusterMap(); + XdsServiceCluster serviceCluster = serviceClusterMap.get(serviceName); + if (serviceCluster == null) { + return Optional.empty(); + } + return serviceCluster.getInstanceCircuitBreakersOfCluster(clusterName); + } + + @Override + public Optional getRetryPolicy(String serviceName, String routeName) { + List xdsRoutes = XdsDataCache.getServiceRoute(serviceName); + for (XdsRoute xdsRoute : xdsRoutes) { + if (StringUtils.equals(xdsRoute.getName(), routeName)) { + XdsRouteAction routeAction = xdsRoute.getRouteAction(); + return routeAction == null ? Optional.empty() : Optional.ofNullable(routeAction.getRetryPolicy()); + } + } + return Optional.empty(); + } + + @Override + public Optional getRateLimit(String serviceName, String routeName) { + List xdsRoutes = XdsDataCache.getServiceRoute(serviceName); + for (XdsRoute xdsRoute : xdsRoutes) { + if (StringUtils.equals(xdsRoute.getName(), routeName)) { + return Optional.ofNullable(xdsRoute.getRateLimit()); + } + } + return Optional.empty(); + } + + @Override + public Optional getHttpFault(String serviceName, String routeName) { + List xdsRoutes = XdsDataCache.getServiceRoute(serviceName); + for (XdsRoute xdsRoute : xdsRoutes) { + if (StringUtils.equals(xdsRoute.getName(), routeName)) { + return Optional.ofNullable(xdsRoute.getHttpFault()); + } + } + return Optional.empty(); + } +} diff --git a/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/CdsProtocolTransformer.java b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/CdsProtocolTransformer.java index 490fb04ccb..8f1c1dbb76 100644 --- a/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/CdsProtocolTransformer.java +++ b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/CdsProtocolTransformer.java @@ -16,11 +16,16 @@ package io.sermant.implement.service.xds.utils; +import io.envoyproxy.envoy.config.cluster.v3.CircuitBreakers; import io.envoyproxy.envoy.config.cluster.v3.Cluster; import io.envoyproxy.envoy.config.cluster.v3.Cluster.LbPolicy; +import io.envoyproxy.envoy.config.cluster.v3.OutlierDetection; import io.sermant.core.service.xds.entity.XdsCluster; +import io.sermant.core.service.xds.entity.XdsInstanceCircuitBreakers; import io.sermant.core.service.xds.entity.XdsLbPolicy; +import io.sermant.core.service.xds.entity.XdsRequestCircuitBreakers; import io.sermant.core.service.xds.entity.XdsServiceCluster; +import io.sermant.core.utils.CollectionUtils; import io.sermant.core.utils.StringUtils; import java.util.HashMap; @@ -65,7 +70,7 @@ private CdsProtocolTransformer() { public static Map getServiceClusters(List clusters) { Map> xdsClusters = clusters.stream() .filter(Objects::nonNull) - .map(cluster -> parseCluster(cluster)) + .map(CdsProtocolTransformer::parseCluster) .filter(xdsCluster -> !StringUtils.isEmpty(xdsCluster.getServiceName())) .collect(Collectors.groupingBy( XdsCluster::getServiceName, @@ -96,6 +101,8 @@ private static XdsCluster parseCluster(Cluster cluster) { xdsCluster.setServiceName(serviceNameFromCluster.get()); xdsCluster.setLocalityLb(cluster.getCommonLbConfig().hasLocalityWeightedLbConfig()); xdsCluster.setLbPolicy(parseClusterLbPolicy(cluster.getLbPolicy())); + xdsCluster.setRequestCircuitBreakers(parseRequestCircuitBreakers(cluster.getCircuitBreakers())); + xdsCluster.setInstanceCircuitBreakers(parseInstanceCircuitBreakers(cluster.getOutlierDetection())); return xdsCluster; } @@ -113,4 +120,32 @@ private static String getServiceBaseClusterName(Set xdsClusters) { private static XdsLbPolicy parseClusterLbPolicy(LbPolicy lbPolicy) { return LB_POLICY_MAPPING.getOrDefault(lbPolicy, XdsLbPolicy.UNRECOGNIZED); } + + private static XdsRequestCircuitBreakers parseRequestCircuitBreakers(CircuitBreakers circuitBreakers) { + XdsRequestCircuitBreakers requestCircuitBreakers = new XdsRequestCircuitBreakers(); + if (!CollectionUtils.isEmpty(circuitBreakers.getPerHostThresholdsList())) { + requestCircuitBreakers.setMaxRequests(circuitBreakers.getThresholds(0).getMaxRequests().getValue()); + } + return requestCircuitBreakers; + } + + private static XdsInstanceCircuitBreakers parseInstanceCircuitBreakers(OutlierDetection outlierDetection) { + XdsInstanceCircuitBreakers xdsInstanceCircuitBreakers = new XdsInstanceCircuitBreakers(); + xdsInstanceCircuitBreakers.setSplitExternalLocalOriginErrors(outlierDetection + .getSplitExternalLocalOriginErrors()); + xdsInstanceCircuitBreakers.setConsecutiveLocalOriginFailure(outlierDetection.getConsecutiveLocalOriginFailure() + .getValue()); + xdsInstanceCircuitBreakers.setConsecutiveGatewayFailure(outlierDetection.getConsecutiveGatewayFailure() + .getValue()); + xdsInstanceCircuitBreakers.setConsecutiveGatewayFailure(outlierDetection.getConsecutive5Xx().getValue()); + long interval = java.time.Duration.ofSeconds(outlierDetection.getInterval().getSeconds()).toMillis(); + xdsInstanceCircuitBreakers.setInterval(interval); + long ejectionTime = java.time.Duration.ofSeconds(outlierDetection.getBaseEjectionTime().getSeconds()) + .toMillis(); + xdsInstanceCircuitBreakers.setBaseEjectionTime(ejectionTime); + xdsInstanceCircuitBreakers.setMaxEjectionPercent(outlierDetection.getMaxEjectionPercent().getValue()); + xdsInstanceCircuitBreakers.setFailurePercentageMinimumHosts(outlierDetection.getFailurePercentageMinimumHosts() + .getValue()); + return xdsInstanceCircuitBreakers; + } } diff --git a/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/RdsProtocolTransformer.java b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/RdsProtocolTransformer.java index c8448b2b8d..357d7d96dd 100644 --- a/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/RdsProtocolTransformer.java +++ b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/RdsProtocolTransformer.java @@ -16,7 +16,16 @@ package io.sermant.implement.service.xds.utils; +import com.github.udpa.udpa.type.v1.TypedStruct; +import com.google.protobuf.Any; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.ListValue; +import com.google.protobuf.Message; +import com.google.protobuf.Struct; +import com.google.protobuf.Value; + import io.envoyproxy.envoy.config.route.v3.HeaderMatcher; +import io.envoyproxy.envoy.config.route.v3.RetryPolicy; import io.envoyproxy.envoy.config.route.v3.Route; import io.envoyproxy.envoy.config.route.v3.RouteAction; import io.envoyproxy.envoy.config.route.v3.RouteConfiguration; @@ -24,16 +33,28 @@ import io.envoyproxy.envoy.config.route.v3.VirtualHost; import io.envoyproxy.envoy.config.route.v3.WeightedCluster; import io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight; +import io.envoyproxy.envoy.extensions.filters.common.fault.v3.FaultDelay; +import io.envoyproxy.envoy.extensions.filters.http.fault.v3.FaultAbort; +import io.envoyproxy.envoy.extensions.filters.http.fault.v3.HTTPFault; import io.envoyproxy.envoy.type.matcher.v3.StringMatcher; +import io.envoyproxy.envoy.type.v3.FractionalPercent; import io.sermant.core.common.LoggerFactory; +import io.sermant.core.service.xds.entity.XdsAbort; +import io.sermant.core.service.xds.entity.XdsDelay; +import io.sermant.core.service.xds.entity.XdsHeader; import io.sermant.core.service.xds.entity.XdsHeaderMatcher; +import io.sermant.core.service.xds.entity.XdsHeaderOption; +import io.sermant.core.service.xds.entity.XdsHttpFault; import io.sermant.core.service.xds.entity.XdsPathMatcher; +import io.sermant.core.service.xds.entity.XdsRateLimit; +import io.sermant.core.service.xds.entity.XdsRetryPolicy; import io.sermant.core.service.xds.entity.XdsRoute; import io.sermant.core.service.xds.entity.XdsRouteAction; import io.sermant.core.service.xds.entity.XdsRouteAction.XdsClusterWeight; import io.sermant.core.service.xds.entity.XdsRouteAction.XdsWeightedClusters; import io.sermant.core.service.xds.entity.XdsRouteConfiguration; import io.sermant.core.service.xds.entity.XdsRouteMatch; +import io.sermant.core.service.xds.entity.XdsTokenBucket; import io.sermant.core.service.xds.entity.XdsVirtualHost; import io.sermant.core.service.xds.entity.match.ExactMatchStrategy; import io.sermant.core.service.xds.entity.match.PrefixMatchStrategy; @@ -41,11 +62,18 @@ import io.sermant.core.service.xds.entity.match.RegexMatchStrategy; import io.sermant.core.service.xds.entity.match.SuffixMatchStrategy; import io.sermant.core.service.xds.entity.match.UnknownMatchStrategy; +import io.sermant.core.utils.StringUtils; +import io.sermant.implement.service.xds.env.XdsConstant; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -73,7 +101,7 @@ private RdsProtocolTransformer() { public static List getRouteConfigurations(List routeConfigurations) { return routeConfigurations.stream() .filter(Objects::nonNull) - .map(routeConfiguration -> parseRouteConfiguration(routeConfiguration)) + .map(RdsProtocolTransformer::parseRouteConfiguration) .collect(Collectors.toList()); } @@ -81,7 +109,7 @@ private static XdsRouteConfiguration parseRouteConfiguration(RouteConfiguration XdsRouteConfiguration xdsRouteConfiguration = new XdsRouteConfiguration(); xdsRouteConfiguration.setRouteConfigName(routeConfiguration.getName()); Map xdsVirtualHostMap = routeConfiguration.getVirtualHostsList().stream() - .map(virtualHost -> parseVirtualHost(virtualHost)) + .map(RdsProtocolTransformer::parseVirtualHost) .collect(Collectors.toMap( xdsVirtualHost -> xdsVirtualHost.getName().split(POINT_SEPARATOR)[0], xdsVirtualHost -> xdsVirtualHost @@ -95,7 +123,7 @@ private static XdsVirtualHost parseVirtualHost(VirtualHost virtualHost) { List domains = virtualHost.getDomainsList(); List xdsRoutes = virtualHost.getRoutesList().stream() - .map(route -> parseRoute(route)) + .map(RdsProtocolTransformer::parseRoute) .collect(Collectors.toList()); xdsVirtualHost.setName(virtualHost.getName()); xdsVirtualHost.setRoutes(xdsRoutes); @@ -108,6 +136,22 @@ private static XdsRoute parseRoute(Route route) { xdsRoute.setName(route.getName()); xdsRoute.setRouteMatch(parseRouteMatch(route.getMatch())); xdsRoute.setRouteAction(parseRouteAction(route.getRoute())); + for (Map.Entry entry : route.getTypedPerFilterConfigMap().entrySet()) { + if (entry.getValue() == null) { + continue; + } + if (StringUtils.equals(entry.getKey(), XdsConstant.HTTP_FAULT_FILTER_NAME)) { + Optional optional = unpackAndParseFilter(entry.getValue(), HTTPFault.class, + RdsProtocolTransformer::parseHttpFault); + optional.ifPresent(xdsRoute::setHttpFault); + continue; + } + if (StringUtils.equals(entry.getKey(), XdsConstant.LOCAL_RATE_LIMIT_FILTER_FILTER_NAME)) { + Optional optional = unpackAndParseFilter(entry.getValue(), TypedStruct.class, + RdsProtocolTransformer::parseRateLimit); + optional.ifPresent(xdsRoute::setRateLimit); + } + } return xdsRoute; } @@ -122,7 +166,7 @@ private static XdsRouteMatch parseRouteMatch(RouteMatch routeMatch) { private static List parseHeaderMatchers(List headerMatchers) { return headerMatchers.stream() .filter(Objects::nonNull) - .map(headerMatcher -> parseHeaderMatcher(headerMatcher)) + .map(RdsProtocolTransformer::parseHeaderMatcher) .collect(Collectors.toList()); } @@ -192,6 +236,7 @@ private static XdsRouteAction parseRouteAction(RouteAction routeAction) { LOGGER.log(Level.WARNING, "The xDS route action strategy is unknown. Please check the route configuration."); } + xdsRouteAction.setRetryPolicy(parseRetryPolicy(routeAction.getRetryPolicy())); return xdsRouteAction; } @@ -204,7 +249,7 @@ private static XdsWeightedClusters parseWeightedClusters(WeightedCluster cluster ); xdsWeightedClusters.setClusters(clusters.getClustersList().stream() .filter(Objects::nonNull) - .map(clusterWeight -> parseClusterWeight(clusterWeight)) + .map(RdsProtocolTransformer::parseClusterWeight) .collect(Collectors.toList())); return xdsWeightedClusters; } @@ -215,4 +260,138 @@ private static XdsClusterWeight parseClusterWeight(ClusterWeight clusterWeight) xdsClusterWeight.setClusterName(clusterWeight.getName()); return xdsClusterWeight; } + + private static XdsRetryPolicy parseRetryPolicy(RetryPolicy retryPolicy) { + XdsRetryPolicy xdsRetryPolicy = new XdsRetryPolicy(); + xdsRetryPolicy.setRetryOn(retryPolicy.getRetryOn()); + xdsRetryPolicy.setMaxAttempts(retryPolicy.getHostSelectionRetryMaxAttempts()); + long perTryTimeout = java.time.Duration.ofSeconds(retryPolicy.getPerTryTimeout().getSeconds()).toMillis(); + xdsRetryPolicy.setPerTryTimeout(perTryTimeout); + if (retryPolicy.getRetryHostPredicateCount() != 0) { + xdsRetryPolicy.setRetryHostPredicate(retryPolicy.getRetryHostPredicate(0).getName()); + } + return xdsRetryPolicy; + } + + private static Optional unpackAndParseFilter(Any value, Class clazz, + Function parser) { + try { + T filter = value.unpack(clazz); + return Optional.of(parser.apply(filter)); + } catch (InvalidProtocolBufferException e) { + LOGGER.log(Level.SEVERE, "Failed to unpack and parse filter of type: " + clazz.getName(), e); + } + return Optional.empty(); + } + + private static XdsHttpFault parseHttpFault(HTTPFault httpFault) { + XdsHttpFault xdsHttpFault = new XdsHttpFault(); + xdsHttpFault.setAbort(parseAbort(httpFault.getAbort())); + xdsHttpFault.setDelay(parseDelay(httpFault.getDelay())); + return xdsHttpFault; + } + + private static XdsAbort parseAbort(FaultAbort faultAbort) { + XdsAbort xdsAbort = new XdsAbort(); + xdsAbort.setPercentage(faultAbort.getPercentage().getNumerator()); + xdsAbort.setHttpStatus(faultAbort.getHttpStatus()); + return xdsAbort; + } + + private static XdsDelay parseDelay(FaultDelay faultDelay) { + XdsDelay xdsDelay = new XdsDelay(); + long fixedDelay = java.time.Duration.ofSeconds(faultDelay.getFixedDelay().getSeconds()).toMillis(); + xdsDelay.setFixedDelay(fixedDelay); + xdsDelay.setPercentage(faultDelay.getPercentage().getNumerator()); + return xdsDelay; + } + + private static XdsRateLimit parseRateLimit(TypedStruct typedStruct) { + XdsRateLimit xdsRateLimit = new XdsRateLimit(); + Struct struct = typedStruct.getValue(); + if (struct.containsFields("token_bucket")) { + Optional optionalTokenBucket = + parseTokenBucket(struct.getFieldsOrThrow("token_bucket").getStructValue()); + optionalTokenBucket.ifPresent(xdsRateLimit::setTokenBucket); + } + if (struct.containsFields("filter_enforced")) { + Optional optionalXdsFractionalPercent = + parseRuntimeFractionalPercent(struct.getFieldsOrThrow("filter_enforced").getStructValue()); + optionalXdsFractionalPercent.ifPresent(xdsRateLimit::setPercent); + } + if (struct.containsFields("filter_enabled") + && (xdsRateLimit.getPercent() == null || xdsRateLimit.getPercent().getNumerator() == 0)) { + Optional optionalXdsFractionalPercent = + parseRuntimeFractionalPercent(struct.getFieldsOrThrow("filter_enabled").getStructValue()); + optionalXdsFractionalPercent.ifPresent(xdsRateLimit::setPercent); + } + List responseHeaders = parseHeaderValueOptions(struct); + xdsRateLimit.setResponseHeaderOption(responseHeaders); + return xdsRateLimit; + } + + private static List parseHeaderValueOptions(Struct struct) { + if (!struct.containsFields("response_headers_to_add")) { + return Collections.emptyList(); + } + List responseHeaders = new ArrayList<>(); + ListValue headers = struct.getFieldsOrThrow("response_headers_to_add").getListValue(); + for (Value value : headers.getValuesList()) { + Map headersMap = value.getStructValue().getFieldsMap(); + if (headersMap.get("header") == null) { + continue; + } + Struct headerStruct = headersMap.get("header").getStructValue(); + XdsHeaderOption responseHeader = new XdsHeaderOption(); + if (headersMap.get("append") != null) { + responseHeader.setEnabledAppend(headerStruct.getFieldsOrThrow("append").getBoolValue()); + } + if (!headerStruct.containsFields("key")) { + continue; + } + XdsHeader xdsHeader = new XdsHeader(); + xdsHeader.setKey(headerStruct.getFieldsOrThrow("key").getStringValue()); + if (headerStruct.containsFields("value")) { + xdsHeader.setValue(headerStruct.getFieldsOrThrow("value").getStringValue()); + } + responseHeader.setHeader(xdsHeader); + responseHeaders.add(responseHeader); + } + return responseHeaders; + } + + private static Optional parseRuntimeFractionalPercent( + Struct filterEnabledStruct) { + Map valueMap = filterEnabledStruct.getFieldsMap(); + Value defaultValue = valueMap.get("default_value"); + if (defaultValue == null) { + return Optional.empty(); + } + Struct defaultValueStruct = defaultValue.getStructValue(); + Map defaultValueMap = defaultValueStruct.getFieldsMap(); + if (defaultValueMap.get("numerator") == null || defaultValueMap.get("denominator") == null) { + return Optional.empty(); + } + io.sermant.core.service.xds.entity.FractionalPercent fractionalPercent = + new io.sermant.core.service.xds.entity.FractionalPercent(); + fractionalPercent.setNumerator((int) defaultValueMap.get("numerator").getNumberValue()); + fractionalPercent.setDenominator(FractionalPercent.DenominatorType.valueOf(defaultValueMap.get("denominator") + .getStringValue()).getNumber()); + return Optional.of(fractionalPercent); + } + + private static Optional parseTokenBucket(Struct tokenBucketStruct) { + Map fieldMap = tokenBucketStruct.getFieldsMap(); + if (fieldMap.get("tokens_per_fill") == null || fieldMap.get("fill_interval") == null + || fieldMap.get("max_tokens") == null) { + return Optional.empty(); + } + double tokensPerFill = fieldMap.get("tokens_per_fill").getNumberValue(); + long fillInterval = Duration.parse("PT" + fieldMap.get("fill_interval").getStringValue()).toMillis(); + XdsTokenBucket xdsTokenBucket = new XdsTokenBucket(); + xdsTokenBucket.setFillInterval(fillInterval); + xdsTokenBucket.setMaxTokens((int) fieldMap.get("max_tokens").getNumberValue()); + xdsTokenBucket.setTokensPerFill((int) tokensPerFill); + return Optional.of(xdsTokenBucket); + } } diff --git a/sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/CommonDataGenerator.java b/sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/CommonDataGenerator.java index 3519804254..704e66901e 100644 --- a/sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/CommonDataGenerator.java +++ b/sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/CommonDataGenerator.java @@ -17,20 +17,25 @@ package io.sermant.implement.service.xds; import io.sermant.core.service.xds.entity.ServiceInstance; +import io.sermant.core.service.xds.entity.XdsAbort; import io.sermant.core.service.xds.entity.XdsCluster; import io.sermant.core.service.xds.entity.XdsClusterLoadAssigment; import io.sermant.core.service.xds.entity.XdsHttpConnectionManager; +import io.sermant.core.service.xds.entity.XdsHttpFault; +import io.sermant.core.service.xds.entity.XdsInstanceCircuitBreakers; import io.sermant.core.service.xds.entity.XdsLbPolicy; import io.sermant.core.service.xds.entity.XdsLocality; +import io.sermant.core.service.xds.entity.XdsRateLimit; +import io.sermant.core.service.xds.entity.XdsRequestCircuitBreakers; import io.sermant.core.service.xds.entity.XdsRoute; import io.sermant.core.service.xds.entity.XdsRouteConfiguration; import io.sermant.core.service.xds.entity.XdsServiceCluster; import io.sermant.core.service.xds.entity.XdsServiceClusterLoadAssigment; +import io.sermant.core.service.xds.entity.XdsTokenBucket; import io.sermant.core.service.xds.entity.XdsVirtualHost; import io.sermant.implement.service.xds.entity.XdsServiceInstance; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -52,20 +57,26 @@ public static List createRouteConfigurations() { XdsRoute route = new XdsRoute(); route.setName("test-route"); - virtualHost1.setRoutes(Arrays.asList(route)); + XdsHttpFault httpFault = new XdsHttpFault(); + httpFault.setAbort(new XdsAbort()); + httpFault.getAbort().setHttpStatus(503); + route.setHttpFault(httpFault); + XdsRateLimit xdsRateLimit = new XdsRateLimit(); + XdsTokenBucket xdsTokenBucket = new XdsTokenBucket(); + xdsTokenBucket.setMaxTokens(10); + xdsRateLimit.setTokenBucket(xdsTokenBucket); + route.setRateLimit(xdsRateLimit); + virtualHost1.setRoutes(Collections.singletonList(route)); virtualHost2.setRoutes(Collections.emptyList()); Map virtualHosts = new HashMap<>(); virtualHosts.put("serviceA", virtualHost1); virtualHosts.put("serviceB", virtualHost2); routeConfiguration.setVirtualHosts(virtualHosts); - return Arrays.asList(routeConfiguration); + return Collections.singletonList(routeConfiguration); } public static Map createServiceClusterMap(String serviceName, String clusterName) { - XdsCluster cluster = new XdsCluster(); - cluster.setClusterName(clusterName); - cluster.setLocalityLb(true); - cluster.setLbPolicy(XdsLbPolicy.RANDOM); + XdsCluster cluster = initCluster(clusterName); Map clusters = new HashMap<>(); clusters.put(clusterName, cluster); @@ -78,6 +89,20 @@ public static Map createServiceClusterMap(String serv return serviceClusterMap; } + private static XdsCluster initCluster(String clusterName) { + XdsCluster cluster = new XdsCluster(); + cluster.setClusterName(clusterName); + cluster.setLocalityLb(true); + cluster.setLbPolicy(XdsLbPolicy.RANDOM); + XdsRequestCircuitBreakers requestCircuitBreakers = new XdsRequestCircuitBreakers(); + requestCircuitBreakers.setMaxRequests(100); + cluster.setRequestCircuitBreakers(requestCircuitBreakers); + XdsInstanceCircuitBreakers instanceCircuitBreakers = new XdsInstanceCircuitBreakers(); + instanceCircuitBreakers.setInterval(1000); + cluster.setInstanceCircuitBreakers(instanceCircuitBreakers); + return cluster; + } + public static XdsServiceClusterLoadAssigment createXdsServiceClusterInstance(List clusterNames, String baseClusterName) { Map clusterInstances = new HashMap<>(); diff --git a/sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/flowcontrol/XdsFlowControlServiceImplTest.java b/sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/flowcontrol/XdsFlowControlServiceImplTest.java new file mode 100644 index 0000000000..ae205164cb --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/flowcontrol/XdsFlowControlServiceImplTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. All rights reserved. + * + * 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 io.sermant.implement.service.xds.flowcontrol; + +import io.sermant.core.service.xds.XdsFlowControlService; +import io.sermant.core.service.xds.entity.XdsHttpFault; +import io.sermant.core.service.xds.entity.XdsInstanceCircuitBreakers; +import io.sermant.core.service.xds.entity.XdsRateLimit; +import io.sermant.core.service.xds.entity.XdsRequestCircuitBreakers; +import io.sermant.implement.service.xds.BaseXdsTest; +import io.sermant.implement.service.xds.CommonDataGenerator; +import io.sermant.implement.service.xds.cache.XdsDataCache; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Optional; + +/** + * XdsFlowControlServiceImplTest + * + * @author zhp + * @since 2024-12-02 + **/ +public class XdsFlowControlServiceImplTest extends BaseXdsTest { + private static final String SERVICE_NAME = "serviceA"; + + private static final String ROUTE_NAME = "test-route"; + + private static final String CLUSTER_NAME = "outbound|8080||serviceA.default.svc.cluster.local"; + + private static XdsFlowControlService flowControlService; + + @BeforeClass + public static void setUp() { + flowControlService = new XdsFlowControlServiceImpl(); + XdsDataCache.updateRouteConfigurations(CommonDataGenerator.createRouteConfigurations()); + XdsDataCache.updateServiceClusterMap(CommonDataGenerator + .createServiceClusterMap(SERVICE_NAME, CLUSTER_NAME)); + } + + @AfterClass + public static void tearDown() { + XdsDataCache.updateRouteConfigurations(new ArrayList<>()); + XdsDataCache.updateServiceClusterMap(new HashMap<>()); + } + + @Test + public void testGetRequestCircuitBreakers() { + Optional result = flowControlService.getRequestCircuitBreakers(SERVICE_NAME, CLUSTER_NAME); + Assert.assertTrue(result.isPresent()); + XdsRequestCircuitBreakers requestCircuitBreakers = result.get(); + Assert.assertEquals(100, requestCircuitBreakers.getMaxRequests()); + } + + @Test + public void testGetInstanceCircuitBreakers() { + Optional result = flowControlService.getInstanceCircuitBreakers(SERVICE_NAME, CLUSTER_NAME); + Assert.assertTrue(result.isPresent()); + XdsInstanceCircuitBreakers instanceCircuitBreakers = result.get(); + Assert.assertEquals(1000, instanceCircuitBreakers.getInterval()); + } + + @Test + public void testGetHttpFault() { + Optional result = flowControlService.getHttpFault(SERVICE_NAME, ROUTE_NAME); + Assert.assertTrue(result.isPresent()); + XdsHttpFault httpFault = result.get(); + Assert.assertEquals(503, httpFault.getAbort().getHttpStatus()); + } + + @Test + public void testGetRateLimit() { + Optional result = flowControlService.getRateLimit(SERVICE_NAME, ROUTE_NAME); + Assert.assertTrue(result.isPresent()); + XdsRateLimit rateLimit = result.get(); + Assert.assertEquals(10, rateLimit.getTokenBucket().getMaxTokens()); + } +}