This is an automated email from the ASF dual-hosted git repository.

MartijnVisser pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git


The following commit(s) were added to refs/heads/master by this push:
     new 7063101f454 [FLINK-40150][tests] De-parameterize 
rescale-history-enabled RescaleTimelineITCase tests
7063101f454 is described below

commit 7063101f45454eddfa0fdb2778dce7c0473046d6
Author: Martijn Visser <[email protected]>
AuthorDate: Wed Jul 15 13:39:34 2026 +0200

    [FLINK-40150][tests] De-parameterize rescale-history-enabled 
RescaleTimelineITCase tests
    
    The terminated-reason tests only assert when rescale history is enabled, yet
    ran as @TestTemplate: on the disabled parameter each paid for a MiniCluster
    startup before assumeThat aborted, and three rebuilt the cluster on top. 
Move
    them to a non-parameterized RescaleTimelineHistoryEnabledITCase that starts 
one
    cluster per test with the configuration it needs. Shared setup and helpers 
move
    to RescaleTimelineITCaseBase; RescaleTimelineITCase keeps the parameterized 
cases.
    
    Generated-by: Claude Code (Claude Opus 4.8)
---
 .../RescaleTimelineHistoryEnabledITCase.java       | 273 ++++++++++++++++
 .../adaptive/timeline/RescaleTimelineITCase.java   | 363 +--------------------
 .../timeline/RescaleTimelineITCaseBase.java        | 156 +++++++++
 3 files changed, 435 insertions(+), 357 deletions(-)

diff --git 
a/flink-runtime/src/test/java/org/apache/flink/runtime/scheduler/adaptive/timeline/RescaleTimelineHistoryEnabledITCase.java
 
b/flink-runtime/src/test/java/org/apache/flink/runtime/scheduler/adaptive/timeline/RescaleTimelineHistoryEnabledITCase.java
new file mode 100644
index 00000000000..06a9a109b5f
--- /dev/null
+++ 
b/flink-runtime/src/test/java/org/apache/flink/runtime/scheduler/adaptive/timeline/RescaleTimelineHistoryEnabledITCase.java
@@ -0,0 +1,273 @@
+/*
+ * 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.flink.runtime.scheduler.adaptive.timeline;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.JobManagerOptions;
+import org.apache.flink.configuration.WebOptions;
+import org.apache.flink.runtime.jobgraph.JobGraph;
+import org.apache.flink.runtime.minicluster.MiniCluster;
+import org.apache.flink.runtime.scheduler.ExecutionGraphInfo;
+import org.apache.flink.runtime.testtasks.OnceBlockingNoOpInvokable;
+import org.apache.flink.util.function.SupplierWithException;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for rescale terminated reasons and terminal state, only meaningful 
when rescale history is
+ * enabled. Each case starts its own cluster with the configuration it needs.
+ */
+class RescaleTimelineHistoryEnabledITCase extends RescaleTimelineITCaseBase {
+
+    // Matches the default poll interval of the 
CommonTestUtils#waitUntilCondition it replaced.
+    private static final long RETRY_INTERVAL_MILLIS = 100L;
+
+    @BeforeEach
+    void setUp() {
+        OnceBlockingNoOpInvokable.reset();
+    }
+
+    @Test
+    void testRescaleTerminatedByJobFinished() throws Exception {
+        // With the short shared cooldown the DefaultStateTransitionManager 
re-enters Idling on a
+        // wall-clock timer and terminates the in-progress rescale with
+        // NO_RESOURCES_OR_PARALLELISMS_CHANGE before the job finishes; 
goToFinished then finds it
+        // already terminated and its JOB_FINISHED stamp is ignored, so 
waiting cannot win.
+        // Widen the cooldown to keep the rescale in-progress until the job 
finishes.
+        startCluster(Duration.ofSeconds(60), Duration.ofSeconds(60));
+
+        final MiniCluster miniCluster = miniClusterResource.getMiniCluster();
+        final JobGraph jobGraph = createBlockingJobGraph(PARALLELISM);
+
+        miniCluster.submitJob(jobGraph).join();
+
+        waitForVertexParallelismReachedAndJobRunning(jobGraph, JOB_VERTEX_ID, 
PARALLELISM);
+
+        updateJobResourceRequirements(miniCluster, jobGraph, 1, PARALLELISM * 
2);
+
+        // The upper bound (PARALLELISM * 2) exceeds the available slots, so 
this rescale is only
+        // recorded in the history without changing the parallelism. Wait 
until it is recorded
+        // before unblocking, otherwise on a slow machine the task can finish 
first and the size-2
+        // condition below times out.
+        waitUntilConditionWithTimeout(
+                () -> getRescaleHistory(miniCluster, jobGraph).size() == 2, 
10000);
+
+        OnceBlockingNoOpInvokable.unblock();
+
+        // Generous budget: on a loaded CI leg the unblock-to-finish window 
can itself exceed 10s.
+        waitUntilConditionWithTimeout(
+                () -> {
+                    List<Rescale> rescaleHistory = 
getRescaleHistory(miniCluster, jobGraph);
+                    return hasRescaleHistoryMetCondition(
+                            rescaleHistory, 2, TerminatedReason.JOB_FINISHED);
+                },
+                60000);
+    }
+
+    @Test
+    void testRescaleTerminatedByJobCancelled() throws Exception {
+        startCluster(createEnabledConfiguration());
+
+        final MiniCluster miniCluster = miniClusterResource.getMiniCluster();
+        final JobGraph jobGraph = createBlockingJobGraph(PARALLELISM);
+
+        miniCluster.submitJob(jobGraph).join();
+
+        waitForVertexParallelismReachedAndJobRunning(jobGraph, JOB_VERTEX_ID, 
PARALLELISM);
+
+        updateJobResourceRequirements(miniCluster, jobGraph, 1, PARALLELISM * 
2);
+
+        miniCluster.cancelJob(jobGraph.getJobID());
+
+        waitUntilConditionWithTimeout(
+                () -> {
+                    List<Rescale> rescaleHistory = 
getRescaleHistory(miniCluster, jobGraph);
+                    return hasRescaleHistoryMetCondition(
+                            rescaleHistory, 2, TerminatedReason.JOB_CANCELED);
+                },
+                10000);
+    }
+
+    @Test
+    void testRescaleTerminatedByJobFailed() throws Exception {
+        startCluster(createEnabledConfiguration());
+
+        final MiniCluster miniCluster = miniClusterResource.getMiniCluster();
+        final JobGraph jobGraph = createBlockingJobGraph(PARALLELISM);
+
+        miniCluster.submitJob(jobGraph).join();
+
+        waitForVertexParallelismReachedAndJobRunning(jobGraph, JOB_VERTEX_ID, 
PARALLELISM);
+
+        miniCluster.terminateTaskManager(1);
+
+        waitForVertexParallelismReachedAndJobRunning(
+                jobGraph, JOB_VERTEX_ID, NUMBER_SLOTS_PER_TASK_MANAGER);
+
+        updateJobResourceRequirements(miniCluster, jobGraph, 1, PARALLELISM * 
2);
+
+        miniCluster.terminateTaskManager(0);
+
+        waitUntilConditionWithTimeout(
+                () -> {
+                    List<Rescale> rescaleHistory = 
getRescaleHistory(miniCluster, jobGraph);
+                    return hasRescaleHistoryMetCondition(
+                            rescaleHistory, 3, TerminatedReason.JOB_FAILED);
+                },
+                10000);
+    }
+
+    @Test
+    void testRescaleTerminatedByNoResourcesOrNoParallelismsChange() throws 
Exception {
+        // NO_RESOURCES_OR_PARALLELISMS_CHANGE is only stamped when the update 
RPC is processed
+        // during Cooldown and the manager re-enters Idling. Unlike the sibling
+        // testRescaleTerminatedByResourceRequirementsUpdated, this case must 
wait out the whole
+        // cooldown, so it cannot reuse 60s: 10s outlasts the RPC yet stays 
within the 60s budget.
+        startCluster(Duration.ofSeconds(10), Duration.ofMillis(50));
+
+        final MiniCluster miniCluster = miniClusterResource.getMiniCluster();
+        final JobGraph jobGraph = createBlockingJobGraph(PARALLELISM);
+        miniCluster.submitJob(jobGraph).join();
+        waitForVertexParallelismReachedAndJobRunning(jobGraph, JOB_VERTEX_ID, 
PARALLELISM);
+
+        updateJobResourceRequirements(miniCluster, jobGraph, 1, PARALLELISM * 
2);
+
+        waitUntilConditionWithTimeout(
+                () -> {
+                    List<Rescale> rescaleHistory = 
getRescaleHistory(miniCluster, jobGraph);
+                    return hasRescaleHistoryMetCondition(
+                            rescaleHistory,
+                            2,
+                            
TerminatedReason.NO_RESOURCES_OR_PARALLELISMS_CHANGE);
+                },
+                60000);
+    }
+
+    @Test
+    void testRescaleTerminatedByResourcesNotEnoughException() throws Exception 
{
+        startCluster(createEnabledConfiguration());
+
+        final MiniCluster miniCluster = miniClusterResource.getMiniCluster();
+        final JobGraph jobGraph = createBlockingJobGraph(PARALLELISM);
+        miniCluster.submitJob(jobGraph).join();
+        waitForVertexParallelismReachedAndJobRunning(jobGraph, JOB_VERTEX_ID, 
PARALLELISM);
+
+        miniCluster.terminateTaskManager(1);
+        miniCluster.terminateTaskManager(0);
+
+        waitUntilConditionWithTimeout(
+                () -> {
+                    List<Rescale> rescaleHistory = 
getRescaleHistory(miniCluster, jobGraph);
+                    return hasRescaleHistoryMetCondition(
+                            rescaleHistory, 2, 
TerminatedReason.EXCEPTION_OCCURRED);
+                },
+                100000);
+    }
+
+    @Test
+    void testRescaleTerminatedByResourceRequirementsUpdated() throws Exception 
{
+        // The second update only terminates the first rescale with 
RESOURCE_REQUIREMENTS_UPDATED
+        // while that rescale is still in-progress; once it is terminated, 
updateRescale is a no-op.
+        // With the short shared cooldown the manager re-enters Idling and 
terminates it on a
+        // wall-clock timer first, so waiting cannot win the race. Widening 
cooldown and
+        // stabilization to 60s keeps the rescale in-progress far longer than 
the RPC round trip.
+        startCluster(Duration.ofSeconds(60), Duration.ofSeconds(60));
+
+        final MiniCluster miniCluster = miniClusterResource.getMiniCluster();
+        final JobGraph jobGraph = createBlockingJobGraph(PARALLELISM);
+        miniCluster.submitJob(jobGraph).join();
+        waitForVertexParallelismReachedAndJobRunning(jobGraph, JOB_VERTEX_ID, 
PARALLELISM);
+
+        updateJobResourceRequirements(miniCluster, jobGraph, 1, PARALLELISM * 
2);
+        updateJobResourceRequirements(miniCluster, jobGraph, 1, PARALLELISM * 
3);
+
+        waitForVertexParallelismReachedAndJobRunning(jobGraph, JOB_VERTEX_ID, 
PARALLELISM);
+
+        final ExecutionGraphInfo executionGraphInfo =
+                miniCluster.getExecutionGraphInfo(jobGraph.getJobID()).join();
+        assertThat(executionGraphInfo.getRescalesStatsSnapshot()).isNotNull();
+        List<Rescale> rescaleHistory =
+                
executionGraphInfo.getRescalesStatsSnapshot().getRescaleHistory();
+        assertThat(rescaleHistory).hasSize(3);
+        Rescale rescale = rescaleHistory.get(1);
+        assertTerminalRelatedFields(rescale, 
TerminatedReason.RESOURCE_REQUIREMENTS_UPDATED);
+    }
+
+    // Private methods.
+    private static Configuration createEnabledConfiguration() {
+        final Configuration configuration = createConfiguration();
+        
configuration.set(WebOptions.MAX_ADAPTIVE_SCHEDULER_RESCALE_HISTORY_SIZE, 3);
+        return configuration;
+    }
+
+    /**
+     * Starts the fixture cluster with rescale history enabled plus the given 
executing-phase
+     * cooldown and resource-stabilization timeouts, kept wide enough to 
observe a rescale's
+     * terminated reason before the default short cooldown races past it.
+     */
+    private void startCluster(Duration cooldown, Duration 
resourceStabilizationTimeout)
+            throws Exception {
+        final Configuration configuration = createEnabledConfiguration();
+        
configuration.set(JobManagerOptions.SCHEDULER_EXECUTING_COOLDOWN_AFTER_RESCALING,
 cooldown);
+        configuration.set(
+                
JobManagerOptions.SCHEDULER_EXECUTING_RESOURCE_STABILIZATION_TIMEOUT,
+                resourceStabilizationTimeout);
+        startCluster(configuration);
+    }
+
+    private void waitUntilConditionWithTimeout(
+            SupplierWithException<Boolean, Exception> condition, long 
timeoutMillis)
+            throws Exception {
+        // Poll synchronously via waitUtil. The previous 
CompletableFuture#runAsync + get(timeout)
+        // wrapper hangs on JDK 11: on a ForkJoinPool worker (JUnit's 
executor) timedGet
+        // help-executes the unbounded poll loop inline on the waiting thread, 
so the timeout
+        // never fires.
+        org.apache.flink.core.testutils.CommonTestUtils.waitUtil(
+                () -> {
+                    try {
+                        return condition.get();
+                    } catch (Exception e) {
+                        throw new RuntimeException(e);
+                    }
+                },
+                Duration.ofMillis(timeoutMillis),
+                Duration.ofMillis(RETRY_INTERVAL_MILLIS),
+                "Condition was not met within " + timeoutMillis + " ms.");
+    }
+
+    private boolean hasRescaleHistoryMetCondition(
+            List<Rescale> rescales, int expectedSize, TerminatedReason 
terminatedReasonOfLatest) {
+        return rescales.size() == expectedSize
+                && terminatedReasonOfLatest == 
rescales.get(0).getTerminatedReason();
+    }
+
+    private static List<Rescale> getRescaleHistory(MiniCluster miniCluster, 
JobGraph jobGraph)
+            throws InterruptedException, ExecutionException {
+        ExecutionGraphInfo executionGraphInfo =
+                miniCluster.getExecutionGraphInfo(jobGraph.getJobID()).get();
+        return 
executionGraphInfo.getRescalesStatsSnapshot().getRescaleHistory();
+    }
+}
diff --git 
a/flink-runtime/src/test/java/org/apache/flink/runtime/scheduler/adaptive/timeline/RescaleTimelineITCase.java
 
b/flink-runtime/src/test/java/org/apache/flink/runtime/scheduler/adaptive/timeline/RescaleTimelineITCase.java
index e5920f282cb..486ff851477 100644
--- 
a/flink-runtime/src/test/java/org/apache/flink/runtime/scheduler/adaptive/timeline/RescaleTimelineITCase.java
+++ 
b/flink-runtime/src/test/java/org/apache/flink/runtime/scheduler/adaptive/timeline/RescaleTimelineITCase.java
@@ -18,68 +18,45 @@
 
 package org.apache.flink.runtime.scheduler.adaptive.timeline;
 
-import org.apache.flink.api.common.JobStatus;
 import org.apache.flink.configuration.Configuration;
-import org.apache.flink.configuration.JobManagerOptions;
 import org.apache.flink.configuration.WebOptions;
-import org.apache.flink.runtime.executiongraph.AccessExecutionJobVertex;
-import org.apache.flink.runtime.executiongraph.ArchivedExecutionGraph;
 import org.apache.flink.runtime.instance.SlotSharingGroupId;
 import org.apache.flink.runtime.jobgraph.JobGraph;
-import org.apache.flink.runtime.jobgraph.JobGraphTestUtils;
-import org.apache.flink.runtime.jobgraph.JobResourceRequirements;
-import org.apache.flink.runtime.jobgraph.JobVertex;
 import org.apache.flink.runtime.jobgraph.JobVertexID;
-import org.apache.flink.runtime.jobmanager.scheduler.SlotSharingGroup;
 import org.apache.flink.runtime.minicluster.MiniCluster;
 import 
org.apache.flink.runtime.rest.messages.job.rescales.JobRescaleDetails.VertexParallelismRescaleInfo;
 import org.apache.flink.runtime.rest.messages.job.rescales.SchedulerStateSpan;
 import org.apache.flink.runtime.scheduler.ExecutionGraphInfo;
 import org.apache.flink.runtime.testtasks.OnceBlockingNoOpInvokable;
-import org.apache.flink.runtime.testutils.CommonTestUtils;
-import org.apache.flink.runtime.testutils.MiniClusterResource;
-import org.apache.flink.runtime.testutils.MiniClusterResourceConfiguration;
-import org.apache.flink.streaming.util.RestartStrategyUtils;
 import org.apache.flink.testutils.junit.extensions.parameterized.Parameter;
 import 
org.apache.flink.testutils.junit.extensions.parameterized.ParameterizedTestExtension;
 import org.apache.flink.testutils.junit.extensions.parameterized.Parameters;
 import org.apache.flink.util.function.RunnableWithException;
-import org.apache.flink.util.function.SupplierWithException;
 
-import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.TestTemplate;
 import org.junit.jupiter.api.extension.ExtendWith;
 
-import java.time.Duration;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assumptions.assumeThat;
 
 /**
- * Test for recording rescale history by {@link DefaultRescaleTimeline} or 
{@link
- * RescaleTimeline.NoOpRescaleTimeline}.
+ * Tests for recording rescale history by {@link DefaultRescaleTimeline} or 
{@link
+ * RescaleTimeline.NoOpRescaleTimeline}. Each case runs once with rescale 
history disabled and once
+ * with it enabled, asserting the parameter-appropriate outcome. Cases that 
are only meaningful with
+ * rescale history enabled live in {@link RescaleTimelineHistoryEnabledITCase}.
  */
 @ExtendWith(ParameterizedTestExtension.class)
-class RescaleTimelineITCase {
-
-    private static final int NUMBER_SLOTS_PER_TASK_MANAGER = 2;
-    private static final int NUMBER_TASK_MANAGERS = 2;
-    private static final int PARALLELISM = NUMBER_SLOTS_PER_TASK_MANAGER * 
NUMBER_TASK_MANAGERS;
-    private static final JobVertexID JOB_VERTEX_ID = new JobVertexID();
-    // Matches the default poll interval of the 
CommonTestUtils#waitUntilCondition it replaced.
-    private static final long RETRY_INTERVAL_MILLIS = 100L;
+class RescaleTimelineITCase extends RescaleTimelineITCaseBase {
 
     @Parameter private Configuration configuration;
-    private MiniClusterResource miniClusterResource;
 
     @Parameters
     static Collection<Object[]> getClusterConfigs() {
@@ -91,42 +68,10 @@ class RescaleTimelineITCase {
                 new Object[] {confEnabledRescaleHistory});
     }
 
-    private static Configuration createConfiguration() {
-        final Configuration configuration = new Configuration();
-        configuration.set(JobManagerOptions.SCHEDULER, 
JobManagerOptions.SchedulerType.Adaptive);
-        configuration.set(
-                
JobManagerOptions.SCHEDULER_SUBMISSION_RESOURCE_STABILIZATION_TIMEOUT,
-                Duration.ofMillis(50));
-        configuration.set(
-                JobManagerOptions.SCHEDULER_EXECUTING_COOLDOWN_AFTER_RESCALING,
-                // Use the 0.1 seconds to trigger the long-time non-terminal 
rescale event after a
-                // rescaling.
-                Duration.ofMillis(100));
-        configuration.set(
-                
JobManagerOptions.SCHEDULER_EXECUTING_RESOURCE_STABILIZATION_TIMEOUT,
-                Duration.ofMillis(50));
-        configuration.set(
-                JobManagerOptions.SCHEDULER_SUBMISSION_RESOURCE_WAIT_TIMEOUT,
-                Duration.ofSeconds(2));
-        return configuration;
-    }
-
     @BeforeEach
     void setUp() throws Exception {
         OnceBlockingNoOpInvokable.reset();
-        this.miniClusterResource =
-                new MiniClusterResource(
-                        new MiniClusterResourceConfiguration.Builder()
-                                .setConfiguration(configuration)
-                                
.setNumberSlotsPerTaskManager(NUMBER_SLOTS_PER_TASK_MANAGER)
-                                .setNumberTaskManagers(NUMBER_TASK_MANAGERS)
-                                .build());
-        miniClusterResource.before();
-    }
-
-    @AfterEach
-    void tearDown() {
-        miniClusterResource.after();
+        startCluster(configuration);
     }
 
     // Tests for rescale trigger causes.
@@ -267,186 +212,6 @@ class RescaleTimelineITCase {
     @TestTemplate
     void testRescaleTerminatedBySucceeded() {}
 
-    @TestTemplate
-    void testRescaleTerminatedByJobFinished() throws Exception {
-        // This case only asserts on the recorded rescale history; skip the 
disabled-history
-        // parameter before the cluster rebuild below so it does not pay for 
an unused cluster.
-        assumeThat(enabledRescaleHistory(configuration)).isTrue();
-
-        // With the short shared cooldown the DefaultStateTransitionManager 
re-enters Idling on a
-        // wall-clock timer and terminates the in-progress rescale with
-        // NO_RESOURCES_OR_PARALLELISMS_CHANGE before the job finishes; 
goToFinished then finds it
-        // already terminated and its JOB_FINISHED stamp is ignored, so 
waiting cannot win.
-        // Widen the cooldown to keep the rescale in-progress until the job 
finishes.
-        rebuildClusterWithExecutingTimeouts(Duration.ofSeconds(60), 
Duration.ofSeconds(60));
-
-        final MiniCluster miniCluster = miniClusterResource.getMiniCluster();
-        final JobGraph jobGraph = createBlockingJobGraph(PARALLELISM);
-
-        miniCluster.submitJob(jobGraph).join();
-
-        waitForVertexParallelismReachedAndJobRunning(jobGraph, JOB_VERTEX_ID, 
PARALLELISM);
-
-        updateJobResourceRequirements(miniCluster, jobGraph, 1, PARALLELISM * 
2);
-
-        // The upper bound (PARALLELISM * 2) exceeds the available slots, so 
this rescale is only
-        // recorded in the history without changing the parallelism. Wait 
until it is recorded
-        // before unblocking, otherwise on a slow machine the task can finish 
first and the size-2
-        // condition below times out.
-        waitUntilConditionWithTimeout(
-                () -> getRescaleHistory(miniCluster, jobGraph).size() == 2, 
10000);
-
-        OnceBlockingNoOpInvokable.unblock();
-
-        // Generous budget: on a loaded CI leg the unblock-to-finish window 
can itself exceed 10s.
-        waitUntilConditionWithTimeout(
-                () -> {
-                    List<Rescale> rescaleHistory = 
getRescaleHistory(miniCluster, jobGraph);
-                    return hasRescaleHistoryMetCondition(
-                            rescaleHistory, 2, TerminatedReason.JOB_FINISHED);
-                },
-                60000);
-    }
-
-    @TestTemplate
-    void testRescaleTerminatedByJobCancelled() throws Exception {
-        final MiniCluster miniCluster = miniClusterResource.getMiniCluster();
-        final JobGraph jobGraph = createBlockingJobGraph(PARALLELISM);
-
-        miniCluster.submitJob(jobGraph).join();
-
-        waitForVertexParallelismReachedAndJobRunning(jobGraph, JOB_VERTEX_ID, 
PARALLELISM);
-
-        updateJobResourceRequirements(miniCluster, jobGraph, 1, PARALLELISM * 
2);
-
-        miniCluster.cancelJob(jobGraph.getJobID());
-
-        assumeThat(enabledRescaleHistory(configuration)).isTrue();
-        waitUntilConditionWithTimeout(
-                () -> {
-                    List<Rescale> rescaleHistory = 
getRescaleHistory(miniCluster, jobGraph);
-                    return hasRescaleHistoryMetCondition(
-                            rescaleHistory, 2, TerminatedReason.JOB_CANCELED);
-                },
-                10000);
-    }
-
-    @TestTemplate
-    void testRescaleTerminatedByJobFailed() throws Exception {
-        final MiniCluster miniCluster = miniClusterResource.getMiniCluster();
-        final JobGraph jobGraph = createBlockingJobGraph(PARALLELISM);
-
-        miniCluster.submitJob(jobGraph).join();
-
-        waitForVertexParallelismReachedAndJobRunning(jobGraph, JOB_VERTEX_ID, 
PARALLELISM);
-
-        miniCluster.terminateTaskManager(1);
-
-        waitForVertexParallelismReachedAndJobRunning(
-                jobGraph, JOB_VERTEX_ID, NUMBER_SLOTS_PER_TASK_MANAGER);
-
-        updateJobResourceRequirements(miniCluster, jobGraph, 1, PARALLELISM * 
2);
-
-        miniCluster.terminateTaskManager(0);
-
-        assumeThat(enabledRescaleHistory(configuration)).isTrue();
-        waitUntilConditionWithTimeout(
-                () -> {
-                    List<Rescale> rescaleHistory = 
getRescaleHistory(miniCluster, jobGraph);
-                    return hasRescaleHistoryMetCondition(
-                            rescaleHistory, 3, TerminatedReason.JOB_FAILED);
-                },
-                10000);
-    }
-
-    @TestTemplate
-    void testRescaleTerminatedByNoResourcesOrNoParallelismsChange() throws 
Exception {
-        // This case only asserts on the recorded rescale history; skip the 
disabled-history
-        // parameter before the cluster rebuild below so it does not pay for 
an unused cluster.
-        assumeThat(enabledRescaleHistory(configuration)).isTrue();
-
-        // NO_RESOURCES_OR_PARALLELISMS_CHANGE is only stamped when the update 
RPC is processed
-        // during Cooldown and the manager re-enters Idling. Unlike the sibling
-        // testRescaleTerminatedByResourceRequirementsUpdated, this case must 
wait out the whole
-        // cooldown, so it cannot reuse 60s: 10s outlasts the RPC yet stays 
within the 60s budget.
-        rebuildClusterWithExecutingTimeouts(Duration.ofSeconds(10), 
Duration.ofMillis(50));
-
-        final MiniCluster miniCluster = miniClusterResource.getMiniCluster();
-        final JobGraph jobGraph = createBlockingJobGraph(PARALLELISM);
-        miniCluster.submitJob(jobGraph).join();
-        waitForVertexParallelismReachedAndJobRunning(jobGraph, JOB_VERTEX_ID, 
PARALLELISM);
-
-        updateJobResourceRequirements(miniCluster, jobGraph, 1, PARALLELISM * 
2);
-
-        waitUntilConditionWithTimeout(
-                () -> {
-                    List<Rescale> rescaleHistory = 
getRescaleHistory(miniCluster, jobGraph);
-                    return hasRescaleHistoryMetCondition(
-                            rescaleHistory,
-                            2,
-                            
TerminatedReason.NO_RESOURCES_OR_PARALLELISMS_CHANGE);
-                },
-                60000);
-    }
-
-    @TestTemplate
-    void testRescaleTerminatedByResourcesNotEnoughException() throws Exception 
{
-        final MiniCluster miniCluster = miniClusterResource.getMiniCluster();
-        final JobGraph jobGraph = createBlockingJobGraph(PARALLELISM);
-        miniCluster.submitJob(jobGraph).join();
-        waitForVertexParallelismReachedAndJobRunning(jobGraph, JOB_VERTEX_ID, 
PARALLELISM);
-
-        miniCluster.terminateTaskManager(1);
-        miniCluster.terminateTaskManager(0);
-
-        assumeThat(enabledRescaleHistory(configuration)).isTrue();
-        waitUntilConditionWithTimeout(
-                () -> {
-                    List<Rescale> rescaleHistory = 
getRescaleHistory(miniCluster, jobGraph);
-                    return hasRescaleHistoryMetCondition(
-                            rescaleHistory, 2, 
TerminatedReason.EXCEPTION_OCCURRED);
-                },
-                100000);
-    }
-
-    @TestTemplate
-    void testRescaleTerminatedByResourceRequirementsUpdated() throws Exception 
{
-        // This case only asserts on the recorded rescale history; skip the 
disabled-history
-        // parameter before the cluster rebuild below so it does not pay for 
an unused cluster.
-        assumeThat(enabledRescaleHistory(configuration)).isTrue();
-
-        // The second update only terminates the first rescale with 
RESOURCE_REQUIREMENTS_UPDATED
-        // while that rescale is still in-progress; once it is terminated, 
updateRescale is a no-op.
-        // With the short shared cooldown the manager re-enters Idling and 
terminates it on a
-        // wall-clock timer first, so waiting cannot win the race. Widening 
cooldown and
-        // stabilization to 60s keeps the rescale in-progress far longer than 
the RPC round trip.
-        rebuildClusterWithExecutingTimeouts(Duration.ofSeconds(60), 
Duration.ofSeconds(60));
-
-        final MiniCluster miniCluster = miniClusterResource.getMiniCluster();
-        final JobGraph jobGraph = createBlockingJobGraph(PARALLELISM);
-        miniCluster.submitJob(jobGraph).join();
-        waitForVertexParallelismReachedAndJobRunning(jobGraph, JOB_VERTEX_ID, 
PARALLELISM);
-
-        updateJobResourceRequirements(miniCluster, jobGraph, 1, PARALLELISM * 
2);
-        updateJobResourceRequirements(miniCluster, jobGraph, 1, PARALLELISM * 
3);
-
-        waitForVertexParallelismReachedAndJobRunning(jobGraph, JOB_VERTEX_ID, 
PARALLELISM);
-
-        final ExecutionGraphInfo executionGraphInfo =
-                miniCluster.getExecutionGraphInfo(jobGraph.getJobID()).join();
-        runAdaptedParameterizedAssertion(
-                executionGraphInfo,
-                () -> {
-                    
assertThat(executionGraphInfo.getRescalesStatsSnapshot()).isNotNull();
-                    List<Rescale> rescaleHistory =
-                            
executionGraphInfo.getRescalesStatsSnapshot().getRescaleHistory();
-                    assertThat(rescaleHistory).hasSize(3);
-                    Rescale rescale = rescaleHistory.get(1);
-                    assertTerminalRelatedFields(
-                            rescale, 
TerminatedReason.RESOURCE_REQUIREMENTS_UPDATED);
-                });
-    }
-
     /**
      * The test case is disabled after processing by
      * https://lists.apache.org/thread/hh7w2p6lnmbo1q6d9ngkttdyrw4lp74h. Merge 
the current
@@ -624,18 +389,6 @@ class RescaleTimelineITCase {
     }
 
     // Private methods.
-    private JobGraph createBlockingJobGraph(int parallelism) {
-        final JobVertex blockingOperator = new JobVertex("Blocking operator", 
JOB_VERTEX_ID);
-        SlotSharingGroup sharingGroup = new SlotSharingGroup();
-        sharingGroup.setSlotSharingGroupName("slot-sharing-group-A");
-        blockingOperator.setSlotSharingGroup(sharingGroup);
-        blockingOperator.setInvokableClass(OnceBlockingNoOpInvokable.class);
-        blockingOperator.setParallelism(parallelism);
-        final JobGraph jobGraph = 
JobGraphTestUtils.streamingJobGraph(blockingOperator);
-        RestartStrategyUtils.configureFixedDelayRestartStrategy(jobGraph, 1, 
0L);
-        return jobGraph;
-    }
-
     private void runAdaptedParameterizedAssertion(
             ExecutionGraphInfo executionGraphInfo,
             RunnableWithException assertionForEnabledRescaleHistory)
@@ -652,15 +405,6 @@ class RescaleTimelineITCase {
         return 
configuration.get(WebOptions.MAX_ADAPTIVE_SCHEDULER_RESCALE_HISTORY_SIZE) > 0;
     }
 
-    private static void assertTerminalRelatedFields(
-            Rescale rescale, TerminatedReason expectedTerminatedReason) {
-        TerminatedReason terminatedReason = rescale.getTerminatedReason();
-        assertThat(terminatedReason).isEqualTo(expectedTerminatedReason);
-        assertThat(terminatedReason.getTerminalState())
-                .isEqualTo(rescale.getTerminalState())
-                .isEqualTo(expectedTerminatedReason.getTerminalState());
-    }
-
     private static void assertTriggerCause(Rescale rescale, TriggerCause 
expectedTriggerCause) {
         assertThat(rescale).isNotNull();
         assertThat(rescale.getTriggerCause()).isEqualTo(expectedTriggerCause);
@@ -687,99 +431,4 @@ class RescaleTimelineITCase {
         assertThat(vpr.getSlotSharingGroupId()).isNotNull();
         assertThat(vpr.getSlotSharingGroupName()).isNotNull();
     }
-
-    private void waitUntilConditionWithTimeout(
-            SupplierWithException<Boolean, Exception> condition, long 
timeoutMillis)
-            throws Exception {
-        // Poll synchronously via waitUtil. The previous 
CompletableFuture#runAsync + get(timeout)
-        // wrapper hangs on JDK 11: on a ForkJoinPool worker (JUnit's 
executor) timedGet
-        // help-executes the unbounded poll loop inline on the waiting thread, 
so the timeout
-        // never fires.
-        org.apache.flink.core.testutils.CommonTestUtils.waitUtil(
-                () -> {
-                    try {
-                        return condition.get();
-                    } catch (Exception e) {
-                        throw new RuntimeException(e);
-                    }
-                },
-                Duration.ofMillis(timeoutMillis),
-                Duration.ofMillis(RETRY_INTERVAL_MILLIS),
-                "Condition was not met within " + timeoutMillis + " ms.");
-    }
-
-    private void waitForVertexParallelismReachedAndJobRunning(
-            JobGraph jobGraph, JobVertexID jobVertexId, int targetParallelism) 
throws Exception {
-        CommonTestUtils.waitUntilCondition(
-                () -> {
-                    final ArchivedExecutionGraph archivedExecutionGraph =
-                            miniClusterResource
-                                    .getMiniCluster()
-                                    
.getArchivedExecutionGraph(jobGraph.getJobID())
-                                    .get();
-                    final AccessExecutionJobVertex executionJobVertex =
-                            
archivedExecutionGraph.getAllVertices().get(jobVertexId);
-                    if (executionJobVertex == null) {
-                        // parallelism was not yet determined
-                        return false;
-                    }
-                    return executionJobVertex.getParallelism() == 
targetParallelism;
-                });
-        CommonTestUtils.waitUntilCondition(
-                () ->
-                        
miniClusterResource.getMiniCluster().getJobStatus(jobGraph.getJobID()).get()
-                                == JobStatus.RUNNING);
-    }
-
-    /**
-     * Rebuilds the shared fixture cluster in place with the given 
executing-phase cooldown and
-     * resource-stabilization timeouts. Rebuilding in place rather than 
starting a second cluster
-     * keeps a single cluster running so the {@link AfterEach} teardown still 
applies.
-     */
-    private void rebuildClusterWithExecutingTimeouts(
-            Duration cooldown, Duration resourceStabilizationTimeout) throws 
Exception {
-        miniClusterResource.after();
-        final Configuration testConfiguration = new 
Configuration(configuration);
-        testConfiguration.set(
-                
JobManagerOptions.SCHEDULER_EXECUTING_COOLDOWN_AFTER_RESCALING, cooldown);
-        testConfiguration.set(
-                
JobManagerOptions.SCHEDULER_EXECUTING_RESOURCE_STABILIZATION_TIMEOUT,
-                resourceStabilizationTimeout);
-        miniClusterResource =
-                new MiniClusterResource(
-                        new MiniClusterResourceConfiguration.Builder()
-                                .setConfiguration(testConfiguration)
-                                
.setNumberSlotsPerTaskManager(NUMBER_SLOTS_PER_TASK_MANAGER)
-                                .setNumberTaskManagers(NUMBER_TASK_MANAGERS)
-                                .build());
-        miniClusterResource.before();
-    }
-
-    private void updateJobResourceRequirements(
-            MiniCluster miniCluster, JobGraph jobGraph, int lowerBound, int 
upperBound)
-            throws ExecutionException, InterruptedException {
-        miniCluster
-                .updateJobResourceRequirements(
-                        jobGraph.getJobID(),
-                        JobResourceRequirements.newBuilder()
-                                .setParallelismForJobVertex(
-                                        
jobGraph.getVertices().iterator().next().getID(),
-                                        lowerBound,
-                                        upperBound)
-                                .build())
-                .get();
-    }
-
-    private boolean hasRescaleHistoryMetCondition(
-            List<Rescale> rescales, int expectedSize, TerminatedReason 
terminatedReasonOfLatest) {
-        return rescales.size() == expectedSize
-                && terminatedReasonOfLatest == 
rescales.get(0).getTerminatedReason();
-    }
-
-    private static List<Rescale> getRescaleHistory(MiniCluster miniCluster, 
JobGraph jobGraph)
-            throws InterruptedException, ExecutionException {
-        ExecutionGraphInfo executionGraphInfo =
-                miniCluster.getExecutionGraphInfo(jobGraph.getJobID()).get();
-        return 
executionGraphInfo.getRescalesStatsSnapshot().getRescaleHistory();
-    }
 }
diff --git 
a/flink-runtime/src/test/java/org/apache/flink/runtime/scheduler/adaptive/timeline/RescaleTimelineITCaseBase.java
 
b/flink-runtime/src/test/java/org/apache/flink/runtime/scheduler/adaptive/timeline/RescaleTimelineITCaseBase.java
new file mode 100644
index 00000000000..0a94648250f
--- /dev/null
+++ 
b/flink-runtime/src/test/java/org/apache/flink/runtime/scheduler/adaptive/timeline/RescaleTimelineITCaseBase.java
@@ -0,0 +1,156 @@
+/*
+ * 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.flink.runtime.scheduler.adaptive.timeline;
+
+import org.apache.flink.api.common.JobStatus;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.JobManagerOptions;
+import org.apache.flink.runtime.executiongraph.AccessExecutionJobVertex;
+import org.apache.flink.runtime.executiongraph.ArchivedExecutionGraph;
+import org.apache.flink.runtime.jobgraph.JobGraph;
+import org.apache.flink.runtime.jobgraph.JobGraphTestUtils;
+import org.apache.flink.runtime.jobgraph.JobResourceRequirements;
+import org.apache.flink.runtime.jobgraph.JobVertex;
+import org.apache.flink.runtime.jobgraph.JobVertexID;
+import org.apache.flink.runtime.jobmanager.scheduler.SlotSharingGroup;
+import org.apache.flink.runtime.minicluster.MiniCluster;
+import org.apache.flink.runtime.testtasks.OnceBlockingNoOpInvokable;
+import org.apache.flink.runtime.testutils.CommonTestUtils;
+import org.apache.flink.runtime.testutils.MiniClusterResource;
+import org.apache.flink.runtime.testutils.MiniClusterResourceConfiguration;
+import org.apache.flink.streaming.util.RestartStrategyUtils;
+
+import org.junit.jupiter.api.AfterEach;
+
+import java.time.Duration;
+import java.util.concurrent.ExecutionException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Shared fixture and helpers for the {@link RescaleTimeline} integration 
tests, reused by {@link
+ * RescaleTimelineITCase} and {@link RescaleTimelineHistoryEnabledITCase}.
+ */
+abstract class RescaleTimelineITCaseBase {
+
+    protected static final int NUMBER_SLOTS_PER_TASK_MANAGER = 2;
+    protected static final int NUMBER_TASK_MANAGERS = 2;
+    protected static final int PARALLELISM = NUMBER_SLOTS_PER_TASK_MANAGER * 
NUMBER_TASK_MANAGERS;
+    protected static final JobVertexID JOB_VERTEX_ID = new JobVertexID();
+
+    protected MiniClusterResource miniClusterResource;
+
+    protected static Configuration createConfiguration() {
+        final Configuration configuration = new Configuration();
+        configuration.set(JobManagerOptions.SCHEDULER, 
JobManagerOptions.SchedulerType.Adaptive);
+        configuration.set(
+                
JobManagerOptions.SCHEDULER_SUBMISSION_RESOURCE_STABILIZATION_TIMEOUT,
+                Duration.ofMillis(50));
+        configuration.set(
+                JobManagerOptions.SCHEDULER_EXECUTING_COOLDOWN_AFTER_RESCALING,
+                // Use the 0.1 seconds to trigger the long-time non-terminal 
rescale event after a
+                // rescaling.
+                Duration.ofMillis(100));
+        configuration.set(
+                
JobManagerOptions.SCHEDULER_EXECUTING_RESOURCE_STABILIZATION_TIMEOUT,
+                Duration.ofMillis(50));
+        configuration.set(
+                JobManagerOptions.SCHEDULER_SUBMISSION_RESOURCE_WAIT_TIMEOUT,
+                Duration.ofSeconds(2));
+        return configuration;
+    }
+
+    /** Builds and starts the fixture cluster with the given configuration. */
+    protected void startCluster(Configuration configuration) throws Exception {
+        miniClusterResource =
+                new MiniClusterResource(
+                        new MiniClusterResourceConfiguration.Builder()
+                                .setConfiguration(configuration)
+                                
.setNumberSlotsPerTaskManager(NUMBER_SLOTS_PER_TASK_MANAGER)
+                                .setNumberTaskManagers(NUMBER_TASK_MANAGERS)
+                                .build());
+        miniClusterResource.before();
+    }
+
+    @AfterEach
+    void tearDown() {
+        if (miniClusterResource != null) {
+            miniClusterResource.after();
+        }
+    }
+
+    protected JobGraph createBlockingJobGraph(int parallelism) {
+        final JobVertex blockingOperator = new JobVertex("Blocking operator", 
JOB_VERTEX_ID);
+        SlotSharingGroup sharingGroup = new SlotSharingGroup();
+        sharingGroup.setSlotSharingGroupName("slot-sharing-group-A");
+        blockingOperator.setSlotSharingGroup(sharingGroup);
+        blockingOperator.setInvokableClass(OnceBlockingNoOpInvokable.class);
+        blockingOperator.setParallelism(parallelism);
+        final JobGraph jobGraph = 
JobGraphTestUtils.streamingJobGraph(blockingOperator);
+        RestartStrategyUtils.configureFixedDelayRestartStrategy(jobGraph, 1, 
0L);
+        return jobGraph;
+    }
+
+    protected void waitForVertexParallelismReachedAndJobRunning(
+            JobGraph jobGraph, JobVertexID jobVertexId, int targetParallelism) 
throws Exception {
+        CommonTestUtils.waitUntilCondition(
+                () -> {
+                    final ArchivedExecutionGraph archivedExecutionGraph =
+                            miniClusterResource
+                                    .getMiniCluster()
+                                    
.getArchivedExecutionGraph(jobGraph.getJobID())
+                                    .get();
+                    final AccessExecutionJobVertex executionJobVertex =
+                            
archivedExecutionGraph.getAllVertices().get(jobVertexId);
+                    if (executionJobVertex == null) {
+                        // parallelism was not yet determined
+                        return false;
+                    }
+                    return executionJobVertex.getParallelism() == 
targetParallelism;
+                });
+        CommonTestUtils.waitUntilCondition(
+                () ->
+                        
miniClusterResource.getMiniCluster().getJobStatus(jobGraph.getJobID()).get()
+                                == JobStatus.RUNNING);
+    }
+
+    protected void updateJobResourceRequirements(
+            MiniCluster miniCluster, JobGraph jobGraph, int lowerBound, int 
upperBound)
+            throws ExecutionException, InterruptedException {
+        miniCluster
+                .updateJobResourceRequirements(
+                        jobGraph.getJobID(),
+                        JobResourceRequirements.newBuilder()
+                                .setParallelismForJobVertex(
+                                        
jobGraph.getVertices().iterator().next().getID(),
+                                        lowerBound,
+                                        upperBound)
+                                .build())
+                .get();
+    }
+
+    protected static void assertTerminalRelatedFields(
+            Rescale rescale, TerminatedReason expectedTerminatedReason) {
+        TerminatedReason terminatedReason = rescale.getTerminatedReason();
+        assertThat(terminatedReason).isEqualTo(expectedTerminatedReason);
+        assertThat(terminatedReason.getTerminalState())
+                .isEqualTo(rescale.getTerminalState())
+                .isEqualTo(expectedTerminatedReason.getTerminalState());
+    }
+}

Reply via email to