diff options
4 files changed, 115 insertions, 11 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java index fd9bddac2c6..293749be32d 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java @@ -13,13 +13,13 @@ public class Record { private final RecordId id; private final Type type; private final RecordName name; - private final RecordData value; + private final RecordData data; - public Record(RecordId id, Type type, RecordName name, RecordData value) { + public Record(RecordId id, Type type, RecordName name, RecordData data) { this.id = Objects.requireNonNull(id, "id cannot be null"); this.type = Objects.requireNonNull(type, "type cannot be null"); this.name = Objects.requireNonNull(name, "name cannot be null"); - this.value = Objects.requireNonNull(value, "value cannot be null"); + this.data = Objects.requireNonNull(data, "data cannot be null"); } /** Unique identifier for this */ @@ -34,7 +34,7 @@ public class Record { /** Value for this, e.g. IP address for "A" record */ public RecordData value() { - return value; + return data; } /** Name of this, e.g. a FQDN for "A" record */ @@ -60,7 +60,7 @@ public class Record { "id=" + id + ", type=" + type + ", name='" + name + '\'' + - ", value='" + value + '\'' + + ", data='" + data + '\'' + '}'; } @@ -72,11 +72,11 @@ public class Record { return Objects.equals(id, record.id) && type == record.type && Objects.equals(name, record.name) && - Objects.equals(value, record.value); + Objects.equals(data, record.data); } @Override public int hashCode() { - return Objects.hash(id, type, name, value); + return Objects.hash(id, type, name, data); } } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordData.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordData.java index 444ee28f672..e0d19e0fff9 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordData.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordData.java @@ -42,8 +42,14 @@ public class RecordData { '}'; } + /** Create a new record containing the given data */ public static RecordData from(String data) { return new RecordData(data); } + /** Create a new record and append a trailing dot to given data, if missing */ + public static RecordData fqdn(String data) { + return from(data.endsWith(".") ? data : data + "."); + } + } 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 ea917147970..dd803cda18b 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 @@ -455,8 +455,16 @@ public class ApplicationController { private void registerRotationInDns(Rotation rotation, String dnsName) { try { Optional<Record> record = nameService.findRecord(Record.Type.CNAME, RecordName.from(dnsName)); - if (!record.isPresent()) { - RecordId id = nameService.createCname(RecordName.from(dnsName), RecordData.from(rotation.name())); + RecordData rotationName = RecordData.fqdn(rotation.name()); + if (record.isPresent()) { + // Ensure that the existing record points to the correct rotation + if (!record.get().value().equals(rotationName)) { + nameService.updateRecord(record.get().id(), rotationName); + log.info("Updated mapping for record ID " + record.get().id().asString() + ": " + dnsName + + " -> " + rotation.name()); + } + } else { + RecordId id = nameService.createCname(RecordName.from(dnsName), rotationName); log.info("Registered mapping with record ID " + id.asString() + ": " + dnsName + " -> " + rotation.name()); } 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 c41fc20d5c2..ccf59b56c00 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 @@ -37,6 +37,7 @@ import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; import com.yahoo.vespa.hosted.controller.deployment.BuildSystem; import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester; import com.yahoo.vespa.hosted.controller.persistence.ApplicationSerializer; +import com.yahoo.vespa.hosted.controller.rotation.RotationId; import com.yahoo.vespa.hosted.controller.versions.DeploymentStatistics; import com.yahoo.vespa.hosted.controller.versions.VersionStatus; import com.yahoo.vespa.hosted.controller.versions.VespaVersion; @@ -607,10 +608,99 @@ public class ControllerTest { tester.deployCompletely(application, applicationPackage); assertEquals(1, tester.controllerTester().nameService().records().size()); - Optional<Record> record = tester.controllerTester().nameService().findRecord(Record.Type.CNAME, RecordName.from("app1.tenant1.global.vespa.yahooapis.com")); + Optional<Record> record = tester.controllerTester().nameService().findRecord( + Record.Type.CNAME, RecordName.from("app1.tenant1.global.vespa.yahooapis.com") + ); assertTrue(record.isPresent()); assertEquals("app1.tenant1.global.vespa.yahooapis.com", record.get().name().asString()); - assertEquals("rotation-fqdn-01", record.get().value().asString()); + assertEquals("rotation-fqdn-01.", record.get().value().asString()); + } + + @Test + public void testUpdatesExistingDnsAlias() { + DeploymentTester tester = new DeploymentTester(); + + // Application 1 is deployed and deleted + { + Application app1 = tester.createApplication("app1", "tenant1", 1, 1L); + ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .environment(Environment.prod) + .globalServiceId("foo") + .region("us-west-1") + .region("us-central-1") // Two deployments should result in DNS alias being registered once + .build(); + + tester.deployCompletely(app1, applicationPackage); + assertEquals(1, tester.controllerTester().nameService().records().size()); + Optional<Record> record = tester.controllerTester().nameService().findRecord( + Record.Type.CNAME, RecordName.from("app1.tenant1.global.vespa.yahooapis.com") + ); + assertTrue(record.isPresent()); + assertEquals("app1.tenant1.global.vespa.yahooapis.com", record.get().name().asString()); + assertEquals("rotation-fqdn-01.", record.get().value().asString()); + + // Application is deleted and rotation is unassigned + applicationPackage = new ApplicationPackageBuilder() + .environment(Environment.prod) + .allow(ValidationId.deploymentRemoval) + .build(); + tester.notifyJobCompletion(component, app1, true); + tester.deployAndNotify(app1, applicationPackage, true, systemTest); + tester.applications().deactivate(app1, new Zone(Environment.test, RegionName.from("us-east-1"))); + tester.applications().deactivate(app1, new Zone(Environment.staging, RegionName.from("us-east-3"))); + tester.applications().deleteApplication(app1.id(), Optional.of(new NToken("ntoken"))); + assertTrue("Rotation is unassigned", + tester.applications().rotationRepository().availableRotations() + .contains(new RotationId("rotation-id-01"))); + + // Record remains + record = tester.controllerTester().nameService().findRecord( + Record.Type.CNAME, RecordName.from("app1.tenant1.global.vespa.yahooapis.com") + ); + assertTrue(record.isPresent()); + } + + // Application 2 is deployed and assigned same rotation as application 1 had before deletion + { + Application app2 = tester.createApplication("app2", "tenant2", 1, 1L); + ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .environment(Environment.prod) + .globalServiceId("foo") + .region("us-west-1") + .region("us-central-1") + .build(); + tester.deployCompletely(app2, applicationPackage); + assertEquals(2, tester.controllerTester().nameService().records().size()); + Optional<Record> record = tester.controllerTester().nameService().findRecord( + Record.Type.CNAME, RecordName.from("app2.tenant2.global.vespa.yahooapis.com") + ); + assertTrue(record.isPresent()); + assertEquals("app2.tenant2.global.vespa.yahooapis.com", record.get().name().asString()); + assertEquals("rotation-fqdn-01.", record.get().value().asString()); + } + + // Application 1 is recreated, deployed and assigned a new rotation + { + Application app1 = tester.createApplication("app1", "tenant1", 1, 1L); + ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .environment(Environment.prod) + .globalServiceId("foo") + .region("us-west-1") + .region("us-central-1") + .build(); + tester.deployCompletely(app1, applicationPackage); + app1 = tester.applications().require(app1.id()); + assertEquals("rotation-id-02", app1.rotation().get().id().asString()); + + // Existing DNS record is updated to point to the newly assigned rotation + assertEquals(2, tester.controllerTester().nameService().records().size()); + Optional<Record> record = tester.controllerTester().nameService().findRecord( + Record.Type.CNAME, RecordName.from("app1.tenant1.global.vespa.yahooapis.com") + ); + assertTrue(record.isPresent()); + assertEquals("rotation-fqdn-02.", record.get().value().asString()); + } + } @Test |