Skip to content
Merged
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 @@ -50,6 +50,7 @@
import org.apache.hadoop.ozone.om.helpers.ErrorInfo;
import org.apache.hadoop.ozone.om.helpers.LeaseKeyInfo;
import org.apache.hadoop.ozone.om.helpers.OmKeyArgs;
import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfo;
import org.apache.hadoop.ozone.om.helpers.OmMultipartInfo;
import org.apache.hadoop.ozone.om.helpers.OmMultipartUploadCompleteInfo;
Expand Down Expand Up @@ -436,6 +437,17 @@ OzoneDataStreamOutput createStreamKey(String volumeName, String bucketName,
OzoneInputStream getKey(String volumeName, String bucketName, String keyName)
throws IOException;

/**
* Reads key info from an existing bucket.
* @param volumeName Name of the Volume
* @param bucketName Name of the Bucket
* @param keyName Name of the Key
* @param forceUpdateContainerCache if true force OM to update container cache location from SCM
* @return {@link OmKeyInfo}
* @throws IOException
*/
OmKeyInfo getKeyInfo(String volumeName, String bucketName, String keyName,
boolean forceUpdateContainerCache) throws IOException;

/**
* Deletes an existing key.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1854,7 +1854,8 @@ private OmKeyInfo getS3PartKeyInfo(
return keyInfoWithS3Context.getKeyInfo();
}

private OmKeyInfo getKeyInfo(
@Override
public OmKeyInfo getKeyInfo(
String volumeName, String bucketName, String keyName,
boolean forceUpdateContainerCache) throws IOException {
Preconditions.checkNotNull(volumeName);
Expand Down
51 changes: 26 additions & 25 deletions hadoop-ozone/dist/src/main/compose/common/replicas-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,31 @@ key="testfile"

execute_robot_test ${SCM} -v "PREFIX:${prefix}" debug/ozone-debug-tests.robot

#TODO HDDS-12890: Add acceptance robot tests for ozone debug replicas verify
# get block locations for key
chunkinfo="${key}-blocks-${prefix}"
docker-compose exec -T ${SCM} bash -c "ozone debug replicas chunk-info ${volume}/${bucket}/${key}" > "$chunkinfo"
host="$(jq -r '.KeyLocations[0][0]["Datanode-HostName"]' ${chunkinfo})"
container="${host%%.*}"

# corrupt the first block of key on one of the datanodes
datafile="$(jq -r '.KeyLocations[0][0].Locations.files[0]' ${chunkinfo})"
docker exec "${container}" sed -i -e '1s/^/a/' "${datafile}"

execute_robot_test ${SCM} -v "PREFIX:${prefix}" -v "CORRUPT_DATANODE:${host}" debug/ozone-debug-corrupt-block.robot

docker stop "${container}"

wait_for_datanode "${container}" STALE 60
execute_robot_test ${SCM} -v "PREFIX:${prefix}" -v "STALE_DATANODE:${host}" debug/ozone-debug-stale-datanode.robot

wait_for_datanode "${container}" DEAD 60
execute_robot_test ${SCM} -v "PREFIX:${prefix}" debug/ozone-debug-dead-datanode.robot

docker start "${container}"

wait_for_datanode "${container}" HEALTHY 60
#chunkinfo="${key}-blocks-${prefix}"
#docker-compose exec -T ${SCM} bash -c "ozone debug replicas chunk-info ${volume}/${bucket}/${key}" > "$chunkinfo"
#host="$(jq -r '.KeyLocations[0][0]["Datanode-HostName"]' ${chunkinfo})"
#container="${host%%.*}"
#
## corrupt the first block of key on one of the datanodes
#datafile="$(jq -r '.KeyLocations[0][0].Locations.files[0]' ${chunkinfo})"
#docker exec "${container}" sed -i -e '1s/^/a/' "${datafile}"
#
#execute_robot_test ${SCM} -v "PREFIX:${prefix}" -v "CORRUPT_DATANODE:${host}" debug/ozone-debug-corrupt-block.robot
#
#docker stop "${container}"
#
#wait_for_datanode "${container}" STALE 60
#execute_robot_test ${SCM} -v "PREFIX:${prefix}" -v "STALE_DATANODE:${host}" debug/ozone-debug-stale-datanode.robot
#
#wait_for_datanode "${container}" DEAD 60
#execute_robot_test ${SCM} -v "PREFIX:${prefix}" debug/ozone-debug-dead-datanode.robot
#
#docker start "${container}"
#
#wait_for_datanode "${container}" HEALTHY 60

start_docker_env 9
execute_robot_test ${SCM} -v "PREFIX:${prefix}" debug/ozone-debug-tests-ec3-2.robot
execute_robot_test ${SCM} -v "PREFIX:${prefix}" debug/ozone-debug-tests-ec6-3.robot
#start_docker_env 9
#execute_robot_test ${SCM} -v "PREFIX:${prefix}" debug/ozone-debug-tests-ec3-2.robot
#execute_robot_test ${SCM} -v "PREFIX:${prefix}" debug/ozone-debug-tests-ec6-3.robot
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ Write keys
Execute ozone sh key put o3://om/${VOLUME}/${BUCKET}/${TESTFILE} ${TEMP_DIR}/${TESTFILE}

*** Test Cases ***
Test ozone debug read-replicas
${directory} = Execute replicas verify checksums CLI tool
Set Test Variable ${DIR} ${directory}

${count_files} = Count Files In Directory ${directory}
Should Be Equal As Integers ${count_files} 1
Test ozone debug replicas verify checksums
${output} = Execute ozone debug replicas verify --checksums o3://om/${VOLUME}/${BUCKET}/${TESTFILE} --output-dir ${TEMP_DIR}
${json} = Evaluate json.loads('''${output}''') json

# 'keys' array should be empty if all keys and their replicas passed checksum verification
Should Be Empty ${json}[keys]
Should Be True ${json}[pass] ${True}

Test ozone debug version
${output} = Execute ozone debug version
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Test Timeout 5 minutes

*** Variables ***
${SCHEME} ofs
${volume} volume1
${volume} obs-volume1
${bucket} obs-bucket1
${PREFIX} ozone

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,29 @@ void shutdown() {
IOUtils.closeQuietly(client);
}

@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testReplicasVerifyCmd(boolean isEcKey) throws Exception {
final String volumeName = UUID.randomUUID().toString();
final String bucketName = UUID.randomUUID().toString();
final String keyName = UUID.randomUUID().toString();

writeKey(volumeName, bucketName, keyName, isEcKey, BucketLayout.FILE_SYSTEM_OPTIMIZED);

String bucketPath = Path.SEPARATOR + volumeName + Path.SEPARATOR + bucketName;
String fullKeyPath = bucketPath + Path.SEPARATOR + keyName;

//TODO HDDS-12715: Create common integration test cluster for debug and repair tools
String[] args = new String[] {
getSetConfStringFromConf(OMConfigKeys.OZONE_OM_ADDRESS_KEY),
getSetConfStringFromConf(ScmConfigKeys.OZONE_SCM_CLIENT_ADDRESS_KEY),
"replicas", "verify", "--checksums", "--block-existence", fullKeyPath, "--output-dir", "/"//, "--all-results"
};

int exitCode = ozoneDebugShell.execute(args);
assertEquals(0, exitCode);
}

@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testChunkInfoCmdBeforeAfterCloseContainer(boolean isEcKey) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.apache.hadoop.ozone.om.helpers.ErrorInfo;
import org.apache.hadoop.ozone.om.helpers.LeaseKeyInfo;
import org.apache.hadoop.ozone.om.helpers.OmKeyArgs;
import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfo;
import org.apache.hadoop.ozone.om.helpers.OmMultipartInfo;
import org.apache.hadoop.ozone.om.helpers.OmMultipartUploadCompleteInfo;
Expand Down Expand Up @@ -254,6 +255,13 @@ public OzoneInputStream getKey(String volumeName, String bucketName,
return getBucket(volumeName, bucketName).readKey(keyName);
}

@Override
public OmKeyInfo getKeyInfo(String volumeName, String bucketName, String keyName,
boolean forceUpdateContainerCache) throws IOException {
return objectStoreStub.getClientProxy().getKeyInfo(
volumeName, bucketName, keyName, forceUpdateContainerCache);
}

private OzoneBucket getBucket(String volumeName, String bucketName)
throws IOException {
return objectStoreStub.getVolume(volumeName).getBucket(bucketName);
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
*
* 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.debug.replicas;

import static org.apache.hadoop.hdds.protocol.proto.HddsProtos.ReplicationFactor.ONE;

import java.io.IOException;
import java.util.Collections;
import org.apache.hadoop.hdds.client.StandaloneReplicationConfig;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.protocol.DatanodeDetails;
import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos;
import org.apache.hadoop.hdds.scm.XceiverClientManager;
import org.apache.hadoop.hdds.scm.XceiverClientSpi;
import org.apache.hadoop.hdds.scm.cli.ContainerOperationClient;
import org.apache.hadoop.hdds.scm.pipeline.Pipeline;
import org.apache.hadoop.hdds.scm.storage.ContainerProtocolCalls;
import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfo;

/**
* Verifies block existence by making getBlock calls to the datanode.
*/
public class BlockExistenceVerifier implements ReplicaVerifier {
private final ContainerOperationClient containerClient;
private final XceiverClientManager xceiverClientManager;
private static final String CHECK_TYPE = "blockExistence";

@Override
public String getType() {
return CHECK_TYPE;
}

public BlockExistenceVerifier(OzoneConfiguration conf) throws IOException {
this.containerClient = new ContainerOperationClient(conf);
this.xceiverClientManager = containerClient.getXceiverClientManager();
}

@Override
public BlockVerificationResult verifyBlock(DatanodeDetails datanode, OmKeyLocationInfo keyLocation,
int replicaIndex) {
XceiverClientSpi client = null;
try {
Pipeline pipeline = Pipeline.newBuilder(keyLocation.getPipeline())
.setReplicationConfig(StandaloneReplicationConfig.getInstance(ONE))
.setNodes(Collections.singletonList(datanode))
.setReplicaIndexes(Collections.singletonMap(datanode, replicaIndex))
.build();

client = xceiverClientManager.acquireClientForReadData(pipeline);
ContainerProtos.GetBlockResponseProto response = ContainerProtocolCalls.getBlock(
client,
keyLocation.getBlockID(),
keyLocation.getToken(),
pipeline.getReplicaIndexes()
);

boolean hasBlock = response != null && response.hasBlockData();

if (hasBlock) {
return BlockVerificationResult.pass();
} else {
return BlockVerificationResult.failCheck("Block does not exist on this replica");
}
} catch (IOException e) {
return BlockVerificationResult.failIncomplete(e.getMessage());
} finally {
xceiverClientManager.releaseClient(client, false);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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.debug.replicas;

import java.util.Collections;
import java.util.List;

/**
* Json structure for replicas to pass through each check and give output.
*/
public class BlockVerificationResult {

private final boolean completed;
private final boolean pass;
private final List<String> failures;

public BlockVerificationResult(boolean completed, boolean pass, List<String> failures) {
this.completed = completed;
this.pass = pass;
this.failures = failures;
}

public static BlockVerificationResult pass() {
return new BlockVerificationResult(true, true, Collections.emptyList());
}

public static BlockVerificationResult failCheck(String message) {
return new BlockVerificationResult(true, false, Collections.singletonList(message));
}

public static BlockVerificationResult failIncomplete(String message) {
return new BlockVerificationResult(false, false, Collections.singletonList(message));
}

public boolean isCompleted() {
return completed;
}

public boolean passed() {
return pass;
}

public List<String> getFailures() {
return failures;
}

}
Loading