diff options
author | Martin Polden <mpolden@mpolden.no> | 2020-12-02 11:16:47 +0100 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2020-12-02 12:17:02 +0100 |
commit | 3d33f25b291d4a4d7ba44847da85ef9ad1f5b62f (patch) | |
tree | 8750cbf8be4c4829f3aa9d45429de58df84e8d5d /controller-server | |
parent | 015ef9e6753931c0edffe80721e6647119950e9b (diff) |
Add host switch updater
Diffstat (limited to 'controller-server')
6 files changed, 149 insertions, 3 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java index 82f37c9bc93..be4c889cd75 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java @@ -52,6 +52,7 @@ public class ControllerMaintenance extends AbstractComponent { private final ApplicationMetaDataGarbageCollector applicationMetaDataGarbageCollector; private final HostRepairMaintainer hostRepairMaintainer; private final ContainerImageExpirer containerImageExpirer; + private final HostSwitchUpdater hostSwitchUpdater; @Inject @SuppressWarnings("unused") // instantiated by Dependency Injection @@ -81,6 +82,7 @@ public class ControllerMaintenance extends AbstractComponent { applicationMetaDataGarbageCollector = new ApplicationMetaDataGarbageCollector(controller, intervals.applicationMetaDataGarbageCollector); hostRepairMaintainer = new HostRepairMaintainer(controller, intervals.hostRepairMaintainer); containerImageExpirer = new ContainerImageExpirer(controller, intervals.containerImageExpirer); + hostSwitchUpdater = new HostSwitchUpdater(controller, intervals.hostSwitchUpdater); } public Upgrader upgrader() { return upgrader; } @@ -111,6 +113,7 @@ public class ControllerMaintenance extends AbstractComponent { applicationMetaDataGarbageCollector.close(); hostRepairMaintainer.close(); containerImageExpirer.close(); + hostSwitchUpdater.close(); } /** Create one OS upgrader per cloud found in the zone registry of controller */ @@ -148,6 +151,7 @@ public class ControllerMaintenance extends AbstractComponent { private final Duration applicationMetaDataGarbageCollector; private final Duration hostRepairMaintainer; private final Duration containerImageExpirer; + private final Duration hostSwitchUpdater; public Intervals(SystemName system) { this.system = Objects.requireNonNull(system); @@ -170,6 +174,7 @@ public class ControllerMaintenance extends AbstractComponent { this.applicationMetaDataGarbageCollector = duration(12, HOURS); this.hostRepairMaintainer = duration(12, HOURS); this.containerImageExpirer = duration(2, HOURS); + this.hostSwitchUpdater = duration(12, HOURS); } private Duration duration(long amount, TemporalUnit unit) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/HostSwitchUpdater.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/HostSwitchUpdater.java new file mode 100644 index 00000000000..35b1407441e --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/HostSwitchUpdater.java @@ -0,0 +1,52 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.maintenance; + +import com.yahoo.vespa.hosted.controller.Controller; +import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node; +import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeRepository; +import com.yahoo.vespa.hosted.controller.api.integration.entity.NodeEntity; +import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeRepositoryNode; + +import java.time.Duration; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Ensures that the switch information for all hosts is up to date. + * + * @author mpolden + */ +public class HostSwitchUpdater extends ControllerMaintainer { + + private final NodeRepository nodeRepository; + + public HostSwitchUpdater(Controller controller, Duration interval) { + super(controller, interval); + this.nodeRepository = controller.serviceRegistry().configServer().nodeRepository(); + } + + @Override + protected boolean maintain() { + Map<String, NodeEntity> nodeEntities = controller().serviceRegistry().entityService().listNodes().stream() + .collect(Collectors.toMap(NodeEntity::hostname, + Function.identity())); + for (var zone : controller().zoneRegistry().zones().controllerUpgraded().all().ids()) { + for (var node : nodeRepository.list(zone)) { + NodeEntity nodeEntity = nodeEntities.get(node.hostname().value()); + if (!shouldUpdate(node, nodeEntity)) continue; + + NodeRepositoryNode updatedNode = new NodeRepositoryNode(); + updatedNode.setSwitchHostname(nodeEntity.switchHostname().orElse(null)); + nodeRepository.patchNode(zone, node.hostname().value(), updatedNode); + } + } + return true; + } + + private static boolean shouldUpdate(Node node, NodeEntity nodeEntity) { + if (nodeEntity == null) return false; + return !node.switchHostname().equals(nodeEntity.switchHostname()); + } + +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java index 61bd7858da2..ca478905893 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java @@ -41,6 +41,8 @@ public class NodeRepositoryMock implements NodeRepository { private final Map<ZoneId, TargetVersions> targetVersions = new HashMap<>(); private final Map<Integer, Duration> osUpgradeBudgets = new HashMap<>(); + private boolean allowPatching = false; + /** Add or update given nodes in zone */ public void putNodes(ZoneId zone, List<Node> nodes) { nodeRepository.putIfAbsent(zone, new HashMap<>()); @@ -230,7 +232,14 @@ public class NodeRepositoryMock implements NodeRepository { @Override public void patchNode(ZoneId zoneId, String hostName, NodeRepositoryNode node) { - throw new UnsupportedOperationException(); + if (!allowPatching) throw new UnsupportedOperationException(); + List<Node> existing = list(zoneId, List.of(HostName.from(hostName))); + if (existing.size() != 1) throw new IllegalArgumentException("Node " + hostName + " not found in " + zoneId); + + // Note: Only supports switchHostname + Node newNode = new Node.Builder(existing.get(0)).switchHostname(node.getSwitchHostname()) + .build(); + putNodes(zoneId, newNode); } @Override @@ -280,4 +289,9 @@ public class NodeRepositoryMock implements NodeRepository { nodeRepository.get(zoneId).get(hostName).reports().put(reportId, report); } + public NodeRepositoryMock allowPatching(boolean allowPatching) { + this.allowPatching = allowPatching; + return this; + } + } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java index ea31667d249..96eb4b39510 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java @@ -15,7 +15,6 @@ import com.yahoo.vespa.hosted.controller.api.integration.aws.ResourceTagger; import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingController; import com.yahoo.vespa.hosted.controller.api.integration.billing.MockBillingController; import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateMock; -import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServer; import com.yahoo.vespa.hosted.controller.api.integration.dns.MemoryNameService; import com.yahoo.vespa.hosted.controller.api.integration.entity.MemoryEntityService; import com.yahoo.vespa.hosted.controller.api.integration.organization.MockContactRetriever; @@ -81,7 +80,7 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg } @Override - public ConfigServer configServer() { + public ConfigServerMock configServer() { return configServerMock; } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/HostSwitchUpdaterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/HostSwitchUpdaterTest.java new file mode 100644 index 00000000000..1688141fe2d --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/HostSwitchUpdaterTest.java @@ -0,0 +1,73 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.maintenance; + +import com.yahoo.vespa.hosted.controller.ControllerTester; +import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node; +import com.yahoo.vespa.hosted.controller.api.integration.entity.NodeEntity; +import org.junit.Test; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +/** + * @author mpolden + */ +public class HostSwitchUpdaterTest { + + @Test + public void maintain() { + ControllerTester tester = new ControllerTester(); + tester.serviceRegistry().configServer().nodeRepository().allowPatching(true); + addNodeEntities(tester); + + // First iteration patches all hosts + HostSwitchUpdater maintainer = new HostSwitchUpdater(tester.controller(), Duration.ofDays(1)); + maintainer.maintain(); + List<Node> nodes = allNodes(tester); + assertFalse(nodes.isEmpty()); + for (var node : nodes) { + assertEquals("Node " + node.hostname().value() + (node.type().isHost() ? " has" : " does not have") + + " switch hostname", node.type().isHost(), node.switchHostname().isPresent()); + if (node.type().isHost()) { + assertEquals("tor-" + node.hostname().value(), node.switchHostname().get()); + } + } + + // Second iteration does not patch anything as all switch information is current + tester.serviceRegistry().configServer().nodeRepository().allowPatching(false); + maintainer.maintain(); + + // One host is moved to a different switch + Node host = allNodes(tester).stream().filter(node -> node.type().isHost()).findFirst().get(); + String newSwitch = "tor2-" + host.hostname().value(); + NodeEntity nodeEntity = new NodeEntity(host.hostname().value(), "", "", newSwitch); + tester.serviceRegistry().entityService().addNodeEntity(nodeEntity); + + // Host is updated + tester.serviceRegistry().configServer().nodeRepository().allowPatching(true); + maintainer.maintain(); + Node updatedHost = allNodes(tester).stream().filter(node -> node.hostname().equals(host.hostname())).findFirst().get(); + assertEquals(newSwitch, updatedHost.switchHostname().get()); + } + + private static List<Node> allNodes(ControllerTester tester) { + List<Node> nodes = new ArrayList<>(); + for (var zone : tester.zoneRegistry().zones().controllerUpgraded().all().ids()) { + nodes.addAll(tester.serviceRegistry().configServer().nodeRepository().list(zone)); + } + return nodes; + } + + private static void addNodeEntities(ControllerTester tester) { + for (var node : allNodes(tester)) { + if (!node.type().isHost()) continue; + NodeEntity nodeEntity = new NodeEntity(node.hostname().value(), "", "", "tor-" + node.hostname().value()); + tester.serviceRegistry().entityService().addNodeEntity(nodeEntity); + } + } + +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json index c1ee1489cd4..5ab087aab17 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json @@ -34,6 +34,9 @@ "name": "HostRepairMaintainer" }, { + "name": "HostSwitchUpdater" + }, + { "name": "JobRunner" }, { |