adixitconfluent commented on code in PR #23360:
URL: https://github.com/apache/kafka/pull/23360#discussion_r3958553867


##########
server/src/main/java/org/apache/kafka/server/share/dlq/ShareGroupDLQRecordHelper.java:
##########
@@ -0,0 +1,262 @@
+/*
+ * 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.share.dlq;
+
+import org.apache.kafka.common.TopicIdPartition;
+import org.apache.kafka.common.Uuid;
+import org.apache.kafka.common.compress.Compression;
+import org.apache.kafka.common.header.Header;
+import org.apache.kafka.common.header.internals.RecordHeader;
+import org.apache.kafka.common.record.internal.DefaultRecord;
+import org.apache.kafka.common.record.internal.DefaultRecordBatch;
+import org.apache.kafka.common.record.internal.MemoryRecords;
+import org.apache.kafka.common.record.internal.Record;
+import org.apache.kafka.common.record.internal.SimpleRecord;
+import org.apache.kafka.common.utils.Time;
+import org.apache.kafka.server.share.LogReader;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+
+/**
+ * Shared helper for DLQ record building and source-record fetching.
+ * Used by both the K1 ({@link ShareGroupDLQStateManager}) and K2 DLQ manager 
implementations.
+ */
+public class ShareGroupDLQRecordHelper {
+
+    /**
+     * In most cases we expect the records getting DLQ'ed will be single 
offsets and
+     * not complete batches. Hence, using a large upper limit while reading 
from the log
+     * would be fruitless in most cases. Therefore, the value of 1 MB has been 
chosen
+     * for the DLQ-related log reads.
+     */
+    public static final int DLQ_MAX_FETCH_BYTES = 1024 * 1024;
+
+    public static final String HEADER_DLQ_ERRORS_TOPIC = "__dlq.errors.topic";
+    public static final String HEADER_DLQ_ERRORS_PARTITION = 
"__dlq.errors.partition";
+    public static final String HEADER_DLQ_ERRORS_OFFSET = 
"__dlq.errors.offset";
+    public static final String HEADER_DLQ_ERRORS_GROUP = "__dlq.errors.group";
+    public static final String HEADER_DLQ_ERRORS_DELIVERY_COUNT = 
"__dlq.errors.delivery.count";
+    public static final String HEADER_DLQ_ERRORS_MESSAGE = 
"__dlq.errors.message";
+
+    /**
+     * Result of building DLQ records for a range of offsets, respecting 
maxMessageBytes.
+     *
+     * @param records         The built MemoryRecords containing DLQ records 
with headers
+     * @param lastOffsetIncluded The last source offset included in this batch
+     * @param recordCount     The number of individual records in the batch
+     */
+    public record BuildResult(MemoryRecords records, long lastOffsetIncluded, 
int recordCount) {
+    }
+
+    /**
+     * Builds DLQ headers for a single offset.
+     *
+     * @param sourceTopic   The resolved source topic name
+     * @param partition     The source partition number
+     * @param offset        The source offset
+     * @param groupId       The share group ID
+     * @param deliveryCount Optional delivery count
+     * @param cause         Optional cause/reason for DLQ
+     * @return Array of DLQ headers
+     */
+    private static Header[] headers(
+            String sourceTopic,
+            int partition,
+            long offset,
+            String groupId,
+            Optional<Short> deliveryCount,
+            Optional<Throwable> cause
+    ) {
+        List<Header> headers = new ArrayList<>();
+        headers.add(new RecordHeader(HEADER_DLQ_ERRORS_TOPIC, 
sourceTopic.getBytes(StandardCharsets.UTF_8)));
+        headers.add(new RecordHeader(HEADER_DLQ_ERRORS_PARTITION, 
Integer.toString(partition).getBytes(StandardCharsets.UTF_8)));
+        headers.add(new RecordHeader(HEADER_DLQ_ERRORS_OFFSET, 
Long.toString(offset).getBytes(StandardCharsets.UTF_8)));
+        headers.add(new RecordHeader(HEADER_DLQ_ERRORS_GROUP, 
groupId.getBytes(StandardCharsets.UTF_8)));
+        deliveryCount.ifPresent(dc -> headers.add(
+                new RecordHeader(HEADER_DLQ_ERRORS_DELIVERY_COUNT, 
Short.toString(dc).getBytes(StandardCharsets.UTF_8))));
+        cause.ifPresent(c -> {
+            if (c.getMessage() != null) {
+                headers.add(new RecordHeader(HEADER_DLQ_ERRORS_MESSAGE, 
c.getMessage().getBytes(StandardCharsets.UTF_8)));
+            }
+        });
+        return headers.toArray(new Header[0]);

Review Comment:
   done



##########
server/src/main/java/org/apache/kafka/server/share/dlq/ShareGroupDLQValidator.java:
##########
@@ -0,0 +1,108 @@
+/*
+ * 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.share.dlq;
+
+import org.apache.kafka.common.config.ConfigException;
+
+import java.util.Optional;
+
+/**
+ * Shared validation logic for DLQ managers ({@link ShareGroupDLQStateManager} 
and
+ * {@code K2ShareGroupDLQManager}).
+ */
+public final class ShareGroupDLQValidator {
+
+    private ShareGroupDLQValidator() {}
+
+    /**
+     * Validates the fields of a {@link ShareGroupDLQRecordParameter}.
+     *
+     * @throws IllegalArgumentException if any field is invalid
+     */
+    public static void validateParam(ShareGroupDLQRecordParameter param) {
+        String prefix = "DLQ records parameters";
+        if (param == null) {
+            throw new IllegalArgumentException(prefix + " cannot be null.");
+        }
+        if (param.groupId() == null || param.groupId().isEmpty()) {
+            throw new IllegalArgumentException(prefix + " group cannot be null 
or empty.");
+        }
+        if (param.topicIdPartition() == null) {
+            throw new IllegalArgumentException(prefix + " topic/partition data 
cannot be null or empty.");
+        }
+        if (param.topicIdPartition().topicId() == null) {
+            throw new IllegalArgumentException(prefix + " topic id data cannot 
be null or empty.");
+        }
+        if (param.topicIdPartition().partition() < 0) {
+            throw new IllegalArgumentException(prefix + " partition cannot be 
negative.");
+        }
+        if (param.firstOffset() < 0) {
+            throw new IllegalArgumentException(prefix + " first offset cannot 
be negative.");
+        }
+        if (param.lastOffset() < 0) {
+            throw new IllegalArgumentException(prefix + " last offset cannot 
be negative.");
+        }
+        if (param.lastOffset() < param.firstOffset()) {
+            throw new IllegalArgumentException(prefix + " last offset cannot 
be less than first offset.");
+        }
+    }
+
+    /**
+     * Validates DLQ topic configuration. Checks that the topic name does not 
start with {@code __},
+     * that DLQ is enabled on the topic (if it exists), and that the topic 
name complies with the
+     * configured prefix.
+     *
+     * <p>Callers are responsible for checking that the topic name is present 
in the config (non-empty)
+     * before calling this method, and for any implementation-specific checks 
(e.g., K1 auto-create).
+     *
+     * @param groupId           the share group ID, for error messages
+     * @param userTopicName     the raw DLQ topic name from config (without 
tenant prefix)
+     * @param resolvedTopicName the topic name used for metadata cache lookups 
(may include tenant
+     *                          prefix in K2; same as {@code userTopicName} in 
K1)

Review Comment:
   done



##########
server/src/main/java/org/apache/kafka/server/share/dlq/ShareGroupDLQValidator.java:
##########
@@ -0,0 +1,108 @@
+/*
+ * 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.share.dlq;
+
+import org.apache.kafka.common.config.ConfigException;
+
+import java.util.Optional;
+
+/**
+ * Shared validation logic for DLQ managers ({@link ShareGroupDLQStateManager} 
and
+ * {@code K2ShareGroupDLQManager}).
+ */
+public final class ShareGroupDLQValidator {
+
+    private ShareGroupDLQValidator() {}
+
+    /**
+     * Validates the fields of a {@link ShareGroupDLQRecordParameter}.
+     *
+     * @throws IllegalArgumentException if any field is invalid
+     */
+    public static void validateParam(ShareGroupDLQRecordParameter param) {
+        String prefix = "DLQ records parameters";
+        if (param == null) {
+            throw new IllegalArgumentException(prefix + " cannot be null.");
+        }
+        if (param.groupId() == null || param.groupId().isEmpty()) {
+            throw new IllegalArgumentException(prefix + " group cannot be null 
or empty.");
+        }
+        if (param.topicIdPartition() == null) {
+            throw new IllegalArgumentException(prefix + " topic/partition data 
cannot be null or empty.");
+        }
+        if (param.topicIdPartition().topicId() == null) {
+            throw new IllegalArgumentException(prefix + " topic id data cannot 
be null or empty.");
+        }
+        if (param.topicIdPartition().partition() < 0) {
+            throw new IllegalArgumentException(prefix + " partition cannot be 
negative.");
+        }
+        if (param.firstOffset() < 0) {
+            throw new IllegalArgumentException(prefix + " first offset cannot 
be negative.");
+        }
+        if (param.lastOffset() < 0) {
+            throw new IllegalArgumentException(prefix + " last offset cannot 
be negative.");
+        }
+        if (param.lastOffset() < param.firstOffset()) {
+            throw new IllegalArgumentException(prefix + " last offset cannot 
be less than first offset.");
+        }
+    }
+
+    /**
+     * Validates DLQ topic configuration. Checks that the topic name does not 
start with {@code __},
+     * that DLQ is enabled on the topic (if it exists), and that the topic 
name complies with the
+     * configured prefix.
+     *
+     * <p>Callers are responsible for checking that the topic name is present 
in the config (non-empty)
+     * before calling this method, and for any implementation-specific checks 
(e.g., K1 auto-create).
+     *
+     * @param groupId           the share group ID, for error messages
+     * @param userTopicName     the raw DLQ topic name from config (without 
tenant prefix)

Review Comment:
   done



-- 
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: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to