From 37ce355370e8483c05bd9a7ce9f4f248e19ad4fb Mon Sep 17 00:00:00 2001 From: Håkon Hallingstad Date: Wed, 25 Oct 2017 17:36:20 +0200 Subject: Provide more info in host Orchestrator REST API --- .../vespa/applicationmodel/ServiceCluster.java | 22 ++++++- .../vespa/applicationmodel/ServiceInstance.java | 20 +++++- .../provision/testutils/OrchestratorMock.java | 6 ++ .../yahoo/vespa/orchestrator/restapi/HostApi.java | 5 +- .../orchestrator/restapi/wire/GetHostResponse.java | 53 ++++++++++++---- .../orchestrator/restapi/wire/HostService.java | 52 +++++++++++++++ .../java/com/yahoo/vespa/orchestrator/Host.java | 42 ++++++++++++ .../com/yahoo/vespa/orchestrator/Orchestrator.java | 6 ++ .../yahoo/vespa/orchestrator/OrchestratorImpl.java | 15 +++++ .../vespa/orchestrator/resources/HostResource.java | 30 ++++++++- .../vespa/orchestrator/OrchestratorImplTest.java | 74 +++++++++++++++++++++- .../service/monitor/ConfigServerApplication.java | 6 ++ .../vespa/service/monitor/ModelGenerator.java | 8 +++ 13 files changed, 316 insertions(+), 23 deletions(-) create mode 100644 orchestrator-restapi/src/main/java/com/yahoo/vespa/orchestrator/restapi/wire/HostService.java create mode 100644 orchestrator/src/main/java/com/yahoo/vespa/orchestrator/Host.java diff --git a/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceCluster.java b/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceCluster.java index d18ced478f1..e53058f40e3 100644 --- a/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceCluster.java +++ b/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceCluster.java @@ -1,9 +1,11 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.applicationmodel; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Objects; +import java.util.Optional; import java.util.Set; /** @@ -16,6 +18,7 @@ public class ServiceCluster { private final ClusterId clusterId; private final ServiceType serviceType; private final Set serviceInstances; + private Optional applicationInstance = Optional.empty(); public ServiceCluster(ClusterId clusterId, ServiceType serviceType, Set serviceInstances) { this.clusterId = clusterId; @@ -38,12 +41,25 @@ public class ServiceCluster { return serviceInstances; } + @JsonIgnore + public void setApplicationInstance(ApplicationInstance applicationInstance) { + this.applicationInstance = Optional.of(applicationInstance); + } + + @JsonIgnore + public ApplicationInstance getApplicationInstance() { + return applicationInstance.get(); + } + @Override public String toString() { return "ServiceCluster{" + "clusterId=" + clusterId + ", serviceType=" + serviceType + ", serviceInstances=" + serviceInstances + + (applicationInstance.isPresent() ? + ", applicationInstance=" + applicationInstance.get() : + "") + '}'; } @@ -54,12 +70,12 @@ public class ServiceCluster { ServiceCluster that = (ServiceCluster) o; return Objects.equals(clusterId, that.clusterId) && Objects.equals(serviceType, that.serviceType) && - Objects.equals(serviceInstances, that.serviceInstances); + Objects.equals(serviceInstances, that.serviceInstances) && + Objects.equals(applicationInstance, that.applicationInstance); } @Override public int hashCode() { - return Objects.hash(clusterId, serviceType, serviceInstances); + return Objects.hash(clusterId, serviceType, serviceInstances, applicationInstance); } - } diff --git a/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceInstance.java b/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceInstance.java index 2b31bb11ff7..4c802218b22 100644 --- a/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceInstance.java +++ b/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceInstance.java @@ -1,9 +1,11 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.applicationmodel; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Objects; +import java.util.Optional; /** * @author bjorncs @@ -13,6 +15,7 @@ public class ServiceInstance { private final ConfigId configId; private final HostName hostName; private final ServiceStatus serviceStatus; + private Optional serviceCluster = Optional.empty(); public ServiceInstance(ConfigId configId, HostName hostName, ServiceStatus serviceStatus) { this.configId = configId; @@ -35,12 +38,23 @@ public class ServiceInstance { return serviceStatus; } + @JsonIgnore + public void setServiceCluster(ServiceCluster serviceCluster) { + this.serviceCluster = Optional.of(serviceCluster); + } + + @JsonIgnore + public ServiceCluster getServiceCluster() { + return serviceCluster.get(); + } + @Override public String toString() { return "ServiceInstance{" + "configId=" + configId + ", hostName=" + hostName + ", serviceStatus=" + serviceStatus + + (serviceCluster.isPresent() ? ", serviceCluster=" + serviceCluster.get() : "") + '}'; } @@ -51,12 +65,12 @@ public class ServiceInstance { ServiceInstance that = (ServiceInstance) o; return Objects.equals(configId, that.configId) && Objects.equals(hostName, that.hostName) && - Objects.equals(serviceStatus, that.serviceStatus); + serviceStatus == that.serviceStatus && + Objects.equals(serviceCluster, that.serviceCluster); } @Override public int hashCode() { - return Objects.hash(configId, hostName, serviceStatus); + return Objects.hash(configId, hostName, serviceStatus, serviceCluster); } - } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/OrchestratorMock.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/OrchestratorMock.java index 19882f0a508..46d72974718 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/OrchestratorMock.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/OrchestratorMock.java @@ -7,6 +7,7 @@ import com.yahoo.vespa.orchestrator.ApplicationIdNotFoundException; import com.yahoo.vespa.orchestrator.ApplicationStateChangeDeniedException; import com.yahoo.vespa.orchestrator.BatchHostNameNotFoundException; import com.yahoo.vespa.orchestrator.BatchInternalErrorException; +import com.yahoo.vespa.orchestrator.Host; import com.yahoo.vespa.orchestrator.HostNameNotFoundException; import com.yahoo.vespa.orchestrator.OrchestrationException; import com.yahoo.vespa.orchestrator.Orchestrator; @@ -28,6 +29,11 @@ public class OrchestratorMock implements Orchestrator { Set suspendedApplications = new HashSet<>(); + @Override + public Host getHost(HostName hostName) throws HostNameNotFoundException { + return null; + } + @Override public HostStatus getNodeStatus(HostName hostName) throws HostNameNotFoundException { return null; diff --git a/orchestrator-restapi/src/main/java/com/yahoo/vespa/orchestrator/restapi/HostApi.java b/orchestrator-restapi/src/main/java/com/yahoo/vespa/orchestrator/restapi/HostApi.java index 1c4d138acef..7f576f6c4e3 100644 --- a/orchestrator-restapi/src/main/java/com/yahoo/vespa/orchestrator/restapi/HostApi.java +++ b/orchestrator-restapi/src/main/java/com/yahoo/vespa/orchestrator/restapi/HostApi.java @@ -14,7 +14,9 @@ import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriInfo; /** * Definition of Orchestrator's REST API for hosts. @@ -38,7 +40,8 @@ public interface HostApi { @GET @Path("/{hostname}") @Produces(MediaType.APPLICATION_JSON) - GetHostResponse getHost(@PathParam("hostname") String hostNameString); + GetHostResponse getHost(@Context UriInfo uriInfo, + @PathParam("hostname") String hostNameString); /** * Tweak internal Orchestrator state for host. diff --git a/orchestrator-restapi/src/main/java/com/yahoo/vespa/orchestrator/restapi/wire/GetHostResponse.java b/orchestrator-restapi/src/main/java/com/yahoo/vespa/orchestrator/restapi/wire/GetHostResponse.java index 99489b345d4..38801c4c817 100644 --- a/orchestrator-restapi/src/main/java/com/yahoo/vespa/orchestrator/restapi/wire/GetHostResponse.java +++ b/orchestrator-restapi/src/main/java/com/yahoo/vespa/orchestrator/restapi/wire/GetHostResponse.java @@ -2,27 +2,48 @@ package com.yahoo.vespa.orchestrator.restapi.wire; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; import java.util.Objects; /* * @author andreer */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) public class GetHostResponse { public static final String FIELD_NAME_HOSTNAME = "hostname"; public static final String FIELD_NAME_STATE = "state"; + public static final String FIELD_NAME_APPLICATION_URL = "applicationUrl"; + public static final String FIELD_NAME_SERVICES = "services"; private final String hostname; private final String state; + private final String applicationUrl; + private final List services; + + // Deprecated - kept for backwards compatibility until clients have migrated away + public GetHostResponse(String hostname, String state) { + this.hostname = hostname; + this.state = state; + this.applicationUrl = null; + this.services = null; + } @JsonCreator public GetHostResponse( - @JsonProperty(FIELD_NAME_HOSTNAME) final String hostname, - @JsonProperty(FIELD_NAME_STATE) final String state) { + @JsonProperty(FIELD_NAME_HOSTNAME) String hostname, + @JsonProperty(FIELD_NAME_STATE) String state, + @JsonProperty(FIELD_NAME_APPLICATION_URL) String applicationUrl, + @JsonProperty(FIELD_NAME_SERVICES) List services) { this.hostname = hostname; this.state = state; + this.applicationUrl = applicationUrl; + this.services = services; } @JsonProperty(FIELD_NAME_HOSTNAME) @@ -35,21 +56,29 @@ public class GetHostResponse { return state; } - @Override - public boolean equals(final Object o) { - if (!(o instanceof GetHostResponse)) { - return false; - } + @JsonProperty(FIELD_NAME_APPLICATION_URL) + public String applicationUrl() { + return applicationUrl; + } - final GetHostResponse other = (GetHostResponse) o; - if (!Objects.equals(this.hostname, other.hostname)) return false; - if (!Objects.equals(this.state, other.state)) return false; + @JsonProperty(FIELD_NAME_SERVICES) + public List services() { + return services; + } - return true; + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GetHostResponse that = (GetHostResponse) o; + return Objects.equals(hostname, that.hostname) && + Objects.equals(state, that.state) && + Objects.equals(applicationUrl, that.applicationUrl) && + Objects.equals(services, that.services); } @Override public int hashCode() { - return Objects.hash(hostname, state); + return Objects.hash(hostname, state, applicationUrl, services); } } diff --git a/orchestrator-restapi/src/main/java/com/yahoo/vespa/orchestrator/restapi/wire/HostService.java b/orchestrator-restapi/src/main/java/com/yahoo/vespa/orchestrator/restapi/wire/HostService.java new file mode 100644 index 00000000000..70cc9187dd1 --- /dev/null +++ b/orchestrator-restapi/src/main/java/com/yahoo/vespa/orchestrator/restapi/wire/HostService.java @@ -0,0 +1,52 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.orchestrator.restapi.wire; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Objects; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class HostService { + @JsonProperty("clusterId") + public final String clusterId; + + @JsonProperty("serviceType") + public final String serviceType; + + @JsonProperty("configId") + public final String configId; + + @JsonProperty("serviceStatus") + public final String serviceStatus; + + @JsonCreator + public HostService(@JsonProperty("clusterId") String clusterId, + @JsonProperty("serviceType") String serviceType, + @JsonProperty("configId") String configId, + @JsonProperty("serviceStatus") String serviceStatus) { + this.clusterId = clusterId; + this.serviceType = serviceType; + this.configId = configId; + this.serviceStatus = serviceStatus; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + HostService that = (HostService) o; + return Objects.equals(clusterId, that.clusterId) && + Objects.equals(serviceType, that.serviceType) && + Objects.equals(configId, that.configId) && + Objects.equals(serviceStatus, that.serviceStatus); + } + + @Override + public int hashCode() { + return Objects.hash(clusterId, serviceType, configId, serviceStatus); + } +} diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/Host.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/Host.java new file mode 100644 index 00000000000..8e06f3b342a --- /dev/null +++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/Host.java @@ -0,0 +1,42 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.orchestrator; + +import com.yahoo.vespa.applicationmodel.ApplicationInstanceReference; +import com.yahoo.vespa.applicationmodel.HostName; +import com.yahoo.vespa.applicationmodel.ServiceInstance; +import com.yahoo.vespa.orchestrator.status.HostStatus; + +import java.util.List; + +public class Host { + private final HostName hostName; + private final HostStatus hostStatus; + private final ApplicationInstanceReference applicationInstanceReference; + private final List serviceInstances; + + public Host(HostName hostName, + HostStatus hostStatus, + ApplicationInstanceReference applicationInstanceReference, + List serviceInstances) { + this.hostName = hostName; + this.hostStatus = hostStatus; + this.applicationInstanceReference = applicationInstanceReference; + this.serviceInstances = serviceInstances; + } + + public HostName getHostName() { + return hostName; + } + + public HostStatus getHostStatus() { + return hostStatus; + } + + public ApplicationInstanceReference getApplicationInstanceReference() { + return applicationInstanceReference; + } + + public List getServiceInstances() { + return serviceInstances; + } +} diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/Orchestrator.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/Orchestrator.java index 9702b97dc28..ab61a51c418 100644 --- a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/Orchestrator.java +++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/Orchestrator.java @@ -31,6 +31,12 @@ import java.util.Set; */ public interface Orchestrator { + /** + * Get orchestrator information related to a host. + * @throws HostNameNotFoundException + */ + Host getHost(HostName hostName) throws HostNameNotFoundException; + /** * Get the status of a given node. If no state is recorded * then this will return the status 'No Remarks' diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/OrchestratorImpl.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/OrchestratorImpl.java index ef81a45fefe..dbc526bdf03 100644 --- a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/OrchestratorImpl.java +++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/OrchestratorImpl.java @@ -9,6 +9,7 @@ import com.yahoo.vespa.applicationmodel.ApplicationInstanceReference; import com.yahoo.vespa.applicationmodel.ClusterId; import com.yahoo.vespa.applicationmodel.HostName; import com.yahoo.vespa.applicationmodel.ServiceCluster; +import com.yahoo.vespa.applicationmodel.ServiceInstance; import com.yahoo.vespa.orchestrator.config.OrchestratorConfig; import com.yahoo.vespa.orchestrator.controller.ClusterControllerClient; import com.yahoo.vespa.orchestrator.controller.ClusterControllerClientFactory; @@ -77,6 +78,20 @@ public class OrchestratorImpl implements Orchestrator { } + @Override + public Host getHost(HostName hostName) throws HostNameNotFoundException { + ApplicationInstance applicationInstance = getApplicationInstance(hostName); + List serviceInstances = applicationInstance + .serviceClusters().stream() + .flatMap(cluster -> cluster.serviceInstances().stream()) + .filter(serviceInstance -> hostName.equals(serviceInstance.hostName())) + .collect(Collectors.toList()); + + HostStatus hostStatus = getNodeStatus(applicationInstance.reference(), hostName); + + return new Host(hostName, hostStatus, applicationInstance.reference(), serviceInstances); + } + @Override public HostStatus getNodeStatus(HostName hostName) throws HostNameNotFoundException { return getNodeStatus(getApplicationInstance(hostName).reference(), hostName); diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/HostResource.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/HostResource.java index b5ef2822761..198f291266b 100644 --- a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/HostResource.java +++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/HostResource.java @@ -4,12 +4,14 @@ package com.yahoo.vespa.orchestrator.resources; import com.yahoo.container.jaxrs.annotation.Component; import com.yahoo.log.LogLevel; import com.yahoo.vespa.applicationmodel.HostName; +import com.yahoo.vespa.orchestrator.Host; import com.yahoo.vespa.orchestrator.HostNameNotFoundException; import com.yahoo.vespa.orchestrator.OrchestrationException; import com.yahoo.vespa.orchestrator.Orchestrator; import com.yahoo.vespa.orchestrator.policy.HostStateChangeDeniedException; import com.yahoo.vespa.orchestrator.restapi.HostApi; import com.yahoo.vespa.orchestrator.restapi.wire.GetHostResponse; +import com.yahoo.vespa.orchestrator.restapi.wire.HostService; import com.yahoo.vespa.orchestrator.restapi.wire.HostStateChangeDenialReason; import com.yahoo.vespa.orchestrator.restapi.wire.PatchHostRequest; import com.yahoo.vespa.orchestrator.restapi.wire.PatchHostResponse; @@ -24,7 +26,11 @@ import javax.ws.rs.Path; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import java.net.URI; +import java.util.List; import java.util.logging.Logger; +import java.util.stream.Collectors; /** * @author oyving @@ -41,11 +47,29 @@ public class HostResource implements HostApi { } @Override - public GetHostResponse getHost(String hostNameString) { + public GetHostResponse getHost(UriInfo uriInfo, String hostNameString) { HostName hostName = new HostName(hostNameString); try { - HostStatus status = orchestrator.getNodeStatus(hostName); - return new GetHostResponse(hostName.s(), status.name()); + Host host = orchestrator.getHost(hostName); + + URI applicationUri = uriInfo.getBaseUriBuilder() + .path(InstanceResource.class) + .path(host.getApplicationInstanceReference().asString()) + .build(); + + List hostServices = host.getServiceInstances().stream() + .map(serviceInstance -> new HostService( + serviceInstance.getServiceCluster().clusterId().s(), + serviceInstance.getServiceCluster().serviceType().s(), + serviceInstance.configId().s(), + serviceInstance.serviceStatus().name())) + .collect(Collectors.toList()); + + return new GetHostResponse( + host.getHostName().s(), + host.getHostStatus().name(), + applicationUri.toString(), + hostServices); } catch (HostNameNotFoundException e) { log.log(LogLevel.INFO, "Host not found: " + hostName); throw new NotFoundException(e); diff --git a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/OrchestratorImplTest.java b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/OrchestratorImplTest.java index 8cf9d343134..76d9398c44e 100644 --- a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/OrchestratorImplTest.java +++ b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/OrchestratorImplTest.java @@ -3,14 +3,25 @@ package com.yahoo.vespa.orchestrator; import com.yahoo.config.provision.ApplicationId; import com.yahoo.vespa.applicationmodel.ApplicationInstance; +import com.yahoo.vespa.applicationmodel.ApplicationInstanceId; import com.yahoo.vespa.applicationmodel.ApplicationInstanceReference; +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.ServiceInstance; +import com.yahoo.vespa.applicationmodel.ServiceStatus; +import com.yahoo.vespa.applicationmodel.ServiceType; +import com.yahoo.vespa.applicationmodel.TenantId; import com.yahoo.vespa.orchestrator.config.OrchestratorConfig; +import com.yahoo.vespa.orchestrator.controller.ClusterControllerClientFactory; import com.yahoo.vespa.orchestrator.controller.ClusterControllerClientFactoryMock; import com.yahoo.vespa.orchestrator.policy.BatchHostStateChangeDeniedException; import com.yahoo.vespa.orchestrator.policy.HostStateChangeDeniedException; import com.yahoo.vespa.orchestrator.status.HostStatus; import com.yahoo.vespa.orchestrator.status.InMemoryStatusService; +import com.yahoo.vespa.orchestrator.status.ReadOnlyStatusRegistry; +import com.yahoo.vespa.orchestrator.status.StatusService; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -18,6 +29,9 @@ import org.mockito.InOrder; import java.util.Arrays; import java.util.Iterator; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static com.yahoo.vespa.orchestrator.status.ApplicationInstanceStatus.ALLOWED_TO_BE_DOWN; import static com.yahoo.vespa.orchestrator.status.ApplicationInstanceStatus.NO_REMARKS; @@ -31,7 +45,9 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; /** * Test Orchestrator with a mock backend (the InMemoryStatusService) @@ -139,7 +155,6 @@ public class OrchestratorImplTest { assertThat(orchestrator.getApplicationInstanceStatus(app2), is(ALLOWED_TO_BE_DOWN)); } - @Test public void application_suspend_sets_application_nodes_in_maintenance_and_allowed_to_be_down() throws Exception { // Pre condition @@ -281,6 +296,63 @@ public class OrchestratorImplTest { order.verifyNoMoreInteractions(); } + @Test + public void testGetHost() throws Exception { + ClusterControllerClientFactory clusterControllerClientFactory = + mock(ClusterControllerClientFactory.class); + StatusService statusService = mock(StatusService.class); + InstanceLookupService lookupService = mock(InstanceLookupService.class); + + orchestrator = new OrchestratorImpl( + clusterControllerClientFactory, + statusService, + new OrchestratorConfig(new OrchestratorConfig.Builder()), + lookupService); + + HostName hostName = new HostName("host.yahoo.com"); + TenantId tenantId = new TenantId("tenant"); + ApplicationInstanceId applicationInstanceId = + new ApplicationInstanceId("applicationInstanceId"); + ApplicationInstanceReference reference = new ApplicationInstanceReference( + tenantId, + applicationInstanceId); + + ApplicationInstance applicationInstance = + new ApplicationInstance( + tenantId, + applicationInstanceId, + Stream.of(new ServiceCluster( + new ClusterId("clusterId"), + new ServiceType("serviceType"), + Stream.of( + new ServiceInstance( + new ConfigId("configId1"), + hostName, + ServiceStatus.UP), + new ServiceInstance( + new ConfigId("configId2"), + hostName, + ServiceStatus.NOT_CHECKED)) + .collect(Collectors.toSet()))) + .collect(Collectors.toSet())); + + when(lookupService.findInstanceByHost(hostName)) + .thenReturn(Optional.of(applicationInstance)); + + ReadOnlyStatusRegistry readOnlyStatusRegistry = mock(ReadOnlyStatusRegistry.class); + when(statusService.forApplicationInstance(reference)) + .thenReturn(readOnlyStatusRegistry); + when(readOnlyStatusRegistry.getHostStatus(hostName)) + .thenReturn(HostStatus.ALLOWED_TO_BE_DOWN); + + Host host = orchestrator.getHost(hostName); + + assertEquals(reference, host.getApplicationInstanceReference()); + assertEquals(hostName, host.getHostName()); + assertEquals(HostStatus.ALLOWED_TO_BE_DOWN, host.getHostStatus()); + assertEquals(2, host.getServiceInstances().size()); + } + private boolean isInMaintenance(ApplicationId appId, HostName hostName) throws ApplicationIdNotFoundException { for (ApplicationInstance app : DummyInstanceLookupService.getApplications()) { if (app.reference().equals(OrchestratorUtil.toApplicationInstanceReference(appId, new DummyInstanceLookupService()))) { diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/ConfigServerApplication.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/ConfigServerApplication.java index 1a48ba870fb..7a76c072076 100644 --- a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/ConfigServerApplication.java +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/ConfigServerApplication.java @@ -48,6 +48,12 @@ public class ConfigServerApplication { APPLICATION_INSTANCE_ID, serviceClusters); + // Fill back-references + serviceCluster.setApplicationInstance(applicationInstance); + for (ServiceInstance serviceInstance : serviceCluster.serviceInstances()) { + serviceInstance.setServiceCluster(serviceCluster); + } + return applicationInstance; } } diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/ModelGenerator.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/ModelGenerator.java index d9f019ade63..9716846dea4 100644 --- a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/ModelGenerator.java +++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/ModelGenerator.java @@ -101,6 +101,14 @@ public class ModelGenerator { 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; } -- cgit v1.2.3 From 935c7d664cb2a0c8ce09da3e417e95ef8d3657ff Mon Sep 17 00:00:00 2001 From: Håkon Hallingstad Date: Wed, 25 Oct 2017 17:41:39 +0200 Subject: Avoid recursive hashCode and equals --- .../main/java/com/yahoo/vespa/applicationmodel/ServiceCluster.java | 5 ++--- .../main/java/com/yahoo/vespa/applicationmodel/ServiceInstance.java | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceCluster.java b/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceCluster.java index e53058f40e3..925527d2732 100644 --- a/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceCluster.java +++ b/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceCluster.java @@ -70,12 +70,11 @@ public class ServiceCluster { ServiceCluster that = (ServiceCluster) o; return Objects.equals(clusterId, that.clusterId) && Objects.equals(serviceType, that.serviceType) && - Objects.equals(serviceInstances, that.serviceInstances) && - Objects.equals(applicationInstance, that.applicationInstance); + Objects.equals(serviceInstances, that.serviceInstances); } @Override public int hashCode() { - return Objects.hash(clusterId, serviceType, serviceInstances, applicationInstance); + return Objects.hash(clusterId, serviceType, serviceInstances); } } diff --git a/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceInstance.java b/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceInstance.java index 4c802218b22..37d802406d1 100644 --- a/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceInstance.java +++ b/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceInstance.java @@ -65,12 +65,11 @@ public class ServiceInstance { ServiceInstance that = (ServiceInstance) o; return Objects.equals(configId, that.configId) && Objects.equals(hostName, that.hostName) && - serviceStatus == that.serviceStatus && - Objects.equals(serviceCluster, that.serviceCluster); + serviceStatus == that.serviceStatus; } @Override public int hashCode() { - return Objects.hash(configId, hostName, serviceStatus, serviceCluster); + return Objects.hash(configId, hostName, serviceStatus); } } -- cgit v1.2.3 From c4907cf034d0a744a0cac2d49a68ef6d2a83c76f Mon Sep 17 00:00:00 2001 From: Håkon Hallingstad Date: Wed, 25 Oct 2017 17:59:36 +0200 Subject: Avoid recursive toString --- .../src/main/java/com/yahoo/vespa/applicationmodel/ServiceCluster.java | 3 --- .../main/java/com/yahoo/vespa/applicationmodel/ServiceInstance.java | 1 - 2 files changed, 4 deletions(-) diff --git a/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceCluster.java b/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceCluster.java index 925527d2732..9c3e568642b 100644 --- a/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceCluster.java +++ b/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceCluster.java @@ -57,9 +57,6 @@ public class ServiceCluster { "clusterId=" + clusterId + ", serviceType=" + serviceType + ", serviceInstances=" + serviceInstances + - (applicationInstance.isPresent() ? - ", applicationInstance=" + applicationInstance.get() : - "") + '}'; } diff --git a/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceInstance.java b/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceInstance.java index 37d802406d1..80178f79bb1 100644 --- a/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceInstance.java +++ b/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceInstance.java @@ -54,7 +54,6 @@ public class ServiceInstance { "configId=" + configId + ", hostName=" + hostName + ", serviceStatus=" + serviceStatus + - (serviceCluster.isPresent() ? ", serviceCluster=" + serviceCluster.get() : "") + '}'; } -- cgit v1.2.3 From f314723a485674937d3952ab5f7b62e6c63ab16e Mon Sep 17 00:00:00 2001 From: Håkon Hallingstad Date: Thu, 26 Oct 2017 10:59:06 +0200 Subject: Undo incompatible change to HostResource::getHost --- .../yahoo/vespa/orchestrator/restapi/HostApi.java | 3 +- .../vespa/orchestrator/resources/HostResource.java | 7 ++- .../orchestrator/resources/HostResourceTest.java | 68 ++++++++++++++++++++-- 3 files changed, 68 insertions(+), 10 deletions(-) diff --git a/orchestrator-restapi/src/main/java/com/yahoo/vespa/orchestrator/restapi/HostApi.java b/orchestrator-restapi/src/main/java/com/yahoo/vespa/orchestrator/restapi/HostApi.java index 7f576f6c4e3..ad0f3e094eb 100644 --- a/orchestrator-restapi/src/main/java/com/yahoo/vespa/orchestrator/restapi/HostApi.java +++ b/orchestrator-restapi/src/main/java/com/yahoo/vespa/orchestrator/restapi/HostApi.java @@ -40,8 +40,7 @@ public interface HostApi { @GET @Path("/{hostname}") @Produces(MediaType.APPLICATION_JSON) - GetHostResponse getHost(@Context UriInfo uriInfo, - @PathParam("hostname") String hostNameString); + GetHostResponse getHost(@PathParam("hostname") String hostNameString); /** * Tweak internal Orchestrator state for host. diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/HostResource.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/HostResource.java index 198f291266b..52994db2b88 100644 --- a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/HostResource.java +++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/HostResource.java @@ -24,6 +24,7 @@ import javax.ws.rs.InternalServerErrorException; import javax.ws.rs.NotFoundException; import javax.ws.rs.Path; import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; @@ -40,14 +41,16 @@ public class HostResource implements HostApi { private static final Logger log = Logger.getLogger(HostResource.class.getName()); private final Orchestrator orchestrator; + private final UriInfo uriInfo; @Inject - public HostResource(@Component Orchestrator orchestrator) { + public HostResource(@Component Orchestrator orchestrator, @Context UriInfo uriInfo) { this.orchestrator = orchestrator; + this.uriInfo = uriInfo; } @Override - public GetHostResponse getHost(UriInfo uriInfo, String hostNameString) { + public GetHostResponse getHost(String hostNameString) { HostName hostName = new HostName(hostNameString); try { Host host = orchestrator.getHost(hostName); diff --git a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/HostResourceTest.java b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/HostResourceTest.java index 0ae385bc717..65309440aee 100644 --- a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/HostResourceTest.java +++ b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/HostResourceTest.java @@ -4,8 +4,15 @@ package com.yahoo.vespa.orchestrator.resources; import com.yahoo.vespa.applicationmodel.ApplicationInstance; import com.yahoo.vespa.applicationmodel.ApplicationInstanceId; import com.yahoo.vespa.applicationmodel.ApplicationInstanceReference; +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.ServiceInstance; +import com.yahoo.vespa.applicationmodel.ServiceStatus; +import com.yahoo.vespa.applicationmodel.ServiceType; import com.yahoo.vespa.applicationmodel.TenantId; +import com.yahoo.vespa.orchestrator.Host; import com.yahoo.vespa.orchestrator.InstanceLookupService; import com.yahoo.vespa.orchestrator.OrchestrationException; import com.yahoo.vespa.orchestrator.Orchestrator; @@ -16,6 +23,7 @@ import com.yahoo.vespa.orchestrator.policy.HostStateChangeDeniedException; import com.yahoo.vespa.orchestrator.policy.Policy; import com.yahoo.vespa.orchestrator.restapi.wire.BatchHostSuspendRequest; import com.yahoo.vespa.orchestrator.restapi.wire.BatchOperationResult; +import com.yahoo.vespa.orchestrator.restapi.wire.GetHostResponse; import com.yahoo.vespa.orchestrator.restapi.wire.PatchHostRequest; import com.yahoo.vespa.orchestrator.restapi.wire.PatchHostResponse; import com.yahoo.vespa.orchestrator.restapi.wire.UpdateHostResponse; @@ -28,6 +36,9 @@ import org.junit.Test; import javax.ws.rs.BadRequestException; import javax.ws.rs.InternalServerErrorException; import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriInfo; +import java.net.URI; import java.util.Arrays; import java.util.Collections; import java.util.Optional; @@ -137,10 +148,12 @@ public class HostResourceTest { SERVICE_MONITOR_CONVERGENCE_LATENCY_SECONDS ); + private final UriInfo uriInfo = mock(UriInfo.class); + @Test public void returns_200_on_success() throws Exception { HostResource hostResource = - new HostResource(alwaysAllowOrchestrator); + new HostResource(alwaysAllowOrchestrator, uriInfo); final String hostName = "hostname"; @@ -171,7 +184,7 @@ public class HostResourceTest { public void throws_404_when_host_unknown() throws Exception { try { HostResource hostResource = - new HostResource(hostNotFoundOrchestrator); + new HostResource(hostNotFoundOrchestrator, uriInfo); hostResource.suspend("hostname"); fail(); } catch (WebApplicationException w) { @@ -244,7 +257,7 @@ public class HostResourceTest { SERVICE_MONITOR_CONVERGENCE_LATENCY_SECONDS); try { - HostResource hostResource = new HostResource(alwaysRejectResolver); + HostResource hostResource = new HostResource(alwaysRejectResolver, uriInfo); hostResource.suspend("hostname"); fail(); } catch (WebApplicationException w) { @@ -275,7 +288,7 @@ public class HostResourceTest { @Test(expected = BadRequestException.class) public void patch_state_may_throw_bad_request() { Orchestrator orchestrator = mock(Orchestrator.class); - HostResource hostResource = new HostResource(orchestrator); + HostResource hostResource = new HostResource(orchestrator, uriInfo); String hostNameString = "hostname"; PatchHostRequest request = new PatchHostRequest(); @@ -287,7 +300,7 @@ public class HostResourceTest { @Test public void patch_works() throws OrchestrationException { Orchestrator orchestrator = mock(Orchestrator.class); - HostResource hostResource = new HostResource(orchestrator); + HostResource hostResource = new HostResource(orchestrator, uriInfo); String hostNameString = "hostname"; PatchHostRequest request = new PatchHostRequest(); @@ -301,7 +314,7 @@ public class HostResourceTest { @Test(expected = InternalServerErrorException.class) public void patch_handles_exception_in_orchestrator() throws OrchestrationException { Orchestrator orchestrator = mock(Orchestrator.class); - HostResource hostResource = new HostResource(orchestrator); + HostResource hostResource = new HostResource(orchestrator, uriInfo); String hostNameString = "hostname"; PatchHostRequest request = new PatchHostRequest(); @@ -310,4 +323,47 @@ public class HostResourceTest { doThrow(new OrchestrationException("error")).when(orchestrator).setNodeStatus(new HostName(hostNameString), HostStatus.NO_REMARKS); hostResource.patch(hostNameString, request); } + + @Test + public void getHost_works() throws Exception { + Orchestrator orchestrator = mock(Orchestrator.class); + HostResource hostResource = new HostResource(orchestrator, uriInfo); + + HostName hostName = new HostName("hostname"); + + UriBuilder baseUriBuilder = mock(UriBuilder.class); + when(uriInfo.getBaseUriBuilder()).thenReturn(baseUriBuilder); + when(baseUriBuilder.path(any(String.class))).thenReturn(baseUriBuilder); + when(baseUriBuilder.path(any(Class.class))).thenReturn(baseUriBuilder); + URI uri = new URI("https://foo.com/bar"); + when(baseUriBuilder.build()).thenReturn(uri); + + ServiceInstance serviceInstance = new ServiceInstance( + new ConfigId("configId"), + hostName, + ServiceStatus.UP); + ServiceCluster serviceCluster = new ServiceCluster( + new ClusterId("clusterId"), + new ServiceType("serviceType"), + Collections.singleton(serviceInstance)); + serviceInstance.setServiceCluster(serviceCluster); + + Host host = new Host( + hostName, + HostStatus.ALLOWED_TO_BE_DOWN, + new ApplicationInstanceReference( + new TenantId("tenantId"), + new ApplicationInstanceId("applicationId")), + Collections.singletonList(serviceInstance)); + when(orchestrator.getHost(hostName)).thenReturn(host); + GetHostResponse response = hostResource.getHost(hostName.s()); + assertEquals("https://foo.com/bar", response.applicationUrl()); + assertEquals("hostname", response.hostname()); + assertEquals("ALLOWED_TO_BE_DOWN", response.state()); + assertEquals(1, response.services().size()); + assertEquals("clusterId", response.services().get(0).clusterId); + assertEquals("configId", response.services().get(0).configId); + assertEquals("UP", response.services().get(0).serviceStatus); + assertEquals("serviceType", response.services().get(0).serviceType); + } } -- cgit v1.2.3