blob: 082b06b329787527665abb4e4c8c997e4e6e1bf8 [file] [log] [blame]
/*
* 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.om.request.key;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.hadoop.hdds.client.ReplicationConfig;
import org.apache.hadoop.hdds.client.ReplicationFactor;
import org.apache.hadoop.hdds.client.ReplicationType;
import org.apache.hadoop.ozone.om.OMMetrics;
import org.apache.hadoop.ozone.om.helpers.BucketLayout;
import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
import org.apache.hadoop.ozone.om.helpers.OzoneFSUtils;
import org.apache.hadoop.ozone.om.helpers.WithObjectID;
import org.apache.hadoop.ozone.om.request.OMRequestTestUtils;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
import org.junit.Assert;
import org.junit.Test;
import org.apache.hadoop.ozone.om.response.OMClientResponse;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
.Status;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
.DeleteOpenKeysRequest;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
.OpenKey;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
.OpenKeyBucket;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
.OMRequest;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
/**
* Tests OMOpenKeysDeleteRequest.
*/
@RunWith(Parameterized.class)
public class TestOMOpenKeysDeleteRequest extends TestOMKeyRequest {
private final BucketLayout bucketLayout;
public TestOMOpenKeysDeleteRequest(BucketLayout bucketLayout) {
this.bucketLayout = bucketLayout;
}
@Override
public BucketLayout getBucketLayout() {
return bucketLayout;
}
@Parameters
public static Collection<BucketLayout> bucketLayouts() {
return Arrays.asList(
BucketLayout.DEFAULT,
BucketLayout.FILE_SYSTEM_OPTIMIZED
);
}
/**
* Tests removing keys from the open key table cache that never existed there.
* The operation should complete without errors.
* <p>
* This simulates a run of the open key cleanup service where a set of
* expired open keys are identified and passed to the request, but before
* the request can process them, those keys are committed and removed from
* the open key table.
* @throws Exception
*/
@Test
public void testDeleteOpenKeysNotInTable() throws Exception {
OMRequestTestUtils.addVolumeAndBucketToDB(volumeName, bucketName,
omMetadataManager, getBucketLayout());
List<Pair<Long, OmKeyInfo>> openKeys =
makeOpenKeys(volumeName, bucketName, 5);
deleteOpenKeysFromCache(openKeys);
assertNotInOpenKeyTable(openKeys);
}
/**
* Tests adding multiple keys to the open key table, and updating the table
* cache to only remove some of them.
* Keys not removed should still be present in the open key table.
* Mixes which keys will be kept and deleted among different volumes and
* buckets.
* @throws Exception
*/
@Test
public void testDeleteSubsetOfOpenKeys() throws Exception {
final String volume1 = UUID.randomUUID().toString();
final String volume2 = UUID.randomUUID().toString();
final String bucket1 = UUID.randomUUID().toString();
final String bucket2 = UUID.randomUUID().toString();
OMRequestTestUtils.addVolumeAndBucketToDB(volume1, bucket1,
omMetadataManager, getBucketLayout());
OMRequestTestUtils.addVolumeAndBucketToDB(volume1, bucket2,
omMetadataManager, getBucketLayout());
OMRequestTestUtils.addVolumeAndBucketToDB(volume2, bucket2,
omMetadataManager, getBucketLayout());
List<Pair<Long, OmKeyInfo>> v1b1KeysToDelete =
makeOpenKeys(volume1, bucket1, 3);
List<Pair<Long, OmKeyInfo>> v1b1KeysToKeep =
makeOpenKeys(volume1, bucket1, 3);
List<Pair<Long, OmKeyInfo>> v1b2KeysToDelete =
makeOpenKeys(volume1, bucket2, 3);
List<Pair<Long, OmKeyInfo>> v1b2KeysToKeep =
makeOpenKeys(volume1, bucket2, 2);
List<Pair<Long, OmKeyInfo>> v2b2KeysToDelete =
makeOpenKeys(volume2, bucket2, 2);
List<Pair<Long, OmKeyInfo>> v2b2KeysToKeep =
makeOpenKeys(volume2, bucket2, 3);
addToOpenKeyTableDB(
v1b1KeysToKeep,
v1b2KeysToKeep,
v2b2KeysToKeep,
v1b1KeysToDelete,
v1b2KeysToDelete,
v2b2KeysToDelete
);
deleteOpenKeysFromCache(
v1b1KeysToDelete,
v1b2KeysToDelete,
v2b2KeysToDelete
);
assertNotInOpenKeyTable(
v1b1KeysToDelete,
v1b2KeysToDelete,
v2b2KeysToDelete
);
assertInOpenKeyTable(
v1b1KeysToKeep,
v1b2KeysToKeep,
v2b2KeysToKeep
);
}
/**
* Tests removing keys from the open key table cache that have the same
* name, but different client IDs.
* @throws Exception
*/
@Test
public void testDeleteSameKeyNameDifferentClient() throws Exception {
final String volume = UUID.randomUUID().toString();
final String bucket = UUID.randomUUID().toString();
final String key = UUID.randomUUID().toString();
OMRequestTestUtils.addVolumeAndBucketToDB(volume, bucket,
omMetadataManager, getBucketLayout());
List<Pair<Long, OmKeyInfo>> keysToKeep =
makeOpenKeys(volume, bucket, key, 3);
List<Pair<Long, OmKeyInfo>> keysToDelete =
makeOpenKeys(volume, bucket, key, 3);
addToOpenKeyTableDB(keysToKeep, keysToDelete);
deleteOpenKeysFromCache(keysToDelete);
assertNotInOpenKeyTable(keysToDelete);
assertInOpenKeyTable(keysToKeep);
}
/**
* Tests removing keys from the open key table cache that have higher
* updateID than the transactionID. Those keys should be ignored.
* It is OK if updateID equals to or less than transactionID.
* See {@link WithObjectID#setUpdateID(long, boolean)}.
*
* @throws Exception
*/
@Test
public void testDeleteKeyWithHigherUpdateID() throws Exception {
final String volume = UUID.randomUUID().toString();
final String bucket = UUID.randomUUID().toString();
OMRequestTestUtils.addVolumeAndBucketToDB(volume, bucket,
omMetadataManager, getBucketLayout());
final long updateId = 200L;
final long transactionId = 100L;
OmKeyInfo.Builder builder = new OmKeyInfo.Builder()
.setVolumeName(volume)
.setBucketName(bucket)
.setUpdateID(updateId)
.setReplicationConfig(ReplicationConfig.fromTypeAndFactor(
ReplicationType.RATIS, ReplicationFactor.THREE));
if (getBucketLayout().isFileSystemOptimized()) {
builder.setParentObjectID(random.nextLong());
}
List<Pair<Long, OmKeyInfo>> keysWithHigherUpdateID = new ArrayList<>(1);
keysWithHigherUpdateID.add(Pair.of(clientID,
builder.setKeyName("key")
.setFileName("key")
.setUpdateID(updateId)
.build()));
List<Pair<Long, OmKeyInfo>> keysWithSameUpdateID = new ArrayList<>(1);
keysWithSameUpdateID.add(Pair.of(clientID,
builder.setKeyName("key2")
.setFileName("key2")
.setUpdateID(transactionId)
.build()));
List<Pair<Long, OmKeyInfo>> allKeys = new ArrayList<>(2);
allKeys.addAll(keysWithHigherUpdateID);
allKeys.addAll(keysWithSameUpdateID);
addToOpenKeyTableDB(allKeys);
OMRequest omRequest = doPreExecute(createDeleteOpenKeyRequest(allKeys));
OMOpenKeysDeleteRequest openKeyDeleteRequest =
new OMOpenKeysDeleteRequest(omRequest, getBucketLayout());
OMClientResponse omClientResponse =
openKeyDeleteRequest.validateAndUpdateCache(ozoneManager,
transactionId, ozoneManagerDoubleBufferHelper);
Assert.assertEquals(Status.OK,
omClientResponse.getOMResponse().getStatus());
assertInOpenKeyTable(keysWithHigherUpdateID);
assertNotInOpenKeyTable(keysWithSameUpdateID);
}
/**
* Tests metrics set by {@link OMOpenKeysDeleteRequest}.
* Submits a set of keys for deletion where only some of the keys actually
* exist in the open key table, and asserts that the metrics count keys
* that were submitted for deletion versus those that were actually deleted.
* @throws Exception
*/
@Test
public void testMetrics() throws Exception {
final String volume = UUID.randomUUID().toString();
final String bucket = UUID.randomUUID().toString();
final String key = UUID.randomUUID().toString();
final int numExistentKeys = 3;
final int numNonExistentKeys = 5;
OMRequestTestUtils.addVolumeAndBucketToDB(volume, bucket,
omMetadataManager, getBucketLayout());
OMMetrics metrics = ozoneManager.getMetrics();
Assert.assertEquals(metrics.getNumOpenKeyDeleteRequests(), 0);
Assert.assertEquals(metrics.getNumOpenKeyDeleteRequestFails(), 0);
Assert.assertEquals(metrics.getNumOpenKeysSubmittedForDeletion(), 0);
Assert.assertEquals(metrics.getNumOpenKeysDeleted(), 0);
List<Pair<Long, OmKeyInfo>> existentKeys =
makeOpenKeys(volume, bucket, key, numExistentKeys);
List<Pair<Long, OmKeyInfo>> nonExistentKeys =
makeOpenKeys(volume, bucket, key, numNonExistentKeys);
addToOpenKeyTableDB(existentKeys);
deleteOpenKeysFromCache(existentKeys, nonExistentKeys);
assertNotInOpenKeyTable(existentKeys);
assertNotInOpenKeyTable(nonExistentKeys);
Assert.assertEquals(1, metrics.getNumOpenKeyDeleteRequests());
Assert.assertEquals(0, metrics.getNumOpenKeyDeleteRequestFails());
Assert.assertEquals(numExistentKeys + numNonExistentKeys,
metrics.getNumOpenKeysSubmittedForDeletion());
Assert.assertEquals(numExistentKeys, metrics.getNumOpenKeysDeleted());
}
/**
* Runs the validate and update cache step of
* {@link OMOpenKeysDeleteRequest} to mark the keys in {@code openKeys}
* as deleted in the open key table cache.
* Asserts that the call's response status is {@link Status#OK}.
* @throws Exception
*/
private void deleteOpenKeysFromCache(List<Pair<Long, OmKeyInfo>>... allKeys)
throws Exception {
deleteOpenKeysFromCache(Arrays.stream(allKeys)
.flatMap(List::stream)
.collect(Collectors.toList()));
}
private void deleteOpenKeysFromCache(List<Pair<Long, OmKeyInfo>> openKeys)
throws Exception {
OMRequest omRequest =
doPreExecute(createDeleteOpenKeyRequest(openKeys));
OMOpenKeysDeleteRequest openKeyDeleteRequest =
new OMOpenKeysDeleteRequest(omRequest, getBucketLayout());
OMClientResponse omClientResponse =
openKeyDeleteRequest.validateAndUpdateCache(ozoneManager,
100L, ozoneManagerDoubleBufferHelper);
Assert.assertEquals(Status.OK,
omClientResponse.getOMResponse().getStatus());
}
/**
* Adds {@code openKeys} to the open key table DB only, and asserts that they
* are present after the addition.
* @throws Exception
*/
private void addToOpenKeyTableDB(List<Pair<Long, OmKeyInfo>>... allKeys)
throws Exception {
addToOpenKeyTableDB(0, Arrays.stream(allKeys)
.flatMap(List::stream)
.collect(Collectors.toList()));
}
private void addToOpenKeyTableDB(long keySize,
List<Pair<Long, OmKeyInfo>> openKeys) throws Exception {
for (Pair<Long, OmKeyInfo> openKey : openKeys) {
final long clientID = openKey.getLeft();
final OmKeyInfo omKeyInfo = openKey.getRight();
if (keySize > 0) {
OMRequestTestUtils.addKeyLocationInfo(omKeyInfo, 0, keySize);
}
if (getBucketLayout().isFileSystemOptimized()) {
OMRequestTestUtils.addFileToKeyTable(
true, false, omKeyInfo.getFileName(),
omKeyInfo, clientID, omKeyInfo.getUpdateID(), omMetadataManager);
} else {
OMRequestTestUtils.addKeyToTable(
true, false,
omKeyInfo, clientID, omKeyInfo.getUpdateID(), omMetadataManager);
}
}
assertInOpenKeyTable(openKeys);
}
/*
* Make open keys with randomized key name and client ID
*/
private List<Pair<Long, OmKeyInfo>> makeOpenKeys(
String volume, String bucket, int count) {
return makeOpenKeys(volume, bucket, null, count);
}
/*
* Make open keys with same key name and randomized client ID
*/
private List<Pair<Long, OmKeyInfo>> makeOpenKeys(
String volume, String bucket, String key, int count) {
List<Pair<Long, OmKeyInfo>> keys = new ArrayList<>(count);
OmKeyInfo.Builder builder = new OmKeyInfo.Builder()
.setVolumeName(volume)
.setBucketName(bucket)
.setReplicationConfig(ReplicationConfig.fromTypeAndFactor(
ReplicationType.RATIS, ReplicationFactor.THREE));
if (getBucketLayout().isFileSystemOptimized()) {
builder.setParentObjectID(random.nextLong());
}
for (int i = 0; i < count; i++) {
final String name = key != null ? key : UUID.randomUUID().toString();
builder.setKeyName(name);
if (getBucketLayout().isFileSystemOptimized()) {
builder.setFileName(OzoneFSUtils.getFileName(name));
}
long clientID = random.nextLong();
keys.add(Pair.of(clientID, builder.build()));
}
return keys;
}
private void assertInOpenKeyTable(List<Pair<Long, OmKeyInfo>>... allKeys)
throws Exception {
assertInOpenKeyTable(Arrays.stream(allKeys)
.flatMap(List::stream)
.collect(Collectors.toList()));
}
private void assertInOpenKeyTable(List<Pair<Long, OmKeyInfo>> openKeys)
throws Exception {
for (String keyName : getDBKeyNames(openKeys)) {
Assert.assertTrue(omMetadataManager.getOpenKeyTable(getBucketLayout())
.isExist(keyName));
}
}
private void assertNotInOpenKeyTable(List<Pair<Long, OmKeyInfo>>... allKeys)
throws Exception {
assertNotInOpenKeyTable(Arrays.stream(allKeys)
.flatMap(List::stream)
.collect(Collectors.toList()));
}
private void assertNotInOpenKeyTable(List<Pair<Long, OmKeyInfo>> openKeys)
throws Exception {
for (String keyName : getDBKeyNames(openKeys)) {
Assert.assertFalse(omMetadataManager.getOpenKeyTable(getBucketLayout())
.isExist(keyName));
}
}
private List<String> getDBKeyNames(List<Pair<Long, OmKeyInfo>> openKeys)
throws IOException {
final List<String> result = new ArrayList<>();
for (Pair<Long, OmKeyInfo> entry : openKeys) {
final OmKeyInfo ki = entry.getRight();
if (getBucketLayout().isFileSystemOptimized()) {
result.add(omMetadataManager.getOpenFileName(
omMetadataManager.getVolumeId(ki.getVolumeName()),
omMetadataManager.getBucketId(ki.getVolumeName(),
ki.getBucketName()),
ki.getParentObjectID(),
ki.getFileName(),
entry.getLeft()));
} else {
result.add(omMetadataManager.getOpenKey(
entry.getRight().getVolumeName(),
entry.getRight().getBucketName(),
entry.getRight().getKeyName(),
entry.getLeft()));
}
}
return result;
}
/**
* Constructs a new {@link OMOpenKeysDeleteRequest} objects, and calls its
* {@link OMOpenKeysDeleteRequest#preExecute} method with {@code
* originalOMRequest}. It verifies that {@code originalOMRequest} is modified
* after the call, and returns it.
* @throws Exception
*/
private OMRequest doPreExecute(OMRequest originalOmRequest) throws Exception {
OMOpenKeysDeleteRequest omOpenKeysDeleteRequest =
new OMOpenKeysDeleteRequest(originalOmRequest, getBucketLayout());
OMRequest modifiedOmRequest =
omOpenKeysDeleteRequest.preExecute(ozoneManager);
// Will not be equal, as UserInfo will be set.
Assert.assertNotEquals(originalOmRequest, modifiedOmRequest);
return modifiedOmRequest;
}
/**
* Creates an {@code OpenKeyDeleteRequest} to delete the keys represented by
* {@code keysToDelete}. Returns an {@code OMRequest} which encapsulates this
* {@code OpenKeyDeleteRequest}.
*/
private OMRequest createDeleteOpenKeyRequest(
List<Pair<Long, OmKeyInfo>> keysToDelete) throws IOException {
List<String> names = getDBKeyNames(keysToDelete);
// TODO: HDDS-6563, volume and bucket in OpenKeyBucket doesn't matter
List<OpenKeyBucket> openKeyBuckets = names.stream()
.map(name -> OpenKeyBucket.newBuilder()
.setVolumeName("")
.setBucketName("")
.addKeys(OpenKey.newBuilder().setName(name).build())
.build())
.collect(Collectors.toList());
DeleteOpenKeysRequest deleteOpenKeysRequest =
DeleteOpenKeysRequest.newBuilder()
.addAllOpenKeysPerBucket(openKeyBuckets)
.build();
return OMRequest.newBuilder()
.setDeleteOpenKeysRequest(deleteOpenKeysRequest)
.setCmdType(OzoneManagerProtocolProtos.Type.DeleteOpenKeys)
.setClientId(UUID.randomUUID().toString()).build();
}
}