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

Reply via email to