diff options
author | HÃ¥kon Hallingstad <hakon@oath.com> | 2017-10-26 12:02:54 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-10-26 12:02:54 +0200 |
commit | 78658b1d99dd881485ace8855fee15832d1a6a75 (patch) | |
tree | e0930295d07e111b7ddba33c8abcf7426b649168 | |
parent | a8966a531bd6d66c8dc01a8bf5e1aa2c305fa0b8 (diff) | |
parent | f314723a485674937d3952ab5f7b62e6c63ab16e (diff) |
Merge pull request #3888 from vespa-engine/hakonhall/provide-more-info-in-host-orchestrator-rest-api
Provide more info in host Orchestrator REST API
14 files changed, 370 insertions, 25 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 d18ced478f1..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 @@ -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<ServiceInstance> serviceInstances; + private Optional<ApplicationInstance> applicationInstance = Optional.empty(); public ServiceCluster(ClusterId clusterId, ServiceType serviceType, Set<ServiceInstance> serviceInstances) { this.clusterId = clusterId; @@ -38,6 +41,16 @@ 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{" + @@ -61,5 +74,4 @@ public class ServiceCluster { public int hashCode() { 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 2b31bb11ff7..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 @@ -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> serviceCluster = Optional.empty(); public ServiceInstance(ConfigId configId, HostName hostName, ServiceStatus serviceStatus) { this.configId = configId; @@ -35,6 +38,16 @@ 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{" + @@ -51,12 +64,11 @@ 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; } @Override public int hashCode() { return Objects.hash(configId, hostName, serviceStatus); } - } 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; @@ -29,6 +30,11 @@ public class OrchestratorMock implements Orchestrator { Set<ApplicationId> 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..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 @@ -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. 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<HostService> 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<HostService> 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<HostService> 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<ServiceInstance> serviceInstances; + + public Host(HostName hostName, + HostStatus hostStatus, + ApplicationInstanceReference applicationInstanceReference, + List<ServiceInstance> 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<ServiceInstance> 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 @@ -32,6 +32,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; @@ -78,6 +79,20 @@ public class OrchestratorImpl implements Orchestrator { } @Override + public Host getHost(HostName hostName) throws HostNameNotFoundException { + ApplicationInstance applicationInstance = getApplicationInstance(hostName); + List<ServiceInstance> 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..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 @@ -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; @@ -22,9 +24,14 @@ 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; +import java.net.URI; +import java.util.List; import java.util.logging.Logger; +import java.util.stream.Collectors; /** * @author oyving @@ -34,18 +41,38 @@ 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(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<HostService> 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/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); + } } 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; } |