summaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2021-07-15 09:52:33 +0200
committerMartin Polden <mpolden@mpolden.no>2021-07-16 09:02:31 +0200
commit6a558ebb82bb58b42990ff7dad8af35c70812424 (patch)
tree95d0bf6ea0b319333b388b5d8ce32072f6cf3ab6 /controller-server
parent073c5be0df2970bd4e6d926a56d14657965992d3 (diff)
Stop using NodeRepositoryNode in patch operations
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudEventReporter.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/HostInfoUpdater.java42
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VCMRMaintainer.java14
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java139
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudEventReporterTest.java21
5 files changed, 101 insertions, 117 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudEventReporter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudEventReporter.java
index 3c5495a6bfe..a8089555ffc 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudEventReporter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudEventReporter.java
@@ -59,7 +59,7 @@ public class CloudEventReporter extends ControllerMaintainer {
if (!affects(node, event)) continue;
log.info("Retiring and deprovisioning " + node.hostname().value() + " in " + zone.getId() +
": Affected by maintenance event " + event.instanceEventId);
- nodeRepository.retireAndDeprovision(zone.getId(), node.hostname().value());
+ nodeRepository.retire(zone.getId(), node.hostname().value(), true, true);
}
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/HostInfoUpdater.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/HostInfoUpdater.java
index 10e6f9eb039..5622fcdac4e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/HostInfoUpdater.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/HostInfoUpdater.java
@@ -7,7 +7,6 @@ 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.EntityService;
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.EnumSet;
@@ -48,13 +47,24 @@ public class HostInfoUpdater extends ControllerMaintainer {
for (var node : nodeRepository.list(zone, false)) {
if (!node.type().isHost()) continue;
NodeEntity nodeEntity = nodeEntities.get(registeredHostnameOf(node));
- if (!shouldUpdateSwitch(node, nodeEntity) && !shouldUpdateModel(node, nodeEntity)) continue;
+ if (nodeEntity == null) continue;
- NodeRepositoryNode updatedNode = new NodeRepositoryNode();
- nodeEntity.switchHostname().ifPresent(updatedNode::setSwitchHostname);
- buildModelName(nodeEntity).ifPresent(updatedNode::setModelName);
- nodeRepository.patchNode(zone, node.hostname().value(), updatedNode);
- hostsUpdated++;
+ boolean updatedHost = false;
+ Optional<String> modelName = modelNameOf(nodeEntity);
+ if (modelName.isPresent() && !modelName.equals(node.modelName())) {
+ nodeRepository.updateModel(zone, node.hostname().value(), modelName.get());
+ updatedHost = true;
+ }
+
+ Optional<String> switchHostname = nodeEntity.switchHostname();
+ if (switchHostname.isPresent() && !switchHostname.equals(node.switchHostname())) {
+ nodeRepository.updateSwitchHostname(zone, node.hostname().value(), switchHostname.get());
+ updatedHost = true;
+ }
+
+ if (updatedHost) {
+ hostsUpdated++;
+ }
}
}
} finally {
@@ -65,9 +75,8 @@ public class HostInfoUpdater extends ControllerMaintainer {
return 1.0;
}
- private static Optional<String> buildModelName(NodeEntity nodeEntity) {
- if(nodeEntity.manufacturer().isEmpty() || nodeEntity.model().isEmpty())
- return Optional.empty();
+ private static Optional<String> modelNameOf(NodeEntity nodeEntity) {
+ if (nodeEntity.manufacturer().isEmpty() || nodeEntity.model().isEmpty()) return Optional.empty();
return Optional.of(nodeEntity.manufacturer().get() + " " + nodeEntity.model().get());
}
@@ -80,17 +89,4 @@ public class HostInfoUpdater extends ControllerMaintainer {
return matcher.replaceFirst("$1$2");
}
- private static boolean shouldUpdateSwitch(Node node, NodeEntity nodeEntity) {
- if (nodeEntity == null) return false;
- if (nodeEntity.switchHostname().isEmpty()) return false;
- return !node.switchHostname().equals(nodeEntity.switchHostname());
- }
-
- private static boolean shouldUpdateModel(Node node, NodeEntity nodeEntity) {
- if (nodeEntity == null) return false;
- if (nodeEntity.model().isEmpty()) return false;
- if (nodeEntity.manufacturer().isEmpty()) return false;
- return !node.modelName().equals(buildModelName(nodeEntity));
- }
-
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VCMRMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VCMRMaintainer.java
index 19617a1f293..96ba87575e4 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VCMRMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VCMRMaintainer.java
@@ -1,7 +1,6 @@
// 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.fasterxml.jackson.databind.JsonNode;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.SystemName;
@@ -10,8 +9,6 @@ import com.yahoo.text.Text;
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.noderepository.NodeRepositoryNode;
-import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeState;
import com.yahoo.vespa.hosted.controller.api.integration.vcmr.ChangeRequest;
import com.yahoo.vespa.hosted.controller.api.integration.vcmr.ChangeRequest.Impact;
import com.yahoo.vespa.hosted.controller.api.integration.vcmr.ChangeRequestClient;
@@ -201,7 +198,7 @@ public class VCMRMaintainer extends ControllerMaintainer {
if (hostAction.getState() == State.RETIRED &&
node.state() == Node.State.parked) {
logger.info("Setting " + node.hostname() + " to dirty");
- nodeRepository.setState(zoneId, NodeState.dirty, node.hostname().value());
+ nodeRepository.setState(zoneId, Node.State.dirty, node.hostname().value());
}
if (hostAction.getState() == State.RETIRING && node.wantToRetire()) {
try {
@@ -275,9 +272,7 @@ public class VCMRMaintainer extends ControllerMaintainer {
}
private void setWantToRetire(ZoneId zoneId, Node node, boolean wantToRetire) {
- var newNode = new NodeRepositoryNode();
- newNode.setWantToRetire(wantToRetire);
- nodeRepository.patchNode(zoneId, node.hostname().value(), newNode);
+ nodeRepository.retire(zoneId, node.hostname().value(), wantToRetire, false);
}
private void approveChangeRequest(VespaChangeRequest changeRequest) {
@@ -311,8 +306,7 @@ public class VCMRMaintainer extends ControllerMaintainer {
private void updateReport(ZoneId zoneId, Node node, VCMRReport report) {
logger.info(Text.format("Updating report for %s: %s", node.hostname(), report));
- var newNode = new NodeRepositoryNode();
- newNode.setReports(report.toNodeReports());
- nodeRepository.patchNode(zoneId, node.hostname().value(), newNode);
+ nodeRepository.updateReports(zoneId, node.hostname().value(), report.toNodeReports());
}
+
}
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 b16817c0f3d..2b9ba7587da 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
@@ -18,16 +18,13 @@ import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeRepoSt
import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeRepository;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.TargetVersions;
import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeRepositoryNode;
-import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeState;
import java.net.URI;
import java.time.Duration;
import java.util.Collection;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.function.UnaryOperator;
@@ -46,7 +43,7 @@ public class NodeRepositoryMock implements NodeRepository {
private final Map<DeploymentId, Pair<Double, Double>> trafficFractions = new HashMap<>();
private final Map<ZoneId, Map<TenantName, URI>> archiveUris = new HashMap<>();
- private boolean allowPatching = false;
+ private boolean allowPatching = true;
private boolean hasSpareCapacity = false;
@Override
@@ -56,40 +53,40 @@ public class NodeRepositoryMock implements NodeRepository {
@Override
public void deleteNode(ZoneId zone, String hostname) {
- throw new UnsupportedOperationException();
+ require(zone, hostname);
+ nodeRepository.get(zone).remove(HostName.from(hostname));
}
@Override
- public void setState(ZoneId zone, NodeState nodeState, String hostName) {
- var existing = list(zone, List.of(HostName.from(hostName)));
- if (existing.size() != 1) throw new IllegalArgumentException("Node " + hostName + " not found in " + zone);
-
- var node = Node.builder(existing.get(0))
- .state(Node.State.valueOf(nodeState.name()))
- .build();
+ public void setState(ZoneId zone, Node.State nodeState, String hostname) {
+ Node node = Node.builder(require(zone, hostname))
+ .state(Node.State.valueOf(nodeState.name()))
+ .build();
putNodes(zone, node);
}
@Override
public Node getNode(ZoneId zone, String hostname) {
- throw new UnsupportedOperationException();
+ return require(zone, hostname);
}
@Override
public List<Node> list(ZoneId zone, boolean includeDeprovisioned) {
- return List.copyOf(nodeRepository.getOrDefault(zone, Map.of()).values());
+ return nodeRepository.getOrDefault(zone, Map.of()).values().stream()
+ .filter(node -> includeDeprovisioned || node.state() != Node.State.deprovisioned)
+ .collect(Collectors.toList());
}
@Override
public List<Node> list(ZoneId zone, ApplicationId application) {
- return nodeRepository.getOrDefault(zone, Collections.emptyMap()).values().stream()
+ return nodeRepository.getOrDefault(zone, Map.of()).values().stream()
.filter(node -> node.owner().map(application::equals).orElse(false))
.collect(Collectors.toList());
}
@Override
public List<Node> list(ZoneId zone, List<HostName> hostnames) {
- return nodeRepository.getOrDefault(zone, Collections.emptyMap()).values().stream()
+ return nodeRepository.getOrDefault(zone, Map.of()).values().stream()
.filter(node -> hostnames.contains(node.hostname()))
.collect(Collectors.toList());
}
@@ -179,42 +176,36 @@ public class NodeRepositoryMock implements NodeRepository {
}
@Override
- public void retireAndDeprovision(ZoneId zoneId, String hostName) {
- nodeRepository.get(zoneId).remove(HostName.from(hostName));
+ public void retire(ZoneId zone, String hostname, boolean wantToRetire, boolean wantToDeprovision) {
+ patchNodes(zone, hostname, (node) -> Node.builder(node).wantToRetire(wantToRetire).wantToDeprovision(wantToDeprovision).build());
}
@Override
- public void patchNode(ZoneId zoneId, String hostName, NodeRepositoryNode node) {
- 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, modelName and wantToRetire
- Node.Builder newNode = Node.builder(existing.get(0));
- if (node.getSwitchHostname() != null)
- newNode.switchHostname(node.getSwitchHostname());
- if (node.getModelName() != null)
- newNode.modelName(node.getModelName());
- if (node.getWantToRetire() != null)
- newNode.wantToRetire(node.getWantToRetire());
-
- Map<String, String> reports = new HashMap<>();
- for (var kv : node.getReports().entrySet()) {
- if (kv.getValue() == null) continue; // Null value clears a report
- reports.put(kv.getKey(), kv.getValue().toString());
- }
- newNode.reports(reports);
+ public void updateReports(ZoneId zone, String hostname, Map<String, String> reports) {
+ Map<String, String> trimmedReports = reports.entrySet().stream()
+ // Null value clears a report
+ .filter(kv -> kv.getValue() != null)
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ patchNodes(zone, hostname, (node) -> Node.builder(node).reports(trimmedReports).build());
+ }
- putNodes(zoneId, newNode.build());
+ @Override
+ public void updateModel(ZoneId zone, String hostname, String modelName) {
+ patchNodes(zone, hostname, (node) -> Node.builder(node).modelName(modelName).build());
+ }
+
+ @Override
+ public void updateSwitchHostname(ZoneId zone, String hostname, String switchHostname) {
+ patchNodes(zone, hostname, (node) -> Node.builder(node).switchHostname(switchHostname).build());
}
@Override
- public void reboot(ZoneId zoneId, String hostName) {
+ public void reboot(ZoneId zone, String hostname) {
throw new UnsupportedOperationException();
}
@Override
- public boolean isReplaceable(ZoneId zoneId, List<HostName> hostNames) {
+ public boolean isReplaceable(ZoneId zone, List<HostName> hostnames) {
return hasSpareCapacity;
}
@@ -250,14 +241,6 @@ public class NodeRepositoryMock implements NodeRepository {
nodeRepository.clear();
}
- public Node require(HostName hostName) {
- return nodeRepository.values().stream()
- .map(zoneNodes -> zoneNodes.get(hostName))
- .filter(Objects::nonNull)
- .findFirst()
- .orElseThrow(() -> new NoSuchElementException("No node with the hostname " + hostName + " is known."));
- }
-
/** Add a fixed set of nodes to given zone */
public void addFixedNodes(ZoneId zone) {
var nodeA = Node.builder()
@@ -300,8 +283,7 @@ public class NodeRepositoryMock implements NodeRepository {
}
public void doUpgrade(DeploymentId deployment, Optional<HostName> hostName, Version version) {
- modifyNodes(deployment, hostName, node -> {
- assert node.wantedVersion().equals(version);
+ patchNodes(deployment, hostName, node -> {
return Node.builder(node)
.currentVersion(version)
.currentDockerImage(node.wantedDockerImage())
@@ -309,38 +291,20 @@ public class NodeRepositoryMock implements NodeRepository {
});
}
- private void modifyNodes(DeploymentId deployment, Optional<HostName> hostname, UnaryOperator<Node> modification) {
- List<Node> nodes = hostname.map(this::require)
- .map(Collections::singletonList)
- .orElse(list(deployment.zoneId(), deployment.applicationId()));
- putNodes(deployment.zoneId(),
- nodes.stream().map(modification).collect(Collectors.toList()));
- }
-
public void requestRestart(DeploymentId deployment, Optional<HostName> hostname) {
- modifyNodes(deployment, hostname, node -> Node.builder(node).wantedRestartGeneration(node.wantedRestartGeneration() + 1).build());
+ patchNodes(deployment, hostname, node -> Node.builder(node).wantedRestartGeneration(node.wantedRestartGeneration() + 1).build());
}
public void doRestart(DeploymentId deployment, Optional<HostName> hostname) {
- modifyNodes(deployment, hostname, node -> Node.builder(node).restartGeneration(node.restartGeneration() + 1).build());
+ patchNodes(deployment, hostname, node -> Node.builder(node).restartGeneration(node.restartGeneration() + 1).build());
}
public void requestReboot(DeploymentId deployment, Optional<HostName> hostname) {
- modifyNodes(deployment, hostname, node -> Node.builder(node).wantedRebootGeneration(node.wantedRebootGeneration() + 1).build());
+ patchNodes(deployment, hostname, node -> Node.builder(node).wantedRebootGeneration(node.wantedRebootGeneration() + 1).build());
}
public void doReboot(DeploymentId deployment, Optional<HostName> hostname) {
- modifyNodes(deployment, hostname, node -> Node.builder(node).rebootGeneration(node.rebootGeneration() + 1).build());
- }
-
- public void addReport(ZoneId zoneId, HostName hostName, String reportId, String report) {
- Node node = nodeRepository.getOrDefault(zoneId, Map.of()).get(hostName);
- if (node == null) throw new IllegalArgumentException("No node named " + hostName + " in " + zoneId);
-
- Map<String, String> reports = new HashMap<>(node.reports());
- reports.put(reportId, report);
- Node newNode = Node.builder(node).reports(reports).build();
- putNodes(zoneId, newNode);
+ patchNodes(deployment, hostname, node -> Node.builder(node).rebootGeneration(node.rebootGeneration() + 1).build());
}
public NodeRepositoryMock allowPatching(boolean allowPatching) {
@@ -352,4 +316,33 @@ public class NodeRepositoryMock implements NodeRepository {
this.hasSpareCapacity = hasSpareCapacity;
}
+ private Node require(ZoneId zone, String hostname) {
+ return require(zone, HostName.from(hostname));
+ }
+
+ private Node require(ZoneId zone, HostName hostname) {
+ Node node = nodeRepository.getOrDefault(zone, Map.of()).get(hostname);
+ if (node == null) throw new IllegalArgumentException("Node not found in " + zone + ": " + hostname);
+ return node;
+ }
+
+ private void patchNodes(ZoneId zone, String hostname, UnaryOperator<Node> patcher) {
+ patchNodes(zone, Optional.of(HostName.from(hostname)), patcher);
+ }
+
+ private void patchNodes(DeploymentId deployment, Optional<HostName> hostname, UnaryOperator<Node> patcher) {
+ patchNodes(deployment.zoneId(), hostname, patcher);
+ }
+
+ private void patchNodes(ZoneId zone, Optional<HostName> hostname, UnaryOperator<Node> patcher) {
+ if (!allowPatching) throw new UnsupportedOperationException("Patching is disabled in this mock");
+ List<Node> nodes;
+ if (hostname.isPresent()) {
+ nodes = List.of(require(zone, hostname.get()));
+ } else {
+ nodes = list(zone, false);
+ }
+ putNodes(zone, nodes.stream().map(patcher).collect(Collectors.toList()));
+ }
+
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudEventReporterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudEventReporterTest.java
index e838a693be1..bb10f29de72 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudEventReporterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudEventReporterTest.java
@@ -42,15 +42,15 @@ public class CloudEventReporterTest {
public void maintain() {
setUpZones();
CloudEventReporter cloudEventReporter = new CloudEventReporter(tester.controller(), Duration.ofMinutes(15));
- assertEquals(Set.of("host1.com", "host2.com", "host3.com"), getHostnames(unsupportedZone.getId()));
- assertEquals(Set.of("host1.com", "host2.com", "host3.com"), getHostnames(zone1.getId()));
- assertEquals(Set.of("host4.com", "host5.com", "confighost.com"), getHostnames(zone2.getId()));
+ assertEquals(Set.of("host1.com", "host2.com", "host3.com"), hostsNotDeprovisioning(unsupportedZone.getId()));
+ assertEquals(Set.of("host1.com", "host2.com", "host3.com"), hostsNotDeprovisioning(zone1.getId()));
+ assertEquals(Set.of("host4.com", "host5.com", "confighost.com"), hostsNotDeprovisioning(zone2.getId()));
mockEvents();
cloudEventReporter.maintain();
- assertEquals(Set.of("host1.com", "host2.com", "host3.com"), getHostnames(unsupportedZone.getId()));
- assertEquals(Set.of("host3.com"), getHostnames(zone1.getId()));
- assertEquals(Set.of("host4.com"), getHostnames(zone2.getId()));
+ assertEquals(Set.of("host1.com", "host2.com", "host3.com"), hostsNotDeprovisioning(unsupportedZone.getId()));
+ assertEquals(Set.of("host3.com"), hostsNotDeprovisioning(zone1.getId()));
+ assertEquals(Set.of("host4.com"), hostsNotDeprovisioning(zone2.getId()));
}
private void mockEvents() {
@@ -127,11 +127,12 @@ public class CloudEventReporterTest {
.build();
}
- private Set<String> getHostnames(ZoneId zoneId) {
+ private Set<String> hostsNotDeprovisioning(ZoneId zoneId) {
return tester.configServer().nodeRepository().list(zoneId, false)
- .stream()
- .map(node -> node.hostname().value())
- .collect(Collectors.toSet());
+ .stream()
+ .filter(node -> !node.wantToDeprovision())
+ .map(node -> node.hostname().value())
+ .collect(Collectors.toSet());
}
private ZoneApiMock createZone(String zoneId, String cloudNativeRegionName, String cloud) {