diff options
Diffstat (limited to 'controller-server')
6 files changed, 146 insertions, 7 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArtifactExpirer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArtifactExpirer.java index 32d06286820..d268a0ebc22 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArtifactExpirer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArtifactExpirer.java @@ -3,21 +3,32 @@ package com.yahoo.vespa.hosted.controller.maintenance; import com.yahoo.component.Version; import com.yahoo.config.provision.CloudName; +import com.yahoo.config.provision.SystemName; +import com.yahoo.vespa.defaults.Defaults; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.api.integration.artifact.Artifact; import com.yahoo.vespa.hosted.controller.api.integration.artifact.ArtifactRegistry; import com.yahoo.vespa.hosted.controller.versions.VersionStatus; import com.yahoo.vespa.hosted.controller.versions.VespaVersion; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.time.Duration; import java.time.Instant; import java.util.Comparator; import java.util.List; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; + +import static com.yahoo.yolean.Exceptions.uncheck; +import static java.util.logging.Level.INFO; /** - * Periodically expire unused artifacts, e.g. container images and RPMs. + * Periodically expire unused artifacts, e.g. container images and RPMs. Artifacts with a version that is + * present in config-models-*.xml are never expired (in cd we . * * @author mpolden */ @@ -27,8 +38,15 @@ public class ArtifactExpirer extends ControllerMaintainer { private static final Duration MIN_AGE = Duration.ofDays(14); + private final Path configModelPath; + public ArtifactExpirer(Controller controller, Duration interval) { + this(controller, interval, Paths.get(Defaults.getDefaults().underVespaHome("conf/configserver-app/"))); + } + + public ArtifactExpirer(Controller controller, Duration interval, Path configModelPath) { super(controller, interval); + this.configModelPath = configModelPath; } @Override @@ -46,10 +64,10 @@ public class ArtifactExpirer extends ControllerMaintainer { try { Instant now = controller().clock().instant(); List<Artifact> artifactsToExpire = artifactRegistry.list().stream() - .filter(artifact -> isExpired(artifact, now, versionStatus)) + .filter(artifact -> isExpired(artifact, now, versionStatus, modelVersionsInUse())) .toList(); if (!artifactsToExpire.isEmpty()) { - log.log(Level.INFO, "Expiring " + artifactsToExpire.size() + " artifacts in " + cloudName + ": " + artifactsToExpire); + log.log(INFO, "Expiring " + artifactsToExpire.size() + " artifacts in " + cloudName + ": " + artifactsToExpire); artifactRegistry.deleteAll(artifactsToExpire); } return 0; @@ -60,10 +78,11 @@ public class ArtifactExpirer extends ControllerMaintainer { } /** Returns whether given artifact is expired */ - private boolean isExpired(Artifact artifact, Instant now, VersionStatus versionStatus) { + private boolean isExpired(Artifact artifact, Instant now, VersionStatus versionStatus, Set<Version> versionsInUse) { List<VespaVersion> versions = versionStatus.versions(); - if (versions.isEmpty()) return false; + versionsInUse.addAll(versions.stream().map(VespaVersion::versionNumber).collect(Collectors.toSet())); + if (versionsInUse.contains(artifact.version())) return false; if (versionStatus.isActive(artifact.version())) return false; if (artifact.createdAt().isAfter(now.minus(MIN_AGE))) return false; @@ -73,4 +92,35 @@ public class ArtifactExpirer extends ControllerMaintainer { return true; } + /** Model versions in use in this system, and, if this is a CD system, in the main/public system */ + private Set<Version> modelVersionsInUse() { + var system = controller().system(); + var versions = versionsForSystem(system); + + if (controller().system().isCd()) { + if (system == SystemName.PublicCd) + versions.addAll(versionsForSystem(SystemName.Public)); + else if (system == SystemName.cd) + versions.addAll(versionsForSystem(SystemName.main)); + } + + log.log(INFO, "model versions in use : " + versions); + return versions; + } + + private Set<Version> versionsForSystem(SystemName systemName) { + var versions = readConfigModelVersionsForSystem(systemName.name()); + log.log(INFO, "versions for system " + systemName.name() + ": " + versions); + return versions; + } + + private Set<Version> readConfigModelVersionsForSystem(String systemName) { + List<String> lines = uncheck(() -> Files.readAllLines(configModelPath.resolve("config-models-" + systemName + ".xml"))); + return lines.stream() + .filter(line -> line.contains("VespaModelFactory.")) + .map(line -> line.substring(line.indexOf("id='VespaModelFactory") + 22, line.indexOf("' class"))) + .map(Version::fromString) + .collect(Collectors.toSet()); + } + } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArtifactExpirerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArtifactExpirerTest.java index e79793bab61..58ac302d567 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArtifactExpirerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArtifactExpirerTest.java @@ -3,11 +3,15 @@ package com.yahoo.vespa.hosted.controller.maintenance; import com.yahoo.component.Version; import com.yahoo.config.provision.CloudName; +import com.yahoo.config.provision.SystemName; +import com.yahoo.vespa.hosted.controller.ControllerTester; import com.yahoo.vespa.hosted.controller.api.integration.artifact.Artifact; import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester; import com.yahoo.vespa.hosted.controller.integration.ArtifactRegistryMock; import org.junit.jupiter.api.Test; +import java.nio.file.Path; +import java.nio.file.Paths; import java.time.Duration; import java.time.Instant; import java.util.List; @@ -19,10 +23,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; */ public class ArtifactExpirerTest { + private static final Path configModelPath = Paths.get("src/test/resources/config-models/"); + @Test void maintain() { DeploymentTester tester = new DeploymentTester(); - ArtifactExpirer expirer = new ArtifactExpirer(tester.controller(), Duration.ofDays(1)); + // Note: No models in config-models-*.xml + ArtifactExpirer expirer = new ArtifactExpirer(tester.controller(), Duration.ofDays(1), configModelPath.resolve("empty")); ArtifactRegistryMock registry = tester.controllerTester().serviceRegistry().artifactRegistry(CloudName.DEFAULT).orElseThrow(); Instant instant = tester.clock().instant(); @@ -52,7 +59,7 @@ public class ArtifactExpirerTest { expirer.maintain(); assertEquals(List.of(image1, image2, image3), registry.list()); - // A new version becomes is published and controllers upgrade. This version, the system version + its unofficial + // A new version is published and controllers upgrade. This version, the system version + its unofficial // version and future versions are all kept Artifact image4 = new Artifact("image4", "registry.example.com", "vespa/vespa", "7.3.0-arm64", tester.clock().instant(), Version.fromString("7.3.0")); registry.add(image4); @@ -66,4 +73,46 @@ public class ArtifactExpirerTest { assertEquals(List.of(image4, image3), registry.list()); } + @Test + void maintainWithConfigModelsInUse() { + DeploymentTester tester = new DeploymentTester(new ControllerTester(SystemName.cd)); + ArtifactExpirer expirer = new ArtifactExpirer(tester.controller(), Duration.ofDays(1), configModelPath.resolve("cd")); + ArtifactRegistryMock registry = tester.controllerTester().serviceRegistry().artifactRegistry(CloudName.DEFAULT).orElseThrow(); + + Instant instant = tester.clock().instant(); + // image0 (with version 8.210.1) is not present in config-models-*.xml + Artifact image0 = new Artifact("image0", "registry.example.com", "vespa/vespa", "8.210.1", instant, Version.fromString("8.210.1")); + Artifact image1 = new Artifact("image1", "registry.example.com", "vespa/vespa", "8.220.15", instant, Version.fromString("8.220.15")); + Artifact image2 = new Artifact("image2", "registry.example.com", "vespa/vespa", "8.223.1", instant, Version.fromString("8.223.1")); + + registry.add(image0) + .add(image1) + .add(image2); + + // Make one image active + tester.controllerTester().upgradeSystem(image1.version()); + + // Nothing is expired initially, image2 is not active, but version is one of known config model versions + expirer.maintain(); + assertEquals(List.of(image0, image1, image2), registry.list()); + + // Nothing is expired as not enough time has passed since image creation + tester.clock().advance(Duration.ofDays(1)); + expirer.maintain(); + assertEquals(List.of(image0, image1, image2), registry.list()); + + // Enough time passes to expire unused image + tester.clock().advance(Duration.ofDays(13).plus(Duration.ofSeconds(1))); + expirer.maintain(); + assertEquals(List.of(image1, image2), registry.list()); + + // A new version is published and controllers upgrade. This version, the system version + its unofficial + // version and future versions are all kept + Artifact image4 = new Artifact("image4", "registry.example.com", "vespa/vespa", "8.223.2-arm64", tester.clock().instant(), Version.fromString("8.223.2")); + registry.add(image4); + tester.controllerTester().upgradeController(image4.version()); + expirer.maintain(); + assertEquals(List.of(image1, image2, image4), registry.list()); + } + } diff --git a/controller-server/src/test/resources/config-models/cd/config-models-cd.xml b/controller-server/src/test/resources/config-models/cd/config-models-cd.xml new file mode 100644 index 00000000000..2ed82101f73 --- /dev/null +++ b/controller-server/src/test/resources/config-models/cd/config-models-cd.xml @@ -0,0 +1,25 @@ + <component id='VespaModelFactory.8.218.31' class='com.yahoo.vespa.model.VespaModelFactory' bundle='config-model-fat-amended:8.218.31' /> + <component id='YahooAdminModelAmender.8.218.31' class='com.yahoo.vespa.model.admin.amender.YahooAdminModelAmender' bundle='config-model-fat-amended:8.218.31' /> + <component id='YahooModelValidator.8.218.31' class='com.yahoo.vespa.model.application.validation.YahooModelValidator' bundle='config-model-fat-amended:8.218.31' /> + <component id='YahooContainerModelAmender.8.218.31' class='com.yahoo.vespa.model.container.amender.YahooContainerModelAmender' bundle='config-model-fat-amended:8.218.31' /> + <component id='YahooContentContainerModelAmender.8.218.31' class='com.yahoo.vespa.model.container.amender.YahooContentContainerModelAmender' bundle='config-model-fat-amended:8.218.31' /> + <component id='VespaModelFactory.8.222.21' class='com.yahoo.vespa.model.VespaModelFactory' bundle='config-model-fat-amended:8.222.21' /> + <component id='YahooAdminModelAmender.8.222.21' class='com.yahoo.vespa.model.admin.amender.YahooAdminModelAmender' bundle='config-model-fat-amended:8.222.21' /> + <component id='YahooModelValidator.8.222.21' class='com.yahoo.vespa.model.application.validation.YahooModelValidator' bundle='config-model-fat-amended:8.222.21' /> + <component id='YahooContainerModelAmender.8.222.21' class='com.yahoo.vespa.model.container.amender.YahooContainerModelAmender' bundle='config-model-fat-amended:8.222.21' /> + <component id='YahooContentContainerModelAmender.8.222.21' class='com.yahoo.vespa.model.container.amender.YahooContentContainerModelAmender' bundle='config-model-fat-amended:8.222.21' /> + <component id='VespaModelFactory.8.222.22' class='com.yahoo.vespa.model.VespaModelFactory' bundle='config-model-fat-amended:8.222.22' /> + <component id='YahooAdminModelAmender.8.222.22' class='com.yahoo.vespa.model.admin.amender.YahooAdminModelAmender' bundle='config-model-fat-amended:8.222.22' /> + <component id='YahooModelValidator.8.222.22' class='com.yahoo.vespa.model.application.validation.YahooModelValidator' bundle='config-model-fat-amended:8.222.22' /> + <component id='YahooContainerModelAmender.8.222.22' class='com.yahoo.vespa.model.container.amender.YahooContainerModelAmender' bundle='config-model-fat-amended:8.222.22' /> + <component id='YahooContentContainerModelAmender.8.222.22' class='com.yahoo.vespa.model.container.amender.YahooContentContainerModelAmender' bundle='config-model-fat-amended:8.222.22' /> + <component id='VespaModelFactory.8.223.1' class='com.yahoo.vespa.model.VespaModelFactory' bundle='config-model-fat-amended:8.223.1' /> + <component id='YahooAdminModelAmender.8.223.1' class='com.yahoo.vespa.model.admin.amender.YahooAdminModelAmender' bundle='config-model-fat-amended:8.223.1' /> + <component id='YahooModelValidator.8.223.1' class='com.yahoo.vespa.model.application.validation.YahooModelValidator' bundle='config-model-fat-amended:8.223.1' /> + <component id='YahooContainerModelAmender.8.223.1' class='com.yahoo.vespa.model.container.amender.YahooContainerModelAmender' bundle='config-model-fat-amended:8.223.1' /> + <component id='YahooContentContainerModelAmender.8.223.1' class='com.yahoo.vespa.model.container.amender.YahooContentContainerModelAmender' bundle='config-model-fat-amended:8.223.1' /> + <component id='VespaModelFactory.8.223.2' class='com.yahoo.vespa.model.VespaModelFactory' bundle='config-model-fat-amended:8.223.2' /> + <component id='YahooAdminModelAmender.8.223.2' class='com.yahoo.vespa.model.admin.amender.YahooAdminModelAmender' bundle='config-model-fat-amended:8.223.2' /> + <component id='YahooModelValidator.8.223.2' class='com.yahoo.vespa.model.application.validation.YahooModelValidator' bundle='config-model-fat-amended:8.223.2' /> + <component id='YahooContainerModelAmender.8.223.2' class='com.yahoo.vespa.model.container.amender.YahooContainerModelAmender' bundle='config-model-fat-amended:8.223.2' /> + <component id='YahooContentContainerModelAmender.8.223.2' class='com.yahoo.vespa.model.container.amender.YahooContentContainerModelAmender' bundle='config-model-fat-amended:8.223.2' /> diff --git a/controller-server/src/test/resources/config-models/cd/config-models-main.xml b/controller-server/src/test/resources/config-models/cd/config-models-main.xml new file mode 100644 index 00000000000..3297840acd6 --- /dev/null +++ b/controller-server/src/test/resources/config-models/cd/config-models-main.xml @@ -0,0 +1,15 @@ + <component id='VespaModelFactory.8.218.31' class='com.yahoo.vespa.model.VespaModelFactory' bundle='config-model-fat-amended:8.218.31' /> + <component id='YahooAdminModelAmender.8.218.31' class='com.yahoo.vespa.model.admin.amender.YahooAdminModelAmender' bundle='config-model-fat-amended:8.218.31' /> + <component id='YahooModelValidator.8.218.31' class='com.yahoo.vespa.model.application.validation.YahooModelValidator' bundle='config-model-fat-amended:8.218.31' /> + <component id='YahooContainerModelAmender.8.218.31' class='com.yahoo.vespa.model.container.amender.YahooContainerModelAmender' bundle='config-model-fat-amended:8.218.31' /> + <component id='YahooContentContainerModelAmender.8.218.31' class='com.yahoo.vespa.model.container.amender.YahooContentContainerModelAmender' bundle='config-model-fat-amended:8.218.31' /> + <component id='VespaModelFactory.8.219.19' class='com.yahoo.vespa.model.VespaModelFactory' bundle='config-model-fat-amended:8.219.19' /> + <component id='YahooAdminModelAmender.8.219.19' class='com.yahoo.vespa.model.admin.amender.YahooAdminModelAmender' bundle='config-model-fat-amended:8.219.19' /> + <component id='YahooModelValidator.8.219.19' class='com.yahoo.vespa.model.application.validation.YahooModelValidator' bundle='config-model-fat-amended:8.219.19' /> + <component id='YahooContainerModelAmender.8.219.19' class='com.yahoo.vespa.model.container.amender.YahooContainerModelAmender' bundle='config-model-fat-amended:8.219.19' /> + <component id='YahooContentContainerModelAmender.8.219.19' class='com.yahoo.vespa.model.container.amender.YahooContentContainerModelAmender' bundle='config-model-fat-amended:8.219.19' /> + <component id='VespaModelFactory.8.220.15' class='com.yahoo.vespa.model.VespaModelFactory' bundle='config-model-fat-amended:8.220.15' /> + <component id='YahooAdminModelAmender.8.220.15' class='com.yahoo.vespa.model.admin.amender.YahooAdminModelAmender' bundle='config-model-fat-amended:8.220.15' /> + <component id='YahooModelValidator.8.220.15' class='com.yahoo.vespa.model.application.validation.YahooModelValidator' bundle='config-model-fat-amended:8.220.15' /> + <component id='YahooContainerModelAmender.8.220.15' class='com.yahoo.vespa.model.container.amender.YahooContainerModelAmender' bundle='config-model-fat-amended:8.220.15' /> + <component id='YahooContentContainerModelAmender.8.220.15' class='com.yahoo.vespa.model.container.amender.YahooContentContainerModelAmender' bundle='config-model-fat-amended:8.222.22' /> diff --git a/controller-server/src/test/resources/config-models/empty/config-models-cd.xml b/controller-server/src/test/resources/config-models/empty/config-models-cd.xml new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/controller-server/src/test/resources/config-models/empty/config-models-cd.xml diff --git a/controller-server/src/test/resources/config-models/empty/config-models-main.xml b/controller-server/src/test/resources/config-models/empty/config-models-main.xml new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/controller-server/src/test/resources/config-models/empty/config-models-main.xml |