Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ public AbstractConfig(ConfigDef definition, Map<?, ?> originals, Map<String, ?>
throw new ConfigException(entry.getKey().toString(), entry.getValue(), "Key must be a string.");

this.originals = resolveConfigVariables(configProviderProps, (Map<String, Object>) originals);

this.values = definition.parse(this.originals);
Map<String, Object> configUpdates = postProcessParsedConfig(Collections.unmodifiableMap(this.values));
for (Map.Entry<String, Object> update : configUpdates.entrySet()) {
Expand Down Expand Up @@ -459,10 +458,11 @@ private Map<String, String> extractPotentialVariables(Map<?, ?> configMap) {
private Map<String, ?> resolveConfigVariables(Map<String, ?> configProviderProps, Map<String, Object> originals) {
Map<String, String> providerConfigString;
Map<String, ?> configProperties;

Map<String, Object> resolvedOriginals = new HashMap<>();
// As variable configs are strings, parse the originals and obtain the potential variable configs.
Map<String, String> indirectVariables = extractPotentialVariables(originals);

resolvedOriginals.putAll(originals);
if (configProviderProps == null || configProviderProps.isEmpty()) {
providerConfigString = indirectVariables;
configProperties = originals;
Expand All @@ -475,10 +475,12 @@ private Map<String, String> extractPotentialVariables(Map<?, ?> configMap) {
if (!providers.isEmpty()) {
ConfigTransformer configTransformer = new ConfigTransformer(providers);
ConfigTransformerResult result = configTransformer.transform(indirectVariables);
originals.putAll(result.data());
if (!result.data().isEmpty()) {
resolvedOriginals.putAll(result.data());
}
}

return originals;
return new ResolvingMap<>(resolvedOriginals, originals);
}

private Map<String, Object> configProviderProperties(String configProviderPrefix, Map<String, ?> providerConfigProperties) {
Expand Down Expand Up @@ -585,4 +587,31 @@ public V get(Object key) {
return super.get(key);
}
}

/**
* ResolvingMap keeps a track of the original map instance and the resolved configs.
* The originals are tracked in a separate nested map and may be a `RecordingMap`; thus
* any access to a value for a key needs to be recorded on the originals map.
* The resolved configs are kept in the inherited map and are therefore mutable, though any
* mutations are not applied to the originals.
*/
private static class ResolvingMap<V> extends HashMap<String, V> {

private final Map<String, ?> originals;

ResolvingMap(Map<String, ? extends V> resolved, Map<String, ?> originals) {
super(resolved);
this.originals = Collections.unmodifiableMap(originals);
}

@Override
public V get(Object key) {
if (key instanceof String && originals.containsKey(key)) {
// Intentionally ignore the result; call just to mark the original entry as used
originals.get(key);
}
// But always use the resolved entry
return super.get(key);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,20 @@ public void testConfigProvidersPropsAsParam() {
assertEquals(config.originals().get("sasl.kerberos.password"), "randomPassword");
}

@Test
public void testImmutableOriginalsWithConfigProvidersProps() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this test fail without the change?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tadsul, what is the answer to this question? (Unresolving this conversation.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes the test fails without the comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I am not sure it would. You should run the test without the changes to AbstractConfig.java to verify. The test invokes new TestIndirectConfigResolution(props, immutableMap) where neither props nor immutableMap is actually immutable. In order fot the test to fail props needs to be unmodifiable since that is the map used as originals. And immutableMap needs to be renamed since it is confusing as it is neither immutable nor the map being tested for immutability.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yes i had to modify the test to make it work .. my yes meant that i could reproduce the issue.. sorry about that i will post the modified test

// Test Case: Valid Test Case for ConfigProviders as a separate variable
Properties providers = new Properties();
providers.put("config.providers", "file");
providers.put("config.providers.file.class", "org.apache.kafka.common.config.provider.MockFileConfigProvider");
Properties props = new Properties();
props.put("sasl.kerberos.key", "${file:/usr/kerberos:key}");
Map<?, ?> immutableMap = Collections.unmodifiableMap(props);
Map<String, ?> provMap = convertPropertiesToMap(providers);
TestIndirectConfigResolution config = new TestIndirectConfigResolution(immutableMap, provMap);
assertEquals(config.originals().get("sasl.kerberos.key"), "testKey");
}

@Test
public void testAutoConfigResolutionWithMultipleConfigProviders() {
// Test Case: Valid Test Case With Multiple ConfigProviders as a separate variable
Expand Down
7 changes: 7 additions & 0 deletions gradle/spotbugs-exclude.xml
Original file line number Diff line number Diff line change
Expand Up @@ -332,4 +332,11 @@ For a detailed description of spotbugs bug categories, see https://spotbugs.read

<!-- END Suppress warnings for unused members that are undetectably used by Jackson -->

<Match>
<!-- Suppress a warning about ignoring return value because this is intentional. -->
<Class name="org.apache.kafka.common.config.AbstractConfig$ResolvingMap"/>
<Method name="get"/>
<Bug pattern="RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT"/>
</Match>

</FindBugsFilter>