Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "client version is equal to or newer" is opposite of desiredClientVersion >= actualClientVersion.

Suggested change
return desiredClientVersion >= actualClientVersion;
return desiredClientVersion <= actualClientVersion;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clients should be able to talk to older servers, a client can choose to mandate a minimum version for the server but a server can mandate the minimum version of client for a given capability.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a server can mandate the minimum version of client for a given capability.

I think I understand that. But the method does exactly the opposite, it mandates a maximum version.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To confirm this problem, try:

  1. Increase CURRENT_VERSION to 4 (i.e. simulate the situation where we add a new feature and increment current version)
  2. Set log level of OMManagerClientVersionValidations to DEBUG
om_1        | 2022-02-10 19:03:36,205 [IPC Server handler 0 on default port 9862] DEBUG protocolPB.OMManagerClientVersionValidations: Request rejected as client version is not EC Compatible: cmdType: InfoBucket
om_1        | traceID: ""
om_1        | clientId: "client-585C0CCB3EA6"
om_1        | version: 4
om_1        | infoBucketRequest {
om_1        |   volumeName: "vol1"
om_1        |   bucketName: "bucket1"
om_1        | }
om_1        |
...
om_1        | 2022-02-10 19:04:11,472 [IPC Server handler 5 on default port 9862] DEBUG protocolPB.OMManagerClientVersionValidations: Request rejected as client version is not FSO Compatible: cmdType: LookupKey
om_1        | traceID: ""
om_1        | clientId: "client-55BD1FDBB8B5"
om_1        | version: 4
om_1        | lookupKeyRequest {
om_1        |   keyArgs {
om_1        |     volumeName: "vol1"
om_1        |     bucketName: "bucket1"
om_1        |     keyName: "etc/passwd"
om_1        |     dataSize: 0
om_1        |     sortDatanodes: false
om_1        |     latestVersionLocation: true
om_1        |     headOp: false
om_1        |   }
om_1        | }

Notice that client is version: 4, but is "rejected".

}

private ClientVersions() {
// no instances
Expand Down
Original file line number Diff line number Diff line change
@@ -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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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";
Comment on lines +29 to +31
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the benefit of having a 3-digit version number? When would we increment each of the digits?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is follows semantic versioning... https://semver.org/
This will allow us to introduce breaking changes and bump up the highest digit for them.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have semantic versioning at the project level. I don't think we can introduce breaking changes without incrementing the project-level major version number.


// 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() {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ozone build expects tests to be named Test*, not *Test. So this class and RpcClientTest are not run at all in CI.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the reminder.

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<ValidateOmVersionTestCases>
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() {
}
Comment on lines +83 to +84
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be incomplete.

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -292,18 +292,12 @@ public XceiverClientFactory getXceiverClientManager() {

static boolean validateOmVersion(String expectedVersion,
List<ServiceInfo> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -69,7 +69,7 @@ private enum ValidateOmVersionTestCases {
"",
null,
null,
true),
false),
EMPTY_EXPECTED_ONE_OM(
"",
OZONE_OM_CLIENT_PROTOCOL_VERSION,
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
@@ -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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand Down Expand Up @@ -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();
}

Expand Down