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 cfb0a1a107 [ISSUE #7913] Start Kubernetes scaling only on the cluster
master (#7964)
cfb0a1a107 is described below
commit cfb0a1a1079b8fd5458d717fdafdad1d7249e14c
Author: imDwAaY <[email protected]>
AuthorDate: Fri Oct 23 11:18:38 2026 +0800
[ISSUE #7913] Start Kubernetes scaling only on the cluster master (#7964)
---
.../shenyu/admin/config/ClusterConfiguration.java | 4 +
.../admin/config/StandaloneConfiguration.java | 8 +-
.../mode/cluster/service/ShenyuClusterService.java | 9 ++
.../mode/standalone/ShenyuStandaloneService.java | 10 +-
...Initializer.java => ScaleLifecycleService.java} | 40 ++++++--
.../shenyu/admin/scale/scaler/ScaleService.java | 23 +++++
shenyu-admin/src/main/resources/application.yml | 4 +
.../cluster/service/ShenyuClusterServiceTest.java | 30 +++++-
.../standalone/ShenyuStandaloneServiceTest.java | 80 +++++++++++++++
.../scale/scaler/ScaleLifecycleServiceTest.java | 107 +++++++++++++++++++++
.../admin/scale/scaler/ScaleServiceTest.java | 40 ++++++++
11 files changed, 341 insertions(+), 14 deletions(-)
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/config/ClusterConfiguration.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/config/ClusterConfiguration.java
index 26846bf6f9..7fcb37f42e 100644
---
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/config/ClusterConfiguration.java
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/config/ClusterConfiguration.java
@@ -24,6 +24,7 @@ import org.apache.shenyu.admin.mode.ShenyuRunningModeService;
import org.apache.shenyu.admin.mode.cluster.filter.ClusterForwardFilter;
import org.apache.shenyu.admin.mode.cluster.service.ClusterSelectMasterService;
import org.apache.shenyu.admin.mode.cluster.service.ShenyuClusterService;
+import org.apache.shenyu.admin.scale.scaler.ScaleLifecycleService;
import org.apache.shenyu.admin.service.impl.InstanceCheckService;
import org.apache.shenyu.admin.service.impl.UpstreamCheckService;
import org.slf4j.Logger;
@@ -52,6 +53,7 @@ public class ClusterConfiguration {
* @param shenyuClusterSelectMasterService shenyu cluster select master
service
* @param upstreamCheckService upstream check service
* @param instanceCheckService instance check service
+ * @param scaleLifecycleService scaling lifecycle service
* @param clusterProperties cluster properties
* @return Shenyu cluster service
*/
@@ -60,11 +62,13 @@ public class ClusterConfiguration {
public ShenyuRunningModeService shenyuRunningModeService(final
ClusterSelectMasterService shenyuClusterSelectMasterService,
final
UpstreamCheckService upstreamCheckService,
final
InstanceCheckService instanceCheckService,
+ final
ScaleLifecycleService scaleLifecycleService,
final
ClusterProperties clusterProperties) {
LOGGER.info("starting in cluster mode ...");
return new ShenyuClusterService(shenyuClusterSelectMasterService,
upstreamCheckService,
instanceCheckService,
+ scaleLifecycleService,
clusterProperties
);
}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/config/StandaloneConfiguration.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/config/StandaloneConfiguration.java
index 3aee40f5ef..9c02a00a6e 100644
---
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/config/StandaloneConfiguration.java
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/config/StandaloneConfiguration.java
@@ -19,6 +19,7 @@ package org.apache.shenyu.admin.config;
import org.apache.shenyu.admin.mode.ShenyuRunningModeService;
import org.apache.shenyu.admin.mode.standalone.ShenyuStandaloneService;
+import org.apache.shenyu.admin.scale.scaler.ScaleLifecycleService;
import org.apache.shenyu.admin.service.impl.InstanceCheckService;
import org.apache.shenyu.admin.service.impl.UpstreamCheckService;
import org.apache.shenyu.admin.service.manager.LoadServiceDocEntry;
@@ -43,6 +44,7 @@ public class StandaloneConfiguration {
* @param upstreamCheckService upstream check service
* @param instanceCheckService instance check service
* @param loadServiceDocEntry load service doc entry
+ * @param scaleLifecycleService scaling lifecycle service
* @return Shenyu standalone service
*/
@Bean(destroyMethod = "shutdown")
@@ -50,12 +52,14 @@ public class StandaloneConfiguration {
@ConditionalOnMissingBean
public ShenyuRunningModeService shenyuRunningModeService(final
UpstreamCheckService upstreamCheckService,
final
InstanceCheckService instanceCheckService,
- final
LoadServiceDocEntry loadServiceDocEntry) {
+ final
LoadServiceDocEntry loadServiceDocEntry,
+ final
ScaleLifecycleService scaleLifecycleService) {
LOGGER.info("starting in standalone mode ...");
return new ShenyuStandaloneService(
upstreamCheckService,
loadServiceDocEntry,
- instanceCheckService
+ instanceCheckService,
+ scaleLifecycleService
);
}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/mode/cluster/service/ShenyuClusterService.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/mode/cluster/service/ShenyuClusterService.java
index 6d89c8c5e6..aa3f41ab74 100644
---
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/mode/cluster/service/ShenyuClusterService.java
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/mode/cluster/service/ShenyuClusterService.java
@@ -19,6 +19,7 @@ package org.apache.shenyu.admin.mode.cluster.service;
import org.apache.shenyu.admin.config.properties.ClusterProperties;
import org.apache.shenyu.admin.mode.ShenyuRunningModeService;
+import org.apache.shenyu.admin.scale.scaler.ScaleLifecycleService;
import org.apache.shenyu.admin.service.impl.InstanceCheckService;
import org.apache.shenyu.admin.service.impl.UpstreamCheckService;
import org.apache.shenyu.common.concurrent.ShenyuThreadFactory;
@@ -45,10 +46,13 @@ public class ShenyuClusterService implements
ShenyuRunningModeService {
private final ClusterProperties clusterProperties;
private final InstanceCheckService instanceCheckService;
+
+ private final ScaleLifecycleService scaleLifecycleService;
public ShenyuClusterService(final ClusterSelectMasterService
shenyuClusterSelectMasterService,
final UpstreamCheckService
upstreamCheckService,
final InstanceCheckService
instanceCheckService,
+ final ScaleLifecycleService
scaleLifecycleService,
final ClusterProperties clusterProperties) {
this.shenyuClusterSelectMasterService =
shenyuClusterSelectMasterService;
this.upstreamCheckService = upstreamCheckService;
@@ -56,6 +60,7 @@ public class ShenyuClusterService implements
ShenyuRunningModeService {
this.executorService = new ScheduledThreadPoolExecutor(1,
ShenyuThreadFactory.create("master-selector", true));
this.instanceCheckService = instanceCheckService;
+ this.scaleLifecycleService = scaleLifecycleService;
}
/**
@@ -90,6 +95,8 @@ public class ShenyuClusterService implements
ShenyuRunningModeService {
instanceCheckService.setup();
+ scaleLifecycleService.start();
+
boolean renewed =
shenyuClusterSelectMasterService.checkMasterStatus();
while (renewed) {
@@ -108,10 +115,12 @@ public class ShenyuClusterService implements
ShenyuRunningModeService {
// close the upstream check service
upstreamCheckService.close();
instanceCheckService.close();
+ scaleLifecycleService.stop();
String message = String.format("renew master fail, %s",
e.getMessage());
throw new ShenyuException(message);
} finally {
+ scaleLifecycleService.stop();
try {
shenyuClusterSelectMasterService.releaseMaster();
} catch (Exception e) {
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/mode/standalone/ShenyuStandaloneService.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/mode/standalone/ShenyuStandaloneService.java
index 36e441626e..913efa4250 100644
---
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/mode/standalone/ShenyuStandaloneService.java
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/mode/standalone/ShenyuStandaloneService.java
@@ -18,6 +18,7 @@
package org.apache.shenyu.admin.mode.standalone;
import org.apache.shenyu.admin.mode.ShenyuRunningModeService;
+import org.apache.shenyu.admin.scale.scaler.ScaleLifecycleService;
import org.apache.shenyu.admin.service.impl.InstanceCheckService;
import org.apache.shenyu.admin.service.impl.UpstreamCheckService;
import org.apache.shenyu.admin.service.manager.LoadServiceDocEntry;
@@ -29,13 +30,17 @@ public class ShenyuStandaloneService implements
ShenyuRunningModeService {
private final LoadServiceDocEntry loadServiceDocEntry;
private final InstanceCheckService instanceCheckService;
+
+ private final ScaleLifecycleService scaleLifecycleService;
public ShenyuStandaloneService(final UpstreamCheckService
upstreamCheckService,
final LoadServiceDocEntry
loadServiceDocEntry,
- final InstanceCheckService
instanceCheckService) {
+ final InstanceCheckService
instanceCheckService,
+ final ScaleLifecycleService
scaleLifecycleService) {
this.upstreamCheckService = upstreamCheckService;
this.loadServiceDocEntry = loadServiceDocEntry;
this.instanceCheckService = instanceCheckService;
+ this.scaleLifecycleService = scaleLifecycleService;
}
@Override
@@ -45,6 +50,8 @@ public class ShenyuStandaloneService implements
ShenyuRunningModeService {
instanceCheckService.setup();
+ scaleLifecycleService.start();
+
// load api
loadServiceDocEntry.loadApiDocument();
}
@@ -53,5 +60,6 @@ public class ShenyuStandaloneService implements
ShenyuRunningModeService {
public void shutdown() {
upstreamCheckService.close();
instanceCheckService.close();
+ scaleLifecycleService.stop();
}
}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/scale/scaler/ScaleTaskInitializer.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/scale/scaler/ScaleLifecycleService.java
similarity index 66%
rename from
shenyu-admin/src/main/java/org/apache/shenyu/admin/scale/scaler/ScaleTaskInitializer.java
rename to
shenyu-admin/src/main/java/org/apache/shenyu/admin/scale/scaler/ScaleLifecycleService.java
index bf0383772b..344be4377b 100644
---
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/scale/scaler/ScaleTaskInitializer.java
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/scale/scaler/ScaleLifecycleService.java
@@ -23,13 +23,15 @@ import org.apache.shenyu.admin.model.entity.ScalePolicyDO;
import org.apache.shenyu.admin.model.entity.ScaleRuleDO;
import org.apache.shenyu.admin.scale.monitor.subject.cache.ScaleRuleCache;
import org.apache.shenyu.admin.scale.scaler.cache.ScalePolicyCache;
-import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.List;
+/**
+ * Manages scaling cache initialization and the scaling service lifecycle.
+ */
@Component
-public class ScaleTaskInitializer implements CommandLineRunner {
+public class ScaleLifecycleService {
private final ScalePolicyMapper scalePolicyMapper;
@@ -41,11 +43,20 @@ public class ScaleTaskInitializer implements
CommandLineRunner {
private final ScaleService scaleService;
- public ScaleTaskInitializer(final ScalePolicyMapper scalePolicyMapper,
- final ScalePolicyCache scalePolicyCache,
- final ScaleService scaleService,
- final ScaleRuleMapper scaleRuleMapper,
- final ScaleRuleCache scaleRuleCache) {
+ /**
+ * Instantiates a scaling lifecycle service.
+ *
+ * @param scalePolicyMapper scale policy mapper
+ * @param scalePolicyCache scale policy cache
+ * @param scaleService scale service
+ * @param scaleRuleMapper scale rule mapper
+ * @param scaleRuleCache scale rule cache
+ */
+ public ScaleLifecycleService(final ScalePolicyMapper scalePolicyMapper,
+ final ScalePolicyCache scalePolicyCache,
+ final ScaleService scaleService,
+ final ScaleRuleMapper scaleRuleMapper,
+ final ScaleRuleCache scaleRuleCache) {
this.scalePolicyMapper = scalePolicyMapper;
this.scalePolicyCache = scalePolicyCache;
this.scaleService = scaleService;
@@ -53,14 +64,23 @@ public class ScaleTaskInitializer implements
CommandLineRunner {
this.scaleRuleCache = scaleRuleCache;
}
- @Override
- public void run(final String... args) {
+ /**
+ * Reload scaling caches and activate scaling.
+ */
+ public synchronized void start() {
List<ScalePolicyDO> policies = scalePolicyMapper.selectAll();
scalePolicyCache.initialize(policies);
List<ScaleRuleDO> rules = scaleRuleMapper.selectAll();
scaleRuleCache.initialize(rules);
- scaleService.executeScaling();
+ scaleService.start();
+ }
+
+ /**
+ * Deactivate scaling.
+ */
+ public synchronized void stop() {
+ scaleService.stop();
}
}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/scale/scaler/ScaleService.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/scale/scaler/ScaleService.java
index 75b8d53ae0..6c5e51c1e7 100644
---
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/scale/scaler/ScaleService.java
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/scale/scaler/ScaleService.java
@@ -64,6 +64,8 @@ public class ScaleService {
private final Clock clock;
+ private boolean active;
+
private AppliedTarget appliedTarget;
@Autowired
@@ -90,12 +92,33 @@ public class ScaleService {
this.clock = clock;
}
+ /**
+ * Activate scaling and reconcile the currently effective policy.
+ */
+ public synchronized void start() {
+ active = true;
+ executeScaling();
+ }
+
+ /**
+ * Deactivate scaling and cancel all scaling tasks.
+ */
+ public synchronized void stop() {
+ active = false;
+ stopDynamicTask();
+ cancelWindowTasks();
+ appliedTarget = null;
+ }
+
/**
* Reconcile the currently effective scaling policy.
*
* <p>This method is synchronized because startup, HTTP request, and
scheduler threads may invoke it.</p>
*/
public synchronized void executeScaling() {
+ if (!active) {
+ return;
+ }
cancelWindowTasks();
Instant now = clock.instant();
scheduleFutureWindowActivation(now);
diff --git a/shenyu-admin/src/main/resources/application.yml
b/shenyu-admin/src/main/resources/application.yml
index 19d88e671b..c7cf7fe0ef 100755
--- a/shenyu-admin/src/main/resources/application.yml
+++ b/shenyu-admin/src/main/resources/application.yml
@@ -154,6 +154,10 @@ shenyu:
- /rule/batch
- /rule
- /rule/**
+ - /scale/policy
+ - /scale/policy/**
+ - /scale/rule
+ - /scale/rule/**
- /plugin
- /plugin/**
- /plugin/createPluginResource/**
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/mode/cluster/service/ShenyuClusterServiceTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/mode/cluster/service/ShenyuClusterServiceTest.java
index 25874909b5..5908189f62 100644
---
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/mode/cluster/service/ShenyuClusterServiceTest.java
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/mode/cluster/service/ShenyuClusterServiceTest.java
@@ -18,11 +18,13 @@
package org.apache.shenyu.admin.mode.cluster.service;
import org.apache.shenyu.admin.config.properties.ClusterProperties;
+import org.apache.shenyu.admin.scale.scaler.ScaleLifecycleService;
import org.apache.shenyu.admin.service.impl.InstanceCheckService;
import org.apache.shenyu.admin.service.impl.UpstreamCheckService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
@@ -36,7 +38,11 @@ import static
org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
@@ -57,13 +63,17 @@ public final class ShenyuClusterServiceTest {
@Mock
private InstanceCheckService instanceCheckService;
+ @Mock
+ private ScaleLifecycleService scaleLifecycleService;
+
private ShenyuClusterService clusterService;
@BeforeEach
public void setUp() {
final ClusterProperties properties = new ClusterProperties();
properties.setSelectPeriod(1L);
- clusterService = new ShenyuClusterService(clusterSelectMasterService,
upstreamCheckService, instanceCheckService, properties);
+ clusterService = new ShenyuClusterService(
+ clusterSelectMasterService, upstreamCheckService,
instanceCheckService, scaleLifecycleService, properties);
}
@Test
@@ -72,6 +82,21 @@ public final class ShenyuClusterServiceTest {
assertTrue(scheduler().isShutdown());
}
+ @Test
+ public void testMasterLifecycleStartsAndStopsScaling() {
+ when(clusterSelectMasterService.selectMaster(anyString(), anyString(),
anyString())).thenReturn(true);
+ when(clusterSelectMasterService.checkMasterStatus()).thenReturn(false);
+
+ ReflectionTestUtils.invokeMethod(clusterService, "doSelectMaster",
"127.0.0.1", "9095", "/");
+
+ final InOrder order = inOrder(upstreamCheckService,
instanceCheckService, scaleLifecycleService);
+ order.verify(upstreamCheckService).setup();
+ order.verify(instanceCheckService).setup();
+ order.verify(scaleLifecycleService).start();
+ order.verify(scaleLifecycleService).stop();
+ verify(clusterSelectMasterService).releaseMaster();
+ }
+
@Test
public void testShutdownStopsElectionScheduler() throws Exception {
final AtomicInteger attempts = new AtomicInteger();
@@ -90,6 +115,7 @@ public final class ShenyuClusterServiceTest {
final int afterShutdown = attempts.get();
Thread.sleep(2500L);
assertEquals(afterShutdown, attempts.get(), "no election attempt may
happen after completed shutdown");
+ verify(scaleLifecycleService, never()).start();
assertDoesNotThrow(clusterService::shutdown);
}
@@ -111,6 +137,8 @@ public final class ShenyuClusterServiceTest {
// and the existing error path closes the owned check services
org.mockito.Mockito.verify(upstreamCheckService,
timeout(3000L)).close();
org.mockito.Mockito.verify(instanceCheckService,
timeout(3000L)).close();
+ verify(scaleLifecycleService).start();
+ verify(scaleLifecycleService, times(2)).stop();
}
private ScheduledExecutorService scheduler() {
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/mode/standalone/ShenyuStandaloneServiceTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/mode/standalone/ShenyuStandaloneServiceTest.java
new file mode 100644
index 0000000000..11731e1a05
--- /dev/null
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/mode/standalone/ShenyuStandaloneServiceTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.admin.mode.standalone;
+
+import org.apache.shenyu.admin.scale.scaler.ScaleLifecycleService;
+import org.apache.shenyu.admin.service.impl.InstanceCheckService;
+import org.apache.shenyu.admin.service.impl.UpstreamCheckService;
+import org.apache.shenyu.admin.service.manager.LoadServiceDocEntry;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.mockito.Mockito.inOrder;
+
+/**
+ * Test cases for {@link ShenyuStandaloneService}.
+ */
+@ExtendWith(MockitoExtension.class)
+public final class ShenyuStandaloneServiceTest {
+
+ @Mock
+ private UpstreamCheckService upstreamCheckService;
+
+ @Mock
+ private LoadServiceDocEntry loadServiceDocEntry;
+
+ @Mock
+ private InstanceCheckService instanceCheckService;
+
+ @Mock
+ private ScaleLifecycleService scaleLifecycleService;
+
+ private ShenyuStandaloneService standaloneService;
+
+ @BeforeEach
+ public void setUp() {
+ standaloneService = new ShenyuStandaloneService(
+ upstreamCheckService, loadServiceDocEntry,
instanceCheckService, scaleLifecycleService);
+ }
+
+ @Test
+ public void testStartDelegatesToServices() {
+ standaloneService.start("127.0.0.1", 9095, "/");
+
+ final InOrder order = inOrder(
+ upstreamCheckService, instanceCheckService,
scaleLifecycleService, loadServiceDocEntry);
+ order.verify(upstreamCheckService).setup();
+ order.verify(instanceCheckService).setup();
+ order.verify(scaleLifecycleService).start();
+ order.verify(loadServiceDocEntry).loadApiDocument();
+ }
+
+ @Test
+ public void testShutdownDelegatesToServices() {
+ standaloneService.shutdown();
+
+ final InOrder order = inOrder(upstreamCheckService,
instanceCheckService, scaleLifecycleService);
+ order.verify(upstreamCheckService).close();
+ order.verify(instanceCheckService).close();
+ order.verify(scaleLifecycleService).stop();
+ }
+}
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/scale/scaler/ScaleLifecycleServiceTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/scale/scaler/ScaleLifecycleServiceTest.java
new file mode 100644
index 0000000000..c1fed230bf
--- /dev/null
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/scale/scaler/ScaleLifecycleServiceTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.admin.scale.scaler;
+
+import org.apache.shenyu.admin.mapper.ScalePolicyMapper;
+import org.apache.shenyu.admin.mapper.ScaleRuleMapper;
+import org.apache.shenyu.admin.model.entity.ScalePolicyDO;
+import org.apache.shenyu.admin.model.entity.ScaleRuleDO;
+import org.apache.shenyu.admin.scale.monitor.subject.cache.ScaleRuleCache;
+import org.apache.shenyu.admin.scale.scaler.cache.ScalePolicyCache;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.List;
+
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Test cases for {@link ScaleLifecycleService}.
+ */
+@ExtendWith(MockitoExtension.class)
+public final class ScaleLifecycleServiceTest {
+
+ @Mock
+ private ScalePolicyMapper scalePolicyMapper;
+
+ @Mock
+ private ScalePolicyCache scalePolicyCache;
+
+ @Mock
+ private ScaleRuleMapper scaleRuleMapper;
+
+ @Mock
+ private ScaleRuleCache scaleRuleCache;
+
+ @Mock
+ private ScaleService scaleService;
+
+ private ScaleLifecycleService scaleLifecycleService;
+
+ @BeforeEach
+ public void setUp() {
+ scaleLifecycleService = new ScaleLifecycleService(
+ scalePolicyMapper, scalePolicyCache, scaleService,
scaleRuleMapper, scaleRuleCache);
+ }
+
+ @Test
+ public void testStartLoadsCachesAndStartsScaling() {
+ final List<ScalePolicyDO> policies = List.of(new ScalePolicyDO());
+ final List<ScaleRuleDO> rules = List.of(new ScaleRuleDO());
+ when(scalePolicyMapper.selectAll()).thenReturn(policies);
+ when(scaleRuleMapper.selectAll()).thenReturn(rules);
+
+ scaleLifecycleService.start();
+
+ final InOrder order = inOrder(scalePolicyMapper, scalePolicyCache,
scaleRuleMapper, scaleRuleCache, scaleService);
+ order.verify(scalePolicyMapper).selectAll();
+ order.verify(scalePolicyCache).initialize(policies);
+ order.verify(scaleRuleMapper).selectAll();
+ order.verify(scaleRuleCache).initialize(rules);
+ order.verify(scaleService).start();
+ }
+
+ @Test
+ public void testStartReloadsCachesEachTime() {
+ when(scalePolicyMapper.selectAll()).thenReturn(List.of());
+ when(scaleRuleMapper.selectAll()).thenReturn(List.of());
+
+ scaleLifecycleService.start();
+ scaleLifecycleService.start();
+
+ verify(scalePolicyMapper, times(2)).selectAll();
+ verify(scalePolicyCache, times(2)).initialize(List.of());
+ verify(scaleRuleMapper, times(2)).selectAll();
+ verify(scaleRuleCache, times(2)).initialize(List.of());
+ verify(scaleService, times(2)).start();
+ }
+
+ @Test
+ public void testStopStopsScaling() {
+ scaleLifecycleService.stop();
+
+ verify(scaleService).stop();
+ }
+}
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/scale/scaler/ScaleServiceTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/scale/scaler/ScaleServiceTest.java
index c29db70d86..64464e50c1 100644
---
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/scale/scaler/ScaleServiceTest.java
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/scale/scaler/ScaleServiceTest.java
@@ -35,6 +35,7 @@ import java.util.List;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -77,6 +78,45 @@ public final class ScaleServiceTest {
service = new ScaleService(
mock(MetricsMonitor.class), schedulerManager,
policyCache, properties, kubernetesScaler, clock);
+ service.start();
+ clearInvocations(schedulerManager, kubernetesScaler);
+ }
+
+ @Test
+ public void testInactiveExecuteScalingDoesNothing() {
+ service.stop();
+ clearInvocations(schedulerManager, kubernetesScaler);
+
+ service.executeScaling();
+
+ verifyNoInteractions(schedulerManager, kubernetesScaler);
+ }
+
+ @Test
+ public void testStartActivatesAndReconciles() {
+ service.stop();
+ final ScalePolicyDO fixedPolicy = policy("1", 1, 1);
+ fixedPolicy.setNum(3);
+ policyCache.initialize(List.of(fixedPolicy));
+ clearInvocations(schedulerManager, kubernetesScaler);
+
+ service.start();
+
+ verify(kubernetesScaler).scaleByNum(3);
+ }
+
+ @Test
+ public void testStopCancelsAllTasksAndIsIdempotent() {
+ service.stop();
+ service.stop();
+
+ verify(schedulerManager,
times(2)).cancelMonitorTask("DynamicScalingTask");
+ verifyWindowTaskCancellations(2);
+ verifyNoMoreInteractions(schedulerManager);
+
+ service.executeScaling();
+ verifyNoMoreInteractions(schedulerManager);
+ verifyNoInteractions(kubernetesScaler);
}
@Test