KAFKA-16308 [4/4]: Add release-version flag to upgrade and downgrade commands (#17362)

I've added the release-version flag to the upgrade and downgrade commands. I've also added tests.

While working on this, I realized that we reveal non-production features to be returned in the version-mapping and dependencies commands. I have changed this to only return production features (except in tests) and added tests for this.

Reviewers: Jun Rao <jun@confluent.io>
This commit is contained in:
Justine Olshan 2024-10-04 13:03:54 -07:00 committed by GitHub
parent c11a38f9df
commit c3f13b5c57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 252 additions and 139 deletions

View File

@ -88,11 +88,11 @@ object StorageTool extends Logging {
0
case "version-mapping" =>
runVersionMappingCommand(namespace, printStream)
runVersionMappingCommand(namespace, printStream, Features.PRODUCTION_FEATURES)
0
case "feature-dependencies" =>
runFeatureDependenciesCommand(namespace, printStream)
runFeatureDependenciesCommand(namespace, printStream, Features.PRODUCTION_FEATURES)
0
case "random-uuid" =>
@ -151,10 +151,12 @@ object StorageTool extends Logging {
*
* @param namespace Arguments containing the release version.
* @param printStream The print stream to output the version mapping.
* @param validFeatures List of features to be considered in the output
*/
def runVersionMappingCommand(
namespace: Namespace,
printStream: PrintStream
printStream: PrintStream,
validFeatures: java.util.List[Features]
): Unit = {
val releaseVersion = Option(namespace.getString("release_version")).getOrElse(MetadataVersion.LATEST_PRODUCTION.toString)
try {
@ -163,7 +165,7 @@ object StorageTool extends Logging {
val metadataVersionLevel = metadataVersion.featureLevel()
printStream.print(f"metadata.version=$metadataVersionLevel%d ($releaseVersion%s)%n")
for (feature <- Features.values()) {
for (feature <- validFeatures.asScala) {
val featureLevel = feature.defaultValue(metadataVersion)
printStream.print(f"${feature.featureName}%s=$featureLevel%d%n")
}
@ -176,58 +178,57 @@ object StorageTool extends Logging {
def runFeatureDependenciesCommand(
namespace: Namespace,
printStream: PrintStream
printStream: PrintStream,
validFeatures: java.util.List[Features]
): Unit = {
val featureArgs = Option(namespace.getList[String]("feature")).map(_.asScala.toList).getOrElse(List.empty)
// Iterate over each feature specified with --feature
if (featureArgs != null) {
for (featureArg <- featureArgs) {
val Array(featureName, versionStr) = featureArg.split("=")
for (featureArg <- featureArgs) {
val Array(featureName, versionStr) = featureArg.split("=")
val featureLevel = try {
versionStr.toShort
val featureLevel = try {
versionStr.toShort
} catch {
case _: NumberFormatException =>
throw new TerseFailure(s"Invalid version format: $versionStr for feature $featureName")
}
if (featureName == MetadataVersion.FEATURE_NAME) {
val metadataVersion = try {
MetadataVersion.fromFeatureLevel(featureLevel)
} catch {
case _: NumberFormatException =>
throw new TerseFailure(s"Invalid version format: $versionStr for feature $featureName")
case _: IllegalArgumentException =>
throw new TerseFailure(s"Unknown metadata.version $featureLevel")
}
printStream.printf("%s=%d (%s) has no dependencies.%n", featureName, featureLevel, metadataVersion.version())
} else {
validFeatures.asScala.find(_.featureName == featureName) match {
case Some(feature) =>
val featureVersion = try {
feature.fromFeatureLevel(featureLevel, true)
} catch {
case _: IllegalArgumentException =>
throw new TerseFailure(s"Feature level $featureLevel is not supported for feature $featureName")
}
val dependencies = featureVersion.dependencies().asScala
if (featureName == MetadataVersion.FEATURE_NAME) {
val metadataVersion = try {
MetadataVersion.fromFeatureLevel(featureLevel)
} catch {
case _: IllegalArgumentException =>
throw new TerseFailure(s"Unknown metadata.version $featureLevel")
}
printStream.printf("%s=%d (%s) has no dependencies.%n", featureName, featureLevel, metadataVersion.version())
} else {
Features.values().find(_.featureName == featureName) match {
case Some(feature) =>
val featureVersion = try {
feature.fromFeatureLevel(featureLevel, true)
} catch {
case _: IllegalArgumentException =>
throw new TerseFailure(s"Feature level $featureLevel is not supported for feature $featureName")
}
val dependencies = featureVersion.dependencies().asScala
if (dependencies.isEmpty) {
printStream.printf("%s=%d has no dependencies.%n", featureName, featureLevel)
} else {
printStream.printf("%s=%d requires:%n", featureName, featureLevel)
for ((depFeature, depLevel) <- dependencies) {
if (depFeature == MetadataVersion.FEATURE_NAME) {
val metadataVersion = MetadataVersion.fromFeatureLevel(depLevel)
printStream.println(s" $depFeature=$depLevel (${metadataVersion.version()})")
} else {
printStream.println(s" $depFeature=$depLevel")
}
if (dependencies.isEmpty) {
printStream.printf("%s=%d has no dependencies.%n", featureName, featureLevel)
} else {
printStream.printf("%s=%d requires:%n", featureName, featureLevel)
for ((depFeature, depLevel) <- dependencies) {
if (depFeature == MetadataVersion.FEATURE_NAME) {
val metadataVersion = MetadataVersion.fromFeatureLevel(depLevel)
printStream.println(s" $depFeature=$depLevel (${metadataVersion.version()})")
} else {
printStream.println(s" $depFeature=$depLevel")
}
}
}
case None =>
throw new TerseFailure(s"Unknown feature: $featureName")
}
case None =>
throw new TerseFailure(s"Unknown feature: $featureName")
}
}
}

View File

@ -39,7 +39,7 @@ import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import scala.collection.mutable.ListBuffer
import scala.jdk.CollectionConverters.IterableHasAsScala
import scala.jdk.CollectionConverters._
@Timeout(value = 40)
class StorageToolTest {
@ -54,7 +54,7 @@ class StorageToolTest {
properties
}
val allFeatures = Features.FEATURES.toList
val testingFeatures = Features.FEATURES.toList.asJava
@Test
def testConfigToLogDirectories(): Unit = {
@ -441,23 +441,17 @@ Found problem:
stream: ByteArrayOutputStream,
releaseVersion: String
): Int = {
val tempDir = TestUtils.tempDir()
try {
// Prepare the arguments list
val arguments = ListBuffer[String]("version-mapping")
// Prepare the arguments list
val arguments = ListBuffer[String]("version-mapping")
// Add the release version argument
if (releaseVersion != null) {
arguments += "--release-version"
arguments += releaseVersion
}
// Execute the StorageTool with the arguments
StorageTool.execute(arguments.toArray, new PrintStream(stream))
} finally {
Utils.delete(tempDir)
// Add the release version argument
if (releaseVersion != null) {
arguments += "--release-version"
arguments += releaseVersion
}
// Execute the StorageTool with the arguments
StorageTool.execute(arguments.toArray, new PrintStream(stream))
}
@Test
@ -473,7 +467,7 @@ Found problem:
s"Output did not contain expected Metadata Version: $output"
)
for (feature <- Features.values()) {
for (feature <- Features.PRODUCTION_FEATURES.asScala) {
val featureLevel = feature.defaultValue(metadataVersion)
assertTrue(output.contains(s"${feature.featureName()}=$featureLevel"),
s"Output did not contain expected feature mapping: $output"
@ -496,7 +490,7 @@ Found problem:
s"Output did not contain expected Metadata Version: $output"
)
for (feature <- Features.values()) {
for (feature <- Features.PRODUCTION_FEATURES.asScala) {
val featureLevel = feature.defaultValue(metadataVersion)
assertTrue(output.contains(s"${feature.featureName()}=$featureLevel"),
s"Output did not contain expected feature mapping: $output"
@ -534,37 +528,20 @@ Found problem:
stream: ByteArrayOutputStream,
features: Seq[String]
): Int = {
val tempDir = TestUtils.tempDir()
try {
val arguments = ListBuffer[String]("feature-dependencies")
features.foreach(feature => {
arguments += "--feature"
arguments += feature
})
StorageTool.execute(arguments.toArray, new PrintStream(stream))
} finally {
Utils.delete(tempDir)
}
val arguments = ListBuffer[String]("feature-dependencies")
features.foreach(feature => {
arguments += "--feature"
arguments += feature
})
StorageTool.execute(arguments.toArray, new PrintStream(stream))
}
@Test
def testHandleFeatureDependenciesForFeatureWithDependencies(): Unit = {
def testTestingFeatureDependencies(): Unit = {
val stream = new ByteArrayOutputStream()
assertEquals(0, runFeatureDependenciesCommand(stream, Seq("test.feature.version=2")))
val namespace = StorageTool.parseArguments(Array("feature-dependencies", "--feature", "test.feature.version=2"))
val output = stream.toString
val metadataVersion = MetadataVersion.latestTesting()
val expectedOutput = s"test.feature.version=2 requires:\n metadata.version=${metadataVersion.featureLevel()} (${metadataVersion.version()})\n"
assertEquals(expectedOutput.trim, output.trim)
}
@Test
def testMultipleFeatureDependencies(): Unit = {
val stream = new ByteArrayOutputStream()
val features = Seq("transaction.version=2", "group.version=1", "test.feature.version=2")
assertEquals(0, runFeatureDependenciesCommand(stream, features))
StorageTool.runFeatureDependenciesCommand(namespace, new PrintStream(stream), testingFeatures)
val output = stream.toString.trim
System.out.println(output)
@ -572,11 +549,27 @@ Found problem:
val latestTestingVersion = MetadataVersion.latestTesting()
val latestTestingVersionString = s"metadata.version=${latestTestingVersion.featureLevel()} (${latestTestingVersion.version()})"
val expectedOutput =
s"""test.feature.version=2 requires:
| $latestTestingVersionString
|""".stripMargin.trim
assertEquals(expectedOutput, output)
}
@Test
def testMultipleFeatureDependencies(): Unit = {
val stream = new ByteArrayOutputStream()
val features = Seq("transaction.version=2", "group.version=1")
assertEquals(0, runFeatureDependenciesCommand(stream, features))
val output = stream.toString.trim
System.out.println(output)
val expectedOutput =
s"""transaction.version=2 has no dependencies.
|group.version=1 has no dependencies.
|test.feature.version=2 requires:
| $latestTestingVersionString
|""".stripMargin.trim
assertEquals(expectedOutput, output)

View File

@ -118,10 +118,10 @@ public class FeatureCommand {
handleDisable(namespace, adminClient);
break;
case "version-mapping":
handleVersionMapping(namespace);
handleVersionMapping(namespace, Features.PRODUCTION_FEATURES);
break;
case "feature-dependencies":
handleFeatureDependencies(namespace);
handleFeatureDependencies(namespace, Features.PRODUCTION_FEATURES);
break;
default:
throw new TerseException("Unknown command " + command);
@ -138,7 +138,11 @@ public class FeatureCommand {
Subparser upgradeParser = subparsers.addParser("upgrade")
.help("Upgrade one or more feature flags.");
upgradeParser.addArgument("--metadata")
.help("The level to which we should upgrade the metadata. For example, 3.3-IV3.")
.help("DEPRECATED -- The level to which we should upgrade the metadata. For example, 3.3-IV3.")
.action(store());
upgradeParser.addArgument("--release-version")
.help("The release version to update all features to. For example, 3.9-IV0 will set metadata.version=21 and kraft.version=1." +
" Use the version-mapping command to learn which features will be set for any given version.")
.action(store());
upgradeParser.addArgument("--feature")
.help("A feature upgrade we should perform, in feature=level format. For example: `metadata.version=5`.")
@ -153,7 +157,11 @@ public class FeatureCommand {
Subparser downgradeParser = subparsers.addParser("downgrade")
.help("Upgrade one or more feature flags.");
downgradeParser.addArgument("--metadata")
.help("The level to which we should downgrade the metadata. For example, 3.3-IV0.")
.help("DEPRECATED -- The level to which we should downgrade the metadata. For example, 3.3-IV0.")
.action(store());
downgradeParser.addArgument("--release-version")
.help("The release version to downgrade all features to. For example, 3.9-IV0 will set metadata.version=21 and kraft.version=1." +
" Use the version-mapping command to learn which features will be set for any given version.")
.action(store());
downgradeParser.addArgument("--feature")
.help("A feature downgrade we should perform, in feature=level format. For example: `metadata.version=5`.")
@ -272,30 +280,61 @@ public class FeatureCommand {
}
private static void handleUpgradeOrDowngrade(String op, Namespace namespace, Admin admin, FeatureUpdate.UpgradeType upgradeType) throws TerseException {
Map<String, FeatureUpdate> updates = new HashMap<>();
MetadataVersion version;
String metadata = namespace.getString("metadata");
if (metadata != null) {
List<String> features = namespace.getList("feature");
String releaseVersion = namespace.getString("release_version");
if (releaseVersion != null && (metadata != null || features != null)) {
throw new TerseException("Can not specify `release-version` with other feature flags.");
}
Map<String, FeatureUpdate> updates = new HashMap<>();
MetadataVersion metadataVersion;
if (releaseVersion != null) {
try {
version = MetadataVersion.fromVersionString(metadata);
metadataVersion = MetadataVersion.fromVersionString(releaseVersion);
updates.put(metadataVersion.featureName(), new FeatureUpdate(metadataVersion.featureLevel(), upgradeType));
} catch (Throwable e) {
throw new TerseException("Unknown metadata.version " + metadata +
throw new TerseException("Unknown metadata.version " + releaseVersion +
". Supported metadata.version are " + metadataVersionsToString(
MetadataVersion.MINIMUM_BOOTSTRAP_VERSION, MetadataVersion.latestProduction()));
}
updates.put(MetadataVersion.FEATURE_NAME, new FeatureUpdate(version.featureLevel(), upgradeType));
}
List<String> features = namespace.getList("feature");
if (features != null) {
features.forEach(feature -> {
String[] nameAndLevel;
nameAndLevel = parseNameAndLevel(feature);
if (updates.put(nameAndLevel[0], new FeatureUpdate(Short.parseShort(nameAndLevel[1]), upgradeType)) != null) {
throw new RuntimeException("Feature " + nameAndLevel[0] + " was specified more than once.");
try {
for (Features feature : Features.PRODUCTION_FEATURES) {
short featureLevel = feature.defaultValue(metadataVersion);
// Don't send a request to upgrade a feature to 0.
if (upgradeType != FeatureUpdate.UpgradeType.UPGRADE || featureLevel > 0) {
updates.put(feature.featureName(), new FeatureUpdate(featureLevel, upgradeType));
}
}
});
} catch (Throwable e) {
throw new TerseException(upgradeType.name() + " for release version " + releaseVersion +
" failed because at least one feature had the following error: " + e.getMessage());
}
} else {
if (metadata != null) {
System.out.println(" `metadata` flag is deprecated and may be removed in a future release.");
try {
metadataVersion = MetadataVersion.fromVersionString(metadata);
} catch (Throwable e) {
throw new TerseException("Unknown metadata.version " + metadata +
". Supported metadata.version are " + metadataVersionsToString(
MetadataVersion.MINIMUM_BOOTSTRAP_VERSION, MetadataVersion.latestProduction()));
}
updates.put(MetadataVersion.FEATURE_NAME, new FeatureUpdate(metadataVersion.featureLevel(), upgradeType));
}
if (features != null) {
features.forEach(feature -> {
String[] nameAndLevel;
nameAndLevel = parseNameAndLevel(feature);
if (updates.put(nameAndLevel[0], new FeatureUpdate(Short.parseShort(nameAndLevel[1]), upgradeType)) != null) {
throw new RuntimeException("Feature " + nameAndLevel[0] + " was specified more than once.");
}
});
}
}
update(op, admin, updates, namespace.getBoolean("dry_run"));
@ -317,7 +356,7 @@ public class FeatureCommand {
update("disable", adminClient, updates, namespace.getBoolean("dry_run"));
}
static void handleVersionMapping(Namespace namespace) throws TerseException {
static void handleVersionMapping(Namespace namespace, List<Features> validFeatures) throws TerseException {
// Get the release version from the command-line arguments or default to the latest stable version
String releaseVersion = Optional.ofNullable(namespace.getString("release_version"))
.orElseGet(() -> MetadataVersion.latestProduction().version());
@ -328,7 +367,7 @@ public class FeatureCommand {
short metadataVersionLevel = version.featureLevel();
System.out.printf("metadata.version=%d (%s)%n", metadataVersionLevel, releaseVersion);
for (Features feature : Features.values()) {
for (Features feature : validFeatures) {
short featureLevel = feature.defaultValue(version);
System.out.printf("%s=%d%n", feature.featureName(), featureLevel);
}
@ -339,7 +378,7 @@ public class FeatureCommand {
}
}
static void handleFeatureDependencies(Namespace namespace) throws TerseException {
static void handleFeatureDependencies(Namespace namespace, List<Features> validFeatures) throws TerseException {
List<String> featureArgs = namespace.getList("feature");
// Iterate over each feature specified with --feature
@ -361,7 +400,7 @@ public class FeatureCommand {
// Assuming metadata versions do not have dependencies.
System.out.printf("%s=%d (%s) has no dependencies.%n", featureName, featureLevel, metadataVersion.version());
} else {
Features featureEnum = Arrays.stream(Features.FEATURES)
Features featureEnum = validFeatures.stream()
.filter(f -> f.featureName().equals(featureName))
.findFirst()
.orElseThrow(() -> new TerseException("Unknown feature: " + featureName));

View File

@ -49,6 +49,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(value = ClusterTestExtensions.class)
public class FeatureCommandTest {
private final List<Features> testingFeatures = Arrays.stream(Features.FEATURES).collect(Collectors.toList());
@ClusterTest(types = {Type.KRAFT}, metadataVersion = MetadataVersion.IBP_3_3_IV1)
public void testDescribeWithKRaft(ClusterInstance cluster) {
String commandOutput = ToolsTestUtils.captureStandardOut(() ->
@ -100,7 +102,7 @@ public class FeatureCommandTest {
assertEquals(0, FeatureCommand.mainNoExit("--bootstrap-server", cluster.bootstrapServers(),
"upgrade", "--metadata", "3.3-IV2"))
);
assertEquals("metadata.version was upgraded to 6.", commandOutput);
assertEquals(format("`metadata` flag is deprecated and may be removed in a future release.%nmetadata.version was upgraded to 6."), commandOutput);
}
@ClusterTest(types = {Type.KRAFT}, metadataVersion = MetadataVersion.IBP_3_3_IV1)
@ -118,16 +120,62 @@ public class FeatureCommandTest {
"downgrade", "--metadata", "3.3-IV0"))
);
assertEquals("Could not downgrade metadata.version to 4. The update failed for all features since the following " +
"feature had an error: Invalid metadata.version 4. Refusing to perform the requested downgrade because it might delete metadata information.", commandOutput);
assertEquals(format("`metadata` flag is deprecated and may be removed in a future release.%nCould not downgrade metadata.version to 4." +
" The update failed for all features since the following feature had an error: Invalid metadata.version 4." +
" Refusing to perform the requested downgrade because it might delete metadata information."), commandOutput);
commandOutput = ToolsTestUtils.captureStandardOut(() ->
assertEquals(1, FeatureCommand.mainNoExit("--bootstrap-server", cluster.bootstrapServers(),
"downgrade", "--unsafe", "--metadata", "3.3-IV0"))
);
assertEquals("Could not downgrade metadata.version to 4. The update failed for all features since the following " +
"feature had an error: Invalid metadata.version 4. Unsafe metadata downgrade is not supported in this version.", commandOutput);
assertEquals(format("`metadata` flag is deprecated and may be removed in a future release.%nCould not downgrade metadata.version to 4." +
" The update failed for all features since the following feature had an error: Invalid metadata.version 4." +
" Unsafe metadata downgrade is not supported in this version."), commandOutput);
}
@ClusterTest(types = {Type.KRAFT}, metadataVersion = MetadataVersion.IBP_3_8_IV0)
public void testUpgradeWithReleaseVersion(ClusterInstance cluster) {
String commandOutput = ToolsTestUtils.captureStandardOut(() ->
assertEquals(1, FeatureCommand.mainNoExit("--bootstrap-server", cluster.bootstrapServers(),
"upgrade", "--release-version", "3.7-IV3"))
);
assertEquals("Could not upgrade metadata.version to 18. The update failed for all features since the following feature had an error:" +
" Invalid update version 18 for feature metadata.version. Can't downgrade the version of this feature without setting the upgrade type to either safe or unsafe downgrade.", commandOutput);
commandOutput = ToolsTestUtils.captureStandardOut(() ->
assertEquals(0, FeatureCommand.mainNoExit("--bootstrap-server", cluster.bootstrapServers(),
"upgrade", "--release-version", "3.9-IV0"))
);
assertEquals("kraft.version was upgraded to 1.\n" +
"metadata.version was upgraded to 21.", commandOutput);
}
@ClusterTest(types = {Type.KRAFT}, metadataVersion = MetadataVersion.IBP_3_8_IV0)
public void testDowngradeWithReleaseVersion(ClusterInstance cluster) {
String commandOutput = ToolsTestUtils.captureStandardOut(() ->
assertEquals(1, FeatureCommand.mainNoExit("--bootstrap-server", cluster.bootstrapServers(),
"downgrade", "--release-version", "3.9-IV0"))
);
assertTrue(commandOutput.contains("The update failed for all features since the following feature had an error:" +
" Invalid update version 1 for feature kraft.version. Can't downgrade to a newer version."));
assertTrue(commandOutput.contains("Could not downgrade group.version to 0."));
assertTrue(commandOutput.contains("Could not downgrade transaction.version to 0."));
assertTrue(commandOutput.contains("Could not downgrade kraft.version to 1."));
assertTrue(commandOutput.contains("Could not downgrade metadata.version to 21."));
commandOutput = ToolsTestUtils.captureStandardOut(() ->
assertEquals(0, FeatureCommand.mainNoExit("--bootstrap-server", cluster.bootstrapServers(),
"downgrade", "--release-version", "3.7-IV3"))
);
assertEquals("group.version was downgraded to 0.\n" +
"kraft.version was downgraded to 0.\n" +
"metadata.version was downgraded to 18.\n" +
"transaction.version was downgraded to 0.", commandOutput);
}
private String outputWithoutEpoch(String output) {
@ -213,7 +261,8 @@ public class FeatureCommandTest {
Throwable t = assertThrows(TerseException.class, () -> FeatureCommand.handleUpgrade(new Namespace(namespace), buildAdminClient()));
assertTrue(t.getMessage().contains("2 out of 2 operation(s) failed."));
});
assertEquals(format("Could not upgrade foo.bar to 6. Invalid update version 5 for feature metadata.version. Can't upgrade to lower version.%n" +
assertEquals(format("`metadata` flag is deprecated and may be removed in a future release.%nCould not upgrade foo.bar to 6." +
" Invalid update version 5 for feature metadata.version. Can't upgrade to lower version.%n" +
"Could not upgrade metadata.version to 5. Invalid update version 5 for feature metadata.version. Can't upgrade to lower version."), upgradeOutput);
}
@ -227,7 +276,8 @@ public class FeatureCommandTest {
Throwable t = assertThrows(TerseException.class, () -> FeatureCommand.handleUpgrade(new Namespace(namespace), buildAdminClient()));
assertTrue(t.getMessage().contains("2 out of 2 operation(s) failed."));
});
assertEquals(format("Can not upgrade foo.bar to 6. Invalid update version 5 for feature metadata.version. Can't upgrade to lower version.%n" +
assertEquals(format("`metadata` flag is deprecated and may be removed in a future release.%nCan not upgrade foo.bar to 6." +
" Invalid update version 5 for feature metadata.version. Can't upgrade to lower version.%n" +
"Can not upgrade metadata.version to 5. Invalid update version 5 for feature metadata.version. Can't upgrade to lower version."), upgradeOutput);
}
@ -241,7 +291,8 @@ public class FeatureCommandTest {
Throwable t = assertThrows(TerseException.class, () -> FeatureCommand.handleDowngrade(new Namespace(namespace), buildAdminClient()));
assertTrue(t.getMessage().contains("2 out of 2 operation(s) failed."));
});
assertEquals(format("Could not downgrade foo.bar to 1. Invalid update version 7 for feature metadata.version. Can't downgrade to newer version.%n" +
assertEquals(format("`metadata` flag is deprecated and may be removed in a future release.%nCould not downgrade foo.bar to 1." +
" Invalid update version 7 for feature metadata.version. Can't downgrade to newer version.%n" +
"Could not downgrade metadata.version to 7. Invalid update version 7 for feature metadata.version. Can't downgrade to newer version."), downgradeOutput);
}
@ -255,8 +306,8 @@ public class FeatureCommandTest {
Throwable t = assertThrows(TerseException.class, () -> FeatureCommand.handleDowngrade(new Namespace(namespace), buildAdminClient()));
assertTrue(t.getMessage().contains("2 out of 2 operation(s) failed."));
});
assertEquals(format("Can not downgrade foo.bar to 1. Invalid update version 7 for feature metadata.version. Can't downgrade to newer version.%n" +
"Can not downgrade metadata.version to 7. Invalid update version 7 for feature metadata.version. Can't downgrade to newer version."), downgradeOutput);
assertEquals(format("`metadata` flag is deprecated and may be removed in a future release.%nCan not downgrade foo.bar to 1. Invalid update version 7 for feature metadata.version." +
" Can't downgrade to newer version.%nCan not downgrade metadata.version to 7. Invalid update version 7 for feature metadata.version. Can't downgrade to newer version."), downgradeOutput);
}
@Test
@ -287,13 +338,42 @@ public class FeatureCommandTest {
"Can not disable quux. Invalid update version 0 for feature metadata.version. Can't downgrade below 4"), disableOutput);
}
@Test
public void testInvalidReleaseVersion() {
Map<String, Object> namespace = new HashMap<>();
namespace.put("release_version", "foo");
ToolsTestUtils.captureStandardOut(() -> {
Throwable t = assertThrows(TerseException.class, () -> FeatureCommand.handleUpgrade(new Namespace(namespace), buildAdminClient()));
assertTrue(t.getMessage().contains("Unknown metadata.version foo."));
});
}
@Test
public void testIncompatibleUpgradeFlags() {
Map<String, Object> namespace = new HashMap<>();
namespace.put("release_version", "3.3-IV3");
namespace.put("feature", Arrays.asList("foo.bar", "metadata.version", "quux"));
ToolsTestUtils.captureStandardOut(() -> {
Throwable t = assertThrows(TerseException.class, () -> FeatureCommand.handleUpgrade(new Namespace(namespace), buildAdminClient()));
assertTrue(t.getMessage().contains("Can not specify `release-version` with other feature flags."));
});
namespace.put("release_version", "3.3-IV3");
namespace.put("metadata", "3.3-IV3");
ToolsTestUtils.captureStandardOut(() -> {
Throwable t = assertThrows(TerseException.class, () -> FeatureCommand.handleUpgrade(new Namespace(namespace), buildAdminClient()));
assertTrue(t.getMessage().contains("Can not specify `release-version` with other feature flags."));
});
}
@Test
public void testHandleVersionMappingWithValidReleaseVersion() {
Map<String, Object> namespace = new HashMap<>();
namespace.put("release_version", "3.3-IV3");
String versionMappingOutput = ToolsTestUtils.captureStandardOut(() -> {
try {
FeatureCommand.handleVersionMapping(new Namespace(namespace));
FeatureCommand.handleVersionMapping(new Namespace(namespace), testingFeatures);
} catch (Exception e) {
throw new RuntimeException(e);
}
@ -317,7 +397,7 @@ public class FeatureCommandTest {
Map<String, Object> namespace = new HashMap<>();
String versionMappingOutput = ToolsTestUtils.captureStandardOut(() -> {
try {
FeatureCommand.handleVersionMapping(new Namespace(namespace));
FeatureCommand.handleVersionMapping(new Namespace(namespace), testingFeatures);
} catch (Exception e) {
throw new RuntimeException(e);
}
@ -342,7 +422,7 @@ public class FeatureCommandTest {
namespace.put("release_version", "2.9-IV2");
TerseException exception1 = assertThrows(TerseException.class, () ->
FeatureCommand.handleVersionMapping(new Namespace(namespace))
FeatureCommand.handleVersionMapping(new Namespace(namespace), testingFeatures)
);
assertEquals("Unknown release version '2.9-IV2'." +
@ -352,7 +432,7 @@ public class FeatureCommandTest {
namespace.put("release_version", "invalid");
TerseException exception2 = assertThrows(TerseException.class, () ->
FeatureCommand.handleVersionMapping(new Namespace(namespace))
FeatureCommand.handleVersionMapping(new Namespace(namespace), testingFeatures)
);
assertEquals("Unknown release version 'invalid'." +
@ -367,7 +447,7 @@ public class FeatureCommandTest {
String output = ToolsTestUtils.captureStandardOut(() -> {
try {
FeatureCommand.handleFeatureDependencies(new Namespace(namespace));
FeatureCommand.handleFeatureDependencies(new Namespace(namespace), testingFeatures);
} catch (TerseException e) {
throw new RuntimeException(e);
}
@ -389,7 +469,7 @@ public class FeatureCommandTest {
String output = ToolsTestUtils.captureStandardOut(() -> {
try {
FeatureCommand.handleFeatureDependencies(new Namespace(namespace));
FeatureCommand.handleFeatureDependencies(new Namespace(namespace), testingFeatures);
} catch (Exception e) {
throw new RuntimeException(e);
}
@ -405,8 +485,8 @@ public class FeatureCommandTest {
Exception exception = assertThrows(
TerseException.class,
() -> FeatureCommand.handleFeatureDependencies(new Namespace(namespace)
));
() -> FeatureCommand.handleFeatureDependencies(new Namespace(namespace), testingFeatures)
);
assertEquals("Unknown feature: unknown.feature", exception.getMessage());
}
@ -418,8 +498,8 @@ public class FeatureCommandTest {
Exception exception = assertThrows(
IllegalArgumentException.class,
() -> FeatureCommand.handleFeatureDependencies(new Namespace(namespace)
));
() -> FeatureCommand.handleFeatureDependencies(new Namespace(namespace), testingFeatures)
);
assertEquals("No feature:transaction.version with feature level 1000", exception.getMessage());
}
@ -431,7 +511,7 @@ public class FeatureCommandTest {
RuntimeException exception = assertThrows(
RuntimeException.class,
() -> FeatureCommand.handleFeatureDependencies(new Namespace(namespace))
() -> FeatureCommand.handleFeatureDependencies(new Namespace(namespace), testingFeatures)
);
assertEquals(
@ -451,7 +531,7 @@ public class FeatureCommandTest {
String output = ToolsTestUtils.captureStandardOut(() -> {
try {
FeatureCommand.handleFeatureDependencies(new Namespace(namespace));
FeatureCommand.handleFeatureDependencies(new Namespace(namespace), testingFeatures);
} catch (TerseException e) {
throw new RuntimeException(e);
}