diff options
author | Håkon Hallingstad <hakon@oath.com> | 2018-05-31 21:10:34 +0200 |
---|---|---|
committer | Håkon Hallingstad <hakon@oath.com> | 2018-06-05 16:42:24 +0200 |
commit | d89b57282a3263927c8a000724e691b8bf4562b0 (patch) | |
tree | b2ff482bd1a821cd2b426df6e35dd765b313e799 /service-monitor/src | |
parent | 7ae7c6d9b3ff6f80ecfb4f70b9fc3ac351196f90 (diff) |
Revert "Revert "Make health client wo using it""
Diffstat (limited to 'service-monitor/src')
33 files changed, 1270 insertions, 394 deletions
diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/ServiceStatusProvider.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/ServiceStatusProvider.java index 35003313775..75e61eef772 100644 --- a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/ServiceStatusProvider.java +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/ServiceStatusProvider.java @@ -11,7 +11,13 @@ import com.yahoo.vespa.applicationmodel.ServiceType; * @author hakon */ public interface ServiceStatusProvider { - /** Get the {@link ServiceStatus} of a particular service. */ + /** + * Get the {@link ServiceStatus} of a particular service. + * + * <p>{@link ServiceStatus#NOT_CHECKED NOT_CHECKED} must be returned if the + * service status provider does does not monitor the service status for + * the particular application, cluster, service type, and config id. + */ ServiceStatus getStatus(ApplicationId applicationId, ClusterId clusterId, ServiceType serviceType, diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/ApplicationInstanceGenerator.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/ApplicationInstanceGenerator.java index ec2702bcfaf..cbdcce125cc 100644 --- a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/ApplicationInstanceGenerator.java +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/ApplicationInstanceGenerator.java @@ -1,13 +1,148 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.service.monitor.application; +import com.yahoo.config.model.api.ApplicationInfo; +import com.yahoo.config.model.api.HostInfo; +import com.yahoo.config.model.api.ServiceInfo; +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.Zone; import com.yahoo.vespa.applicationmodel.ApplicationInstance; +import com.yahoo.vespa.applicationmodel.ApplicationInstanceId; +import com.yahoo.vespa.applicationmodel.ClusterId; +import com.yahoo.vespa.applicationmodel.ConfigId; +import com.yahoo.vespa.applicationmodel.HostName; +import com.yahoo.vespa.applicationmodel.ServiceCluster; +import com.yahoo.vespa.applicationmodel.ServiceClusterKey; +import com.yahoo.vespa.applicationmodel.ServiceInstance; +import com.yahoo.vespa.applicationmodel.ServiceStatus; +import com.yahoo.vespa.applicationmodel.ServiceType; +import com.yahoo.vespa.applicationmodel.TenantId; import com.yahoo.vespa.service.monitor.ServiceStatusProvider; +import com.yahoo.vespa.service.monitor.internal.ServiceId; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.yahoo.vespa.service.monitor.application.ConfigServerApplication.CONFIG_SERVER_APPLICATION; /** + * Class to generate an ApplicationInstance given service status for a standard (deployed) application. + * * @author hakon */ -public interface ApplicationInstanceGenerator { - /** Make an ApplicationInstance based on current service status. */ - ApplicationInstance makeApplicationInstance(ServiceStatusProvider serviceStatusProvider); +public class ApplicationInstanceGenerator { + public static final String CLUSTER_ID_PROPERTY_NAME = "clustername"; + + private final ApplicationInfo applicationInfo; + private final Zone zone; + + public ApplicationInstanceGenerator(ApplicationInfo applicationInfo, Zone zone) { + this.applicationInfo = applicationInfo; + this.zone = zone; + } + + public ApplicationInstance makeApplicationInstance(ServiceStatusProvider serviceStatusProvider) { + Map<ServiceClusterKey, Set<ServiceInstance>> groupedServiceInstances = new HashMap<>(); + + for (HostInfo host : applicationInfo.getModel().getHosts()) { + HostName hostName = new HostName(host.getHostname()); + for (ServiceInfo serviceInfo : host.getServices()) { + ServiceClusterKey serviceClusterKey = toServiceClusterKey(serviceInfo); + ServiceInstance serviceInstance = + toServiceInstance( + applicationInfo.getApplicationId(), + serviceClusterKey.clusterId(), + serviceInfo, + hostName, + serviceStatusProvider); + + if (!groupedServiceInstances.containsKey(serviceClusterKey)) { + groupedServiceInstances.put(serviceClusterKey, new HashSet<>()); + } + groupedServiceInstances.get(serviceClusterKey).add(serviceInstance); + } + } + + Set<ServiceCluster> serviceClusters = groupedServiceInstances.entrySet().stream() + .map(entry -> new ServiceCluster( + entry.getKey().clusterId(), + entry.getKey().serviceType(), + entry.getValue())) + .collect(Collectors.toSet()); + + ApplicationInstance applicationInstance = new ApplicationInstance( + new TenantId(applicationInfo.getApplicationId().tenant().toString()), + toApplicationInstanceId(applicationInfo, zone), + serviceClusters); + + // Fill back-references + for (ServiceCluster serviceCluster : applicationInstance.serviceClusters()) { + serviceCluster.setApplicationInstance(applicationInstance); + for (ServiceInstance serviceInstance : serviceCluster.serviceInstances()) { + serviceInstance.setServiceCluster(serviceCluster); + } + } + + return applicationInstance; + } + + private ServiceInstance toServiceInstance( + ApplicationId applicationId, + ClusterId clusterId, + ServiceInfo serviceInfo, + HostName hostName, + ServiceStatusProvider serviceStatusProvider) { + ConfigId configId = toConfigId(serviceInfo); + + ServiceStatus status = serviceStatusProvider.getStatus( + applicationId, + clusterId, + toServiceType(serviceInfo), configId); + + return new ServiceInstance(configId, hostName, status); + } + + private ApplicationInstanceId toApplicationInstanceId(ApplicationInfo applicationInfo, Zone zone) { + if (applicationInfo.getApplicationId().equals(CONFIG_SERVER_APPLICATION.getApplicationId())) { + // Removing this historical discrepancy would break orchestration during rollout. + // An alternative may be to use a feature flag and flip it between releases, + // once that's available. + return new ApplicationInstanceId(applicationInfo.getApplicationId().application().value()); + } else { + return new ApplicationInstanceId(String.format("%s:%s:%s:%s", + applicationInfo.getApplicationId().application().value(), + zone.environment().value(), + zone.region().value(), + applicationInfo.getApplicationId().instance().value())); + } + } + + public static ServiceId getServiceId(ApplicationInfo applicationInfo, ServiceInfo serviceInfo) { + return new ServiceId( + applicationInfo.getApplicationId(), + getClusterId(serviceInfo), + toServiceType(serviceInfo), + toConfigId(serviceInfo)); + } + + private static ClusterId getClusterId(ServiceInfo serviceInfo) { + return new ClusterId(serviceInfo.getProperty(CLUSTER_ID_PROPERTY_NAME).orElse("")); + } + + private static ServiceClusterKey toServiceClusterKey(ServiceInfo serviceInfo) { + ClusterId clusterId = getClusterId(serviceInfo); + ServiceType serviceType = toServiceType(serviceInfo); + return new ServiceClusterKey(clusterId, serviceType); + } + + private static ServiceType toServiceType(ServiceInfo serviceInfo) { + return new ServiceType(serviceInfo.getServiceType()); + } + + private static ConfigId toConfigId(ServiceInfo serviceInfo) { + return new ConfigId(serviceInfo.getConfigId()); + } } diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/ConfigServerAppGenerator.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/ConfigServerAppGenerator.java deleted file mode 100644 index 76ca59cf583..00000000000 --- a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/ConfigServerAppGenerator.java +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.service.monitor.application; - -import com.yahoo.vespa.applicationmodel.ApplicationInstance; -import com.yahoo.vespa.applicationmodel.ConfigId; -import com.yahoo.vespa.applicationmodel.HostName; -import com.yahoo.vespa.applicationmodel.ServiceCluster; -import com.yahoo.vespa.applicationmodel.ServiceInstance; -import com.yahoo.vespa.applicationmodel.ServiceStatus; -import com.yahoo.vespa.service.monitor.ServiceStatusProvider; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * Class for generating an ApplicationInstance for the synthesized config server application. - * - * @author hakon - */ -public class ConfigServerAppGenerator implements ApplicationInstanceGenerator { - private final List<String> hostnames; - - public ConfigServerAppGenerator(List<String> hostnames) { - this.hostnames = hostnames; - } - - @Override - public ApplicationInstance makeApplicationInstance(ServiceStatusProvider statusProvider) { - Set<ServiceInstance> serviceInstances = hostnames.stream() - .map(hostname -> makeServiceInstance(hostname, statusProvider)) - .collect(Collectors.toSet()); - - ServiceCluster serviceCluster = new ServiceCluster( - ConfigServerApplication.CLUSTER_ID, - ConfigServerApplication.SERVICE_TYPE, - serviceInstances); - - Set<ServiceCluster> serviceClusters = new HashSet<>(); - serviceClusters.add(serviceCluster); - - ApplicationInstance applicationInstance = new ApplicationInstance( - ConfigServerApplication.TENANT_ID, - ConfigServerApplication.APPLICATION_INSTANCE_ID, - serviceClusters); - - // Fill back-references - serviceCluster.setApplicationInstance(applicationInstance); - for (ServiceInstance serviceInstance : serviceCluster.serviceInstances()) { - serviceInstance.setServiceCluster(serviceCluster); - } - - return applicationInstance; - } - - private ServiceInstance makeServiceInstance(String hostname, ServiceStatusProvider statusProvider) { - ConfigId configId = new ConfigId(ConfigServerApplication.CONFIG_ID_PREFIX + hostname); - ServiceStatus status = statusProvider.getStatus( - ConfigServerApplication.CONFIG_SERVER_APPLICATION.getApplicationId(), - ConfigServerApplication.CLUSTER_ID, - ConfigServerApplication.SERVICE_TYPE, - configId); - - return new ServiceInstance(configId, new HostName(hostname), status); - } -} diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/ConfigServerApplication.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/ConfigServerApplication.java index 132bb0927b8..ff51b40e4dd 100644 --- a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/ConfigServerApplication.java +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/ConfigServerApplication.java @@ -1,12 +1,26 @@ // 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.application; +import com.yahoo.cloud.config.ConfigserverConfig; +import com.yahoo.config.model.api.ApplicationInfo; +import com.yahoo.config.model.api.HostInfo; +import com.yahoo.config.model.api.PortInfo; +import com.yahoo.config.model.api.ServiceInfo; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.NodeType; import com.yahoo.vespa.applicationmodel.ApplicationInstanceId; import com.yahoo.vespa.applicationmodel.ClusterId; +import com.yahoo.vespa.applicationmodel.ConfigId; import com.yahoo.vespa.applicationmodel.ServiceType; import com.yahoo.vespa.applicationmodel.TenantId; +import com.yahoo.vespa.service.monitor.internal.ModelGenerator; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * A service/application model of the config server with health status. @@ -21,8 +35,45 @@ public class ConfigServerApplication extends HostedVespaApplication { public static final ServiceType SERVICE_TYPE = new ServiceType("configserver"); public static final String CONFIG_ID_PREFIX = "configid."; + public static ConfigId configIdFrom(int index) { + return new ConfigId(CONFIG_ID_PREFIX + index); + } + private ConfigServerApplication() { super("zone-config-servers", NodeType.config, ClusterSpec.Type.admin, ClusterSpec.Id.from("zone-config-servers")); } + + public ApplicationInfo makeApplicationInfo(ConfigserverConfig config) { + List<HostInfo> hostInfos = new ArrayList<>(); + List<ConfigserverConfig.Zookeeperserver> zooKeeperServers = config.zookeeperserver(); + for (int index = 0; index < zooKeeperServers.size(); ++index) { + String hostname = zooKeeperServers.get(index).hostname(); + hostInfos.add(makeHostInfo(hostname, index)); + } + + return new ApplicationInfo( + CONFIG_SERVER_APPLICATION.getApplicationId(), + 0, + new HostsModel(hostInfos)); + } + + private static HostInfo makeHostInfo(String hostname, int configIndex) { + // /state/v1/health API is available with STATE and either HTTP or HTTPS. + PortInfo portInfo = new PortInfo(4443, Arrays.asList("HTTPS", "STATE")); + + Map<String, String> properties = new HashMap<>(); + properties.put(ModelGenerator.CLUSTER_ID_PROPERTY_NAME, CLUSTER_ID.s()); + + ServiceInfo serviceInfo = new ServiceInfo( + // service name == service type for the first service of each type on each host + SERVICE_TYPE.s(), + SERVICE_TYPE.s(), + Collections.singletonList(portInfo), + properties, + configIdFrom(configIndex).s(), + hostname); + + return new HostInfo(hostname, Collections.singletonList(serviceInfo)); + } } diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/DeployedAppGenerator.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/DeployedAppGenerator.java deleted file mode 100644 index 2691a8bf1ee..00000000000 --- a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/DeployedAppGenerator.java +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.service.monitor.application; - -import com.yahoo.config.model.api.ApplicationInfo; -import com.yahoo.config.model.api.HostInfo; -import com.yahoo.config.model.api.ServiceInfo; -import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.Zone; -import com.yahoo.vespa.applicationmodel.ApplicationInstance; -import com.yahoo.vespa.applicationmodel.ApplicationInstanceId; -import com.yahoo.vespa.applicationmodel.ClusterId; -import com.yahoo.vespa.applicationmodel.ConfigId; -import com.yahoo.vespa.applicationmodel.HostName; -import com.yahoo.vespa.applicationmodel.ServiceCluster; -import com.yahoo.vespa.applicationmodel.ServiceClusterKey; -import com.yahoo.vespa.applicationmodel.ServiceInstance; -import com.yahoo.vespa.applicationmodel.ServiceStatus; -import com.yahoo.vespa.applicationmodel.ServiceType; -import com.yahoo.vespa.applicationmodel.TenantId; -import com.yahoo.vespa.service.monitor.ServiceStatusProvider; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * Class to generate an ApplicationInstance given service status for a standard (deployed) application. - * - * @author hakon - */ -public class DeployedAppGenerator implements ApplicationInstanceGenerator { - public static final String CLUSTER_ID_PROPERTY_NAME = "clustername"; - - private final ApplicationInfo applicationInfo; - private final Zone zone; - - public DeployedAppGenerator(ApplicationInfo applicationInfo, Zone zone) { - this.applicationInfo = applicationInfo; - this.zone = zone; - } - - @Override - public ApplicationInstance makeApplicationInstance(ServiceStatusProvider serviceStatusProvider) { - Map<ServiceClusterKey, Set<ServiceInstance>> groupedServiceInstances = new HashMap<>(); - - for (HostInfo host : applicationInfo.getModel().getHosts()) { - HostName hostName = new HostName(host.getHostname()); - for (ServiceInfo serviceInfo : host.getServices()) { - ServiceClusterKey serviceClusterKey = toServiceClusterKey(serviceInfo); - ServiceInstance serviceInstance = - toServiceInstance( - applicationInfo.getApplicationId(), - serviceClusterKey.clusterId(), - serviceInfo, - hostName, - serviceStatusProvider); - - if (!groupedServiceInstances.containsKey(serviceClusterKey)) { - groupedServiceInstances.put(serviceClusterKey, new HashSet<>()); - } - groupedServiceInstances.get(serviceClusterKey).add(serviceInstance); - } - } - - Set<ServiceCluster> serviceClusters = groupedServiceInstances.entrySet().stream() - .map(entry -> new ServiceCluster( - entry.getKey().clusterId(), - entry.getKey().serviceType(), - entry.getValue())) - .collect(Collectors.toSet()); - - ApplicationInstance applicationInstance = new ApplicationInstance( - new TenantId(applicationInfo.getApplicationId().tenant().toString()), - toApplicationInstanceId(applicationInfo, zone), - serviceClusters); - - // Fill back-references - for (ServiceCluster serviceCluster : applicationInstance.serviceClusters()) { - serviceCluster.setApplicationInstance(applicationInstance); - for (ServiceInstance serviceInstance : serviceCluster.serviceInstances()) { - serviceInstance.setServiceCluster(serviceCluster); - } - } - - return applicationInstance; - } - - static ClusterId getClusterId(ServiceInfo serviceInfo) { - return new ClusterId(serviceInfo.getProperty(CLUSTER_ID_PROPERTY_NAME).orElse("")); - } - - private ServiceClusterKey toServiceClusterKey(ServiceInfo serviceInfo) { - ClusterId clusterId = getClusterId(serviceInfo); - ServiceType serviceType = toServiceType(serviceInfo); - return new ServiceClusterKey(clusterId, serviceType); - } - - private ServiceInstance toServiceInstance( - ApplicationId applicationId, - ClusterId clusterId, - ServiceInfo serviceInfo, - HostName hostName, - ServiceStatusProvider serviceStatusProvider) { - ConfigId configId = new ConfigId(serviceInfo.getConfigId()); - - ServiceStatus status = serviceStatusProvider.getStatus( - applicationId, - clusterId, - toServiceType(serviceInfo), configId); - - return new ServiceInstance(configId, hostName, status); - } - - private ApplicationInstanceId toApplicationInstanceId(ApplicationInfo applicationInfo, Zone zone) { - return new ApplicationInstanceId(String.format("%s:%s:%s:%s", - applicationInfo.getApplicationId().application().value(), - zone.environment().value(), - zone.region().value(), - applicationInfo.getApplicationId().instance().value())); - } - - private ServiceType toServiceType(ServiceInfo serviceInfo) { - return new ServiceType(serviceInfo.getServiceType()); - } -} diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/HostsModel.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/HostsModel.java new file mode 100644 index 00000000000..225ffb0adbc --- /dev/null +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/HostsModel.java @@ -0,0 +1,75 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.service.monitor.application; + +import com.yahoo.config.FileReference; +import com.yahoo.config.model.api.FileDistribution; +import com.yahoo.config.model.api.HostInfo; +import com.yahoo.config.model.api.Model; +import com.yahoo.config.provision.AllocatedHosts; +import com.yahoo.vespa.config.ConfigKey; +import com.yahoo.vespa.config.ConfigPayload; +import com.yahoo.vespa.config.buildergen.ConfigDefinition; + +import java.time.Instant; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * Model that only supports the subset necessary to create an ApplicationInstance. + * + * @author hakon + */ +public class HostsModel implements Model { + private final Collection<HostInfo> hosts; + + public HostsModel(List<HostInfo> hosts) { + this.hosts = Collections.unmodifiableCollection(hosts); + } + + @Override + public Collection<HostInfo> getHosts() { + return hosts; + } + + @Override + public ConfigPayload getConfig(ConfigKey<?> configKey, ConfigDefinition configDefinition) { + throw new UnsupportedOperationException(); + } + + @Override + public Set<ConfigKey<?>> allConfigsProduced() { + throw new UnsupportedOperationException(); + } + + @Override + public Set<String> allConfigIds() { + throw new UnsupportedOperationException(); + } + + @Override + public void distributeFiles(FileDistribution fileDistribution) { + throw new UnsupportedOperationException(); + } + + @Override + public Set<FileReference> fileReferences() { + throw new UnsupportedOperationException(); + } + + @Override + public AllocatedHosts allocatedHosts() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean allowModelVersionMismatch(Instant now) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean skipOldConfigModels(Instant now) { + throw new UnsupportedOperationException(); + } +} diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/ZoneApplication.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/ZoneApplication.java index 6bbf0cb6d1d..c10015d3bfa 100644 --- a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/ZoneApplication.java +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/ZoneApplication.java @@ -21,8 +21,8 @@ public class ZoneApplication { .createHostedVespaApplicationId("routing"); public static boolean isNodeAdminService(ApplicationId applicationId, - ClusterId clusterId, - ServiceType serviceType) { + ClusterId clusterId, + ServiceType serviceType) { return Objects.equals(applicationId, ZONE_APPLICATION_ID) && Objects.equals(serviceType, ServiceType.CONTAINER) && Objects.equals(clusterId, ClusterId.NODE_ADMIN); diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/DuperModel.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/DuperModel.java new file mode 100644 index 00000000000..d3c6f92312c --- /dev/null +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/DuperModel.java @@ -0,0 +1,38 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.service.monitor.internal; + +import com.yahoo.cloud.config.ConfigserverConfig; +import com.yahoo.config.model.api.ApplicationInfo; +import com.yahoo.config.model.api.SuperModel; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static com.yahoo.vespa.service.monitor.application.ConfigServerApplication.CONFIG_SERVER_APPLICATION; + +/** + * The {@code DuperModel} unites the {@link com.yahoo.config.model.api.SuperModel SuperModel} + * with the synthetically produced applications like the config server application. + * + * @author hakon + */ +public class DuperModel { + private final List<ApplicationInfo> staticApplicationInfos = new ArrayList<>(); + + public DuperModel(ConfigserverConfig configServerConfig) { + staticApplicationInfos.add(CONFIG_SERVER_APPLICATION.makeApplicationInfo(configServerConfig)); + } + + /** For testing. */ + DuperModel(ApplicationInfo... staticApplicationInfos) { + this.staticApplicationInfos.addAll(Arrays.asList(staticApplicationInfos)); + } + + public List<ApplicationInfo> getApplicationInfos(SuperModel superModelSnapshot) { + List<ApplicationInfo> allApplicationInfos = new ArrayList<>(); + allApplicationInfos.addAll(staticApplicationInfos); + allApplicationInfos.addAll(superModelSnapshot.getAllApplicationInfos()); + return allApplicationInfos; + } +} diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/DuperModelListener.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/DuperModelListener.java new file mode 100644 index 00000000000..235c7db5c36 --- /dev/null +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/DuperModelListener.java @@ -0,0 +1,28 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.service.monitor.internal; + +import com.yahoo.config.model.api.ApplicationInfo; +import com.yahoo.config.model.api.SuperModel; +import com.yahoo.config.provision.ApplicationId; + +/** + * Interface for listening for changes to the {@link DuperModel}. + * + * @author hakon + */ +public interface DuperModelListener { + /** + * An application has been activated: + * + * <ul> + * <li>A synthetic application like the config server application has been added/"activated" + * <li>A super model application has been activated (see + * {@link com.yahoo.config.model.api.SuperModelListener#applicationActivated(SuperModel, ApplicationInfo) + * SuperModelListener} + * </ul> + */ + void applicationActivated(ApplicationInfo application); + + /** Application has been removed. */ + void applicationRemoved(ApplicationId id); +} diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/ModelGenerator.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/ModelGenerator.java index 9da449289a7..ad2f223acf8 100644 --- a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/ModelGenerator.java +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/ModelGenerator.java @@ -1,56 +1,40 @@ // 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.internal; -import com.yahoo.config.model.api.SuperModel; +import com.yahoo.config.model.api.ApplicationInfo; import com.yahoo.config.provision.Zone; import com.yahoo.vespa.applicationmodel.ApplicationInstance; import com.yahoo.vespa.applicationmodel.ApplicationInstanceReference; import com.yahoo.vespa.service.monitor.ServiceModel; import com.yahoo.vespa.service.monitor.ServiceStatusProvider; import com.yahoo.vespa.service.monitor.application.ApplicationInstanceGenerator; -import com.yahoo.vespa.service.monitor.application.ConfigServerAppGenerator; -import com.yahoo.vespa.service.monitor.application.DeployedAppGenerator; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; /** - * Util to convert SuperModel to ServiceModel and application model classes + * Util to make ServiceModel and its related application model classes */ public class ModelGenerator { public static final String CLUSTER_ID_PROPERTY_NAME = "clustername"; - private final List<ApplicationInstanceGenerator> staticGenerators; - - public ModelGenerator(List<String> configServerHosts) { - if (configServerHosts.isEmpty()) { - staticGenerators = Collections.emptyList(); - } else { - staticGenerators = Collections.singletonList(new ConfigServerAppGenerator(configServerHosts)); - } - } - /** * Create service model based primarily on super model. * * If the configServerhosts is non-empty, a config server application is added. */ - ServiceModel toServiceModel( - SuperModel superModel, - Zone zone, - ServiceStatusProvider serviceStatusProvider) { - List<ApplicationInstanceGenerator> generators = new ArrayList<>(staticGenerators); - superModel.getAllApplicationInfos() - .forEach(info -> generators.add(new DeployedAppGenerator(info, zone))); - - Map<ApplicationInstanceReference, ApplicationInstance> applicationInstances = generators.stream() - .map(generator -> generator.makeApplicationInstance(serviceStatusProvider)) - .collect(Collectors.toMap(ApplicationInstance::reference, Function.identity())); + public ServiceModel toServiceModel(List<ApplicationInfo> allApplicationInfos, + Zone zone, + ServiceStatusProvider serviceStatusProvider) { + Map<ApplicationInstanceReference, ApplicationInstance> applicationInstances = + allApplicationInfos.stream() + .map(info -> new ApplicationInstanceGenerator(info, zone) + .makeApplicationInstance(serviceStatusProvider)) + .collect(Collectors.toMap(ApplicationInstance::reference, Function.identity())); return new ServiceModel(applicationInstances); } + } diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/MonitorManager.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/MonitorManager.java index 49863672c43..1edf3a18215 100644 --- a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/MonitorManager.java +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/MonitorManager.java @@ -1,11 +1,10 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.service.monitor.internal;// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.service.monitor.internal; -import com.yahoo.config.model.api.SuperModelListener; import com.yahoo.vespa.service.monitor.ServiceStatusProvider; /** * @author hakon */ -public interface MonitorManager extends SuperModelListener, ServiceStatusProvider { +public interface MonitorManager extends DuperModelListener, ServiceStatusProvider { } diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/ServiceId.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/ServiceId.java new file mode 100644 index 00000000000..993ea7fed5c --- /dev/null +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/ServiceId.java @@ -0,0 +1,75 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.service.monitor.internal; + +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.vespa.applicationmodel.ClusterId; +import com.yahoo.vespa.applicationmodel.ConfigId; +import com.yahoo.vespa.applicationmodel.ServiceType; + +import javax.annotation.concurrent.Immutable; +import java.util.Objects; + +/** + * Identifies a service. + * + * @author hakon + */ +@Immutable +public class ServiceId { + private final ApplicationId applicationId; + private final ClusterId clusterId; + private final ServiceType serviceType; + private final ConfigId configId; + + public ServiceId(ApplicationId applicationId, + ClusterId clusterId, + ServiceType serviceType, + ConfigId configId) { + this.applicationId = applicationId; + this.clusterId = clusterId; + this.serviceType = serviceType; + this.configId = configId; + } + + public ApplicationId getApplicationId() { + return applicationId; + } + + public ClusterId getClusterId() { + return clusterId; + } + + public ServiceType getServiceType() { + return serviceType; + } + + public ConfigId getConfigId() { + return configId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ServiceId serviceId = (ServiceId) o; + return Objects.equals(applicationId, serviceId.applicationId) && + Objects.equals(clusterId, serviceId.clusterId) && + Objects.equals(serviceType, serviceId.serviceType) && + Objects.equals(configId, serviceId.configId); + } + + @Override + public int hashCode() { + return Objects.hash(applicationId, clusterId, serviceType, configId); + } + + @Override + public String toString() { + return "ServiceId{" + + "applicationId=" + applicationId + + ", clusterId=" + clusterId + + ", serviceType=" + serviceType + + ", configId=" + configId + + '}'; + } +} diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/ServiceMonitorImpl.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/ServiceMonitorImpl.java index 8183d5e1960..bd8fd4a50e0 100644 --- a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/ServiceMonitorImpl.java +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/ServiceMonitorImpl.java @@ -14,9 +14,7 @@ import com.yahoo.vespa.service.monitor.ServiceMonitor; import com.yahoo.vespa.service.monitor.internal.health.HealthMonitorManager; import com.yahoo.vespa.service.monitor.internal.slobrok.SlobrokMonitorManagerImpl; -import java.util.List; import java.util.Map; -import java.util.stream.Collectors; public class ServiceMonitorImpl implements ServiceMonitor { private final ServiceModelCache serviceModelCache; @@ -31,26 +29,20 @@ public class ServiceMonitorImpl implements ServiceMonitor { Zone zone = superModelProvider.getZone(); ServiceMonitorMetrics metrics = new ServiceMonitorMetrics(metric, timer); - UnionMonitorManager monitorManager = new UnionMonitorManager( - slobrokMonitorManager, - healthMonitorManager, - configserverConfig); + DuperModel duperModel = new DuperModel(configserverConfig); + UnionMonitorManager monitorManager = + new UnionMonitorManager(slobrokMonitorManager, healthMonitorManager); SuperModelListenerImpl superModelListener = new SuperModelListenerImpl( monitorManager, metrics, - new ModelGenerator(toConfigServerList(configserverConfig)), + duperModel, + new ModelGenerator(), zone); superModelListener.start(superModelProvider); serviceModelCache = new ServiceModelCache(superModelListener, timer); } - private List<String> toConfigServerList(ConfigserverConfig configserverConfig) { - return configserverConfig.zookeeperserver().stream() - .map(ConfigserverConfig.Zookeeperserver::hostname) - .collect(Collectors.toList()); - } - @Override public Map<ApplicationInstanceReference, ApplicationInstance> getAllApplicationInstances() { return serviceModelCache.get().getAllApplicationInstances(); diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/SuperModelListenerImpl.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/SuperModelListenerImpl.java index b2f3617131b..f509809c33d 100644 --- a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/SuperModelListenerImpl.java +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/SuperModelListenerImpl.java @@ -8,7 +8,9 @@ import com.yahoo.config.model.api.SuperModelProvider; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Zone; import com.yahoo.vespa.service.monitor.ServiceModel; +import com.yahoo.vespa.service.monitor.ServiceStatusProvider; +import java.util.List; import java.util.function.Supplier; import java.util.logging.Logger; @@ -16,6 +18,7 @@ public class SuperModelListenerImpl implements SuperModelListener, Supplier<Serv private static final Logger logger = Logger.getLogger(SuperModelListenerImpl.class.getName()); private final ServiceMonitorMetrics metrics; + private final DuperModel duperModel; private final ModelGenerator modelGenerator; private final Zone zone; @@ -27,10 +30,12 @@ public class SuperModelListenerImpl implements SuperModelListener, Supplier<Serv SuperModelListenerImpl(MonitorManager monitorManager, ServiceMonitorMetrics metrics, + DuperModel duperModel, ModelGenerator modelGenerator, Zone zone) { this.monitorManager = monitorManager; this.metrics = metrics; + this.duperModel = duperModel; this.modelGenerator = modelGenerator; this.zone = zone; } @@ -41,8 +46,7 @@ public class SuperModelListenerImpl implements SuperModelListener, Supplier<Serv // since applicationActivated()/applicationRemoved() may be called // asynchronously even before snapshot() returns. this.superModel = superModelProvider.snapshot(this); - superModel.getAllApplicationInfos().stream().forEach(application -> - monitorManager.applicationActivated(superModel, application)); + duperModel.getApplicationInfos(superModel).forEach(monitorManager::applicationActivated); } } @@ -50,7 +54,7 @@ public class SuperModelListenerImpl implements SuperModelListener, Supplier<Serv public void applicationActivated(SuperModel superModel, ApplicationInfo application) { synchronized (monitor) { this.superModel = superModel; - monitorManager.applicationActivated(superModel, application); + monitorManager.applicationActivated(application); } } @@ -58,7 +62,7 @@ public class SuperModelListenerImpl implements SuperModelListener, Supplier<Serv public void applicationRemoved(SuperModel superModel, ApplicationId id) { synchronized (monitor) { this.superModel = superModel; - monitorManager.applicationRemoved(superModel, id); + monitorManager.applicationRemoved(id); } } @@ -71,7 +75,9 @@ public class SuperModelListenerImpl implements SuperModelListener, Supplier<Serv dummy(measurement); // WARNING: The slobrok monitor manager may be out-of-sync with super model (no locking) - return modelGenerator.toServiceModel(superModel, zone, monitorManager); + List<ApplicationInfo> applicationInfos = duperModel.getApplicationInfos(superModel); + + return modelGenerator.toServiceModel(applicationInfos, zone, (ServiceStatusProvider) monitorManager); } } diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/UnionMonitorManager.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/UnionMonitorManager.java index 82d2043bd17..81cf6f2af5e 100644 --- a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/UnionMonitorManager.java +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/UnionMonitorManager.java @@ -1,16 +1,12 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.service.monitor.internal; -import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.config.model.api.ApplicationInfo; -import com.yahoo.config.model.api.SuperModel; import com.yahoo.config.provision.ApplicationId; import com.yahoo.vespa.applicationmodel.ClusterId; import com.yahoo.vespa.applicationmodel.ConfigId; import com.yahoo.vespa.applicationmodel.ServiceStatus; import com.yahoo.vespa.applicationmodel.ServiceType; -import com.yahoo.vespa.service.monitor.application.ConfigServerApplication; -import com.yahoo.vespa.service.monitor.application.ZoneApplication; import com.yahoo.vespa.service.monitor.internal.health.HealthMonitorManager; import com.yahoo.vespa.service.monitor.internal.slobrok.SlobrokMonitorManagerImpl; @@ -20,14 +16,11 @@ import com.yahoo.vespa.service.monitor.internal.slobrok.SlobrokMonitorManagerImp public class UnionMonitorManager implements MonitorManager { private final SlobrokMonitorManagerImpl slobrokMonitorManager; private final HealthMonitorManager healthMonitorManager; - private final ConfigserverConfig configserverConfig; UnionMonitorManager(SlobrokMonitorManagerImpl slobrokMonitorManager, - HealthMonitorManager healthMonitorManager, - ConfigserverConfig configserverConfig) { + HealthMonitorManager healthMonitorManager) { this.slobrokMonitorManager = slobrokMonitorManager; this.healthMonitorManager = healthMonitorManager; - this.configserverConfig = configserverConfig; } @Override @@ -35,33 +28,25 @@ public class UnionMonitorManager implements MonitorManager { ClusterId clusterId, ServiceType serviceType, ConfigId configId) { - - if (applicationId.equals(ConfigServerApplication.CONFIG_SERVER_APPLICATION.getApplicationId())) { - // todo: use health - return ServiceStatus.NOT_CHECKED; + // Trust the new health monitoring status if it actually monitors the particular service. + ServiceStatus status = healthMonitorManager.getStatus(applicationId, clusterId, serviceType, configId); + if (status != ServiceStatus.NOT_CHECKED) { + return status; } - MonitorManager monitorManager = useHealth(applicationId, clusterId, serviceType) ? - healthMonitorManager : - slobrokMonitorManager; - - return monitorManager.getStatus(applicationId, clusterId, serviceType, configId); + // fallback is the older slobrok + return slobrokMonitorManager.getStatus(applicationId, clusterId, serviceType, configId); } @Override - public void applicationActivated(SuperModel superModel, ApplicationInfo application) { - slobrokMonitorManager.applicationActivated(superModel, application); - healthMonitorManager.applicationActivated(superModel, application); + public void applicationActivated(ApplicationInfo application) { + slobrokMonitorManager.applicationActivated(application); + healthMonitorManager.applicationActivated(application); } @Override - public void applicationRemoved(SuperModel superModel, ApplicationId id) { - slobrokMonitorManager.applicationRemoved(superModel, id); - healthMonitorManager.applicationRemoved(superModel, id); - } - - private boolean useHealth(ApplicationId applicationId, ClusterId clusterId, ServiceType serviceType) { - return !configserverConfig.nodeAdminInContainer() && - ZoneApplication.isNodeAdminService(applicationId, clusterId, serviceType); + public void applicationRemoved(ApplicationId id) { + slobrokMonitorManager.applicationRemoved(id); + healthMonitorManager.applicationRemoved(id); } } diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/ApplicationHealthMonitor.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/ApplicationHealthMonitor.java new file mode 100644 index 00000000000..e3d35f6d6e9 --- /dev/null +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/ApplicationHealthMonitor.java @@ -0,0 +1,112 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.service.monitor.internal.health; + +import com.yahoo.config.model.api.ApplicationInfo; +import com.yahoo.config.model.api.HostInfo; +import com.yahoo.config.model.api.PortInfo; +import com.yahoo.config.model.api.ServiceInfo; +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.vespa.applicationmodel.ClusterId; +import com.yahoo.vespa.applicationmodel.ConfigId; +import com.yahoo.vespa.applicationmodel.ServiceStatus; +import com.yahoo.vespa.applicationmodel.ServiceType; +import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider; +import com.yahoo.vespa.service.monitor.ServiceStatusProvider; +import com.yahoo.vespa.service.monitor.application.ApplicationInstanceGenerator; +import com.yahoo.vespa.service.monitor.internal.ServiceId; + +import java.net.URL; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import static com.yahoo.yolean.Exceptions.uncheck; + +/** + * Responsible for monitoring a whole application using /state/v1/health. + * + * @author hakon + */ +public class ApplicationHealthMonitor implements ServiceStatusProvider, AutoCloseable { + private final Map<ServiceId, HealthMonitor> healthMonitors; + + public static ApplicationHealthMonitor startMonitoring( + ApplicationInfo application, + ServiceIdentityProvider identityProvider) { + return new ApplicationHealthMonitor(makeHealthMonitors(application, identityProvider)); + } + + private ApplicationHealthMonitor(Map<ServiceId, HealthMonitor> healthMonitors) { + this.healthMonitors = healthMonitors; + } + + @Override + public ServiceStatus getStatus(ApplicationId applicationId, + ClusterId clusterId, + ServiceType serviceType, + ConfigId configId) { + ServiceId serviceId = new ServiceId(applicationId, clusterId, serviceType, configId); + HealthMonitor monitor = healthMonitors.get(serviceId); + if (monitor == null) { + return ServiceStatus.NOT_CHECKED; + } + + return monitor.getStatus(); + } + + @Override + public void close() { + healthMonitors.values().forEach(HealthMonitor::close); + healthMonitors.clear(); + } + + private static Map<ServiceId, HealthMonitor> makeHealthMonitors( + ApplicationInfo application, + ServiceIdentityProvider identityProvider) { + Map<ServiceId, HealthMonitor> healthMonitors = new HashMap<>(); + for (HostInfo hostInfo : application.getModel().getHosts()) { + for (ServiceInfo serviceInfo : hostInfo.getServices()) { + for (PortInfo portInfo : serviceInfo.getPorts()) { + maybeCreateHealthMonitor( + application, + hostInfo, + serviceInfo, + portInfo, + identityProvider) + .ifPresent(healthMonitor -> healthMonitors.put( + ApplicationInstanceGenerator.getServiceId(application, serviceInfo), + healthMonitor)); + } + } + } + return healthMonitors; + } + + private static Optional<HealthMonitor> maybeCreateHealthMonitor( + ApplicationInfo applicationInfo, + HostInfo hostInfo, + ServiceInfo serviceInfo, + PortInfo portInfo, + ServiceIdentityProvider identityProvider) { + Collection<String> portTags = portInfo.getTags(); + if (portTags.contains("STATE")) { + if (portTags.contains("HTTPS")) { + URL url = uncheck(() -> new URL( + "https", + hostInfo.getHostname(), + portInfo.getPort(), + "/state/v1/health")); + // todo: get hostname verifier + // "vespa.vespa[.cd].provider_%s_%s" from AthenzProviderServiceConfig + // new AthenzIdentityVerifier(Collections.singleton("vespa.vespa[.cd].provider_%s_%s")); + // HealthEndpoint healthEndpoint = HealthEndpoint.forHttps(...); + // HealthMonitor healthMonitor = new HealthMonitor(url, identityProvider, hostnameVerifier); + // healthMonitor.startMonitoring() + return Optional.empty(); + } + } + + return Optional.empty(); + } +} diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthClient.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthClient.java new file mode 100644 index 00000000000..1ecdf432ada --- /dev/null +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthClient.java @@ -0,0 +1,154 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.service.monitor.internal.health; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.yahoo.config.provision.HostName; +import com.yahoo.vespa.athenz.api.AthenzService; +import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.ConnectionKeepAliveStrategy; +import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.BasicHttpClientConnectionManager; +import org.apache.http.protocol.HttpContext; +import org.apache.http.util.EntityUtils; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import java.net.URL; + +import static com.yahoo.yolean.Exceptions.uncheck; + +/** + * @author hakon + */ +public class HealthClient implements AutoCloseable, ServiceIdentityProvider.Listener { + private static final ObjectMapper mapper = new ObjectMapper(); + private static final long MAX_CONTENT_LENGTH = 1L << 20; // 1 MB + private static final int DEFAULT_TIMEOUT_MILLIS = 1_000; + + private static final ConnectionKeepAliveStrategy KEEP_ALIVE_STRATEGY = + new DefaultConnectionKeepAliveStrategy() { + @Override + public long getKeepAliveDuration(HttpResponse response, HttpContext context) { + long keepAlive = super.getKeepAliveDuration(response, context); + if (keepAlive == -1) { + // Keep connections alive 60 seconds if a keep-alive value + // has not be explicitly set by the server + keepAlive = 60000; + } + return keepAlive; + } + }; + + private final URL url; + private final ServiceIdentityProvider serviceIdentityProvider; + private final HostnameVerifier hostnameVerifier; + + private volatile CloseableHttpClient httpClient; + + public HealthClient(HostName hostname, + int port, + ServiceIdentityProvider identityProvider, + HostnameVerifier hostnameVerifier) { + this(uncheck(() -> new URL("https", hostname.value(), port, "/state/v1/health")), + identityProvider, + hostnameVerifier); + } + + public HealthClient(URL stateV1HealthEndpoint, + ServiceIdentityProvider serviceIdentityProvider, + HostnameVerifier hostnameVerifier) { + this.url = stateV1HealthEndpoint; + this.serviceIdentityProvider = serviceIdentityProvider; + this.hostnameVerifier = hostnameVerifier; + + onCredentialsUpdate(serviceIdentityProvider.getIdentitySslContext(), null); + serviceIdentityProvider.addIdentityListener(this); + } + + @Override + public void onCredentialsUpdate(SSLContext sslContext, AthenzService ignored) { + SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier); + + Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create() + .register("https", socketFactory) + .build(); + + HttpClientConnectionManager connectionManager = new BasicHttpClientConnectionManager(registry); + + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(DEFAULT_TIMEOUT_MILLIS) // establishment of connection + .setConnectionRequestTimeout(DEFAULT_TIMEOUT_MILLIS) // connection from connection manager + .setSocketTimeout(DEFAULT_TIMEOUT_MILLIS) // waiting for data + .build(); + + this.httpClient = HttpClients.custom() + .setKeepAliveStrategy(KEEP_ALIVE_STRATEGY) + .setConnectionManager(connectionManager) + .disableAutomaticRetries() + .setDefaultRequestConfig(requestConfig) + .build(); + } + + public HealthInfo getHealthInfo() { + try { + return probeHealth(); + } catch (Exception e) { + return HealthInfo.fromException(e); + } + } + + @Override + public void close() { + serviceIdentityProvider.removeIdentityListener(this); + + try { + httpClient.close(); + } catch (Exception e) { + // ignore + } + httpClient = null; + } + + private HealthInfo probeHealth() throws Exception { + HttpGet httpget = new HttpGet(url.toString()); + CloseableHttpResponse httpResponse; + + CloseableHttpClient httpClient = this.httpClient; + if (httpClient == null) { + throw new IllegalStateException("HTTP client has closed"); + } + + httpResponse = httpClient.execute(httpget); + + int httpStatusCode = httpResponse.getStatusLine().getStatusCode(); + if (httpStatusCode < 200 || httpStatusCode >= 300) { + return HealthInfo.fromBadHttpStatusCode(httpStatusCode); + } + + HttpEntity bodyEntity = httpResponse.getEntity(); + long contentLength = bodyEntity.getContentLength(); + if (contentLength > MAX_CONTENT_LENGTH) { + throw new IllegalArgumentException("Content too long: " + contentLength + " bytes"); + } + String body = EntityUtils.toString(bodyEntity); + HealthResponse healthResponse = mapper.readValue(body, HealthResponse.class); + + if (healthResponse.status == null || healthResponse.status.code == null) { + return HealthInfo.fromHealthStatusCode(HealthResponse.Status.DEFAULT_STATUS); + } else { + return HealthInfo.fromHealthStatusCode(healthResponse.status.code); + } + } +} diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthEndpoint.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthEndpoint.java new file mode 100644 index 00000000000..d3b81c213dc --- /dev/null +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthEndpoint.java @@ -0,0 +1,39 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.service.monitor.internal.health; + +import com.yahoo.config.provision.HostName; +import com.yahoo.vespa.athenz.api.AthenzIdentity; +import com.yahoo.vespa.athenz.tls.AthenzIdentityVerifier; + +import javax.net.ssl.HostnameVerifier; +import java.net.URL; +import java.util.Collections; + +import static com.yahoo.yolean.Exceptions.uncheck; + +/** + * @author hakon + */ +class HealthEndpoint { + private final URL url; + private final HostnameVerifier hostnameVerifier; + + static HealthEndpoint forHttps(HostName hostname, int port, AthenzIdentity remoteIdentity) { + URL url = uncheck(() -> new URL("https", hostname.value(), port, "/state/v1/health")); + HostnameVerifier peerVerifier = new AthenzIdentityVerifier(Collections.singleton(remoteIdentity)); + return new HealthEndpoint(url, peerVerifier); + } + + private HealthEndpoint(URL url, HostnameVerifier hostnameVerifier) { + this.url = url; + this.hostnameVerifier = hostnameVerifier; + } + + public URL getStateV1HealthUrl() { + return url; + } + + public HostnameVerifier getHostnameVerifier() { + return hostnameVerifier; + } +} diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthInfo.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthInfo.java new file mode 100644 index 00000000000..a3fe3cb3106 --- /dev/null +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthInfo.java @@ -0,0 +1,75 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.service.monitor.internal.health; + +import com.yahoo.vespa.applicationmodel.ServiceStatus; +import com.yahoo.yolean.Exceptions; + +import java.time.Instant; +import java.util.Optional; +import java.util.OptionalInt; + +/** + * The result of a health lookup. + * + * @author hakon + */ +public class HealthInfo { + public static final String UP_STATUS_CODE = "up"; + + private final Optional<Exception> exception; + private final OptionalInt httpStatusCode; + private final Optional<String> healthStatusCode; + private final Instant time; + + static HealthInfo fromException(Exception exception) { + return new HealthInfo(Optional.of(exception), OptionalInt.empty(), Optional.empty()); + } + + static HealthInfo fromBadHttpStatusCode(int httpStatusCode) { + return new HealthInfo(Optional.empty(), OptionalInt.of(httpStatusCode), Optional.empty()); + } + + static HealthInfo fromHealthStatusCode(String healthStatusCode) { + return new HealthInfo(Optional.empty(), OptionalInt.empty(), Optional.of(healthStatusCode)); + } + + static HealthInfo empty() { + return new HealthInfo(Optional.empty(), OptionalInt.empty(), Optional.empty()); + } + + private HealthInfo(Optional<Exception> exception, + OptionalInt httpStatusCode, + Optional<String> healthStatusCode) { + this.exception = exception; + this.httpStatusCode = httpStatusCode; + this.healthStatusCode = healthStatusCode; + this.time = Instant.now(); + } + + public boolean isHealthy() { + return healthStatusCode.map(UP_STATUS_CODE::equals).orElse(false); + } + + public ServiceStatus toSerivceStatus() { + return isHealthy() ? ServiceStatus.UP : ServiceStatus.DOWN; + } + + public Instant time() { + return time; + } + + @Override + public String toString() { + if (isHealthy()) { + return UP_STATUS_CODE; + } else if (healthStatusCode.isPresent()) { + return "Bad health status code '" + healthStatusCode.get() + "'"; + } else if (exception.isPresent()) { + return Exceptions.toMessageString(exception.get()); + } else if (httpStatusCode.isPresent()) { + return "Bad HTTP response status code " + httpStatusCode.getAsInt(); + } else { + return "No health info available"; + } + } +} diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthMonitor.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthMonitor.java new file mode 100644 index 00000000000..2ad623f15cf --- /dev/null +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthMonitor.java @@ -0,0 +1,77 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.service.monitor.internal.health; + +import com.yahoo.log.LogLevel; +import com.yahoo.vespa.applicationmodel.ServiceStatus; +import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider; + +import javax.net.ssl.HostnameVerifier; +import java.net.URL; +import java.time.Duration; +import java.util.Random; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +/** + * Used to monitor the health of a single URL endpoint. + * + * @author hakon + */ +public class HealthMonitor implements AutoCloseable { + private static final Logger logger = Logger.getLogger(HealthMonitor.class.getName()); + private static final Duration DELAY = Duration.ofSeconds(20); + // About 'static': Javadoc says "Instances of java.util.Random are threadsafe." + private static final Random random = new Random(); + + private final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1); + private final HealthClient healthClient; + + private volatile HealthInfo lastHealthInfo = HealthInfo.empty(); + + public HealthMonitor(URL stateV1HealthEndpoint, + ServiceIdentityProvider identityProvider, + HostnameVerifier hostnameVerifier) { + this.healthClient = new HealthClient(stateV1HealthEndpoint, identityProvider, hostnameVerifier); + } + + /** For testing. */ + HealthMonitor(HealthClient healthClient) { + this.healthClient = healthClient; + } + + public void startMonitoring() { + executor.scheduleWithFixedDelay( + this::updateSynchronously, + initialDelayInSeconds(DELAY.getSeconds()), + DELAY.getSeconds(), + TimeUnit.SECONDS); + } + + public ServiceStatus getStatus() { + // todo: return lastHealthInfo.toServiceStatus(); + return ServiceStatus.NOT_CHECKED; + } + + @Override + public void close() { + executor.shutdown(); + + try { + executor.awaitTermination(2, TimeUnit.SECONDS); + } catch (InterruptedException e) { + logger.log(LogLevel.INFO, "Interrupted while waiting for health monitor termination: " + + e.getMessage()); + } + + healthClient.close(); + } + + private long initialDelayInSeconds(long maxInitialDelayInSeconds) { + return random.nextLong() % maxInitialDelayInSeconds; + } + + private void updateSynchronously() { + lastHealthInfo = healthClient.getHealthInfo(); + } +} diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthMonitorManager.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthMonitorManager.java index 5a4b7251ae2..54d26798ad2 100644 --- a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthMonitorManager.java +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthMonitorManager.java @@ -2,29 +2,52 @@ package com.yahoo.vespa.service.monitor.internal.health; import com.google.inject.Inject; +import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.config.model.api.ApplicationInfo; -import com.yahoo.config.model.api.SuperModel; import com.yahoo.config.provision.ApplicationId; import com.yahoo.vespa.applicationmodel.ClusterId; import com.yahoo.vespa.applicationmodel.ConfigId; import com.yahoo.vespa.applicationmodel.ServiceStatus; import com.yahoo.vespa.applicationmodel.ServiceType; +import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider; import com.yahoo.vespa.service.monitor.application.ZoneApplication; import com.yahoo.vespa.service.monitor.internal.MonitorManager; +import java.util.HashMap; +import java.util.Map; + /** * @author hakon */ public class HealthMonitorManager implements MonitorManager { + private final Map<ApplicationId, ApplicationHealthMonitor> healthMonitors = new HashMap<>(); + private final ConfigserverConfig configserverConfig; + private final ServiceIdentityProvider serviceIdentityProvider; + @Inject - public HealthMonitorManager() {} + public HealthMonitorManager(ConfigserverConfig configserverConfig, + ServiceIdentityProvider serviceIdentityProvider) { + this.configserverConfig = configserverConfig; + this.serviceIdentityProvider = serviceIdentityProvider; + } @Override - public void applicationActivated(SuperModel superModel, ApplicationInfo application) { + public void applicationActivated(ApplicationInfo application) { + if (applicationMonitored(application.getApplicationId())) { + ApplicationHealthMonitor monitor = + ApplicationHealthMonitor.startMonitoring(application, serviceIdentityProvider); + healthMonitors.put(application.getApplicationId(), monitor); + } } @Override - public void applicationRemoved(SuperModel superModel, ApplicationId id) { + public void applicationRemoved(ApplicationId id) { + if (applicationMonitored(id)) { + ApplicationHealthMonitor monitor = healthMonitors.remove(id); + if (monitor != null) { + monitor.close(); + } + } } @Override @@ -32,13 +55,18 @@ public class HealthMonitorManager implements MonitorManager { ClusterId clusterId, ServiceType serviceType, ConfigId configId) { - // TODO: Do proper health check - if (ZoneApplication.isNodeAdminService(applicationId, clusterId, serviceType)) { + if (!configserverConfig.nodeAdminInContainer() && + ZoneApplication.isNodeAdminService(applicationId, clusterId, serviceType)) { + // If node admin doesn't run in a JDisc container, it must be monitored with health. + // TODO: Do proper health check return ServiceStatus.UP; } - throw new IllegalArgumentException("Health monitoring not implemented for application " + - applicationId.toShortString() + ", cluster " + clusterId.s() + ", serviceType " + - serviceType); + return ServiceStatus.NOT_CHECKED; + } + + private boolean applicationMonitored(ApplicationId id) { + // todo: health-check config server + return false; } } diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthResponse.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthResponse.java new file mode 100644 index 00000000000..574523ad564 --- /dev/null +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthResponse.java @@ -0,0 +1,35 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.service.monitor.internal.health; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.yahoo.text.JSON; + +/** + * Response entity from /state/v1/health + * + * @author hakon + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class HealthResponse { + @JsonProperty("status") + public Status status = new Status(); + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Status { + public static final String DEFAULT_STATUS = "down"; + + @JsonProperty("code") + public String code = DEFAULT_STATUS; + + @Override + public String toString() { + return "{ \"code\": \"" + JSON.escape(code) + "\" }"; + } + } + + @Override + public String toString() { + return "{ \"status\": " + status.toString() + " }"; + } +} diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/slobrok/SlobrokMonitorManagerImpl.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/slobrok/SlobrokMonitorManagerImpl.java index aaaab22e742..68958c94dfd 100644 --- a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/slobrok/SlobrokMonitorManagerImpl.java +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/slobrok/SlobrokMonitorManagerImpl.java @@ -3,8 +3,6 @@ package com.yahoo.vespa.service.monitor.internal.slobrok; import com.google.inject.Inject; import com.yahoo.config.model.api.ApplicationInfo; -import com.yahoo.config.model.api.SuperModel; -import com.yahoo.config.model.api.SuperModelListener; import com.yahoo.config.provision.ApplicationId; import com.yahoo.jrt.slobrok.api.Mirror; import com.yahoo.log.LogLevel; @@ -13,6 +11,7 @@ import com.yahoo.vespa.applicationmodel.ConfigId; import com.yahoo.vespa.applicationmodel.ServiceStatus; import com.yahoo.vespa.applicationmodel.ServiceType; import com.yahoo.vespa.service.monitor.SlobrokApi; +import com.yahoo.vespa.service.monitor.application.ConfigServerApplication; import com.yahoo.vespa.service.monitor.internal.MonitorManager; import java.util.HashMap; @@ -21,7 +20,7 @@ import java.util.Optional; import java.util.function.Supplier; import java.util.logging.Logger; -public class SlobrokMonitorManagerImpl implements SuperModelListener, SlobrokApi, MonitorManager { +public class SlobrokMonitorManagerImpl implements SlobrokApi, MonitorManager { private static final Logger logger = Logger.getLogger(SlobrokMonitorManagerImpl.class.getName()); @@ -40,7 +39,11 @@ public class SlobrokMonitorManagerImpl implements SuperModelListener, SlobrokApi } @Override - public void applicationActivated(SuperModel superModel, ApplicationInfo application) { + public void applicationActivated(ApplicationInfo application) { + if (!applicationMonitoredWithSlobrok(application.getApplicationId())) { + return; + } + synchronized (monitor) { SlobrokMonitor slobrokMonitor = slobrokMonitors.computeIfAbsent( application.getApplicationId(), @@ -50,7 +53,11 @@ public class SlobrokMonitorManagerImpl implements SuperModelListener, SlobrokApi } @Override - public void applicationRemoved(SuperModel superModel, ApplicationId id) { + public void applicationRemoved(ApplicationId id) { + if (!applicationMonitoredWithSlobrok(id)) { + return; + } + synchronized (monitor) { SlobrokMonitor slobrokMonitor = slobrokMonitors.remove(id); if (slobrokMonitor == null) { @@ -79,6 +86,10 @@ public class SlobrokMonitorManagerImpl implements SuperModelListener, SlobrokApi ClusterId clusterId, ServiceType serviceType, ConfigId configId) { + if (!applicationMonitoredWithSlobrok(applicationId)) { + return ServiceStatus.NOT_CHECKED; + } + Optional<String> slobrokServiceName = findSlobrokServiceName(serviceType, configId); if (slobrokServiceName.isPresent()) { synchronized (monitor) { @@ -95,6 +106,14 @@ public class SlobrokMonitorManagerImpl implements SuperModelListener, SlobrokApi } } + private boolean applicationMonitoredWithSlobrok(ApplicationId applicationId) { + if (applicationId.equals(ConfigServerApplication.CONFIG_SERVER_APPLICATION.getApplicationId())) { + return false; + } + + return true; + } + /** * Get the Slobrok service name of the service, or empty if the service * is not registered with Slobrok. diff --git a/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/application/ConfigServerAppGeneratorTest.java b/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/application/ApplicationInstanceGeneratorTest.java index 58f99786017..899cc59bb34 100644 --- a/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/application/ConfigServerAppGeneratorTest.java +++ b/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/application/ApplicationInstanceGeneratorTest.java @@ -1,22 +1,27 @@ // 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.application; +import com.yahoo.cloud.config.ConfigserverConfig; +import com.yahoo.config.model.api.ApplicationInfo; +import com.yahoo.config.provision.Zone; import com.yahoo.vespa.applicationmodel.ApplicationInstance; import com.yahoo.vespa.applicationmodel.ServiceStatus; import com.yahoo.vespa.service.monitor.ServiceStatusProvider; +import com.yahoo.vespa.service.monitor.internal.ConfigserverUtil; import org.junit.Test; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; +import static com.yahoo.vespa.service.monitor.application.ConfigServerApplication.CONFIG_SERVER_APPLICATION; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class ConfigServerAppGeneratorTest { +public class ApplicationInstanceGeneratorTest { private static final String configServer1 = "cfg1.yahoo.com"; private static final String configServer2 = "cfg2.yahoo.com"; private static final String configServer3 = "cfg3.yahoo.com"; @@ -28,9 +33,17 @@ public class ConfigServerAppGeneratorTest { private final ServiceStatusProvider statusProvider = mock(ServiceStatusProvider.class); @Test - public void toApplicationInstance() throws Exception { + public void toApplicationInstance() { when(statusProvider.getStatus(any(), any(), any(), any())).thenReturn(ServiceStatus.NOT_CHECKED); - ApplicationInstance applicationInstance = new ConfigServerAppGenerator(configServerList) + ConfigserverConfig config = ConfigserverUtil.create( + true, + true, + configServer1, + configServer2, + configServer3); + Zone zone = mock(Zone.class); + ApplicationInfo configServer = CONFIG_SERVER_APPLICATION.makeApplicationInfo(config); + ApplicationInstance applicationInstance = new ApplicationInstanceGenerator(configServer, zone) .makeApplicationInstance(statusProvider); assertEquals( diff --git a/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/ConfigserverUtil.java b/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/ConfigserverUtil.java new file mode 100644 index 00000000000..8ad153ba8e7 --- /dev/null +++ b/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/ConfigserverUtil.java @@ -0,0 +1,48 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.service.monitor.internal; + +import com.yahoo.cloud.config.ConfigserverConfig; +import com.yahoo.config.model.api.ApplicationInfo; +import com.yahoo.vespa.service.monitor.application.ConfigServerApplication; + +/** + * @author hakon + */ +public class ConfigserverUtil { + /** Create a ConfigserverConfig with the given settings. */ + public static ConfigserverConfig create( + boolean hostedVespa, + boolean nodeAdminInContainer, + String configServerHostname1, + String configServerHostname2, + String configServerHostname3) { + return new ConfigserverConfig( + new ConfigserverConfig.Builder() + .hostedVespa(hostedVespa) + .nodeAdminInContainer(nodeAdminInContainer) + .zookeeperserver(new ConfigserverConfig.Zookeeperserver.Builder().hostname(configServerHostname1).port(1)) + .zookeeperserver(new ConfigserverConfig.Zookeeperserver.Builder().hostname(configServerHostname2).port(2)) + .zookeeperserver(new ConfigserverConfig.Zookeeperserver.Builder().hostname(configServerHostname3).port(3))); + } + + public static ConfigserverConfig createExampleConfigserverConfig(boolean nodeAdminInContainer) { + return create(true, nodeAdminInContainer, "cfg1", "cfg2", "cfg3"); + } + + public static ApplicationInfo makeConfigServerApplicationInfo( + boolean hostedVespa, + String configServerHostname1, + String configServerHostname2, + String configServerHostname3) { + return ConfigServerApplication.CONFIG_SERVER_APPLICATION.makeApplicationInfo(create( + hostedVespa, + true, + configServerHostname1, + configServerHostname2, + configServerHostname3)); + } + + public static ApplicationInfo makeExampleConfigServer() { + return makeConfigServerApplicationInfo(true, "cfg1", "cfg2", "cfg3"); + } +} diff --git a/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/DuperModelTest.java b/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/DuperModelTest.java new file mode 100644 index 00000000000..0a68b4b0ff7 --- /dev/null +++ b/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/DuperModelTest.java @@ -0,0 +1,40 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.service.monitor.internal; + +import com.yahoo.cloud.config.ConfigserverConfig; +import com.yahoo.config.model.api.ApplicationInfo; +import com.yahoo.config.model.api.SuperModel; +import com.yahoo.vespa.applicationmodel.ServiceStatus; +import com.yahoo.vespa.service.monitor.ServiceStatusProvider; +import com.yahoo.vespa.service.monitor.application.ConfigServerApplication; +import org.junit.Test; + +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author hakon + */ +public class DuperModelTest { + private final ServiceStatusProvider statusProvider = mock(ServiceStatusProvider.class); + + @Test + public void toApplicationInstance() { + when(statusProvider.getStatus(any(), any(), any(), any())).thenReturn(ServiceStatus.NOT_CHECKED); + ConfigserverConfig config = ConfigserverUtil.createExampleConfigserverConfig(true); + DuperModel duperModel = new DuperModel(config); + SuperModel superModel = mock(SuperModel.class); + ApplicationInfo superModelApplicationInfo = mock(ApplicationInfo.class); + when(superModel.getAllApplicationInfos()).thenReturn(Collections.singletonList(superModelApplicationInfo)); + List<ApplicationInfo> applicationInfos = duperModel.getApplicationInfos(superModel); + assertEquals(2, applicationInfos.size()); + assertEquals(ConfigServerApplication.CONFIG_SERVER_APPLICATION.getApplicationId(), applicationInfos.get(0).getApplicationId()); + assertSame(superModelApplicationInfo, applicationInfos.get(1)); + } +} diff --git a/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/ModelGeneratorTest.java b/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/ModelGeneratorTest.java index a21691ee4d0..72326c7352a 100644 --- a/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/ModelGeneratorTest.java +++ b/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/ModelGeneratorTest.java @@ -1,6 +1,7 @@ // 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.internal; +import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.config.model.api.SuperModel; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; @@ -15,13 +16,9 @@ import com.yahoo.vespa.service.monitor.application.ConfigServerApplication; import com.yahoo.vespa.service.monitor.internal.slobrok.SlobrokMonitorManagerImpl; import org.junit.Test; -import java.util.Collections; import java.util.Iterator; -import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.any; @@ -39,9 +36,10 @@ public class ModelGeneratorTest { SuperModel superModel = ExampleModel.createExampleSuperModelWithOneRpcPort(HOSTNAME, PORT); - List<String> configServerHosts = Stream.of("cfg1", "cfg2", "cfg3") - .collect(Collectors.toList()); - ModelGenerator modelGenerator = new ModelGenerator(configServerHosts); + ConfigserverConfig config = ConfigserverUtil.create( + true, true, "cfg1", "cfg2", "cfg3"); + DuperModel duperModel = new DuperModel(config); + ModelGenerator modelGenerator = new ModelGenerator(); Zone zone = new Zone(Environment.from(ENVIRONMENT), RegionName.from(REGION)); @@ -51,7 +49,7 @@ public class ModelGeneratorTest { ServiceModel serviceModel = modelGenerator.toServiceModel( - superModel, + duperModel.getApplicationInfos(superModel), zone, slobrokMonitorManager); @@ -80,9 +78,11 @@ public class ModelGeneratorTest { @Test public void toApplicationModel() throws Exception { - SuperModel superModel = - ExampleModel.createExampleSuperModelWithOneRpcPort(HOSTNAME, PORT); - ModelGenerator modelGenerator = new ModelGenerator(Collections.emptyList()); + SuperModel superModel = ExampleModel.createExampleSuperModelWithOneRpcPort(HOSTNAME, PORT); + ConfigserverConfig config = ConfigserverUtil.create( + false, true, "cfg1", "cfg2", "cfg3"); + DuperModel duperModel = new DuperModel(config); + ModelGenerator modelGenerator = new ModelGenerator(); Zone zone = new Zone(Environment.from(ENVIRONMENT), RegionName.from(REGION)); @@ -90,14 +90,12 @@ public class ModelGeneratorTest { when(slobrokMonitorManager.getStatus(any(), any(), any(), any())) .thenReturn(ServiceStatus.UP); - ServiceModel serviceModel = - modelGenerator.toServiceModel( - superModel, - zone, - slobrokMonitorManager); + ServiceModel serviceModel = modelGenerator.toServiceModel( + duperModel.getApplicationInfos(superModel), + zone, + slobrokMonitorManager); - Map<ApplicationInstanceReference, - ApplicationInstance> applicationInstances = + Map<ApplicationInstanceReference, ApplicationInstance> applicationInstances = serviceModel.getAllApplicationInstances(); assertEquals(1, applicationInstances.size()); diff --git a/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/SuperModelListenerImplTest.java b/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/SuperModelListenerImplTest.java index 83bad0ddb2a..eb6d6d583f7 100644 --- a/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/SuperModelListenerImplTest.java +++ b/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/SuperModelListenerImplTest.java @@ -14,6 +14,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -22,11 +23,13 @@ public class SuperModelListenerImplTest { public void sanityCheck() { SlobrokMonitorManagerImpl slobrokMonitorManager = mock(SlobrokMonitorManagerImpl.class); ServiceMonitorMetrics metrics = mock(ServiceMonitorMetrics.class); + DuperModel duperModel = mock(DuperModel.class); ModelGenerator modelGenerator = mock(ModelGenerator.class); Zone zone = mock(Zone.class); SuperModelListenerImpl listener = new SuperModelListenerImpl( slobrokMonitorManager, metrics, + duperModel, modelGenerator, zone); @@ -38,13 +41,15 @@ public class SuperModelListenerImplTest { ApplicationInfo application2 = mock(ApplicationInfo.class); List<ApplicationInfo> applications = Stream.of(application1, application2) .collect(Collectors.toList()); - when(superModel.getAllApplicationInfos()).thenReturn(applications); + when(duperModel.getApplicationInfos(superModel)).thenReturn(applications); listener.start(superModelProvider); - verify(slobrokMonitorManager).applicationActivated(superModel, application1); - verify(slobrokMonitorManager).applicationActivated(superModel, application2); + verify(duperModel, times(1)).getApplicationInfos(superModel); + verify(slobrokMonitorManager).applicationActivated(application1); + verify(slobrokMonitorManager).applicationActivated(application2); ServiceModel serviceModel = listener.get(); - verify(modelGenerator).toServiceModel(superModel, zone, slobrokMonitorManager); + verify(duperModel, times(2)).getApplicationInfos(superModel); + verify(modelGenerator).toServiceModel(applications, zone, slobrokMonitorManager); } }
\ No newline at end of file diff --git a/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/UnionMonitorManagerTest.java b/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/UnionMonitorManagerTest.java index b7c3ed8e1e1..79916e43712 100644 --- a/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/UnionMonitorManagerTest.java +++ b/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/UnionMonitorManagerTest.java @@ -1,95 +1,44 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.service.monitor.internal; -import com.yahoo.cloud.config.ConfigserverConfig; -import com.yahoo.config.provision.ApplicationId; -import com.yahoo.vespa.applicationmodel.ClusterId; import com.yahoo.vespa.applicationmodel.ConfigId; -import com.yahoo.vespa.applicationmodel.ServiceType; +import com.yahoo.vespa.applicationmodel.ServiceStatus; import com.yahoo.vespa.service.monitor.internal.health.HealthMonitorManager; import com.yahoo.vespa.service.monitor.internal.slobrok.SlobrokMonitorManagerImpl; import org.junit.Test; import static com.yahoo.vespa.applicationmodel.ClusterId.NODE_ADMIN; +import static com.yahoo.vespa.applicationmodel.ServiceStatus.*; +import static com.yahoo.vespa.applicationmodel.ServiceStatus.NOT_CHECKED; +import static com.yahoo.vespa.applicationmodel.ServiceStatus.UP; import static com.yahoo.vespa.applicationmodel.ServiceType.CONTAINER; import static com.yahoo.vespa.service.monitor.application.ZoneApplication.ZONE_APPLICATION_ID; +import static org.junit.Assert.assertSame; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class UnionMonitorManagerTest { - @Test - public void nodeAdminInContainer() { - testWith( - true, - ZONE_APPLICATION_ID, - NODE_ADMIN, - CONTAINER, - 1, - 0); - } - - @Test - public void nodeAdminOutsideContainer() { - boolean inContainer = false; - - // When nodeAdminInContainer is set, then only the node admin cluster should use health - testWith( - inContainer, - ZONE_APPLICATION_ID, - NODE_ADMIN, - CONTAINER, - 0, - 1); - - testWith( - inContainer, - ApplicationId.fromSerializedForm("a:b:default"), - NODE_ADMIN, - CONTAINER, - 1, - 0); + private final SlobrokMonitorManagerImpl slobrokMonitorManager = mock(SlobrokMonitorManagerImpl.class); + private final HealthMonitorManager healthMonitorManager = mock(HealthMonitorManager.class); - testWith( - inContainer, - ZONE_APPLICATION_ID, - new ClusterId("foo"), - CONTAINER, - 1, - 0); + private final UnionMonitorManager manager = new UnionMonitorManager( + slobrokMonitorManager, + healthMonitorManager); - testWith( - inContainer, - ZONE_APPLICATION_ID, - NODE_ADMIN, - new ServiceType("foo"), - 1, - 0); + @Test + public void verifyHealthTakesPriority() { + testWith(UP, DOWN, UP); + testWith(NOT_CHECKED, DOWN, DOWN); + testWith(NOT_CHECKED, NOT_CHECKED, NOT_CHECKED); } - private void testWith(boolean nodeAdminInContainer, - ApplicationId applicationId, - ClusterId clusterId, - ServiceType serviceType, - int expectedSlobrokCalls, - int expectedHealthCalls) { - SlobrokMonitorManagerImpl slobrokMonitorManager = mock(SlobrokMonitorManagerImpl.class); - HealthMonitorManager healthMonitorManager = mock(HealthMonitorManager.class); - - ConfigserverConfig.Builder builder = new ConfigserverConfig.Builder(); - builder.nodeAdminInContainer(nodeAdminInContainer); - ConfigserverConfig config = new ConfigserverConfig(builder); - - - UnionMonitorManager manager = new UnionMonitorManager( - slobrokMonitorManager, - healthMonitorManager, - config); - - manager.getStatus(applicationId, clusterId, serviceType, new ConfigId("config-id")); - - verify(slobrokMonitorManager, times(expectedSlobrokCalls)).getStatus(any(), any(), any(), any()); - verify(healthMonitorManager, times(expectedHealthCalls)).getStatus(any(), any(), any(), any()); + private void testWith(ServiceStatus healthStatus, + ServiceStatus slobrokStatus, + ServiceStatus expectedStatus) { + when(healthMonitorManager.getStatus(any(), any(), any(), any())).thenReturn(healthStatus); + when(slobrokMonitorManager.getStatus(any(), any(), any(), any())).thenReturn(slobrokStatus); + ServiceStatus status = manager.getStatus(ZONE_APPLICATION_ID, NODE_ADMIN, CONTAINER, new ConfigId("config-id")); + assertSame(expectedStatus, status); } }
\ No newline at end of file diff --git a/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/health/ApplicationHealthMonitorTest.java b/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/health/ApplicationHealthMonitorTest.java new file mode 100644 index 00000000000..fd96b519649 --- /dev/null +++ b/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/health/ApplicationHealthMonitorTest.java @@ -0,0 +1,28 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.service.monitor.internal.health; + +import com.yahoo.vespa.applicationmodel.ServiceStatus; +import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider; +import com.yahoo.vespa.service.monitor.application.ConfigServerApplication; +import com.yahoo.vespa.service.monitor.internal.ConfigserverUtil; +import org.junit.Test; + +import static com.yahoo.vespa.applicationmodel.ServiceStatus.NOT_CHECKED; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +public class ApplicationHealthMonitorTest { + @Test + public void sanityCheck() { + ServiceIdentityProvider provider = mock(ServiceIdentityProvider.class); + ApplicationHealthMonitor monitor = ApplicationHealthMonitor.startMonitoring( + ConfigserverUtil.makeExampleConfigServer(), + provider); + ServiceStatus status = monitor.getStatus( + ConfigServerApplication.CONFIG_SERVER_APPLICATION.getApplicationId(), + ConfigServerApplication.CLUSTER_ID, + ConfigServerApplication.SERVICE_TYPE, + ConfigServerApplication.configIdFrom(0)); + assertEquals(NOT_CHECKED, status); + } +}
\ No newline at end of file diff --git a/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/health/HealthMonitorManagerTest.java b/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/health/HealthMonitorManagerTest.java new file mode 100644 index 00000000000..b416e3f6f2a --- /dev/null +++ b/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/health/HealthMonitorManagerTest.java @@ -0,0 +1,54 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.service.monitor.internal.health; + +import com.yahoo.cloud.config.ConfigserverConfig; +import com.yahoo.config.model.api.ApplicationInfo; +import com.yahoo.vespa.applicationmodel.ClusterId; +import com.yahoo.vespa.applicationmodel.ConfigId; +import com.yahoo.vespa.applicationmodel.ServiceStatus; +import com.yahoo.vespa.applicationmodel.ServiceType; +import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider; +import com.yahoo.vespa.service.monitor.application.ZoneApplication; +import com.yahoo.vespa.service.monitor.internal.ConfigserverUtil; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +public class HealthMonitorManagerTest { + @Test + public void addRemove() { + ConfigserverConfig config = ConfigserverUtil.createExampleConfigserverConfig(true); + ServiceIdentityProvider provider = mock(ServiceIdentityProvider.class); + HealthMonitorManager manager = new HealthMonitorManager(config, provider); + ApplicationInfo applicationInfo = ConfigserverUtil.makeExampleConfigServer(); + manager.applicationActivated(applicationInfo); + manager.applicationRemoved(applicationInfo.getApplicationId()); + } + + @Test + public void withNodeAdmin() { + ConfigserverConfig config = ConfigserverUtil.createExampleConfigserverConfig(true); + ServiceIdentityProvider provider = mock(ServiceIdentityProvider.class); + HealthMonitorManager manager = new HealthMonitorManager(config, provider); + ServiceStatus status = manager.getStatus( + ZoneApplication.ZONE_APPLICATION_ID, + ClusterId.NODE_ADMIN, + ServiceType.CONTAINER, + new ConfigId("config-id-1")); + assertEquals(ServiceStatus.NOT_CHECKED, status); + } + + @Test + public void withHostAdmin() { + ConfigserverConfig config = ConfigserverUtil.createExampleConfigserverConfig(false); + ServiceIdentityProvider provider = mock(ServiceIdentityProvider.class); + HealthMonitorManager manager = new HealthMonitorManager(config, provider); + ServiceStatus status = manager.getStatus( + ZoneApplication.ZONE_APPLICATION_ID, + ClusterId.NODE_ADMIN, + ServiceType.CONTAINER, + new ConfigId("config-id-1")); + assertEquals(ServiceStatus.UP, status); + } +}
\ No newline at end of file diff --git a/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/health/HealthMonitorTest.java b/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/health/HealthMonitorTest.java new file mode 100644 index 00000000000..cca1530ad97 --- /dev/null +++ b/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/health/HealthMonitorTest.java @@ -0,0 +1,21 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.service.monitor.internal.health; + +import com.yahoo.vespa.applicationmodel.ServiceStatus; +import org.junit.Test; + +import java.net.MalformedURLException; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +public class HealthMonitorTest { + @Test + public void basicTests() throws MalformedURLException { + HealthClient healthClient = mock(HealthClient.class); + try (HealthMonitor monitor = new HealthMonitor(healthClient)) { + monitor.startMonitoring(); + assertEquals(ServiceStatus.NOT_CHECKED, monitor.getStatus()); + } + } +}
\ No newline at end of file diff --git a/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/slobrok/SlobrokMonitorManagerImplTest.java b/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/slobrok/SlobrokMonitorManagerImplTest.java index 8e4443df83b..a567559980b 100644 --- a/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/slobrok/SlobrokMonitorManagerImplTest.java +++ b/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/slobrok/SlobrokMonitorManagerImplTest.java @@ -2,7 +2,7 @@ package com.yahoo.vespa.service.monitor.internal.slobrok; import com.yahoo.config.model.api.ApplicationInfo; -import com.yahoo.config.model.api.SuperModel; +import com.yahoo.config.provision.ApplicationId; import com.yahoo.vespa.applicationmodel.ClusterId; import com.yahoo.vespa.applicationmodel.ConfigId; import com.yahoo.vespa.applicationmodel.ServiceStatus; @@ -28,18 +28,19 @@ public class SlobrokMonitorManagerImplTest { private final SlobrokMonitorManagerImpl slobrokMonitorManager = new SlobrokMonitorManagerImpl(slobrokMonitorFactory); private final SlobrokMonitor slobrokMonitor = mock(SlobrokMonitor.class); - private final SuperModel superModel = mock(SuperModel.class); + private final ApplicationId applicationId = ApplicationId.from("tenant", "app", "instance"); private final ApplicationInfo application = mock(ApplicationInfo.class); private final ClusterId clusterId = new ClusterId("cluster-id"); @Before public void setup() { when(slobrokMonitorFactory.get()).thenReturn(slobrokMonitor); + when(application.getApplicationId()).thenReturn(applicationId); } @Test public void testActivationOfApplication() { - slobrokMonitorManager.applicationActivated(superModel, application); + slobrokMonitorManager.applicationActivated(application); verify(slobrokMonitorFactory, times(1)).get(); } @@ -51,14 +52,14 @@ public class SlobrokMonitorManagerImplTest { @Test public void testGetStatus_ApplicationInSlobrok() { - slobrokMonitorManager.applicationActivated(superModel, application); + slobrokMonitorManager.applicationActivated(application); when(slobrokMonitor.registeredInSlobrok("config.id")).thenReturn(true); assertEquals(ServiceStatus.UP, getStatus("topleveldispatch")); } @Test public void testGetStatus_ServiceNotInSlobrok() { - slobrokMonitorManager.applicationActivated(superModel, application); + slobrokMonitorManager.applicationActivated(application); when(slobrokMonitor.registeredInSlobrok("config.id")).thenReturn(false); assertEquals(ServiceStatus.DOWN, getStatus("topleveldispatch")); } |