errose28 commented on code in PR #11800:
URL: https://github.com/apache/ozone/pull/11800#discussion_r4461591136
##########
hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/upgrade/TestScmStartupInvalidApparentVersion.java:
##########
Review Comment:
We should rename this class since it now testing valid DB version cases.
##########
hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/upgrade/TestScmStartupInvalidApparentVersion.java:
##########
@@ -59,8 +68,97 @@ public void
testStartupFailsWhenApparentVersionBeyondLastKnownComponentVersion()
assertStartupFailsWithComponentVersionMessage(SOFTWARE_VERSION.serialize()
+ 1);
}
+ /**
+ * An SCM that stops after replacing its DB with a finalized checkpoint, but
before updating its VERSION file, cannot
+ * replay the finalization from the Raft log, so it should finalize up to
the version in its DB on restart.
+ */
+ @Test
+ public void testStartupFinalizesToApparentVersionInDB() throws Exception {
+ OzoneConfiguration conf = createScmStorage(INITIAL_VERSION.serialize());
+ StorageContainerManager scm = HddsTestUtils.getScm(conf, new
SCMConfigurator());
+ try {
+ // Without a version in the DB, startup leaves the SCM pre-finalized.
+
assertThat(scm.getScmMetadataStore().getMetaTable().get(APPARENT_VERSION_KEY)).isNull();
+
assertThat(scm.getVersionManager().getApparentVersion()).isEqualTo(INITIAL_VERSION);
+
+ // The DB is ahead of the VERSION file, as the interrupted install
leaves it. The SCM finalizes to the
+ // software version
+ scm = restartWithDBVersion(scm, SOFTWARE_VERSION);
+ // finalizeFromDBIfRequired is called by scm.start(), but calling that
here makes the tests slow and we would
+ // need more setup for safemode etc. As this test is unit testing
finalizeFromDBIfRequired we call it directly
+ // to keep the tests simple.
+
scm.getVersionManager().finalizeFromDBIfRequired(scm.getScmMetadataStore().getMetaTable());
+ assertVersions(scm, SOFTWARE_VERSION, SOFTWARE_VERSION);
+ assertThat(scm.getVersionManager().needsFinalization()).isFalse();
+ } finally {
+ scm.stop();
+ scm.join();
+ }
+ }
+
+ /**
+ * The VERSION file is ahead of the DB. This can happen if finalization
wrote the VERSION file (inside Ratis
+ * applyTransaction) but the process crashed before the Ratis transaction
buffer was flushed to RocksDB.
+ * Ratis log replay should correct this, but finalizeFromSnapshotIfRequired
also updates the DB as a safety net.
Review Comment:
This and line 114 have a stale method name in the comment.
##########
hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java:
##########
@@ -1536,6 +1536,11 @@ public CertificateStore getCertificateStore() {
*/
@Override
public void start() throws IOException {
+ // If the SCM stopped during a snapshot install after the DB was replaced,
the VERSION file is behind the DB, and
+ // the finalization included in the snapshot is not in the Raft log to be
replayed.
+ // The metadata store may have been closed by a prior stop(); start() is
idempotent and reopens it if needed.
+ scmMetadataStore.start(configuration);
Review Comment:
`TestStorageContainerManagerHA` has a bug. We should not add a workaround
for this in prod code. This is Claude's alternative to review:
---
The test now passes with all three SCMs actually started, and with the
DB-reopen line removed from `StorageContainerManager.start()`. It takes about 5
s longer, because the old version only passed with one SCM silently failing to
start.
| Version | Runs (test time)
|
| ------------------------------------- | ----------------------------------
|
| Before (current PR branch) | 18.70 s, 18.61 s, 18.53 s, 18.72 s
|
| After (test fix, reopen line removed) | 24.16 s, 24.08 s, 23.93 s, 24.09 s
|
**Why the old test was faster:** it only ever brought up two of the three
SCMs.
- The cluster's SCM list comes from a `HashMap`, so `get(0)` is always
scmNode-2. That SCM had never been started; the running one is scmNode-1.
- The old test stopped scmNode-2, called `start()` again on the
still-running scmNode-1, and started scmNode-3.
- Starting scmNode-2 again threw `IllegalStateException: CLOSED -> STARTING`
from its already-closed Ratis server. The test only catches `IOException`, so
that thread died unnoticed and the test passed.
- In the fixed run, scmNode-2 and scmNode-3 join the leader at the same
time. The second join waits about 5 s behind the first Ratis reconfiguration.
**The fix (test only, plus removing the reopen line):**
- It leaves the running SCM alone and starts only the SCMs that were never
started, all at once. It counts any exception as a failure and asserts there
were none.
- It drops the retry loop. That loop restarted stopped instances, which
can't work. Replacing them with new instances doesn't work in one JVM either:
an SCM's RPC ports are bound in its constructor, and stopping an SCM that never
started doesn't release them.
**Two other ways I tried that didn't work:**
- **Replace every SCM with a new instance:** this hit `BindException`,
because of those unreleased ports.
- **Replace only the running SCM with a new instance, starting all three
together:** this hung until the 5-minute timeout. SCMs 2 and 3 each block in
`addSCM` waiting on the other, whose port is bound but not yet serving. I only
saw this in the test. In production the gap between creating an SCM and
starting it is much shorter, but the same hang might be possible.
A separate flaw I left alone: the leader-count loop `break`s at the first
leader, so it only checks that there is at least one leader, not exactly one.
```diff
diff --git
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java
index 679118e798..ae4bd0d96a 100644
---
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java
+++
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java
@@ -1538,8 +1538,6 @@ public CertificateStore getCertificateStore() {
public void start() throws IOException {
// If the SCM stopped during a snapshot install after the DB was
replaced, the VERSION file is behind the DB, and
// the finalization included in the snapshot is not in the Raft log to
be replayed.
- // The metadata store may have been closed by a prior stop(); start()
is idempotent and reopens it if needed.
- scmMetadataStore.start(configuration);
versionManager.finalizeFromDBIfRequired(scmMetadataStore.getMetaTable());
if (LOG.isInfoEnabled()) {
LOG.info(buildRpcServerStartMessage(
diff --git
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/TestStorageContainerManagerHA.java
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/TestStorageContainerManagerHA.java
index 9ded335c96..288d7807ee 100644
---
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/TestStorageContainerManagerHA.java
+++
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/TestStorageContainerManagerHA.java
@@ -22,6 +22,7 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
@@ -146,48 +147,30 @@ public void testSCMLeadershipMetric() throws
IOException, InterruptedException {
// in order to initialize and bootstrap SCM instances only
cluster = haMiniClusterBuilder.build();
- List<StorageContainerManager> storageContainerManagersList =
cluster.getStorageContainerManagersList();
-
- // stop the single SCM instance in order to imitate further
simultaneous start of SCMs
- storageContainerManagersList.get(0).stop();
- storageContainerManagersList.get(0).join();
-
- // WHEN (imitate simultaneous start of the SCMs)
- int retryCount = 0;
- while (true) {
- CountDownLatch scmInstancesCounter = new
CountDownLatch(scmInstancesCount);
- AtomicInteger failedSCMs = new AtomicInteger();
- for (StorageContainerManager scm : storageContainerManagersList) {
- new Thread(() -> {
- try {
- scm.start();
- } catch (IOException e) {
- failedSCMs.incrementAndGet();
- } finally {
- scmInstancesCounter.countDown();
- }
- }).start();
- }
- scmInstancesCounter.await();
- if (failedSCMs.get() == 0) {
- break;
- } else {
- for (StorageContainerManager scm : storageContainerManagersList) {
- scm.stop();
- scm.join();
- LOG.info("Stopping StorageContainerManager server at {}",
- scm.getClientRpcAddress());
+ // WHEN (imitate simultaneous start of the SCMs that were bootstrapped
but not started). A stopped SCM cannot be
+ // started again, so the running SCM is left as is.
+ List<StorageContainerManager> inactiveSCMs = new ArrayList<>();
+ cluster.getInactiveSCM().forEachRemaining(inactiveSCMs::add);
+ CountDownLatch scmInstancesCounter = new
CountDownLatch(inactiveSCMs.size());
+ AtomicInteger failedSCMs = new AtomicInteger();
+ for (StorageContainerManager scm : inactiveSCMs) {
+ new Thread(() -> {
+ try {
+ scm.start();
+ } catch (Exception e) {
+ LOG.error("Failed to start SCM {}", scm.getSCMNodeId(), e);
+ failedSCMs.incrementAndGet();
+ } finally {
+ scmInstancesCounter.countDown();
}
- ++retryCount;
- LOG.info("SCMs port conflicts, retried {} times",
- retryCount);
- failedSCMs.set(0);
- }
+ }).start();
}
+ scmInstancesCounter.await();
+ assertEquals(0, failedSCMs.get());
// THEN expect only one SCM node (leader) will have
'scmha_metrics_scmha_leader_state' metric set to 1
int leaderCount = 0;
- for (StorageContainerManager scm : storageContainerManagersList) {
+ for (StorageContainerManager scm :
cluster.getStorageContainerManagersList()) {
if (scm.getScmHAMetrics() != null &&
scm.getScmHAMetrics().getSCMHAMetricsInfoLeaderState() == 1) {
leaderCount++;
break;
```
##########
hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/upgrade/RatisBasedVersionManager.java:
##########
@@ -46,14 +46,32 @@ public void validateDBVersion(Table<String, String>
finalizationStore) throws IO
}
}
- public void finalizeFromSnapshotIfRequired(Table<String, String>
finalizationStore) throws IOException {
+ public void finalizeFromDBIfRequired(Table<String, String> finalizationStore)
Review Comment:
I need some more time to work through the cases in this method. Will post
more on this area in a follow-up review.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]