aboutsummaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorJon Marius Venstad <venstad@gmail.com>2022-03-17 12:50:40 +0100
committerJon Marius Venstad <venstad@gmail.com>2022-03-17 12:50:40 +0100
commitf63e9b5daf5549e5f6d0e195bf18eda642808040 (patch)
tree27a95e5b39348f78fe7ed46b179335dbba2e4550 /controller-server
parent66075d95afe0105ae308712fb5d7c33329c6350a (diff)
Change compile version computation to account for incompatibilities
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java101
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java8
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java2
4 files changed, 75 insertions, 37 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
index a87c44b02e0..48f909df6b6 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
@@ -90,6 +90,7 @@ import java.util.OptionalInt;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Consumer;
+import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
@@ -97,10 +98,12 @@ import java.util.stream.Collectors;
import static com.yahoo.vespa.flags.FetchVector.Dimension.APPLICATION_ID;
import static com.yahoo.vespa.hosted.controller.api.integration.configserver.Node.State.active;
import static com.yahoo.vespa.hosted.controller.api.integration.configserver.Node.State.reserved;
+import static com.yahoo.vespa.hosted.controller.versions.VespaVersion.Confidence.low;
import static java.util.Comparator.naturalOrder;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
+import static java.util.stream.Collectors.toSet;
/**
* A singleton owned by the Controller which contains the methods and state for controlling applications.
@@ -310,36 +313,76 @@ public class ApplicationController {
}
/**
- * Returns a non-broken, released version at least as old as the oldest platform the given application is on.
+ * Returns the preferred Vespa version to compile against, for the given application, on an optionally restricted major.
*
- * If no known version is applicable, the newest version at least as old as the oldest platform is selected,
- * among all versions released for this system. If no such versions exists, throws an IllegalStateException.
+ * A target major may be specified as an argument; or it may be set specifically for the application;
+ * or in general, for all applications; the first such specification wins.
*
- * If a non-empty wantedMajor is given, return a version within that major if such version is exists in this system.
+ * The returned version is not newer than the oldest deployed platform for the application, unless
+ * the target major differs from the oldest deployed platform, in which case it is not newer than
+ * the oldest available platform version on that major instead.
+ *
+ * The returned version is compatible with a platform version available in the system.
+ *
+ * A candidate is sought first among versions with non-broken confidence, then among those with forgotten confidence.
+ *
+ * The returned version is the latest in the relevant candidate set.
+ *
+ * If no such version exists, an {@link IllegalArgumentException} is thrown.
*/
public Version compileVersion(TenantAndApplicationId id, OptionalInt wantedMajor) {
- Version oldestPlatform = oldestInstalledPlatform(id);
+
+ // Read version status, and pick out target platforms we could run the compiled package on.
+ OptionalInt targetMajor = firstNonEmpty(wantedMajor, requireApplication(id).majorVersion(), targetMajorVersion());
VersionStatus versionStatus = controller.readVersionStatus();
- Set<Version> versionsInSystem = versionStatus.versions().stream()
+ Version systemVersion = controller.systemVersion(versionStatus);
+ Version oldestInstalledPlatform = oldestInstalledPlatform(id);
+
+ // Target platforms are all versions not older than the oldest installed platform, unless forcing a major version change.
+ Predicate<Version> isTargetPlatform = targetMajor.isEmpty() || targetMajor.getAsInt() == oldestInstalledPlatform.getMajor()
+ ? version -> ! version.isBefore(oldestInstalledPlatform)
+ : version -> targetMajor.getAsInt() == version.getMajor();
+ Set<Version> platformVersions = versionStatus.versions().stream()
.map(VespaVersion::versionNumber)
- .collect(Collectors.toSet());
- Version compileVersion = versionStatus.versions().stream()
- .filter(version -> version.confidence().equalOrHigherThan(VespaVersion.Confidence.low))
- .filter(VespaVersion::isReleased)
- .map(VespaVersion::versionNumber)
- .filter(version -> !version.isAfter(oldestPlatform))
- .max(Comparator.naturalOrder())
- .orElseGet(() -> publishedVersionNotAfter(oldestPlatform, versionsInSystem));
- OptionalInt targetMajor = firstNonEmpty(wantedMajor, requireApplication(id).majorVersion(), targetMajorVersion());
- if (targetMajor.isPresent() && compileVersion.getMajor() != targetMajor.getAsInt()) {
- // Choose the oldest version matching wanted major so that the returned version is stable in the transition
- // to next major
- Optional<Version> versionMatchingMajor = versionsInSystem.stream()
- .filter(version -> version.getMajor() == targetMajor.getAsInt())
- .min(Comparator.naturalOrder());
- return versionMatchingMajor.orElse(compileVersion);
- }
- return compileVersion;
+ .filter(version -> ! version.isAfter(systemVersion))
+ .filter(isTargetPlatform)
+ .collect(toSet());
+ if (platformVersions.isEmpty())
+ throw new IllegalArgumentException("this system has no available versions" +
+ (targetMajor.isPresent() ? " on specified major: " + targetMajor.getAsInt() : ""));
+
+ // The returned compile version must be compatible with at least one target platform.
+ // If it is incompatible with any of the current platforms, the system will trigger a platform change.
+ // The returned compile version should also be at least as old as both the oldest target platform version,
+ // and the oldest current platform, unless the two are incompatible, in which case only the target matters.
+ VersionCompatibility compatibility = versionCompatibility(id.defaultInstance()); // Wrong id level >_<
+ Version oldestTargetPlatform = platformVersions.stream().min(naturalOrder()).get();
+ Version newestVersion = compatibility.accept(oldestInstalledPlatform, oldestTargetPlatform)
+ && oldestInstalledPlatform.isBefore(oldestTargetPlatform)
+ ? oldestInstalledPlatform
+ : oldestTargetPlatform;
+ Predicate<Version> systemCompatible = version -> ! version.isAfter(newestVersion)
+ && platformVersions.stream().anyMatch(platform -> compatibility.accept(platform, version));
+
+ // Find the newest, system-compatible version with non-broken confidence.
+ Optional<Version> nonBroken = versionStatus.versions().stream()
+ .filter(VespaVersion::isReleased)
+ .filter(version -> version.confidence().equalOrHigherThan(low))
+ .map(VespaVersion::versionNumber)
+ .filter(systemCompatible)
+ .max(naturalOrder());
+ if (nonBroken.isPresent()) return nonBroken.get();
+
+ // Fall back to the newest, system-compatible version with unknown confidence.
+ Set<Version> knownVersions = versionStatus.versions().stream().map(VespaVersion::versionNumber).collect(toSet());
+ Optional<Version> unknown = controller.mavenRepository().metadata().versions().stream()
+ .filter(version -> ! knownVersions.contains(version))
+ .filter(systemCompatible)
+ .max(naturalOrder());
+ if (unknown.isPresent()) return unknown.get();
+
+ throw new IllegalArgumentException("no suitable, released compile version exists" +
+ (targetMajor.isPresent() ? " for specified major: " + targetMajor.getAsInt() : ""));
}
private OptionalInt firstNonEmpty(OptionalInt... choices) {
@@ -350,16 +393,6 @@ public class ApplicationController {
return OptionalInt.empty();
}
- /** Returns a version that is published to a Maven repository not older than oldestPlatform and known by the system */
- private Version publishedVersionNotAfter(Version oldestPlatform, Set<Version> versionsInSystem) {
- return controller.mavenRepository().metadata().versions().stream()
- .filter(version -> !version.isAfter(oldestPlatform))
- .filter(versionsInSystem::contains)
- .max(Comparator.naturalOrder())
- .orElseThrow(() -> new IllegalStateException("No available releases of " +
- controller.mavenRepository().artifactId()));
- }
-
/**
* Creates a new application for an existing tenant.
*
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
index 8a6dac29305..5ababdd8250 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
@@ -28,7 +28,6 @@ import com.yahoo.vespa.hosted.controller.api.integration.dns.Record;
import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordData;
import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName;
import com.yahoo.vespa.hosted.controller.api.integration.dns.WeightedAliasTarget;
-import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockMavenRepository;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
import com.yahoo.vespa.hosted.controller.application.Endpoint;
@@ -1106,7 +1105,6 @@ public class ControllerTest {
DeploymentContext context = tester.newDeploymentContext();
ApplicationPackage applicationPackage = new ApplicationPackageBuilder().region("us-west-1").build();
TenantAndApplicationId application = TenantAndApplicationId.from(context.instanceId());
- ((MockMavenRepository) tester.controller().mavenRepository()).addVersion("7.1", "7.2", "8.0");
// No deployments result in system version
Version version0 = Version.fromString("7.1");
@@ -1126,8 +1124,12 @@ public class ControllerTest {
// A new major is released to the system
Version version2 = Version.fromString("8.0");
- tester.controllerTester().upgradeController(version2);
+ tester.controllerTester().upgradeSystem(version2);
assertEquals(version1, tester.applications().compileVersion(application, OptionalInt.empty()));
+ assertEquals(version1, tester.applications().compileVersion(application, OptionalInt.of(8)));
+
+ // The new major is marked as incompatible with older compile versions
+ tester.controllerTester().flagSource().withListFlag(PermanentFlags.INCOMPATIBLE_VERSIONS.id(), List.of("8"), String.class);
assertEquals(version2, tester.applications().compileVersion(application, OptionalInt.of(8)));
// Default major version is set to 8.
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
index 15a194d2ffa..75c33952754 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
@@ -274,6 +274,7 @@ public final class ControllerTester {
/** Upgrade entire system to given version */
public void upgradeSystem(Version version) {
+ ((MockMavenRepository) controller.mavenRepository()).addVersion(version);
upgradeController(version);
upgradeSystemApplications(version);
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
index 74bab37773a..8ab43f15c89 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
@@ -25,6 +25,7 @@ import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzPrincipal;
import com.yahoo.vespa.athenz.api.AthenzUser;
import com.yahoo.vespa.athenz.api.OAuthCredentials;
+import com.yahoo.vespa.flags.PermanentFlags;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.Instance;
@@ -861,6 +862,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// GET compile version for specific major
deploymentTester.controllerTester().upgradeSystem(Version.fromString("7.0"));
+ deploymentTester.controllerTester().flagSource().withListFlag(PermanentFlags.INCOMPATIBLE_VERSIONS.id(), List.of("*"), String.class);
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/compile-version", GET)
.userIdentity(USER_ID).properties(Map.of("allowMajor", "7")),
"{\"compileVersion\":\"7.0.0\"}");