diff options
8 files changed, 40 insertions, 25 deletions
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java index 5e9e1e8bf33..02b48c9d626 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java @@ -3,6 +3,7 @@ package com.yahoo.vespa.config.server.application; import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.component.Version; +import com.yahoo.component.VersionCompatibility; import com.yahoo.concurrent.StripedExecutor; import com.yahoo.config.FileReference; import com.yahoo.config.provision.ApplicationId; @@ -74,7 +75,7 @@ public class TenantApplications implements RequestHandler, HostValidator<Applica private final Clock clock; private final TenantFileSystemDirs tenantFileSystemDirs; private final ConfigserverConfig configserverConfig; - private final ListFlag<Integer> incompatibleMajorVersions; + private final ListFlag<String> incompatibleVersions; public TenantApplications(TenantName tenant, Curator curator, StripedExecutor<TenantName> zkWatcherExecutor, ExecutorService zkCacheExecutor, Metrics metrics, ReloadListener reloadListener, @@ -95,7 +96,7 @@ public class TenantApplications implements RequestHandler, HostValidator<Applica this.tenantFileSystemDirs = tenantFileSystemDirs; this.clock = clock; this.configserverConfig = configserverConfig; - this.incompatibleMajorVersions = PermanentFlags.INCOMPATIBLE_MAJOR_VERSIONS.bindTo(flagSource); + this.incompatibleVersions = PermanentFlags.INCOMPATIBLE_VERSIONS.bindTo(flagSource); } /** The curator backed ZK storage of this. */ @@ -392,8 +393,8 @@ public class TenantApplications implements RequestHandler, HostValidator<Applica if (vespaVersion.isEmpty()) return true; Version wantedVersion = applicationMapper.getForVersion(application, Optional.empty(), clock.instant()) .getModel().wantedNodeVersion(); - boolean compatibleMajor = ! incompatibleMajorVersions.value().contains(wantedVersion.getMajor()); - return compatibleMajor || vespaVersion.get().getMajor() == wantedVersion.getMajor(); + return VersionCompatibility.fromVersionList(incompatibleVersions.value()) + .accept(vespaVersion.get(), wantedVersion); } @Override diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/TenantApplicationsTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/TenantApplicationsTest.java index c95c95750a1..93637536182 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/application/TenantApplicationsTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/TenantApplicationsTest.java @@ -186,7 +186,7 @@ public class TenantApplicationsTest { applications.activateApplication(createSet(app1, deployedVersion1), 1); assertTrue("New major is compatible", applications.compatibleWith(Optional.of(nodeVersion0), app1)); - flagSource.withListFlag(PermanentFlags.INCOMPATIBLE_MAJOR_VERSIONS.id(), List.of(8), Integer.class); + flagSource.withListFlag(PermanentFlags.INCOMPATIBLE_VERSIONS.id(), List.of("8"), String.class); Version deployedVersion2 = Version.fromString("8.1"); applications.activateApplication(createSet(app1, deployedVersion2), 1); assertFalse("New major is incompatible", applications.compatibleWith(Optional.of(nodeVersion0), app1)); 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 9b64e27e3da..a4aac26b973 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 @@ -2,6 +2,7 @@ package com.yahoo.vespa.hosted.controller; import com.yahoo.component.Version; +import com.yahoo.component.VersionCompatibility; import com.yahoo.config.application.api.DeploymentSpec; import com.yahoo.config.application.api.ValidationId; import com.yahoo.config.application.api.ValidationOverrides; @@ -124,7 +125,7 @@ public class ApplicationController { private final ApplicationPackageValidator applicationPackageValidator; private final EndpointCertificates endpointCertificates; private final StringFlag dockerImageRepoFlag; - private final ListFlag<Integer> incompatibleMajorVersions; + private final ListFlag<String> incompatibleVersions; private final BillingController billingController; ApplicationController(Controller controller, CuratorDb curator, AccessControl accessControl, Clock clock, @@ -139,7 +140,7 @@ public class ApplicationController { artifactRepository = controller.serviceRegistry().artifactRepository(); applicationStore = controller.serviceRegistry().applicationStore(); dockerImageRepoFlag = PermanentFlags.DOCKER_IMAGE_REPO.bindTo(flagSource); - incompatibleMajorVersions = PermanentFlags.INCOMPATIBLE_MAJOR_VERSIONS.bindTo(flagSource); + incompatibleVersions = PermanentFlags.INCOMPATIBLE_VERSIONS.bindTo(flagSource); deploymentTrigger = new DeploymentTrigger(controller, clock); applicationPackageValidator = new ApplicationPackageValidator(controller); endpointCertificates = new EndpointCertificates(controller, @@ -754,8 +755,8 @@ public class ApplicationController { return curator.lockForDeployment(application, zone); } - public List<Integer> incompatibleMajorVersions() { - return incompatibleMajorVersions.value(); + public VersionCompatibility versionCompatibility() { + return VersionCompatibility.fromVersionList(incompatibleVersions.value()); } /** diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java index 5d2d0454d8e..87ca088f3dc 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java @@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.deployment; import com.google.common.collect.ImmutableMap; import com.yahoo.component.Version; +import com.yahoo.component.VersionCompatibility; import com.yahoo.config.application.api.DeploymentInstanceSpec; import com.yahoo.config.application.api.DeploymentSpec; import com.yahoo.config.application.api.DeploymentSpec.DeclaredTest; @@ -87,18 +88,18 @@ public class DeploymentStatus { private final JobList allJobs; private final SystemName system; private final Version systemVersion; - private final List<Integer> incompatibleMajorVersions; + private final VersionCompatibility versionCompatibility; private final Instant now; private final Map<JobId, StepStatus> jobSteps; private final List<StepStatus> allSteps; public DeploymentStatus(Application application, Map<JobId, JobStatus> allJobs, SystemName system, - Version systemVersion, List<Integer> incompatibleMajorVersions, Instant now) { + Version systemVersion, VersionCompatibility versionCompatibility, Instant now) { this.application = requireNonNull(application); this.allJobs = JobList.from(allJobs.values()); this.system = requireNonNull(system); this.systemVersion = requireNonNull(systemVersion); - this.incompatibleMajorVersions = List.copyOf(incompatibleMajorVersions); + this.versionCompatibility = versionCompatibility; this.now = requireNonNull(now); List<StepStatus> allSteps = new ArrayList<>(); this.jobSteps = jobDependencies(application.deploymentSpec(), allSteps); @@ -347,14 +348,10 @@ public class DeploymentStatus { return jobs; } - public boolean isIncompatible(Version platform, Optional<Version> compileVersion) { - return compileVersion.map(version -> incompatibleMajorVersions.stream().anyMatch(major -> major >= platform.getMajor() != major >= version.getMajor())) - .orElse(false); - } - - public boolean isIncompatible(Optional<Version> platform, Optional<ApplicationVersion> application) { - return platform.map(version -> isIncompatible(version, application.flatMap(ApplicationVersion::compileVersion))) - .orElse(false); + private boolean isIncompatible(Optional<Version> platform, Optional<ApplicationVersion> application) { + return platform.isPresent() + && application.flatMap(ApplicationVersion::compileVersion).isPresent() + && versionCompatibility.refuse(platform.get(), application.get().compileVersion().get()); } /** Changes to deploy with the given job, possibly split in two steps. */ diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java index 992c9cacc0f..b0e51c8fe9d 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java @@ -2,6 +2,7 @@ package com.yahoo.vespa.hosted.controller.deployment; import com.yahoo.component.Version; +import com.yahoo.component.VersionCompatibility; import com.yahoo.config.application.api.DeploymentInstanceSpec; import com.yahoo.config.application.api.DeploymentSpec; import com.yahoo.config.provision.ApplicationId; @@ -118,12 +119,13 @@ public class DeploymentTrigger { Optional<Version> compileVersion = outstanding.application().flatMap(ApplicationVersion::compileVersion); // If the outstanding revision requires a certain platform for compatibility, add that here. + VersionCompatibility compatibility = applications().versionCompatibility(); + Predicate<Version> compatibleWithCompileVersion = version -> compileVersion.map(compiled -> compatibility.accept(version, compiled)).orElse(true); if (status.application().productionDeployments().getOrDefault(instance, List.of()).stream() - .anyMatch(deployment -> status.isIncompatible(deployment.version(), compileVersion))) { + .anyMatch(deployment -> ! compatibleWithCompileVersion.test(deployment.version()))) { return targetsForPolicy(controller.readVersionStatus(), status.application().deploymentSpec().requireInstance(instance).upgradePolicy()) .stream() // Pick the latest platform which is compatible with the compile version, and is ready for this instance. - .filter(platform -> controller.applications().incompatibleMajorVersions().stream() - .noneMatch(boundary -> platform.getMajor() >= boundary != compileVersion.get().getMajor() >= boundary)) + .filter(compatibleWithCompileVersion) .map(outstanding::with) .filter(change -> status.instanceSteps().get(instance).readyAt(change) .map(readyAt -> ! readyAt.isAfter(controller.clock().instant())) diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java index 009e594af65..3cc08fb9f52 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java @@ -340,7 +340,7 @@ public class JobController { LinkedHashMap::new)), controller.system(), systemVersion, - controller.applications().incompatibleMajorVersions(), + controller.applications().versionCompatibility(), controller.clock().instant()); } 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 4b46d7777e7..bca4e2b09df 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 @@ -138,7 +138,7 @@ public final class ControllerTester { this.curator = curator; this.rotationsConfig = rotationsConfig; this.flagSource = flagSource.withBooleanFlag(PermanentFlags.ENABLE_PUBLIC_SIGNUP_FLOW.id(), true) - .withListFlag(PermanentFlags.INCOMPATIBLE_MAJOR_VERSIONS.id(), List.of(), Integer.class); + .withListFlag(PermanentFlags.INCOMPATIBLE_VERSIONS.id(), List.of(), String.class); this.controller = controller; // Make root logger use time from manual clock diff --git a/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java b/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java index 225c45fbfdb..72a16a37a8f 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java @@ -240,6 +240,20 @@ public class PermanentFlags { "Takes effect immediately.", ZONE_ID, APPLICATION_ID); + public static final UnboundListFlag<String> INCOMPATIBLE_VERSIONS = defineListFlag( + "incompatible-versions", List.of("8"), String.class, + "A list of versions which are binary-incompatible with earlier versions. " + + "A platform version A and an application package compiled against version B are thus incompatible if this " + + "list contains a version X such that (A >= X) != (B >= X). " + + "A version specifying only major, or major and minor, imply 0s for the unspecified parts." + + "This list may also contain '*' wildcards for any suffix of its version number; see the VersionCompatibility " + + "class for further details. " + + "The controller will attempt to couple platform upgrades to application changes if their compile versions are " + + "incompatible with any current deployments. " + + "The config server will refuse to serve config to nodes running a version which is incompatible with their " + + "current wanted node version, i.e., nodes about to upgrade to a version which is incompatible with the current.", + "Takes effect immediately"); + public static final UnboundListFlag<Integer> INCOMPATIBLE_MAJOR_VERSIONS = defineListFlag( "incompatible-major-versions", List.of(8), Integer.class, "A list of major versions which are binary-incompatible and requires an application package to " + |