Skip to content

Commit 11f3d72

Browse files
authored
HDDS-8118. Fail container delete on non empty chunks dir (apache#4367)
1 parent 354d3dc commit 11f3d72

9 files changed

Lines changed: 248 additions & 11 deletions

File tree

hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/helpers/ContainerMetrics.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ public class ContainerMetrics {
4949
public static final String STORAGE_CONTAINER_METRICS =
5050
"StorageContainerMetrics";
5151
@Metric private MutableCounterLong numOps;
52+
@Metric private MutableCounterLong containerDeleteFailedNonEmptyDir;
53+
@Metric private MutableCounterLong containerDeleteFailedBlockCountNotZero;
54+
@Metric private MutableCounterLong containerForceDelete;
55+
5256
private MutableCounterLong[] numOpsArray;
5357
private MutableCounterLong[] opsBytesArray;
5458
private MutableRate[] opsLatency;
@@ -125,4 +129,27 @@ public void incContainerBytesStats(ContainerProtos.Type type, long bytes) {
125129
public long getContainerBytesMetrics(ContainerProtos.Type type) {
126130
return opsBytesArray[type.ordinal()].value();
127131
}
132+
133+
public void incContainerDeleteFailedBlockCountNotZero() {
134+
containerDeleteFailedBlockCountNotZero.incr();
135+
}
136+
public void incContainerDeleteFailedNonEmpty() {
137+
containerDeleteFailedNonEmptyDir.incr();
138+
}
139+
140+
public void incContainersForceDelete() {
141+
containerForceDelete.incr();
142+
}
143+
144+
public long getContainerDeleteFailedNonEmptyDir() {
145+
return containerDeleteFailedNonEmptyDir.value();
146+
}
147+
148+
public long getContainerDeleteFailedBlockCountNotZero() {
149+
return containerDeleteFailedBlockCountNotZero.value();
150+
}
151+
152+
public long getContainerForceDelete() {
153+
return containerForceDelete.value();
154+
}
128155
}

hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/interfaces/Container.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ void create(VolumeSet volumeSet, VolumeChoosingPolicy volumeChoosingPolicy,
5757
*/
5858
void delete() throws StorageContainerException;
5959

60+
/**
61+
* Returns true if container is empty.
62+
* @return true of container is empty
63+
* @throws IOException if was unable to check container status.
64+
*/
65+
boolean isEmpty() throws IOException;
66+
6067
/**
6168
* Update the container.
6269
*

hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/DatanodeConfiguration.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,13 @@ public class DatanodeConfiguration {
8787
ROCKSDB_DELETE_OBSOLETE_FILES_PERIOD_MICRO_SECONDS_KEY =
8888
"hdds.datanode.rocksdb.delete_obsolete_files_period";
8989

90+
public static final String
91+
OZONE_DATANODE_CHECK_EMPTY_CONTAINER_ON_DISK_ON_DELETE =
92+
"hdds.datanode.check.empty.container.delete";
93+
public static final Boolean
94+
OZONE_DATANODE_CHECK_EMPTY_CONTAINER_ON_DISK_ON_DELETE_DEFAULT =
95+
true;
96+
9097
/**
9198
* Number of threads per volume that Datanode will use for chunk read.
9299
*/

hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/KeyValueContainer.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,11 @@ public void delete() throws StorageContainerException {
308308
}
309309
}
310310

311+
@Override
312+
public boolean isEmpty() throws IOException {
313+
return KeyValueContainerUtil.noBlocksInContainer(containerData);
314+
}
315+
311316
@Override
312317
public void markContainerForClose() throws StorageContainerException {
313318
writeLock();

hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/KeyValueHandler.java

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ public class KeyValueHandler extends Handler {
131131
private final long maxContainerSize;
132132
private final Function<ByteBuffer, ByteString> byteBufferToByteString;
133133
private final boolean validateChunkChecksumData;
134+
private boolean checkIfNoBlockFiles;
134135

135136
// A striped lock that is held during container creation.
136137
private final Striped<Lock> containerCreationLocks;
@@ -155,6 +156,14 @@ public KeyValueHandler(ConfigurationSource config,
155156
} catch (Exception e) {
156157
throw new RuntimeException(e);
157158
}
159+
160+
checkIfNoBlockFiles =
161+
conf.getBoolean(
162+
DatanodeConfiguration.
163+
OZONE_DATANODE_CHECK_EMPTY_CONTAINER_ON_DISK_ON_DELETE,
164+
DatanodeConfiguration.
165+
OZONE_DATANODE_CHECK_EMPTY_CONTAINER_ON_DISK_ON_DELETE_DEFAULT);
166+
158167
maxContainerSize = (long) config.getStorageSize(
159168
ScmConfigKeys.OZONE_SCM_CONTAINER_SIZE,
160169
ScmConfigKeys.OZONE_SCM_CONTAINER_SIZE_DEFAULT, StorageUnit.BYTES);
@@ -1231,19 +1240,45 @@ private void deleteInternal(Container container, boolean force)
12311240
}
12321241
// Safety check that the container is empty.
12331242
// If the container is not empty, it should not be deleted unless the
1234-
// container is beinf forcefully deleted (which happens when
1243+
// container is being forcefully deleted (which happens when
12351244
// container is unhealthy or over-replicated).
12361245
if (container.getContainerData().getBlockCount() != 0) {
1246+
metrics.incContainerDeleteFailedBlockCountNotZero();
12371247
LOG.error("Received container deletion command for container {} but" +
1238-
" the container is not empty.",
1239-
container.getContainerData().getContainerID());
1248+
" the container is not empty with blockCount {}",
1249+
container.getContainerData().getContainerID(),
1250+
container.getContainerData().getBlockCount());
12401251
throw new StorageContainerException("Non-force deletion of " +
12411252
"non-empty container is not allowed.",
12421253
DELETE_ON_NON_EMPTY_CONTAINER);
12431254
}
1255+
1256+
if (checkIfNoBlockFiles && !container.isEmpty()) {
1257+
metrics.incContainerDeleteFailedNonEmpty();
1258+
LOG.error("Received container deletion command for container {} but" +
1259+
" the container is not empty",
1260+
container.getContainerData().getContainerID());
1261+
throw new StorageContainerException("Non-force deletion of " +
1262+
"non-empty container:" +
1263+
container.getContainerData().getContainerID() +
1264+
" is not allowed.",
1265+
DELETE_ON_NON_EMPTY_CONTAINER);
1266+
}
1267+
} else {
1268+
metrics.incContainersForceDelete();
12441269
}
12451270
long containerId = container.getContainerData().getContainerID();
12461271
containerSet.removeContainer(containerId);
1272+
} catch (StorageContainerException e) {
1273+
throw e;
1274+
} catch (IOException e) {
1275+
// All other IO Exceptions should be treated as if the container is not
1276+
// empty as a defensive check.
1277+
LOG.error("Could not determine if the container {} is empty",
1278+
container.getContainerData().getContainerID(), e);
1279+
throw new StorageContainerException("Could not determine if container "
1280+
+ container.getContainerData().getContainerID() +
1281+
" is empty", DELETE_ON_NON_EMPTY_CONTAINER);
12471282
} finally {
12481283
container.writeUnlock();
12491284
}

hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/helpers/KeyValueContainerUtil.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import java.io.File;
2121
import java.io.IOException;
22+
import java.nio.file.DirectoryStream;
2223
import java.nio.file.Files;
2324
import java.nio.file.Path;
2425
import java.nio.file.Paths;
@@ -159,6 +160,26 @@ public static void removeContainer(KeyValueContainerData containerData,
159160
FileUtils.deleteDirectory(containerMetaDataPath.getParentFile());
160161
}
161162

163+
/**
164+
* Returns if there are no blocks in the container.
165+
* @param containerData Container to check
166+
* @param conf configuration
167+
* @return true if the directory containing blocks is empty
168+
* @throws IOException
169+
*/
170+
public static boolean noBlocksInContainer(KeyValueContainerData
171+
containerData)
172+
throws IOException {
173+
Preconditions.checkNotNull(containerData);
174+
File chunksPath = new File(containerData.getChunksPath());
175+
Preconditions.checkArgument(chunksPath.isDirectory());
176+
177+
try (DirectoryStream<Path> dir
178+
= Files.newDirectoryStream(chunksPath.toPath())) {
179+
return !dir.iterator().hasNext();
180+
}
181+
}
182+
162183
/**
163184
* Parse KeyValueContainerData and verify checksum. Set block related
164185
* metadata like block commit sequence id, block count, bytes used and

hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/OzoneContainer.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ public class OzoneContainer {
119119
private DatanodeDetails datanodeDetails;
120120
private StateContext context;
121121

122+
123+
private final ContainerMetrics metrics;
124+
122125
enum InitializingStatus {
123126
UNINITIALIZED, INITIALIZING, INITIALIZED
124127
}
@@ -162,7 +165,7 @@ public OzoneContainer(
162165
metadataScanner = null;
163166

164167
buildContainerSet();
165-
final ContainerMetrics metrics = ContainerMetrics.create(conf);
168+
metrics = ContainerMetrics.create(conf);
166169
handlers = Maps.newHashMap();
167170

168171
IncrementalReportSender<Container> icrSender = container -> {
@@ -521,4 +524,8 @@ public MutableVolumeSet getDbVolumeSet() {
521524
StorageVolumeChecker getVolumeChecker(ConfigurationSource conf) {
522525
return new StorageVolumeChecker(conf, new Timer());
523526
}
527+
528+
public ContainerMetrics getMetrics() {
529+
return metrics;
530+
}
524531
}

hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/keyvalue/TestKeyValueHandler.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -363,9 +363,7 @@ public void testDeleteContainer() throws IOException {
363363
Mockito.when(volumeSet.getVolumesList())
364364
.thenReturn(Collections.singletonList(hddsVolume));
365365

366-
final int[] interval = new int[1];
367-
interval[0] = 2;
368-
final ContainerMetrics metrics = new ContainerMetrics(interval);
366+
final ContainerMetrics metrics = ContainerMetrics.create(conf);
369367

370368
final AtomicInteger icrReceived = new AtomicInteger(0);
371369

0 commit comments

Comments
 (0)