diff --git a/build.gradle b/build.gradle index 05f77e054f04a..f79d37b91b5e2 100644 --- a/build.gradle +++ b/build.gradle @@ -1226,6 +1226,7 @@ project(':clients') { include "**/org/apache/kafka/server/authorizer/*" include "**/org/apache/kafka/server/policy/*" include "**/org/apache/kafka/server/quota/*" + include "**/org/apache/kafka/server/log/remote/storage/*" } } diff --git a/clients/src/main/java/org/apache/kafka/common/TopicIdPartition.java b/clients/src/main/java/org/apache/kafka/common/TopicIdPartition.java new file mode 100644 index 0000000000000..9fb570a67f438 --- /dev/null +++ b/clients/src/main/java/org/apache/kafka/common/TopicIdPartition.java @@ -0,0 +1,74 @@ +/* + * 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.common; + +import java.util.Objects; + +/** + * This represents universally unique identifier with topic id for a topic partition. This makes sure that topics + * recreated with the same name will always have unique topic identifiers. + */ +public class TopicIdPartition { + + private final Uuid topicId; + private final TopicPartition topicPartition; + + public TopicIdPartition(Uuid topicId, TopicPartition topicPartition) { + this.topicId = Objects.requireNonNull(topicId, "topicId can not be null"); + this.topicPartition = Objects.requireNonNull(topicPartition, "topicPartition can not be null"); + } + + /** + * @return Universally unique id representing this topic partition. + */ + public Uuid topicId() { + return topicId; + } + + /** + * @return Topic partition representing this instance. + */ + public TopicPartition topicPartition() { + return topicPartition; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TopicIdPartition that = (TopicIdPartition) o; + return Objects.equals(topicId, that.topicId) && + Objects.equals(topicPartition, that.topicPartition); + } + + @Override + public int hashCode() { + return Objects.hash(topicId, topicPartition); + } + + @Override + public String toString() { + return "TopicIdPartition{" + + "topicId=" + topicId + + ", topicPartition=" + topicPartition + + '}'; + } +} diff --git a/clients/src/main/java/org/apache/kafka/server/log/remote/storage/LogSegmentData.java b/clients/src/main/java/org/apache/kafka/server/log/remote/storage/LogSegmentData.java new file mode 100644 index 0000000000000..662e396352142 --- /dev/null +++ b/clients/src/main/java/org/apache/kafka/server/log/remote/storage/LogSegmentData.java @@ -0,0 +1,139 @@ +/* + * 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.annotation.InterfaceStability; + +import java.io.File; +import java.nio.ByteBuffer; +import java.util.Objects; + +/** + * This represents all the required data and indexes for a specific log segment that needs to be stored in the remote + * storage. This is passed with {@link RemoteStorageManager#copyLogSegmentData(RemoteLogSegmentMetadata, LogSegmentData)} + * while copying a specific log segment to the remote storage. + */ +@InterfaceStability.Evolving +public class LogSegmentData { + + private final File logSegment; + private final File offsetIndex; + private final File timeIndex; + private final File txnIndex; + private final File producerSnapshotIndex; + private final ByteBuffer leaderEpochIndex; + + /** + * Creates a LogSegmentData instance with data and indexes. + * + * @param logSegment actual log segment file + * @param offsetIndex offset index file + * @param timeIndex time index file + * @param txnIndex transaction index file + * @param producerSnapshotIndex producer snapshot until this segment + * @param leaderEpochIndex leader-epoch-index until this segment + */ + public LogSegmentData(File logSegment, + File offsetIndex, + File timeIndex, + File txnIndex, + File producerSnapshotIndex, + ByteBuffer leaderEpochIndex) { + this.logSegment = Objects.requireNonNull(logSegment, "logSegment can not be null"); + this.offsetIndex = Objects.requireNonNull(offsetIndex, "offsetIndex can not be null"); + this.timeIndex = Objects.requireNonNull(timeIndex, "timeIndex can not be null"); + this.txnIndex = Objects.requireNonNull(txnIndex, "txnIndex can not be null"); + this.producerSnapshotIndex = Objects.requireNonNull(producerSnapshotIndex, "producerSnapshotIndex can not be null"); + this.leaderEpochIndex = Objects.requireNonNull(leaderEpochIndex, "leaderEpochIndex can not be null"); + } + + /** + * @return Log segment file of this segment. + */ + public File logSegment() { + return logSegment; + } + + /** + * @return Offset index file. + */ + public File offsetIndex() { + return offsetIndex; + } + + /** + * @return Time index file of this segment. + */ + public File timeIndex() { + return timeIndex; + } + + /** + * @return Transaction index file of this segment. + */ + public File txnIndex() { + return txnIndex; + } + + /** + * @return Producer snapshot file until this segment. + */ + public File producerSnapshotIndex() { + return producerSnapshotIndex; + } + + /** + * @return Leader epoch index until this segment. + */ + public ByteBuffer leaderEpochIndex() { + return leaderEpochIndex; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + LogSegmentData that = (LogSegmentData) o; + return Objects.equals(logSegment, that.logSegment) && Objects + .equals(offsetIndex, that.offsetIndex) && Objects + .equals(timeIndex, that.timeIndex) && Objects + .equals(txnIndex, that.txnIndex) && Objects + .equals(producerSnapshotIndex, that.producerSnapshotIndex) && Objects + .equals(leaderEpochIndex, that.leaderEpochIndex); + } + + @Override + public int hashCode() { + return Objects.hash(logSegment, offsetIndex, timeIndex, txnIndex, producerSnapshotIndex, leaderEpochIndex); + } + + @Override + public String toString() { + return "LogSegmentData{" + + "logSegment=" + logSegment + + ", offsetIndex=" + offsetIndex + + ", timeIndex=" + timeIndex + + ", txnIndex=" + txnIndex + + ", producerSnapshotIndex=" + producerSnapshotIndex + + ", leaderEpochIndex=" + leaderEpochIndex + + '}'; + } +} \ No newline at end of file diff --git a/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteLogMetadataManager.java b/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteLogMetadataManager.java new file mode 100644 index 0000000000000..f53858d7a4540 --- /dev/null +++ b/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteLogMetadataManager.java @@ -0,0 +1,200 @@ +/* + * 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.Configurable; +import org.apache.kafka.common.TopicIdPartition; +import org.apache.kafka.common.annotation.InterfaceStability; + +import java.io.Closeable; +import java.util.Iterator; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +/** + * This interface provides storing and fetching remote log segment metadata with strongly consistent semantics. + *

+ * This class can be plugged in to Kafka cluster by adding the implementation class as + * remote.log.metadata.manager.class.name property value. There is an inbuilt implementation backed by + * topic storage in the local cluster. This is used as the default implementation if + * remote.log.metadata.manager.class.name is not configured. + *

+ *

+ * remote.log.metadata.manager.class.path property is about the class path of the RemoteLogStorageManager + * implementation. If specified, the RemoteLogStorageManager implementation and its dependent libraries will be loaded + * by a dedicated classloader which searches this class path before the Kafka broker class path. The syntax of this + * parameter is same with the standard Java class path string. + *

+ *

+ * remote.log.metadata.manager.listener.name property is about listener name of the local broker to which + * it should get connected if needed by RemoteLogMetadataManager implementation. When this is configured all other + * required properties can be passed as properties with prefix of 'remote.log.metadata.manager.listener. + *

+ * "cluster.id", "broker.id" and all other properties prefixed with "remote.log.metadata." are passed when + * {@link #configure(Map)} is invoked on this instance. + *

+ */ +@InterfaceStability.Evolving +public interface RemoteLogMetadataManager extends Configurable, Closeable { + + /** + * Adds {@link RemoteLogSegmentMetadata} with the containing {@link RemoteLogSegmentId} into {@link RemoteLogMetadataManager}. + *

+ * RemoteLogSegmentMetadata is identified by RemoteLogSegmentId and it should have the initial state which is {@link RemoteLogSegmentState#COPY_SEGMENT_STARTED}. + *

+ * {@link #updateRemoteLogSegmentMetadata(RemoteLogSegmentMetadataUpdate)} should be used to update an existing RemoteLogSegmentMetadata. + * + * @param remoteLogSegmentMetadata metadata about the remote log segment. + * @throws RemoteStorageException if there are any storage related errors occurred. + * @throws IllegalArgumentException if the given metadata instance does not have the state as {@link RemoteLogSegmentState#COPY_SEGMENT_STARTED} + */ + void addRemoteLogSegmentMetadata(RemoteLogSegmentMetadata remoteLogSegmentMetadata) throws RemoteStorageException; + + /** + * This method is used to update the {@link RemoteLogSegmentMetadata}. Currently, it allows to update with the new + * state based on the life cycle of the segment. It can go through the below state transitions. + *

+ *

+     * +---------------------+            +----------------------+
+     * |COPY_SEGMENT_STARTED |----------->|COPY_SEGMENT_FINISHED |
+     * +-------------------+-+            +--+-------------------+
+     *                     |                 |
+     *                     |                 |
+     *                     v                 v
+     *                  +--+-----------------+-+
+     *                  |DELETE_SEGMENT_STARTED|
+     *                  +-----------+----------+
+     *                              |
+     *                              |
+     *                              v
+     *                  +-----------+-----------+
+     *                  |DELETE_SEGMENT_FINISHED|
+     *                  +-----------------------+
+     * 
+ *

+ * {@link RemoteLogSegmentState#COPY_SEGMENT_STARTED} - This state indicates that the segment copying to remote storage is started but not yet finished. + * {@link RemoteLogSegmentState#COPY_SEGMENT_FINISHED} - This state indicates that the segment copying to remote storage is finished. + *
+ * The leader broker copies the log segments to the remote storage and puts the remote log segment metadata with the + * state as “COPY_SEGMENT_STARTED” and updates the state as “COPY_SEGMENT_FINISHED” once the copy is successful. + *

+ * {@link RemoteLogSegmentState#DELETE_SEGMENT_STARTED} - This state indicates that the segment deletion is started but not yet finished. + * {@link RemoteLogSegmentState#DELETE_SEGMENT_FINISHED} - This state indicates that the segment is deleted successfully. + *
+ * Leader partitions publish both the above delete segment events when remote log retention is reached for the + * respective segments. Remote Partition Removers also publish these events when a segment is deleted as part of + * the remote partition deletion. + * + * @param remoteLogSegmentMetadataUpdate update of the remote log segment metadata. + * @throws RemoteStorageException if there are any storage related errors occurred. + * @throws RemoteResourceNotFoundException when there are no resources associated with the given remoteLogSegmentMetadataUpdate. + * @throws IllegalArgumentException if the given metadata instance has the state as {@link RemoteLogSegmentState#COPY_SEGMENT_STARTED} + */ + void updateRemoteLogSegmentMetadata(RemoteLogSegmentMetadataUpdate remoteLogSegmentMetadataUpdate) + throws RemoteStorageException; + + /** + * Returns {@link RemoteLogSegmentMetadata} if it exists for the given topic partition containing the offset with + * the given leader-epoch for the offset, else returns {@link Optional#empty()}. + * + * @param topicIdPartition topic partition + * @param offset offset + * @param epochForOffset leader epoch for the given offset + * @return the requested remote log segment metadata if it exists. + * @throws RemoteStorageException if there are any storage related errors occurred. + */ + Optional remoteLogSegmentMetadata(TopicIdPartition topicIdPartition, + long offset, + int epochForOffset) + throws RemoteStorageException; + + /** + * Returns the highest log offset of topic partition for the given leader epoch in remote storage. This is used by + * remote log management subsystem to know up to which offset the segments have been copied to remote storage for + * a given leader epoch. + * + * @param topicIdPartition topic partition + * @param leaderEpoch leader epoch + * @return the requested highest log offset if exists. + * @throws RemoteStorageException if there are any storage related errors occurred. + */ + Optional highestLogOffset(TopicIdPartition topicIdPartition, + int leaderEpoch) throws RemoteStorageException; + + /** + * This method is used to update the metadata about remote partition delete event. Currently, it allows updating the + * state ({@link RemotePartitionDeleteState}) of a topic partition in remote metadata storage. Controller invokes + * this method with {@link RemotePartitionDeleteMetadata} having state as {@link RemotePartitionDeleteState#DELETE_PARTITION_MARKED}. + * So, remote partition removers can act on this event to clean the respective remote log segments of the partition. + *


+ * In the case of default RLMM implementation, remote partition remover processes {@link RemotePartitionDeleteState#DELETE_PARTITION_MARKED} + *

+ * + * @param remotePartitionDeleteMetadata update on delete state of a partition. + * @throws RemoteStorageException if there are any storage related errors occurred. + * @throws RemoteResourceNotFoundException when there are no resources associated with the given remotePartitionDeleteMetadata. + */ + void putRemotePartitionDeleteMetadata(RemotePartitionDeleteMetadata remotePartitionDeleteMetadata) + throws RemoteStorageException; + + /** + * List all the remote log segment metadata of the given topicIdPartition. + *

+ * Remote Partition Removers uses this method to fetch all the segments for a given topic partition, so that they + * can delete them. + * + * @return Iterator of remote log segment metadata for the given topic partition. + */ + Iterator listRemoteLogSegments(TopicIdPartition topicIdPartition) + throws RemoteStorageException; + + /** + * Returns iterator of remote log segment metadata, sorted by {@link RemoteLogSegmentMetadata#startOffset()} in + * ascending order which contains the given leader epoch. This is used by remote log retention management subsystem + * to fetch the segment metadata for a given leader epoch. + * + * @param topicIdPartition topic partition + * @param leaderEpoch leader epoch + * @return Iterator of remote segments, sorted by start offset in ascending order. + */ + Iterator listRemoteLogSegments(TopicIdPartition topicIdPartition, + int leaderEpoch) throws RemoteStorageException; + + /** + * This method is invoked only when there are changes in leadership of the topic partitions that this broker is + * responsible for. + * + * @param leaderPartitions partitions that have become leaders on this broker. + * @param followerPartitions partitions that have become followers on this broker. + */ + void onPartitionLeadershipChanges(Set leaderPartitions, + Set followerPartitions); + + /** + * This method is invoked only when the topic partitions are stopped on this broker. This can happen when a + * partition is emigrated to other broker or a partition is deleted. + * + * @param partitions topic partitions that have been stopped. + */ + void onStopPartitions(Set partitions); +} \ No newline at end of file diff --git a/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteLogSegmentId.java b/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteLogSegmentId.java new file mode 100644 index 0000000000000..cbebd9f606d32 --- /dev/null +++ b/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteLogSegmentId.java @@ -0,0 +1,80 @@ +/* + * 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.annotation.InterfaceStability; + +import java.util.Objects; + +/** + * This class represents a universally unique identifier associated to a topic partition's log segment. This will be + * regenerated for every attempt of copying a specific log segment in {@link RemoteStorageManager#copyLogSegmentData(RemoteLogSegmentMetadata, LogSegmentData)}. + * Once it is stored in remote storage, it is used to access that segment later from remote log metadata storage. + */ +@InterfaceStability.Evolving +public class RemoteLogSegmentId { + + private final TopicIdPartition topicIdPartition; + private final Uuid id; + + public RemoteLogSegmentId(TopicIdPartition topicIdPartition, Uuid id) { + this.topicIdPartition = Objects.requireNonNull(topicIdPartition, "topicIdPartition can not be null"); + this.id = Objects.requireNonNull(id, "id can not be null"); + } + + /** + * @return TopicIdPartition of this remote log segment. + */ + public TopicIdPartition topicIdPartition() { + return topicIdPartition; + } + + /** + * @return Universally Unique Id of this remote log segment. + */ + public Uuid id() { + return id; + } + + @Override + public String toString() { + return "RemoteLogSegmentId{" + + "topicIdPartition=" + topicIdPartition + + ", id=" + id + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RemoteLogSegmentId that = (RemoteLogSegmentId) o; + return Objects.equals(topicIdPartition, that.topicIdPartition) && Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(topicIdPartition, id); + } + +} diff --git a/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteLogSegmentMetadata.java b/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteLogSegmentMetadata.java new file mode 100644 index 0000000000000..9f0dd817be849 --- /dev/null +++ b/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteLogSegmentMetadata.java @@ -0,0 +1,282 @@ +/* + * 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.annotation.InterfaceStability; + +import java.util.Collections; +import java.util.Map; +import java.util.NavigableMap; +import java.util.Objects; +import java.util.TreeMap; + +/** + * It describes the metadata about a topic partition's remote log segment in the remote storage. This is uniquely + * represented with {@link RemoteLogSegmentId}. + *

+ * New instance is always created with the state as {@link RemoteLogSegmentState#COPY_SEGMENT_STARTED}. This can be + * updated by applying {@link RemoteLogSegmentMetadataUpdate} for the respective {@link RemoteLogSegmentId} of the + * {@code RemoteLogSegmentMetadata}. + */ +@InterfaceStability.Evolving +public class RemoteLogSegmentMetadata { + + /** + * Universally unique remote log segment id. + */ + private final RemoteLogSegmentId remoteLogSegmentId; + + /** + * Start offset of this segment. + */ + private final long startOffset; + + /** + * End offset of this segment. + */ + private final long endOffset; + + /** + * Broker id from which this event is generated. + */ + private final int brokerId; + + /** + * Maximum timestamp in the segment + */ + private final long maxTimestamp; + + /** + * Epoch time at which the respective {@link #state} is set. + */ + private final long eventTimestamp; + + /** + * LeaderEpoch vs offset for messages within this segment. + */ + private final NavigableMap segmentLeaderEpochs; + + /** + * Size of the segment in bytes. + */ + private final int segmentSizeInBytes; + + /** + * It indicates the state in which the action is executed on this segment. + */ + private final RemoteLogSegmentState state; + + /** + * Creates an instance with the given metadata of remote log segment. + * + * {@code segmentLeaderEpochs} can not be empty. If all the records in this segment belong to the same leader epoch + * then it should have an entry with epoch mapping to start-offset of this segment. + * + * @param remoteLogSegmentId Universally unique remote log segment id. + * @param startOffset Start offset of this segment (inclusive). + * @param endOffset End offset of this segment (inclusive). + * @param maxTimestamp Maximum timestamp in this segment. + * @param brokerId Broker id from which this event is generated. + * @param eventTimestamp Epoch time in milli seconds at which the remote log segment is copied to the remote tier storage. + * @param segmentSizeInBytes Size of this segment in bytes. + * @param state State of the respective segment of remoteLogSegmentId. + * @param segmentLeaderEpochs leader epochs occurred within this segment. + */ + private RemoteLogSegmentMetadata(RemoteLogSegmentId remoteLogSegmentId, + long startOffset, + long endOffset, + long maxTimestamp, + int brokerId, + long eventTimestamp, + int segmentSizeInBytes, + RemoteLogSegmentState state, + Map segmentLeaderEpochs) { + this.remoteLogSegmentId = Objects.requireNonNull(remoteLogSegmentId, "remoteLogSegmentId can not be null"); + this.state = Objects.requireNonNull(state, "state can not be null"); + + this.startOffset = startOffset; + this.endOffset = endOffset; + this.brokerId = brokerId; + this.maxTimestamp = maxTimestamp; + this.eventTimestamp = eventTimestamp; + this.segmentSizeInBytes = segmentSizeInBytes; + + if (segmentLeaderEpochs == null || segmentLeaderEpochs.isEmpty()) { + throw new IllegalArgumentException("segmentLeaderEpochs can not be null or empty"); + } + + this.segmentLeaderEpochs = Collections.unmodifiableNavigableMap(new TreeMap<>(segmentLeaderEpochs)); + } + + /** + * Creates an instance with the given metadata of remote log segment and its state as {@link RemoteLogSegmentState#COPY_SEGMENT_STARTED}. + * + * {@code segmentLeaderEpochs} can not be empty. If all the records in this segment belong to the same leader epoch + * then it should have an entry with epoch mapping to start-offset of this segment. + * + * @param remoteLogSegmentId Universally unique remote log segment id. + * @param startOffset Start offset of this segment (inclusive). + * @param endOffset End offset of this segment (inclusive). + * @param maxTimestamp Maximum timestamp in this segment + * @param brokerId Broker id from which this event is generated. + * @param eventTimestamp Epoch time in milli seconds at which the remote log segment is copied to the remote tier storage. + * @param segmentSizeInBytes Size of this segment in bytes. + * @param segmentLeaderEpochs leader epochs occurred within this segment + */ + public RemoteLogSegmentMetadata(RemoteLogSegmentId remoteLogSegmentId, + long startOffset, + long endOffset, + long maxTimestamp, + int brokerId, + long eventTimestamp, + int segmentSizeInBytes, + Map segmentLeaderEpochs) { + this(remoteLogSegmentId, + startOffset, + endOffset, + maxTimestamp, + brokerId, + eventTimestamp, segmentSizeInBytes, + RemoteLogSegmentState.COPY_SEGMENT_STARTED, + segmentLeaderEpochs); + } + + + /** + * @return unique id of this segment. + */ + public RemoteLogSegmentId remoteLogSegmentId() { + return remoteLogSegmentId; + } + + /** + * @return Start offset of this segment (inclusive). + */ + public long startOffset() { + return startOffset; + } + + /** + * @return End offset of this segment (inclusive). + */ + public long endOffset() { + return endOffset; + } + + /** + * @return Epoch time at which this event is occurred. + */ + public long eventTimestamp() { + return eventTimestamp; + } + + /** + * @return Total size of this segment in bytes. + */ + public int segmentSizeInBytes() { + return segmentSizeInBytes; + } + + /** + * @return Maximum timestamp of a record within this segment. + */ + public long maxTimestamp() { + return maxTimestamp; + } + + /** + * @return Map of leader epoch vs offset for the records available in this segment. + */ + public NavigableMap segmentLeaderEpochs() { + return segmentLeaderEpochs; + } + + /** + * @return Broker id from which this event is generated. + */ + public int brokerId() { + return brokerId; + } + + /** + * Returns the current state of this remote log segment. It can be any of the below + *

+ */ + public RemoteLogSegmentState state() { + return state; + } + + /** + * Creates a new RemoteLogSegmentMetadata applying the given {@code rlsmUpdate} on this instance. This method will + * not update this instance. + * + * @param rlsmUpdate update to be applied. + * @return a new instance created by applying the given update on this instance. + */ + public RemoteLogSegmentMetadata createRemoteLogSegmentWithUpdates(RemoteLogSegmentMetadataUpdate rlsmUpdate) { + if (!remoteLogSegmentId.equals(rlsmUpdate.remoteLogSegmentId())) { + throw new IllegalArgumentException("Given rlsmUpdate does not have this instance's remoteLogSegmentId."); + } + + return new RemoteLogSegmentMetadata(remoteLogSegmentId, startOffset, + endOffset, maxTimestamp, rlsmUpdate.brokerId(), rlsmUpdate.eventTimestamp(), + segmentSizeInBytes, rlsmUpdate.state(), segmentLeaderEpochs); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RemoteLogSegmentMetadata that = (RemoteLogSegmentMetadata) o; + return startOffset == that.startOffset && endOffset == that.endOffset && brokerId == that.brokerId + && maxTimestamp == that.maxTimestamp && eventTimestamp == that.eventTimestamp + && segmentSizeInBytes == that.segmentSizeInBytes + && Objects.equals(remoteLogSegmentId, that.remoteLogSegmentId) + && Objects.equals(segmentLeaderEpochs, that.segmentLeaderEpochs) && state == that.state; + } + + @Override + public int hashCode() { + return Objects.hash(remoteLogSegmentId, startOffset, endOffset, brokerId, maxTimestamp, eventTimestamp, + segmentLeaderEpochs, segmentSizeInBytes, state); + } + + @Override + public String toString() { + return "RemoteLogSegmentMetadata{" + + "remoteLogSegmentId=" + remoteLogSegmentId + + ", startOffset=" + startOffset + + ", endOffset=" + endOffset + + ", brokerId=" + brokerId + + ", maxTimestamp=" + maxTimestamp + + ", eventTimestamp=" + eventTimestamp + + ", segmentLeaderEpochs=" + segmentLeaderEpochs + + ", segmentSizeInBytes=" + segmentSizeInBytes + + ", state=" + state + + '}'; + } + +} diff --git a/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteLogSegmentMetadataUpdate.java b/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteLogSegmentMetadataUpdate.java new file mode 100644 index 0000000000000..4fc1ea1b4cc95 --- /dev/null +++ b/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteLogSegmentMetadataUpdate.java @@ -0,0 +1,120 @@ +/* + * 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.annotation.InterfaceStability; + +import java.util.Objects; + +/** + * It describes the metadata update about the log segment in the remote storage. This is currently used to update the + * state of the remote log segment by using {@link RemoteLogMetadataManager#updateRemoteLogSegmentMetadata(RemoteLogSegmentMetadataUpdate)}. + * This also includes the timestamp of this event. + */ +@InterfaceStability.Evolving +public class RemoteLogSegmentMetadataUpdate { + + /** + * Universally unique remote log segment id. + */ + private final RemoteLogSegmentId remoteLogSegmentId; + + /** + * Epoch time at which this event is generated. + */ + private final long eventTimestamp; + + /** + * It indicates the state in which the action is executed on this segment. + */ + private final RemoteLogSegmentState state; + + /** + * Broker id from which this event is generated. + */ + private final int brokerId; + + /** + * @param remoteLogSegmentId Universally unique remote log segment id. + * @param eventTimestamp Epoch time in milli seconds at which the remote log segment is copied to the remote tier storage. + * @param state State of the remote log segment. + * @param brokerId Broker id from which this event is generated. + */ + public RemoteLogSegmentMetadataUpdate(RemoteLogSegmentId remoteLogSegmentId, long eventTimestamp, + RemoteLogSegmentState state, int brokerId) { + this.remoteLogSegmentId = Objects.requireNonNull(remoteLogSegmentId, "remoteLogSegmentId can not be null"); + this.state = Objects.requireNonNull(state, "state can not be null"); + this.brokerId = brokerId; + this.eventTimestamp = eventTimestamp; + } + + /** + * @return Universally unique id of this remote log segment. + */ + public RemoteLogSegmentId remoteLogSegmentId() { + return remoteLogSegmentId; + } + + /** + * @return Epoch time at which this event is generated. + */ + public long eventTimestamp() { + return eventTimestamp; + } + + /** + * It represents the state of the remote log segment. It can be one of the values of {@link RemoteLogSegmentState}. + */ + public RemoteLogSegmentState state() { + return state; + } + + /** + * @return Broker id from which this event is generated. + */ + public int brokerId() { + return brokerId; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RemoteLogSegmentMetadataUpdate that = (RemoteLogSegmentMetadataUpdate) o; + return eventTimestamp == that.eventTimestamp && Objects + .equals(remoteLogSegmentId, that.remoteLogSegmentId) && state == that.state && brokerId == that.brokerId; + } + + @Override + public int hashCode() { + return Objects.hash(remoteLogSegmentId, eventTimestamp, state, brokerId); + } + + @Override + public String toString() { + return "RemoteLogSegmentMetadataUpdate{" + + "remoteLogSegmentId=" + remoteLogSegmentId + + ", eventTimestamp=" + eventTimestamp + + ", state=" + state + + ", brokerId=" + brokerId + + '}'; + } +} diff --git a/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteLogSegmentState.java b/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteLogSegmentState.java new file mode 100644 index 0000000000000..e27fdd19cd43b --- /dev/null +++ b/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteLogSegmentState.java @@ -0,0 +1,90 @@ +/* + * 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.annotation.InterfaceStability; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * It indicates the state of the remote log segment. This will be based on the action executed on this + * segment by the remote log service implementation. + *

+ * It goes through the below state transitions. + *

+ *

+ * +---------------------+            +----------------------+
+ * |COPY_SEGMENT_STARTED |----------->|COPY_SEGMENT_FINISHED |
+ * +-------------------+-+            +--+-------------------+
+ *                     |                 |
+ *                     |                 |
+ *                     v                 v
+ *                  +--+-----------------+-+
+ *                  |DELETE_SEGMENT_STARTED|
+ *                  +-----------+----------+
+ *                              |
+ *                              |
+ *                              v
+ *                  +-----------+-----------+
+ *                  |DELETE_SEGMENT_FINISHED|
+ *                  +-----------------------+
+ * 
+ */ +@InterfaceStability.Evolving +public enum RemoteLogSegmentState { + + /** + * This state indicates that the segment copying to remote storage is started but not yet finished. + */ + COPY_SEGMENT_STARTED((byte) 0), + + /** + * This state indicates that the segment copying to remote storage is finished. + */ + COPY_SEGMENT_FINISHED((byte) 1), + + /** + * This state indicates that the segment deletion is started but not yet finished. + */ + DELETE_SEGMENT_STARTED((byte) 2), + + /** + * This state indicates that the segment is deleted successfully. + */ + DELETE_SEGMENT_FINISHED((byte) 3); + + private static final Map STATE_TYPES = Collections.unmodifiableMap( + Arrays.stream(values()).collect(Collectors.toMap(RemoteLogSegmentState::id, Function.identity()))); + + private final byte id; + + RemoteLogSegmentState(byte id) { + this.id = id; + } + + public byte id() { + return id; + } + + public static RemoteLogSegmentState forId(byte id) { + return STATE_TYPES.get(id); + } +} diff --git a/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemotePartitionDeleteMetadata.java b/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemotePartitionDeleteMetadata.java new file mode 100644 index 0000000000000..fdf4e61c1dbec --- /dev/null +++ b/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemotePartitionDeleteMetadata.java @@ -0,0 +1,110 @@ +/* + * 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.annotation.InterfaceStability; + +import java.util.Objects; + +/** + * This class represents the metadata about the remote partition. It can be updated with {@link RemoteLogMetadataManager#putRemotePartitionDeleteMetadata(RemotePartitionDeleteMetadata)}. + * Possible state transitions are mentioned at {@link RemotePartitionDeleteState}. + */ +@InterfaceStability.Evolving +public class RemotePartitionDeleteMetadata { + + private final TopicIdPartition topicIdPartition; + private final RemotePartitionDeleteState state; + private final long eventTimestamp; + private final int brokerId; + + /** + * + * @param topicIdPartition + * @param state + * @param eventTimestamp + * @param brokerId + */ + public RemotePartitionDeleteMetadata(TopicIdPartition topicIdPartition, + RemotePartitionDeleteState state, + long eventTimestamp, + int brokerId) { + this.topicIdPartition = Objects.requireNonNull(topicIdPartition); + this.state = Objects.requireNonNull(state); + this.eventTimestamp = eventTimestamp; + this.brokerId = brokerId; + } + + /** + * @return TopicIdPartition for which this event is meant for. + */ + public TopicIdPartition topicIdPartition() { + return topicIdPartition; + } + + /** + * It represents the state of the remote partition. It can be one of the values of {@link RemotePartitionDeleteState}. + */ + public RemotePartitionDeleteState state() { + return state; + } + + /** + * @return Epoch time at which this event is occurred. + */ + public long eventTimestamp() { + return eventTimestamp; + } + + /** + * @return broker id from which this event is generated. + */ + public int brokerId() { + return brokerId; + } + + @Override + public String toString() { + return "RemotePartitionDeleteMetadata{" + + "topicPartition=" + topicIdPartition + + ", state=" + state + + ", eventTimestamp=" + eventTimestamp + + ", brokerId=" + brokerId + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RemotePartitionDeleteMetadata that = (RemotePartitionDeleteMetadata) o; + return eventTimestamp == that.eventTimestamp && + brokerId == that.brokerId && + Objects.equals(topicIdPartition, that.topicIdPartition) && + state == that.state; + } + + @Override + public int hashCode() { + return Objects.hash(topicIdPartition, state, eventTimestamp, brokerId); + } +} diff --git a/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemotePartitionDeleteState.java b/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemotePartitionDeleteState.java new file mode 100644 index 0000000000000..d5172fc1f942e --- /dev/null +++ b/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemotePartitionDeleteState.java @@ -0,0 +1,86 @@ +/* + * 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.annotation.InterfaceStability; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * It indicates the deletion state of the remote topic partition. This will be based on the action executed on this + * partition by the remote log service implementation. + * State transitions are mentioned below. + *

+ *

+ * +-------------------------+
+ * |DELETE_PARTITION_MARKED  |
+ * +-----------+-------------+
+ * |
+ * |
+ * +-----------v--------------+
+ * |DELETE_PARTITION_STARTED  |
+ * +-----------+--------------+
+ * |
+ * |
+ * +-----------v--------------+
+ * |DELETE_PARTITION_FINISHED |
+ * +--------------------------+
+ * 
+ *

+ */ +@InterfaceStability.Evolving +public enum RemotePartitionDeleteState { + + /** + * This is used when a topic/partition is marked for delete by the controller. + * That means, all its remote log segments are eligible for deletion so that remote partition removers can + * start deleting them. + */ + DELETE_PARTITION_MARKED((byte) 0), + + /** + * This state indicates that the partition deletion is started but not yet finished. + */ + DELETE_PARTITION_STARTED((byte) 1), + + /** + * This state indicates that the partition is deleted successfully. + */ + DELETE_PARTITION_FINISHED((byte) 2); + + private static final Map STATE_TYPES = Collections.unmodifiableMap( + Arrays.stream(values()).collect(Collectors.toMap(RemotePartitionDeleteState::id, Function.identity()))); + + private final byte id; + + RemotePartitionDeleteState(byte id) { + this.id = id; + } + + public byte id() { + return id; + } + + public static RemotePartitionDeleteState forId(byte id) { + return STATE_TYPES.get(id); + } + +} \ No newline at end of file diff --git a/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteResourceNotFoundException.java b/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteResourceNotFoundException.java new file mode 100644 index 0000000000000..f6ac4ec5ccc19 --- /dev/null +++ b/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteResourceNotFoundException.java @@ -0,0 +1,39 @@ +/* + * 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; + +/** + * Exception thrown when a resource is not found on the remote storage. + *

+ * A resource can be a log segment, any of the indexes or any which was stored in remote storage for a particular log + * segment. + */ +public class RemoteResourceNotFoundException extends RemoteStorageException { + private static final long serialVersionUID = 1L; + + public RemoteResourceNotFoundException(final String message) { + super(message); + } + + public RemoteResourceNotFoundException(final Throwable cause) { + super("Requested remote resource was not found", cause); + } + + public RemoteResourceNotFoundException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteStorageException.java b/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteStorageException.java new file mode 100644 index 0000000000000..f2488cb2957fb --- /dev/null +++ b/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteStorageException.java @@ -0,0 +1,36 @@ +/* + * 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; + +/** + * Exception thrown when there is a remote storage error. + * This can be used as the base exception by implementors of + * {@link RemoteStorageManager} or {@link RemoteLogMetadataManager} to create extended exceptions. + */ +public class RemoteStorageException extends Exception { + private static final long serialVersionUID = 1L; + + public RemoteStorageException(final String message) { + super(message); + } + + public RemoteStorageException(final String message, + final Throwable cause) { + super(message, cause); + } + +} diff --git a/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteStorageManager.java b/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteStorageManager.java new file mode 100644 index 0000000000000..64e3fdd026616 --- /dev/null +++ b/clients/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteStorageManager.java @@ -0,0 +1,141 @@ +/* + * 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.Configurable; +import org.apache.kafka.common.annotation.InterfaceStability; + +import java.io.Closeable; +import java.io.InputStream; + +/** + * This interface provides the lifecycle of remote log segments that includes copy, fetch, and delete from remote + * storage. + *

+ * Each upload or copy of a segment is initiated with {@link RemoteLogSegmentMetadata} containing {@link RemoteLogSegmentId} + * which is universally unique even for the same topic partition and offsets. + *

+ * {@link RemoteLogSegmentMetadata} is stored in {@link RemoteLogMetadataManager} before and after copy/delete operations on + * {@link RemoteStorageManager} with the respective {@link RemoteLogSegmentState}. {@link RemoteLogMetadataManager} is + * responsible for storing and fetching metadata about the remote log segments in a strongly consistent manner. + * This allows {@link RemoteStorageManager} to have eventual consistency on metadata (although the data is stored + * in strongly consistent semantics). + */ +@InterfaceStability.Evolving +public interface RemoteStorageManager extends Configurable, Closeable { + + /** + * Type of the index file. + */ + enum IndexType { + /** + * Represents offset index. + */ + Offset, + + /** + * Represents timestamp index. + */ + Timestamp, + + /** + * Represents producer snapshot index. + */ + ProducerSnapshot, + + /** + * Represents transaction index. + */ + Transaction, + + /** + * Represents leader epoch index. + */ + LeaderEpoch, + } + + /** + * Copies the given {@link LogSegmentData} provided for the given {@code remoteLogSegmentMetadata}. This includes + * log segment and its auxiliary indexes like offset index, time index, transaction index, leader epoch index, and + * producer snapshot index. + *

+ * Invoker of this API should always send a unique id as part of {@link RemoteLogSegmentMetadata#remoteLogSegmentId()} + * even when it retries to invoke this method for the same log segment data. + * + * @param remoteLogSegmentMetadata metadata about the remote log segment. + * @param logSegmentData data to be copied to tiered storage. + * @throws RemoteStorageException if there are any errors in storing the data of the segment. + */ + void copyLogSegmentData(RemoteLogSegmentMetadata remoteLogSegmentMetadata, + LogSegmentData logSegmentData) + throws RemoteStorageException; + + /** + * Returns the remote log segment data file/object as InputStream for the given {@link RemoteLogSegmentMetadata} + * starting from the given startPosition. The stream will end at the end of the remote log segment data file/object. + * + * @param remoteLogSegmentMetadata metadata about the remote log segment. + * @param startPosition start position of log segment to be read, inclusive. + * @return input stream of the requested log segment data. + * @throws RemoteStorageException if there are any errors while fetching the desired segment. + * @throws RemoteResourceNotFoundException when there are no resources associated with the given remoteLogSegmentMetadata. + */ + InputStream fetchLogSegment(RemoteLogSegmentMetadata remoteLogSegmentMetadata, + int startPosition) throws RemoteStorageException; + + /** + * Returns the remote log segment data file/object as InputStream for the given {@link RemoteLogSegmentMetadata} + * starting from the given startPosition. The stream will end at the smaller of endPosition and the end of the + * remote log segment data file/object. + * + * @param remoteLogSegmentMetadata metadata about the remote log segment. + * @param startPosition start position of log segment to be read, inclusive. + * @param endPosition end position of log segment to be read, inclusive. + * @return input stream of the requested log segment data. + * @throws RemoteStorageException if there are any errors while fetching the desired segment. + * @throws RemoteResourceNotFoundException when there are no resources associated with the given remoteLogSegmentMetadata. + */ + InputStream fetchLogSegment(RemoteLogSegmentMetadata remoteLogSegmentMetadata, + int startPosition, + int endPosition) throws RemoteStorageException; + + /** + * Returns the index for the respective log segment of {@link RemoteLogSegmentMetadata}. + * + * @param remoteLogSegmentMetadata metadata about the remote log segment. + * @param indexType type of the index to be fetched for the segment. + * @return input stream of the requested index. + * @throws RemoteStorageException if there are any errors while fetching the index. + * @throws RemoteResourceNotFoundException when there are no resources associated with the given remoteLogSegmentMetadata. + */ + InputStream fetchIndex(RemoteLogSegmentMetadata remoteLogSegmentMetadata, + IndexType indexType) throws RemoteStorageException; + + /** + * Deletes the resources associated with the given {@code remoteLogSegmentMetadata}. Deletion is considered as + * successful if this call returns successfully without any errors. It will throw {@link RemoteStorageException} if + * there are any errors in deleting the file. + *

+ * + * @param remoteLogSegmentMetadata metadata about the remote log segment to be deleted. + * @throws RemoteResourceNotFoundException if the requested resource is not found + * @throws RemoteStorageException if there are any storage related errors occurred. + * @throws RemoteResourceNotFoundException when there are no resources associated with the given remoteLogSegmentMetadata. + */ + void deleteLogSegmentData(RemoteLogSegmentMetadata remoteLogSegmentMetadata) throws RemoteStorageException; + +} \ No newline at end of file