summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--athenz-identity-provider-service/pom.xml8
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslKeyStoreConfigurator.java10
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGenerator.java28
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentResource.java18
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidator.java1
-rw-r--r--athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGeneratorTest.java9
-rw-r--r--athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidatorTest.java7
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/ValidationOverrides.java9
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java7
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ClusterSizeReductionValidatorTest.java5
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ContentClusterRemovalValidatorTest.java5
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ContentTypeRemovalValidatorTest.java5
-rw-r--r--config/src/main/java/com/yahoo/config/subscription/impl/JRTConfigRequester.java4
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionImpl.java6
-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/main/java/com/yahoo/vespa/config/server/session/FileDistributionFactory.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--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Organization.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java47
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java211
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackage.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/concurrent/Locks.java55
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentOrder.java20
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java26
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java10
-rw-r--r--docprocs/src/test/java/com/yahoo/docprocs/indexing/DocumentScriptTestCase.java91
-rw-r--r--document/src/main/java/com/yahoo/document/datatypes/Array.java3
-rw-r--r--fat-model-dependencies/pom.xml7
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldPathUpdateHelper.java17
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/IdentityFieldPathUpdateAdapter.java68
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/SimpleAdapterFactory.java6
-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/StorageMaintainer.java4
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java104
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java2
-rw-r--r--node-maintainer/src/main/java/com/yahoo/vespa/hosted/node/maintainer/CoredumpHandler.java14
-rw-r--r--node-maintainer/src/main/java/com/yahoo/vespa/hosted/node/maintainer/FileHelper.java12
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java4
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/NodeIdentifierTest.java3
-rw-r--r--orchestrator/src/main/java/com/yahoo/vespa/orchestrator/ServiceMonitorInstanceLookupService.java2
-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--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapper.java43
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/IdentityDocument.java2
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/IdentityType.java25
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/SignedIdentityDocument.java39
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/VespaUniqueInstanceId.java46
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/IdentityDocumentApi.java6
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/IdentityDocumentEntity.java2
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/SignedIdentityDocumentEntity.java41
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/VespaUniqueInstanceIdEntity.java26
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzCredentialsService.java3
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/DefaultIdentityDocumentClient.java16
-rw-r--r--vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/api/VespaUniqueInstanceIdTest.java13
-rw-r--r--vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImplTest.java10
106 files changed, 2478 insertions, 1088 deletions
diff --git a/athenz-identity-provider-service/pom.xml b/athenz-identity-provider-service/pom.xml
index 86d4defa861..982cb89f2bf 100644
--- a/athenz-identity-provider-service/pom.xml
+++ b/athenz-identity-provider-service/pom.xml
@@ -131,6 +131,14 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <compilerArgs>
+ <arg>-Xlint:all</arg>
+ <arg>-Xlint:-deprecation</arg>
+ <arg>-Xlint:-serial</arg>
+ <arg>-Werror</arg>
+ </compilerArgs>
+ </configuration>
</plugin>
</plugins>
</build>
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslKeyStoreConfigurator.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslKeyStoreConfigurator.java
index f1fc938d3ea..2a517e06ae2 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslKeyStoreConfigurator.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslKeyStoreConfigurator.java
@@ -23,11 +23,11 @@ import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PrivateKey;
-import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.time.Instant;
import java.util.Optional;
+import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -45,7 +45,6 @@ import static com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.Utils.g
@SuppressWarnings("unused") // Component injected into Jetty connector factory
public class AthenzSslKeyStoreConfigurator extends AbstractComponent implements SslKeyStoreConfigurator {
private static final Logger log = Logger.getLogger(AthenzSslKeyStoreConfigurator.class.getName());
- private static final SecureRandom secureRandom = new SecureRandom();
private static final String CERTIFICATE_ALIAS = "athenz";
private static final Duration EXPIRATION_MARGIN = Duration.ofHours(6);
@@ -172,12 +171,7 @@ public class AthenzSslKeyStoreConfigurator extends AbstractComponent implements
}
private static char[] generateKeystorePassword() {
- int length = 128;
- char[] pwd = new char[length];
- for (int i = 0; i < length; i++) {
- pwd[i] = (char) secureRandom.nextInt();
- }
- return pwd;
+ return UUID.randomUUID().toString().toCharArray();
}
private class AthenzCertificateUpdater implements Runnable {
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGenerator.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGenerator.java
index 728406c297f..59126fd023f 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGenerator.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGenerator.java
@@ -7,6 +7,7 @@ import com.yahoo.net.HostName;
import com.yahoo.vespa.athenz.api.AthenzService;
import com.yahoo.vespa.athenz.identityprovider.api.EntityBindingsMapper;
import com.yahoo.vespa.athenz.identityprovider.api.IdentityDocument;
+import com.yahoo.vespa.athenz.identityprovider.api.IdentityType;
import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument;
import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId;
import com.yahoo.vespa.hosted.athenz.instanceproviderservice.KeyProvider;
@@ -27,7 +28,10 @@ import java.util.Objects;
import java.util.Set;
/**
+ * Generates a signed identity document for a given hostname and type
+ *
* @author mortent
+ * @author bjorncs
*/
public class IdentityDocumentGenerator {
@@ -47,10 +51,10 @@ public class IdentityDocumentGenerator {
this.keyProvider = keyProvider;
}
- public SignedIdentityDocument generateSignedIdentityDocument(String hostname) {
+ public SignedIdentityDocument generateSignedIdentityDocument(String hostname, IdentityType identityType) {
Node node = nodeRepository.getNode(hostname).orElseThrow(() -> new RuntimeException("Unable to find node " + hostname));
try {
- IdentityDocument identityDocument = generateIdDocument(node);
+ IdentityDocument identityDocument = generateIdDocument(node, identityType);
String identityDocumentString = Utils.getMapper().writeValueAsString(EntityBindingsMapper.toIdentityDocumentEntity(identityDocument));
String encodedIdentityDocument =
@@ -70,13 +74,18 @@ public class IdentityDocumentGenerator {
toZoneDnsSuffix(zone, zoneConfig.certDnsSuffix()),
new AthenzService(zoneConfig.domain(), zoneConfig.serviceName()),
URI.create(zoneConfig.ztsUrl()),
- SignedIdentityDocument.DEFAULT_DOCUMENT_VERSION);
+ SignedIdentityDocument.DEFAULT_DOCUMENT_VERSION,
+ identityDocument.configServerHostname(),
+ identityDocument.instanceHostname(),
+ identityDocument.createdAt(),
+ identityDocument.ipAddresses(),
+ identityType);
} catch (Exception e) {
throw new RuntimeException("Exception generating identity document: " + e.getMessage(), e);
}
}
- private IdentityDocument generateIdDocument(Node node) {
+ private IdentityDocument generateIdDocument(Node node, IdentityType identityType) {
Allocation allocation = node.allocation().orElseThrow(() -> new RuntimeException("No allocation for node " + node.hostname()));
VespaUniqueInstanceId providerUniqueId = new VespaUniqueInstanceId(
allocation.membership().index(),
@@ -85,17 +94,10 @@ public class IdentityDocumentGenerator {
allocation.owner().application().value(),
allocation.owner().tenant().value(),
zone.region().value(),
- zone.environment().value());
+ zone.environment().value(),
+ identityType);
- // TODO: Hack to allow access from docker containers to non-ipv6 services.
- // Remove when yca-bridge is no longer needed
Set<String> ips = new HashSet<>(node.ipAddresses());
- if(node.parentHostname().isPresent()) {
- String parentHostName = node.parentHostname().get();
- nodeRepository.getNode(parentHostName)
- .map(Node::ipAddresses)
- .ifPresent(ips::addAll);
- }
return new IdentityDocument(
providerUniqueId,
HostName.getLocalhost(),
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentResource.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentResource.java
index 93668006e26..219e12c7223 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentResource.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentResource.java
@@ -6,6 +6,7 @@ import com.yahoo.container.jaxrs.annotation.Component;
import com.yahoo.jdisc.http.servlet.ServletRequest;
import com.yahoo.log.LogLevel;
import com.yahoo.vespa.athenz.identityprovider.api.EntityBindingsMapper;
+import com.yahoo.vespa.athenz.identityprovider.api.IdentityType;
import com.yahoo.vespa.athenz.identityprovider.api.bindings.IdentityDocumentApi;
import com.yahoo.vespa.athenz.identityprovider.api.bindings.SignedIdentityDocumentEntity;
import com.yahoo.vespa.hosted.provision.restapi.v2.filter.NodePrincipal;
@@ -18,7 +19,6 @@ import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import java.util.logging.Logger;
@@ -41,15 +41,7 @@ public class IdentityDocumentResource implements IdentityDocumentApi {
this.request = request;
}
- /**
- * @deprecated Use {@link #getNodeIdentityDocument(String)} and {@link #getTenantIdentityDocument(String)} instead.
- */
- @GET
- @Produces(MediaType.APPLICATION_JSON)
- @Deprecated
- @Override
- // TODO Make this method private when the rest api is not longer in use
- public SignedIdentityDocumentEntity getIdentityDocument(@QueryParam("hostname") String hostname) {
+ private SignedIdentityDocumentEntity getIdentityDocument(String hostname, IdentityType identityType) {
if (hostname == null) {
throw new BadRequestException("The 'hostname' query parameter is missing");
}
@@ -67,7 +59,7 @@ public class IdentityDocumentResource implements IdentityDocumentApi {
throw new ForbiddenException();
}
try {
- return EntityBindingsMapper.toSignedIdentityDocumentEntity(identityDocumentGenerator.generateSignedIdentityDocument(hostname));
+ return EntityBindingsMapper.toSignedIdentityDocumentEntity(identityDocumentGenerator.generateSignedIdentityDocument(hostname, identityType));
} catch (Exception e) {
String message = String.format("Unable to generate identity doument for '%s': %s", hostname, e.getMessage());
log.log(LogLevel.ERROR, message, e);
@@ -80,7 +72,7 @@ public class IdentityDocumentResource implements IdentityDocumentApi {
@Path("/node/{host}")
@Override
public SignedIdentityDocumentEntity getNodeIdentityDocument(@PathParam("host") String host) {
- return getIdentityDocument(host);
+ return getIdentityDocument(host, IdentityType.NODE);
}
@GET
@@ -88,7 +80,7 @@ public class IdentityDocumentResource implements IdentityDocumentApi {
@Path("/tenant/{host}")
@Override
public SignedIdentityDocumentEntity getTenantIdentityDocument(@PathParam("host") String host) {
- return getIdentityDocument(host);
+ return getIdentityDocument(host, IdentityType.TENANT);
}
}
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidator.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidator.java
index e457df37946..0201c46b253 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidator.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidator.java
@@ -82,6 +82,7 @@ public class InstanceValidator {
}
// If/when we dont care about logging exactly whats wrong, this can be simplified
+ // TODO Use identity type to determine if this check should be performed
boolean isSameIdentityAsInServicesXml(ApplicationId applicationId, String domain, String service) {
Optional<ApplicationInfo> applicationInfo = superModelProvider.getSuperModel().getApplicationInfo(applicationId);
diff --git a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGeneratorTest.java b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGeneratorTest.java
index d7b061ca2f1..078ef1b7e39 100644
--- a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGeneratorTest.java
+++ b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGeneratorTest.java
@@ -15,6 +15,7 @@ import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Zone;
import com.yahoo.vespa.athenz.identityprovider.api.EntityBindingsMapper;
+import com.yahoo.vespa.athenz.identityprovider.api.IdentityType;
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;
@@ -81,7 +82,7 @@ public class IdentityDocumentGeneratorTest {
AthenzProviderServiceConfig config = getAthenzProviderConfig("domain", "service", dnsSuffix, ZONE);
IdentityDocumentGenerator identityDocumentGenerator =
new IdentityDocumentGenerator(config, nodeRepository, ZONE, keyProvider);
- SignedIdentityDocument signedIdentityDocument = identityDocumentGenerator.generateSignedIdentityDocument(containerHostname);
+ SignedIdentityDocument signedIdentityDocument = identityDocumentGenerator.generateSignedIdentityDocument(containerHostname, IdentityType.TENANT);
// Verify attributes
assertEquals(containerHostname, signedIdentityDocument.identityDocument().instanceHostname());
@@ -92,11 +93,11 @@ public class IdentityDocumentGeneratorTest {
assertEquals(expectedZoneDnsSuffix, signedIdentityDocument.dnsSuffix());
VespaUniqueInstanceId expectedProviderUniqueId =
- new VespaUniqueInstanceId(0, "default", "default", "application", "tenant", region, environment);
+ new VespaUniqueInstanceId(0, "default", "default", "application", "tenant", region, environment, IdentityType.TENANT);
assertEquals(expectedProviderUniqueId, signedIdentityDocument.providerUniqueId());
- // Validate that both parent and container ips are present
- assertThat(signedIdentityDocument.identityDocument().ipAddresses(), Matchers.containsInAnyOrder("127.0.0.1", "::1"));
+ // Validate that container ips are present
+ assertThat(signedIdentityDocument.identityDocument().ipAddresses(), Matchers.containsInAnyOrder("::1"));
SignedIdentityDocumentEntity signedIdentityDocumentEntity = EntityBindingsMapper.toSignedIdentityDocumentEntity(signedIdentityDocument);
diff --git a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidatorTest.java b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidatorTest.java
index 54786c86cd3..54411b424eb 100644
--- a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidatorTest.java
+++ b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidatorTest.java
@@ -143,7 +143,12 @@ public class InstanceValidatorTest {
"dnssuffix",
"service",
URI.create("http://localhost/zts"),
- 1));
+ 1,
+ identityDocument.configServerHostname,
+ identityDocument.instanceHostname,
+ identityDocument.createdAt,
+ identityDocument.ipAddresses,
+ null)); // TODO Remove support for legacy representation without type
} catch (Exception e) {
throw new RuntimeException(e);
}
diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/ValidationOverrides.java b/config-model-api/src/main/java/com/yahoo/config/application/api/ValidationOverrides.java
index 11f9add6b25..441ef273a6f 100644
--- a/config-model-api/src/main/java/com/yahoo/config/application/api/ValidationOverrides.java
+++ b/config-model-api/src/main/java/com/yahoo/config/application/api/ValidationOverrides.java
@@ -66,6 +66,11 @@ public class ValidationOverrides {
return false;
}
+ public static String toAllowMessage(ValidationId id) {
+ return "To allow this add <allow until='yyyy-mm-dd'>" + id + "</allow> to validation-overrides.xml" +
+ ", see https://docs.vespa.ai/documentation/reference/validation-overrides.html";
+ }
+
/** Returns the XML form of this, or null if it was not created by fromXml, nor is empty */
public String xmlForm() { return xmlForm; }
@@ -155,7 +160,9 @@ public class ValidationOverrides {
/** Returns "validationId: message" */
@Override
- public String getMessage() { return validationId + ": " + super.getMessage(); }
+ public String getMessage() {
+ return validationId + ": " + super.getMessage() + ". " + toAllowMessage(validationId);
+ }
}
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/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ClusterSizeReductionValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ClusterSizeReductionValidatorTest.java
index 765acf9e27b..4c3583ba0ae 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ClusterSizeReductionValidatorTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ClusterSizeReductionValidatorTest.java
@@ -1,6 +1,8 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.application.validation.change;
+import com.yahoo.config.application.api.ValidationId;
+import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.model.api.ConfigChangeAction;
import com.yahoo.config.model.api.ConfigChangeRefeedAction;
import com.yahoo.vespa.model.VespaModel;
@@ -33,7 +35,8 @@ public class ClusterSizeReductionValidatorTest {
fail("Expected exception due to cluster size reduction");
}
catch (IllegalArgumentException expected) {
- assertEquals("cluster-size-reduction: Size reduction in 'default' is too large. Current size: 30, new size: 14. New size must be at least 50% of the current size",
+ assertEquals("cluster-size-reduction: Size reduction in 'default' is too large. Current size: 30, new size: 14. New size must be at least 50% of the current size. " +
+ ValidationOverrides.toAllowMessage(ValidationId.clusterSizeReduction),
Exceptions.toMessageString(expected));
}
}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ContentClusterRemovalValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ContentClusterRemovalValidatorTest.java
index 25ad6dbc620..ee58ca67b02 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ContentClusterRemovalValidatorTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ContentClusterRemovalValidatorTest.java
@@ -1,6 +1,8 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.application.validation.change;
+import com.yahoo.config.application.api.ValidationId;
+import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.vespa.model.VespaModel;
import com.yahoo.vespa.model.application.validation.ValidationTester;
import com.yahoo.yolean.Exceptions;
@@ -24,7 +26,8 @@ public class ContentClusterRemovalValidatorTest {
fail("Expected exception due to content cluster id change");
}
catch (IllegalArgumentException expected) {
- assertEquals("content-cluster-removal: Content cluster 'contentClusterId' is removed. This will cause loss of all data in this cluster",
+ assertEquals("content-cluster-removal: Content cluster 'contentClusterId' is removed. This will cause loss of all data in this cluster. " +
+ ValidationOverrides.toAllowMessage(ValidationId.contentClusterRemoval),
Exceptions.toMessageString(expected));
}
}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ContentTypeRemovalValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ContentTypeRemovalValidatorTest.java
index a52c6d7c7a2..ca45520711e 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ContentTypeRemovalValidatorTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ContentTypeRemovalValidatorTest.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.model.application.validation.change;
+import com.yahoo.config.application.api.ValidationId;
+import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.vespa.model.VespaModel;
import com.yahoo.vespa.model.application.validation.ValidationTester;
import com.yahoo.yolean.Exceptions;
@@ -28,7 +30,8 @@ public class ContentTypeRemovalValidatorTest {
}
catch (IllegalArgumentException expected) {
assertEquals("content-type-removal: Type 'music' is removed in content cluster 'test'. " +
- "This will cause loss of all data of this type",
+ "This will cause loss of all data of this type. " +
+ ValidationOverrides.toAllowMessage(ValidationId.contentTypeRemoval),
Exceptions.toMessageString(expected));
}
}
diff --git a/config/src/main/java/com/yahoo/config/subscription/impl/JRTConfigRequester.java b/config/src/main/java/com/yahoo/config/subscription/impl/JRTConfigRequester.java
index 88af414e28d..243c9e932a8 100644
--- a/config/src/main/java/com/yahoo/config/subscription/impl/JRTConfigRequester.java
+++ b/config/src/main/java/com/yahoo/config/subscription/impl/JRTConfigRequester.java
@@ -30,10 +30,10 @@ import com.yahoo.vespa.config.protocol.Trace;
* as context, and puts the requests objects on a queue on the subscription,
* for handling by the user thread.
*
- * @author vegardh
- * @since 5.1
+ * @author Vegard Havdal
*/
public class JRTConfigRequester implements RequestWaiter {
+
private static final Logger log = Logger.getLogger(JRTConfigRequester.class.getName());
public static final ConfigSourceSet defaultSourceSet = ConfigSourceSet.createDefault();
private static final int TRACELEVEL = 6;
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionImpl.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionImpl.java
index 2db89c2e8ed..36d76bbfc79 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionImpl.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionImpl.java
@@ -10,7 +10,6 @@ import com.yahoo.jrt.Spec;
import com.yahoo.jrt.StringArray;
import com.yahoo.jrt.Supervisor;
import com.yahoo.jrt.Target;
-import com.yahoo.jrt.Transport;
import com.yahoo.log.LogLevel;
import com.yahoo.vespa.defaults.Defaults;
@@ -24,11 +23,12 @@ import java.util.logging.Logger;
public class FileDistributionImpl implements FileDistribution {
private final static Logger log = Logger.getLogger(FileDistributionImpl.class.getName());
- private final Supervisor supervisor = new Supervisor(new Transport());
+ private final Supervisor supervisor;
private final File fileReferencesDir;
- public FileDistributionImpl(ConfigserverConfig configserverConfig) {
+ public FileDistributionImpl(ConfigserverConfig configserverConfig, Supervisor supervisor) {
this.fileReferencesDir = new File(Defaults.getDefaults().underVespaHome(configserverConfig.fileReferencesDir()));
+ this.supervisor = supervisor;
}
@Override
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/main/java/com/yahoo/vespa/config/server/session/FileDistributionFactory.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/FileDistributionFactory.java
index 8394494adca..15bc3c1fb46 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/FileDistributionFactory.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/FileDistributionFactory.java
@@ -3,6 +3,8 @@ package com.yahoo.vespa.config.server.session;
import com.google.inject.Inject;
import com.yahoo.cloud.config.ConfigserverConfig;
+import com.yahoo.jrt.Supervisor;
+import com.yahoo.jrt.Transport;
import com.yahoo.vespa.config.server.filedistribution.FileDistributionImpl;
import com.yahoo.vespa.config.server.filedistribution.FileDistributionProvider;
@@ -17,6 +19,7 @@ import java.io.File;
public class FileDistributionFactory {
private final ConfigserverConfig configserverConfig;
+ private final Supervisor supervisor = new Supervisor(new Transport());
@Inject
public FileDistributionFactory(ConfigserverConfig configserverConfig) {
@@ -24,7 +27,7 @@ public class FileDistributionFactory {
}
public FileDistributionProvider createProvider(File applicationPackage) {
- return new FileDistributionProvider(applicationPackage, new FileDistributionImpl(configserverConfig));
+ return new FileDistributionProvider(applicationPackage, new FileDistributionImpl(configserverConfig, supervisor));
}
}
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/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Organization.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Organization.java
index 00c0d87554a..776002f31cb 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Organization.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Organization.java
@@ -3,7 +3,6 @@ package com.yahoo.vespa.hosted.controller.api.integration.organization;
import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
-import java.io.UncheckedIOException;
import java.net.URI;
import java.time.Duration;
import java.util.List;
@@ -87,8 +86,9 @@ public interface Organization {
*
* @param issueId ID of the issue to escalate.
* @param propertyId PropertyId of the tenant owning the application for which the issue was filed.
+ * @return User that was assigned issue as a result of the escalation, if any
*/
- default boolean escalate(IssueId issueId, PropertyId propertyId) {
+ default Optional<User> escalate(IssueId issueId, PropertyId propertyId) {
List<? extends List<? extends User>> contacts = contactsFor(propertyId);
Optional<User> assignee = assigneeOf(issueId);
@@ -101,9 +101,9 @@ public interface Organization {
for (int level = assigneeLevel + 1; level < contacts.size(); level++)
for (User target : contacts.get(level))
if (reassign(issueId, target))
- return true;
+ return Optional.of(target);
- return false;
+ return Optional.empty();
}
/**
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
index 8b0dc35e16b..f0e278c3e6d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
@@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableList;
import com.yahoo.component.Version;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.ValidationId;
+import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.TenantName;
@@ -109,7 +110,7 @@ public class ApplicationController {
this.artifactRepository = artifactRepository;
this.rotationRepository = new RotationRepository(rotationsConfig, this, curator);
- this.deploymentTrigger = new DeploymentTrigger(controller, curator, buildService, clock);
+ this.deploymentTrigger = new DeploymentTrigger(controller, buildService, clock);
for (Application application : curator.readApplications()) {
lockIfPresent(application.id(), this::store);
@@ -256,7 +257,7 @@ public class ApplicationController {
LockedApplication application = new LockedApplication(new Application(id), lock);
store(application);
log.info("Created " + application);
- return application;
+ return application.get();
}
}
@@ -285,7 +286,7 @@ public class ApplicationController {
} else {
JobType jobType = JobType.from(controller.system(), zone)
.orElseThrow(() -> new IllegalArgumentException("No job found for zone " + zone));
- Optional<JobStatus> job = Optional.ofNullable(application.deploymentJobs().jobStatus().get(jobType));
+ Optional<JobStatus> job = Optional.ofNullable(application.get().deploymentJobs().jobStatus().get(jobType));
if ( ! job.isPresent()
|| ! job.get().lastTriggered().isPresent()
|| job.get().lastCompleted().isPresent() && job.get().lastCompleted().get().at().isAfter(job.get().lastTriggered().get().at()))
@@ -297,8 +298,8 @@ public class ApplicationController {
applicationVersion = preferOldestVersion
? triggered.sourceApplication().orElse(triggered.application())
: triggered.application();
- applicationPackage = new ApplicationPackage(artifactRepository.getApplicationPackage(application.id(), applicationVersion.id()));
- validateRun(application, zone, platformVersion, applicationVersion);
+ applicationPackage = new ApplicationPackage(artifactRepository.getApplicationPackage(application.get().id(), applicationVersion.id()));
+ validateRun(application.get(), zone, platformVersion, applicationVersion);
}
validate(applicationPackage.deploymentSpec());
@@ -323,7 +324,7 @@ public class ApplicationController {
application = withRotation(application, zone);
Set<String> rotationNames = new HashSet<>();
Set<String> cnames = new HashSet<>();
- application.rotation().ifPresent(applicationRotation -> {
+ application.get().rotation().ifPresent(applicationRotation -> {
rotationNames.add(applicationRotation.id().asString());
cnames.add(applicationRotation.dnsName());
cnames.add(applicationRotation.secureDnsName());
@@ -366,15 +367,15 @@ public class ApplicationController {
/** Makes sure the application has a global rotation, if eligible. */
private LockedApplication withRotation(LockedApplication application, ZoneId zone) {
- if (zone.environment() == Environment.prod && application.deploymentSpec().globalServiceId().isPresent()) {
+ if (zone.environment() == Environment.prod && application.get().deploymentSpec().globalServiceId().isPresent()) {
try (RotationLock rotationLock = rotationRepository.lock()) {
- Rotation rotation = rotationRepository.getOrAssignRotation(application, rotationLock);
+ Rotation rotation = rotationRepository.getOrAssignRotation(application.get(), rotationLock);
application = application.with(rotation.id());
store(application); // store assigned rotation even if deployment fails
- registerRotationInDns(rotation, application.rotation().get().dnsName());
- registerRotationInDns(rotation, application.rotation().get().secureDnsName());
- registerRotationInDns(rotation, application.rotation().get().oathDnsName());
+ registerRotationInDns(rotation, application.get().rotation().get().dnsName());
+ registerRotationInDns(rotation, application.get().rotation().get().secureDnsName());
+ registerRotationInDns(rotation, application.get().rotation().get().oathDnsName());
}
}
return application;
@@ -394,22 +395,23 @@ public class ApplicationController {
}
private LockedApplication deleteRemovedDeployments(LockedApplication application) {
- List<Deployment> deploymentsToRemove = application.productionDeployments().values().stream()
- .filter(deployment -> ! application.deploymentSpec().includes(deployment.zone().environment(),
- Optional.of(deployment.zone().region())))
+ List<Deployment> deploymentsToRemove = application.get().productionDeployments().values().stream()
+ .filter(deployment -> ! application.get().deploymentSpec().includes(deployment.zone().environment(),
+ Optional.of(deployment.zone().region())))
.collect(Collectors.toList());
if (deploymentsToRemove.isEmpty()) return application;
- if ( ! application.validationOverrides().allows(ValidationId.deploymentRemoval, clock.instant()))
- throw new IllegalArgumentException(ValidationId.deploymentRemoval.value() + ": " + application +
+ if ( ! application.get().validationOverrides().allows(ValidationId.deploymentRemoval, clock.instant()))
+ throw new IllegalArgumentException(ValidationId.deploymentRemoval.value() + ": " + application.get() +
" is deployed in " +
deploymentsToRemove.stream()
.map(deployment -> deployment.zone().region().value())
.collect(Collectors.joining(", ")) +
", but does not include " +
(deploymentsToRemove.size() > 1 ? "these zones" : "this zone") +
- " in deployment.xml");
+ " in deployment.xml. " +
+ ValidationOverrides.toAllowMessage(ValidationId.deploymentRemoval));
LockedApplication applicationWithRemoval = application;
for (Deployment deployment : deploymentsToRemove)
@@ -418,10 +420,11 @@ public class ApplicationController {
}
private LockedApplication deleteUnreferencedDeploymentJobs(LockedApplication application) {
- for (JobType job : application.deploymentJobs().jobStatus().keySet()) {
+ for (JobType job : application.get().deploymentJobs().jobStatus().keySet()) {
Optional<ZoneId> zone = job.zone(controller.system());
- if ( ! job.isProduction() || (zone.isPresent() && application.deploymentSpec().includes(zone.get().environment(), zone.map(ZoneId::region))))
+ if ( ! job.isProduction() || (zone.isPresent() && application.get().deploymentSpec().includes(
+ zone.get().environment(), zone.map(ZoneId::region))))
continue;
application = application.withoutDeploymentJob(job);
}
@@ -493,7 +496,7 @@ public class ApplicationController {
// TODO: Make this one transaction when database is moved to ZooKeeper
instances.forEach(id -> lockOrThrow(id, application -> {
- if ( ! application.deployments().isEmpty())
+ if ( ! application.get().deployments().isEmpty())
throw new IllegalArgumentException("Could not delete '" + application + "': It has active deployments");
Tenant tenant = controller.tenants().tenant(id.tenant()).get();
@@ -518,7 +521,7 @@ public class ApplicationController {
* @param application a locked application to store
*/
public void store(LockedApplication application) {
- curator.writeApplication(application);
+ curator.writeApplication(application.get());
}
/**
@@ -572,7 +575,7 @@ public class ApplicationController {
*/
private LockedApplication deactivate(LockedApplication application, ZoneId zone) {
try {
- configServer.deactivate(new DeploymentId(application.id(), zone));
+ configServer.deactivate(new DeploymentId(application.get().id(), zone));
}
catch (NoInstanceException ignored) {
// ok; already gone
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
index 795b12f8af9..3207d4b8399 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
@@ -26,17 +26,29 @@ import com.yahoo.vespa.hosted.controller.rotation.RotationId;
import java.time.Instant;
import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import java.util.OptionalLong;
/**
- * A combination of an application instance and a lock for that application. Provides methods for updating application
- * fields.
+ * An application that has been locked for modification. Provides methods for modifying an application's fields.
*
* @author mpolden
* @author jvenstad
*/
-public class LockedApplication extends Application {
+public class LockedApplication {
+
+ private final Lock lock;
+ private final ApplicationId id;
+ private final DeploymentSpec deploymentSpec;
+ private final ValidationOverrides validationOverrides;
+ private final Map<ZoneId, Deployment> deployments;
+ private final DeploymentJobs deploymentJobs;
+ private final Change change;
+ private final Change outstandingChange;
+ private final Optional<IssueId> ownershipIssueId;
+ private final ApplicationMetrics metrics;
+ private final Optional<RotationId> rotation;
/**
* Used to create a locked application
@@ -44,38 +56,69 @@ public class LockedApplication extends Application {
* @param application The application to lock.
* @param lock The lock for the application.
*/
- LockedApplication(Application application, @SuppressWarnings("unused") Lock lock) {
- this(new Builder(application));
- }
-
- private LockedApplication(Builder builder) {
- super(builder.applicationId, builder.deploymentSpec, builder.validationOverrides,
- builder.deployments, builder.deploymentJobs, builder.deploying,
- builder.outstandingChange, builder.ownershipIssueId, builder.metrics, builder.rotation);
+ LockedApplication(Application application, Lock lock) {
+ this(Objects.requireNonNull(lock, "lock cannot be null"), application.id(),
+ application.deploymentSpec(), application.validationOverrides(),
+ application.deployments(),
+ application.deploymentJobs(), application.change(), application.outstandingChange(),
+ application.ownershipIssueId(), application.metrics(),
+ application.rotation().map(ApplicationRotation::id));
+ }
+
+ private LockedApplication(Lock lock, ApplicationId id,
+ DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides,
+ Map<ZoneId, Deployment> deployments, DeploymentJobs deploymentJobs, Change change,
+ Change outstandingChange, Optional<IssueId> ownershipIssueId, ApplicationMetrics metrics,
+ Optional<RotationId> rotation) {
+ this.lock = lock;
+ this.id = id;
+ this.deploymentSpec = deploymentSpec;
+ this.validationOverrides = validationOverrides;
+ this.deployments = deployments;
+ this.deploymentJobs = deploymentJobs;
+ this.change = change;
+ this.outstandingChange = outstandingChange;
+ this.ownershipIssueId = ownershipIssueId;
+ this.metrics = metrics;
+ this.rotation = rotation;
+ }
+
+ /** Returns a read-only copy of this */
+ public Application get() {
+ return new Application(id, deploymentSpec, validationOverrides, deployments, deploymentJobs, change,
+ outstandingChange, ownershipIssueId, metrics, rotation);
}
public LockedApplication withProjectId(OptionalLong projectId) {
- return new LockedApplication(new Builder(this).with(deploymentJobs().withProjectId(projectId)));
+ return new LockedApplication(lock, id, deploymentSpec, validationOverrides, deployments,
+ deploymentJobs.withProjectId(projectId), change, outstandingChange,
+ ownershipIssueId, metrics, rotation);
}
public LockedApplication withDeploymentIssueId(IssueId issueId) {
- return new LockedApplication(new Builder(this).with(deploymentJobs().with(issueId)));
+ return new LockedApplication(lock, id, deploymentSpec, validationOverrides, deployments,
+ deploymentJobs.with(issueId), change, outstandingChange,
+ ownershipIssueId, metrics, rotation);
}
- public LockedApplication withJobCompletion(long projectId, JobType jobType, JobStatus.JobRun completion, Optional<DeploymentJobs.JobError> jobError) {
- return new LockedApplication(new Builder(this).with(deploymentJobs().withCompletion(projectId, jobType, completion, jobError))
- );
+ public LockedApplication withJobCompletion(long projectId, JobType jobType, JobStatus.JobRun completion,
+ Optional<DeploymentJobs.JobError> jobError) {
+ return new LockedApplication(lock, id, deploymentSpec, validationOverrides, deployments,
+ deploymentJobs.withCompletion(projectId, jobType, completion, jobError),
+ change, outstandingChange, ownershipIssueId, metrics, rotation);
}
public LockedApplication withJobTriggering(JobType jobType, JobStatus.JobRun job) {
- return new LockedApplication(new Builder(this).with(deploymentJobs().withTriggering(jobType, job)));
+ return new LockedApplication(lock, id, deploymentSpec, validationOverrides, deployments,
+ deploymentJobs.withTriggering(jobType, job), change, outstandingChange,
+ ownershipIssueId, metrics, rotation);
}
public LockedApplication withNewDeployment(ZoneId zone, ApplicationVersion applicationVersion, Version version,
Instant instant) {
// Use info from previous deployment if available, otherwise create a new one.
- Deployment previousDeployment = deployments().getOrDefault(zone, new Deployment(zone, applicationVersion,
- version, instant));
+ Deployment previousDeployment = deployments.getOrDefault(zone, new Deployment(zone, applicationVersion,
+ version, instant));
Deployment newDeployment = new Deployment(zone, applicationVersion, version, instant,
previousDeployment.clusterUtils(),
previousDeployment.clusterInfo(),
@@ -85,146 +128,100 @@ public class LockedApplication extends Application {
}
public LockedApplication withClusterUtilization(ZoneId zone, Map<ClusterSpec.Id, ClusterUtilization> clusterUtilization) {
- Deployment deployment = deployments().get(zone);
+ Deployment deployment = deployments.get(zone);
if (deployment == null) return this; // No longer deployed in this zone.
return with(deployment.withClusterUtils(clusterUtilization));
}
public LockedApplication withClusterInfo(ZoneId zone, Map<ClusterSpec.Id, ClusterInfo> clusterInfo) {
- Deployment deployment = deployments().get(zone);
+ Deployment deployment = deployments.get(zone);
if (deployment == null) return this; // No longer deployed in this zone.
return with(deployment.withClusterInfo(clusterInfo));
}
public LockedApplication recordActivityAt(Instant instant, ZoneId zone) {
- Deployment deployment = deployments().get(zone);
+ Deployment deployment = deployments.get(zone);
if (deployment == null) return this;
return with(deployment.recordActivityAt(instant));
}
public LockedApplication with(ZoneId zone, DeploymentMetrics deploymentMetrics) {
- Deployment deployment = deployments().get(zone);
+ Deployment deployment = deployments.get(zone);
if (deployment == null) return this; // No longer deployed in this zone.
return with(deployment.withMetrics(deploymentMetrics));
}
public LockedApplication withoutDeploymentIn(ZoneId zone) {
- Map<ZoneId, Deployment> deployments = new LinkedHashMap<>(deployments());
+ Map<ZoneId, Deployment> deployments = new LinkedHashMap<>(this.deployments);
deployments.remove(zone);
- return new LockedApplication(new Builder(this).with(deployments));
+ return with(deployments);
}
public LockedApplication withoutDeploymentJob(DeploymentJobs.JobType jobType) {
- return new LockedApplication(new Builder(this).with(deploymentJobs().without(jobType)));
+ return new LockedApplication(lock, id, deploymentSpec, validationOverrides, deployments,
+ deploymentJobs.without(jobType), change, outstandingChange,
+ ownershipIssueId, metrics, rotation);
}
public LockedApplication with(DeploymentSpec deploymentSpec) {
- return new LockedApplication(new Builder(this).with(deploymentSpec));
+ return new LockedApplication(lock, id, deploymentSpec, validationOverrides, deployments,
+ deploymentJobs, change, outstandingChange,
+ ownershipIssueId, metrics, rotation);
}
public LockedApplication with(ValidationOverrides validationOverrides) {
- return new LockedApplication(new Builder(this).with(validationOverrides));
+ return new LockedApplication(lock, id, deploymentSpec, validationOverrides, deployments,
+ deploymentJobs, change, outstandingChange,
+ ownershipIssueId, metrics, rotation);
}
public LockedApplication withChange(Change change) {
- return new LockedApplication(new Builder(this).withChange(change));
+ return new LockedApplication(lock, id, deploymentSpec, validationOverrides, deployments,
+ deploymentJobs, change, outstandingChange,
+ ownershipIssueId, metrics, rotation);
}
public LockedApplication withOutstandingChange(Change outstandingChange) {
- return new LockedApplication(new Builder(this).withOutstandingChange(outstandingChange));
+ return new LockedApplication(lock, id, deploymentSpec, validationOverrides, deployments,
+ deploymentJobs, change, outstandingChange,
+ ownershipIssueId, metrics, rotation);
}
public LockedApplication withOwnershipIssueId(IssueId issueId) {
- return new LockedApplication(new Builder(this).withOwnershipIssueId(Optional.ofNullable(issueId)));
+ return new LockedApplication(lock, id, deploymentSpec, validationOverrides, deployments,
+ deploymentJobs, change, outstandingChange,
+ Optional.ofNullable(issueId), metrics, rotation);
}
public LockedApplication with(MetricsService.ApplicationMetrics metrics) {
- return new LockedApplication(new Builder(this).with(metrics));
+ return new LockedApplication(lock, id, deploymentSpec, validationOverrides, deployments,
+ deploymentJobs, change, outstandingChange,
+ ownershipIssueId, metrics, rotation);
}
public LockedApplication with(RotationId rotation) {
- return new LockedApplication(new Builder(this).with(rotation));
+ return new LockedApplication(lock, id, deploymentSpec, validationOverrides, deployments,
+ deploymentJobs, change, outstandingChange,
+ ownershipIssueId, metrics, Optional.of(rotation));
}
/** Don't expose non-leaf sub-objects. */
private LockedApplication with(Deployment deployment) {
- Map<ZoneId, Deployment> deployments = new LinkedHashMap<>(deployments());
+ Map<ZoneId, Deployment> deployments = new LinkedHashMap<>(this.deployments);
deployments.put(deployment.zone(), deployment);
- return new LockedApplication(new Builder(this).with(deployments));
- }
-
- private static class Builder {
-
- private final ApplicationId applicationId;
- private DeploymentSpec deploymentSpec;
- private ValidationOverrides validationOverrides;
- private Map<ZoneId, Deployment> deployments;
- private DeploymentJobs deploymentJobs;
- private Change deploying;
- private Change outstandingChange;
- private Optional<IssueId> ownershipIssueId;
- private ApplicationMetrics metrics;
- private Optional<RotationId> rotation;
-
- private Builder(Application application) {
- this.applicationId = application.id();
- this.deploymentSpec = application.deploymentSpec();
- this.validationOverrides = application.validationOverrides();
- this.deployments = application.deployments();
- this.deploymentJobs = application.deploymentJobs();
- this.deploying = application.change();
- this.outstandingChange = application.outstandingChange();
- this.ownershipIssueId = application.ownershipIssueId();
- this.metrics = application.metrics();
- this.rotation = application.rotation().map(ApplicationRotation::id);
- }
-
- private Builder with(DeploymentSpec deploymentSpec) {
- this.deploymentSpec = deploymentSpec;
- return this;
- }
-
- private Builder with(ValidationOverrides validationOverrides) {
- this.validationOverrides = validationOverrides;
- return this;
- }
-
- private Builder with(Map<ZoneId, Deployment> deployments) {
- this.deployments = deployments;
- return this;
- }
-
- private Builder with(DeploymentJobs deploymentJobs) {
- this.deploymentJobs = deploymentJobs;
- return this;
- }
-
- private Builder withChange(Change deploying) {
- this.deploying = deploying;
- return this;
- }
-
- private Builder withOutstandingChange(Change outstandingChange) {
- this.outstandingChange = outstandingChange;
- return this;
- }
-
- private Builder withOwnershipIssueId(Optional<IssueId> ownershipIssueId) {
- this.ownershipIssueId = ownershipIssueId;
- return this;
- }
-
- private Builder with(ApplicationMetrics metrics) {
- this.metrics = metrics;
- return this;
- }
-
- private Builder with(RotationId rotation) {
- this.rotation = Optional.of(rotation);
- return this;
- }
+ return with(deployments);
+ }
+
+ private LockedApplication with(Map<ZoneId, Deployment> deployments) {
+ return new LockedApplication(lock, id, deploymentSpec, validationOverrides, deployments,
+ deploymentJobs, change, outstandingChange,
+ ownershipIssueId, metrics, rotation);
+ }
+ @Override
+ public String toString() {
+ return "application '" + id + "'";
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackage.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackage.java
index 6df8e901653..40e2e4a92d1 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackage.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackage.java
@@ -34,9 +34,8 @@ public class ApplicationPackage {
* it must not be further changed by the caller.
*/
public ApplicationPackage(byte[] zippedContent) {
- Objects.requireNonNull(zippedContent, "The application package content cannot be null");
+ this.zippedContent = Objects.requireNonNull(zippedContent, "The application package content cannot be null");
this.contentHash = DigestUtils.shaHex(zippedContent);
- this.zippedContent = zippedContent;
this.deploymentSpec = extractFile("deployment.xml", zippedContent).map(DeploymentSpec::fromXml).orElse(DeploymentSpec.empty);
this.validationOverrides = extractFile("validation-overrides.xml", zippedContent).map(ValidationOverrides::fromXml).orElse(ValidationOverrides.empty);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/concurrent/Locks.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/concurrent/Locks.java
deleted file mode 100644
index 6168812203a..00000000000
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/concurrent/Locks.java
+++ /dev/null
@@ -1,55 +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.vespa.hosted.controller.concurrent;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * Holds a map of locks indexed on keys of a given type.
- * This is suitable in cases where exclusive access should be granted to any one of a set of keyed objects and
- * there is a finite collection of keyed objects.
- *
- * The returned locks are reentrant (i.e the owning thread may call lock multiple times) and auto-closable.
- *
- * Typical use is
- * <code>
- * try (Lock lock = locks.lock(id)) {
- * exclusive use of the object with key id
- * }
- * </code>
- *
- * @author bratseth
- */
-public class Locks<TYPE> {
-
- private final Map<TYPE, ReentrantLock> locks = new ConcurrentHashMap<>();
-
- private final long timeoutMs;
-
- public Locks(int timeout, TimeUnit timeoutUnit) {
- timeoutMs = timeoutUnit.toMillis(timeout);
- }
-
- /**
- * Locks key. This will block until the key is acquired.
- * Users of this <b>must</b> close any lock acquired.
- *
- * @param key the key to lock
- * @return the acquired lock
- * @throws TimeoutException if the lock could not be acquired within the timeout
- */
- public Lock lock(TYPE key) {
- try {
- ReentrantLock lock = locks.computeIfAbsent(key, k -> new ReentrantLock(true));
- boolean acquired = lock.tryLock(timeoutMs, TimeUnit.MILLISECONDS);
- if ( ! acquired)
- throw new TimeoutException("Timed out waiting for the lock to " + key);
- return new Lock(lock);
- } catch (InterruptedException e) {
- throw new RuntimeException("Interrupted while waiting for lock of " + key);
- }
- }
-
-}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentOrder.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentOrder.java
index 405c8d17263..1c535a5a331 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentOrder.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentOrder.java
@@ -3,7 +3,6 @@ package com.yahoo.vespa.hosted.controller.deployment;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.provision.SystemName;
-import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
@@ -30,8 +29,7 @@ public class DeploymentOrder {
private final Supplier<SystemName> system;
public DeploymentOrder(Supplier<SystemName> system) {
- Objects.requireNonNull(system, "system may not be null");
- this.system = system;
+ this.system = Objects.requireNonNull(system, "system may not be null");
}
/** Returns jobs for given deployment spec, in the order they are declared */
@@ -46,25 +44,25 @@ public class DeploymentOrder {
public List<JobStatus> sortBy(DeploymentSpec deploymentSpec, Collection<JobStatus> jobStatus) {
List<DeploymentJobs.JobType> sortedJobs = jobsFrom(deploymentSpec);
return jobStatus.stream()
- .sorted(comparingInt(job -> sortedJobs.indexOf(job.type())))
- .collect(collectingAndThen(toList(), Collections::unmodifiableList));
+ .sorted(comparingInt(job -> sortedJobs.indexOf(job.type())))
+ .collect(collectingAndThen(toList(), Collections::unmodifiableList));
}
/** Returns deployments sorted according to declared zones */
public List<Deployment> sortBy(List<DeploymentSpec.DeclaredZone> zones, Collection<Deployment> deployments) {
List<ZoneId> productionZones = zones.stream()
- .filter(z -> z.region().isPresent())
- .map(z -> ZoneId.from(z.environment(), z.region().get()))
- .collect(toList());
+ .filter(z -> z.region().isPresent())
+ .map(z -> ZoneId.from(z.environment(), z.region().get()))
+ .collect(toList());
return deployments.stream()
- .sorted(comparingInt(deployment -> productionZones.indexOf(deployment.zone())))
- .collect(collectingAndThen(toList(), Collections::unmodifiableList));
+ .sorted(comparingInt(deployment -> productionZones.indexOf(deployment.zone())))
+ .collect(collectingAndThen(toList(), Collections::unmodifiableList));
}
/** Resolve job from deployment step */
public JobType toJob(DeploymentSpec.DeclaredZone zone) {
return JobType.from(system.get(), zone.environment(), zone.region().orElse(null))
- .orElseThrow(() -> new IllegalArgumentException("Invalid zone " + zone));
+ .orElseThrow(() -> new IllegalArgumentException("Invalid zone " + zone));
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
index e902206ad8b..63a6ac234ff 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
@@ -20,7 +20,6 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobReport;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobType;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
import com.yahoo.vespa.hosted.controller.application.JobStatus.JobRun;
-import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import java.time.Clock;
import java.time.Duration;
@@ -78,14 +77,11 @@ public class DeploymentTrigger {
private final DeploymentOrder order;
private final BuildService buildService;
- public DeploymentTrigger(Controller controller, CuratorDb curator, BuildService buildService, Clock clock) {
- Objects.requireNonNull(controller, "controller cannot be null");
- Objects.requireNonNull(curator, "curator cannot be null");
- Objects.requireNonNull(clock, "clock cannot be null");
- this.controller = controller;
- this.clock = clock;
+ public DeploymentTrigger(Controller controller, BuildService buildService, Clock clock) {
+ this.controller = Objects.requireNonNull(controller, "controller cannot be null");
+ this.buildService = Objects.requireNonNull(buildService, "buildService cannot be null");
+ this.clock = Objects.requireNonNull(clock, "clock cannot be null");
this.order = new DeploymentOrder(controller::system);
- this.buildService = buildService;
}
public DeploymentOrder deploymentOrder() {
@@ -116,15 +112,15 @@ public class DeploymentTrigger {
triggering = JobRun.triggering(controller.systemVersion(), applicationVersion, Optional
.empty(), Optional.empty(), "Application commit", clock.instant());
if (report.success()) {
- if (acceptNewApplicationVersion(application))
- application = application.withChange(application.change().with(applicationVersion))
+ if (acceptNewApplicationVersion(application.get()))
+ application = application.withChange(application.get().change().with(applicationVersion))
.withOutstandingChange(Change.empty());
else
application = application.withOutstandingChange(Change.of(applicationVersion));
}
}
else {
- triggering = application.deploymentJobs().statusOf(report.jobType()).flatMap(JobStatus::lastTriggered)
+ triggering = application.get().deploymentJobs().statusOf(report.jobType()).flatMap(JobStatus::lastTriggered)
.orElseThrow(() -> new IllegalStateException("Notified of completion of " + report.jobType().jobName() + " for " +
report.applicationId() + ", but that has neither been triggered nor deployed"));
}
@@ -132,7 +128,7 @@ public class DeploymentTrigger {
report.jobType(),
triggering.completion(report.buildNumber(), clock.instant()),
report.jobError());
- application = application.withChange(remainingChange(application));
+ application = application.withChange(remainingChange(application.get()));
applications().store(application);
});
}
@@ -216,9 +212,9 @@ public class DeploymentTrigger {
*/
public void triggerChange(ApplicationId applicationId, Change change) {
applications().lockOrThrow(applicationId, application -> {
- if (application.changeAt(controller.clock().instant()).isPresent() && ! application.deploymentJobs().hasFailures())
+ if (application.get().changeAt(controller.clock().instant()).isPresent() && ! application.get().deploymentJobs().hasFailures())
throw new IllegalArgumentException("Could not start " + change + " on " + application + ": " +
- application.change() + " is already in progress");
+ application.get().change() + " is already in progress");
application = application.withChange(change);
if (change.application().isPresent())
application = application.withOutstandingChange(Change.empty());
@@ -230,7 +226,7 @@ public class DeploymentTrigger {
/** Cancels a platform upgrade of the given application, and an application upgrade as well if {@code keepApplicationChange}. */
public void cancelChange(ApplicationId applicationId, boolean keepApplicationChange) {
applications().lockOrThrow(applicationId, application -> {
- applications().store(application.withChange(application.change().application()
+ applications().store(application.withChange(application.get().change().application()
.filter(__ -> keepApplicationChange)
.map(Change::of)
.orElse(Change.empty())));
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
index bd8b8fc8747..22cbe942932 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
@@ -7,7 +7,6 @@ import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.application.ApplicationList;
-import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion.Confidence;
@@ -18,6 +17,7 @@ import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -36,7 +36,7 @@ public class Upgrader extends Maintainer {
public Upgrader(Controller controller, Duration interval, JobControl jobControl, CuratorDb curator) {
super(controller, interval, jobControl);
- this.curator = curator;
+ this.curator = Objects.requireNonNull(curator, "curator cannot be null");
}
/**
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
index d58c3ed7dae..6b35e09e049 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
@@ -678,7 +678,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
ApplicationId id = ApplicationId.from(tenantName, applicationName, "default");
controller.applications().lockOrThrow(id, application -> {
- controller.applications().deploymentTrigger().triggerChange(application.id(), Change.of(version));
+ controller.applications().deploymentTrigger().triggerChange(application.get().id(), Change.of(version));
});
return new MessageResponse("Triggered deployment of application '" + id + "' on version " + version);
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
index 0de153fc3f9..c24c8693688 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller;
import com.yahoo.component.Version;
import com.yahoo.config.application.api.ValidationId;
+import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.Environment;
@@ -179,7 +180,9 @@ public class ControllerTest {
fail("Expected exception due to illegal production deployment removal");
}
catch (IllegalArgumentException e) {
- assertEquals("deployment-removal: application 'tenant1.app1' is deployed in corp-us-east-1, but does not include this zone in deployment.xml", e.getMessage());
+ assertEquals("deployment-removal: application 'tenant1.app1' is deployed in corp-us-east-1, but does not include this zone in deployment.xml. " +
+ ValidationOverrides.toAllowMessage(ValidationId.deploymentRemoval),
+ e.getMessage());
}
assertNotNull("Zone was not removed",
applications.require(app1.id()).deployments().get(productionCorpUsEast1.zone(main).get()));
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
index 26cb68647e4..5c5827fa167 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
@@ -156,26 +156,26 @@ public class ApplicationSerializerTest {
assertEquals(original.deployments().get(zone2).metrics().writeLatencyMillis(), serialized.deployments().get(zone2).metrics().writeLatencyMillis(), Double.MIN_VALUE);
{ // test more deployment serialization cases
- Application original2 = writable(original).withChange(Change.of(ApplicationVersion.from(new SourceRevision("repo1", "branch1", "commit1"), 42)));
+ Application original2 = writable(original).withChange(Change.of(ApplicationVersion.from(new SourceRevision("repo1", "branch1", "commit1"), 42))).get();
Application serialized2 = applicationSerializer.fromSlime(applicationSerializer.toSlime(original2));
assertEquals(original2.change(), serialized2.change());
assertEquals(serialized2.change().application().get().source(),
original2.change().application().get().source());
- Application original3 = writable(original).withChange(Change.of(ApplicationVersion.from(new SourceRevision("a", "b", "c"), 42)));
+ Application original3 = writable(original).withChange(Change.of(ApplicationVersion.from(new SourceRevision("a", "b", "c"), 42))).get();
Application serialized3 = applicationSerializer.fromSlime(applicationSerializer.toSlime(original3));
assertEquals(original3.change(), serialized3.change());
assertEquals(serialized3.change().application().get().source(),
original3.change().application().get().source());
- Application original4 = writable(original).withChange(Change.empty());
+ Application original4 = writable(original).withChange(Change.empty()).get();
Application serialized4 = applicationSerializer.fromSlime(applicationSerializer.toSlime(original4));
assertEquals(original4.change(), serialized4.change());
- Application original5 = writable(original).withChange(Change.of(ApplicationVersion.from(new SourceRevision("a", "b", "c"), 42)));
+ Application original5 = writable(original).withChange(Change.of(ApplicationVersion.from(new SourceRevision("a", "b", "c"), 42))).get();
Application serialized5 = applicationSerializer.fromSlime(applicationSerializer.toSlime(original5));
assertEquals(original5.change(), serialized5.change());
- Application original6 = writable(original).withOutstandingChange(Change.of(ApplicationVersion.from(new SourceRevision("a", "b", "c"), 42)));
+ Application original6 = writable(original).withOutstandingChange(Change.of(ApplicationVersion.from(new SourceRevision("a", "b", "c"), 42))).get();
Application serialized6 = applicationSerializer.fromSlime(applicationSerializer.toSlime(original6));
assertEquals(original6.outstandingChange(), serialized6.outstandingChange());
}
diff --git a/docprocs/src/test/java/com/yahoo/docprocs/indexing/DocumentScriptTestCase.java b/docprocs/src/test/java/com/yahoo/docprocs/indexing/DocumentScriptTestCase.java
index 5b1a4412b41..419b60432c4 100644
--- a/docprocs/src/test/java/com/yahoo/docprocs/indexing/DocumentScriptTestCase.java
+++ b/docprocs/src/test/java/com/yahoo/docprocs/indexing/DocumentScriptTestCase.java
@@ -1,11 +1,13 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.docprocs.indexing;
+import com.yahoo.document.ArrayDataType;
import com.yahoo.document.DataType;
import com.yahoo.document.Document;
import com.yahoo.document.DocumentType;
import com.yahoo.document.DocumentUpdate;
import com.yahoo.document.Field;
+import com.yahoo.document.MapDataType;
import com.yahoo.document.StructDataType;
import com.yahoo.document.annotation.SpanTree;
import com.yahoo.document.annotation.SpanTrees;
@@ -16,6 +18,7 @@ import com.yahoo.document.datatypes.StringFieldValue;
import com.yahoo.document.datatypes.Struct;
import com.yahoo.document.datatypes.WeightedSet;
import com.yahoo.document.fieldpathupdate.AssignFieldPathUpdate;
+import com.yahoo.document.fieldpathupdate.FieldPathUpdate;
import com.yahoo.document.update.FieldUpdate;
import com.yahoo.document.update.MapValueUpdate;
import com.yahoo.document.update.ValueUpdate;
@@ -30,6 +33,7 @@ import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -157,6 +161,82 @@ public class DocumentScriptTestCase {
assertSpanTrees(str, "mySpanTree");
}
+ private class FieldPathFixture {
+ final DocumentType type;
+ final StructDataType structType;
+ final DataType structMap;
+ final DataType structArray;
+
+ FieldPathFixture() {
+ type = newDocumentType();
+ structType = new StructDataType("mystruct");
+ structType.addField(new Field("title", DataType.STRING));
+ structType.addField(new Field("rating", DataType.INT));
+ structArray = new ArrayDataType(structType);
+ type.addField(new Field("structarray", structArray));
+ structMap = new MapDataType(DataType.STRING, structType);
+ type.addField(new Field("structmap", structMap));
+ type.addField(new Field("structfield", structType));
+ }
+
+ DocumentUpdate executeWithUpdate(String fieldName, FieldPathUpdate updateIn) {
+ DocumentUpdate update = new DocumentUpdate(type, "doc:scheme:");
+ update.addFieldPathUpdate(updateIn);
+ return newScript(type, fieldName).execute(ADAPTER_FACTORY, update);
+ }
+
+ FieldPathUpdate executeWithUpdateAndExpectFieldPath(String fieldName, FieldPathUpdate updateIn) {
+ DocumentUpdate update = executeWithUpdate(fieldName, updateIn);
+ assertEquals(1, update.getFieldPathUpdates().size());
+ return update.getFieldPathUpdates().get(0);
+ }
+ }
+
+ @Test
+ public void array_field_path_updates_survive_indexing_scripts() {
+ FieldPathFixture f = new FieldPathFixture();
+
+ Struct newElemValue = new Struct(f.structType);
+ newElemValue.setFieldValue("title", "iron moose 2, the moosening");
+
+ FieldPathUpdate updated = f.executeWithUpdateAndExpectFieldPath("structarray", new AssignFieldPathUpdate(f.type, "structarray[10]", newElemValue));
+
+ assertTrue(updated instanceof AssignFieldPathUpdate);
+ AssignFieldPathUpdate assignUpdate = (AssignFieldPathUpdate)updated;
+ assertEquals("structarray[10]", assignUpdate.getOriginalFieldPath());
+ assertEquals(newElemValue, assignUpdate.getFieldValue());
+ }
+
+ @Test
+ public void map_field_path_updates_survive_indexing_scripts() {
+ FieldPathFixture f = new FieldPathFixture();
+
+ Struct newElemValue = new Struct(f.structType);
+ newElemValue.setFieldValue("title", "iron moose 3, moose in new york");
+
+ FieldPathUpdate updated = f.executeWithUpdateAndExpectFieldPath("structmap", new AssignFieldPathUpdate(f.type, "structmap{foo}", newElemValue));
+
+ assertTrue(updated instanceof AssignFieldPathUpdate);
+ AssignFieldPathUpdate assignUpdate = (AssignFieldPathUpdate)updated;
+ assertEquals("structmap{foo}", assignUpdate.getOriginalFieldPath());
+ assertEquals(newElemValue, assignUpdate.getFieldValue());
+ }
+
+ @Test
+ public void nested_struct_fieldpath_update_is_not_converted_to_regular_field_value_update() {
+ FieldPathFixture f = new FieldPathFixture();
+
+ StringFieldValue newTitleValue = new StringFieldValue("iron moose 4, moose with a vengeance");
+ DocumentUpdate update = f.executeWithUpdate("structfield", new AssignFieldPathUpdate(f.type, "structfield.title", newTitleValue));
+
+ assertEquals(1, update.getFieldPathUpdates().size());
+ assertEquals(0, update.getFieldUpdates().size());
+ assertTrue(update.getFieldPathUpdates().get(0) instanceof AssignFieldPathUpdate);
+ AssignFieldPathUpdate assignUpdate = (AssignFieldPathUpdate)update.getFieldPathUpdates().get(0);
+ assertEquals("structfield.title", assignUpdate.getOriginalFieldPath());
+ assertEquals(newTitleValue, assignUpdate.getFieldValue());
+ }
+
private static FieldValue processDocument(FieldValue fieldValue) {
DocumentType docType = new DocumentType("myDocumentType");
docType.addField("myField", fieldValue.getDataType());
@@ -184,11 +264,15 @@ public class DocumentScriptTestCase {
return update.getFieldUpdate("myField").getValueUpdate(0);
}
+ private static DocumentScript newScript(DocumentType docType, String fieldName) {
+ return new DocumentScript(docType.getName(), Collections.singletonList(fieldName),
+ new StatementExpression(new InputExpression(fieldName),
+ new IndexExpression(fieldName)));
+ }
+
private static DocumentScript newScript(DocumentType docType) {
String fieldName = docType.getFields().iterator().next().getName();
- return new DocumentScript(docType.getName(), Arrays.asList(fieldName),
- new StatementExpression(new InputExpression(fieldName),
- new IndexExpression(fieldName)));
+ return newScript(docType, fieldName);
}
private static StringFieldValue newString(String... spanTrees) {
@@ -210,6 +294,7 @@ public class DocumentScriptTestCase {
DocumentType type = new DocumentType("documentType");
type.addField("documentField", DataType.STRING);
type.addField("extraField", DataType.STRING);
+
return type;
}
diff --git a/document/src/main/java/com/yahoo/document/datatypes/Array.java b/document/src/main/java/com/yahoo/document/datatypes/Array.java
index e37a32f28f4..01326bcea62 100644
--- a/document/src/main/java/com/yahoo/document/datatypes/Array.java
+++ b/document/src/main/java/com/yahoo/document/datatypes/Array.java
@@ -290,7 +290,8 @@ public final class Array<T extends FieldValue> extends CollectionFieldValue<T> i
if (pos < fieldPath.size()) {
switch (fieldPath.get(pos).getType()) {
case ARRAY_INDEX:
- return iterateSubset(fieldPath.get(pos).getLookupIndex(), fieldPath.get(pos).getLookupIndex(), fieldPath, null, pos + 1, handler);
+ final int elemIndex = fieldPath.get(pos).getLookupIndex();
+ return iterateSubset(elemIndex, elemIndex, fieldPath, null, pos + 1, handler);
case VARIABLE: {
FieldPathIteratorHandler.IndexValue val = handler.getVariables().get(fieldPath.get(pos).getVariableName());
if (val != null) {
diff --git a/fat-model-dependencies/pom.xml b/fat-model-dependencies/pom.xml
index 1415ca6e5aa..0011d108b98 100644
--- a/fat-model-dependencies/pom.xml
+++ b/fat-model-dependencies/pom.xml
@@ -16,13 +16,6 @@
<groupId>com.yahoo.vespa</groupId>
<artifactId>config-model</artifactId>
<version>${project.version}</version>
- <exclusions>
- <exclusion>
- <!-- Large, and installed separately as part of Vespa -->
- <groupId>org.tensorflow</groupId>
- <artifactId>libtensorflow_jni</artifactId>
- </exclusion>
- </exclusions>
</dependency>
<dependency>
<groupId>com.yahoo.vespa</groupId>
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldPathUpdateHelper.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldPathUpdateHelper.java
index 171c6a8eb9a..5c170fe147e 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldPathUpdateHelper.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldPathUpdateHelper.java
@@ -20,19 +20,10 @@ public abstract class FieldPathUpdateHelper {
if (!(update instanceof AssignFieldPathUpdate)) {
return false;
}
- for (FieldPathEntry entry : update.getFieldPath()) {
- switch (entry.getType()) {
- case STRUCT_FIELD:
- case MAP_ALL_KEYS:
- case MAP_ALL_VALUES:
- continue;
- case ARRAY_INDEX:
- case MAP_KEY:
- case VARIABLE:
- return false;
- }
- }
- return true;
+ // Only consider field path updates that touch a top-level field as 'complete',
+ // as these may be converted to regular field value updates.
+ return ((update.getFieldPath().size() == 1)
+ && update.getFieldPath().get(0).getType() == FieldPathEntry.Type.STRUCT_FIELD);
}
public static void applyUpdate(FieldPathUpdate update, Document doc) {
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/IdentityFieldPathUpdateAdapter.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/IdentityFieldPathUpdateAdapter.java
new file mode 100644
index 00000000000..42c9bd8c10c
--- /dev/null
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/IdentityFieldPathUpdateAdapter.java
@@ -0,0 +1,68 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.indexinglanguage;
+
+import com.yahoo.document.DataType;
+import com.yahoo.document.Document;
+import com.yahoo.document.DocumentUpdate;
+import com.yahoo.document.FieldPath;
+import com.yahoo.document.datatypes.FieldValue;
+import com.yahoo.document.fieldpathupdate.FieldPathUpdate;
+import com.yahoo.vespa.indexinglanguage.expressions.Expression;
+import com.yahoo.vespa.indexinglanguage.expressions.FieldValueAdapter;
+
+/**
+ * No-op update adapter which simply passes through the input update unchanged.
+ * I.e. getOutput() will return a DocumentUpdate containing only the FieldPathUpdate
+ * the IdentityFieldPathUpdateAdapter was created with. All other applicable calls are
+ * forwarded to the provided DocumentAdapter instance.
+ *
+ * This removes the need for a potentially lossy round-trip of update -&gt; synthetic document -&gt; update.
+ */
+public class IdentityFieldPathUpdateAdapter implements UpdateAdapter {
+
+ private final FieldPathUpdate update;
+ private final DocumentAdapter fwdAdapter;
+
+ public IdentityFieldPathUpdateAdapter(FieldPathUpdate update, DocumentAdapter fwdAdapter) {
+ this.update = update;
+ this.fwdAdapter = fwdAdapter;
+ }
+
+ @Override
+ public DocumentUpdate getOutput() {
+ Document doc = fwdAdapter.getFullOutput();
+ DocumentUpdate upd = new DocumentUpdate(doc.getDataType(), doc.getId());
+ upd.addFieldPathUpdate(update);
+ return upd;
+ }
+
+ @Override
+ public Expression getExpression(Expression expression) {
+ return expression;
+ }
+
+ @Override
+ public FieldValue getInputValue(String fieldName) {
+ return fwdAdapter.getInputValue(fieldName);
+ }
+
+ @Override
+ public FieldValue getInputValue(FieldPath fieldPath) {
+ return fwdAdapter.getInputValue(fieldPath);
+ }
+
+ @Override
+ public FieldValueAdapter setOutputValue(Expression exp, String fieldName, FieldValue fieldValue) {
+ return fwdAdapter.setOutputValue(exp, fieldName, fieldValue);
+ }
+
+ @Override
+ public DataType getInputType(Expression exp, String fieldName) {
+ return fwdAdapter.getInputType(exp, fieldName);
+ }
+
+ @Override
+ public void tryOutputType(Expression exp, String fieldName, DataType valueType) {
+ fwdAdapter.tryOutputType(exp, fieldName, valueType);
+ }
+}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/SimpleAdapterFactory.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/SimpleAdapterFactory.java
index 2ad09dfbdc4..509bdcaa32d 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/SimpleAdapterFactory.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/SimpleAdapterFactory.java
@@ -49,10 +49,12 @@ public class SimpleAdapterFactory implements AdapterFactory {
Document complete = new Document(docType, upd.getId());
for (FieldPathUpdate fieldUpd : upd) {
if (FieldPathUpdateHelper.isComplete(fieldUpd)) {
+ // A 'complete' field path update is basically a regular top-level field update
+ // in wolf's clothing. Convert it to a regular field update to be friendlier
+ // towards the search core backend.
FieldPathUpdateHelper.applyUpdate(fieldUpd, complete);
} else {
- Document partial = FieldPathUpdateHelper.newPartialDocument(docId, fieldUpd);
- ret.add(new FieldPathUpdateAdapter(newDocumentAdapter(partial, true), fieldUpd));
+ ret.add(new IdentityFieldPathUpdateAdapter(fieldUpd, newDocumentAdapter(complete, true)));
}
}
for (FieldUpdate fieldUpd : upd.getFieldUpdates()) {
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/StorageMaintainer.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java
index 7f2d1f1eff7..a7bf22591d4 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java
@@ -276,8 +276,8 @@ public class StorageMaintainer {
*/
public void handleCoreDumpsForContainer(ContainerName containerName, NodeSpec node, boolean force) {
// Sample number of coredumps on the host
- try {
- numberOfCoredumpsOnHost.sample(Files.list(environment.pathInNodeAdminToDoneCoredumps()).count());
+ try (Stream<Path> files = Files.list(environment.pathInNodeAdminToDoneCoredumps())) {
+ numberOfCoredumpsOnHost.sample(files.count());
} catch (IOException e) {
// Ignore for now - this is either test or a misconfiguration
}
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 f7e9c3ca1d8..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,19 +147,6 @@ public class AthenzCredentialsMaintainer {
}
}
- 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;
}
@@ -174,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())
@@ -210,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);
}
}
@@ -237,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/node-maintainer/src/main/java/com/yahoo/vespa/hosted/node/maintainer/CoredumpHandler.java b/node-maintainer/src/main/java/com/yahoo/vespa/hosted/node/maintainer/CoredumpHandler.java
index 99dfdb48334..63c74c17dd5 100644
--- a/node-maintainer/src/main/java/com/yahoo/vespa/hosted/node/maintainer/CoredumpHandler.java
+++ b/node-maintainer/src/main/java/com/yahoo/vespa/hosted/node/maintainer/CoredumpHandler.java
@@ -72,7 +72,7 @@ class CoredumpHandler {
FileHelper.deleteDirectories(doneCoredumpsPath, Duration.ofDays(10), Optional.empty());
}
- private void handleNewCoredumps() throws IOException {
+ private void handleNewCoredumps() {
Path processingCoredumps = enqueueCoredumps();
processAndReportCoredumps(processingCoredumps);
}
@@ -82,12 +82,12 @@ class CoredumpHandler {
* Moves a coredump to a new directory under the processing/ directory. Limit to only processing
* one coredump at the time, starting with the oldest.
*/
- Path enqueueCoredumps() throws IOException {
+ Path enqueueCoredumps() {
Path processingCoredumpsPath = coredumpsPath.resolve(PROCESSING_DIRECTORY_NAME);
processingCoredumpsPath.toFile().mkdirs();
- if (Files.list(processingCoredumpsPath).count() > 0) return processingCoredumpsPath;
+ if (!FileHelper.listContentsOfDirectory(processingCoredumpsPath).isEmpty()) return processingCoredumpsPath;
- Files.list(coredumpsPath)
+ FileHelper.listContentsOfDirectory(coredumpsPath).stream()
.filter(path -> path.toFile().isFile() && ! path.getFileName().toString().startsWith("."))
.min((Comparator.comparingLong(o -> o.toFile().lastModified())))
.ifPresent(coredumpPath -> {
@@ -101,10 +101,10 @@ class CoredumpHandler {
return processingCoredumpsPath;
}
- void processAndReportCoredumps(Path processingCoredumpsPath) throws IOException {
+ void processAndReportCoredumps(Path processingCoredumpsPath) {
doneCoredumpsPath.toFile().mkdirs();
- Files.list(processingCoredumpsPath)
+ FileHelper.listContentsOfDirectory(processingCoredumpsPath).stream()
.filter(path -> path.toFile().isDirectory())
.forEach(coredumpDirectory -> {
try {
@@ -130,7 +130,7 @@ class CoredumpHandler {
String collectMetadata(Path coredumpDirectory, Map<String, Object> nodeAttributes) throws IOException {
Path metadataPath = coredumpDirectory.resolve(METADATA_FILE_NAME);
if (!Files.exists(metadataPath)) {
- Path coredumpPath = Files.list(coredumpDirectory).findFirst()
+ Path coredumpPath = FileHelper.listContentsOfDirectory(coredumpDirectory).stream().findFirst()
.orElseThrow(() -> new RuntimeException("No coredump file found in processing directory " + coredumpDirectory));
Map<String, Object> metadata = coreCollector.collect(coredumpPath, installStatePath);
metadata.putAll(nodeAttributes);
diff --git a/node-maintainer/src/main/java/com/yahoo/vespa/hosted/node/maintainer/FileHelper.java b/node-maintainer/src/main/java/com/yahoo/vespa/hosted/node/maintainer/FileHelper.java
index ae872042853..7b93e7ad98d 100644
--- a/node-maintainer/src/main/java/com/yahoo/vespa/hosted/node/maintainer/FileHelper.java
+++ b/node-maintainer/src/main/java/com/yahoo/vespa/hosted/node/maintainer/FileHelper.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.node.maintainer;
import java.io.IOException;
+import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
@@ -63,7 +64,7 @@ public class FileHelper {
throw new IllegalArgumentException("Number of files to keep must be a positive number");
}
- List<Path> pathsInDeleteDir = Files.list(basePath)
+ List<Path> pathsInDeleteDir = listContentsOfDirectory(basePath).stream()
.filter(Files::isRegularFile)
.sorted(Comparator.comparing(FileHelper::getLastModifiedTime))
.skip(nMostRecentToKeep)
@@ -153,13 +154,16 @@ public class FileHelper {
return pattern == null || pattern.matcher(path.getFileName().toString()).find();
}
- static List<Path> listContentsOfDirectory(Path basePath) {
+ /**
+ * @return list all files in a directory, returns empty list if directory does not exist
+ */
+ public static List<Path> listContentsOfDirectory(Path basePath) {
try (Stream<Path> directoryStream = Files.list(basePath)) {
return directoryStream.collect(Collectors.toList());
} catch (NoSuchFileException ignored) {
return Collections.emptyList();
} catch (IOException e) {
- throw new RuntimeException("Failed to list contents of directory " + basePath.toAbsolutePath(), e);
+ throw new UncheckedIOException("Failed to list contents of directory " + basePath.toAbsolutePath(), e);
}
}
@@ -167,7 +171,7 @@ public class FileHelper {
try {
return Files.getLastModifiedTime(path, LinkOption.NOFOLLOW_LINKS);
} catch (IOException e) {
- throw new RuntimeException("Failed to get last modified time of " + path.toAbsolutePath(), e);
+ throw new UncheckedIOException("Failed to get last modified time of " + path.toAbsolutePath(), e);
}
}
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java
index 93704d244b5..d31b4438a38 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java
@@ -9,7 +9,6 @@ import com.yahoo.transaction.Mutex;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
-import java.time.Clock;
import java.util.List;
/**
@@ -67,6 +66,7 @@ public class GroupPreparer {
allocation.offer(prioritizer.prioritize());
if (! allocation.fullfilled())
throw new OutOfCapacityException("Could not satisfy " + requestedNodes + " for " + cluster +
+ " in " + application.toShortString() +
outOfCapacityDetails(allocation));
// Extend reservation for already reserved nodes
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java
index e1b1d74c6d0..2cabee98c0d 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java
@@ -160,13 +160,13 @@ public class DockerProvisioningTest {
assertEquals(setOf("host1", "host2"), hostsOf(tester.getNodes(application1, Node.State.active)));
try {
- ApplicationId application2 = tester.makeApplicationId();
+ ApplicationId application2 = ApplicationId.from("tenant1", "app1", "default");
prepareAndActivate(application2, 3, false, tester);
fail("Expected allocation failure");
}
catch (Exception e) {
assertEquals("No room for 3 nodes as 2 of 4 hosts are exclusive",
- "Could not satisfy request for 3 nodes of flavor 'dockerSmall' for container cluster 'myContainer' group 0 6.39: Not enough nodes available due to host exclusivity constraints.",
+ "Could not satisfy request for 3 nodes of flavor 'dockerSmall' for container cluster 'myContainer' group 0 6.39 in tenant1.app1: Not enough nodes available due to host exclusivity constraints.",
e.getMessage());
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/NodeIdentifierTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/NodeIdentifierTest.java
index c0cead74f5f..11c7832091b 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/NodeIdentifierTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/filter/NodeIdentifierTest.java
@@ -29,6 +29,7 @@ import java.security.cert.X509Certificate;
import java.time.Instant;
import java.util.Optional;
+import static com.yahoo.vespa.athenz.identityprovider.api.IdentityType.*;
import static com.yahoo.vespa.athenz.tls.KeyAlgorithm.RSA;
import static com.yahoo.vespa.athenz.tls.SignatureAlgorithm.SHA256_WITH_RSA;
import static java.util.Collections.emptySet;
@@ -161,7 +162,7 @@ public class NodeIdentifierTest {
Pkcs10Csr csr = Pkcs10CsrBuilder
.fromKeypair(new X500Principal("CN=" + TENANT_NODE_IDENTITY), KEYPAIR, SHA256_WITH_RSA)
.build();
- VespaUniqueInstanceId vespaUniqueInstanceId = new VespaUniqueInstanceId(clusterIndex, clusterId, INSTANCE_ID, application, tenant, region, environment);
+ VespaUniqueInstanceId vespaUniqueInstanceId = new VespaUniqueInstanceId(clusterIndex, clusterId, INSTANCE_ID, application, tenant, region, environment, NODE);
X509Certificate certificate = X509CertificateBuilder
.fromCsr(csr, ATHENZ_YAHOO_CA_CERT.getSubjectX500Principal(), Instant.EPOCH, Instant.EPOCH.plusSeconds(60), KEYPAIR.getPrivate(), SHA256_WITH_RSA, 1)
.addSubjectAlternativeName(vespaUniqueInstanceId.asDottedString() + ".instanceid.athenz.provider-name.vespa.yahoo.cloud")
diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/ServiceMonitorInstanceLookupService.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/ServiceMonitorInstanceLookupService.java
index a09ec29dada..d1d5f3e8c95 100644
--- a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/ServiceMonitorInstanceLookupService.java
+++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/ServiceMonitorInstanceLookupService.java
@@ -46,7 +46,7 @@ public class ServiceMonitorInstanceLookupService implements InstanceLookupServic
return Optional.empty();
}
if (applicationInstancesUsingHost.size() > 1) {
- throw new AssertionError(
+ throw new IllegalStateException(
"Major assumption broken: Multiple application instances contain host " + hostName.s()
+ ": " + applicationInstancesUsingHost);
}
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/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 1504119d9cc..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,8 +10,13 @@ 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.fromDottedString;
+
/**
* Utility class for mapping objects model types and their Jackson binding versions.
*
@@ -33,7 +38,7 @@ public class EntityBindingsMapper {
public static VespaUniqueInstanceId toVespaUniqueInstanceId(VespaUniqueInstanceIdEntity entity) {
return new VespaUniqueInstanceId(
- entity.clusterIndex, entity.clusterId, entity.instance, entity.application, entity.tenant, entity.region, entity.environment);
+ entity.clusterIndex, entity.clusterId, entity.instance, entity.application, entity.tenant, entity.region, entity.environment, entity.type != null ? IdentityType.fromId(entity.type) : null); // TODO Remove support for legacy representation without type
}
public static IdentityDocument toIdentityDocument(IdentityDocumentEntity entity) {
@@ -50,17 +55,22 @@ public class EntityBindingsMapper {
toIdentityDocument(entity.identityDocument),
entity.signature,
entity.signingKeyVersion,
- VespaUniqueInstanceId.fromDottedString(entity.providerUniqueId),
+ fromDottedString(entity.providerUniqueId),
entity.dnsSuffix,
(AthenzService) AthenzIdentities.from(entity.providerService),
entity.ztsEndpoint,
- entity.documentVersion);
+ entity.documentVersion,
+ entity.configServerHostname,
+ entity.instanceHostname,
+ entity.createdAt,
+ entity.ipAddresses,
+ entity.identityType != null ? IdentityType.fromId(entity.identityType) : null); // TODO Remove support for legacy representation without type
}
public static VespaUniqueInstanceIdEntity toVespaUniqueInstanceIdEntity(VespaUniqueInstanceId model) {
return new VespaUniqueInstanceIdEntity(
model.tenant(), model.application(), model.environment(), model.region(),
- model.instance(), model.clusterId(), model.clusterIndex());
+ model.instance(), model.clusterId(), model.clusterIndex(), model.type() != null ? model.type().id() : null); // TODO Remove support for legacy representation without type
}
public static IdentityDocumentEntity toIdentityDocumentEntity(IdentityDocument model) {
@@ -84,10 +94,33 @@ public class EntityBindingsMapper {
model.dnsSuffix(),
model.providerService().getFullName(),
model.ztsEndpoint(),
- model.documentVersion());
+ model.documentVersion(),
+ model.configServerHostname(),
+ model.instanceHostname(),
+ model.createdAt(),
+ model.ipAddresses(),
+ model.identityType() != null ? model.identityType().id() : null); // TODO Remove support for legacy representation without type
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
+ 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);
+ }
+ }
+
}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/IdentityDocument.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/IdentityDocument.java
index 8da2bd0a343..82d0a3d622c 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/IdentityDocument.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/IdentityDocument.java
@@ -8,7 +8,9 @@ import java.util.Set;
* The identity document that contains the instance specific information
*
* @author bjorncs
+ * @deprecated Will soon be inlined into {@link SignedIdentityDocument}
*/
+@Deprecated
public class IdentityDocument {
private final VespaUniqueInstanceId providerUniqueId;
private final String configServerHostname;
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/IdentityType.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/IdentityType.java
new file mode 100644
index 00000000000..4ca2e34a618
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/IdentityType.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.vespa.athenz.identityprovider.api;
+
+import java.util.Arrays;
+
+/**
+ * Represents the types of identities that the configserver can provide.
+ *
+ * @author bjorncs
+ */
+public enum IdentityType {TENANT("tenant"), NODE("node");
+ private final String id;
+
+ IdentityType(String id) { this.id = id; }
+
+ public String id() { return id; }
+
+ public static IdentityType fromId(String id) {
+ return Arrays.stream(values())
+ .filter(v -> v.id.equals(id))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("Invalid id: " + id));
+ }
+}
+
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/SignedIdentityDocument.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/SignedIdentityDocument.java
index d184efc0221..60be42544c7 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/SignedIdentityDocument.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/SignedIdentityDocument.java
@@ -4,6 +4,8 @@ package com.yahoo.vespa.athenz.identityprovider.api;
import com.yahoo.vespa.athenz.api.AthenzService;
import java.net.URI;
+import java.time.Instant;
+import java.util.Set;
/**
* A signed identity document which contains a {@link IdentityDocument}
@@ -22,6 +24,11 @@ public class SignedIdentityDocument {
private final AthenzService providerService;
private final URI ztsEndpoint;
private final int documentVersion;
+ private final String configServerHostname;
+ private final String instanceHostname;
+ private final Instant createdAt;
+ private final Set<String> ipAddresses;
+ private final IdentityType identityType;
public SignedIdentityDocument(IdentityDocument identityDocument,
String signature,
@@ -30,7 +37,12 @@ public class SignedIdentityDocument {
String dnsSuffix,
AthenzService providerService,
URI ztsEndpoint,
- int documentVersion) {
+ int documentVersion,
+ String configServerHostname,
+ String instanceHostname,
+ Instant createdAt,
+ Set<String> ipAddresses,
+ IdentityType identityType) {
this.identityDocument = identityDocument;
this.signature = signature;
this.signingKeyVersion = signingKeyVersion;
@@ -39,6 +51,11 @@ public class SignedIdentityDocument {
this.providerService = providerService;
this.ztsEndpoint = ztsEndpoint;
this.documentVersion = documentVersion;
+ this.configServerHostname = configServerHostname;
+ this.instanceHostname = instanceHostname;
+ this.createdAt = createdAt;
+ this.ipAddresses = ipAddresses;
+ this.identityType = identityType;
}
public IdentityDocument identityDocument() {
@@ -72,4 +89,24 @@ public class SignedIdentityDocument {
public int documentVersion() {
return documentVersion;
}
+
+ public String configServerHostname() {
+ return configServerHostname;
+ }
+
+ public String instanceHostname() {
+ return instanceHostname;
+ }
+
+ public Instant createdAt() {
+ return createdAt;
+ }
+
+ public Set<String> ipAddresses() {
+ return ipAddresses;
+ }
+
+ public IdentityType identityType() {
+ return identityType;
+ }
}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/VespaUniqueInstanceId.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/VespaUniqueInstanceId.java
index 5539ba53882..be94cc59691 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/VespaUniqueInstanceId.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/VespaUniqueInstanceId.java
@@ -4,6 +4,8 @@ package com.yahoo.vespa.athenz.identityprovider.api;
import java.util.Objects;
/**
+ * Represents the unique instance id as used in Vespa's integration with Athenz Copper Argos
+ *
* @author bjorncs
*/
public class VespaUniqueInstanceId {
@@ -15,6 +17,7 @@ public class VespaUniqueInstanceId {
private final String tenant;
private final String region;
private final String environment;
+ private final IdentityType type;
public VespaUniqueInstanceId(int clusterIndex,
String clusterId,
@@ -22,7 +25,8 @@ public class VespaUniqueInstanceId {
String application,
String tenant,
String region,
- String environment) {
+ String environment,
+ IdentityType type) {
this.clusterIndex = clusterIndex;
this.clusterId = clusterId;
this.instance = instance;
@@ -30,21 +34,43 @@ public class VespaUniqueInstanceId {
this.tenant = tenant;
this.region = region;
this.environment = environment;
+ this.type = type;
}
+ // TODO Remove support for legacy representation without type
+ @Deprecated
+ public VespaUniqueInstanceId(int clusterIndex,
+ String clusterId,
+ String instance,
+ String application,
+ String tenant,
+ String region,
+ String environment) {
+ this(clusterIndex, clusterId, instance, application, tenant, region, environment, null);
+ }
+
+
+ // TODO Remove support for legacy representation without type
public static VespaUniqueInstanceId fromDottedString(String instanceId) {
String[] tokens = instanceId.split("\\.");
- if (tokens.length != 7) {
+ if (tokens.length != 7 && tokens.length != 8) {
throw new IllegalArgumentException("Invalid instance id: " + instanceId);
}
return new VespaUniqueInstanceId(
- Integer.parseInt(tokens[0]), tokens[1], tokens[2], tokens[3], tokens[4], tokens[5], tokens[6]);
+ Integer.parseInt(tokens[0]), tokens[1], tokens[2], tokens[3], tokens[4], tokens[5], tokens[6], tokens.length == 8 ? IdentityType.fromId(tokens[7]) : null);
}
+ // TODO Remove support for legacy representation without type
public String asDottedString() {
- return String.format(
- "%d.%s.%s.%s.%s.%s.%s",
- clusterIndex, clusterId, instance, application, tenant, region, environment);
+ if (type != null) {
+ return String.format(
+ "%d.%s.%s.%s.%s.%s.%s.%s",
+ clusterIndex, clusterId, instance, application, tenant, region, environment, type.id());
+ } else {
+ return String.format(
+ "%d.%s.%s.%s.%s.%s.%s",
+ clusterIndex, clusterId, instance, application, tenant, region, environment);
+ }
}
public int clusterIndex() {
@@ -75,6 +101,8 @@ public class VespaUniqueInstanceId {
return environment;
}
+ public IdentityType type() { return type; }
+
@Override
public String toString() {
return "VespaUniqueInstanceId{" +
@@ -85,6 +113,7 @@ public class VespaUniqueInstanceId {
", tenant='" + tenant + '\'' +
", region='" + region + '\'' +
", environment='" + environment + '\'' +
+ ", type=" + type +
'}';
}
@@ -99,11 +128,12 @@ public class VespaUniqueInstanceId {
Objects.equals(application, that.application) &&
Objects.equals(tenant, that.tenant) &&
Objects.equals(region, that.region) &&
- Objects.equals(environment, that.environment);
+ Objects.equals(environment, that.environment) &&
+ type == that.type;
}
@Override
public int hashCode() {
- return Objects.hash(clusterIndex, clusterId, instance, application, tenant, region, environment);
+ return Objects.hash(clusterIndex, clusterId, instance, application, tenant, region, environment, type);
}
}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/IdentityDocumentApi.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/IdentityDocumentApi.java
index 775a49349a3..fc5392411c1 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/IdentityDocumentApi.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/IdentityDocumentApi.java
@@ -5,7 +5,6 @@ import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
/**
@@ -16,11 +15,6 @@ public interface IdentityDocumentApi {
@GET
@Produces(MediaType.APPLICATION_JSON)
- @Deprecated
- SignedIdentityDocumentEntity getIdentityDocument(@QueryParam("hostname") String hostname);
-
- @GET
- @Produces(MediaType.APPLICATION_JSON)
@Path("/node/{host}")
SignedIdentityDocumentEntity getNodeIdentityDocument(@PathParam("host") String host);
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/IdentityDocumentEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/IdentityDocumentEntity.java
index 58a4f1e24bf..b4b2e82ab0e 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/IdentityDocumentEntity.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/IdentityDocumentEntity.java
@@ -10,8 +10,10 @@ import java.util.Set;
/**
* @author bjorncs
+ * @deprecated Will soon be inlined into {@link SignedIdentityDocumentEntity}
*/
@JsonIgnoreProperties(ignoreUnknown = true)
+@Deprecated
public class IdentityDocumentEntity {
@JsonProperty("provider-unique-id")
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/SignedIdentityDocumentEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/SignedIdentityDocumentEntity.java
index e397b81ef9e..aa514b3caf3 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/SignedIdentityDocumentEntity.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/SignedIdentityDocumentEntity.java
@@ -11,8 +11,10 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
+import java.time.Instant;
import java.util.Base64;
import java.util.Objects;
+import java.util.Set;
/**
* @author bjorncs
@@ -31,6 +33,11 @@ public class SignedIdentityDocumentEntity {
@JsonProperty("provider-service") public final String providerService;
@JsonProperty("zts-endpoint") public final URI ztsEndpoint;
@JsonProperty("document-version") public final int documentVersion;
+ @JsonProperty("configserver-hostname") public final String configServerHostname;
+ @JsonProperty("instance-hostname") public final String instanceHostname;
+ @JsonProperty("created-at") public final Instant createdAt;
+ @JsonProperty("ip-addresses") public final Set<String> ipAddresses;
+ @JsonProperty("identity-type") public final String identityType;
@JsonCreator
public SignedIdentityDocumentEntity(@JsonProperty("identity-document") String rawIdentityDocument,
@@ -40,7 +47,12 @@ public class SignedIdentityDocumentEntity {
@JsonProperty("dns-suffix") String dnsSuffix,
@JsonProperty("provider-service") String providerService,
@JsonProperty("zts-endpoint") URI ztsEndpoint,
- @JsonProperty("document-version") int documentVersion) {
+ @JsonProperty("document-version") int documentVersion,
+ @JsonProperty("configserver-hostname") String configServerHostname,
+ @JsonProperty("instance-hostname") String instanceHostname,
+ @JsonProperty("created-at") Instant createdAt,
+ @JsonProperty("ip-addresses") Set<String> ipAddresses,
+ @JsonProperty("identity-type") String identityType) {
this.rawIdentityDocument = rawIdentityDocument;
this.identityDocument = parseIdentityDocument(rawIdentityDocument);
this.signature = signature;
@@ -50,6 +62,11 @@ public class SignedIdentityDocumentEntity {
this.providerService = providerService;
this.ztsEndpoint = ztsEndpoint;
this.documentVersion = documentVersion;
+ this.configServerHostname = configServerHostname;
+ this.instanceHostname = instanceHostname;
+ this.createdAt = createdAt;
+ this.ipAddresses = ipAddresses;
+ this.identityType = identityType;
}
private static IdentityDocumentEntity parseIdentityDocument(String rawIdentityDocument) {
@@ -73,7 +90,16 @@ public class SignedIdentityDocumentEntity {
", identityDocument=" + identityDocument +
", signature='" + signature + '\'' +
", signingKeyVersion=" + signingKeyVersion +
+ ", providerUniqueId='" + providerUniqueId + '\'' +
+ ", dnsSuffix='" + dnsSuffix + '\'' +
+ ", providerService='" + providerService + '\'' +
+ ", ztsEndpoint=" + ztsEndpoint +
", documentVersion=" + documentVersion +
+ ", configServerHostname='" + configServerHostname + '\'' +
+ ", instanceHostname='" + instanceHostname + '\'' +
+ ", createdAt=" + createdAt +
+ ", ipAddresses=" + ipAddresses +
+ ", identityType=" + identityType +
'}';
}
@@ -86,11 +112,20 @@ public class SignedIdentityDocumentEntity {
documentVersion == that.documentVersion &&
Objects.equals(rawIdentityDocument, that.rawIdentityDocument) &&
Objects.equals(identityDocument, that.identityDocument) &&
- Objects.equals(signature, that.signature);
+ Objects.equals(signature, that.signature) &&
+ Objects.equals(providerUniqueId, that.providerUniqueId) &&
+ Objects.equals(dnsSuffix, that.dnsSuffix) &&
+ Objects.equals(providerService, that.providerService) &&
+ Objects.equals(ztsEndpoint, that.ztsEndpoint) &&
+ Objects.equals(configServerHostname, that.configServerHostname) &&
+ Objects.equals(instanceHostname, that.instanceHostname) &&
+ Objects.equals(createdAt, that.createdAt) &&
+ Objects.equals(ipAddresses, that.ipAddresses) &&
+ Objects.equals(identityType, identityType);
}
@Override
public int hashCode() {
- return Objects.hash(rawIdentityDocument, identityDocument, signature, signingKeyVersion, documentVersion);
+ return Objects.hash(rawIdentityDocument, identityDocument, signature, signingKeyVersion, providerUniqueId, dnsSuffix, providerService, ztsEndpoint, documentVersion, configServerHostname, instanceHostname, createdAt, ipAddresses, identityType);
}
}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/VespaUniqueInstanceIdEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/VespaUniqueInstanceIdEntity.java
index 3c521e992ad..3fdbb49b28e 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/VespaUniqueInstanceIdEntity.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/VespaUniqueInstanceIdEntity.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.athenz.identityprovider.api.bindings;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Objects;
@@ -26,14 +27,18 @@ public class VespaUniqueInstanceIdEntity {
public final String clusterId;
@JsonProperty("cluster-index")
public final int clusterIndex;
+ @JsonProperty("type")
+ public final String type;
+ @JsonCreator
public VespaUniqueInstanceIdEntity(@JsonProperty("tenant") String tenant,
@JsonProperty("application") String application,
@JsonProperty("environment") String environment,
@JsonProperty("region") String region,
@JsonProperty("instance") String instance,
@JsonProperty("cluster-id") String clusterId,
- @JsonProperty("cluster-index") int clusterIndex) {
+ @JsonProperty("cluster-index") int clusterIndex,
+ @JsonProperty("type") String type) {
this.tenant = tenant;
this.application = application;
this.environment = environment;
@@ -41,8 +46,21 @@ public class VespaUniqueInstanceIdEntity {
this.instance = instance;
this.clusterId = clusterId;
this.clusterIndex = clusterIndex;
+ this.type = type;
}
+ @Deprecated
+ public VespaUniqueInstanceIdEntity(String tenant,
+ String application,
+ String environment,
+ String region,
+ String instance,
+ String clusterId,
+ int clusterIndex) {
+ this(tenant, application, environment, region, instance, clusterId, clusterIndex, null);
+ }
+
+
@Override
public String toString() {
return "VespaUniqueInstanceIdEntity{" +
@@ -53,6 +71,7 @@ public class VespaUniqueInstanceIdEntity {
", instance='" + instance + '\'' +
", clusterId='" + clusterId + '\'' +
", clusterIndex=" + clusterIndex +
+ ", type='" + type + '\'' +
'}';
}
@@ -67,11 +86,12 @@ public class VespaUniqueInstanceIdEntity {
Objects.equals(environment, that.environment) &&
Objects.equals(region, that.region) &&
Objects.equals(instance, that.instance) &&
- Objects.equals(clusterId, that.clusterId);
+ Objects.equals(clusterId, that.clusterId) &&
+ Objects.equals(type, that.type);
}
@Override
public int hashCode() {
- return Objects.hash(tenant, application, environment, region, instance, clusterId, clusterIndex);
+ return Objects.hash(tenant, application, environment, region, instance, clusterId, clusterIndex, type);
}
}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzCredentialsService.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzCredentialsService.java
index 96e93ca419d..e8ef2d9f97e 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzCredentialsService.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzCredentialsService.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.athenz.identityprovider.client;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.yahoo.container.core.identity.IdentityConfig;
import com.yahoo.vespa.athenz.api.AthenzService;
import com.yahoo.vespa.athenz.identityprovider.api.EntityBindingsMapper;
@@ -28,7 +29,7 @@ import static com.yahoo.vespa.athenz.tls.KeyStoreType.JKS;
*/
class AthenzCredentialsService {
- private static final ObjectMapper mapper = new ObjectMapper();
+ private static final ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule());
private final IdentityConfig identityConfig;
private final IdentityDocumentClient identityDocumentClient;
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/DefaultIdentityDocumentClient.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/DefaultIdentityDocumentClient.java
index 90d1312c9f9..b9aba6e66b0 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/DefaultIdentityDocumentClient.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/DefaultIdentityDocumentClient.java
@@ -2,14 +2,12 @@
package com.yahoo.vespa.athenz.identityprovider.client;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.yahoo.vespa.athenz.api.AthenzService;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
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.utils.AthenzIdentities;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
@@ -34,7 +32,7 @@ import java.util.function.Supplier;
public class DefaultIdentityDocumentClient implements IdentityDocumentClient {
private static final String IDENTITY_DOCUMENT_API = "/athenz/v1/provider/identity-document/";
- private static final ObjectMapper objectMapper = new ObjectMapper();
+ private static final ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule());
private final Supplier<SSLContext> sslContextSupplier;
private final HostnameVerifier hostnameVerifier;
@@ -82,15 +80,7 @@ public class DefaultIdentityDocumentClient implements IdentityDocumentClient {
String responseContent = EntityUtils.toString(response.getEntity());
if (HttpStatus.isSuccess(response.getStatusLine().getStatusCode())) {
SignedIdentityDocumentEntity entity = objectMapper.readValue(responseContent, SignedIdentityDocumentEntity.class);
- return new SignedIdentityDocument(
- EntityBindingsMapper.toIdentityDocument(entity.identityDocument),
- entity.signature,
- entity.signingKeyVersion,
- VespaUniqueInstanceId.fromDottedString(entity.providerUniqueId),
- entity.dnsSuffix,
- (AthenzService) AthenzIdentities.from(entity.providerService),
- entity.ztsEndpoint,
- entity.documentVersion);
+ return EntityBindingsMapper.toSignedIdentityDocument(entity);
} else {
throw new RuntimeException(
String.format(
diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/api/VespaUniqueInstanceIdTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/api/VespaUniqueInstanceIdTest.java
index 8c4e4c1262d..86b6c566987 100644
--- a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/api/VespaUniqueInstanceIdTest.java
+++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/api/VespaUniqueInstanceIdTest.java
@@ -2,6 +2,7 @@ package com.yahoo.vespa.athenz.identityprovider.api;
import org.junit.Test;
+import static com.yahoo.vespa.athenz.identityprovider.api.IdentityType.*;
import static org.junit.Assert.*;
/**
@@ -12,6 +13,18 @@ public class VespaUniqueInstanceIdTest {
@Test
public void can_serialize_to_and_deserialize_from_string() {
VespaUniqueInstanceId id =
+ new VespaUniqueInstanceId(1, "cluster-id", "instance", "application", "tenant", "region", "environment", TENANT);
+ String stringRepresentation = id.asDottedString();
+ String expectedStringRepresentation = "1.cluster-id.instance.application.tenant.region.environment.tenant";
+ assertEquals(expectedStringRepresentation, stringRepresentation);
+ VespaUniqueInstanceId deserializedId = VespaUniqueInstanceId.fromDottedString(stringRepresentation);
+ assertEquals(id, deserializedId);
+ }
+
+ // TODO Remove support for legacy representation without type
+ @Test
+ public void supports_legacy_representation_without_type() {
+ VespaUniqueInstanceId id =
new VespaUniqueInstanceId(1, "cluster-id", "instance", "application", "tenant", "region", "environment");
String stringRepresentation = id.asDottedString();
String expectedStringRepresentation = "1.cluster-id.instance.application.tenant.region.environment";
diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImplTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImplTest.java
index 2e9b29f5327..7ad465a7d80 100644
--- a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImplTest.java
+++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImplTest.java
@@ -11,6 +11,7 @@ import com.yahoo.test.ManualClock;
import com.yahoo.vespa.athenz.api.AthenzService;
import com.yahoo.vespa.athenz.identityprovider.api.EntityBindingsMapper;
import com.yahoo.vespa.athenz.identityprovider.api.IdentityDocument;
+import com.yahoo.vespa.athenz.identityprovider.api.IdentityType;
import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument;
import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId;
import com.yahoo.vespa.athenz.tls.KeyStoreBuilder;
@@ -132,7 +133,7 @@ public class AthenzIdentityProviderImplTest {
}
private static String getIdentityDocument() throws JsonProcessingException {
- VespaUniqueInstanceId instanceId = new VespaUniqueInstanceId(0, "default", "default", "application", "tenant", "us-north-1", "dev");
+ VespaUniqueInstanceId instanceId = new VespaUniqueInstanceId(0, "default", "default", "application", "tenant", "us-north-1", "dev", IdentityType.TENANT);
SignedIdentityDocument signedIdentityDocument = new SignedIdentityDocument(
new IdentityDocument(instanceId, "localhost", "x.y.com", Instant.EPOCH, Collections.emptySet()),
"dummysignature",
@@ -141,7 +142,12 @@ public class AthenzIdentityProviderImplTest {
"dev-us-north-1.vespa.cloud",
new AthenzService("vespa.vespa.provider_dev_us-north-1"),
URI.create("https://zts:4443/zts/v1"),
- 1);
+ 1,
+ "localhost",
+ "x.y.com",
+ Instant.EPOCH,
+ Collections.emptySet(),
+ IdentityType.TENANT);
return new ObjectMapper().registerModule(new JavaTimeModule())
.writeValueAsString(EntityBindingsMapper.toSignedIdentityDocumentEntity(signedIdentityDocument));