kamalcph commented on code in PR #13837:
URL: https://github.com/apache/kafka/pull/13837#discussion_r1261590988


##########
storage/src/test/java/org/apache/kafka/server/log/remote/storage/LocalTieredStorage.java:
##########
@@ -0,0 +1,578 @@
+/*
+ * 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.kafka.server.log.remote.storage;
+
+import org.apache.kafka.common.TopicIdPartition;
+import org.apache.kafka.common.Uuid;
+import org.apache.kafka.common.errors.InvalidConfigurationException;
+import 
org.apache.kafka.server.log.remote.storage.LocalTieredStorageListener.LocalTieredStorageListeners;
+import org.apache.kafka.common.utils.LogContext;
+import org.apache.kafka.test.TestUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static java.lang.String.format;
+import static java.nio.file.Files.newInputStream;
+import static java.nio.file.StandardOpenOption.READ;
+import static 
org.apache.kafka.server.log.remote.storage.LocalTieredStorageEvent.EventType;
+import static 
org.apache.kafka.server.log.remote.storage.LocalTieredStorageEvent.EventType.DELETE_PARTITION;
+import static 
org.apache.kafka.server.log.remote.storage.LocalTieredStorageEvent.EventType.DELETE_SEGMENT;
+import static 
org.apache.kafka.server.log.remote.storage.LocalTieredStorageEvent.EventType.FETCH_LEADER_EPOCH_CHECKPOINT;
+import static 
org.apache.kafka.server.log.remote.storage.LocalTieredStorageEvent.EventType.FETCH_OFFSET_INDEX;
+import static 
org.apache.kafka.server.log.remote.storage.LocalTieredStorageEvent.EventType.FETCH_PRODUCER_SNAPSHOT;
+import static 
org.apache.kafka.server.log.remote.storage.LocalTieredStorageEvent.EventType.FETCH_SEGMENT;
+import static 
org.apache.kafka.server.log.remote.storage.LocalTieredStorageEvent.EventType.FETCH_TIME_INDEX;
+import static 
org.apache.kafka.server.log.remote.storage.LocalTieredStorageEvent.EventType.FETCH_TRANSACTION_INDEX;
+import static 
org.apache.kafka.server.log.remote.storage.LocalTieredStorageEvent.EventType.OFFLOAD_SEGMENT;
+import static 
org.apache.kafka.server.log.remote.storage.RemoteLogSegmentFileset.RemoteLogSegmentFileType.LEADER_EPOCH_CHECKPOINT;
+import static 
org.apache.kafka.server.log.remote.storage.RemoteLogSegmentFileset.RemoteLogSegmentFileType.OFFSET_INDEX;
+import static 
org.apache.kafka.server.log.remote.storage.RemoteLogSegmentFileset.RemoteLogSegmentFileType.PRODUCER_SNAPSHOT;
+import static 
org.apache.kafka.server.log.remote.storage.RemoteLogSegmentFileset.RemoteLogSegmentFileType.SEGMENT;
+import static 
org.apache.kafka.server.log.remote.storage.RemoteLogSegmentFileset.RemoteLogSegmentFileType.TIME_INDEX;
+import static 
org.apache.kafka.server.log.remote.storage.RemoteLogSegmentFileset.RemoteLogSegmentFileType.TRANSACTION_INDEX;
+import static 
org.apache.kafka.server.log.remote.storage.RemoteLogSegmentFileset.openFileset;
+import static 
org.apache.kafka.server.log.remote.storage.RemoteTopicPartitionDirectory.openExistingTopicPartitionDirectory;
+import static 
org.apache.kafka.server.log.remote.storage.RemoteTopicPartitionDirectory.openTopicPartitionDirectory;
+
+/**
+ * An implementation of {@link RemoteStorageManager} which relies on the local 
file system to store
+ * offloaded log segments and associated data.
+ * <p>
+ * Due to the consistency semantic of POSIX-compliant file systems, this 
remote storage provides strong
+ * read-after-write consistency and a segment's data can be accessed once the 
copy to the storage succeeded.
+ * </p>
+ * <p>
+ * In order to guarantee isolation, independence, reproducibility and 
consistency of unit and integration
+ * tests, the scope of a storage implemented by this class, and identified via 
the storage ID provided to
+ * the constructor, should be limited to a test or well-defined self-contained 
use-case.
+ * </p>
+ * <p>
+ * The local tiered storage keeps a simple structure of directories mimicking 
that of Apache Kafka.
+ * <p>
+ * The name of each of the files under the scope of a log segment (the log 
file, its indexes, etc.)
+ * follows the structure UuidBase64-FileType.
+ * <p>
+ * Given the root directory of the storage, segments and associated files are 
organized as represented below.
+ * </p>
+ * <code>
+ * / storage-directory  / LWgrMmVrT0a__7a4SasuPA-0-topic / 
bCqX9U--S-6U8XUM9II25Q.log
+ * .                                                     . 
bCqX9U--S-6U8XUM9II25Q.index
+ * .                                                     . 
bCqX9U--S-6U8XUM9II25Q.timeindex
+ * .                                                     . 
h956soEzTzi9a-NOQ-DvKA.log
+ * .                                                     . 
h956soEzTzi9a-NOQ-DvKA.index
+ * .                                                     . 
h956soEzTzi9a-NOQ-DvKA.timeindex
+ * .
+ * / LWgrMmVrT0a__7a4SasuPA-1-topic / o8CQPT86QQmbFmi3xRmiHA.log
+ * .                                . o8CQPT86QQmbFmi3xRmiHA.index
+ * .                                . o8CQPT86QQmbFmi3xRmiHA.timeindex
+ * .
+ * / DRagLm_PS9Wl8fz1X43zVg-3-btopic / jvj3vhliTGeU90sIosmp_g.log
+ * .                                 . jvj3vhliTGeU90sIosmp_g.index
+ * .                                 . jvj3vhliTGeU90sIosmp_g.timeindex
+ * </code>
+ */
+public final class LocalTieredStorage implements RemoteStorageManager {
+
+    public static final String STORAGE_CONFIG_PREFIX = 
"remote.log.storage.local.";
+
+    /**
+     * The root directory of this storage.
+     */
+    public static final String STORAGE_DIR_PROP = "dir";
+
+    /**
+     * Delete all files and directories from this storage on close, 
substantially removing it
+     * entirely from the file system.
+     */
+    public static final String DELETE_ON_CLOSE_PROP = "delete.on.close";
+
+    /**
+     * The implementation of the transfer of the data of the canonical segment 
and index files to
+     * this storage. The only reason the "transferer" abstraction exists is to 
be able to simulate
+     * file copy errors and exercise the associated failure modes.
+     */
+    public static final String TRANSFERER_CLASS_PROP = "transferer";
+
+    /**
+     * Whether the deleteLogSegment() implemented by this storage should 
actually delete data or behave
+     * as a no-operation. This allows to simulate non-strongly consistent 
storage systems which do not
+     * guarantee visibility of a successful delete for subsequent read or list 
operations.
+     */
+    public static final String ENABLE_DELETE_API_PROP = "delete.enable";
+
+    /**
+     * The ID of the broker which owns this instance of {@link 
LocalTieredStorage}.
+     */
+    public static final String BROKER_ID = "broker.id";
+
+    private static final String ROOT_STORAGES_DIR_NAME = 
"kafka-tiered-storage";
+
+    private volatile File storageDirectory;
+    private volatile boolean deleteOnClose = false;
+    private volatile boolean deleteEnabled = true;
+    private volatile Transferer transferer = new Transferer() {
+        @Override
+        public void transfer(File from, File to) throws IOException {
+            if (from.exists()) {
+                Files.copy(from.toPath(), to.toPath());
+            }
+        }
+
+        @Override
+        public void transfer(ByteBuffer from, File to) throws IOException {
+            if (from != null && from.hasRemaining()) {
+                try (FileOutputStream fileOutputStream = new 
FileOutputStream(to, false);
+                     FileChannel channel = fileOutputStream.getChannel()) {
+                    channel.write(from);
+                }
+            }
+        }
+    };
+
+    private volatile int brokerId = -1;
+
+    private volatile Logger logger = 
LoggerFactory.getLogger(LocalTieredStorage.class);
+
+    /**
+     * Used to explicit a chronological ordering of the events generated by 
the local tiered storage
+     * which this instance gives access to.
+     */
+    // TODO: Makes this timestamp only dependent on the assigned broker, not 
the class instance.
+    private final AtomicInteger eventTimestamp = new AtomicInteger(-1);
+
+    /**
+     * Used to notify users of this storage of internal updates - new 
topic-partition recorded (upon directory
+     * creation) and segment file written (upon segment file write).
+     */
+    private final LocalTieredStorageListeners storageListeners = new 
LocalTieredStorageListeners();
+
+    private final LocalTieredStorageHistory history = new 
LocalTieredStorageHistory();
+
+    public LocalTieredStorage() {
+        history.listenTo(this);
+    }
+
+    /**
+     * Walks through this storage and notify the traverser of every 
topic-partition, segment and record discovered.
+     * <p>
+     * - The order of traversal of the topic-partition is not specified.
+     * - The order of traversal of the segments within a topic-partition is in 
ascending order
+     * of the modified timestamp of the segment file.
+     * - The order of traversal of records within a segment corresponds to the 
insertion
+     * order of these records in the original segment from which the segment 
in this storage
+     * was transferred from.
+     * <p>
+     * This method is NOT an atomic operation w.r.t the local tiered storage. 
This storage may change while
+     * being traversed topic-partitions, segments and records are communicated 
to the traverser. There is
+     * no guarantee updates to the storage which happens during traversal will 
be communicated to the traverser.
+     * Especially, in case of concurrent read and write/delete to a 
topic-partition, a segment or a record,
+     * the behaviour depends on the underlying file system.
+     *
+     * @param traverser User-specified object to which this storage 
communicates the topic-partitions,
+     *                  segments and records as they are discovered.
+     */
+    public void traverse(final LocalTieredStorageTraverser traverser) {
+        Objects.requireNonNull(traverser);
+
+        final File[] files = storageDirectory.listFiles();
+        if (files == null) {
+            // files can be null if the directory is empty.
+            return;
+        }
+
+        Arrays.stream(files)

Review Comment:
   `Files.list` throws IOException and it needs to be wrapped with 
RuntimeException to avoid bubbling up the exception to the callers.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: jira-unsubscr...@kafka.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org

Reply via email to