diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/ClientVersions.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/ClientVersions.java index 691cf9a7314b..d095a5bdb0ea 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/ClientVersions.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/ClientVersions.java @@ -28,8 +28,27 @@ public final class ClientVersions { // DatanodeDetails#getFromProtobuf handles unknown types of ports public static final int VERSION_HANDLES_UNKNOWN_DN_PORTS = 1; + // TODO: Change the version of FSO/EC if one is released before the other. + // Client supports EC objects + public static final int CLIENT_EC_CAPABLE = 2; + // Client supports FSO buckets + public static final int CLIENT_FSO_CAPABLE = CLIENT_EC_CAPABLE; + // this should always point to the latest version - public static final int CURRENT_VERSION = VERSION_HANDLES_UNKNOWN_DN_PORTS; + public static final int CURRENT_VERSION = CLIENT_EC_CAPABLE; + + /** + * Validates if the client version is equal to or newer than the expected + * version supported. Client are expected to be backward compatible if it + * is sending a request. + * @param desiredClientVersion Minimum version of the client expected. + * @param actualClientVersion Version of the client witnessed. + * @return true if client is at or newer than the desired version. + */ + public static boolean isClientCompatible(int desiredClientVersion, + int actualClientVersion) { + return desiredClientVersion >= actualClientVersion; + } private ClientVersions() { // no instances diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OMProtocolVersion.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OMProtocolVersion.java new file mode 100644 index 000000000000..fdcb994328dc --- /dev/null +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OMProtocolVersion.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.ozone; + +import org.apache.hadoop.util.ComparableVersion; + +/** + * Versioning for OM's APIs used by ozone clients. + */ +public final class OMProtocolVersion { + + // OM Authenticates the user using the S3 Auth info embedded in request proto. + public static final String OM_SUPPORTS_S3AUTH_VIA_PROTO = "2.0.0"; + public static final String OM_SUPPORTS_EC = "2.1.0"; + public static final String OM_SUPPORTS_FSO = "2.1.0"; + + // Points to the latest version in code, always update this when adding + // a new version. + public static final String OM_LATEST_VERSION = OM_SUPPORTS_S3AUTH_VIA_PROTO; + + /** + * Checks if the OM is running a version at or greater than the desired + * version. A Client might leverage a version that breaks compatibility + * with older OMs and this method can be used to check the compatability. + * If the desired version is not specified, it assumes there is no minimum + * version requirement. + * @param desiredOMVersion Minimum version that is required for OM. + * @param actualOMVersion Actual version received. + * @return true if actual OM version is at or newer than the desired version. + */ + public static boolean isOMNewerThan( + String desiredOMVersion, + String actualOMVersion) { + if (desiredOMVersion == null || desiredOMVersion.isEmpty()) { + // Empty strings assumes client is fine with any OM version. + return true; + } + ComparableVersion comparableExpectedVersion = + new ComparableVersion(desiredOMVersion); + ComparableVersion comparableOMVersion = + new ComparableVersion(actualOMVersion); + return comparableOMVersion.compareTo(comparableExpectedVersion) >= 0; + } + + private OMProtocolVersion() { + + } +} diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java index 9c7d7690faa4..4d199600ae99 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java @@ -30,6 +30,8 @@ import java.util.concurrent.TimeUnit; +import static org.apache.hadoop.ozone.OMProtocolVersion.OM_LATEST_VERSION; + /** * This class contains constants for configuration keys used in Ozone. */ @@ -465,7 +467,8 @@ public final class OzoneConfigKeys { "ozone.om.client.protocol.version"; // The version of the protocol for Client (S3G/OFS) to OM Communication. // The protocol starts at 2.0.0 and a null or empty value for older versions. - public static final String OZONE_OM_CLIENT_PROTOCOL_VERSION = "2.0.0"; + public static final String OZONE_OM_CLIENT_PROTOCOL_VERSION = + OM_LATEST_VERSION; /** * There is no need to instantiate this class. diff --git a/hadoop-hdds/common/src/test/java/org/apache/hadoop/OMProtocolVersionTest.java b/hadoop-hdds/common/src/test/java/org/apache/hadoop/OMProtocolVersionTest.java new file mode 100644 index 000000000000..286609accd35 --- /dev/null +++ b/hadoop-hdds/common/src/test/java/org/apache/hadoop/OMProtocolVersionTest.java @@ -0,0 +1,85 @@ +/** + * 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; + +import org.junit.jupiter.api.Test; + +import java.util.LinkedList; +import java.util.List; + +import static org.apache.hadoop.ozone.OMProtocolVersion.OM_LATEST_VERSION; + +class OMProtocolVersionTest { + private enum ValidateOmVersionTestCases { + NULL_EXPECTED_NULL_OM( + null, // Expected version + null, // OM Version + true), // Should validation pass + EMPTY_EXPECTED_NULL_OM( + "", + null, + true), + EMPTY_EXPECTED_EMPTY_OM( + "", + "", + true), + NULL_EXPECTED_EMPTY_OM( + null, + "", + true), + OM_EXPECTED_LATEST_OM( + "1.0.0", + OM_LATEST_VERSION, + true), + OM_EXPECTED_NEWER_OM( + "1.1.0", + "1.11.0", + true), + NEWER_EXPECTED_OLD_OM( + "1.20.0", + "1.19.0", + false), + NULL_EXPECTED_OM( + null, + OM_LATEST_VERSION, + true); + private static final List + TEST_CASES = new LinkedList<>(); + + static { + for (ValidateOmVersionTestCases t : values()) { + TEST_CASES.add(t); + } + } + private final String expectedVersion; + private final String omVersion; + private final boolean validation; + ValidateOmVersionTestCases(String expectedVersion, + String omVersion, + boolean validation) { + this.expectedVersion = expectedVersion; + this.omVersion = omVersion; + this.validation = validation; + } + + } + @Test + void isOMCompatible() { + } +} \ No newline at end of file diff --git a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java index b9db113e935f..160ea53693ae 100644 --- a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java +++ b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java @@ -41,6 +41,7 @@ import java.util.function.Function; import java.util.stream.Collectors; +import org.apache.hadoop.ozone.OMProtocolVersion; import org.apache.hadoop.crypto.CryptoInputStream; import org.apache.hadoop.crypto.CryptoOutputStream; import org.apache.hadoop.crypto.key.KeyProvider; @@ -139,7 +140,6 @@ import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_OM_CLIENT_PROTOCOL_VERSION_KEY; import static org.apache.hadoop.ozone.OzoneConsts.OLD_QUOTA_DEFAULT; -import org.apache.hadoop.util.ComparableVersion; import org.apache.logging.log4j.util.Strings; import org.apache.ratis.protocol.ClientId; import org.jetbrains.annotations.NotNull; @@ -292,18 +292,12 @@ public XceiverClientFactory getXceiverClientManager() { static boolean validateOmVersion(String expectedVersion, List serviceInfoList) { - if (expectedVersion == null || expectedVersion.isEmpty()) { - // Empty strings assumes client is fine with any OM version. - return true; - } boolean found = false; // At min one OM should be present. for (ServiceInfo s: serviceInfoList) { if (s.getNodeType() == HddsProtos.NodeType.OM) { - ComparableVersion comparableExpectedVersion = - new ComparableVersion(expectedVersion); - ComparableVersion comparableOMVersion = - new ComparableVersion(s.getProtobuf().getOMProtocolVersion()); - if (comparableOMVersion.compareTo(comparableExpectedVersion) < 0) { + if (!OMProtocolVersion.isOMNewerThan( + expectedVersion, + s.getProtobuf().getOMProtocolVersion())) { return false; } else { found = true; diff --git a/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/rpc/RpcClientTest.java b/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/rpc/RpcClientTest.java index c7d393a44ccd..f8a548932e97 100644 --- a/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/rpc/RpcClientTest.java +++ b/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/rpc/RpcClientTest.java @@ -39,7 +39,7 @@ private enum ValidateOmVersionTestCases { null, // Expected version null, // First OM Version null, // Second OM Version - true), // Should validation pass + false), // Should validation pass NULL_EXPECTED_ONE_OM( null, OZONE_OM_CLIENT_PROTOCOL_VERSION, @@ -69,7 +69,7 @@ private enum ValidateOmVersionTestCases { "", null, null, - true), + false), EMPTY_EXPECTED_ONE_OM( "", OZONE_OM_CLIENT_PROTOCOL_VERSION, @@ -95,6 +95,11 @@ private enum ValidateOmVersionTestCases { null, null, false), + VALID_EXPECTED_EMPTY_OM( + OZONE_OM_CLIENT_PROTOCOL_VERSION, + "", + "", + false), VALID_EXPECTED_ONE_OM( OZONE_OM_CLIENT_PROTOCOL_VERSION, OZONE_OM_CLIENT_PROTOCOL_VERSION, diff --git a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto index df3f2cc6dbd3..faa8c9f0ed14 100644 --- a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto +++ b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto @@ -114,7 +114,7 @@ message OMRequest { required string clientId = 3; optional UserInfo userInfo = 4; - optional uint32 version = 5; + optional uint32 version = 5; // client version optional LayoutVersion layoutVersion = 6; diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OMManagerClientVersionValidations.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OMManagerClientVersionValidations.java new file mode 100644 index 000000000000..4db4c51d9acf --- /dev/null +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OMManagerClientVersionValidations.java @@ -0,0 +1,71 @@ +/* + * 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.protocolPB; + +import org.apache.hadoop.ozone.ClientVersions; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * OMManagerClientVersionsValidations is a collection of all the validations + * that need to be run to maintain compatibility with varying versions of + * client. + */ +public final class OMManagerClientVersionValidations { + private static final Logger LOG = + LoggerFactory.getLogger(OMManagerClientVersionValidations.class); + private OMManagerClientVersionValidations() { + + } + + public static void validateClientVersionPostProcess( + OzoneManagerProtocolProtos.Type lookupKey, + OzoneManagerProtocolProtos.OMRequest request, + OzoneManagerProtocolProtos.InfoBucketResponse infoBucketResponse) { + // ToDo: Add EC specific rejection of request based on key info. + if (request.hasVersion() + && + ClientVersions.isClientCompatible( + ClientVersions.CLIENT_EC_CAPABLE, + request.getVersion())){ + return; + } + // TODO: Update the response and log message + LOG.debug("Request rejected as client version is not EC Compatible: {}", + request); + } + public static void validateClientVersionPostProcess( + OzoneManagerProtocolProtos.Type lookupKey, + OzoneManagerProtocolProtos.OMRequest request, + OzoneManagerProtocolProtos.LookupKeyResponse lookupKeyResponse) { + // ToDo: Add FSO specific rejection of request based on bucket info. + if (request.hasVersion() + && + ClientVersions.isClientCompatible( + ClientVersions.CLIENT_FSO_CAPABLE, + request.getVersion())){ + return; + } + // TODO: Update the response and log message + LOG.debug("Request rejected as client version is not FSO Compatible: {}", + request); + } + +} diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java index 5674d6a3152f..66cb791c04d2 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java @@ -94,6 +94,8 @@ import static org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.MultipartUploadInfo; import static org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OzoneAclInfo; import static org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.PartInfo; +import static org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Type.InfoBucket; +import static org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Type.LookupKey; import org.apache.hadoop.ozone.upgrade.UpgradeFinalizer.StatusAndMessages; import org.slf4j.Logger; @@ -146,6 +148,10 @@ public OMResponse handleReadRequest(OMRequest request) { InfoBucketResponse infoBucketResponse = infoBucket( request.getInfoBucketRequest()); responseBuilder.setInfoBucketResponse(infoBucketResponse); + OMManagerClientVersionValidations.validateClientVersionPostProcess( + InfoBucket, + request, + infoBucketResponse); break; case ListBuckets: ListBucketsResponse listBucketsResponse = listBuckets( @@ -156,6 +162,10 @@ public OMResponse handleReadRequest(OMRequest request) { LookupKeyResponse lookupKeyResponse = lookupKey( request.getLookupKeyRequest(), request.getVersion()); responseBuilder.setLookupKeyResponse(lookupKeyResponse); + OMManagerClientVersionValidations.validateClientVersionPostProcess( + LookupKey, + request, + lookupKeyResponse); break; case ListKeys: ListKeysResponse listKeysResponse = listKeys( @@ -389,10 +399,7 @@ private LookupKeyResponse lookupKey(LookupKeyRequest request, .setHeadOp(keyArgs.getHeadOp()) .build(); OmKeyInfo keyInfo = impl.lookupKey(omKeyArgs); - resp.setKeyInfo(keyInfo.getProtobuf(keyArgs.getHeadOp(), clientVersion)); - - return resp.build(); }