-
Notifications
You must be signed in to change notification settings - Fork 749
[GOBBLIN-1927] Add topic validation support in KafkaSource, and add TopicNameValidator #3793
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 2 commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
.../java/org/apache/gobblin/source/extractor/extract/kafka/validator/TopicNameValidator.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| /* | ||
| * 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.gobblin.source.extractor.extract.kafka.validator; | ||
|
|
||
| import org.apache.gobblin.configuration.SourceState; | ||
| import org.apache.gobblin.source.extractor.extract.kafka.KafkaTopic; | ||
|
|
||
| /** | ||
| * A topic validator that validates the topic name | ||
| */ | ||
| public class TopicNameValidator extends TopicValidatorBase { | ||
| private static final String DOT = "."; | ||
|
|
||
| public TopicNameValidator(SourceState sourceState) { | ||
| super(sourceState); | ||
| } | ||
|
|
||
| /** | ||
| * Check if a topic name is valid, current rules are: | ||
| * 1. must not contain "." | ||
| * @param topic the topic to be validated | ||
| * @return true if the topic name is valid (aka. doesn't contain ".") | ||
| */ | ||
| @Override | ||
| public boolean validate(KafkaTopic topic) { | ||
| return !topic.getName().contains(DOT); | ||
| } | ||
| } |
33 changes: 33 additions & 0 deletions
33
.../java/org/apache/gobblin/source/extractor/extract/kafka/validator/TopicValidatorBase.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| /* | ||
| * 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.gobblin.source.extractor.extract.kafka.validator; | ||
|
|
||
| import org.apache.gobblin.configuration.SourceState; | ||
| import org.apache.gobblin.source.extractor.extract.kafka.KafkaTopic; | ||
|
|
||
| /** | ||
| * The base class of a topic validator | ||
| */ | ||
| public abstract class TopicValidatorBase { | ||
| protected SourceState sourceState; | ||
|
|
||
| public TopicValidatorBase(SourceState sourceState) { | ||
| this.sourceState = sourceState; | ||
| } | ||
|
|
||
| public abstract boolean validate(KafkaTopic topic); | ||
| } |
78 changes: 78 additions & 0 deletions
78
...ain/java/org/apache/gobblin/source/extractor/extract/kafka/validator/TopicValidators.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| /* | ||
| * 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.gobblin.source.extractor.extract.kafka.validator; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.apache.commons.lang.StringUtils; | ||
| import org.apache.gobblin.configuration.SourceState; | ||
| import org.apache.gobblin.source.extractor.extract.kafka.KafkaTopic; | ||
| import org.apache.gobblin.util.reflection.GobblinConstructorUtils; | ||
|
|
||
| /** | ||
| * The TopicValidators contains a list of {@link TopicValidatorBase} that validate topics. | ||
| * To enable it, add below settings in the config: | ||
| * gobblin.kafka.topicValidators=validator1_class_name,validator2_class_name... | ||
| */ | ||
| @Slf4j | ||
| public class TopicValidators { | ||
| public static final String VALIDATOR_CLASSES_KEY = "gobblin.kafka.topicValidators"; | ||
|
|
||
| public static final String VALIDATOR_CLASS_DELIMITER = ","; | ||
|
|
||
| private final List<TopicValidatorBase> validators = new ArrayList<>(); | ||
|
|
||
| public TopicValidators(SourceState state) { | ||
| for (String validatorClassName : state.getPropAsList(VALIDATOR_CLASSES_KEY, StringUtils.EMPTY)) { | ||
| try { | ||
| this.validators.add(GobblinConstructorUtils.invokeConstructor(TopicValidatorBase.class, validatorClassName, | ||
| state)); | ||
| } catch (Exception e) { | ||
| log.error("Failed to create topic validator: {}, due to {}", validatorClassName, e); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Validate topics with all the internal validators. | ||
| * Note: the validations for every topic run in parallel. | ||
| * @param topics the topics to be validated | ||
| * @return the topics that pass all the validators | ||
| */ | ||
| public List<KafkaTopic> validate(List<KafkaTopic> topics) { | ||
| // Validate the topics in parallel | ||
| return topics.parallelStream() | ||
| .filter(this::validate) | ||
| .collect(Collectors.toList()); | ||
| } | ||
|
|
||
| /** | ||
| * Validates a single topic with all the internal validators | ||
| */ | ||
| private boolean validate(KafkaTopic topic) { | ||
| log.debug("Validating topic {} in thread: {}", topic, Thread.currentThread().getName()); | ||
| for (TopicValidatorBase validator : this.validators) { | ||
| if (!validator.validate(topic)) { | ||
| log.info("Skip KafkaTopic: {}, by validator: {}", topic, validator.getClass().getName()); | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
72 changes: 72 additions & 0 deletions
72
...java/org/apache/gobblin/source/extractor/extract/kafka/validator/TopicValidatorsTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| /* | ||
| * 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.gobblin.source.extractor.extract.kafka.validator; | ||
|
|
||
| import com.google.common.collect.ImmutableList; | ||
| import com.google.common.collect.ImmutableSet; | ||
| import java.util.Arrays; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
| import java.util.stream.Collectors; | ||
| import org.apache.gobblin.configuration.SourceState; | ||
| import org.apache.gobblin.source.extractor.extract.kafka.KafkaTopic; | ||
| import org.testng.Assert; | ||
| import org.testng.annotations.Test; | ||
|
|
||
|
|
||
| public class TopicValidatorsTest { | ||
| @Test | ||
| public void testTopicValidators() { | ||
| List<String> allTopics = Arrays.asList( | ||
| "topic1", "topic2", // allowed | ||
| "topic-with.period-in_middle", ".topic-with-period-at-start", "topicWithPeriodAtEnd.", // bad topics | ||
| "topic3", "topic4"); // in deny list | ||
| List<KafkaTopic> topics = allTopics.stream() | ||
| .map(topicName -> new KafkaTopic(topicName, Collections.emptyList())).collect(Collectors.toList()); | ||
|
|
||
| SourceState state = new SourceState(); | ||
|
|
||
| // Without any topic validators | ||
| List<KafkaTopic> validTopics = new TopicValidators(state).validate(topics); | ||
| Assert.assertEquals(validTopics.size(), 7); | ||
|
|
||
| // Use 2 topic validators: TopicNameValidator and DenyListValidator | ||
| String validatorsToUse = String.join(TopicValidators.VALIDATOR_CLASS_DELIMITER, | ||
| ImmutableList.of(TopicNameValidator.class.getName(), DenyListValidator.class.getName())); | ||
| state.setProp(TopicValidators.VALIDATOR_CLASSES_KEY, validatorsToUse); | ||
| validTopics = new TopicValidators(state).validate(topics); | ||
|
|
||
| Assert.assertEquals(validTopics.size(), 2); | ||
| Assert.assertTrue(validTopics.stream().anyMatch(topic -> topic.getName().equals("topic1"))); | ||
| Assert.assertTrue(validTopics.stream().anyMatch(topic -> topic.getName().equals("topic2"))); | ||
| } | ||
|
|
||
| // A TopicValidator class to mimic a deny list | ||
| public static class DenyListValidator extends TopicValidatorBase { | ||
| Set<String> denyList = ImmutableSet.of("topic3", "topic4"); | ||
|
|
||
| public DenyListValidator(SourceState sourceState) { | ||
| super(sourceState); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean validate(KafkaTopic topic) { | ||
| return !this.denyList.contains(topic.getName()); | ||
| } | ||
| } | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not that I think this class will be used within Incremental Compute, but just FYI, we have seen in the past where parallelStream has caused some strange bugs related to not picking up dependencies in Spark. In general, I'd suggest always thinking about whether the parallelism is important given that the api has caused issue in the past.
#3706
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the reference of that PR.
Yes, the parallelism is on purpose. Some validator could be slow, validating the topics sequentially would significantly slow down the startup.
For example, there will be another PR to add an OrcSchemaConversionValidator that queries the remote schema registry for every topic.
If the
parallelStreamis not recommended, we would have to do it in a separate thread pool while trying to avoid the context class loader issue. Any comment on this approach?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we intend to keep this code limited to Kafka source? i.e. do we see a world where we'd want to use this topic validation in incremental?..
If we do see spark uses cases, could we look into the following comment from that PR that describes an alternative approach that would set the appropriate class loader? This sort of topic validation could make sense since Iceberg does not support some of our weird edge cases (e.g. non optional union schema)
#3706 (comment)
JFYI, in compaction we do have a similar validation step at the dataset finder level (and the PR I linked previously is a bug fix for that feature). If the intention is to use this topic validation across the board you can consider whether it makes sense to converge these code paths.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally, the data from a bad topic should not come to the incremental compaction stage, because:
KafkaSourcecan skip the bad topics during the work unit creation.However, the incremental compaction may potentially use a different set of validators, so it still make sense to provide the validation support beyond FastIngest. So let's go for it.
To set the context class loader correctly, in the new iteration the
ExecutorsUtilsis enhanced to help create thread pools where the running tasks can have the same access control and class loader settings as the thread that submits the tasks. Please help take a look.