You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ozone.apache.org by ad...@apache.org on 2026/10/03 09:51:46 UTC

(ozone) branch master updated: HDDS-14337. Fix SCM startup failure near year boundary when seeding localId (#11184)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 898bb4310f2 HDDS-14337. Fix SCM startup failure near year boundary when seeding localId (#11184)
898bb4310f2 is described below

commit 898bb4310f24be462ea6301ba62488537f5e975e
Author: SaketaChalamchala <sa...@gmail.com>
AuthorDate: Sat Oct 3 02:51:40 2026 -0700

    HDDS-14337. Fix SCM startup failure near year boundary when seeding localId (#11184)
---
 .../hadoop/hdds/scm/ha/SequenceIdGenerator.java    | 17 +++++--
 .../hdds/scm/ha/TestSequenceIDGenerator.java       | 53 ++++++++++++++++++++++
 2 files changed, 66 insertions(+), 4 deletions(-)

diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SequenceIdGenerator.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SequenceIdGenerator.java
index 08916b65dc5..30b0a62553f 100644
--- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SequenceIdGenerator.java
+++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SequenceIdGenerator.java
@@ -26,6 +26,7 @@
 import java.math.BigInteger;
 import java.security.cert.X509Certificate;
 import java.time.LocalDate;
+import java.time.ZoneOffset;
 import java.util.Collections;
 import java.util.EnumMap;
 import java.util.Map;
@@ -346,10 +347,7 @@ public static void upgradeToSequenceId(SCMMetadataStore scmMetadataStore)
     // scm.db from leader SCM, and drop its own scm.db. Thus the upgrade
     // operations can take effect exactly once in a SCM HA cluster.
     if (sequenceIdTable.get(SequenceIdType.localId) == null) {
-      long millisSinceEpoch = TimeUnit.DAYS.toMillis(
-          LocalDate.of(LocalDate.now().getYear() + 1, 1, 1).toEpochDay());
-
-      long localId = millisSinceEpoch << Short.SIZE;
+      long localId = computeInitialLocalId(LocalDate.now(ZoneOffset.UTC).getYear());
       Preconditions.checkArgument(localId > UniqueId.next());
 
       sequenceIdTable.put(SequenceIdType.localId, localId);
@@ -386,6 +384,17 @@ public static void upgradeToSequenceId(SCMMetadataStore scmMetadataStore)
     upgradeToCertificateSequenceId(scmMetadataStore, false);
   }
 
+  /**
+   * Compute the initial localId for a new SCM based on the UTC calendar year.
+   * Uses Jan 2 of (utcYear + 1) so the value stays above legacy UniqueId values
+   * even if the UTC year rolls over before UniqueId.next() is checked.
+   */
+  static long computeInitialLocalId(int utcYear) {
+    long millisSinceEpoch = TimeUnit.DAYS.toMillis(
+        LocalDate.of(utcYear + 1, 1, 2).toEpochDay());
+    return millisSinceEpoch << Short.SIZE;
+  }
+
   public static void upgradeToCertificateSequenceId(
       SCMMetadataStore scmMetadataStore, boolean force) throws IOException {
     Table<SequenceIdType, Long> sequenceIdTable = scmMetadataStore.getSequenceIdTable();
diff --git a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSequenceIDGenerator.java b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSequenceIDGenerator.java
index 1f07927a49d..14fe7d12a3f 100644
--- a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSequenceIDGenerator.java
+++ b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSequenceIDGenerator.java
@@ -20,25 +20,34 @@
 import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_SEQUENCE_ID_BATCH_SIZE;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mockStatic;
 import static org.mockito.Mockito.spy;
 
 import java.io.File;
+import java.time.LocalDate;
+import java.time.ZoneOffset;
 import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import org.apache.hadoop.hdds.HddsUtils;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.scm.exceptions.SCMException;
 import org.apache.hadoop.hdds.scm.metadata.SCMDBTransactionBufferImpl;
 import org.apache.hadoop.hdds.scm.metadata.SCMMetadataStore;
 import org.apache.hadoop.hdds.scm.metadata.SCMMetadataStoreImpl;
+import org.apache.hadoop.hdds.utils.UniqueId;
 import org.apache.hadoop.hdds.utils.db.Table;
 import org.apache.hadoop.ozone.container.common.SCMTestUtils;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
+import org.mockito.MockedStatic;
 
 /**
  * Tests for {@link SequenceIdGenerator}.
@@ -270,4 +279,48 @@ public void testAllocateBatchFailsOnUnknownSequenceId() throws Exception {
       // ignore
     }
   }
+
+  /**
+   * Regression test for HDDS-14337. Simulates SCM first start when UTC is
+   * already in the new year (Jan 1 2026) while the old formula used the
+   * local timezone year (2025). Verifies upgradeToSequenceId succeeds and
+   * block localIds are allocated.
+   */
+  @SuppressWarnings("ReturnValueIgnored")
+  @Test
+  public void testUpgradeToSequenceIdNearYearBoundary() throws Exception {
+    OzoneConfiguration conf = SCMTestUtils.getConf(testDir);
+    SCMMetadataStore scmMetadataStore = new SCMMetadataStoreImpl(conf);
+    scmMetadataStore.start(conf);
+    SCMHAManager scmHAManager = SCMHAManagerStub
+        .getInstance(true, new SCMDBTransactionBufferImpl());
+
+    LocalDate utcDate = LocalDate.of(2026, 1, 1);
+    long simulatedUtcMillis = TimeUnit.DAYS.toMillis(utcDate.toEpochDay())
+        + TimeUnit.HOURS.toMillis(6);
+    // UTC year is 2026; seed is Jan 2, 2027 (Jan 1 of next year + 1 day).
+    long expectedLocalId = TimeUnit.DAYS.toMillis(
+        LocalDate.of(2027, 1, 2).toEpochDay()) << Short.SIZE;
+
+    try (MockedStatic<HddsUtils> mockedTime = mockStatic(HddsUtils.class);
+         MockedStatic<LocalDate> mockedLocalDate = mockStatic(LocalDate.class, CALLS_REAL_METHODS)) {
+      mockedTime.when(HddsUtils::getTime).thenReturn(simulatedUtcMillis);
+      mockedLocalDate.when(() -> LocalDate.now(ZoneOffset.UTC)).thenReturn(utcDate);
+
+      long legacyLocalId = TimeUnit.DAYS.toMillis(
+          LocalDate.of(2026, 1, 1).toEpochDay()) << Short.SIZE;
+      assertFalse(legacyLocalId > UniqueId.next(),
+          "pre-HDDS-14337 local timezone year would fail the upgrade check");
+
+      SequenceIdGenerator.upgradeToSequenceId(scmMetadataStore);
+      Long initialLocalId = scmMetadataStore.getSequenceIdTable().get(SequenceIdType.localId);
+      assertNotNull(initialLocalId);
+      assertEquals(expectedLocalId, initialLocalId.longValue());
+
+      SequenceIdGenerator sequenceIdGen = new SequenceIdGenerator(
+          conf, scmHAManager, scmMetadataStore.getSequenceIdTable());
+      assertEquals(expectedLocalId + 1, sequenceIdGen.getNextId(SequenceIdType.localId));
+      assertEquals(expectedLocalId + 2, sequenceIdGen.getNextId(SequenceIdType.localId));
+    }
+  }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@ozone.apache.org
For additional commands, e-mail: commits-help@ozone.apache.org