diff options
author | Valerij Fredriksen <freva@users.noreply.github.com> | 2020-03-09 09:33:20 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-03-09 09:33:20 +0100 |
commit | bbc74ec90bcba32b2de15b3cb5f17b0ed221ca33 (patch) | |
tree | 147fa5b15eb1363c22e443caf6d8018693cb2e20 | |
parent | 238581e9723fd2340192e7cd48c452286a25cd6b (diff) | |
parent | ab0fc576e409c72c21d14ce7d3e473b4d4d7005a (diff) |
Merge pull request #12500 from vespa-engine/hakonhall/avoid-building-lots-of-applicationinstances
Avoid building lots of ApplicationInstances
12 files changed, 159 insertions, 97 deletions
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/OrchestratorMock.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/OrchestratorMock.java index 837cbd4605a..88d486cef87 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/application/OrchestratorMock.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/OrchestratorMock.java @@ -2,6 +2,7 @@ package com.yahoo.vespa.config.server.application; import com.yahoo.config.provision.ApplicationId; +import com.yahoo.vespa.applicationmodel.ApplicationInstanceReference; import com.yahoo.vespa.applicationmodel.HostName; import com.yahoo.vespa.orchestrator.Host; import com.yahoo.vespa.orchestrator.Orchestrator; @@ -41,6 +42,12 @@ public class OrchestratorMock implements Orchestrator { } @Override + public HostInfo getHostInfo(ApplicationInstanceReference reference, HostName hostname) { + HostInfo hostInfo = hostInfos.get(hostname); + return hostInfo == null ? HostInfo.createNoRemarks() : hostInfo; + } + + @Override public Function<HostName, Optional<HostInfo>> getHostResolver() { return hostName -> Optional.of(hostInfos.getOrDefault(hostName, HostInfo.createNoRemarks())); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporter.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporter.java index e65b7273b9e..e107abf8fbb 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporter.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporter.java @@ -6,6 +6,7 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; import com.yahoo.jdisc.Metric; +import com.yahoo.vespa.applicationmodel.ApplicationInstance; import com.yahoo.vespa.applicationmodel.HostName; import com.yahoo.vespa.applicationmodel.ServiceInstance; import com.yahoo.vespa.applicationmodel.ServiceStatus; @@ -15,7 +16,7 @@ import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.node.Allocation; import com.yahoo.vespa.hosted.provision.node.History; import com.yahoo.vespa.orchestrator.Orchestrator; -import com.yahoo.vespa.orchestrator.status.HostInfo; +import com.yahoo.vespa.service.monitor.ServiceModel; import com.yahoo.vespa.service.monitor.ServiceMonitor; import java.time.Clock; @@ -24,7 +25,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -36,7 +36,7 @@ import static com.yahoo.config.provision.NodeResources.DiskSpeed.any; public class MetricsReporter extends Maintainer { private final Metric metric; - private final Function<HostName, Optional<HostInfo>> orchestrator; + private final Orchestrator orchestrator; private final ServiceMonitor serviceMonitor; private final Map<Map<String, String>, Metric.Context> contextMap = new HashMap<>(); private final Supplier<Integer> pendingRedeploymentsSupplier; @@ -51,7 +51,7 @@ public class MetricsReporter extends Maintainer { Clock clock) { super(nodeRepository, interval); this.metric = metric; - this.orchestrator = orchestrator.getHostResolver(); + this.orchestrator = orchestrator; this.serviceMonitor = serviceMonitor; this.pendingRedeploymentsSupplier = pendingRedeploymentsSupplier; this.clock = clock; @@ -60,9 +60,9 @@ public class MetricsReporter extends Maintainer { @Override public void maintain() { NodeList nodes = nodeRepository().list(); - Map<HostName, List<ServiceInstance>> servicesByHost = serviceMonitor.getServicesByHostname(); + ServiceModel serviceModel = serviceMonitor.getServiceModelSnapshot(); - nodes.forEach(node -> updateNodeMetrics(node, servicesByHost)); + nodes.forEach(node -> updateNodeMetrics(node, serviceModel)); updateStateMetrics(nodes); updateMaintenanceMetrics(); updateDockerMetrics(nodes); @@ -73,7 +73,7 @@ public class MetricsReporter extends Maintainer { metric.set("hostedVespa.pendingRedeployments", pendingRedeploymentsSupplier.get(), null); } - private void updateNodeMetrics(Node node, Map<HostName, List<ServiceInstance>> servicesByHost) { + private void updateNodeMetrics(Node node, ServiceModel serviceModel) { Metric.Context context; Optional<Allocation> allocation = node.allocation(); @@ -128,19 +128,23 @@ public class MetricsReporter extends Maintainer { metric.set("wantToDeprovision", node.status().wantToDeprovision() ? 1 : 0, context); metric.set("failReport", NodeFailer.reasonsToFailParentHost(node).isEmpty() ? 0 : 1, context); - orchestrator.apply(new HostName(node.hostname())).ifPresent(info -> { - int suspended = info.status().isSuspended() ? 1 : 0; - metric.set("suspended", suspended, context); - metric.set("allowedToBeDown", suspended, context); // remove summer 2020. - long suspendedSeconds = info.suspendedSince() - .map(suspendedSince -> Duration.between(suspendedSince, clock.instant()).getSeconds()) - .orElse(0L); - metric.set("suspendedSeconds", suspendedSeconds, context); - }); + HostName hostname = new HostName(node.hostname()); + + serviceModel.getApplication(hostname) + .map(ApplicationInstance::reference) + .map(reference -> orchestrator.getHostInfo(reference, hostname)) + .ifPresent(info -> { + int suspended = info.status().isSuspended() ? 1 : 0; + metric.set("suspended", suspended, context); + metric.set("allowedToBeDown", suspended, context); // remove summer 2020. + long suspendedSeconds = info.suspendedSince() + .map(suspendedSince -> Duration.between(suspendedSince, clock.instant()).getSeconds()) + .orElse(0L); + metric.set("suspendedSeconds", suspendedSeconds, context); + }); long numberOfServices; - HostName hostName = new HostName(node.hostname()); - List<ServiceInstance> services = servicesByHost.get(hostName); + List<ServiceInstance> services = serviceModel.getServiceInstancesByHostName().get(hostname); if (services == null) { numberOfServices = 0; } else { diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java index 8a5a119f6a7..b0b8f7d8d2c 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java @@ -186,7 +186,7 @@ public class NodeFailer extends Maintainer { Map<String, Node> activeNodesByHostname = nodeRepository().getNodes(Node.State.active).stream() .collect(Collectors.toMap(Node::hostname, node -> node)); - serviceMonitor.getServicesByHostname() + serviceMonitor.getServiceModelSnapshot().getServiceInstancesByHostName() .forEach((hostName, serviceInstances) -> { Node node = activeNodesByHostname.get(hostName.s()); if (node == null) return; diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java index ecc550527fc..c3c488c9616 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java @@ -8,10 +8,10 @@ import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.HostLivenessTracker; import com.yahoo.config.provision.InfraDeployer; import com.yahoo.config.provision.Zone; -import com.yahoo.vespa.hosted.provision.autoscale.NodeMetrics; import com.yahoo.jdisc.Metric; import com.yahoo.vespa.flags.FlagSource; import com.yahoo.vespa.hosted.provision.NodeRepository; +import com.yahoo.vespa.hosted.provision.autoscale.NodeMetrics; import com.yahoo.vespa.hosted.provision.autoscale.NodeMetricsDb; import com.yahoo.vespa.hosted.provision.provisioning.ProvisionServiceProvider; import com.yahoo.vespa.orchestrator.Orchestrator; diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/OrchestratorMock.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/OrchestratorMock.java index d183e62c96c..ff9cb98783c 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/OrchestratorMock.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/OrchestratorMock.java @@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.provision.testutils; import com.yahoo.component.AbstractComponent; import com.yahoo.config.provision.ApplicationId; +import com.yahoo.vespa.applicationmodel.ApplicationInstanceReference; import com.yahoo.vespa.applicationmodel.HostName; import com.yahoo.vespa.orchestrator.Host; import com.yahoo.vespa.orchestrator.Orchestrator; @@ -40,6 +41,12 @@ public class OrchestratorMock extends AbstractComponent implements Orchestrator } @Override + public HostInfo getHostInfo(ApplicationInstanceReference reference, HostName hostname) { + HostInfo hostInfo = suspendedHosts.get(hostname); + return hostInfo == null ? HostInfo.createNoRemarks() : hostInfo; + } + + @Override public Function<HostName, Optional<HostInfo>> getHostResolver() { return hostName -> Optional.of(suspendedHosts.getOrDefault(hostName, HostInfo.createNoRemarks())); } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporterTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporterTest.java index 8daec1d641e..ddca903a5c2 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporterTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporterTest.java @@ -11,6 +11,8 @@ import com.yahoo.config.provision.NodeType; import com.yahoo.config.provision.Zone; import com.yahoo.jdisc.Metric; import com.yahoo.test.ManualClock; +import com.yahoo.vespa.applicationmodel.ApplicationInstance; +import com.yahoo.vespa.applicationmodel.ApplicationInstanceReference; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.curator.mock.MockCurator; import com.yahoo.vespa.flags.InMemoryFlagSource; @@ -26,7 +28,9 @@ import com.yahoo.vespa.hosted.provision.testutils.MockNameResolver; import com.yahoo.vespa.orchestrator.Orchestrator; import com.yahoo.vespa.orchestrator.status.HostInfo; import com.yahoo.vespa.orchestrator.status.HostStatus; +import com.yahoo.vespa.service.monitor.ServiceModel; import com.yahoo.vespa.service.monitor.ServiceMonitor; +import org.junit.Before; import org.junit.Test; import java.time.Clock; @@ -40,6 +44,8 @@ import java.util.Optional; import java.util.Set; import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -48,6 +54,23 @@ import static org.mockito.Mockito.when; * @author smorgrav */ public class MetricsReporterTest { + private final ServiceMonitor serviceMonitor = mock(ServiceMonitor.class); + private final ApplicationInstanceReference reference = mock(ApplicationInstanceReference.class); + + @Before + public void setUp() { + // On the serviceModel returned by serviceMonitor.getServiceModelSnapshot(), + // 2 methods should be used by MetricsReporter: + // - getServiceInstancesByHostName() -> empty Map + // - getApplication() which is mapped to a dummy ApplicationInstanceReference and + // used for lookup. + ServiceModel serviceModel = mock(ServiceModel.class); + when(serviceMonitor.getServiceModelSnapshot()).thenReturn(serviceModel); + when(serviceModel.getServiceInstancesByHostName()).thenReturn(Map.of()); + ApplicationInstance applicationInstance = mock(ApplicationInstance.class); + when(serviceModel.getApplication(any())).thenReturn(Optional.of(applicationInstance)); + when(applicationInstance.reference()).thenReturn(reference); + } @Test public void test_registered_metric() { @@ -91,12 +114,10 @@ public class MetricsReporterTest { expectedMetrics.put("numberOfServices", 0L); ManualClock clock = new ManualClock(Instant.ofEpochSecond(124)); + Orchestrator orchestrator = mock(Orchestrator.class); - ServiceMonitor serviceMonitor = mock(ServiceMonitor.class); - when(orchestrator.getHostResolver()).thenReturn(hostName -> - Optional.of(HostInfo.createSuspended(HostStatus.ALLOWED_TO_BE_DOWN, Instant.ofEpochSecond(1))) - ); - when(serviceMonitor.getServicesByHostname()).thenReturn(Map.of()); + when(orchestrator.getHostInfo(eq(reference), any())).thenReturn( + HostInfo.createSuspended(HostStatus.ALLOWED_TO_BE_DOWN, Instant.ofEpochSecond(1))); TestMetric metric = new TestMetric(); MetricsReporter metricsReporter = new MetricsReporter( @@ -141,9 +162,7 @@ public class MetricsReporterTest { nodeRepository.addDockerNodes(new LockedNodeList(List.of(container2), nodeRepository.lockUnallocated())); Orchestrator orchestrator = mock(Orchestrator.class); - ServiceMonitor serviceMonitor = mock(ServiceMonitor.class); - when(orchestrator.getHostResolver()).thenReturn(hostName -> Optional.of(HostInfo.createNoRemarks())); - when(serviceMonitor.getServicesByHostname()).thenReturn(Map.of()); + when(orchestrator.getHostInfo(eq(reference), any())).thenReturn(HostInfo.createNoRemarks()); TestMetric metric = new TestMetric(); ManualClock clock = new ManualClock(); diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/Orchestrator.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/Orchestrator.java index 10fa10f1150..9d2d72277e5 100644 --- a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/Orchestrator.java +++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/Orchestrator.java @@ -2,8 +2,8 @@ package com.yahoo.vespa.orchestrator; import com.yahoo.config.provision.ApplicationId; +import com.yahoo.vespa.applicationmodel.ApplicationInstanceReference; import com.yahoo.vespa.applicationmodel.HostName; -import com.yahoo.vespa.orchestrator.model.NodeGroup; import com.yahoo.vespa.orchestrator.policy.BatchHostStateChangeDeniedException; import com.yahoo.vespa.orchestrator.policy.HostStateChangeDeniedException; import com.yahoo.vespa.orchestrator.status.ApplicationInstanceStatus; @@ -45,10 +45,13 @@ public interface Orchestrator { * * @param hostName The FQDN which are used in the noderepo. * @return The enum describing the current state. - * @throws HostNameNotFoundException if hostName is unrecognized (in node repo) + * @throws HostNameNotFoundException if hostName is not associated with any application */ HostStatus getNodeStatus(HostName hostName) throws HostNameNotFoundException; + /** Get host info for hostname in application, returning no-remarks if not in application. */ + HostInfo getHostInfo(ApplicationInstanceReference reference, HostName hostname); + /** * Returns a lambda, which when invoked with a hostname, returns its current host info. * diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/OrchestratorImpl.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/OrchestratorImpl.java index 5ac5c66cc7c..f36f7088424 100644 --- a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/OrchestratorImpl.java +++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/OrchestratorImpl.java @@ -127,19 +127,25 @@ public class OrchestratorImpl implements Orchestrator { @Override public HostStatus getNodeStatus(HostName hostName) throws HostNameNotFoundException { - return getNodeStatus(getApplicationInstance(hostName).reference(), hostName); + ApplicationInstanceReference reference = getApplicationInstanceReference(hostName); + return statusService.getHostInfo(reference, hostName).status(); + } + + @Override + public HostInfo getHostInfo(ApplicationInstanceReference reference, HostName hostname) { + return statusService.getHostInfo(reference, hostname); } @Override public Function<HostName, Optional<HostInfo>> getHostResolver() { return hostName -> serviceMonitor - .getApplication(hostName) - .map(application -> statusService.getHostInfo(application.reference(), hostName)); + .getApplicationInstanceReference(hostName) + .map(reference -> statusService.getHostInfo(reference, hostName)); } @Override public void setNodeStatus(HostName hostName, HostStatus status) throws OrchestrationException { - ApplicationInstanceReference reference = getApplicationInstance(hostName).reference(); + ApplicationInstanceReference reference = getApplicationInstanceReference(hostName); OrchestratorContext context = OrchestratorContext.createContextForSingleAppOp(clock); try (ApplicationLock lock = statusService.lockApplication(context, reference)) { lock.setHostState(hostName, status); @@ -347,11 +353,7 @@ public class OrchestratorImpl implements Orchestrator { return leftApplicationReference.asString().compareTo(rightApplicationReference.asString()); } - private HostStatus getNodeStatus(ApplicationInstanceReference applicationRef, HostName hostName) { - return statusService.getHostInfo(applicationRef, hostName).status(); - } - - private void setApplicationStatus(ApplicationId appId, ApplicationInstanceStatus status) + private void setApplicationStatus(ApplicationId appId, ApplicationInstanceStatus status) throws ApplicationStateChangeDeniedException, ApplicationIdNotFoundException{ OrchestratorContext context = OrchestratorContext.createContextForSingleAppOp(clock); ApplicationInstanceReference reference = OrchestratorUtil.toApplicationInstanceReference(appId, serviceMonitor); @@ -420,9 +422,14 @@ public class OrchestratorImpl implements Orchestrator { } } + private ApplicationInstanceReference getApplicationInstanceReference(HostName hostname) throws HostNameNotFoundException { + return serviceMonitor.getApplicationInstanceReference(hostname) + .orElseThrow(() -> new HostNameNotFoundException(hostname)); + } + private ApplicationInstance getApplicationInstance(HostName hostName) throws HostNameNotFoundException{ - return serviceMonitor.getApplication(hostName).orElseThrow( - () -> new HostNameNotFoundException(hostName)); + return serviceMonitor.getApplication(hostName) + .orElseThrow(() -> new HostNameNotFoundException(hostName)); } private static void sleep(long time, TimeUnit timeUnit) { diff --git a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/DummyServiceMonitor.java b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/DummyServiceMonitor.java index a784187fb62..501a09f78ff 100644 --- a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/DummyServiceMonitor.java +++ b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/DummyServiceMonitor.java @@ -163,11 +163,6 @@ public class DummyServiceMonitor implements ServiceMonitor, AntiServiceMonitor { } @Override - public Map<HostName, List<ServiceInstance>> getServicesByHostname() { - throw new UnsupportedOperationException(); - } - - @Override public CriticalRegion disallowDuperModelLockAcquisition(String regionDescription) { return () -> {}; } diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/model/ServiceMonitorImpl.java b/service-monitor/src/main/java/com/yahoo/vespa/service/model/ServiceMonitorImpl.java index 678b0d3d5ba..e6e6e85c710 100644 --- a/service-monitor/src/main/java/com/yahoo/vespa/service/model/ServiceMonitorImpl.java +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/model/ServiceMonitorImpl.java @@ -10,7 +10,6 @@ import com.yahoo.jdisc.Timer; import com.yahoo.vespa.applicationmodel.ApplicationInstance; import com.yahoo.vespa.applicationmodel.ApplicationInstanceReference; import com.yahoo.vespa.applicationmodel.HostName; -import com.yahoo.vespa.applicationmodel.ServiceInstance; import com.yahoo.vespa.service.duper.DuperModelManager; import com.yahoo.vespa.service.manager.MonitorManager; import com.yahoo.vespa.service.manager.UnionMonitorManager; @@ -21,8 +20,6 @@ import com.yahoo.vespa.service.monitor.ServiceModel; import com.yahoo.vespa.service.monitor.ServiceMonitor; import com.yahoo.vespa.service.monitor.ServiceStatusProvider; -import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Set; @@ -70,13 +67,16 @@ public class ServiceMonitorImpl implements ServiceMonitor, AntiServiceMonitor { } @Override - public Optional<ApplicationInstance> getApplication(HostName hostname) { - Optional<ApplicationInfo> applicationInfo = getApplicationInfo(hostname); - if (applicationInfo.isEmpty()) { - return Optional.empty(); - } + public Optional<ApplicationInstanceReference> getApplicationInstanceReference(HostName hostname) { + return duperModelManager.getApplicationInfo(toConfigProvisionHostName(hostname)) + .map(ApplicationInfo::getApplicationId) + .map(modelGenerator::toApplicationInstanceReference); + } - return Optional.of(modelGenerator.toApplicationInstance(applicationInfo.get(), serviceStatusProvider)); + @Override + public Optional<ApplicationInstance> getApplication(HostName hostname) { + return getApplicationInfo(hostname) + .map(applicationInfo -> modelGenerator.toApplicationInstance(applicationInfo, serviceStatusProvider)); } @Override @@ -97,11 +97,6 @@ public class ServiceMonitorImpl implements ServiceMonitor, AntiServiceMonitor { } @Override - public Map<HostName, List<ServiceInstance>> getServicesByHostname() { - return getServiceModelSnapshot().getServiceInstancesByHostName(); - } - - @Override public void registerListener(ServiceHostListener listener) { var duperModelListener = ServiceHostListenerAdapter.asDuperModelListener(listener, modelGenerator); duperModelManager.registerListener(duperModelListener); @@ -118,8 +113,11 @@ public class ServiceMonitorImpl implements ServiceMonitor, AntiServiceMonitor { } private Optional<ApplicationInfo> getApplicationInfo(HostName hostname) { - // The duper model uses HostName from config.provision, which is more natural than applicationmodel. - var configProvisionHostname = com.yahoo.config.provision.HostName.from(hostname.s()); - return duperModelManager.getApplicationInfo(configProvisionHostname); + return duperModelManager.getApplicationInfo(toConfigProvisionHostName(hostname)); + } + + /** The duper model uses HostName from config.provision. */ + private static com.yahoo.config.provision.HostName toConfigProvisionHostName(HostName hostname) { + return com.yahoo.config.provision.HostName.from(hostname.s()); } } diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/ServiceModel.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/ServiceModel.java index ed72893400a..c8f491a0fc7 100644 --- a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/ServiceModel.java +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/ServiceModel.java @@ -1,66 +1,91 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.service.monitor; +import com.yahoo.config.model.api.ApplicationInfo; import com.yahoo.vespa.applicationmodel.ApplicationInstance; import com.yahoo.vespa.applicationmodel.ApplicationInstanceReference; import com.yahoo.vespa.applicationmodel.HostName; import com.yahoo.vespa.applicationmodel.ServiceCluster; import com.yahoo.vespa.applicationmodel.ServiceInstance; +import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.stream.Collectors; /** - * The ServiceModel is almost a mirror of the SuperModel, except that it - * also gives ServiceStatus on each service, and there may be - * artificial applications like the config server "application". + * The service model is the union of the duper model and the service monitor, and presented + * as classes from the {@code application-model} module. + * + * <p>The duper model contains the latest {@link ApplicationInfo} of both tenant and infrastructure + * applications. The service monitor provides Slobrok or {@code /state/v1/health} information + * on (most) services. The application model presents an application as a set of clusters, and + * each cluster a set of services, and each service is associated with a particular host + * and has a health status.</p> + * + * @author hakonhall */ public class ServiceModel { - private final Map<ApplicationInstanceReference, ApplicationInstance> applications; - private final Map<HostName, ApplicationInstance> applicationsByHostName; + private final Map<ApplicationInstanceReference, ApplicationInstance> applicationsByReference; - public ServiceModel(Map<ApplicationInstanceReference, ApplicationInstance> applications) { - this.applications = Collections.unmodifiableMap(applications); - this.applicationsByHostName = Collections.unmodifiableMap(applicationsByHostNames(applications.values())); + private Map<HostName, ApplicationInstance> applicationsByHostName = null; + private Map<HostName, List<ServiceInstance>> servicesByHostName = null; + + public ServiceModel(Map<ApplicationInstanceReference, ApplicationInstance> applicationsByReference) { + this.applicationsByReference = Collections.unmodifiableMap(Map.copyOf(applicationsByReference)); } public Map<ApplicationInstanceReference, ApplicationInstance> getAllApplicationInstances() { - return applications; + return applicationsByReference; } public Optional<ApplicationInstance> getApplicationInstance(ApplicationInstanceReference reference) { - return Optional.ofNullable(applications.get(reference)); + return Optional.ofNullable(applicationsByReference.get(reference)); } - public Map<HostName, ApplicationInstance> getApplicationsByHostName() { - return applicationsByHostName; + public Optional<ApplicationInstance> getApplication(HostName hostname) { + if (applicationsByHostName == null) { + fillMaps(); + } + + return Optional.ofNullable(applicationsByHostName.get(hostname)); } public Map<HostName, List<ServiceInstance>> getServiceInstancesByHostName() { - return applications.values().stream() - .flatMap(application -> application.serviceClusters().stream()) - .flatMap(cluster -> cluster.serviceInstances().stream()) - .collect(Collectors.groupingBy(ServiceInstance::hostName, Collectors.toList())); + if (servicesByHostName == null) { + fillMaps(); + } + + return servicesByHostName; } - private static Map<HostName, ApplicationInstance> applicationsByHostNames(Collection<ApplicationInstance> applications) { - Map<HostName, ApplicationInstance> hosts = new HashMap<>(); - for (ApplicationInstance application : applications) - for (ServiceCluster cluster : application.serviceClusters()) + private void fillMaps() { + Map<HostName, ApplicationInstance> applicationInstances = new HashMap<>(); + Map<HostName, List<ServiceInstance>> serviceInstances = new HashMap<>(); + + for (ApplicationInstance application : applicationsByReference.values()) { + for (ServiceCluster cluster : application.serviceClusters()) { for (ServiceInstance instance : cluster.serviceInstances()) { - ApplicationInstance previous = hosts.put(instance.hostName(), application); - if (previous != null && ! previous.equals(application)) + + ApplicationInstance previous = applicationInstances.put(instance.hostName(), application); + if (previous != null && !previous.equals(application)) { throw new IllegalStateException("Major assumption broken: Multiple application instances contain host " + - instance.hostName().s() + ": " + Arrays.asList(previous, application)); + instance.hostName().s() + ": " + Arrays.asList(previous, application)); + } + + serviceInstances + .computeIfAbsent(instance.hostName(), key -> new ArrayList<>()) + .add(instance); } - return hosts; + } + } + + applicationsByHostName = Collections.unmodifiableMap(applicationInstances); + servicesByHostName = Collections.unmodifiableMap(serviceInstances); } } diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/ServiceMonitor.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/ServiceMonitor.java index 0d162fee182..4f52d7d229c 100644 --- a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/ServiceMonitor.java +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/ServiceMonitor.java @@ -4,10 +4,7 @@ package com.yahoo.vespa.service.monitor; import com.yahoo.vespa.applicationmodel.ApplicationInstance; import com.yahoo.vespa.applicationmodel.ApplicationInstanceReference; import com.yahoo.vespa.applicationmodel.HostName; -import com.yahoo.vespa.applicationmodel.ServiceInstance; -import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Set; @@ -31,8 +28,12 @@ public interface ServiceMonitor { return getServiceModelSnapshot().getAllApplicationInstances().keySet(); } + default Optional<ApplicationInstanceReference> getApplicationInstanceReference(HostName hostname) { + return getApplication(hostname).map(ApplicationInstance::reference); + } + default Optional<ApplicationInstance> getApplication(HostName hostname) { - return Optional.ofNullable(getServiceModelSnapshot().getApplicationsByHostName().get(hostname)); + return getServiceModelSnapshot().getApplication(hostname); } default Optional<ApplicationInstance> getApplication(ApplicationInstanceReference reference) { @@ -43,10 +44,6 @@ public interface ServiceMonitor { return getApplication(hostname); } - default Map<HostName, List<ServiceInstance>> getServicesByHostname() { - return getServiceModelSnapshot().getServiceInstancesByHostName(); - } - /** * Get notified of changes to the set of applications, or set of hosts assigned to an application. * |