This is an automated email from the ASF dual-hosted git repository.
erose pushed a commit to branch HDDS-14496-zdu
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/HDDS-14496-zdu by this push:
new 50f407e2f61 HDDS-14732. Create a new VersionManager for unified
component versioning (#9897)
50f407e2f61 is described below
commit 50f407e2f61ad0d9ca959fe4bd9d8911ea61f330
Author: Ethan Rose <[email protected]>
AuthorDate: Thu Mar 26 08:56:23 2026 -0400
HDDS-14732. Create a new VersionManager for unified component versioning
(#9897)
---
.../annotations/RegisterValidatorProcessor.java | 2 +-
.../org/apache/hadoop/hdds/ComponentVersion.java | 49 ++++++-
.../java/org/apache/hadoop/hdds/HDDSVersion.java | 47 ++++---
.../hadoop/hdds/upgrade/HDDSLayoutFeature.java | 42 +++++-
.../org/apache/hadoop/ozone/ClientVersion.java | 34 ++---
.../apache/hadoop/ozone/OzoneManagerVersion.java | 45 +++---
.../apache/hadoop/ozone/upgrade/LayoutFeature.java | 8 --
.../hadoop/hdds/AbstractComponentVersionTest.java | 135 ++++++++++++++++++
.../hadoop/hdds/ComponentVersionTestUtils.java | 51 +++++++
.../org/apache/hadoop/hdds/TestClientVersion.java} | 30 ++--
.../hdds/TestComponentVersionInvariants.java | 148 --------------------
.../org/apache/hadoop/hdds/TestHDDSVersion.java | 63 +++++++++
.../hadoop/hdds/TestOzoneManagerVersion.java | 64 +++++++++
.../hadoop/hdds/upgrade/TestHDDSLayoutFeature.java | 109 +++++++++++++++
.../hadoop/hdds/upgrade/HDDSVersionManager.java | 48 +++++++
.../ozone/upgrade/ComponentVersionManager.java | 151 +++++++++++++++++++++
.../upgrade/ComponentVersionManagerMetrics.java | 76 +++++++++++
.../hdds/upgrade/TestHDDSVersionManager.java | 69 ++++++++++
.../AbstractComponentVersionManagerTest.java | 149 ++++++++++++++++++++
.../upgrade/TestAbstractLayoutVersionManager.java | 7 +
.../ozone/upgrade/TestUpgradeFinalizerActions.java | 7 +
.../hadoop/ozone/om/upgrade/OMLayoutFeature.java | 42 +++++-
.../hadoop/ozone/om/upgrade/OMVersionManager.java | 48 +++++++
.../ozone/om/upgrade/TestOMLayoutFeature.java | 109 +++++++++++++++
...anager.java => TestOMLayoutVersionManager.java} | 22 +--
.../ozone/om/upgrade/TestOMVersionManager.java | 140 +++++--------------
26 files changed, 1335 insertions(+), 360 deletions(-)
diff --git
a/hadoop-hdds/annotations/src/main/java/org/apache/ozone/annotations/RegisterValidatorProcessor.java
b/hadoop-hdds/annotations/src/main/java/org/apache/ozone/annotations/RegisterValidatorProcessor.java
index 6b26043efb2..c5639e11e54 100644
---
a/hadoop-hdds/annotations/src/main/java/org/apache/ozone/annotations/RegisterValidatorProcessor.java
+++
b/hadoop-hdds/annotations/src/main/java/org/apache/ozone/annotations/RegisterValidatorProcessor.java
@@ -54,7 +54,7 @@
public class RegisterValidatorProcessor extends AbstractProcessor {
public static final String ANNOTATION_SIMPLE_NAME = "RegisterValidator";
- public static final String VERSION_CLASS_NAME =
"org.apache.hadoop.hdds.ComponentVersion";
+ public static final String VERSION_CLASS_NAME =
"org.apache.hadoop.ozone.Version";
public static final String REQUEST_PROCESSING_PHASE_CLASS_NAME =
"org.apache.hadoop.ozone.om.request.validation" +
".RequestProcessingPhase";
public static final String APPLY_BEFORE_METHOD_NAME = "applyBefore";
diff --git
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/ComponentVersion.java
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/ComponentVersion.java
index 10f232ee0c9..9c1223f35c3 100644
---
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/ComponentVersion.java
+++
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/ComponentVersion.java
@@ -21,26 +21,61 @@
import org.apache.hadoop.ozone.upgrade.UpgradeAction;
/**
- * Base type for component version enums.
+ * The logical versioning system used to track incompatible changes to a
component, regardless whether they affect disk
+ * or network compatibility between the same or different types of components.
+ *
+ * This interface is the base type for component version enums.
*/
public interface ComponentVersion {
/**
- * @return The serialized representation of this version. This is an opaque
value which should not be checked or
- * compared directly.
+ * Returns an integer representation of this version. To callers outside
this class, this is an opaque value which
+ * should not be checked or compared directly. {@link #isSupportedBy} should
be used for version comparisons.
+ *
+ * To implementors of this interface, versions should serialize such that
+ * {@code version1 <= version2} if and only if
+ * {@code version1.serialize() <= version2.serialize()}.
+ * Negative numbers may be used as serialized values to represent unknown
future versions which are trivially larger
+ * than all other versions.
+ *
+ * @return The serialized representation of this version.
*/
int serialize();
/**
- * @return the description of the version enum value.
+ * @return The description of this version.
*/
String description();
/**
- * Deserializes a ComponentVersion and checks if its feature set is
supported by the current ComponentVersion.
+ * @return The next version immediately following this one, or null if there
is no such version.
+ */
+ ComponentVersion nextVersion();
+
+ /**
+ * Uses the serialized representation of a ComponentVersion to check if its
feature set is supported by the current
+ * ComponentVersion.
*
- * @return true if this version supports the features of otherVersion. False
otherwise.
+ * @return true if this version supports the features of the provided
version. False otherwise.
*/
- boolean isSupportedBy(int serializedVersion);
+ default boolean isSupportedBy(int serializedVersion) {
+ if (serialize() < 0) {
+ // Our version is an unknown future version, it is not supported by any
other version.
+ return false;
+ } else if (serializedVersion < 0) {
+ // The other version is an unknown future version, it trivially supports
all other versions.
+ return true;
+ } else {
+ // If both versions have positive values, they represent concrete
versions and we can compare them directly.
+ return serialize() <= serializedVersion;
+ }
+ }
+
+ /**
+ * @return true if this version supports the features of the provided
version. False otherwise.
+ */
+ default boolean isSupportedBy(ComponentVersion other) {
+ return isSupportedBy(other.serialize());
+ }
default Optional<? extends UpgradeAction> action() {
return Optional.empty();
diff --git
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HDDSVersion.java
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HDDSVersion.java
index 6b4131e2226..51d4229a748 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HDDSVersion.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HDDSVersion.java
@@ -21,13 +21,16 @@
import static java.util.stream.Collectors.toMap;
import java.util.Arrays;
-import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
/**
* Versioning for datanode.
*/
public enum HDDSVersion implements ComponentVersion {
+ ////////////////////////////// //////////////////////////////
+
DEFAULT_VERSION(0, "Initial version"),
SEPARATE_RATIS_PORTS_AVAILABLE(1, "Version with separated Ratis port."),
@@ -36,14 +39,18 @@ public enum HDDSVersion implements ComponentVersion {
STREAM_BLOCK_SUPPORT(3,
"This version has support for reading a block by streaming chunks."),
+ ZDU(100, "Version that supports zero downtime upgrade"),
+
FUTURE_VERSION(-1, "Used internally in the client when the server side is "
+ " newer and an unknown server version has arrived to the client.");
- public static final HDDSVersion SOFTWARE_VERSION = latest();
+ ////////////////////////////// //////////////////////////////
- private static final Map<Integer, HDDSVersion> BY_VALUE =
+ private static final SortedMap<Integer, HDDSVersion> BY_VALUE =
Arrays.stream(values())
- .collect(toMap(HDDSVersion::serialize, identity()));
+ .collect(toMap(HDDSVersion::serialize, identity(), (v1, v2) -> v1,
TreeMap::new));
+
+ public static final HDDSVersion SOFTWARE_VERSION =
BY_VALUE.get(BY_VALUE.lastKey());
private final int version;
private final String description;
@@ -58,31 +65,35 @@ public String description() {
return description;
}
+ /**
+ * @return The next version immediately following this one and excluding
FUTURE_VERSION,
+ * or null if there is no such version.
+ */
+ @Override
+ public HDDSVersion nextVersion() {
+ int nextOrdinal = ordinal() + 1;
+ if (nextOrdinal >= values().length - 1) {
+ return null;
+ }
+ return values()[nextOrdinal];
+ }
+
@Override
public int serialize() {
return version;
}
+ /**
+ * @param value The serialized version to convert.
+ * @return The version corresponding to this serialized value, or {@link
#FUTURE_VERSION} if no matching version is
+ * found.
+ */
public static HDDSVersion deserialize(int value) {
return BY_VALUE.getOrDefault(value, FUTURE_VERSION);
}
- @Override
- public boolean isSupportedBy(int serializedVersion) {
- // In order for the other serialized version to support this version's
features,
- // the other version must be equal or larger to this version.
- return deserialize(serializedVersion).compareTo(this) >= 0;
- }
-
@Override
public String toString() {
return name() + " (" + serialize() + ")";
}
-
- private static HDDSVersion latest() {
- HDDSVersion[] versions = HDDSVersion.values();
- // The last entry in the array will be `FUTURE_VERSION`. We want the entry
prior to this which defines the latest
- // version in the software.
- return versions[versions.length - 2];
- }
}
diff --git
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/upgrade/HDDSLayoutFeature.java
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/upgrade/HDDSLayoutFeature.java
index e78e1dcbffe..4e753c5cab3 100644
---
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/upgrade/HDDSLayoutFeature.java
+++
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/upgrade/HDDSLayoutFeature.java
@@ -17,11 +17,21 @@
package org.apache.hadoop.hdds.upgrade;
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toMap;
+
+import java.util.Arrays;
import java.util.Optional;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import org.apache.hadoop.hdds.ComponentVersion;
+import org.apache.hadoop.hdds.HDDSVersion;
import org.apache.hadoop.ozone.upgrade.LayoutFeature;
/**
- * List of HDDS Features.
+ * List of HDDS Layout Features. All version management has been migrated to
{@link HDDSVersion} and no new additions
+ * should be made to this class. Existing versions are kept here for backwards
compatibility when upgrading to this
+ * version from older versions.
*/
public enum HDDSLayoutFeature implements LayoutFeature {
////////////////////////////// //////////////////////////////
@@ -44,8 +54,14 @@ public enum HDDSLayoutFeature implements LayoutFeature {
WITNESSED_CONTAINER_DB_PROTO_VALUE(9, "ContainerID table schema to use value
type as proto"),
STORAGE_SPACE_DISTRIBUTION(10, "Enhanced block deletion function for storage
space distribution feature.");
+ // ALL NEW VERSIONS SHOULD NOW BE ADDED TO HDDSVersion
+
////////////////////////////// //////////////////////////////
+ private static final SortedMap<Integer, HDDSLayoutFeature> BY_VALUE =
+ Arrays.stream(values())
+ .collect(toMap(HDDSLayoutFeature::serialize, identity(), (v1, v2) ->
v1, TreeMap::new));
+
private final int layoutVersion;
private final String description;
private HDDSUpgradeAction scmAction;
@@ -90,6 +106,30 @@ public String description() {
return description;
}
+ /**
+ * @return The next version immediately following this one. If there is no
next version found in this enum,
+ * the next version is {@link HDDSVersion#ZDU}, since all HDDS versioning
has been migrated to
+ * {@link HDDSVersion} as part of the ZDU feature.
+ */
+ @Override
+ public ComponentVersion nextVersion() {
+ HDDSLayoutFeature nextFeature = BY_VALUE.get(layoutVersion + 1);
+ if (nextFeature == null) {
+ return HDDSVersion.ZDU;
+ } else {
+ return nextFeature;
+ }
+ }
+
+ /**
+ * @param version The serialized version to convert.
+ * @return The version corresponding to this serialized value, or {@code
null} if no matching version is
+ * found.
+ */
+ public static HDDSLayoutFeature deserialize(int version) {
+ return BY_VALUE.get(version);
+ }
+
@Override
public String toString() {
return name() + " (" + serialize() + ")";
diff --git
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/ClientVersion.java
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/ClientVersion.java
index ac20b5fe3b7..dd451ab052c 100644
---
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/ClientVersion.java
+++
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/ClientVersion.java
@@ -21,7 +21,8 @@
import static java.util.stream.Collectors.toMap;
import java.util.Arrays;
-import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
import org.apache.hadoop.hdds.ComponentVersion;
/**
@@ -44,11 +45,11 @@ public enum ClientVersion implements ComponentVersion {
FUTURE_VERSION(-1, "Used internally when the server side is older and an"
+ " unknown client version has arrived from the client.");
- public static final ClientVersion CURRENT = latest();
-
- private static final Map<Integer, ClientVersion> BY_VALUE =
+ private static final SortedMap<Integer, ClientVersion> BY_VALUE =
Arrays.stream(values())
- .collect(toMap(ClientVersion::serialize, identity()));
+ .collect(toMap(ClientVersion::serialize, identity(), (v1, v2) -> v1,
TreeMap::new));
+
+ public static final ClientVersion CURRENT = BY_VALUE.get(BY_VALUE.lastKey());
private final int version;
private final String description;
@@ -63,6 +64,15 @@ public String description() {
return description;
}
+ @Override
+ public ClientVersion nextVersion() {
+ int nextOrdinal = ordinal() + 1;
+ if (nextOrdinal >= values().length - 1) {
+ return null;
+ }
+ return values()[nextOrdinal];
+ }
+
@Override
public int serialize() {
return version;
@@ -72,22 +82,8 @@ public static ClientVersion deserialize(int value) {
return BY_VALUE.getOrDefault(value, FUTURE_VERSION);
}
- @Override
- public boolean isSupportedBy(int serializedVersion) {
- // In order for the other serialized version to support this version's
features,
- // the other version must be equal or larger to this version.
- return deserialize(serializedVersion).compareTo(this) >= 0;
- }
-
@Override
public String toString() {
return name() + " (" + serialize() + ")";
}
-
- private static ClientVersion latest() {
- ClientVersion[] versions = ClientVersion.values();
- // The last entry in the array will be `FUTURE_VERSION`. We want the entry
prior to this which defines the latest
- // version in the software.
- return versions[versions.length - 2];
- }
}
diff --git
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneManagerVersion.java
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneManagerVersion.java
index 9ec8870855e..163696b4f15 100644
---
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneManagerVersion.java
+++
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneManagerVersion.java
@@ -21,13 +21,17 @@
import static java.util.stream.Collectors.toMap;
import java.util.Arrays;
-import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
import org.apache.hadoop.hdds.ComponentVersion;
/**
* Versioning for Ozone Manager.
*/
public enum OzoneManagerVersion implements ComponentVersion {
+
+ ////////////////////////////// //////////////////////////////
+
DEFAULT_VERSION(0, "Initial version"),
S3G_PERSISTENT_CONNECTIONS(1,
"New S3G persistent connection support is present in OM."),
@@ -54,15 +58,19 @@ public enum OzoneManagerVersion implements ComponentVersion
{
S3_LIST_MULTIPART_UPLOADS_PAGINATION(11,
"OzoneManager version that supports S3 list multipart uploads API with
pagination"),
-
+
+ ZDU(100, "OzoneManager version that supports zero downtime upgrade"),
+
FUTURE_VERSION(-1, "Used internally in the client when the server side is "
+ " newer and an unknown server version has arrived to the client.");
- public static final OzoneManagerVersion SOFTWARE_VERSION = latest();
+ ////////////////////////////// //////////////////////////////
- private static final Map<Integer, OzoneManagerVersion> BY_VALUE =
+ private static final SortedMap<Integer, OzoneManagerVersion> BY_VALUE =
Arrays.stream(values())
- .collect(toMap(OzoneManagerVersion::serialize, identity()));
+ .collect(toMap(OzoneManagerVersion::serialize, identity(), (v1, v2)
-> v1, TreeMap::new));
+
+ public static final OzoneManagerVersion SOFTWARE_VERSION =
BY_VALUE.get(BY_VALUE.lastKey());
private final int version;
private final String description;
@@ -82,26 +90,31 @@ public int serialize() {
return version;
}
+ /**
+ * @param value The serialized version to convert.
+ * @return The version corresponding to this serialized value, or {@link
#FUTURE_VERSION} if no matching version is
+ * found.
+ */
public static OzoneManagerVersion deserialize(int value) {
return BY_VALUE.getOrDefault(value, FUTURE_VERSION);
}
+
+ /**
+ * @return The next version immediately following this one and excluding
FUTURE_VERSION,
+ * or null if there is no such version.
+ */
@Override
- public boolean isSupportedBy(int serializedVersion) {
- // In order for the other serialized version to support this version's
features,
- // the other version must be equal or larger to this version.
- return deserialize(serializedVersion).compareTo(this) >= 0;
+ public OzoneManagerVersion nextVersion() {
+ int nextOrdinal = ordinal() + 1;
+ if (nextOrdinal >= values().length - 1) {
+ return null;
+ }
+ return values()[nextOrdinal];
}
@Override
public String toString() {
return name() + " (" + serialize() + ")";
}
-
- private static OzoneManagerVersion latest() {
- OzoneManagerVersion[] versions = OzoneManagerVersion.values();
- // The last entry in the array will be `FUTURE_VERSION`. We want the entry
prior to this which defines the latest
- // version in the software.
- return versions[versions.length - 2];
- }
}
diff --git
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/upgrade/LayoutFeature.java
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/upgrade/LayoutFeature.java
index 848a35104e5..7f7374c8137 100644
---
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/upgrade/LayoutFeature.java
+++
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/upgrade/LayoutFeature.java
@@ -29,12 +29,4 @@ public interface LayoutFeature extends ComponentVersion {
default int serialize() {
return this.layoutVersion();
}
-
- @Override
- default boolean isSupportedBy(int serializedVersion) {
- // In order for the other serialized version to support this version's
features,
- // the other version must be equal or larger to this version.
- // We can compare the values directly since there is no FUTURE_VERSION for
layout features.
- return serializedVersion >= layoutVersion();
- }
}
diff --git
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/AbstractComponentVersionTest.java
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/AbstractComponentVersionTest.java
new file mode 100644
index 00000000000..c9b2a32647f
--- /dev/null
+++
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/AbstractComponentVersionTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.hadoop.hdds;
+
+import static
org.apache.hadoop.hdds.ComponentVersionTestUtils.assertNotSupportedBy;
+import static
org.apache.hadoop.hdds.ComponentVersionTestUtils.assertSupportedBy;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Shared invariants for component version enums.
+ */
+public abstract class AbstractComponentVersionTest {
+
+ protected abstract ComponentVersion[] getValues();
+
+ protected abstract ComponentVersion getDefaultVersion();
+
+ protected abstract ComponentVersion getFutureVersion();
+
+ protected abstract ComponentVersion deserialize(int value);
+
+ // FUTURE_VERSION is the latest
+ @Test
+ public void testFutureVersionHasTheHighestOrdinal() {
+ ComponentVersion[] values = getValues();
+ ComponentVersion futureValue = getFutureVersion();
+ assertEquals(values[values.length - 1], futureValue);
+ }
+
+ // FUTURE_VERSION's internal version id is -1
+ @Test
+ public void testFutureVersionSerializesToMinusOne() {
+ ComponentVersion futureValue = getFutureVersion();
+ assertEquals(-1, futureValue.serialize());
+ }
+
+ // DEFAULT_VERSION's internal version id is 0
+ @Test
+ public void testDefaultVersionSerializesToZero() {
+ ComponentVersion defaultValue = getDefaultVersion();
+ assertEquals(0, defaultValue.serialize());
+ }
+
+ // known (non-future) versions are strictly increasing
+ @Test
+ public void testSerializedValuesAreMonotonic() {
+ ComponentVersion[] values = getValues();
+ int knownVersionCount = values.length - 1;
+ for (int i = 1; i < knownVersionCount; i++) {
+ assertTrue(values[i].serialize() > values[i - 1].serialize(),
+ "Expected known version serialization to increase: " + values[i - 1]
+ " -> " + values[i]);
+ }
+ }
+
+ @Test
+ public void testNextVersionProgression() {
+ ComponentVersion[] values = getValues();
+ ComponentVersion futureValue = getFutureVersion();
+ int knownVersionCount = values.length - 1;
+ for (int i = 0; i < knownVersionCount - 1; i++) {
+ assertEquals(values[i + 1], values[i].nextVersion(),
+ "Expected nextVersion progression for " + values[i]);
+ }
+ assertNull(values[knownVersionCount - 1].nextVersion(),
+ "Expected latest known version to have no nextVersion");
+ assertNull(futureValue.nextVersion(),
+ "Expected FUTURE_VERSION.nextVersion() to return null");
+ }
+
+ @Test
+ public void testOnlyEqualOrHigherVersionsCanSupportAFeature() {
+ ComponentVersion[] values = getValues();
+ int knownVersionCount = values.length - 1;
+ for (int featureIndex = 0; featureIndex < knownVersionCount;
featureIndex++) {
+ ComponentVersion requiredFeature = values[featureIndex];
+ for (int providerIndex = 0; providerIndex < knownVersionCount;
providerIndex++) {
+ ComponentVersion provider = values[providerIndex];
+ if (providerIndex >= featureIndex) {
+ assertSupportedBy(requiredFeature, provider);
+ } else {
+ assertNotSupportedBy(requiredFeature, provider);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testFutureVersionSupportsAllKnownVersions() {
+ ComponentVersion[] values = getValues();
+ int unknownFutureVersion = Integer.MAX_VALUE;
+ for (ComponentVersion knownVersion : values) {
+ if (knownVersion == getFutureVersion()) {
+ // FUTURE_VERSION with serialized value < 0 is considered larger than
any version with a concrete
+ // positive value.
+ assertFalse(knownVersion.isSupportedBy(unknownFutureVersion),
knownVersion +
+ " should not support unknown future version " +
unknownFutureVersion);
+ } else {
+ assertTrue(knownVersion.isSupportedBy(unknownFutureVersion),
knownVersion +
+ " should support unknown future version " + unknownFutureVersion);
+ }
+ }
+ }
+
+ @Test
+ public void testVersionSerDes() {
+ for (ComponentVersion version : getValues()) {
+ assertEquals(version, deserialize(version.serialize()));
+ }
+ }
+
+ @Test
+ public void testDeserializeUnknownReturnsFutureVersion() {
+ assertEquals(getFutureVersion(), deserialize(Integer.MAX_VALUE));
+ }
+}
diff --git
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/ComponentVersionTestUtils.java
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/ComponentVersionTestUtils.java
new file mode 100644
index 00000000000..0531523cfac
--- /dev/null
+++
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/ComponentVersionTestUtils.java
@@ -0,0 +1,51 @@
+/*
+ * 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.hadoop.hdds;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Shared assertions for {@link ComponentVersion} tests.
+ */
+public final class ComponentVersionTestUtils {
+
+ private ComponentVersionTestUtils() { }
+
+ public static void assertSupportedBy(
+ ComponentVersion requiredFeature, ComponentVersion provider) {
+ assertSupportedBy(requiredFeature, provider, true);
+ }
+
+ public static void assertNotSupportedBy(
+ ComponentVersion requiredFeature, ComponentVersion provider) {
+ assertSupportedBy(requiredFeature, provider, false);
+ }
+
+ /**
+ * Helper method to test support by passing both serialized and deserialized
versions.
+ */
+ private static void assertSupportedBy(
+ ComponentVersion requiredFeature, ComponentVersion provider, boolean
expected) {
+ int providerSerializedVersion = provider.serialize();
+ assertEquals(expected,
requiredFeature.isSupportedBy(providerSerializedVersion),
+ "Expected support check via serialized overload to match for version "
+ + providerSerializedVersion);
+ assertEquals(expected, requiredFeature.isSupportedBy(provider),
+ "Expected support check via version overload to match for " +
provider);
+ }
+}
diff --git
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/upgrade/LayoutFeature.java
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/TestClientVersion.java
similarity index 57%
copy from
hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/upgrade/LayoutFeature.java
copy to
hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/TestClientVersion.java
index 848a35104e5..721fb2466c9 100644
---
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/upgrade/LayoutFeature.java
+++
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/TestClientVersion.java
@@ -15,26 +15,32 @@
* limitations under the License.
*/
-package org.apache.hadoop.ozone.upgrade;
+package org.apache.hadoop.hdds;
-import org.apache.hadoop.hdds.ComponentVersion;
+import org.apache.hadoop.ozone.ClientVersion;
/**
- * Generic Layout feature interface for Ozone.
+ * Invariants for {@link ClientVersion}.
*/
-public interface LayoutFeature extends ComponentVersion {
- int layoutVersion();
+public class TestClientVersion extends AbstractComponentVersionTest {
@Override
- default int serialize() {
- return this.layoutVersion();
+ protected ComponentVersion[] getValues() {
+ return ClientVersion.values();
}
@Override
- default boolean isSupportedBy(int serializedVersion) {
- // In order for the other serialized version to support this version's
features,
- // the other version must be equal or larger to this version.
- // We can compare the values directly since there is no FUTURE_VERSION for
layout features.
- return serializedVersion >= layoutVersion();
+ protected ComponentVersion getDefaultVersion() {
+ return ClientVersion.DEFAULT_VERSION;
+ }
+
+ @Override
+ protected ComponentVersion getFutureVersion() {
+ return ClientVersion.FUTURE_VERSION;
+ }
+
+ @Override
+ protected ComponentVersion deserialize(int value) {
+ return ClientVersion.deserialize(value);
}
}
diff --git
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/TestComponentVersionInvariants.java
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/TestComponentVersionInvariants.java
deleted file mode 100644
index 0edab7e76d2..00000000000
---
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/TestComponentVersionInvariants.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * 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.hadoop.hdds;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.params.provider.Arguments.arguments;
-
-import java.util.stream.Stream;
-import org.apache.hadoop.ozone.ClientVersion;
-import org.apache.hadoop.ozone.OzoneManagerVersion;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
-
-/**
- * Test to ensure Component version instances conform with invariants relied
- * upon in other parts of the codebase.
- */
-public class TestComponentVersionInvariants {
-
- public static Stream<Arguments> values() {
- return Stream.of(
- arguments(
- HDDSVersion.values(),
- HDDSVersion.DEFAULT_VERSION,
- HDDSVersion.FUTURE_VERSION),
- arguments(
- ClientVersion.values(),
- ClientVersion.DEFAULT_VERSION,
- ClientVersion.FUTURE_VERSION),
- arguments(
- OzoneManagerVersion.values(),
- OzoneManagerVersion.DEFAULT_VERSION,
- OzoneManagerVersion.FUTURE_VERSION)
- );
- }
-
- // FUTURE_VERSION is the latest
- @ParameterizedTest
- @MethodSource("values")
- public void testFutureVersionHasTheHighestOrdinal(ComponentVersion[] values,
ComponentVersion defaultValue,
- ComponentVersion futureValue) {
-
- assertEquals(values[values.length - 1], futureValue);
- }
-
- // FUTURE_VERSION's internal version id is -1
- @ParameterizedTest
- @MethodSource("values")
- public void testFutureVersionSerializesToMinusOne(ComponentVersion[] values,
ComponentVersion defaultValue,
- ComponentVersion futureValue) {
- assertEquals(-1, futureValue.serialize());
-
- }
-
- // DEFAULT_VERSION's internal version id is 0
- @ParameterizedTest
- @MethodSource("values")
- public void testDefaultVersionSerializesToZero(ComponentVersion[] values,
ComponentVersion defaultValue,
- ComponentVersion futureValue) {
- assertEquals(0, defaultValue.serialize());
- }
-
- // versions are increasing monotonically by one
- @ParameterizedTest
- @MethodSource("values")
- public void testSerializedValuesAreMonotonic(ComponentVersion[] values,
ComponentVersion defaultValue,
- ComponentVersion futureValue) {
- int startValue = defaultValue.serialize();
- // we skip the future version at the last position
- for (int i = 0; i < values.length - 1; i++) {
- assertEquals(values[i].serialize(), startValue++);
- }
- assertEquals(values.length, ++startValue);
- }
-
- @ParameterizedTest
- @MethodSource("values")
- public void testVersionIsSupportedByItself(ComponentVersion[] values,
ComponentVersion defaultValue,
- ComponentVersion futureValue) {
- for (ComponentVersion value : values) {
- assertTrue(value.isSupportedBy(value.serialize()));
- }
- }
-
- @ParameterizedTest
- @MethodSource("values")
- public void
testOnlyEqualOrHigherVersionsCanSupportAFeature(ComponentVersion[] values,
ComponentVersion defaultValue,
- ComponentVersion futureValue) {
- int knownVersionCount = values.length - 1;
- for (int featureIndex = 0; featureIndex < knownVersionCount;
featureIndex++) {
- ComponentVersion requiredFeature = values[featureIndex];
- for (int providerIndex = 0; providerIndex < knownVersionCount;
providerIndex++) {
- ComponentVersion provider = values[providerIndex];
- boolean expected = providerIndex >= featureIndex;
- assertEquals(expected,
requiredFeature.isSupportedBy(provider.serialize()));
- }
- }
- }
-
- @ParameterizedTest
- @MethodSource("values")
- public void testFutureVersionSupportsAllKnownVersions(ComponentVersion[]
values, ComponentVersion defaultValue,
- ComponentVersion futureValue) {
- int unknownFutureVersion = Integer.MAX_VALUE;
- for (ComponentVersion requiredFeature : values) {
- assertTrue(requiredFeature.isSupportedBy(unknownFutureVersion));
- }
- }
-
- @Test
- public void testHDDSVersionSerDes() {
- for (HDDSVersion version: HDDSVersion.values()) {
- assertEquals(version, HDDSVersion.deserialize(version.serialize()));
- }
- }
-
- @Test
- public void testOMVersionSerDes() {
- for (OzoneManagerVersion version: OzoneManagerVersion.values()) {
- assertEquals(version,
OzoneManagerVersion.deserialize(version.serialize()));
- }
- }
-
- @Test
- public void testClientVersionSerDes() {
- for (ClientVersion version: ClientVersion.values()) {
- assertEquals(version, ClientVersion.deserialize(version.serialize()));
- }
- }
-}
diff --git
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/TestHDDSVersion.java
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/TestHDDSVersion.java
new file mode 100644
index 00000000000..89d24d3c82e
--- /dev/null
+++
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/TestHDDSVersion.java
@@ -0,0 +1,63 @@
+/*
+ * 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.hadoop.hdds;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Invariants for {@link HDDSVersion}.
+ */
+public class TestHDDSVersion extends AbstractComponentVersionTest {
+
+ @Override
+ protected ComponentVersion[] getValues() {
+ return HDDSVersion.values();
+ }
+
+ @Override
+ protected ComponentVersion getDefaultVersion() {
+ return HDDSVersion.DEFAULT_VERSION;
+ }
+
+ @Override
+ protected ComponentVersion getFutureVersion() {
+ return HDDSVersion.FUTURE_VERSION;
+ }
+
+ @Override
+ protected ComponentVersion deserialize(int value) {
+ return HDDSVersion.deserialize(value);
+ }
+
+ @Test
+ public void testKnownVersionNumbersAreContiguousExceptForZDU() {
+ HDDSVersion[] values = HDDSVersion.values();
+ int knownVersionCount = values.length - 1;
+ for (int i = 0; i < knownVersionCount - 1; i++) {
+ HDDSVersion current = values[i];
+ HDDSVersion next = values[i + 1];
+ if (next == HDDSVersion.ZDU) {
+ assertEquals(100, next.serialize());
+ } else {
+ assertEquals(current.serialize() + 1, next.serialize());
+ }
+ }
+ }
+}
diff --git
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/TestOzoneManagerVersion.java
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/TestOzoneManagerVersion.java
new file mode 100644
index 00000000000..bb94ac938fd
--- /dev/null
+++
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/TestOzoneManagerVersion.java
@@ -0,0 +1,64 @@
+/*
+ * 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.hadoop.hdds;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.hadoop.ozone.OzoneManagerVersion;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Invariants for {@link OzoneManagerVersion}.
+ */
+public class TestOzoneManagerVersion extends AbstractComponentVersionTest {
+
+ @Override
+ protected ComponentVersion[] getValues() {
+ return OzoneManagerVersion.values();
+ }
+
+ @Override
+ protected ComponentVersion getDefaultVersion() {
+ return OzoneManagerVersion.DEFAULT_VERSION;
+ }
+
+ @Override
+ protected ComponentVersion getFutureVersion() {
+ return OzoneManagerVersion.FUTURE_VERSION;
+ }
+
+ @Override
+ protected ComponentVersion deserialize(int value) {
+ return OzoneManagerVersion.deserialize(value);
+ }
+
+ @Test
+ public void testKnownVersionNumbersAreContiguousExceptForZDU() {
+ OzoneManagerVersion[] values = OzoneManagerVersion.values();
+ int knownVersionCount = values.length - 1;
+ for (int i = 0; i < knownVersionCount - 1; i++) {
+ OzoneManagerVersion current = values[i];
+ OzoneManagerVersion next = values[i + 1];
+ if (next == OzoneManagerVersion.ZDU) {
+ assertEquals(100, next.serialize());
+ } else {
+ assertEquals(current.serialize() + 1, next.serialize());
+ }
+ }
+ }
+}
diff --git
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/upgrade/TestHDDSLayoutFeature.java
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/upgrade/TestHDDSLayoutFeature.java
new file mode 100644
index 00000000000..fbb89220e97
--- /dev/null
+++
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/upgrade/TestHDDSLayoutFeature.java
@@ -0,0 +1,109 @@
+/*
+ * 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.hadoop.hdds.upgrade;
+
+import static
org.apache.hadoop.hdds.ComponentVersionTestUtils.assertNotSupportedBy;
+import static
org.apache.hadoop.hdds.ComponentVersionTestUtils.assertSupportedBy;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.hadoop.hdds.HDDSVersion;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests invariants for legacy HDDS layout feature versions.
+ */
+public class TestHDDSLayoutFeature {
+ @Test
+ public void testHDDSLayoutFeaturesHaveIncreasingLayoutVersion() {
+ HDDSLayoutFeature[] values = HDDSLayoutFeature.values();
+ int currVersion = -1;
+ for (HDDSLayoutFeature lf : values) {
+ // This will skip the jump from the last HDDSLayoutFeature to
HDDSVersion#ZDU,
+ // since that is expected to be a larger version increment.
+ assertEquals(currVersion + 1, lf.layoutVersion(),
+ "Expected monotonically increasing layout version for " + lf);
+ currVersion = lf.layoutVersion();
+ }
+ }
+
+ /**
+ * All incompatible changes to HDDS (SCM and Datanodes) should now be added
to {@link HDDSVersion}.
+ */
+ @Test
+ public void testNoNewHDDSLayoutFeaturesAdded() {
+ int numHDDSLayoutFeatures = HDDSLayoutFeature.values().length;
+ HDDSLayoutFeature lastFeature =
HDDSLayoutFeature.values()[numHDDSLayoutFeatures - 1];
+ assertEquals(11, numHDDSLayoutFeatures);
+ assertEquals(HDDSLayoutFeature.STORAGE_SPACE_DISTRIBUTION, lastFeature);
+ assertEquals(10, lastFeature.layoutVersion());
+ }
+
+ @Test
+ public void testNextVersion() {
+ HDDSLayoutFeature[] values = HDDSLayoutFeature.values();
+ for (int i = 1; i < values.length; i++) {
+ HDDSLayoutFeature previous = values[i - 1];
+ HDDSLayoutFeature current = values[i];
+ assertEquals(current, previous.nextVersion(),
+ "Expected " + previous + ".nextVersion() to be " + current);
+ }
+ // The last layout feature should point us to the ZDU version to switch to
using HDDSVersion.
+ assertEquals(HDDSVersion.ZDU, values[values.length - 1].nextVersion());
+ }
+
+ @Test
+ public void testSerDes() {
+ for (HDDSLayoutFeature version : HDDSLayoutFeature.values()) {
+ assertEquals(version,
HDDSLayoutFeature.deserialize(version.serialize()));
+ }
+ }
+
+ @Test
+ public void testDeserializeUnknownVersionReturnsNull() {
+ assertNull(HDDSLayoutFeature.deserialize(-1));
+ assertNull(HDDSLayoutFeature.deserialize(Integer.MAX_VALUE));
+ // HDDSLayoutFeature can only deserialize values from its own enum.
+ assertNull(HDDSLayoutFeature.deserialize(HDDSVersion.ZDU.serialize()));
+ }
+
+ @Test
+ public void testIsSupportedByFeatureBoundary() {
+ for (HDDSLayoutFeature feature : HDDSLayoutFeature.values()) {
+ // A layout feature should support itself.
+ int layoutVersion = feature.layoutVersion();
+ assertSupportedBy(feature, feature);
+ if (layoutVersion > 0) {
+ // A layout feature should not be supported by older features.
+ HDDSLayoutFeature previousFeature =
HDDSLayoutFeature.values()[layoutVersion - 1];
+ assertNotSupportedBy(feature, previousFeature);
+ }
+ }
+ }
+
+ @Test
+ public void testAllLayoutFeaturesAreSupportedByFutureVersions() {
+ for (HDDSLayoutFeature feature : HDDSLayoutFeature.values()) {
+ assertSupportedBy(feature, HDDSVersion.ZDU);
+ assertSupportedBy(feature, HDDSVersion.FUTURE_VERSION);
+ // No ComponentVersion instance represents an arbitrary future version.
+ assertTrue(feature.isSupportedBy(Integer.MAX_VALUE));
+ }
+ }
+}
diff --git
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/upgrade/HDDSVersionManager.java
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/upgrade/HDDSVersionManager.java
new file mode 100644
index 00000000000..ad6bad1c702
--- /dev/null
+++
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/upgrade/HDDSVersionManager.java
@@ -0,0 +1,48 @@
+/*
+ * 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.hadoop.hdds.upgrade;
+
+import java.io.IOException;
+import org.apache.hadoop.hdds.ComponentVersion;
+import org.apache.hadoop.hdds.HDDSVersion;
+import org.apache.hadoop.ozone.upgrade.ComponentVersionManager;
+
+/**
+ * Component version manager for HDDS (Datanodes and SCM).
+ */
+public class HDDSVersionManager extends ComponentVersionManager {
+ public HDDSVersionManager(int serializedApparentVersion) throws IOException {
+ super(computeApparentVersion(serializedApparentVersion),
HDDSVersion.SOFTWARE_VERSION);
+ }
+
+ /**
+ * If the apparent version stored on the disk is >= 100, it indicates the
component has been finalized for the
+ * ZDU feature, and the apparent version corresponds to a version in {@link
HDDSVersion}.
+ * If the apparent version stored on the disk is < 100, it indicates the
component is not yet finalized for the
+ * ZDU feature, and the apparent version corresponds to a version in {@link
HDDSLayoutFeature}.
+ */
+ private static ComponentVersion computeApparentVersion(int
serializedApparentVersion) {
+ if (serializedApparentVersion < HDDSVersion.ZDU.serialize()) {
+ return HDDSLayoutFeature.deserialize(serializedApparentVersion);
+ } else {
+ return HDDSVersion.deserialize(serializedApparentVersion);
+ }
+ }
+
+ // TODO HDDS-14826: Register upgrade actions based on annotations
+}
diff --git
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/upgrade/ComponentVersionManager.java
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/upgrade/ComponentVersionManager.java
new file mode 100644
index 00000000000..11bf4a3f228
--- /dev/null
+++
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/upgrade/ComponentVersionManager.java
@@ -0,0 +1,151 @@
+/*
+ * 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.hadoop.ozone.upgrade;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import org.apache.hadoop.hdds.ComponentVersion;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Tracks information about the apparent version, software version, and
finalization status of a component.
+ *
+ * Software version: The {@link ComponentVersion} of the code that is
currently running.
+ * This is always the highest component version within the code and does
not change while the process is running.
+ *
+ * Apparent version: The {@link ComponentVersion} the software is acting as,
which is persisted to the disk.
+ * The apparent version determines the API that is exposed by the component
and the format it uses to persist data.
+ * Using an apparent version less than software version allows us to
support rolling upgrades and downgrades.
+ *
+ * Pre-finalized: State a component enters when the apparent version on disk
is less than the software version.
+ * At this time all other machines may or may not be running the new bits,
new features are blocked, and downgrade
+ * is allowed.
+ *
+ * Finalized: State a component enters when the apparent version is equal to
the software version.
+ * A component transitions from pre-finalized to finalized when it receives
a finalize command from the
+ * admin. At this time all machines are running the new bits, and even
though this component is finalized,
+ * different types of components may not be. Downgrade is not allowed after
this point.
+ *
+ */
+public abstract class ComponentVersionManager implements Closeable {
+ // Apparent version may be updated during the finalization process.
+ private volatile ComponentVersion apparentVersion;
+ // Software version will never change.
+ private final ComponentVersion softwareVersion;
+ private final ComponentVersionManagerMetrics metrics;
+
+ private static final Logger LOG =
+ LoggerFactory.getLogger(ComponentVersionManager.class);
+
+ protected ComponentVersionManager(ComponentVersion apparentVersion,
ComponentVersion softwareVersion)
+ throws IOException {
+ this.apparentVersion = apparentVersion;
+ this.softwareVersion = softwareVersion;
+
+ if (!apparentVersion.isSupportedBy(softwareVersion)) {
+ throw new IOException(
+ "Cannot initialize ComponentVersionManager. Apparent version "
+ + apparentVersion + " is larger than software version "
+ + softwareVersion);
+ }
+
+ LOG.info("Initializing version manager with apparent version {} and
software version {}",
+ apparentVersion, softwareVersion);
+ this.metrics = ComponentVersionManagerMetrics.create(this);
+ }
+
+ public ComponentVersion getApparentVersion() {
+ return apparentVersion;
+ }
+
+ public ComponentVersion getSoftwareVersion() {
+ return softwareVersion;
+ }
+
+ public boolean isAllowed(ComponentVersion version) {
+ return version.isSupportedBy(apparentVersion);
+ }
+
+ public boolean needsFinalization() {
+ return !apparentVersion.equals(softwareVersion);
+ }
+
+ /**
+ * @return An Iterable of all versions after the current apparent version
which still need to be finalized. If this
+ * component is already finalized, the Iterable will be empty.
+ */
+ public Iterable<ComponentVersion> getUnfinalizedVersions() {
+ return () -> new Iterator<ComponentVersion>() {
+ private ComponentVersion currentVersion = apparentVersion;
+
+ @Override
+ public boolean hasNext() {
+ return currentVersion.nextVersion() != null;
+ }
+
+ @Override
+ public ComponentVersion next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ currentVersion = currentVersion.nextVersion();
+ return currentVersion;
+ }
+ };
+ }
+
+ /**
+ * Validates that the provided version is valid to finalize to, and if so,
updates the in-memory apparent version to
+ * this version. Also logs corresponding messages about finalization status.
+ *
+ * @param newApparentVersion The version to mark as finalized.
+ */
+ public void markFinalized(ComponentVersion newApparentVersion) {
+ String versionMsg = "Software version: " + softwareVersion
+ + ", apparent version: " + apparentVersion
+ + ", provided version: " + newApparentVersion
+ + ".";
+
+ if (newApparentVersion.isSupportedBy(apparentVersion)) {
+ LOG.info("Finalize attempt on a version which has already been
finalized. {} This can happen when " +
+ "Raft Log is replayed during service restart.", versionMsg);
+ } else {
+ ComponentVersion nextVersion = apparentVersion.nextVersion();
+ if (nextVersion == null) {
+ throw new IllegalArgumentException("Attempt to finalize when no future
versions exist." + versionMsg);
+ } else if (nextVersion.equals(newApparentVersion)) {
+ apparentVersion = newApparentVersion;
+ LOG.info("Version {} has been finalized.", apparentVersion);
+ if (!needsFinalization()) {
+ LOG.info("Finalization is complete.");
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "Finalize attempt on a version that is newer than the next feature
to be finalized. " + versionMsg);
+ }
+ }
+ }
+
+ @Override
+ public void close() {
+ metrics.unRegister();
+ }
+}
diff --git
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/upgrade/ComponentVersionManagerMetrics.java
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/upgrade/ComponentVersionManagerMetrics.java
new file mode 100644
index 00000000000..3cafd99d48e
--- /dev/null
+++
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/upgrade/ComponentVersionManagerMetrics.java
@@ -0,0 +1,76 @@
+/*
+ * 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.hadoop.ozone.upgrade;
+
+import org.apache.hadoop.metrics2.MetricsCollector;
+import org.apache.hadoop.metrics2.MetricsInfo;
+import org.apache.hadoop.metrics2.MetricsRecordBuilder;
+import org.apache.hadoop.metrics2.MetricsSource;
+import org.apache.hadoop.metrics2.annotation.Metrics;
+import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
+import org.apache.hadoop.metrics2.lib.Interns;
+import org.apache.hadoop.ozone.OzoneConsts;
+
+/**
+ * Metrics for {@link ComponentVersionManager}.
+ */
+@Metrics(about = "Component Version Manager Metrics", context =
OzoneConsts.OZONE)
+public final class ComponentVersionManagerMetrics implements MetricsSource {
+
+ public static final String METRICS_SOURCE_NAME =
+ ComponentVersionManagerMetrics.class.getSimpleName();
+
+ private static final MetricsInfo SOFTWARE_VERSION = Interns.info(
+ "SoftwareVersion",
+ "Software version in serialized int form.");
+ private static final MetricsInfo APPARENT_VERSION = Interns.info(
+ "ApparentVersion",
+ "Current apparent version in serialized int form.");
+
+ private final ComponentVersionManager versionManager;
+
+ private ComponentVersionManagerMetrics(ComponentVersionManager
versionManager) {
+ this.versionManager = versionManager;
+ }
+
+ public static ComponentVersionManagerMetrics create(ComponentVersionManager
versionManager) {
+ ComponentVersionManagerMetrics metrics = (ComponentVersionManagerMetrics)
DefaultMetricsSystem.instance()
+ .getSource(METRICS_SOURCE_NAME);
+ if (metrics == null) {
+ return DefaultMetricsSystem.instance().register(
+ METRICS_SOURCE_NAME,
+ "Metrics for component version management.",
+ new ComponentVersionManagerMetrics(versionManager));
+ }
+ return metrics;
+ }
+
+ @Override
+ public void getMetrics(MetricsCollector collector, boolean all) {
+ MetricsRecordBuilder builder = collector.addRecord(METRICS_SOURCE_NAME);
+ builder
+ .addGauge(SOFTWARE_VERSION,
+ versionManager.getSoftwareVersion().serialize())
+ .addGauge(APPARENT_VERSION,
+ versionManager.getApparentVersion().serialize());
+ }
+
+ public void unRegister() {
+ DefaultMetricsSystem.instance().unregisterSource(METRICS_SOURCE_NAME);
+ }
+}
diff --git
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/upgrade/TestHDDSVersionManager.java
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/upgrade/TestHDDSVersionManager.java
new file mode 100644
index 00000000000..1e76ff91be3
--- /dev/null
+++
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/upgrade/TestHDDSVersionManager.java
@@ -0,0 +1,69 @@
+/*
+ * 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.hadoop.hdds.upgrade;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Stream;
+import org.apache.hadoop.hdds.ComponentVersion;
+import org.apache.hadoop.hdds.HDDSVersion;
+import org.apache.hadoop.ozone.upgrade.AbstractComponentVersionManagerTest;
+import org.apache.hadoop.ozone.upgrade.ComponentVersionManager;
+import org.junit.jupiter.params.provider.Arguments;
+
+/**
+ * Tests for {@link HDDSVersionManager}.
+ */
+class TestHDDSVersionManager extends AbstractComponentVersionManagerTest {
+
+ private static final List<ComponentVersion> ALL_VERSIONS;
+
+ static {
+ ALL_VERSIONS = new ArrayList<>(Arrays.asList(HDDSLayoutFeature.values()));
+
+ for (HDDSVersion version : HDDSVersion.values()) {
+ // Add all defined versions after and including ZDU to get the complete
version list.
+ if (HDDSVersion.ZDU.isSupportedBy(version) && version !=
HDDSVersion.FUTURE_VERSION) {
+ ALL_VERSIONS.add(version);
+ }
+ }
+ }
+
+ public static Stream<Arguments> preFinalizedVersionArgs() {
+ return ALL_VERSIONS.stream()
+ .limit(ALL_VERSIONS.size() - 1)
+ .map(Arguments::of);
+ }
+
+ @Override
+ protected ComponentVersionManager createManager(int
serializedApparentVersion) throws IOException {
+ return new HDDSVersionManager(serializedApparentVersion);
+ }
+
+ @Override
+ protected List<ComponentVersion> allVersionsInOrder() {
+ return ALL_VERSIONS;
+ }
+
+ @Override
+ protected ComponentVersion expectedSoftwareVersion() {
+ return HDDSVersion.SOFTWARE_VERSION;
+ }
+}
diff --git
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/ozone/upgrade/AbstractComponentVersionManagerTest.java
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/ozone/upgrade/AbstractComponentVersionManagerTest.java
new file mode 100644
index 00000000000..0bf439326ca
--- /dev/null
+++
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/ozone/upgrade/AbstractComponentVersionManagerTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.hadoop.ozone.upgrade;
+
+import static org.apache.ozone.test.MetricsAsserts.assertGauge;
+import static org.apache.ozone.test.MetricsAsserts.getMetrics;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import org.apache.hadoop.hdds.ComponentVersion;
+import org.apache.hadoop.metrics2.MetricsRecordBuilder;
+import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Mockito;
+
+/**
+ * Shared tests for concrete {@link ComponentVersionManager} implementations.
+ */
+public abstract class AbstractComponentVersionManagerTest {
+
+ protected abstract ComponentVersionManager createManager(int
serializedApparentVersion) throws IOException;
+
+ protected abstract List<ComponentVersion> allVersionsInOrder();
+
+ protected abstract ComponentVersion expectedSoftwareVersion();
+
+ @AfterEach
+ public void cleanupMetricsSource() {
+
DefaultMetricsSystem.instance().unregisterSource(ComponentVersionManagerMetrics.METRICS_SOURCE_NAME);
+ }
+
+ @Test
+ public void testApparentVersionTranslation() throws Exception {
+ for (ComponentVersion apparentVersion : allVersionsInOrder()) {
+ try (ComponentVersionManager versionManager =
createManager(apparentVersion.serialize())) {
+ assertApparentVersion(versionManager, apparentVersion);
+ }
+ }
+ }
+
+ @Test
+ public void testApparentVersionBehindSoftwareVersion() {
+ int serializedNextVersion = expectedSoftwareVersion().serialize() + 1;
+ assertThrows(IOException.class, () ->
createManager(serializedNextVersion));
+ }
+
+ @ParameterizedTest
+ // Child classes must implement this as a static method to provide the
versions to start finalization from.
+ @MethodSource("preFinalizedVersionArgs")
+ public void testFinalizationFromEarlierVersions(ComponentVersion
apparentVersion) throws Exception {
+ List<ComponentVersion> allVersions = allVersionsInOrder();
+ int apparentVersionIndex = allVersions.indexOf(apparentVersion);
+ assertTrue(apparentVersionIndex >= 0, "Apparent version " +
apparentVersion + " must exist");
+ Iterator<ComponentVersion> expectedVersions =
allVersions.subList(apparentVersionIndex + 1, allVersions.size())
+ .iterator();
+
+ try (ComponentVersionManager versionManager =
createManager(apparentVersion.serialize())) {
+ assertApparentVersion(versionManager, apparentVersion);
+
+ for (ComponentVersion versionToFinalize :
versionManager.getUnfinalizedVersions()) {
+ assertTrue(versionManager.needsFinalization());
+ assertFalse(versionManager.isAllowed(versionToFinalize),
+ "Unfinalized version " + versionToFinalize + " should not be
allowed by apparent version "
+ + versionManager.getApparentVersion());
+ assertTrue(expectedVersions.hasNext());
+ assertEquals(expectedVersions.next(), versionToFinalize);
+
+ versionManager.markFinalized(versionToFinalize);
+ assertApparentVersion(versionManager, versionToFinalize);
+ }
+
+ assertFalse(expectedVersions.hasNext());
+ assertThrows(NoSuchElementException.class, expectedVersions::next);
+ }
+ }
+
+ @Test
+ public void testFinalizationFromSoftwareVersionNoOp() throws Exception {
+ try (ComponentVersionManager versionManager =
createManager(expectedSoftwareVersion().serialize())) {
+ assertApparentVersion(versionManager, expectedSoftwareVersion());
+ assertFalse(versionManager.needsFinalization());
+
assertFalse(versionManager.getUnfinalizedVersions().iterator().hasNext());
+
+ versionManager.markFinalized(expectedSoftwareVersion());
+
+ assertApparentVersion(versionManager, expectedSoftwareVersion());
+ assertFalse(versionManager.needsFinalization());
+
assertFalse(versionManager.getUnfinalizedVersions().iterator().hasNext());
+ }
+ }
+
+ @Test
+ public void testFinalizationOfNonExistentVersion() throws Exception {
+ try (ComponentVersionManager versionManager =
createManager(expectedSoftwareVersion().serialize())) {
+ assertApparentVersion(versionManager, expectedSoftwareVersion());
+ assertFalse(versionManager.needsFinalization());
+
assertFalse(versionManager.getUnfinalizedVersions().iterator().hasNext());
+
+ ComponentVersion mockVersion = Mockito.mock(ComponentVersion.class);
+ when(mockVersion.isSupportedBy(any())).thenReturn(false);
+
+ assertThrows(IllegalArgumentException.class, () ->
versionManager.markFinalized(mockVersion));
+ // The failed finalization call should not have changed the version
manager's state.
+ assertApparentVersion(versionManager, expectedSoftwareVersion());
+ assertFalse(versionManager.needsFinalization());
+
assertFalse(versionManager.getUnfinalizedVersions().iterator().hasNext());
+ }
+ }
+
+ private void assertApparentVersion(ComponentVersionManager versionManager,
ComponentVersion apparentVersion) {
+ assertEquals(apparentVersion, versionManager.getApparentVersion());
+ assertTrue(versionManager.isAllowed(apparentVersion), apparentVersion + "
should be allowed");
+ assertEquals(expectedSoftwareVersion(),
versionManager.getSoftwareVersion(),
+ "Software version should never change");
+ if (!versionManager.needsFinalization()) {
+ assertTrue(versionManager.isAllowed(expectedSoftwareVersion()),
+ "Software version should always be allowed when finalized");
+ }
+ MetricsRecordBuilder metrics =
getMetrics(ComponentVersionManagerMetrics.METRICS_SOURCE_NAME);
+ assertGauge("SoftwareVersion", expectedSoftwareVersion().serialize(),
metrics);
+ assertGauge("ApparentVersion", apparentVersion.serialize(), metrics);
+ }
+}
diff --git
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/ozone/upgrade/TestAbstractLayoutVersionManager.java
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/ozone/upgrade/TestAbstractLayoutVersionManager.java
index f006698518a..6cd9391e28f 100644
---
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/ozone/upgrade/TestAbstractLayoutVersionManager.java
+++
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/ozone/upgrade/TestAbstractLayoutVersionManager.java
@@ -29,6 +29,7 @@
import java.util.Iterator;
import javax.management.MBeanServer;
import javax.management.ObjectName;
+import org.apache.hadoop.hdds.ComponentVersion;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -188,6 +189,12 @@ public int layoutVersion() {
public String description() {
return null;
}
+
+ @Override
+ public ComponentVersion nextVersion() {
+ // TODO HDDS-14826 will remove this test. No need to add handling
for this new method.
+ return null;
+ }
};
}
return lfs;
diff --git
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/ozone/upgrade/TestUpgradeFinalizerActions.java
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/ozone/upgrade/TestUpgradeFinalizerActions.java
index ade86b488b9..89a52b7c31d 100644
---
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/ozone/upgrade/TestUpgradeFinalizerActions.java
+++
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/ozone/upgrade/TestUpgradeFinalizerActions.java
@@ -86,6 +86,13 @@ public String description() {
return null;
}
+ @Override
+ public MockLayoutFeature nextVersion() {
+ // TODO HDDS-14826 will remove the tests that are using this. No need to
provide an implementation for this new
+ // method.
+ return null;
+ }
+
@Override
public String toString() {
return name() + " (" + serialize() + ")";
diff --git
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/upgrade/OMLayoutFeature.java
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/upgrade/OMLayoutFeature.java
index c19b720682d..9f69215b694 100644
---
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/upgrade/OMLayoutFeature.java
+++
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/upgrade/OMLayoutFeature.java
@@ -17,11 +17,21 @@
package org.apache.hadoop.ozone.om.upgrade;
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toMap;
+
+import java.util.Arrays;
import java.util.Optional;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import org.apache.hadoop.hdds.ComponentVersion;
+import org.apache.hadoop.ozone.OzoneManagerVersion;
import org.apache.hadoop.ozone.upgrade.LayoutFeature;
/**
- * List of OM Layout features / versions.
+ * List of OM Layout Features. All version management has been migrated to
{@link OzoneManagerVersion} and no new
+ * additions should be made to this class. Existing versions are kept here for
backwards compatibility when upgrading
+ * to this version from older versions.
*/
public enum OMLayoutFeature implements LayoutFeature {
////////////////////////////// //////////////////////////////
@@ -46,8 +56,14 @@ public enum OMLayoutFeature implements LayoutFeature {
DELEGATION_TOKEN_SYMMETRIC_SIGN(8, "Delegation token signed by symmetric
key"),
SNAPSHOT_DEFRAG(9, "Supporting defragmentation of snapshot");
+ // ALL NEW VERSIONS SHOULD NOW BE ADDED TO OzoneManagerVersion
+
/////////////////////////////// /////////////////////////////
+ private static final SortedMap<Integer, OMLayoutFeature> BY_VALUE =
+ Arrays.stream(values())
+ .collect(toMap(OMLayoutFeature::serialize, identity(), (v1, v2) ->
v1, TreeMap::new));
+
private final int layoutVersion;
private final String description;
private OmUpgradeAction action;
@@ -62,6 +78,15 @@ public int layoutVersion() {
return layoutVersion;
}
+ /**
+ * @param version The serialized version to convert.
+ * @return The version corresponding to this serialized value, or {@code
null} if no matching version is
+ * found.
+ */
+ public static OMLayoutFeature deserialize(int version) {
+ return BY_VALUE.get(version);
+ }
+
@Override
public String description() {
return description;
@@ -84,6 +109,21 @@ public void addAction(OmUpgradeAction upgradeAction) {
}
}
+ /**
+ * @return The next version immediately following this one. If there is no
next version found in this enum,
+ * the next version is {@link OzoneManagerVersion#ZDU}, since all OM
versioning has been migrated to
+ * {@link OzoneManagerVersion} as part of the ZDU feature.
+ */
+ @Override
+ public ComponentVersion nextVersion() {
+ OMLayoutFeature nextFeature = BY_VALUE.get(layoutVersion + 1);
+ if (nextFeature == null) {
+ return OzoneManagerVersion.ZDU;
+ } else {
+ return nextFeature;
+ }
+ }
+
@Override
public Optional<OmUpgradeAction> action() {
return Optional.ofNullable(action);
diff --git
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/upgrade/OMVersionManager.java
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/upgrade/OMVersionManager.java
new file mode 100644
index 00000000000..f250928b2a9
--- /dev/null
+++
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/upgrade/OMVersionManager.java
@@ -0,0 +1,48 @@
+/*
+ * 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.hadoop.ozone.om.upgrade;
+
+import java.io.IOException;
+import org.apache.hadoop.hdds.ComponentVersion;
+import org.apache.hadoop.ozone.OzoneManagerVersion;
+import org.apache.hadoop.ozone.upgrade.ComponentVersionManager;
+
+/**
+ * Component version manager for Ozone Manager.
+ */
+public class OMVersionManager extends ComponentVersionManager {
+ public OMVersionManager(int serializedApparentVersion) throws IOException {
+ super(computeApparentVersion(serializedApparentVersion),
OzoneManagerVersion.SOFTWARE_VERSION);
+ }
+
+ /**
+ * If the apparent version stored on the disk is >= 100, it indicates the
component has been finalized for the
+ * ZDU feature, and the apparent version corresponds to a version in {@link
OzoneManagerVersion}.
+ * If the apparent version stored on the disk is < 100, it indicates the
component is not yet finalized for the
+ * ZDU feature, and the apparent version corresponds to a version in {@link
OMLayoutFeature}.
+ */
+ private static ComponentVersion computeApparentVersion(int
serializedApparentVersion) {
+ if (serializedApparentVersion < OzoneManagerVersion.ZDU.serialize()) {
+ return OMLayoutFeature.deserialize(serializedApparentVersion);
+ } else {
+ return OzoneManagerVersion.deserialize(serializedApparentVersion);
+ }
+ }
+
+ // TODO HDDS-14826: Register upgrade actions based on annotations
+}
diff --git
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/TestOMLayoutFeature.java
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/TestOMLayoutFeature.java
new file mode 100644
index 00000000000..9e98935b5b3
--- /dev/null
+++
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/TestOMLayoutFeature.java
@@ -0,0 +1,109 @@
+/*
+ * 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.hadoop.ozone.om.upgrade;
+
+import static
org.apache.hadoop.hdds.ComponentVersionTestUtils.assertNotSupportedBy;
+import static
org.apache.hadoop.hdds.ComponentVersionTestUtils.assertSupportedBy;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.hadoop.ozone.OzoneManagerVersion;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests invariants for legacy OM layout feature versions.
+ */
+public class TestOMLayoutFeature {
+ @Test
+ public void testOMLayoutFeaturesHaveIncreasingLayoutVersion() {
+ OMLayoutFeature[] values = OMLayoutFeature.values();
+ int currVersion = -1;
+ for (OMLayoutFeature lf : values) {
+ // This will skip the jump from the last OMLayoutFeature to
OzoneManagerVersion#ZDU,
+ // since that is expected to be a larger version increment.
+ assertEquals(currVersion + 1, lf.layoutVersion(),
+ "Expected monotonically increasing layout version for " + lf);
+ currVersion = lf.layoutVersion();
+ }
+ }
+
+ /**
+ * All incompatible changes to OM should now be added to {@link
OzoneManagerVersion}.
+ */
+ @Test
+ public void testNoNewOMLayoutFeaturesAdded() {
+ int numOMLayoutFeatures = OMLayoutFeature.values().length;
+ OMLayoutFeature lastFeature = OMLayoutFeature.values()[numOMLayoutFeatures
- 1];
+ assertEquals(10, numOMLayoutFeatures);
+ assertEquals(OMLayoutFeature.SNAPSHOT_DEFRAG, lastFeature);
+ assertEquals(9, lastFeature.layoutVersion());
+ }
+
+ @Test
+ public void testNextVersion() {
+ OMLayoutFeature[] values = OMLayoutFeature.values();
+ for (int i = 1; i < values.length; i++) {
+ OMLayoutFeature previous = values[i - 1];
+ OMLayoutFeature current = values[i];
+ assertEquals(current, previous.nextVersion(),
+ "Expected " + previous + ".nextVersion() to be " + current);
+ }
+ // The last layout feature should point us to the ZDU version to switch to
using OzoneManagerVersion.
+ assertEquals(OzoneManagerVersion.ZDU, values[values.length -
1].nextVersion());
+ }
+
+ @Test
+ public void testSerDes() {
+ for (OMLayoutFeature version : OMLayoutFeature.values()) {
+ assertEquals(version, OMLayoutFeature.deserialize(version.serialize()));
+ }
+ }
+
+ @Test
+ public void testDeserializeUnknownVersionReturnsNull() {
+ assertNull(OMLayoutFeature.deserialize(-1));
+ assertNull(OMLayoutFeature.deserialize(Integer.MAX_VALUE));
+ // OMLayoutFeature can only deserialize values from its own enum.
+
assertNull(OMLayoutFeature.deserialize(OzoneManagerVersion.ZDU.serialize()));
+ }
+
+ @Test
+ public void testIsSupportedByFeatureBoundary() {
+ for (OMLayoutFeature feature : OMLayoutFeature.values()) {
+ // A layout feature should support itself.
+ int layoutVersion = feature.layoutVersion();
+ assertSupportedBy(feature, feature);
+ if (layoutVersion > 0) {
+ // A layout feature should not be supported by older features.
+ OMLayoutFeature previousFeature =
OMLayoutFeature.values()[layoutVersion - 1];
+ assertNotSupportedBy(feature, previousFeature);
+ }
+ }
+ }
+
+ @Test
+ public void testAllLayoutFeaturesAreSupportedByFutureVersions() {
+ for (OMLayoutFeature feature : OMLayoutFeature.values()) {
+ assertSupportedBy(feature, OzoneManagerVersion.ZDU);
+ assertSupportedBy(feature, OzoneManagerVersion.FUTURE_VERSION);
+ // No ComponentVersion instance represents an arbitrary unknown future
version.
+ assertTrue(feature.isSupportedBy(Integer.MAX_VALUE));
+ }
+ }
+}
diff --git
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/TestOMVersionManager.java
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/TestOMLayoutVersionManager.java
similarity index 87%
copy from
hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/TestOMVersionManager.java
copy to
hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/TestOMLayoutVersionManager.java
index 388b5134c41..2c2044e9d04 100644
---
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/TestOMVersionManager.java
+++
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/TestOMLayoutVersionManager.java
@@ -44,7 +44,7 @@
/**
* Test OM layout version management.
*/
-public class TestOMVersionManager {
+public class TestOMLayoutVersionManager {
@Test
public void testOMLayoutVersionManager() throws IOException {
@@ -65,26 +65,6 @@ public void testOMLayoutVersionManagerInitError() {
assertEquals(NOT_SUPPORTED_OPERATION, ome.getResult());
}
- @Test
- public void testOMLayoutFeaturesHaveIncreasingLayoutVersion()
- throws Exception {
- OMLayoutFeature[] values = OMLayoutFeature.values();
- int currVersion = -1;
- OMLayoutFeature lastFeature = null;
- for (OMLayoutFeature lf : values) {
- assertEquals(currVersion + 1, lf.layoutVersion());
- currVersion = lf.layoutVersion();
- lastFeature = lf;
- }
- lastFeature.addAction(arg -> {
- String v = arg.getVersion();
- });
-
- OzoneManager omMock = mock(OzoneManager.class);
- lastFeature.action().get().execute(omMock);
- verify(omMock, times(1)).getVersion();
- }
-
@Test
/*
diff --git
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/TestOMVersionManager.java
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/TestOMVersionManager.java
index 388b5134c41..eed52733acb 100644
---
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/TestOMVersionManager.java
+++
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/TestOMVersionManager.java
@@ -17,128 +17,52 @@
package org.apache.hadoop.ozone.om.upgrade;
-import static
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.NOT_SUPPORTED_OPERATION;
-import static
org.apache.hadoop.ozone.om.upgrade.OMLayoutFeature.INITIAL_VERSION;
-import static
org.apache.hadoop.ozone.om.upgrade.OMLayoutVersionManager.OM_UPGRADE_CLASS_PACKAGE;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.doCallRealMethod;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
import java.io.IOException;
-import java.lang.reflect.Method;
+import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Optional;
-import org.apache.hadoop.ozone.om.OzoneManager;
-import org.apache.hadoop.ozone.om.exceptions.OMException;
-import org.apache.hadoop.ozone.om.request.OMClientRequest;
-import org.junit.jupiter.api.Test;
+import java.util.List;
+import java.util.stream.Stream;
+import org.apache.hadoop.hdds.ComponentVersion;
+import org.apache.hadoop.ozone.OzoneManagerVersion;
+import org.apache.hadoop.ozone.upgrade.AbstractComponentVersionManagerTest;
+import org.apache.hadoop.ozone.upgrade.ComponentVersionManager;
+import org.junit.jupiter.params.provider.Arguments;
/**
- * Test OM layout version management.
+ * Tests for {@link OMVersionManager}.
*/
-public class TestOMVersionManager {
-
- @Test
- public void testOMLayoutVersionManager() throws IOException {
- OMLayoutVersionManager omVersionManager =
- new OMLayoutVersionManager();
+class TestOMVersionManager extends AbstractComponentVersionManagerTest {
- // Initial Version is always allowed.
- assertTrue(omVersionManager.isAllowed(INITIAL_VERSION));
- assertThat(INITIAL_VERSION.layoutVersion())
- .isLessThanOrEqualTo(omVersionManager.getMetadataLayoutVersion());
- }
+ private static final List<ComponentVersion> ALL_VERSIONS;
- @Test
- public void testOMLayoutVersionManagerInitError() {
- int lV = OMLayoutFeature.values()[OMLayoutFeature.values().length - 1]
- .layoutVersion() + 1;
- OMException ome = assertThrows(OMException.class, () -> new
OMLayoutVersionManager(lV));
- assertEquals(NOT_SUPPORTED_OPERATION, ome.getResult());
- }
-
- @Test
- public void testOMLayoutFeaturesHaveIncreasingLayoutVersion()
- throws Exception {
- OMLayoutFeature[] values = OMLayoutFeature.values();
- int currVersion = -1;
- OMLayoutFeature lastFeature = null;
- for (OMLayoutFeature lf : values) {
- assertEquals(currVersion + 1, lf.layoutVersion());
- currVersion = lf.layoutVersion();
- lastFeature = lf;
+ static {
+ ALL_VERSIONS = new ArrayList<>(Arrays.asList(OMLayoutFeature.values()));
+ for (OzoneManagerVersion version : OzoneManagerVersion.values()) {
+ // Add all defined versions after and including ZDU to get the complete
version list.
+ if (OzoneManagerVersion.ZDU.isSupportedBy(version) && version !=
OzoneManagerVersion.FUTURE_VERSION) {
+ ALL_VERSIONS.add(version);
+ }
}
- lastFeature.addAction(arg -> {
- String v = arg.getVersion();
- });
-
- OzoneManager omMock = mock(OzoneManager.class);
- lastFeature.action().get().execute(omMock);
- verify(omMock, times(1)).getVersion();
}
- @Test
-
- /*
- * The OMLayoutFeatureAspect relies on the fact that the OM client
- * request handler class has a preExecute method with first argument as
- * 'OzoneManager'. If that is not true, please fix
- * OMLayoutFeatureAspect#beforeRequestApplyTxn.
- */
- public void testOmClientRequestPreExecuteIsCompatibleWithAspect() {
- Method[] methods = OMClientRequest.class.getMethods();
-
- Optional<Method> preExecuteMethod = Arrays.stream(methods)
- .filter(m -> m.getName().equals("preExecute"))
- .findFirst();
-
- assertTrue(preExecuteMethod.isPresent());
-
assertThat(preExecuteMethod.get().getParameterCount()).isGreaterThanOrEqualTo(1);
- assertEquals(OzoneManager.class,
- preExecuteMethod.get().getParameterTypes()[0]);
+ public static Stream<Arguments> preFinalizedVersionArgs() {
+ return ALL_VERSIONS.stream()
+ .limit(ALL_VERSIONS.size() - 1)
+ .map(Arguments::of);
}
- @Test
- public void testOmUpgradeActionsRegistered() throws Exception {
- OMLayoutVersionManager lvm = new OMLayoutVersionManager(); // MLV >= 0
- assertFalse(lvm.needsFinalization());
-
- // INITIAL_VERSION is finalized, hence should not register.
- Optional<OmUpgradeAction> action =
- INITIAL_VERSION.action();
- assertFalse(action.isPresent());
-
- lvm = mock(OMLayoutVersionManager.class);
- when(lvm.getMetadataLayoutVersion()).thenReturn(-1);
- doCallRealMethod().when(lvm).registerUpgradeActions(anyString());
- lvm.registerUpgradeActions(OM_UPGRADE_CLASS_PACKAGE);
-
- action = INITIAL_VERSION.action();
- assertTrue(action.isPresent());
- assertEquals(MockOmUpgradeAction.class, action.get().getClass());
- OzoneManager omMock = mock(OzoneManager.class);
- action.get().execute(omMock);
- verify(omMock, times(1)).getVersion();
+ @Override
+ protected ComponentVersionManager createManager(int
serializedApparentVersion) throws IOException {
+ return new OMVersionManager(serializedApparentVersion);
}
- /**
- * Mock OM upgrade action class.
- */
- @UpgradeActionOm(feature =
- INITIAL_VERSION)
- public static class MockOmUpgradeAction implements OmUpgradeAction {
+ @Override
+ protected List<ComponentVersion> allVersionsInOrder() {
+ return ALL_VERSIONS;
+ }
- @Override
- public void execute(OzoneManager arg) {
- arg.getVersion();
- }
+ @Override
+ protected ComponentVersion expectedSoftwareVersion() {
+ return OzoneManagerVersion.SOFTWARE_VERSION;
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]