aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/Host.java10
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java7
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintenance.java2
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/FileDistributionMaintainer.java5
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java4
-rw-r--r--container-dependency-versions/pom.xml2
-rw-r--r--container-disc/src/main/java/com/yahoo/container/jdisc/metric/GarbageCollectionMetrics.java94
-rw-r--r--container-disc/src/main/java/com/yahoo/container/jdisc/metric/MetricUpdater.java6
-rw-r--r--container-disc/src/test/java/com/yahoo/container/jdisc/metric/GarbageCollectionMetricsTest.java57
-rw-r--r--container-disc/src/test/java/com/yahoo/container/jdisc/metric/MetricUpdaterTest.java5
-rw-r--r--container-jersey2/pom.xml4
-rw-r--r--container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/ComponentGraphProvider.java73
-rw-r--r--container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/JerseyApplication.java25
-rw-r--r--container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/JerseyServletProvider.java118
-rw-r--r--container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/ResourceOrProviderClassVisitor.java103
-rw-r--r--container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/ComponentGraphProvider.scala40
-rw-r--r--container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/JerseyApplication.scala16
-rw-r--r--container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/JerseyServletProvider.scala109
-rw-r--r--container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/ResourceOrProviderClassVisitor.scala74
-rw-r--r--jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerConformanceTest.java2
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java105
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java2
-rw-r--r--searchsummary/CMakeLists.txt1
-rw-r--r--searchsummary/src/tests/docsummary/attribute_combiner/CMakeLists.txt8
-rw-r--r--searchsummary/src/tests/docsummary/attribute_combiner/attribute_combiner_test.cpp217
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/CMakeLists.txt3
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/array_attribute_combiner_dfw.cpp89
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/array_attribute_combiner_dfw.h29
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/attribute_combiner_dfw.cpp141
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/attribute_combiner_dfw.h36
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/attribute_field_writer.cpp172
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/attribute_field_writer.h34
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/docsum_field_writer_state.h21
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/docsumfieldwriter.cpp6
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/docsumfieldwriter.h1
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/docsumstate.cpp2
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h2
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/docsumwriter.cpp7
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/docsumwriter.h1
-rw-r--r--service-monitor/pom.xml23
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/monitor/ServiceStatusProvider.java8
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/ApplicationInstanceGenerator.java141
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/ConfigServerAppGenerator.java67
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/ConfigServerApplication.java50
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/DeployedAppGenerator.java127
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/HostsModel.java75
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/monitor/application/ZoneApplication.java4
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/DuperModel.java42
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/DuperModelListener.java28
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/ModelGenerator.java38
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/MonitorManager.java5
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/ServiceId.java75
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/ServiceMonitorImpl.java23
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/SuperModelListenerImpl.java16
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/UnionMonitorManager.java41
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/ApplicationHealthMonitor.java102
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthClient.java139
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthEndpoint.java57
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthInfo.java75
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthMonitor.java73
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthMonitorManager.java42
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthResponse.java35
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/slobrok/SlobrokMonitorManagerImpl.java29
-rw-r--r--service-monitor/src/test/java/com/yahoo/vespa/service/monitor/application/ApplicationInstanceGeneratorTest.java (renamed from service-monitor/src/test/java/com/yahoo/vespa/service/monitor/application/ConfigServerAppGeneratorTest.java)19
-rw-r--r--service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/ConfigserverUtil.java52
-rw-r--r--service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/DuperModelTest.java53
-rw-r--r--service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/ModelGeneratorTest.java44
-rw-r--r--service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/SuperModelListenerImplTest.java13
-rw-r--r--service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/UnionMonitorManagerTest.java97
-rw-r--r--service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/health/ApplicationHealthMonitorTest.java24
-rw-r--r--service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/health/HealthMonitorManagerTest.java49
-rw-r--r--service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/health/HealthMonitorTest.java21
-rw-r--r--service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/slobrok/SlobrokMonitorManagerImplTest.java11
-rw-r--r--valgrind-suppressions.txt17
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapper.java23
75 files changed, 2634 insertions, 737 deletions
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/Host.java b/config-model/src/main/java/com/yahoo/vespa/model/Host.java
index 0adfe9e4bdb..624a9fd4da7 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/Host.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/Host.java
@@ -42,16 +42,14 @@ public final class Host extends AbstractConfigProducer<AbstractConfigProducer<?>
private void checkName(HostSystem parent, String hostname) {
// Give a warning if the host does not exist
- // Host exists - warn if given hostname is not a fully qualified one.
- String canonical = hostname;
try {
- canonical = parent.getCanonicalHostname(hostname);
+ Object address = java.net.InetAddress.getByName(hostname);
} catch (UnknownHostException e) {
- deployLogger().log(Level.WARNING, "Unable to find canonical hostname of host: " + hostname);
+ deployLogger().log(Level.WARNING, "Unable to lookup IP address of host: " + hostname);
}
- if ((null != canonical) && (! hostname.equals(canonical))) {
+ if (! hostname.contains(".")) {
deployLogger().log(Level.WARNING, "Host named '" + hostname + "' may not receive any config " +
- "since it does not match its canonical hostname: " + canonical);
+ "since it is not a canonical hostname");
}
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java
index 6467199d9f9..fc46ed18dde 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java
@@ -110,6 +110,13 @@ public class VespaMetricSet {
metrics.add(new Metric("jdisc.memory_mappings.max"));
metrics.add(new Metric("jdisc.open_file_descriptors.max"));
+ metrics.add(new Metric("jdisc.gc.count.average"));
+ metrics.add(new Metric("jdisc.gc.count.max"));
+ metrics.add(new Metric("jdisc.gc.count.last"));
+ metrics.add(new Metric("jdisc.gc.ms.average"));
+ metrics.add(new Metric("jdisc.gc.ms.max"));
+ metrics.add(new Metric("jdisc.gc.ms.last"));
+
metrics.add(new Metric("jdisc.deactivated_containers.total.last"));
metrics.add(new Metric("jdisc.deactivated_containers.with_retained_refs.last"));
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintenance.java b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintenance.java
index c6a390caf86..2a53f9ee45c 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintenance.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintenance.java
@@ -51,7 +51,7 @@ public class ConfigServerMaintenance extends AbstractComponent {
this.defaultInterval = Duration.ofMinutes(configserverConfig.maintainerIntervalMinutes());
// TODO: Want job control or feature flag to control when to run this, for now use a very
// long interval to avoid running the maintainer
- this.tenantsMaintainerInterval = isCd || isTest
+ this.tenantsMaintainerInterval = isCd || isTest || configserverConfig.region().equals("us-central-1")
? defaultInterval
: Duration.ofMinutes(configserverConfig.tenantsMaintainerIntervalMinutes());
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/FileDistributionMaintainer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/FileDistributionMaintainer.java
index 2664a0bde8c..1d16283d938 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/FileDistributionMaintainer.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/FileDistributionMaintainer.java
@@ -31,9 +31,10 @@ public class FileDistributionMaintainer extends Maintainer {
@Override
protected void maintain() {
- // TODO: For now only deletes files in CD system
+ // TODO: Delete files in all zones
boolean deleteFiles = (SystemName.from(configserverConfig.system()) == SystemName.cd)
- || Environment.from(configserverConfig.environment()).isTest();
+ || Environment.from(configserverConfig.environment()).isTest()
+ || configserverConfig.region().equals("us-central-1");
applicationRepository.deleteUnusedFiledistributionReferences(fileReferencesDir, deleteFiles);
}
}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java
index c0c3683bebb..992d46d3115 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java
@@ -47,9 +47,9 @@ public class ConfigServerBootstrapTest {
VipStatus vipStatus = new VipStatus();
ConfigServerBootstrap bootstrap = new ConfigServerBootstrap(tester.applicationRepository(), rpcServer, versionState, createStateMonitor(), vipStatus);
assertFalse(vipStatus.isInRotation());
- waitUntil(() -> bootstrap.status() == StateMonitor.Status.up, "failed waiting for status 'up'");
waitUntil(rpcServer::isRunning, "failed waiting for Rpc server running");
- assertTrue(vipStatus.isInRotation());
+ waitUntil(() -> bootstrap.status() == StateMonitor.Status.up, "failed waiting for status 'up'");
+ waitUntil(vipStatus::isInRotation, "failed waiting for server to be in rotation");
bootstrap.deconstruct();
assertEquals(StateMonitor.Status.down, bootstrap.status());
diff --git a/container-dependency-versions/pom.xml b/container-dependency-versions/pom.xml
index b4af6800768..f546a4e36d2 100644
--- a/container-dependency-versions/pom.xml
+++ b/container-dependency-versions/pom.xml
@@ -459,7 +459,7 @@
<properties>
<bouncycastle.version>1.58</bouncycastle.version>
- <felix.version>5.0.1</felix.version>
+ <felix.version>5.4.0</felix.version>
<findbugs.version>1.3.9</findbugs.version>
<guava.version>18.0</guava.version>
<guice.version>3.0</guice.version>
diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/metric/GarbageCollectionMetrics.java b/container-disc/src/main/java/com/yahoo/container/jdisc/metric/GarbageCollectionMetrics.java
new file mode 100644
index 00000000000..04fd8572ad4
--- /dev/null
+++ b/container-disc/src/main/java/com/yahoo/container/jdisc/metric/GarbageCollectionMetrics.java
@@ -0,0 +1,94 @@
+package com.yahoo.container.jdisc.metric;
+
+import com.yahoo.jdisc.Metric;
+
+import java.lang.management.GarbageCollectorMXBean;
+import java.lang.management.ManagementFactory;
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+
+/**
+ * @author ollivir
+ */
+public class GarbageCollectionMetrics {
+ private static final String GC_COUNT = "jdisc.gc.count";
+ private static final String GC_TIME = "jdisc.gc.ms";
+ private static final String DIMENSION_KEY = "gcName";
+
+ public static final Duration REPORTING_INTERVAL = Duration.ofSeconds(62);
+
+ static class GcStats {
+ private final Instant when;
+ private final long count;
+ private final Duration totalRuntime;
+
+ private GcStats(Instant when, long count, Duration totalRuntime) {
+ this.when = when;
+ this.count = count;
+ this.totalRuntime = totalRuntime;
+ }
+ }
+
+ private Map<String, LinkedList<GcStats>> gcStatistics;
+
+ private final Clock clock;
+
+ public GarbageCollectionMetrics(Clock clock) {
+ this.clock = clock;
+ this.gcStatistics = new HashMap<>();
+ collectGcStatistics(clock.instant());
+ }
+
+ private void collectGcStatistics(Instant now) {
+ for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) {
+ String gcName = gcBean.getName().replace(" ", "");
+ GcStats stats = new GcStats(now, gcBean.getCollectionCount(), Duration.ofMillis(gcBean.getCollectionTime()));
+
+ LinkedList<GcStats> window = gcStatistics.computeIfAbsent(gcName, anyName -> new LinkedList<>());
+ window.addLast(stats);
+ }
+ }
+
+ private void cleanStatistics(Instant now) {
+ Instant oldestToKeep = now.minus(REPORTING_INTERVAL);
+
+ for(Iterator<Map.Entry<String, LinkedList<GcStats>>> it = gcStatistics.entrySet().iterator(); it.hasNext(); ) {
+ Map.Entry<String, LinkedList<GcStats>> entry = it.next();
+ LinkedList<GcStats> history = entry.getValue();
+ while(history.isEmpty() == false && oldestToKeep.isAfter(history.getFirst().when)) {
+ history.removeFirst();
+ }
+ if(history.isEmpty()) {
+ it.remove();
+ }
+ }
+ }
+
+ public void emitMetrics(Metric metric) {
+ Instant now = clock.instant();
+
+ collectGcStatistics(now);
+ cleanStatistics(now);
+
+ for (Map.Entry<String, LinkedList<GcStats>> item : gcStatistics.entrySet()) {
+ GcStats reference = item.getValue().getFirst();
+ GcStats latest = item.getValue().getLast();
+ Map<String, String> contextData = new HashMap<>();
+ contextData.put(DIMENSION_KEY, item.getKey());
+ Metric.Context gcContext = metric.createContext(contextData);
+
+ metric.set(GC_COUNT, latest.count - reference.count, gcContext);
+ metric.set(GC_TIME, latest.totalRuntime.minus(reference.totalRuntime).toMillis(), gcContext);
+ }
+ }
+
+ // partial exposure for testing
+ Map<String, LinkedList<GcStats>> getGcStatistics() {
+ return gcStatistics;
+ }
+}
diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/metric/MetricUpdater.java b/container-disc/src/main/java/com/yahoo/container/jdisc/metric/MetricUpdater.java
index 22b049c9ab7..c2ef789e8fc 100644
--- a/container-disc/src/main/java/com/yahoo/container/jdisc/metric/MetricUpdater.java
+++ b/container-disc/src/main/java/com/yahoo/container/jdisc/metric/MetricUpdater.java
@@ -10,6 +10,7 @@ import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.time.Clock;
import java.time.Duration;
import java.util.Timer;
import java.util.TimerTask;
@@ -89,10 +90,12 @@ public class MetricUpdater extends AbstractComponent {
private final Runtime runtime = Runtime.getRuntime();
private final Metric metric;
private final ContainerWatchdogMetrics containerWatchdogMetrics;
+ private final GarbageCollectionMetrics garbageCollectionMetrics;
public UpdaterTask(Metric metric, ContainerWatchdogMetrics containerWatchdogMetrics) {
this.metric = metric;
this.containerWatchdogMetrics = containerWatchdogMetrics;
+ this.garbageCollectionMetrics = new GarbageCollectionMetrics(Clock.systemUTC());
}
@SuppressWarnings("deprecation")
@@ -109,9 +112,10 @@ public class MetricUpdater extends AbstractComponent {
metric.set(TOTAL_MEMORY_BYTES, totalMemory, null);
metric.set(MEMORY_MAPPINGS_COUNT, count_mappings(), null);
metric.set(OPEN_FILE_DESCRIPTORS, count_open_files(), null);
+
containerWatchdogMetrics.emitMetrics(metric);
+ garbageCollectionMetrics.emitMetrics(metric);
}
-
}
private static class TimerScheduler implements Scheduler {
diff --git a/container-disc/src/test/java/com/yahoo/container/jdisc/metric/GarbageCollectionMetricsTest.java b/container-disc/src/test/java/com/yahoo/container/jdisc/metric/GarbageCollectionMetricsTest.java
new file mode 100644
index 00000000000..61d8763b852
--- /dev/null
+++ b/container-disc/src/test/java/com/yahoo/container/jdisc/metric/GarbageCollectionMetricsTest.java
@@ -0,0 +1,57 @@
+package com.yahoo.container.jdisc.metric;
+
+import com.yahoo.jdisc.Metric;
+import com.yahoo.test.ManualClock;
+import org.junit.Test;
+
+import java.lang.management.ManagementFactory;
+import java.time.Duration;
+import java.util.LinkedList;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+/**
+ * @author ollivir
+ */
+public class GarbageCollectionMetricsTest {
+ @Test
+ public void gc_metrics_are_collected_in_a_sliding_window() {
+ ManualClock clock = new ManualClock();
+ Metric metric = mock(Metric.class);
+ int garbageCollectors = ManagementFactory.getGarbageCollectorMXBeans().size();
+
+ Duration interval = GarbageCollectionMetrics.REPORTING_INTERVAL;
+ GarbageCollectionMetrics garbageCollectionMetrics = new GarbageCollectionMetrics(clock);
+ assertThat(garbageCollectionMetrics.getGcStatistics().keySet().size(), is(garbageCollectors));
+
+ clock.advance(interval.minus(Duration.ofMillis(10)));
+ garbageCollectionMetrics.emitMetrics(metric);
+ assertWindowLengths(garbageCollectionMetrics, 2);
+
+ clock.advance(Duration.ofMillis(10));
+ garbageCollectionMetrics.emitMetrics(metric);
+ assertWindowLengths(garbageCollectionMetrics, 3);
+
+ clock.advance(Duration.ofMillis(10));
+ garbageCollectionMetrics.emitMetrics(metric);
+ assertWindowLengths(garbageCollectionMetrics, 3);
+
+ clock.advance(interval);
+ garbageCollectionMetrics.emitMetrics(metric);
+ assertWindowLengths(garbageCollectionMetrics, 2);
+
+ verify(metric, times(garbageCollectors * 4 * 2)).set(anyString(), any(), any());
+ }
+
+ private static void assertWindowLengths(GarbageCollectionMetrics gcm, int count) {
+ for(LinkedList<GarbageCollectionMetrics.GcStats> window: gcm.getGcStatistics().values()) {
+ assertThat(window.size(), is(count));
+ }
+ }
+}
diff --git a/container-disc/src/test/java/com/yahoo/container/jdisc/metric/MetricUpdaterTest.java b/container-disc/src/test/java/com/yahoo/container/jdisc/metric/MetricUpdaterTest.java
index f10af7593a4..e9e04eab3b4 100644
--- a/container-disc/src/test/java/com/yahoo/container/jdisc/metric/MetricUpdaterTest.java
+++ b/container-disc/src/test/java/com/yahoo/container/jdisc/metric/MetricUpdaterTest.java
@@ -5,6 +5,7 @@ import com.yahoo.jdisc.Metric;
import com.yahoo.jdisc.statistics.ContainerWatchdogMetrics;
import org.junit.Test;
+import java.lang.management.ManagementFactory;
import java.time.Duration;
import static org.mockito.Matchers.any;
@@ -20,11 +21,13 @@ public class MetricUpdaterTest {
@Test
public void metrics_are_updated_in_scheduler_cycle() throws InterruptedException {
+ int gcCount = ManagementFactory.getGarbageCollectorMXBeans().size();
+
Metric metric = mock(Metric.class);
ContainerWatchdogMetrics containerWatchdogMetrics = mock(ContainerWatchdogMetrics.class);
new MetricUpdater(new MockScheduler(), metric, containerWatchdogMetrics);
verify(containerWatchdogMetrics, times(1)).emitMetrics(any());
- verify(metric, times(8)).set(anyString(), any(), any());
+ verify(metric, times(8 + 2 * gcCount)).set(anyString(), any(), any());
}
private static class MockScheduler implements MetricUpdater.Scheduler {
diff --git a/container-jersey2/pom.xml b/container-jersey2/pom.xml
index 76ff21dc028..c5ed7d872bf 100644
--- a/container-jersey2/pom.xml
+++ b/container-jersey2/pom.xml
@@ -53,10 +53,6 @@
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
</dependency>
- <dependency>
- <groupId>org.scala-lang</groupId>
- <artifactId>scala-library</artifactId>
- </dependency>
</dependencies>
<build>
<plugins>
diff --git a/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/ComponentGraphProvider.java b/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/ComponentGraphProvider.java
new file mode 100644
index 00000000000..7ff9646cb27
--- /dev/null
+++ b/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/ComponentGraphProvider.java
@@ -0,0 +1,73 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.servlet.jersey;
+
+import com.yahoo.container.di.config.ResolveDependencyException;
+import com.yahoo.container.di.config.RestApiContext;
+import com.yahoo.container.jaxrs.annotation.Component;
+import org.glassfish.hk2.api.Injectee;
+import org.glassfish.hk2.api.InjectionResolver;
+import org.glassfish.hk2.api.ServiceHandle;
+
+import javax.inject.Singleton;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Resolves jdisc container components for jersey 2 components.
+ *
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+@Singleton // jersey2 requirement: InjectionResolvers must be in the Singleton scope
+public class ComponentGraphProvider implements InjectionResolver<Component> {
+ private Collection<RestApiContext.Injectable> injectables;
+
+ public ComponentGraphProvider(Collection<RestApiContext.Injectable> injectables) {
+ this.injectables = injectables;
+ }
+
+ @Override
+ public Object resolve(Injectee injectee, ServiceHandle<?> root) {
+ Class<?> wantedClass;
+ Type type = injectee.getRequiredType();
+ if (type instanceof Class) {
+ wantedClass = (Class<?>) type;
+ } else {
+ throw new UnsupportedOperationException("Only classes are supported, got " + type);
+ }
+
+ List<RestApiContext.Injectable> componentsWithMatchingType = new ArrayList<>();
+ for (RestApiContext.Injectable injectable : injectables) {
+ if (wantedClass.isInstance(injectable.instance)) {
+ componentsWithMatchingType.add(injectable);
+ }
+ }
+
+ if (componentsWithMatchingType.size() == 1) {
+ return componentsWithMatchingType.get(0).instance;
+ } else {
+ String injectionDescription = "class '" + wantedClass + "' to inject into Jersey resource/provider '"
+ + injectee.getInjecteeClass() + "')";
+ if (componentsWithMatchingType.size() > 1) {
+ String ids = componentsWithMatchingType.stream().map(c -> c.id.toString()).collect(Collectors.joining(","));
+ throw new ResolveDependencyException("Multiple components found of " + injectionDescription + ": " + ids);
+ } else {
+ throw new ResolveDependencyException("Could not find a component of " + injectionDescription + ".");
+ }
+ }
+ }
+
+ @Override
+ public boolean isMethodParameterIndicator() {
+ return true;
+ }
+
+ @Override
+ public boolean isConstructorParameterIndicator() {
+ return true;
+ }
+}
diff --git a/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/JerseyApplication.java b/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/JerseyApplication.java
new file mode 100644
index 00000000000..4c4e43bc8d5
--- /dev/null
+++ b/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/JerseyApplication.java
@@ -0,0 +1,25 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.servlet.jersey;
+
+import javax.ws.rs.core.Application;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class JerseyApplication extends Application {
+ private Set<Class<?>> classes;
+
+ public JerseyApplication(Collection<Class<?>> resourcesAndProviderClasses) {
+ this.classes = new HashSet<>(resourcesAndProviderClasses);
+ }
+
+ @Override
+ public Set<Class<?>> getClasses() {
+ return classes;
+ }
+}
diff --git a/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/JerseyServletProvider.java b/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/JerseyServletProvider.java
new file mode 100644
index 00000000000..1dbe410ba54
--- /dev/null
+++ b/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/JerseyServletProvider.java
@@ -0,0 +1,118 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.servlet.jersey;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
+import com.yahoo.container.di.componentgraph.Provider;
+import com.yahoo.container.di.config.RestApiContext;
+import com.yahoo.container.di.config.RestApiContext.BundleInfo;
+import com.yahoo.container.jaxrs.annotation.Component;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.glassfish.hk2.api.InjectionResolver;
+import org.glassfish.hk2.api.TypeLiteral;
+import org.glassfish.hk2.utilities.Binder;
+import org.glassfish.hk2.utilities.binding.AbstractBinder;
+import org.glassfish.jersey.media.multipart.MultiPartFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.servlet.ServletContainer;
+import org.objectweb.asm.ClassReader;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+
+import static com.yahoo.container.servlet.jersey.util.ResourceConfigUtil.registerComponent;
+
+/**
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class JerseyServletProvider implements Provider<ServletHolder> {
+ private final ServletHolder jerseyServletHolder;
+
+ public JerseyServletProvider(RestApiContext restApiContext) {
+ this.jerseyServletHolder = new ServletHolder(new ServletContainer(resourceConfig(restApiContext)));
+ }
+
+ private ResourceConfig resourceConfig(RestApiContext restApiContext) {
+ final ResourceConfig resourceConfig = ResourceConfig
+ .forApplication(new JerseyApplication(resourcesAndProviders(restApiContext.getBundles())));
+
+ registerComponent(resourceConfig, componentInjectorBinder(restApiContext));
+ registerComponent(resourceConfig, jacksonDatatypeJdk8Provider());
+ resourceConfig.register(MultiPartFeature.class);
+
+ return resourceConfig;
+ }
+
+ private static Collection<Class<?>> resourcesAndProviders(Collection<BundleInfo> bundles) {
+ final List<Class<?>> ret = new ArrayList<>();
+
+ for (BundleInfo bundle : bundles) {
+ for (String classEntry : bundle.getClassEntries()) {
+ Optional<String> className = detectResourceOrProvider(bundle.classLoader, classEntry);
+ className.ifPresent(cname -> ret.add(loadClass(bundle.symbolicName, bundle.classLoader, cname)));
+ }
+ }
+ return ret;
+ }
+
+ private static Optional<String> detectResourceOrProvider(ClassLoader bundleClassLoader, String classEntry) {
+ try (InputStream inputStream = getResourceAsStream(bundleClassLoader, classEntry)) {
+ ResourceOrProviderClassVisitor visitor = ResourceOrProviderClassVisitor.visit(new ClassReader(inputStream));
+ return visitor.getJerseyClassName();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static InputStream getResourceAsStream(ClassLoader bundleClassLoader, String classEntry) {
+ InputStream is = bundleClassLoader.getResourceAsStream(classEntry);
+ if (is == null) {
+ throw new RuntimeException("No entry " + classEntry + " in bundle " + bundleClassLoader);
+ } else {
+ return is;
+ }
+ }
+
+ private static Class<?> loadClass(String bundleSymbolicName, ClassLoader classLoader, String className) {
+ try {
+ return classLoader.loadClass(className);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed loading class " + className + " from bundle " + bundleSymbolicName, e);
+ }
+ }
+
+ private static Binder componentInjectorBinder(RestApiContext restApiContext) {
+ final ComponentGraphProvider componentGraphProvider = new ComponentGraphProvider(restApiContext.getInjectableComponents());
+ final TypeLiteral<InjectionResolver<Component>> componentAnnotationType = new TypeLiteral<InjectionResolver<Component>>() {
+ };
+
+ return new AbstractBinder() {
+ @Override
+ public void configure() {
+ bind(componentGraphProvider).to(componentAnnotationType);
+ }
+ };
+ }
+
+ private static JacksonJaxbJsonProvider jacksonDatatypeJdk8Provider() {
+ JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();
+ provider.setMapper(new ObjectMapper().registerModule(new Jdk8Module()).registerModule(new JavaTimeModule()));
+ return provider;
+ }
+
+ @Override
+ public ServletHolder get() {
+ return jerseyServletHolder;
+ }
+
+ @Override
+ public void deconstruct() {
+ }
+}
diff --git a/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/ResourceOrProviderClassVisitor.java b/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/ResourceOrProviderClassVisitor.java
new file mode 100644
index 00000000000..7cb47ac6118
--- /dev/null
+++ b/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/ResourceOrProviderClassVisitor.java
@@ -0,0 +1,103 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.servlet.jersey;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import javax.ws.rs.Path;
+import javax.ws.rs.ext.Provider;
+
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class ResourceOrProviderClassVisitor extends ClassVisitor {
+ private String className = null;
+ private boolean isPublic = false;
+ private boolean isAbstract = false;
+
+ private boolean isInnerClass = false;
+ private boolean isStatic = false;
+
+ private boolean isAnnotated = false;
+
+ public ResourceOrProviderClassVisitor() {
+ super(Opcodes.ASM6);
+ }
+
+ public Optional<String> getJerseyClassName() {
+ if (isJerseyClass()) {
+ return Optional.of(getClassName());
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ public boolean isJerseyClass() {
+ return isAnnotated && isPublic && !isAbstract && (!isInnerClass || isStatic);
+ }
+
+ public String getClassName() {
+ assert (className != null);
+ return org.objectweb.asm.Type.getObjectType(className).getClassName();
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+ isPublic = isPublic(access);
+ className = name;
+ isAbstract = isAbstract(access);
+ }
+
+ @Override
+ public void visitInnerClass(String name, String outerName, String innerName, int access) {
+ assert (className != null);
+
+ if (name.equals(className)) {
+ isInnerClass = true;
+ isStatic = isStatic(access);
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ isAnnotated |= annotationClassDescriptors.contains(desc);
+ return null;
+ }
+
+ private static Set<String> annotationClassDescriptors = new HashSet<>();
+
+ static {
+ annotationClassDescriptors.add(Type.getDescriptor(Path.class));
+ annotationClassDescriptors.add(Type.getDescriptor(Provider.class));
+ }
+
+ private static boolean isPublic(int access) {
+ return isSet(Opcodes.ACC_PUBLIC, access);
+ }
+
+ private static boolean isStatic(int access) {
+ return isSet(Opcodes.ACC_STATIC, access);
+ }
+
+ private static boolean isAbstract(int access) {
+ return isSet(Opcodes.ACC_ABSTRACT, access);
+ }
+
+ private static boolean isSet(int bits, int access) {
+ return (access & bits) == bits;
+ }
+
+ public static ResourceOrProviderClassVisitor visit(ClassReader classReader) {
+ ResourceOrProviderClassVisitor visitor = new ResourceOrProviderClassVisitor();
+ classReader.accept(visitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES);
+ return visitor;
+ }
+}
diff --git a/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/ComponentGraphProvider.scala b/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/ComponentGraphProvider.scala
deleted file mode 100644
index cabde3680a4..00000000000
--- a/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/ComponentGraphProvider.scala
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.servlet.jersey
-
-import javax.inject.Singleton
-
-import com.yahoo.container.di.config.{ResolveDependencyException, RestApiContext}
-import com.yahoo.container.jaxrs.annotation.Component
-import org.glassfish.hk2.api.{ServiceHandle, Injectee, InjectionResolver}
-
-/**
- * Resolves jdisc container components for jersey 2 components.
- * Similar to Gjoran's ComponentGraphProvider for jersey 1.
- * @author tonytv
- */
-@Singleton //jersey2 requirement: InjectionResolvers must be in the Singleton scope
-class ComponentGraphProvider(injectables: Traversable[RestApiContext.Injectable]) extends InjectionResolver[Component] {
- override def resolve(injectee: Injectee, root: ServiceHandle[_]): AnyRef = {
- val wantedClass = injectee.getRequiredType match {
- case c: Class[_] => c
- case unsupported => throw new UnsupportedOperationException("Only classes are supported, got " + unsupported)
- }
-
- val componentsWithMatchingType = injectables.filter{ injectable =>
- wantedClass.isInstance(injectable.instance) }
-
- val injectionDescription =
- s"class '$wantedClass' to inject into Jersey resource/provider '${injectee.getInjecteeClass}')"
-
- if (componentsWithMatchingType.size > 1)
- throw new ResolveDependencyException(s"Multiple components found of $injectionDescription: " +
- componentsWithMatchingType.map(_.id).mkString(","))
-
- componentsWithMatchingType.headOption.map(_.instance).getOrElse {
- throw new ResolveDependencyException(s"Could not find a component of $injectionDescription.")
- }
- }
-
- override def isMethodParameterIndicator: Boolean = true
- override def isConstructorParameterIndicator: Boolean = true
-}
diff --git a/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/JerseyApplication.scala b/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/JerseyApplication.scala
deleted file mode 100644
index eea41003984..00000000000
--- a/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/JerseyApplication.scala
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.servlet.jersey
-
-import javax.ws.rs.core.Application
-
-import scala.collection.JavaConverters._
-
-/**
- * @author tonytv
- */
-class JerseyApplication(resourcesAndProviderClasses: Set[Class[_]]) extends Application {
- private val classes: java.util.Set[Class[_]] = resourcesAndProviderClasses.asJava
-
- override def getClasses = classes
- override def getSingletons = super.getSingletons
-}
diff --git a/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/JerseyServletProvider.scala b/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/JerseyServletProvider.scala
deleted file mode 100644
index f0eff54dc16..00000000000
--- a/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/JerseyServletProvider.scala
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.servlet.jersey
-
-import java.io.{IOException, InputStream}
-
-import com.fasterxml.jackson.databind.ObjectMapper
-import com.fasterxml.jackson.datatype.jdk8.Jdk8Module
-import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
-import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider
-import com.yahoo.container.di.componentgraph.Provider
-import com.yahoo.container.di.config.RestApiContext
-import com.yahoo.container.di.config.RestApiContext.BundleInfo
-import com.yahoo.container.jaxrs.annotation.Component
-import com.yahoo.container.servlet.jersey.util.ResourceConfigUtil.registerComponent
-import org.eclipse.jetty.servlet.ServletHolder
-import org.glassfish.hk2.api.{InjectionResolver, TypeLiteral}
-import org.glassfish.hk2.utilities.Binder
-import org.glassfish.hk2.utilities.binding.AbstractBinder
-import org.glassfish.jersey.media.multipart.MultiPartFeature
-import org.glassfish.jersey.server.ResourceConfig
-import org.glassfish.jersey.servlet.ServletContainer
-import org.objectweb.asm.ClassReader
-
-import scala.collection.JavaConverters._
-import scala.util.control.Exception
-
-
-/**
- * @author tonytv
- */
-class JerseyServletProvider(restApiContext: RestApiContext) extends Provider[ServletHolder] {
- private val jerseyServletHolder = new ServletHolder(new ServletContainer(resourceConfig(restApiContext)))
-
- private def resourceConfig(restApiContext: RestApiContext) = {
- val resourceConfig = ResourceConfig.forApplication(
- new JerseyApplication(resourcesAndProviders(restApiContext.getBundles.asScala)))
-
- registerComponent(resourceConfig, componentInjectorBinder(restApiContext))
- registerComponent(resourceConfig, jacksonDatatypeJdk8Provider)
- resourceConfig.register(classOf[MultiPartFeature])
-
- resourceConfig
- }
-
- def resourcesAndProviders(bundles: Traversable[BundleInfo]) =
- (for {
- bundle <- bundles.view
- classEntry <- bundle.getClassEntries.asScala
- className <- detectResourceOrProvider(bundle.classLoader, classEntry)
- } yield loadClass(bundle.symbolicName, bundle.classLoader, className)).toSet
-
-
- def detectResourceOrProvider(bundleClassLoader: ClassLoader, classEntry: String): Option[String] = {
- using(getResourceAsStream(bundleClassLoader, classEntry)) { inputStream =>
- val visitor = ResourceOrProviderClassVisitor.visit(new ClassReader(inputStream))
- visitor.getJerseyClassName
- }
- }
-
- private def getResourceAsStream(bundleClassLoader: ClassLoader, classEntry: String) = {
- bundleClassLoader.getResourceAsStream(classEntry) match {
- case null => throw new RuntimeException(s"No entry $classEntry in bundle $bundleClassLoader")
- case stream => stream
- }
-
- }
-
- def using[T <: InputStream, R](stream: T)(f: T => R): R = {
- try {
- f(stream)
- } finally {
- Exception.ignoring(classOf[IOException]) {
- stream.close()
- }
- }
- }
-
- def loadClass(bundleSymbolicName: String, classLoader: ClassLoader, className: String) = {
- try {
- classLoader.loadClass(className)
- } catch {
- case e: Exception => throw new RuntimeException(s"Failed loading class $className from bundle $bundleSymbolicName", e)
- }
- }
-
- def componentInjectorBinder(restApiContext: RestApiContext): Binder = {
- val componentGraphProvider = new ComponentGraphProvider(restApiContext.getInjectableComponents.asScala)
- val componentAnnotationType = new TypeLiteral[InjectionResolver[Component]] {}
-
- new AbstractBinder {
- override def configure() {
- bind(componentGraphProvider).to(componentAnnotationType)
- }
- }
- }
-
- def jacksonDatatypeJdk8Provider: JacksonJaxbJsonProvider = {
- val provider = new JacksonJaxbJsonProvider()
- provider.setMapper(
- new ObjectMapper()
- .registerModule(new Jdk8Module)
- .registerModule(new JavaTimeModule))
- provider
- }
-
- override def get() = jerseyServletHolder
- override def deconstruct() {}
-}
-
diff --git a/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/ResourceOrProviderClassVisitor.scala b/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/ResourceOrProviderClassVisitor.scala
deleted file mode 100644
index 52674026c25..00000000000
--- a/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/ResourceOrProviderClassVisitor.scala
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.servlet.jersey
-
-import javax.ws.rs.Path
-import javax.ws.rs.ext.Provider
-
-import org.objectweb.asm.{ClassVisitor, Opcodes, Type, AnnotationVisitor, ClassReader}
-
-
-/**
- * @author tonytv
- */
-class ResourceOrProviderClassVisitor private () extends ClassVisitor(Opcodes.ASM6) {
- private var className: String = null
- private var isPublic: Boolean = false
- private var isAbstract = false
-
- private var isInnerClass: Boolean = false
- private var isStatic: Boolean = false
-
- private var isAnnotated: Boolean = false
-
- def getJerseyClassName: Option[String] = {
- if (isJerseyClass) Some(getClassName)
- else None
- }
-
- def isJerseyClass: Boolean = {
- isAnnotated && isPublic && !isAbstract &&
- (!isInnerClass || isStatic)
- }
-
- def getClassName = {
- assert (className != null)
- Type.getObjectType(className).getClassName
- }
-
- override def visit(version: Int, access: Int, name: String, signature: String, superName: String, interfaces: Array[String]) {
- isPublic = ResourceOrProviderClassVisitor.isPublic(access)
- className = name
- isAbstract = ResourceOrProviderClassVisitor.isAbstract(access)
- }
-
- override def visitInnerClass(name: String, outerName: String, innerName: String, access: Int) {
- assert (className != null)
-
- if (name == className) {
- isInnerClass = true
- isStatic = ResourceOrProviderClassVisitor.isStatic(access)
- }
- }
-
- override def visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor = {
- isAnnotated |= ResourceOrProviderClassVisitor.annotationClassDescriptors(desc)
- null
- }
-}
-
-
-object ResourceOrProviderClassVisitor {
- val annotationClassDescriptors = Set(classOf[Path], classOf[Provider]) map Type.getDescriptor
-
- def isPublic = isSet(Opcodes.ACC_PUBLIC) _
- def isStatic = isSet(Opcodes.ACC_STATIC) _
- def isAbstract = isSet(Opcodes.ACC_ABSTRACT) _
-
- private def isSet(bits: Int)(access: Int): Boolean = (access & bits) == bits
-
- def visit(classReader: ClassReader): ResourceOrProviderClassVisitor = {
- val visitor = new ResourceOrProviderClassVisitor
- classReader.accept(visitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES)
- visitor
- }
-}
diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerConformanceTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerConformanceTest.java
index 80c1cb8b458..77411fc080e 100644
--- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerConformanceTest.java
+++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerConformanceTest.java
@@ -323,7 +323,7 @@ public class HttpServerConformanceTest extends ServerProviderConformanceTest {
@Override
@Test
public void testRequestContentWriteExceptionAfterResponseWriteWithSyncCompletion() throws Throwable {
- new TestRunner().expect(success())
+ new TestRunner().expect(anyOf(success(), successNoContent()))
.execute();
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java
index bd75368a0dc..ff85c49bb13 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java
@@ -1,6 +1,8 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.maintenance.identity;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.yahoo.vespa.athenz.api.AthenzService;
import com.yahoo.vespa.athenz.client.zts.DefaultZtsClient;
import com.yahoo.vespa.athenz.client.zts.InstanceIdentity;
@@ -9,7 +11,7 @@ import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider;
import com.yahoo.vespa.athenz.identityprovider.api.EntityBindingsMapper;
import com.yahoo.vespa.athenz.identityprovider.api.IdentityDocumentClient;
import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument;
-import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId;
+import com.yahoo.vespa.athenz.identityprovider.api.bindings.SignedIdentityDocumentEntity;
import com.yahoo.vespa.athenz.identityprovider.client.DefaultIdentityDocumentClient;
import com.yahoo.vespa.athenz.identityprovider.client.InstanceCsrGenerator;
import com.yahoo.vespa.athenz.tls.AthenzIdentityVerifier;
@@ -19,9 +21,9 @@ import com.yahoo.vespa.athenz.tls.KeyUtils;
import com.yahoo.vespa.athenz.tls.Pkcs10Csr;
import com.yahoo.vespa.athenz.tls.SslContextBuilder;
import com.yahoo.vespa.athenz.tls.X509CertificateUtils;
+import com.yahoo.vespa.athenz.utils.SiaUtils;
import com.yahoo.vespa.hosted.dockerapi.ContainerName;
import com.yahoo.vespa.hosted.node.admin.component.Environment;
-import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeSpec;
import com.yahoo.vespa.hosted.node.admin.util.PrefixLogger;
import javax.net.ssl.SSLContext;
@@ -38,7 +40,6 @@ import java.security.cert.X509Certificate;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
-import java.util.Set;
import static java.util.Collections.singleton;
@@ -53,12 +54,15 @@ public class AthenzCredentialsMaintainer {
private static final Duration REFRESH_PERIOD = Duration.ofDays(1);
private static final Path CONTAINER_SIA_DIRECTORY = Paths.get("/var/lib/sia");
+ private static final ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule());
+
private final boolean enabled;
private final PrefixLogger log;
private final String hostname;
private final Path trustStorePath;
private final Path privateKeyFile;
private final Path certificateFile;
+ private final Path identityDocumentFile;
private final AthenzService containerIdentity;
private final URI ztsEndpoint;
private final Clock clock;
@@ -66,8 +70,6 @@ public class AthenzCredentialsMaintainer {
private final IdentityDocumentClient identityDocumentClient;
private final InstanceCsrGenerator csrGenerator;
private final AthenzService configserverIdentity;
- private final String zoneRegion;
- private final String zoneEnvironment;
public AthenzCredentialsMaintainer(String hostname,
Environment environment,
@@ -82,8 +84,9 @@ public class AthenzCredentialsMaintainer {
this.configserverIdentity = environment.getConfigserverAthenzIdentity();
this.csrGenerator = new InstanceCsrGenerator(environment.getCertificateDnsSuffix());
this.trustStorePath = environment.getTrustStorePath();
- this.privateKeyFile = getPrivateKeyFile(containerSiaDirectory, containerIdentity);
- this.certificateFile = getCertificateFile(containerSiaDirectory, containerIdentity);
+ this.privateKeyFile = SiaUtils.getPrivateKeyFile(containerSiaDirectory, containerIdentity);
+ this.certificateFile = SiaUtils.getCertificateFile(containerSiaDirectory, containerIdentity);
+ this.identityDocumentFile = containerSiaDirectory.resolve("vespa-node-identity-document.json");
this.hostIdentityProvider = hostIdentityProvider;
this.identityDocumentClient =
new DefaultIdentityDocumentClient(
@@ -91,15 +94,12 @@ public class AthenzCredentialsMaintainer {
hostIdentityProvider,
new AthenzIdentityVerifier(singleton(configserverIdentity)));
this.clock = Clock.systemUTC();
- this.zoneRegion = environment.getRegion();
- this.zoneEnvironment = environment.getEnvironment();
}
/**
- * @param nodeSpec Node specification
* @return Returns true if credentials were updated
*/
- public boolean converge(NodeSpec nodeSpec) {
+ public boolean converge() {
try {
if (!enabled) {
log.debug("Feature disabled on this host - not fetching certificate");
@@ -107,26 +107,25 @@ public class AthenzCredentialsMaintainer {
}
log.debug("Checking certificate");
Instant now = clock.instant();
- VespaUniqueInstanceId instanceId = getVespaUniqueInstanceId(nodeSpec);
- Set<String> ipAddresses = nodeSpec.getIpAddresses();
- if (!Files.exists(privateKeyFile) || !Files.exists(certificateFile)) {
- log.info("Certificate and/or private key file does not exist");
+ if (!Files.exists(privateKeyFile) || !Files.exists(certificateFile) || !Files.exists(identityDocumentFile)) {
+ log.info("Certificate/private key/identity document file does not exist");
Files.createDirectories(privateKeyFile.getParent());
Files.createDirectories(certificateFile.getParent());
- registerIdentity(instanceId, ipAddresses);
+ Files.createDirectories(identityDocumentFile.getParent());
+ registerIdentity();
return true;
}
X509Certificate certificate = readCertificateFromFile();
Instant expiry = certificate.getNotAfter().toInstant();
if (isCertificateExpired(expiry, now)) {
log.info(String.format("Certificate has expired (expiry=%s)", expiry.toString()));
- registerIdentity(instanceId, ipAddresses);
+ registerIdentity();
return true;
}
Duration age = Duration.between(certificate.getNotBefore().toInstant(), now);
if (shouldRefreshCredentials(age)) {
log.info(String.format("Certificate is ready to be refreshed (age=%s)", age.toString()));
- refreshIdentity(instanceId, ipAddresses);
+ refreshIdentity();
return true;
}
log.debug("Certificate is still valid");
@@ -148,20 +147,6 @@ public class AthenzCredentialsMaintainer {
}
}
- @SuppressWarnings("deprecation")
- private VespaUniqueInstanceId getVespaUniqueInstanceId(NodeSpec nodeSpec) {
- NodeSpec.Membership membership = nodeSpec.getMembership().get();
- NodeSpec.Owner owner = nodeSpec.getOwner().get();
- return new VespaUniqueInstanceId(
- membership.getIndex(),
- membership.getClusterId(),
- owner.getInstance(),
- owner.getApplication(),
- owner.getTenant(),
- zoneRegion,
- zoneEnvironment);
- }
-
private boolean shouldRefreshCredentials(Duration age) {
return age.compareTo(REFRESH_PERIOD) >= 0;
}
@@ -175,32 +160,32 @@ public class AthenzCredentialsMaintainer {
return now.isAfter(expiry.minus(EXPIRY_MARGIN));
}
- private void registerIdentity(VespaUniqueInstanceId instanceId, Set<String> ipAddresses) {
+ private void registerIdentity() {
KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA);
- Pkcs10Csr csr = csrGenerator.generateCsr(containerIdentity, instanceId, ipAddresses, keyPair);
SignedIdentityDocument signedIdentityDocument = identityDocumentClient.getNodeIdentityDocument(hostname);
+ Pkcs10Csr csr = csrGenerator.generateCsr(
+ containerIdentity, signedIdentityDocument.providerUniqueId(), signedIdentityDocument.ipAddresses(), keyPair);
try (ZtsClient ztsClient = new DefaultZtsClient(ztsEndpoint, hostIdentityProvider)) {
InstanceIdentity instanceIdentity =
ztsClient.registerInstance(
configserverIdentity,
containerIdentity,
- instanceId.asDottedString(),
+ signedIdentityDocument.providerUniqueId().asDottedString(),
EntityBindingsMapper.toAttestationData(signedIdentityDocument),
false,
csr);
+ writeIdentityDocument(signedIdentityDocument);
writePrivateKeyAndCertificate(keyPair.getPrivate(), instanceIdentity.certificate());
log.info("Instance successfully registered and credentials written to file");
} catch (IOException e) {
throw new UncheckedIOException(e);
- } catch (Exception e) {
- // TODO Change close() in ZtsClient to not throw checked exception
- throw new RuntimeException(e);
}
}
- private void refreshIdentity(VespaUniqueInstanceId instanceId, Set<String> ipAddresses) {
+ private void refreshIdentity() {
+ SignedIdentityDocument identityDocument = readIdentityDocument();
KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA);
- Pkcs10Csr csr = csrGenerator.generateCsr(containerIdentity, instanceId, ipAddresses, keyPair);
+ Pkcs10Csr csr = csrGenerator.generateCsr(containerIdentity, identityDocument.providerUniqueId(), identityDocument.ipAddresses(), keyPair);
SSLContext containerIdentitySslContext =
new SslContextBuilder()
.withKeyStore(privateKeyFile.toFile(), certificateFile.toFile())
@@ -211,16 +196,34 @@ public class AthenzCredentialsMaintainer {
ztsClient.refreshInstance(
configserverIdentity,
containerIdentity,
- instanceId.asDottedString(),
+ identityDocument.providerUniqueId().asDottedString(),
false,
csr);
writePrivateKeyAndCertificate(keyPair.getPrivate(), instanceIdentity.certificate());
log.info("Instance successfully refreshed and credentials written to file");
} catch (IOException e) {
throw new UncheckedIOException(e);
- } catch (Exception e) {
- // TODO Change close() in ZtsClient to not throw checked exception
- throw new RuntimeException(e);
+ }
+ }
+
+ private SignedIdentityDocument readIdentityDocument() {
+ try {
+ SignedIdentityDocumentEntity entity = mapper.readValue(identityDocumentFile.toFile(), SignedIdentityDocumentEntity.class);
+ return EntityBindingsMapper.toSignedIdentityDocument(entity);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private void writeIdentityDocument(SignedIdentityDocument signedIdentityDocument) {
+ try {
+ SignedIdentityDocumentEntity entity =
+ EntityBindingsMapper.toSignedIdentityDocumentEntity(signedIdentityDocument);
+ Path tempIdentityDocumentFile = toTempPath(identityDocumentFile);
+ mapper.writeValue(tempIdentityDocumentFile.toFile(), entity);
+ Files.move(tempIdentityDocumentFile, identityDocumentFile, StandardCopyOption.ATOMIC_MOVE);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
}
}
@@ -238,18 +241,4 @@ public class AthenzCredentialsMaintainer {
return Paths.get(file.toAbsolutePath().toString() + ".tmp");
}
- // TODO Move to vespa-athenz
- private static Path getPrivateKeyFile(Path root, AthenzService service) {
- return root
- .resolve("keys")
- .resolve(String.format("%s.%s.key.pem", service.getDomain().getName(), service.getName()));
- }
-
- // TODO Move to vespa-athenz
- private static Path getCertificateFile(Path root, AthenzService service) {
- return root
- .resolve("certs")
- .resolve(String.format("%s.%s.cert.pem", service.getDomain().getName(), service.getName()));
- }
-
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java
index 7fa9a90b744..5f1b7aefcfe 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java
@@ -498,7 +498,7 @@ public class NodeAgentImpl implements NodeAgent {
runLocalResumeScriptIfNeeded(node);
- athenzCredentialsMaintainer.converge(node);
+ athenzCredentialsMaintainer.converge();
doBeforeConverge(node);
diff --git a/searchsummary/CMakeLists.txt b/searchsummary/CMakeLists.txt
index 5f6e8881f13..4df636e0219 100644
--- a/searchsummary/CMakeLists.txt
+++ b/searchsummary/CMakeLists.txt
@@ -24,6 +24,7 @@ vespa_define_module(
TESTS
src/tests/docsumformat
src/tests/docsummary
+ src/tests/docsummary/attribute_combiner
src/tests/docsummary/slime_summary
src/tests/extractkeywords
)
diff --git a/searchsummary/src/tests/docsummary/attribute_combiner/CMakeLists.txt b/searchsummary/src/tests/docsummary/attribute_combiner/CMakeLists.txt
new file mode 100644
index 00000000000..df323b9c982
--- /dev/null
+++ b/searchsummary/src/tests/docsummary/attribute_combiner/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(searchsummary_attribute_combiner_test_app TEST
+ SOURCES
+ attribute_combiner_test.cpp
+ DEPENDS
+ searchsummary
+)
+vespa_add_test(NAME searchsummary_attribute_combiner_test_app COMMAND searchsummary_attribute_combiner_test_app)
diff --git a/searchsummary/src/tests/docsummary/attribute_combiner/attribute_combiner_test.cpp b/searchsummary/src/tests/docsummary/attribute_combiner/attribute_combiner_test.cpp
new file mode 100644
index 00000000000..97fafd0a446
--- /dev/null
+++ b/searchsummary/src/tests/docsummary/attribute_combiner/attribute_combiner_test.cpp
@@ -0,0 +1,217 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/searchcommon/common/undefinedvalues.h>
+#include <vespa/searchlib/attribute/attributefactory.h>
+#include <vespa/searchlib/attribute/attributemanager.h>
+#include <vespa/searchlib/attribute/attributevector.h>
+#include <vespa/searchlib/attribute/attributevector.hpp>
+#include <vespa/searchlib/attribute/floatbase.h>
+#include <vespa/searchlib/attribute/integerbase.h>
+#include <vespa/searchlib/attribute/stringbase.h>
+#include <vespa/searchlib/util/slime_output_raw_buf_adapter.h>
+#include <vespa/searchsummary/docsummary/docsumstate.h>
+#include <vespa/searchsummary/docsummary/docsum_field_writer_state.h>
+#include <vespa/searchsummary/docsummary/attribute_combiner_dfw.h>
+#include <vespa/vespalib/data/slime/slime.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+#include <vespa/log/log.h>
+LOG_SETUP("attribute_combiner_test");
+
+using search::AttributeFactory;
+using search::AttributeManager;
+using search::AttributeVector;
+using search::IntegerAttribute;
+using search::FloatingPointAttribute;
+using search::StringAttribute;
+using search::attribute::BasicType;
+using search::attribute::CollectionType;
+using search::attribute::Config;
+using search::attribute::IAttributeVector;
+using search::attribute::getUndefined;
+using search::docsummary::AttributeCombinerDFW;
+using search::docsummary::GetDocsumsState;
+using search::docsummary::GetDocsumsStateCallback;
+using search::docsummary::IDocsumEnvironment;
+using search::docsummary::IDocsumFieldWriter;
+
+namespace {
+
+vespalib::string
+toCompactJsonString(const vespalib::Slime &slime)
+{
+ vespalib::SimpleBuffer buf;
+ vespalib::slime::JsonFormat::encode(slime, buf, true);
+ return buf.get().make_string();
+}
+
+struct FieldBlock {
+ vespalib::string input;
+ vespalib::Slime slime;
+ search::RawBuf binary;
+ vespalib::string json;
+
+ explicit FieldBlock(const vespalib::string &jsonInput)
+ : input(jsonInput), slime(), binary(1024), json()
+ {
+ size_t used = vespalib::slime::JsonFormat::decode(jsonInput, slime);
+ EXPECT_TRUE(used > 0);
+ json = toCompactJsonString(slime);
+ search::SlimeOutputRawBufAdapter adapter(binary);
+ vespalib::slime::BinaryFormat::encode(slime, adapter);
+ }
+ const char *data() const { return binary.GetDrainPos(); }
+ size_t dataLen() const { return binary.GetUsedLen(); }
+};
+
+struct AttributeManagerFixture
+{
+ AttributeManager mgr;
+
+ AttributeManagerFixture();
+
+ ~AttributeManagerFixture();
+
+ template <typename AttributeType, typename ValueType>
+ void
+ buildAttribute(const vespalib::string &name,
+ BasicType type,
+ std::vector<std::vector<ValueType>> values);
+
+ void
+ buildStringAttribute(const vespalib::string &name,
+ std::vector<std::vector<vespalib::string>> values);
+ void
+ buildFloatAttribute(const vespalib::string &name,
+ std::vector<std::vector<double>> values);
+
+ void
+ buildIntegerAttribute(const vespalib::string &name,
+ BasicType type,
+ std::vector<std::vector<IAttributeVector::largeint_t>> values);
+};
+
+AttributeManagerFixture::AttributeManagerFixture()
+ : mgr()
+{
+ buildStringAttribute("array.name", {{"n1.1", "n1.2"}, {"n2"}, {"n3.1", "n3.2"}, {"", "n4.2"}});
+ buildIntegerAttribute("array.val", BasicType::Type::INT8, {{ 10, 11}, {20, 21 }, {30}, { getUndefined<int8_t>(), 41}});
+ buildFloatAttribute("array.fval", {{ 110.0}, { 120.0, 121.0 }, { 130.0, 131.0}, { getUndefined<double>(), 141.0 }});
+}
+
+AttributeManagerFixture::~AttributeManagerFixture() = default;
+
+template <typename AttributeType, typename ValueType>
+void
+AttributeManagerFixture::buildAttribute(const vespalib::string &name,
+ BasicType type,
+ std::vector<std::vector<ValueType>> values)
+{
+ Config cfg(type, CollectionType::Type::ARRAY);
+ auto attrBase = AttributeFactory::createAttribute(name, cfg);
+ EXPECT_TRUE(attrBase);
+ auto attr = std::dynamic_pointer_cast<AttributeType>(attrBase);
+ EXPECT_TRUE(attr);
+ attr->addReservedDoc();
+ for (const auto &docValues : values) {
+ uint32_t docId = 0;
+ EXPECT_TRUE(attr->addDoc(docId));
+ EXPECT_NOT_EQUAL(0u, docId);
+ for (const auto &value : docValues) {
+ attr->append(docId, value, 1);
+ }
+ attr->commit();
+ }
+ EXPECT_TRUE(mgr.add(attr));
+}
+
+void
+AttributeManagerFixture::buildStringAttribute(const vespalib::string &name,
+ std::vector<std::vector<vespalib::string>> values)
+{
+ buildAttribute<StringAttribute, vespalib::string>(name, BasicType::Type::STRING, std::move(values));
+}
+
+void
+AttributeManagerFixture::buildFloatAttribute(const vespalib::string &name,
+ std::vector<std::vector<double>> values)
+{
+ buildAttribute<FloatingPointAttribute, double>(name, BasicType::Type::DOUBLE, std::move(values));
+}
+
+void
+AttributeManagerFixture::buildIntegerAttribute(const vespalib::string &name,
+ BasicType type,
+ std::vector<std::vector<IAttributeVector::largeint_t>> values)
+{
+ buildAttribute<IntegerAttribute, IAttributeVector::largeint_t>(name, type, std::move(values));
+}
+
+
+class DummyStateCallback : public GetDocsumsStateCallback
+{
+public:
+ void FillSummaryFeatures(GetDocsumsState *, IDocsumEnvironment *) override { }
+ void FillRankFeatures(GetDocsumsState *, IDocsumEnvironment *) override { }
+ void ParseLocation(GetDocsumsState *) override { }
+ ~DummyStateCallback() override { }
+};
+
+
+struct Fixture
+{
+ AttributeManagerFixture attrs;
+ std::unique_ptr<IDocsumFieldWriter> writer;
+ DummyStateCallback stateCallback;
+ GetDocsumsState state;
+
+ Fixture();
+ ~Fixture();
+ void assertWritten(const vespalib::string &exp, uint32_t docId);
+};
+
+Fixture::Fixture()
+ : attrs(),
+ writer(AttributeCombinerDFW::create("array", attrs.mgr)),
+ stateCallback(),
+ state(stateCallback)
+{
+ EXPECT_TRUE(writer->setFieldWriterStateIndex(0));
+ state._attrCtx = attrs.mgr.createContext();
+ state._fieldWriterStates.resize(1);
+}
+
+Fixture::~Fixture()
+{
+}
+
+void
+Fixture::assertWritten(const vespalib::string &expectedJson, uint32_t docId)
+{
+ vespalib::Slime target;
+ vespalib::slime::SlimeInserter inserter(target);
+ writer->insertField(docId, nullptr, &state, search::docsummary::RES_JSONSTRING, inserter);
+ search::RawBuf binary(1024);
+ vespalib::string json = toCompactJsonString(target);
+ search::SlimeOutputRawBufAdapter adapter(binary);
+ vespalib::slime::BinaryFormat::encode(target, adapter);
+ FieldBlock block(expectedJson);
+ if (!EXPECT_EQUAL(block.dataLen(), binary.GetUsedLen()) ||
+ !EXPECT_EQUAL(0, memcmp(block.data(), binary.GetDrainPos(), block.dataLen()))) {
+ LOG(error, "Expected '%s'", expectedJson.c_str());
+ LOG(error, "Expected normalized '%s'", block.json.c_str());
+ LOG(error, "Got '%s'", json.c_str());
+ }
+}
+
+TEST_F("require that attributes combiner dfw generates correct slime output for array of struct", Fixture())
+{
+ f.assertWritten("[ { fval: 110.0, name: \"n1.1\", val: 10}, { name: \"n1.2\", val: 11}]", 1);
+ f.assertWritten("[ { fval: 120.0, name: \"n2\", val: 20}, { fval: 121.0, val: 21 }]", 2);
+ f.assertWritten("[ { fval: 130.0, name: \"n3.1\", val: 30}, { fval: 131.0, name: \"n3.2\"} ]", 3);
+ f.assertWritten("[ { }, { fval: 141.0, name: \"n4.2\", val: 41} ]", 4);
+}
+
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/CMakeLists.txt b/searchsummary/src/vespa/searchsummary/docsummary/CMakeLists.txt
index 9009f0bcbc7..ce54e7b0ea7 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/CMakeLists.txt
+++ b/searchsummary/src/vespa/searchsummary/docsummary/CMakeLists.txt
@@ -1,6 +1,9 @@
# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
vespa_add_library(searchsummary_docsummary OBJECT
SOURCES
+ array_attribute_combiner_dfw.cpp
+ attribute_combiner_dfw.cpp
+ attribute_field_writer.cpp
resultclass.cpp
resultconfig.cpp
resultpacker.cpp
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/array_attribute_combiner_dfw.cpp b/searchsummary/src/vespa/searchsummary/docsummary/array_attribute_combiner_dfw.cpp
new file mode 100644
index 00000000000..84e329f159d
--- /dev/null
+++ b/searchsummary/src/vespa/searchsummary/docsummary/array_attribute_combiner_dfw.cpp
@@ -0,0 +1,89 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "array_attribute_combiner_dfw.h"
+#include "docsum_field_writer_state.h"
+#include "attribute_field_writer.h"
+#include <vespa/searchcommon/attribute/iattributecontext.h>
+#include <vespa/searchcommon/attribute/iattributevector.h>
+#include <vespa/vespalib/data/slime/cursor.h>
+
+using search::attribute::IAttributeContext;
+using search::attribute::IAttributeVector;
+using vespalib::slime::Cursor;
+
+namespace search::docsummary {
+
+namespace {
+
+class ArrayAttributeFieldWriterState : public DocsumFieldWriterState
+{
+ std::vector<std::unique_ptr<AttributeFieldWriter>> _writers;
+
+public:
+ ArrayAttributeFieldWriterState(const std::vector<vespalib::string> &fieldNames,
+ const std::vector<vespalib::string> &attributeNames,
+ IAttributeContext &context);
+ ~ArrayAttributeFieldWriterState() override;
+ void insertField(uint32_t docId, vespalib::slime::Inserter &target) override;
+};
+
+ArrayAttributeFieldWriterState::ArrayAttributeFieldWriterState(const std::vector<vespalib::string> &fieldNames,
+ const std::vector<vespalib::string> &attributeNames,
+ IAttributeContext &context)
+ : DocsumFieldWriterState()
+{
+ size_t fields = fieldNames.size();
+ _writers.reserve(fields);
+ for (uint32_t field = 0; field < fields; ++field) {
+ const IAttributeVector *attr = context.getAttribute(attributeNames[field]);
+ if (attr != nullptr) {
+ _writers.emplace_back(AttributeFieldWriter::create(fieldNames[field], *attr));
+ }
+ }
+}
+
+ArrayAttributeFieldWriterState::~ArrayAttributeFieldWriterState() = default;
+
+void
+ArrayAttributeFieldWriterState::insertField(uint32_t docId, vespalib::slime::Inserter &target)
+{
+ uint32_t elems = 0;
+ for (auto &writer : _writers) {
+ writer->fetch(docId);
+ if (elems < writer->size()) {
+ elems = writer->size();
+ }
+ }
+ Cursor &arr = target.insertArray();
+ for (uint32_t idx = 0; idx < elems; ++idx) {
+ Cursor &obj = arr.addObject();
+ for (auto &writer : _writers) {
+ writer->print(idx, obj);
+ }
+ }
+}
+
+}
+
+ArrayAttributeCombinerDFW::ArrayAttributeCombinerDFW(const vespalib::string &fieldName,
+ const std::vector<vespalib::string> &fields)
+ : AttributeCombinerDFW(fieldName),
+ _fields(fields),
+ _attributeNames()
+{
+ _attributeNames.reserve(_fields.size());
+ vespalib::string prefix = fieldName + ".";
+ for (const auto &field : _fields) {
+ _attributeNames.emplace_back(prefix + field);
+ }
+}
+
+ArrayAttributeCombinerDFW::~ArrayAttributeCombinerDFW() = default;
+
+std::unique_ptr<DocsumFieldWriterState>
+ArrayAttributeCombinerDFW::allocFieldWriterState(IAttributeContext &context)
+{
+ return std::make_unique<ArrayAttributeFieldWriterState>(_fields, _attributeNames, context);
+}
+
+}
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/array_attribute_combiner_dfw.h b/searchsummary/src/vespa/searchsummary/docsummary/array_attribute_combiner_dfw.h
new file mode 100644
index 00000000000..c02d2bd5da6
--- /dev/null
+++ b/searchsummary/src/vespa/searchsummary/docsummary/array_attribute_combiner_dfw.h
@@ -0,0 +1,29 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "attribute_combiner_dfw.h"
+
+namespace search::attribute { class IAttributeContext; }
+
+namespace search::docsummary {
+
+class DocsumFieldWriterState;
+
+/*
+ * This class reads values from multiple struct field attributes and
+ * inserts them as an array of struct.
+ */
+class ArrayAttributeCombinerDFW : public AttributeCombinerDFW
+{
+ std::vector<vespalib::string> _fields;
+ std::vector<vespalib::string> _attributeNames;
+
+ std::unique_ptr<DocsumFieldWriterState> allocFieldWriterState(search::attribute::IAttributeContext &context) override;
+public:
+ ArrayAttributeCombinerDFW(const vespalib::string &fieldName,
+ const std::vector<vespalib::string> &fields);
+ ~ArrayAttributeCombinerDFW() override;
+};
+
+}
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/attribute_combiner_dfw.cpp b/searchsummary/src/vespa/searchsummary/docsummary/attribute_combiner_dfw.cpp
new file mode 100644
index 00000000000..b532cfb273a
--- /dev/null
+++ b/searchsummary/src/vespa/searchsummary/docsummary/attribute_combiner_dfw.cpp
@@ -0,0 +1,141 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "attribute_combiner_dfw.h"
+#include "array_attribute_combiner_dfw.h"
+#include "docsum_field_writer_state.h"
+#include "docsumstate.h"
+#include <vespa/searchlib/attribute/attributeguard.h>
+#include <vespa/searchlib/attribute/attributevector.h>
+#include <vespa/searchlib/attribute/iattributemanager.h>
+#include <algorithm>
+
+#include <vespa/log/log.h>
+LOG_SETUP(".searchsummary.docsummary.attribute_combiner_dfw");
+
+using search::AttributeGuard;
+using search::AttributeVector;
+using search::attribute::CollectionType;
+
+namespace search::docsummary {
+
+namespace {
+
+class StructFields
+{
+ std::vector<vespalib::string> _mapFields;
+ std::vector<vespalib::string> _arrayFields;
+ bool _hasMapKey;
+ bool _error;
+
+public:
+ StructFields(const vespalib::string &fieldName, const IAttributeManager &attrMgr);
+ ~StructFields();
+ const std::vector<vespalib::string> &getMapFields() const { return _mapFields; }
+ const std::vector<vespalib::string> &getArrayFields() const { return _arrayFields; }
+ bool hasMapKey() const { return _hasMapKey; }
+ bool getError() const { return _error; }
+};
+
+
+StructFields::StructFields(const vespalib::string &fieldName, const IAttributeManager &attrMgr)
+ : _mapFields(),
+ _arrayFields(),
+ _hasMapKey(false),
+ _error(false)
+{
+ // Note: Doesn't handle imported attributes
+ std::vector<AttributeGuard> attrs;
+ attrMgr.getAttributeList(attrs);
+ vespalib::string prefix = fieldName + ".";
+ vespalib::string keyName = prefix + "key";
+ vespalib::string valuePrefix = prefix + "value.";
+ for (const auto &guard : attrs) {
+ vespalib::string name = guard->getName();
+ if (name.substr(0, prefix.size()) != prefix) {
+ continue;
+ }
+ auto collType = guard->getCollectionType();
+ if (collType != CollectionType::Type::ARRAY) {
+ LOG(warning, "Attribute %s is not an array attribute", name.c_str());
+ _error = true;
+ break;
+ }
+ if (name.substr(0, valuePrefix.size()) == valuePrefix) {
+ _mapFields.emplace_back(name.substr(valuePrefix.size()));
+ } else {
+ _arrayFields.emplace_back(name.substr(prefix.size()));
+ if (name == keyName) {
+ _hasMapKey = true;
+ }
+ }
+ }
+ if (!_error) {
+ std::sort(_arrayFields.begin(), _arrayFields.end());
+ std::sort(_mapFields.begin(), _mapFields.end());
+ if (!_mapFields.empty()) {
+ if (!_hasMapKey) {
+ LOG(warning, "Missing key attribute '%s', have value attributes for map", keyName.c_str());
+ _error = true;
+ } else if (_arrayFields.size() != 1u) {
+ LOG(warning, "Could not determine if field '%s' is array or map of struct", fieldName.c_str());
+ _error = true;
+ }
+ }
+ }
+}
+
+StructFields::~StructFields() = default;
+
+}
+
+AttributeCombinerDFW::AttributeCombinerDFW(const vespalib::string &fieldName)
+ : IDocsumFieldWriter(),
+ _stateIndex(0),
+ _fieldName(fieldName)
+{
+}
+
+AttributeCombinerDFW::~AttributeCombinerDFW() = default;
+
+bool
+AttributeCombinerDFW::IsGenerated() const
+{
+ return true;
+}
+
+bool
+AttributeCombinerDFW::setFieldWriterStateIndex(uint32_t fieldWriterStateIndex)
+{
+ _stateIndex = fieldWriterStateIndex;
+ return true;
+}
+
+std::unique_ptr<IDocsumFieldWriter>
+AttributeCombinerDFW::create(const vespalib::string &fieldName, IAttributeManager &attrMgr)
+{
+ StructFields structFields(fieldName, attrMgr);
+ if (structFields.getError()) {
+ return std::unique_ptr<IDocsumFieldWriter>();
+ } else if (!structFields.getMapFields().empty()) {
+ LOG(warning, "map of struct is not yet supported for field '%s'", fieldName.c_str());
+ return std::unique_ptr<IDocsumFieldWriter>();
+ }
+ return std::make_unique<ArrayAttributeCombinerDFW>(fieldName, structFields.getArrayFields());
+}
+
+void
+AttributeCombinerDFW::insertField(uint32_t docid,
+ GeneralResult *,
+ GetDocsumsState *state,
+ ResType,
+ vespalib::slime::Inserter &target)
+{
+ auto &fieldWriterState = state->_fieldWriterStates[_stateIndex];
+ if (!fieldWriterState) {
+ fieldWriterState = allocFieldWriterState(*state->_attrCtx);
+ }
+ fieldWriterState->insertField(docid, target);
+}
+
+}
+
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/attribute_combiner_dfw.h b/searchsummary/src/vespa/searchsummary/docsummary/attribute_combiner_dfw.h
new file mode 100644
index 00000000000..ef54522a923
--- /dev/null
+++ b/searchsummary/src/vespa/searchsummary/docsummary/attribute_combiner_dfw.h
@@ -0,0 +1,36 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "docsumfieldwriter.h"
+
+namespace search::attribute { class IAttributeContext; }
+
+namespace search::docsummary {
+
+class DocsumFieldWriterState;
+class DynamicDocsumWriter;
+
+/*
+ * This class reads values from multiple struct field attributes and
+ * inserts them as an array of struct or a map of struct.
+ */
+class AttributeCombinerDFW : public IDocsumFieldWriter
+{
+protected:
+ uint32_t _stateIndex;
+ vespalib::string _fieldName;
+ AttributeCombinerDFW(const vespalib::string &fieldName);
+protected:
+ virtual std::unique_ptr<DocsumFieldWriterState> allocFieldWriterState(search::attribute::IAttributeContext &context) = 0;
+public:
+ ~AttributeCombinerDFW() override;
+ bool IsGenerated() const override;
+ bool setFieldWriterStateIndex(uint32_t fieldWriterStateIndex) override;
+ static std::unique_ptr<IDocsumFieldWriter> create(const vespalib::string &fieldName, IAttributeManager &attrMgr);
+ void insertField(uint32_t docid, GeneralResult *gres, GetDocsumsState *state,
+ ResType type, vespalib::slime::Inserter &target) override;
+};
+
+}
+
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/attribute_field_writer.cpp b/searchsummary/src/vespa/searchsummary/docsummary/attribute_field_writer.cpp
new file mode 100644
index 00000000000..2eebe7137dc
--- /dev/null
+++ b/searchsummary/src/vespa/searchsummary/docsummary/attribute_field_writer.cpp
@@ -0,0 +1,172 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "attribute_field_writer.h"
+#include <vespa/searchcommon/attribute/attributecontent.h>
+#include <vespa/searchcommon/common/undefinedvalues.h>
+#include <vespa/vespalib/data/slime/cursor.h>
+#include <cassert>
+
+using search::attribute::BasicType;
+using search::attribute::IAttributeVector;
+using search::attribute::getUndefined;
+using vespalib::slime::Cursor;
+
+namespace search::docsummary {
+
+AttributeFieldWriter::AttributeFieldWriter(const vespalib::string &fieldName,
+ const IAttributeVector &attr)
+ : _fieldName(fieldName),
+ _attr(attr),
+ _size(0)
+{
+}
+
+AttributeFieldWriter::~AttributeFieldWriter() = default;
+
+namespace {
+
+template <class Content>
+class WriteField : public AttributeFieldWriter
+{
+protected:
+ Content _content;
+
+ WriteField(const vespalib::string &fieldName, const IAttributeVector &attr);
+ ~WriteField() override;
+private:
+ void fetch(uint32_t docId) override;
+};
+
+class WriteStringField : public WriteField<search::attribute::ConstCharContent>
+{
+public:
+ WriteStringField(const vespalib::string &fieldName,
+ const IAttributeVector &attr);
+ ~WriteStringField() override;
+ void print(uint32_t idx, Cursor &cursor) override;
+};
+
+
+class WriteFloatField : public WriteField<search::attribute::FloatContent>
+{
+public:
+ WriteFloatField(const vespalib::string &fieldName,
+ const IAttributeVector &attr);
+ ~WriteFloatField() override;
+ void print(uint32_t idx, Cursor &cursor) override;
+};
+
+class WriteIntField : public WriteField<search::attribute::IntegerContent>
+{
+ IAttributeVector::largeint_t _undefined;
+public:
+ WriteIntField(const vespalib::string &fieldName,
+ const IAttributeVector &attr,
+ IAttributeVector::largeint_t undefined);
+ ~WriteIntField() override;
+ void print(uint32_t idx, Cursor &cursor) override;
+};
+
+template <class Content>
+WriteField<Content>::WriteField(const vespalib::string &fieldName, const IAttributeVector &attr)
+ : AttributeFieldWriter(fieldName, attr),
+ _content()
+{
+}
+
+template <class Content>
+WriteField<Content>::~WriteField() = default;
+
+template <class Content>
+void
+WriteField<Content>::fetch(uint32_t docId)
+{
+ _content.fill(_attr, docId);
+ _size = _content.size();
+}
+
+WriteStringField::WriteStringField(const vespalib::string &fieldName,
+ const IAttributeVector &attr)
+ : WriteField(fieldName, attr)
+{
+}
+
+WriteStringField::~WriteStringField() = default;
+
+void
+WriteStringField::print(uint32_t idx, Cursor &cursor)
+{
+ if (idx < _size) {
+ const char *s = _content[idx];
+ if (s[0] != '\0') {
+ cursor.setString(_fieldName, vespalib::Memory(s));
+ }
+ }
+}
+
+WriteFloatField::WriteFloatField(const vespalib::string &fieldName,
+ const IAttributeVector &attr)
+ : WriteField(fieldName, attr)
+{
+}
+
+WriteFloatField::~WriteFloatField() = default;
+
+void
+WriteFloatField::print(uint32_t idx, Cursor &cursor)
+{
+ if (idx < _size) {
+ double val = _content[idx];
+ if (!search::attribute::isUndefined(val)) {
+ cursor.setDouble(_fieldName, val);
+ }
+ }
+}
+
+WriteIntField::WriteIntField(const vespalib::string &fieldName,
+ const IAttributeVector &attr,
+ IAttributeVector::largeint_t undefined)
+ : WriteField(fieldName, attr),
+ _undefined(undefined)
+{
+}
+
+WriteIntField::~WriteIntField() = default;
+
+void
+WriteIntField::print(uint32_t idx, Cursor &cursor)
+{
+ if (idx < _size) {
+ auto val = _content[idx];
+ if (val != _undefined) {
+ cursor.setLong(_fieldName, _content[idx]);
+ }
+ }
+}
+
+}
+
+std::unique_ptr<AttributeFieldWriter>
+AttributeFieldWriter::create(const vespalib::string &fieldName, const IAttributeVector &attr)
+{
+ switch (attr.getBasicType()) {
+ case BasicType::INT8:
+ return std::make_unique<WriteIntField>(fieldName, attr, getUndefined<int8_t>());
+ case BasicType::INT16:
+ return std::make_unique<WriteIntField>(fieldName, attr, getUndefined<int16_t>());
+ case BasicType::INT32:
+ return std::make_unique<WriteIntField>(fieldName, attr, getUndefined<int32_t>());
+ case BasicType::INT64:
+ return std::make_unique<WriteIntField>(fieldName, attr, getUndefined<int64_t>());
+ case BasicType::FLOAT:
+ case BasicType::DOUBLE:
+ return std::make_unique<WriteFloatField>(fieldName, attr);
+ case BasicType::STRING:
+ return std::make_unique<WriteStringField>(fieldName, attr);
+ default:
+ assert(false);
+ abort();
+ }
+}
+
+}
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/attribute_field_writer.h b/searchsummary/src/vespa/searchsummary/docsummary/attribute_field_writer.h
new file mode 100644
index 00000000000..104455a0e79
--- /dev/null
+++ b/searchsummary/src/vespa/searchsummary/docsummary/attribute_field_writer.h
@@ -0,0 +1,34 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/vespalib/data/memory.h>
+
+namespace search::attribute { class IAttributeVector; }
+namespace vespalib::slime { class Cursor; }
+
+namespace search::docsummary {
+
+/*
+ * This class reads values from a struct field attribute and inserts
+ * them into proper position in an array of struct or map of struct.
+ * If the value to be inserted is considered to be undefined then
+ * the value is not inserted.
+ */
+class AttributeFieldWriter
+{
+protected:
+ const vespalib::Memory _fieldName;
+ const search::attribute::IAttributeVector &_attr;
+ size_t _size;
+public:
+ AttributeFieldWriter(const vespalib::string &fieldName,
+ const search::attribute::IAttributeVector &attr);
+ virtual ~AttributeFieldWriter();
+ virtual void fetch(uint32_t docId) = 0;
+ virtual void print(uint32_t idx, vespalib::slime::Cursor &cursor) = 0;
+ static std::unique_ptr<AttributeFieldWriter> create(const vespalib::string &fieldName, const search::attribute::IAttributeVector &attr);
+ uint32_t size() const { return _size; }
+};
+
+}
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/docsum_field_writer_state.h b/searchsummary/src/vespa/searchsummary/docsummary/docsum_field_writer_state.h
new file mode 100644
index 00000000000..940cfd6ce06
--- /dev/null
+++ b/searchsummary/src/vespa/searchsummary/docsummary/docsum_field_writer_state.h
@@ -0,0 +1,21 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+namespace vespalib::slime { class Inserter; }
+
+namespace search::docsummary {
+
+/*
+ * A subclass of this class can be instantiated by a document field writer to
+ * track extra state during handling of a document summary request and
+ * insert the field value using that state.
+ */
+class DocsumFieldWriterState
+{
+public:
+ virtual void insertField(uint32_t docId, vespalib::slime::Inserter &target) = 0;
+ virtual ~DocsumFieldWriterState() = default;
+};
+
+}
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/docsumfieldwriter.cpp b/searchsummary/src/vespa/searchsummary/docsummary/docsumfieldwriter.cpp
index 7b463352155..18e7e471663 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/docsumfieldwriter.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/docsumfieldwriter.cpp
@@ -21,6 +21,12 @@ using search::common::Location;
const vespalib::string IDocsumFieldWriter::_empty("");
+bool
+IDocsumFieldWriter::setFieldWriterStateIndex(uint32_t)
+{
+ return false; // Don't need any field writer state by default
+}
+
//--------------------------------------------------------------------------
EmptyDFW::EmptyDFW() { }
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/docsumfieldwriter.h b/searchsummary/src/vespa/searchsummary/docsummary/docsumfieldwriter.h
index abce5c12227..51079f7736e 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/docsumfieldwriter.h
+++ b/searchsummary/src/vespa/searchsummary/docsummary/docsumfieldwriter.h
@@ -40,6 +40,7 @@ public:
}
void setIndex(size_t v) { _index = v; }
size_t getIndex() const { return _index; }
+ virtual bool setFieldWriterStateIndex(uint32_t fieldWriterStateIndex);
private:
size_t _index;
static const vespalib::string _empty;
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.cpp b/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.cpp
index 91953612f6a..b0431b6e6ac 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.cpp
@@ -4,6 +4,7 @@
#include <vespa/juniper/rpinterface.h>
#include <vespa/searchcommon/attribute/iattributecontext.h>
#include <vespa/searchlib/common/location.h>
+#include "docsum_field_writer_state.h"
namespace search {
namespace docsummary {
@@ -19,6 +20,7 @@ GetDocsumsState::GetDocsumsState(GetDocsumsStateCallback &callback)
_docSumFieldSpace(_docSumFieldSpaceStore, sizeof(_docSumFieldSpaceStore)), // only alloc buffer if needed
_attrCtx(),
_attributes(),
+ _fieldWriterStates(),
_jsonStringer(),
_parsedLocation(),
_summaryFeatures(NULL),
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h b/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h
index 4ffed79043e..fa47d5244eb 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h
+++ b/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h
@@ -23,6 +23,7 @@ namespace search::docsummary {
class GetDocsumsState;
class IDocsumEnvironment;
class KeywordExtractor;
+class DocsumFieldWriterState;
class GetDocsumsStateCallback
{
@@ -70,6 +71,7 @@ public:
char _docSumFieldSpaceStore[2048];
std::unique_ptr<search::attribute::IAttributeContext> _attrCtx;
std::vector<const search::attribute::IAttributeVector *> _attributes;
+ std::vector<std::unique_ptr<DocsumFieldWriterState>> _fieldWriterStates;
vespalib::JSONStringer _jsonStringer;
// used by AbsDistanceDFW
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/docsumwriter.cpp b/searchsummary/src/vespa/searchsummary/docsummary/docsumwriter.cpp
index bf660b1319b..abd1780b773 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/docsumwriter.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/docsumwriter.cpp
@@ -2,6 +2,7 @@
#include "docsumwriter.h"
#include "docsumstate.h"
+#include "docsum_field_writer_state.h"
#include <vespa/searchlib/common/transport.h>
#include <vespa/searchlib/util/slime_output_raw_buf_adapter.h>
#include <vespa/searchlib/attribute/iattributemanager.h>
@@ -77,7 +78,6 @@ DynamicDocsumWriter::resolveInputClass(ResolveClassInfo &rci, uint32_t id) const
}
}
-
static void convertEntry(GetDocsumsState *state,
const ResConfigEntry *resCfg,
const ResEntry *entry,
@@ -194,6 +194,7 @@ DynamicDocsumWriter::DynamicDocsumWriter( ResultConfig *config, KeywordExtractor
_defaultOutputClass(ResultConfig::NoClassID()),
_numClasses(config->GetNumResultClasses()),
_numEnumValues(config->GetFieldNameEnum().GetNumEntries()),
+ _numFieldWriterStates(0),
_classInfoTable(nullptr),
_overrideTable(nullptr)
{
@@ -267,6 +268,9 @@ DynamicDocsumWriter::Override(const char *fieldName, IDocsumFieldWriter *writer)
writer->setIndex(fieldEnumValue);
_overrideTable[fieldEnumValue] = writer;
+ if (writer->setFieldWriterStateIndex(_numFieldWriterStates)) {
+ ++_numFieldWriterStates;
+ }
for (ResultConfig::iterator it(_resultConfig->begin()), mt(_resultConfig->end()); it != mt; it++) {
@@ -288,6 +292,7 @@ DynamicDocsumWriter::InitState(IAttributeManager & attrMan, GetDocsumsState *sta
state->_kwExtractor = _keywordExtractor;
state->_attrCtx = attrMan.createContext();
state->_attributes.resize(_numEnumValues);
+ state->_fieldWriterStates.resize(_numFieldWriterStates);
for (size_t i(0); i < state->_attributes.size(); i++) {
const IDocsumFieldWriter *fw = _overrideTable[i];
if (fw) {
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/docsumwriter.h b/searchsummary/src/vespa/searchsummary/docsummary/docsumwriter.h
index 6ef21a71e74..92b26d5cf14 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/docsumwriter.h
+++ b/searchsummary/src/vespa/searchsummary/docsummary/docsumwriter.h
@@ -54,6 +54,7 @@ private:
uint32_t _defaultOutputClass;
uint32_t _numClasses;
uint32_t _numEnumValues;
+ uint32_t _numFieldWriterStates;
ResultClass::DynamicInfo *_classInfoTable;
IDocsumFieldWriter **_overrideTable;
diff --git a/service-monitor/pom.xml b/service-monitor/pom.xml
index 70f9d4aa655..b8065ed3636 100644
--- a/service-monitor/pom.xml
+++ b/service-monitor/pom.xml
@@ -64,6 +64,12 @@
<version>${project.version}</version>
</dependency>
<dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>vespa-athenz</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<scope>provided</scope>
@@ -76,6 +82,23 @@
<scope>provided</scope>
</dependency>
<dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <version>4.5</version>
+ <!-- This is necessary to get 4.4's HostnameVerifier API of SSLConnectionSocketFactory::new -->
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
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..5ad38cebcfc 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 com.yahoo.vespa.service.monitor.internal.health.ApplicationHealthMonitor;
+
+import java.util.ArrayList;
+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,44 @@ 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, config.httpport(), index));
+ }
+
+ return new ApplicationInfo(
+ CONFIG_SERVER_APPLICATION.getApplicationId(),
+ 0,
+ new HostsModel(hostInfos));
+ }
+
+ private static HostInfo makeHostInfo(String hostname, int port, int configIndex) {
+ PortInfo portInfo = new PortInfo(port, ApplicationHealthMonitor.PORT_TAGS_HEALTH);
+
+ 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..80e0bfd2710
--- /dev/null
+++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/DuperModel.java
@@ -0,0 +1,42 @@
+// 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) {
+ // Single-tenant applications have the config server as part of the application model.
+ // TODO: Add health monitoring for config server when part of application model.
+ if (configServerConfig.multitenant()) {
+ 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 97c4fdda0f3..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,10 +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.Collections;
-import java.util.List;
import java.util.Map;
-import java.util.stream.Collectors;
public class ServiceMonitorImpl implements ServiceMonitor {
private final ServiceModelCache serviceModelCache;
@@ -32,30 +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) {
- if (configserverConfig.multitenant()) {
- return configserverConfig.zookeeperserver().stream()
- .map(ConfigserverConfig.Zookeeperserver::hostname)
- .collect(Collectors.toList());
- }
-
- return Collections.emptyList();
- }
-
@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..bd2658db8aa
--- /dev/null
+++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/ApplicationHealthMonitor.java
@@ -0,0 +1,102 @@
+// 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.config.provision.HostName;
+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.ServiceStatusProvider;
+import com.yahoo.vespa.service.monitor.application.ApplicationInstanceGenerator;
+import com.yahoo.vespa.service.monitor.internal.ServiceId;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Responsible for monitoring a whole application using /state/v1/health.
+ *
+ * @author hakon
+ */
+public class ApplicationHealthMonitor implements ServiceStatusProvider, AutoCloseable {
+ public static final String PORT_TAG_STATE = "STATE";
+ public static final String PORT_TAG_HTTP = "HTTP";
+ /** Port tags implying /state/v1/health is served */
+ public static final List<String> PORT_TAGS_HEALTH =
+ Collections.unmodifiableList(Arrays.asList(PORT_TAG_HTTP, PORT_TAG_STATE));
+
+ private final Map<ServiceId, HealthMonitor> healthMonitors;
+
+ public static ApplicationHealthMonitor startMonitoring(ApplicationInfo application) {
+ return new ApplicationHealthMonitor(makeHealthMonitors(application));
+ }
+
+ 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) {
+ 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)
+ .ifPresent(healthMonitor -> healthMonitors.put(
+ ApplicationInstanceGenerator.getServiceId(application, serviceInfo),
+ healthMonitor));
+ }
+ }
+ }
+ return healthMonitors;
+ }
+
+ private static Optional<HealthMonitor> maybeCreateHealthMonitor(
+ ApplicationInfo applicationInfo,
+ HostInfo hostInfo,
+ ServiceInfo serviceInfo,
+ PortInfo portInfo) {
+ if (portInfo.getTags().containsAll(PORT_TAGS_HEALTH)) {
+ HostName hostname = HostName.from(hostInfo.getHostname());
+ HealthEndpoint endpoint = HealthEndpoint.forHttp(hostname, portInfo.getPort());
+ // todo: make HealthMonitor
+ // HealthMonitor healthMonitor = new HealthMonitor(endpoint);
+ // 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..43a02a385be
--- /dev/null
+++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthClient.java
@@ -0,0 +1,139 @@
+// 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.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.SSLContext;
+
+/**
+ * @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 HealthEndpoint endpoint;
+
+ private volatile CloseableHttpClient httpClient;
+
+ public HealthClient(HealthEndpoint endpoint) {
+ this.endpoint = endpoint;
+ }
+
+ public void start() {
+ endpoint.getServiceIdentityProvider().ifPresent(provider -> {
+ onCredentialsUpdate(provider.getIdentitySslContext(), null);
+ provider.addIdentityListener(this);
+ });
+ }
+
+ @Override
+ public void onCredentialsUpdate(SSLContext sslContext, AthenzService ignored) {
+ SSLConnectionSocketFactory socketFactory =
+ new SSLConnectionSocketFactory(sslContext, endpoint.getHostnameVerifier().orElse(null));
+
+ 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() {
+ endpoint.getServiceIdentityProvider().ifPresent(provider -> provider.removeIdentityListener(this));
+
+ try {
+ httpClient.close();
+ } catch (Exception e) {
+ // ignore
+ }
+ httpClient = null;
+ }
+
+ private HealthInfo probeHealth() throws Exception {
+ HttpGet httpget = new HttpGet(endpoint.getStateV1HealthUrl().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..e9d17a9ab70
--- /dev/null
+++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthEndpoint.java
@@ -0,0 +1,57 @@
+// 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.identity.ServiceIdentityProvider;
+import com.yahoo.vespa.athenz.tls.AthenzIdentityVerifier;
+
+import javax.net.ssl.HostnameVerifier;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Optional;
+
+import static com.yahoo.yolean.Exceptions.uncheck;
+
+/**
+ * @author hakon
+ */
+class HealthEndpoint {
+ private final URL url;
+ private final Optional<HostnameVerifier> hostnameVerifier;
+ private final Optional<ServiceIdentityProvider> serviceIdentityProvider;
+
+ static HealthEndpoint forHttp(HostName hostname, int port) {
+ URL url = uncheck(() -> new URL("http", hostname.value(), port, "/state/v1/health"));
+ return new HealthEndpoint(url, Optional.empty(), Optional.empty());
+ }
+
+ static HealthEndpoint forHttps(HostName hostname,
+ int port,
+ ServiceIdentityProvider serviceIdentityProvider,
+ 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, Optional.of(serviceIdentityProvider), Optional.of(peerVerifier));
+ }
+
+ private HealthEndpoint(URL url,
+ Optional<ServiceIdentityProvider> serviceIdentityProvider,
+ Optional<HostnameVerifier> hostnameVerifier) {
+ this.url = url;
+ this.serviceIdentityProvider = serviceIdentityProvider;
+ this.hostnameVerifier = hostnameVerifier;
+ }
+
+ public URL getStateV1HealthUrl() {
+ return url;
+ }
+
+ public Optional<ServiceIdentityProvider> getServiceIdentityProvider() {
+ return serviceIdentityProvider;
+ }
+
+ public Optional<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..fd809b32918
--- /dev/null
+++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/internal/health/HealthMonitor.java
@@ -0,0 +1,73 @@
+// 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 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(HealthEndpoint stateV1HealthEndpoint) {
+ this.healthClient = new HealthClient(stateV1HealthEndpoint);
+ }
+
+ /** For testing. */
+ HealthMonitor(HealthClient healthClient) {
+ this.healthClient = healthClient;
+ }
+
+ public void startMonitoring() {
+ healthClient.start();
+ 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..473ef5e3a94 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,8 +2,8 @@
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;
@@ -12,19 +12,38 @@ import com.yahoo.vespa.applicationmodel.ServiceType;
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;
+
@Inject
- public HealthMonitorManager() {}
+ public HealthMonitorManager(ConfigserverConfig configserverConfig) {
+ this.configserverConfig = configserverConfig;
+ }
@Override
- public void applicationActivated(SuperModel superModel, ApplicationInfo application) {
+ public void applicationActivated(ApplicationInfo application) {
+ if (applicationMonitored(application.getApplicationId())) {
+ ApplicationHealthMonitor monitor =
+ ApplicationHealthMonitor.startMonitoring(application);
+ 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 +51,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..85df02949a6
--- /dev/null
+++ b/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/ConfigserverUtil.java
@@ -0,0 +1,52 @@
+// 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 nodeAdminInContainer,
+ boolean multitenant,
+ String configServerHostname1,
+ String configServerHostname2,
+ String configServerHostname3) {
+ return new ConfigserverConfig(
+ new ConfigserverConfig.Builder()
+ .nodeAdminInContainer(nodeAdminInContainer)
+ .multitenant(multitenant)
+ .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() {
+ return create(true, true, "cfg1", "cfg2", "cfg3");
+ }
+
+ public static ConfigserverConfig createExampleConfigserverConfig(boolean nodeAdminInContainer,
+ boolean multitenant) {
+ return create(nodeAdminInContainer, multitenant, "cfg1", "cfg2", "cfg3");
+ }
+
+ public static ApplicationInfo makeConfigServerApplicationInfo(
+ String configServerHostname1,
+ String configServerHostname2,
+ String configServerHostname3) {
+ return ConfigServerApplication.CONFIG_SERVER_APPLICATION.makeApplicationInfo(create(
+ true,
+ true,
+ configServerHostname1,
+ configServerHostname2,
+ configServerHostname3));
+ }
+
+ public static ApplicationInfo makeExampleConfigServer() {
+ return makeConfigServerApplicationInfo("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..c9d19d0ccd9
--- /dev/null
+++ b/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/DuperModelTest.java
@@ -0,0 +1,53 @@
+// 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();
+ 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));
+ }
+
+ @Test
+ public void toApplicationInstanceInSingleTenantMode() {
+ when(statusProvider.getStatus(any(), any(), any(), any())).thenReturn(ServiceStatus.NOT_CHECKED);
+ ConfigserverConfig config = ConfigserverUtil.createExampleConfigserverConfig(true, false);
+ 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(1, applicationInfos.size());
+ assertSame(superModelApplicationInfo, applicationInfos.get(0));
+ }
+}
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..5a57451a298 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;
@@ -35,13 +32,12 @@ public class ModelGeneratorTest {
private final int PORT = 2;
@Test
- public void toApplicationModelWithConfigServerApplication() throws Exception {
- SuperModel superModel =
- ExampleModel.createExampleSuperModelWithOneRpcPort(HOSTNAME, PORT);
+ public void toApplicationModel() throws Exception {
+ 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.createExampleConfigserverConfig();
+ DuperModel duperModel = new DuperModel(config);
+ ModelGenerator modelGenerator = new ModelGenerator();
Zone zone = new Zone(Environment.from(ENVIRONMENT), RegionName.from(REGION));
@@ -51,7 +47,7 @@ public class ModelGeneratorTest {
ServiceModel serviceModel =
modelGenerator.toServiceModel(
- superModel,
+ duperModel.getApplicationInfos(superModel),
zone,
slobrokMonitorManager);
@@ -78,32 +74,6 @@ public class ModelGeneratorTest {
}
}
- @Test
- public void toApplicationModel() throws Exception {
- SuperModel superModel =
- ExampleModel.createExampleSuperModelWithOneRpcPort(HOSTNAME, PORT);
- ModelGenerator modelGenerator = new ModelGenerator(Collections.emptyList());
-
- Zone zone = new Zone(Environment.from(ENVIRONMENT), RegionName.from(REGION));
-
- SlobrokMonitorManagerImpl slobrokMonitorManager = mock(SlobrokMonitorManagerImpl.class);
- when(slobrokMonitorManager.getStatus(any(), any(), any(), any()))
- .thenReturn(ServiceStatus.UP);
-
- ServiceModel serviceModel =
- modelGenerator.toServiceModel(
- superModel,
- zone,
- slobrokMonitorManager);
-
- Map<ApplicationInstanceReference,
- ApplicationInstance> applicationInstances =
- serviceModel.getAllApplicationInstances();
-
- assertEquals(1, applicationInstances.size());
- verifyOtherApplication(applicationInstances.values().iterator().next());
- }
-
private void verifyOtherApplication(ApplicationInstance applicationInstance) {
assertEquals(String.format("%s:%s:%s:%s:%s",
ExampleModel.TENANT,
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..51b0503565f
--- /dev/null
+++ b/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/health/ApplicationHealthMonitorTest.java
@@ -0,0 +1,24 @@
+// 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.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;
+
+public class ApplicationHealthMonitorTest {
+ @Test
+ public void sanityCheck() {
+ ApplicationHealthMonitor monitor = ApplicationHealthMonitor.startMonitoring(
+ ConfigserverUtil.makeExampleConfigServer());
+ 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..b9d25406f9b
--- /dev/null
+++ b/service-monitor/src/test/java/com/yahoo/vespa/service/monitor/internal/health/HealthMonitorManagerTest.java
@@ -0,0 +1,49 @@
+// 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.service.monitor.application.ZoneApplication;
+import com.yahoo.vespa.service.monitor.internal.ConfigserverUtil;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class HealthMonitorManagerTest {
+ @Test
+ public void addRemove() {
+ ConfigserverConfig config = ConfigserverUtil.createExampleConfigserverConfig();
+ HealthMonitorManager manager = new HealthMonitorManager(config);
+ ApplicationInfo applicationInfo = ConfigserverUtil.makeExampleConfigServer();
+ manager.applicationActivated(applicationInfo);
+ manager.applicationRemoved(applicationInfo.getApplicationId());
+ }
+
+ @Test
+ public void withNodeAdmin() {
+ ConfigserverConfig config = ConfigserverUtil.createExampleConfigserverConfig();
+ HealthMonitorManager manager = new HealthMonitorManager(config);
+ 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, true);
+ HealthMonitorManager manager = new HealthMonitorManager(config);
+ 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"));
}
diff --git a/valgrind-suppressions.txt b/valgrind-suppressions.txt
index 2df6c9c5691..2587552ceff 100644
--- a/valgrind-suppressions.txt
+++ b/valgrind-suppressions.txt
@@ -339,3 +339,20 @@
fun:__static_initialization_and_destruction_0
...
}
+{
+ Apparent memory leak on Fedora 28.
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:malloc
+ fun:tsearch
+ fun:__add_to_environ
+ fun:setenv
+}
+{
+ Apparent memory leak on Fedora 28.
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:malloc
+ fun:__add_to_environ
+ fun:setenv
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapper.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapper.java
index 12389712976..ab127b19bf1 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapper.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapper.java
@@ -10,9 +10,12 @@ import com.yahoo.vespa.athenz.identityprovider.api.bindings.SignedIdentityDocume
import com.yahoo.vespa.athenz.identityprovider.api.bindings.VespaUniqueInstanceIdEntity;
import com.yahoo.vespa.athenz.utils.AthenzIdentities;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Path;
import java.util.Base64;
-import static com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId.*;
+import static com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId.fromDottedString;
/**
* Utility class for mapping objects model types and their Jackson binding versions.
@@ -102,4 +105,22 @@ public class EntityBindingsMapper {
}
}
+ public static SignedIdentityDocument readSignedIdentityDocumentFromFile(Path file) {
+ try {
+ SignedIdentityDocumentEntity entity = mapper.readValue(file.toFile(), SignedIdentityDocumentEntity.class);
+ return EntityBindingsMapper.toSignedIdentityDocument(entity);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ public static void writeSignedIdentityDocumentToFile(Path file, SignedIdentityDocument document) {
+ try {
+ SignedIdentityDocumentEntity entity = EntityBindingsMapper.toSignedIdentityDocumentEntity(document);
+ mapper.writeValue(file.toFile(), entity);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
}