summaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorØyvind Grønnesby <oyving@verizonmedia.com>2020-12-03 19:33:21 +0100
committerØyvind Grønnesby <oyving@verizonmedia.com>2020-12-03 19:33:21 +0100
commit2b01b49d4f9fa0338c00a113daf7e61e10041646 (patch)
tree2c970f2cae118746977147fbaca49e5f0c02c09a /controller-server
parentf0771b2201f06684f66c7261c528167adb8f4c7b (diff)
parent5ad34990803dc859ee107ba46b3e60ef877898ca (diff)
Merge remote-tracking branch 'origin/master' into ogronnesby/trial-tenant-limit
Conflicts: controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java flags/src/main/java/com/yahoo/vespa/flags/Flags.java
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java20
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/HostSwitchUpdater.java80
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/JobControlFlags.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java7
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java16
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/HostSwitchUpdaterTest.java110
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java7
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java5
14 files changed, 239 insertions, 30 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
index ed1e442f266..0d47d738bed 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
@@ -184,9 +184,7 @@ public class InternalStepRunner implements StepRunner {
}
private Optional<RunStatus> deployReal(RunId id, boolean setTheStage, DualLogger logger) {
- return deploy(id.application(),
- id.type(),
- () -> controller.applications().deploy2(id.job(), setTheStage),
+ return deploy(() -> controller.applications().deploy2(id.job(), setTheStage),
controller.jobController().run(id).get()
.stepInfo(setTheStage ? deployInitialReal : deployReal).get()
.startTime().get(),
@@ -196,9 +194,7 @@ public class InternalStepRunner implements StepRunner {
private Optional<RunStatus> deployTester(RunId id, DualLogger logger) {
Version platform = testerPlatformVersion(id);
logger.log("Deploying the tester container on platform " + platform + " ...");
- return deploy(id.tester().id(),
- id.type(),
- () -> controller.applications().deployTester(id.tester(),
+ return deploy(() -> controller.applications().deployTester(id.tester(),
testerPackage(id),
id.type().zone(controller.system()),
platform),
@@ -208,8 +204,7 @@ public class InternalStepRunner implements StepRunner {
logger);
}
- private Optional<RunStatus> deploy(ApplicationId id, JobType type, Supplier<ActivateResult> deployment,
- Instant startTime, DualLogger logger) {
+ private Optional<RunStatus> deploy(Supplier<ActivateResult> deployment, Instant startTime, DualLogger logger) {
try {
PrepareResponse prepareResponse = deployment.get().prepareResponse();
if (prepareResponse.log != null)
@@ -232,9 +227,9 @@ public class InternalStepRunner implements StepRunner {
? Optional.of(deploymentFailed) : Optional.empty();
switch (e.getErrorCode()) {
case CERTIFICATE_NOT_READY:
- logger.log("Waiting for provisioned web certificate — new application, or old one has expired");
+ logger.log("Waiting for certificate to become ready on config server: New application, or old one has expired");
if (startTime.plus(timeouts.endpointCertificate()).isBefore(controller.clock().instant())) {
- logger.log("Deployment failed to find provisioned endpoint certificate after " + timeouts.endpointCertificate());
+ logger.log("Certificate did not become available on config server within (" + timeouts.endpointCertificate() + ")");
return Optional.of(RunStatus.endpointCertificateTimeout);
}
return result;
@@ -264,9 +259,10 @@ public class InternalStepRunner implements StepRunner {
switch (e.type()) {
case CERT_NOT_AVAILABLE:
// Same as CERTIFICATE_NOT_READY above, only from the controller
- logger.log("Waiting for provisioned web certificate — new application, or old one has expired");
+ logger.log("Waiting for certificate to become valid: New application, or old one has expired");
if (startTime.plus(timeouts.endpointCertificate()).isBefore(controller.clock().instant())) {
- logger.log("Deployment failed to find provisioned endpoint certificate after " + timeouts.endpointCertificate());
+ logger.log("Controller could not validate certificate within " +
+ timeouts.endpointCertificate() + ": " + Exceptions.toMessageString(e));
return Optional.of(RunStatus.endpointCertificateTimeout);
}
return Optional.empty();
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..8e7a364b5f3
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/HostSwitchUpdater.java
@@ -0,0 +1,80 @@
+// 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.config.provision.SystemName;
+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.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;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+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 static final Logger LOG = Logger.getLogger(HostSwitchUpdater.class.getName());
+ private static final Pattern HOST_PATTERN = Pattern.compile("^(proxy|cfg|controller)host(.+)$");
+
+ private final NodeRepository nodeRepository;
+
+ public HostSwitchUpdater(Controller controller, Duration interval) {
+ super(controller, interval, null, EnumSet.of(SystemName.cd, SystemName.main));
+ 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()));
+ int nodesUpdated = 0;
+ try {
+ for (var zone : controller().zoneRegistry().zones().controllerUpgraded().all().ids()) {
+ for (var node : nodeRepository.list(zone)) {
+ if (!node.type().isHost()) continue;
+ NodeEntity nodeEntity = nodeEntities.get(registeredHostnameOf(node));
+ if (!shouldUpdate(node, nodeEntity)) continue;
+
+ NodeRepositoryNode updatedNode = new NodeRepositoryNode();
+ updatedNode.setSwitchHostname(nodeEntity.switchHostname().get());
+ nodeRepository.patchNode(zone, node.hostname().value(), updatedNode);
+ nodesUpdated++;
+ }
+ }
+ } finally {
+ if (nodesUpdated > 0) {
+ LOG.info("Updated switch hostname for " + nodesUpdated + " node(s)");
+ }
+ }
+ return true;
+ }
+
+ /** Returns the hostname that given host is registered under in the {@link EntityService} */
+ private static String registeredHostnameOf(Node host) {
+ String hostname = host.hostname().value();
+ if (!host.type().isHost()) return hostname;
+ Matcher matcher = HOST_PATTERN.matcher(hostname);
+ if (!matcher.matches()) return hostname;
+ return matcher.replaceFirst("$1$2");
+ }
+
+ private static boolean shouldUpdate(Node node, NodeEntity nodeEntity) {
+ if (nodeEntity == null) return false;
+ if (nodeEntity.switchHostname().isEmpty()) return false;
+ return !node.switchHostname().equals(nodeEntity.switchHostname());
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/JobControlFlags.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/JobControlFlags.java
index 1cb23d0c515..109e761f925 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/JobControlFlags.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/JobControlFlags.java
@@ -4,8 +4,8 @@ package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.concurrent.maintenance.JobControlState;
import com.yahoo.transaction.Mutex;
import com.yahoo.vespa.flags.FlagSource;
-import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.flags.ListFlag;
+import com.yahoo.vespa.flags.PermanentFlags;
import java.util.Set;
@@ -21,7 +21,7 @@ public class JobControlFlags implements JobControlState {
public JobControlFlags(CuratorDb curator, FlagSource flagSource) {
this.curator = curator;
- this.inactiveJobsFlag = Flags.INACTIVE_MAINTENANCE_JOBS.bindTo(flagSource);
+ this.inactiveJobsFlag = PermanentFlags.INACTIVE_MAINTENANCE_JOBS.bindTo(flagSource);
}
@Override
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
index 4a6d6e2e72d..cbf6307f5e6 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
@@ -1576,7 +1576,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
.forEach(cluster -> {
Cursor clusterObject = clustersArray.addObject();
clusterObject.setString("name", cluster.getKey());
- setStatus(clusterObject.setObject("status"), cluster.getValue().common());
+ cluster.getValue().common().ifPresent(common -> setStatus(clusterObject.setObject("status"), common));
Cursor pendingArray = clusterObject.setArray("pending");
cluster.getValue().pending().entrySet().stream().sorted(comparingByKey())
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java
index 954582a8ec7..e097b82b7d0 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java
@@ -23,6 +23,7 @@ import com.yahoo.vespa.flags.FetchVector;
import com.yahoo.vespa.flags.FlagSource;
import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.flags.IntFlag;
+import com.yahoo.vespa.flags.PermanentFlags;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.LockedTenant;
import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId;
@@ -75,7 +76,7 @@ public class UserApiHandler extends LoggingRequestHandler {
super(parentCtx);
this.users = users;
this.controller = controller;
- this.enable_public_signup_flow = Flags.ENABLE_PUBLIC_SIGNUP_FLOW.bindTo(flagSource);
+ this.enable_public_signup_flow = PermanentFlags.ENABLE_PUBLIC_SIGNUP_FLOW.bindTo(flagSource);
this.maxTrialTenants = Flags.MAX_TRIAL_TENANTS.bindTo(flagSource);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java
index 1f8c44ea82d..d37e1e05030 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java
@@ -8,6 +8,7 @@ import com.yahoo.vespa.flags.FetchVector;
import com.yahoo.vespa.flags.FlagSource;
import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.flags.IntFlag;
+import com.yahoo.vespa.flags.PermanentFlags;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.api.integration.ServiceRegistry;
import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingController;
@@ -26,7 +27,9 @@ import javax.ws.rs.ForbiddenException;
import java.util.List;
import java.util.stream.Collectors;
-import static com.yahoo.vespa.hosted.controller.api.role.RoleDefinition.*;
+import static com.yahoo.vespa.hosted.controller.api.role.RoleDefinition.administrator;
+import static com.yahoo.vespa.hosted.controller.api.role.RoleDefinition.hostedOperator;
+import static com.yahoo.vespa.hosted.controller.api.role.RoleDefinition.hostedSupporter;
/**
* @author jonmv
@@ -42,7 +45,7 @@ public class CloudAccessControl implements AccessControl {
@Inject
public CloudAccessControl(UserManagement userManagement, FlagSource flagSource, ServiceRegistry serviceRegistry) {
this.userManagement = userManagement;
- this.enablePublicSignup = Flags.ENABLE_PUBLIC_SIGNUP_FLOW.bindTo(flagSource);
+ this.enablePublicSignup = PermanentFlags.ENABLE_PUBLIC_SIGNUP_FLOW.bindTo(flagSource);
this.maxTrialTenants = Flags.MAX_TRIAL_TENANTS.bindTo(flagSource);
billingController = serviceRegistry.billingController();
}
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..4dcacb3934b
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/HostSwitchUpdaterTest.java
@@ -0,0 +1,110 @@
+// 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.config.provision.HostName;
+import com.yahoo.config.provision.NodeType;
+import com.yahoo.config.provision.zone.ZoneId;
+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;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @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();
+ assertEquals(newSwitch, getNode(host.hostname(), tester).switchHostname().get());
+
+ // Host keeps old switch hostname if removed from the node entity
+ nodeEntity = new NodeEntity(host.hostname().value(), "", "", "");
+ tester.serviceRegistry().entityService().addNodeEntity(nodeEntity);
+ maintainer.maintain();
+ assertEquals(newSwitch, getNode(host.hostname(), tester).switchHostname().get());
+
+ // Updates node registered under a different hostname
+ ZoneId zone = tester.zoneRegistry().zones().controllerUpgraded().all().ids().get(0);
+ String hostnameSuffix = ".prod." + zone.value();
+ Node configNode = new Node.Builder().hostname(HostName.from("cfg3" + hostnameSuffix))
+ .type(NodeType.config)
+ .build();
+ Node configHost = new Node.Builder().hostname(HostName.from("cfghost3" + hostnameSuffix))
+ .type(NodeType.confighost)
+ .build();
+ tester.serviceRegistry().configServer().nodeRepository().putNodes(zone, List.of(configNode, configHost));
+ String switchHostname = switchHostname(configHost);
+ NodeEntity configNodeEntity = new NodeEntity("cfg3" + hostnameSuffix, "", "", switchHostname);
+ tester.serviceRegistry().entityService().addNodeEntity(configNodeEntity);
+ maintainer.maintain();
+ assertEquals(switchHostname, getNode(configHost.hostname(), tester).switchHostname().get());
+ assertTrue("Switch hostname is not set for non-host", getNode(configNode.hostname(), tester).switchHostname().isEmpty());
+ }
+
+ private static Node getNode(HostName hostname, ControllerTester tester) {
+ return allNodes(tester).stream()
+ .filter(node -> node.hostname().equals(hostname))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("No such node: " + hostname));
+ }
+
+ 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 String switchHostname(Node node) {
+ return "tor-" + node.hostname().value();
+ }
+
+ private static void addNodeEntities(ControllerTester tester) {
+ for (var node : allNodes(tester)) {
+ if (!node.type().isHost()) continue;
+ NodeEntity nodeEntity = new NodeEntity(node.hostname().value(), "", "", switchHostname(node));
+ tester.serviceRegistry().entityService().addNodeEntity(nodeEntity);
+ }
+ }
+
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java
index 1da7bdeca19..f327851f902 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java
@@ -2,10 +2,9 @@
package com.yahoo.vespa.hosted.controller.restapi.application;
import com.yahoo.config.provision.ApplicationName;
-import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.TenantName;
-import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.flags.InMemoryFlagSource;
+import com.yahoo.vespa.flags.PermanentFlags;
import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId;
import com.yahoo.vespa.hosted.controller.api.role.Role;
@@ -25,8 +24,8 @@ import java.util.Collections;
import java.util.Set;
import static com.yahoo.application.container.handler.Request.Method.GET;
-import static com.yahoo.application.container.handler.Request.Method.PUT;
import static com.yahoo.application.container.handler.Request.Method.POST;
+import static com.yahoo.application.container.handler.Request.Method.PUT;
import static com.yahoo.vespa.hosted.controller.restapi.application.ApplicationApiTest.createApplicationSubmissionData;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -45,7 +44,7 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest {
public void before() {
tester = new ContainerTester(container, responseFiles);
((InMemoryFlagSource) tester.controller().flagSource())
- .withBooleanFlag(Flags.ENABLE_PUBLIC_SIGNUP_FLOW.id(), true);
+ .withBooleanFlag(PermanentFlags.ENABLE_PUBLIC_SIGNUP_FLOW.id(), true);
deploymentTester = new DeploymentTester(new ControllerTester(tester));
deploymentTester.controllerTester().computeVersionStatus();
setupTenantAndApplication();
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java
index c061bfed21a..5523ce8dd9f 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java
@@ -6,8 +6,8 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.test.ManualClock;
-import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.flags.InMemoryFlagSource;
+import com.yahoo.vespa.flags.PermanentFlags;
import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceSnapshot;
import com.yahoo.vespa.hosted.controller.auditlog.AuditLogger;
import com.yahoo.vespa.hosted.controller.restapi.ContainerTester;
@@ -42,7 +42,7 @@ public class ControllerApiTest extends ControllerContainerTest {
public void testControllerApi() {
tester.assertResponse(authenticatedRequest("http://localhost:8080/controller/v1/", "", Request.Method.GET), new File("root.json"));
- ((InMemoryFlagSource) tester.controller().flagSource()).withListFlag(Flags.INACTIVE_MAINTENANCE_JOBS.id(), List.of("DeploymentExpirer"), String.class);
+ ((InMemoryFlagSource) tester.controller().flagSource()).withListFlag(PermanentFlags.INACTIVE_MAINTENANCE_JOBS.id(), List.of("DeploymentExpirer"), String.class);
// GET a list of all maintenance jobs
tester.assertResponse(authenticatedRequest("http://localhost:8080/controller/v1/maintenance/", "", Request.Method.GET),
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"
},
{
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java
index 0ed30871ded..547ce769e87 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java
@@ -4,8 +4,8 @@ package com.yahoo.vespa.hosted.controller.restapi.user;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.TenantName;
-import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.flags.InMemoryFlagSource;
+import com.yahoo.vespa.flags.PermanentFlags;
import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.api.integration.user.User;
import com.yahoo.vespa.hosted.controller.api.role.Role;
@@ -19,7 +19,6 @@ import java.util.Set;
import static com.yahoo.application.container.handler.Request.Method.DELETE;
import static com.yahoo.application.container.handler.Request.Method.POST;
-import static com.yahoo.application.container.handler.Request.Method.PUT;
import static org.junit.Assert.assertEquals;
/**
@@ -203,7 +202,7 @@ public class UserApiTest extends ControllerContainerCloudTest {
public void userMetadataTest() {
ContainerTester tester = new ContainerTester(container, responseFiles);
((InMemoryFlagSource) tester.controller().flagSource())
- .withBooleanFlag(Flags.ENABLE_PUBLIC_SIGNUP_FLOW.id(), true);
+ .withBooleanFlag(PermanentFlags.ENABLE_PUBLIC_SIGNUP_FLOW.id(), true);
ControllerTester controller = new ControllerTester(tester);
Set<Role> operator = Set.of(Role.hostedOperator(), Role.hostedSupporter(), Role.hostedAccountant());
User user = new User("dev@domail", "Joe Developer", "dev", null);