Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -947,9 +947,15 @@ public String toString() {
}

public static class ValidString implements Validator {
final List<String> validStrings;
// visible for testing

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am not a fan of "visible for testing". What about the following:

  1. We add an enum to StreamsConfig:
    public enum UpgradeFromValues {
        UPGRADE_FROM_0100("0.10.0"),
        ...
        UPGRADE_FROM_34("3.4");


        private final String value;

        UpgradeFromValues(final String value) {
            this.value = value;
        }

        public String toString() {
            return value;
        }
    }
  1. We change the constants to:
public static final String UPGRADE_FROM_34 = UpgradeFromValues.UPGRADE_FROM_34.toString();
  1. We can use the enum in the tests and in the code.

@cadonna cadonna Jul 27, 2023

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since we just add a constant enum to StreamsConfig and given the hotfix character, I would argue we do not need a KIP for this.

@mjsax mjsax Aug 10, 2023

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This part does not work: public static final String UPGRADE_FROM_34 = UpgradeFromValues.UPGRADE_FROM_34.toString();

We use

switch ((String) upgradeFrom) {
                case StreamsConfig.UPGRADE_FROM_0100:
                ...
}

and the compiler complain that StreamsConfig.UPGRADE_FROM_0100 must be a const

error: constant string expression required
    case StreamsConfig.UPGRADE_FROM_0100:

So not sure if we gain much adding the enum? -- We would still need to remember update the enum, and thus have a "split brain" problem -- getting the List out of the CONFIG object seems to be safer.

Thoughts?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I pushed a corresponding update, but I am not super happy with it... I believe the original idea to get the list of values from CONFIG is safer. Curious to hear what others think. In the end I am fine both ways.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You can use:

switch (StreamsConfig.UpgradeFromValues.valueOf((String) upgradeFrom)) {
    case UPGRADE_FROM_0100:
    ...

Additionally, we can implement a validator that uses the enum to verify the values here:

.define(UPGRADE_FROM_CONFIG,
                    ConfigDef.Type.STRING,
                    null,
                    in(null,
                       UPGRADE_FROM_0100,
                       UPGRADE_FROM_0101,
                       UPGRADE_FROM_0102,
                       UPGRADE_FROM_0110,
                       UPGRADE_FROM_10,
                       UPGRADE_FROM_11,
                       UPGRADE_FROM_20,
                       UPGRADE_FROM_21,
                       UPGRADE_FROM_22,
                       UPGRADE_FROM_23,
                       UPGRADE_FROM_24,
                       UPGRADE_FROM_25,
                       UPGRADE_FROM_26,
                       UPGRADE_FROM_27,
                       UPGRADE_FROM_28,
                       UPGRADE_FROM_30,
                       UPGRADE_FROM_31,
                       UPGRADE_FROM_32,
                       UPGRADE_FROM_33,
                       UPGRADE_FROM_34),
                    Importance.LOW,
                    UPGRADE_FROM_DOC)

So we do not need to maintain this list separately from the strings and enums.

protected final List<String> validStrings;

private ValidString(List<String> validStrings) {
// visible for testing
protected ValidString(ValidString validString) {
this(validString.validStrings);
}

ValidString(List<String> validStrings) {
this.validStrings = validStrings;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ public class StreamsConfig extends AbstractConfig {

private static final Logger log = LoggerFactory.getLogger(StreamsConfig.class);

private static final ConfigDef CONFIG;
// visible for testing
protected static final ConfigDef CONFIG;

private final boolean eosEnabled;
private static final long DEFAULT_COMMIT_INTERVAL_MS = 30000L;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ private static boolean isUpgrade(final Map<String, ?> configs) {
case StreamsConfig.UPGRADE_FROM_32:
case StreamsConfig.UPGRADE_FROM_33:
case StreamsConfig.UPGRADE_FROM_34:
// there is no need to add new version here
return true;
default:
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ private boolean isNotUpgrade(final Map<String, ?> configs) {
case StreamsConfig.UPGRADE_FROM_32:
case StreamsConfig.UPGRADE_FROM_33:
case StreamsConfig.UPGRADE_FROM_34:
// there is no need to add new versions here
return false;
default:
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ private static boolean upgradeFromV0(final Map<String, ?> configs) {
case StreamsConfig.UPGRADE_FROM_31:
case StreamsConfig.UPGRADE_FROM_32:
case StreamsConfig.UPGRADE_FROM_33:
// there is no need to add new versions hers

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.

Suggested change
// there is no need to add new versions hers
// there is no need to add new versions here

return true;
default:
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ public RebalanceProtocol rebalanceProtocol() {
case StreamsConfig.UPGRADE_FROM_31:
case StreamsConfig.UPGRADE_FROM_32:
case StreamsConfig.UPGRADE_FROM_33:
case StreamsConfig.UPGRADE_FROM_34:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is the actual fix.

// we need to add new version when new "upgrade.from" values become available

// This config is for explicitly sending FK response to a requested partition
// and should not affect the rebalance protocol
break;
Expand Down Expand Up @@ -178,6 +181,9 @@ public int configuredMetadataVersion(final int priorVersion) {
case StreamsConfig.UPGRADE_FROM_31:
case StreamsConfig.UPGRADE_FROM_32:
case StreamsConfig.UPGRADE_FROM_33:
case StreamsConfig.UPGRADE_FROM_34:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is the actual fix.

// we need to add new version when new "upgrade.from" values become available

// This config is for explicitly sending FK response to a requested partition
// and should not affect the metadata version
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,34 @@
*/
package org.apache.kafka.streams.processor.internals.assignment;

import org.apache.kafka.clients.consumer.ConsumerPartitionAssignor;
import org.apache.kafka.common.config.ConfigDef;
import org.apache.kafka.common.config.ConfigException;
import org.apache.kafka.streams.StreamsConfig;
import org.junit.Before;
import org.junit.Test;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.apache.kafka.common.utils.Utils.mkEntry;
import static org.apache.kafka.common.utils.Utils.mkMap;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.EMPTY_RACK_AWARE_ASSIGNMENT_TAGS;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.mock;

public class AssignorConfigurationTest {
private final Map<String, Object> config = new HashMap<>();

@Before
public void setup() {
config.put(StreamsConfig.APPLICATION_ID_CONFIG, "app.id");
config.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "dummy");
config.put(StreamsConfig.InternalConfig.REFERENCE_CONTAINER_PARTITION_ASSIGNOR, mock(ReferenceContainer.class));
}

@Test
public void configsShouldRejectZeroWarmups() {
Expand All @@ -35,4 +54,52 @@ public void configsShouldRejectZeroWarmups() {

assertThat(exception.getMessage(), containsString("Invalid value 0 for configuration max.warmup.replicas"));
}

@Test
public void rebalanceProtocolShouldSupportAllUpgradeFromVersions() {
for (final String upgradeFrom : TestConfig.upgradeFromVersions()) {
config.put(StreamsConfig.UPGRADE_FROM_CONFIG, upgradeFrom);
final AssignorConfiguration assignorConfiguration = new AssignorConfiguration(config);

// next call should not throw
assignorConfiguration.rebalanceProtocol();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A more readable option without the need for a comment is:

Suggested change
// next call should not throw
assignorConfiguration.rebalanceProtocol();
try {
assignorConfiguration.rebalanceProtocol();
} catch (final Throwable throwable) {
throw new AssertionFailedError("Upgrade from " + upgradeFrom + " failed with " + throwable.getMessage() + "!");
}

}
}

@Test
public void configuredMetadataVersionShouldSupportAllUpgradeFromVersions() {
for (final String upgradeFrom : TestConfig.upgradeFromVersions()) {
config.put(StreamsConfig.UPGRADE_FROM_CONFIG, upgradeFrom);
final AssignorConfiguration assignorConfiguration = new AssignorConfiguration(config);

// next call should not throw
assignorConfiguration.configuredMetadataVersion(0);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

See my previous comment.

}
}

private static class TestValidString extends ConfigDef.ValidString {
protected TestValidString(final ConfigDef.ValidString validString) {
super(validString);
}

List<String> validStrings() {
return validStrings;
}
}

private static class TestConfig extends StreamsConfig {
public TestConfig() {
super(mkMap(
mkEntry(StreamsConfig.APPLICATION_ID_CONFIG, "app.id"),
mkEntry(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "dummy")
));
}

private static List<String> upgradeFromVersions() {
return new TestValidString(
(ConfigDef.ValidString) CONFIG.configKeys().get(StreamsConfig.UPGRADE_FROM_CONFIG).validator
).validStrings();
}

}
}