From 7a31731f908fb7e0bb091304528c55d477203f7f Mon Sep 17 00:00:00 2001 From: Valerij Fredriksen Date: Thu, 15 Feb 2018 15:58:41 +0100 Subject: Move noderepostiory and orchestrator under configserver. No functional changes --- .../admin/configserver/ConfigServerClients.java | 4 +- .../configserver/ConfigServerClientsImpl.java | 8 +- .../noderepository/NodeRepository.java | 26 +++ .../noderepository/NodeRepositoryImpl.java | 172 +++++++++++++++++++ .../noderepository/bindings/GetAclResponse.java | 66 ++++++++ .../noderepository/bindings/GetNodesResponse.java | 174 ++++++++++++++++++++ .../bindings/NodeMessageResponse.java | 18 ++ .../bindings/UpdateNodeAttributesRequestBody.java | 36 ++++ .../bindings/UpdateNodeAttributesResponse.java | 24 +++ .../configserver/orchestrator/Orchestrator.java | 31 ++++ .../orchestrator/OrchestratorException.java | 9 + .../orchestrator/OrchestratorImpl.java | 99 +++++++++++ .../OrchestratorNotFoundException.java | 9 + .../node/admin/maintenance/acl/AclMaintainer.java | 2 +- .../admin/nodeadmin/NodeAdminStateUpdaterImpl.java | 2 +- .../hosted/node/admin/nodeagent/NodeAgentImpl.java | 2 +- .../node/admin/noderepository/NodeRepository.java | 26 --- .../admin/noderepository/NodeRepositoryImpl.java | 172 ------------------- .../noderepository/bindings/GetAclResponse.java | 66 -------- .../noderepository/bindings/GetNodesResponse.java | 174 -------------------- .../bindings/NodeMessageResponse.java | 18 -- .../bindings/UpdateNodeAttributesRequestBody.java | 36 ---- .../bindings/UpdateNodeAttributesResponse.java | 24 --- .../node/admin/orchestrator/Orchestrator.java | 31 ---- .../admin/orchestrator/OrchestratorException.java | 9 - .../node/admin/orchestrator/OrchestratorImpl.java | 99 ----------- .../OrchestratorNotFoundException.java | 9 - .../noderepository/NodeRepositoryImplTest.java | 182 +++++++++++++++++++++ .../orchestrator/OrchestratorImplTest.java | 159 ++++++++++++++++++ .../node/admin/integrationTests/NodeRepoMock.java | 2 +- .../admin/integrationTests/OrchestratorMock.java | 2 +- .../admin/integrationTests/RunInContainerTest.java | 6 +- .../admin/maintenance/acl/AclMaintainerTest.java | 2 +- .../nodeadmin/NodeAdminStateUpdaterImplTest.java | 6 +- .../node/admin/nodeagent/NodeAgentImplTest.java | 4 +- .../noderepository/NodeRepositoryImplTest.java | 182 --------------------- .../admin/orchestrator/OrchestratorImplTest.java | 159 ------------------ 37 files changed, 1025 insertions(+), 1025 deletions(-) create mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeRepository.java create mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeRepositoryImpl.java create mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/GetAclResponse.java create mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/GetNodesResponse.java create mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/NodeMessageResponse.java create mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/UpdateNodeAttributesRequestBody.java create mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/UpdateNodeAttributesResponse.java create mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/Orchestrator.java create mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorException.java create mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorImpl.java create mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorNotFoundException.java delete mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepository.java delete mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImpl.java delete mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/GetAclResponse.java delete mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/GetNodesResponse.java delete mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/NodeMessageResponse.java delete mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/UpdateNodeAttributesRequestBody.java delete mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/UpdateNodeAttributesResponse.java delete mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/Orchestrator.java delete mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorException.java delete mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImpl.java delete mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorNotFoundException.java create mode 100644 node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeRepositoryImplTest.java create mode 100644 node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorImplTest.java delete mode 100644 node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImplTest.java delete mode 100644 node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImplTest.java (limited to 'node-admin') diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClients.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClients.java index 9686ddb0494..f52487c306f 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClients.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClients.java @@ -1,8 +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.configserver; -import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepository; -import com.yahoo.vespa.hosted.node.admin.orchestrator.Orchestrator; +import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository; +import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.Orchestrator; /** * @author freva diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClientsImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClientsImpl.java index ae1a5f31c87..951dc5b490b 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClientsImpl.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClientsImpl.java @@ -2,10 +2,10 @@ package com.yahoo.vespa.hosted.node.admin.configserver; import com.yahoo.vespa.hosted.node.admin.component.Environment; -import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepository; -import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepositoryImpl; -import com.yahoo.vespa.hosted.node.admin.orchestrator.Orchestrator; -import com.yahoo.vespa.hosted.node.admin.orchestrator.OrchestratorImpl; +import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository; +import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepositoryImpl; +import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.Orchestrator; +import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.OrchestratorImpl; import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.security.Security; diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeRepository.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeRepository.java new file mode 100644 index 00000000000..8012805f4d1 --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeRepository.java @@ -0,0 +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.hosted.node.admin.configserver.noderepository; + +import com.yahoo.vespa.hosted.node.admin.ContainerAclSpec; +import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec; +import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAttributes; + +import java.util.List; +import java.util.Optional; + +/** + * @author stiankri + */ +public interface NodeRepository { + List getContainersToRun(String baseHostName); + + Optional getContainerNodeSpec(String hostName); + + List getContainerAclSpecs(String hostName); + + void updateNodeAttributes(String hostName, NodeAttributes nodeAttributes); + + void markAsDirty(String hostName); + + void markNodeAvailableForNewAllocation(String hostName); +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeRepositoryImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeRepositoryImpl.java new file mode 100644 index 00000000000..f2152dffc0c --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeRepositoryImpl.java @@ -0,0 +1,172 @@ +// 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.node.admin.configserver.noderepository; + +import com.yahoo.vespa.hosted.node.admin.ContainerAclSpec; +import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec; +import com.yahoo.vespa.hosted.dockerapi.ContainerName; +import com.yahoo.vespa.hosted.dockerapi.DockerImage; +import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApi; +import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAttributes; +import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.bindings.GetAclResponse; +import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.bindings.GetNodesResponse; +import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.bindings.NodeMessageResponse; +import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.bindings.UpdateNodeAttributesRequestBody; +import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.bindings.UpdateNodeAttributesResponse; +import com.yahoo.vespa.hosted.node.admin.configserver.HttpException; +import com.yahoo.vespa.hosted.node.admin.util.PrefixLogger; +import com.yahoo.vespa.hosted.provision.Node; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * @author stiankri, dybis + */ +public class NodeRepositoryImpl implements NodeRepository { + private static final PrefixLogger NODE_ADMIN_LOGGER = PrefixLogger.getNodeAdminLogger(NodeRepositoryImpl.class); + + private final ConfigServerApi configServerApi; + + public NodeRepositoryImpl(ConfigServerApi configServerApi) { + this.configServerApi = configServerApi; + } + + @Override + public List getContainersToRun(String baseHostName) { + final GetNodesResponse nodesForHost = configServerApi.get( + "/nodes/v2/node/?parentHost=" + baseHostName + "&recursive=true", + GetNodesResponse.class); + + List nodes = new ArrayList<>(nodesForHost.nodes.size()); + for (GetNodesResponse.Node node : nodesForHost.nodes) { + ContainerNodeSpec nodeSpec; + try { + nodeSpec = createContainerNodeSpec(node); + } catch (IllegalArgumentException | NullPointerException e) { + NODE_ADMIN_LOGGER.warning("Bad node received from node repo when requesting children of the " + + baseHostName + " host: " + node, e); + continue; + } + nodes.add(nodeSpec); + } + return nodes; + } + + @Override + public Optional getContainerNodeSpec(String hostName) { + try { + GetNodesResponse.Node nodeResponse = configServerApi.get("/nodes/v2/node/" + hostName, + GetNodesResponse.Node.class); + if (nodeResponse == null) { + return Optional.empty(); + } + return Optional.of(createContainerNodeSpec(nodeResponse)); + } catch (HttpException.NotFoundException e) { + return Optional.empty(); + } + } + + @Override + public List getContainerAclSpecs(String hostName) { + try { + final String path = String.format("/nodes/v2/acl/%s?children=true", hostName); + final GetAclResponse response = configServerApi.get(path, GetAclResponse.class); + return response.trustedNodes.stream() + .map(node -> new ContainerAclSpec( + node.hostname, node.ipAddress, ContainerName.fromHostname(node.trustedBy))) + .collect(Collectors.toList()); + } catch (HttpException.NotFoundException e) { + return Collections.emptyList(); + } + } + + private static ContainerNodeSpec createContainerNodeSpec(GetNodesResponse.Node node) + throws IllegalArgumentException, NullPointerException { + Objects.requireNonNull(node.nodeState, "Unknown node state"); + Node.State nodeState = Node.State.valueOf(node.nodeState); + if (nodeState == Node.State.active) { + Objects.requireNonNull(node.wantedVespaVersion, "Unknown vespa version for active node"); + Objects.requireNonNull(node.wantedDockerImage, "Unknown docker image for active node"); + Objects.requireNonNull(node.wantedRestartGeneration, "Unknown wantedRestartGeneration for active node"); + Objects.requireNonNull(node.currentRestartGeneration, "Unknown currentRestartGeneration for active node"); + } + + String hostName = Objects.requireNonNull(node.hostname, "hostname is null"); + + ContainerNodeSpec.Owner owner = null; + if (node.owner != null) { + owner = new ContainerNodeSpec.Owner(node.owner.tenant, node.owner.application, node.owner.instance); + } + + ContainerNodeSpec.Membership membership = null; + if (node.membership != null) { + membership = new ContainerNodeSpec.Membership(node.membership.clusterType, node.membership.clusterId, + node.membership.group, node.membership.index, node.membership.retired); + } + + return new ContainerNodeSpec( + hostName, + Optional.ofNullable(node.wantedDockerImage).map(DockerImage::new), + Optional.ofNullable(node.currentDockerImage).map(DockerImage::new), + nodeState, + node.nodeType, + node.nodeFlavor, + node.nodeCanonicalFlavor, + Optional.ofNullable(node.wantedVespaVersion), + Optional.ofNullable(node.vespaVersion), + Optional.ofNullable(owner), + Optional.ofNullable(membership), + Optional.ofNullable(node.wantedRestartGeneration), + Optional.ofNullable(node.currentRestartGeneration), + Optional.ofNullable(node.wantedRebootGeneration), + Optional.ofNullable(node.currentRestartGeneration), + node.minCpuCores, + node.minMainMemoryAvailableGb, + node.minDiskAvailableGb, + node.fastDisk, + node.ipAddresses, + Optional.ofNullable(node.hardwareDivergence)); + } + + @Override + public void updateNodeAttributes(final String hostName, final NodeAttributes nodeAttributes) { + UpdateNodeAttributesResponse response = configServerApi.patch( + "/nodes/v2/node/" + hostName, + new UpdateNodeAttributesRequestBody(nodeAttributes), + UpdateNodeAttributesResponse.class); + + if (response.errorCode == null || response.errorCode.isEmpty()) { + return; + } + throw new RuntimeException("Unexpected message " + response.message + " " + response.errorCode); + } + + @Override + public void markAsDirty(String hostName) { + // This will never happen once the new allocation scheme is rolled out. + markNodeToState(hostName, Node.State.dirty.name()); + } + + @Override + public void markNodeAvailableForNewAllocation(final String hostName) { + // TODO replace with call to delete node when everything has been migrated to dynamic docker allocation + markNodeToState(hostName, "availablefornewallocations"); + } + + private void markNodeToState(String hostName, String state) { + NodeMessageResponse response = configServerApi.put( + "/nodes/v2/state/" + state + "/" + hostName, + Optional.empty(), /* body */ + NodeMessageResponse.class); + NODE_ADMIN_LOGGER.info(response.message); + + if (response.errorCode == null || response.errorCode.isEmpty()) { + return; + } + throw new RuntimeException("Unexpected message " + response.message + " " + response.errorCode); + } +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/GetAclResponse.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/GetAclResponse.java new file mode 100644 index 00000000000..b7762cf6aa9 --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/GetAclResponse.java @@ -0,0 +1,66 @@ +// 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.node.admin.configserver.noderepository.bindings; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Collections; +import java.util.List; + +/** + * This class represents a response from the /nodes/v2/acl/ API. + * + * @author mpolden + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class GetAclResponse { + + @JsonProperty("trustedNodes") + public final List trustedNodes; + + @JsonProperty("trustedNetworks") + public final List trustedNetworks; + + @JsonCreator + public GetAclResponse(@JsonProperty("trustedNodes") List trustedNodes, + @JsonProperty("trustedNetworks") List trustedNetworks) { + this.trustedNodes = trustedNodes == null ? Collections.emptyList() : trustedNodes; + this.trustedNetworks = trustedNetworks == null ? Collections.emptyList() : trustedNetworks; + } + + public static class Node { + + @JsonProperty("hostname") + public final String hostname; + + @JsonProperty("ipAddress") + public final String ipAddress; + + @JsonProperty("trustedBy") + public final String trustedBy; + + @JsonCreator + public Node(@JsonProperty("hostname") String hostname, @JsonProperty("ipAddress") String ipAddress, + @JsonProperty("trustedBy") String trustedBy) { + this.hostname = hostname; + this.ipAddress = ipAddress; + this.trustedBy = trustedBy; + } + } + + public static class Network { + + @JsonProperty("network") + public final String network; + + @JsonProperty("trustedBy") + public final String trustedBy; + + @JsonCreator + public Network(@JsonProperty("network") String network, @JsonProperty("trustedBy") String trustedBy) { + this.network = network; + this.trustedBy = trustedBy; + } + } +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/GetNodesResponse.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/GetNodesResponse.java new file mode 100644 index 00000000000..c94b3836100 --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/GetNodesResponse.java @@ -0,0 +1,174 @@ +// 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.node.admin.configserver.noderepository.bindings; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * This class represents a response from the /nodes/v2/node/ API. It is designed to be + * usable by any module, by not depending itself on any module-specific classes. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class GetNodesResponse { + + public final List nodes; + + @JsonCreator + public GetNodesResponse(@JsonProperty("nodes") List nodes) { + this.nodes = Collections.unmodifiableList(nodes); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Node { + + public final String hostname; + public final String wantedDockerImage; + public final String currentDockerImage; + public final String nodeState; + public final String nodeType; + public final String nodeFlavor; + public final String nodeCanonicalFlavor; + public final String wantedVespaVersion; + public final String vespaVersion; + public final Owner owner; + public final Membership membership; + public final Long wantedRestartGeneration; + public final Long currentRestartGeneration; + public final Long wantedRebootGeneration; + public final Long currentRebootGeneration; + public final Double minCpuCores; + public final Double minMainMemoryAvailableGb; + public final Double minDiskAvailableGb; + public final Boolean fastDisk; + public final Set ipAddresses; + public final String hardwareDivergence; + + @JsonCreator + public Node(@JsonProperty("id") String hostname, + @JsonProperty("wantedDockerImage") String wantedDockerImage, + @JsonProperty("currentDockerImage") String currentDockerImage, + @JsonProperty("state") String nodeState, + @JsonProperty("type") String nodeType, + @JsonProperty("flavor") String nodeFlavor, + @JsonProperty("canonicalFlavor") String nodeCanonicalFlavor, + @JsonProperty("wantedVespaVersion") String wantedVespaVersion, + @JsonProperty("vespaVersion") String vespaVersion, + @JsonProperty("owner") Owner owner, + @JsonProperty("membership") Membership membership, + @JsonProperty("restartGeneration") Long wantedRestartGeneration, + @JsonProperty("currentRestartGeneration") Long currentRestartGeneration, + @JsonProperty("rebootGeneration") Long wantedRebootGeneration, + @JsonProperty("currentRebootGeneration") Long currentRebootGeneration, + @JsonProperty("minCpuCores") Double minCpuCores, + @JsonProperty("minMainMemoryAvailableGb") Double minMainMemoryAvailableGb, + @JsonProperty("minDiskAvailableGb") Double minDiskAvailableGb, + @JsonProperty("fastDisk") Boolean fastDisk, + @JsonProperty("ipAddresses") Set ipAddresses, + @JsonProperty("hardwareDivergence") String hardwareDivergence) { + this.hostname = hostname; + this.wantedDockerImage = wantedDockerImage; + this.currentDockerImage = currentDockerImage; + this.nodeState = nodeState; + this.nodeType = nodeType; + this.nodeFlavor = nodeFlavor; + this.nodeCanonicalFlavor = nodeCanonicalFlavor; + this.wantedVespaVersion = wantedVespaVersion; + this.vespaVersion = vespaVersion; + this.owner = owner; + this.membership = membership; + this.wantedRestartGeneration = wantedRestartGeneration; + this.currentRestartGeneration = currentRestartGeneration; + this.wantedRebootGeneration = wantedRebootGeneration; + this.currentRebootGeneration = currentRebootGeneration; + this.minCpuCores = minCpuCores; + this.minMainMemoryAvailableGb = minMainMemoryAvailableGb; + this.minDiskAvailableGb = minDiskAvailableGb; + this.fastDisk = fastDisk; + this.ipAddresses = ipAddresses; + this.hardwareDivergence = hardwareDivergence; + } + + public String toString() { + return "Node {" + + " containerHostname = " + hostname + + " wantedDockerImage = " + wantedDockerImage + + " currentDockerImage = " + currentDockerImage + + " nodeState = " + nodeState + + " nodeType = " + nodeType + + " nodeFlavor = " + nodeFlavor + + " wantedVespaVersion = " + wantedVespaVersion + + " vespaVersion = " + vespaVersion + + " owner = " + owner + + " membership = " + membership + + " wantedRestartGeneration = " + wantedRestartGeneration + + " currentRestartGeneration = " + currentRestartGeneration + + " wantedRebootGeneration = " + wantedRebootGeneration + + " currentRebootGeneration = " + currentRebootGeneration + + " minCpuCores = " + minCpuCores + + " minMainMemoryAvailableGb = " + minMainMemoryAvailableGb + + " minDiskAvailableGb = " + minDiskAvailableGb + + " }"; + } + + + public static class Owner { + public final String tenant; + public final String application; + public final String instance; + + public Owner( + @JsonProperty("tenant") String tenant, + @JsonProperty("application") String application, + @JsonProperty("instance") String instance) { + this.tenant = tenant; + this.application = application; + this.instance = instance; + } + + public String toString() { + return "Owner {" + + " tenant = " + tenant + + " application = " + application + + " instance = " + instance + + " }"; + } + } + + public static class Membership { + public final String clusterType; + public final String clusterId; + public final String group; + public final int index; + public final boolean retired; + + public Membership( + @JsonProperty("clustertype") String clusterType, + @JsonProperty("clusterid") String clusterId, + @JsonProperty("group") String group, + @JsonProperty("index") int index, + @JsonProperty("retired") boolean retired) { + this.clusterType = clusterType; + this.clusterId = clusterId; + this.group = group; + this.index = index; + this.retired = retired; + } + + @Override + public String toString() { + return "Membership {" + + " clusterType = " + clusterType + + " clusterId = " + clusterId + + " group = " + group + + " index = " + index + + " retired = " + retired + + " }"; + } + } + } +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/NodeMessageResponse.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/NodeMessageResponse.java new file mode 100644 index 00000000000..0f5e896c290 --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/NodeMessageResponse.java @@ -0,0 +1,18 @@ +// 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.node.admin.configserver.noderepository.bindings; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Response from PUT /nodes/v2/state/ call to node-repository. + * + * @author dybis + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class NodeMessageResponse { + @JsonProperty("message") + public String message; + @JsonProperty("error-code") + public String errorCode; +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/UpdateNodeAttributesRequestBody.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/UpdateNodeAttributesRequestBody.java new file mode 100644 index 00000000000..28605bc3a8d --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/UpdateNodeAttributesRequestBody.java @@ -0,0 +1,36 @@ +// 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.node.admin.configserver.noderepository.bindings; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAttributes; + +/** + * Automagically handles (de)serialization based on 1:1 message fields and identifier names. + * Instances of this class should serialize as: + *
+ *   {
+ *     "currentRestartGeneration": 42
+ *   }
+ * 
+ * + * @author bakksjo + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class UpdateNodeAttributesRequestBody { + public Long currentRestartGeneration; + public Long currentRebootGeneration; + public String currentDockerImage; + public String currentVespaVersion; + public String hardwareDivergence; + + public UpdateNodeAttributesRequestBody(NodeAttributes nodeAttributes) { + if (nodeAttributes.getDockerImage() != null) { + this.currentDockerImage = nodeAttributes.getDockerImage().asString(); + } + + this.currentRestartGeneration = nodeAttributes.getRestartGeneration(); + this.currentVespaVersion = nodeAttributes.getVespaVersion(); + this.currentRebootGeneration = nodeAttributes.getRebootGeneration(); + this.hardwareDivergence = nodeAttributes.getHardwareDivergence(); + } +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/UpdateNodeAttributesResponse.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/UpdateNodeAttributesResponse.java new file mode 100644 index 00000000000..80c90e8311f --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/UpdateNodeAttributesResponse.java @@ -0,0 +1,24 @@ +// 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.node.admin.configserver.noderepository.bindings; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Automagically handles (de)serialization based on 1:1 message fields and identifier names. + * Deserializes JSON strings on the form: + *
+ *   {
+ *     "message": "Updated host.com"
+ *   }
+ * 
+ * + * @author bakksjo + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class UpdateNodeAttributesResponse { + @JsonProperty("message") + public String message; + @JsonProperty("error-code") + public String errorCode; +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/Orchestrator.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/Orchestrator.java new file mode 100644 index 00000000000..b5d41b7fbb4 --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/Orchestrator.java @@ -0,0 +1,31 @@ +// 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.node.admin.configserver.orchestrator; + +import java.util.List; + +/** + * Abstraction for communicating with Orchestrator. + * + * @author bakksjo + */ +public interface Orchestrator { + /** + * Invokes orchestrator suspend of a host. + * @throws OrchestratorException if suspend was denied. + * @throws OrchestratorNotFoundException if host is unknown to the orchestrator + */ + void suspend(String hostName); + + /** + * Invokes orchestrator resume of a host. + * @throws OrchestratorException if resume was denied + * @throws OrchestratorNotFoundException if host is unknown to the orchestrator + */ + void resume(String hostName); + + /** + * Invokes orchestrator suspend hosts. + * @throws OrchestratorException if batch suspend was denied. + */ + void suspend(String parentHostName, List hostNames); +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorException.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorException.java new file mode 100644 index 00000000000..fe19da0c41c --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorException.java @@ -0,0 +1,9 @@ +// 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.node.admin.configserver.orchestrator; + +@SuppressWarnings("serial") +public class OrchestratorException extends RuntimeException { + public OrchestratorException(String message) { + super(message); + } +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorImpl.java new file mode 100644 index 00000000000..0409004c6e6 --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorImpl.java @@ -0,0 +1,99 @@ +// 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.node.admin.configserver.orchestrator; + +import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApi; + +import com.yahoo.vespa.hosted.node.admin.configserver.HttpException; +import com.yahoo.vespa.orchestrator.restapi.HostApi; +import com.yahoo.vespa.orchestrator.restapi.HostSuspensionApi; +import com.yahoo.vespa.orchestrator.restapi.wire.BatchHostSuspendRequest; +import com.yahoo.vespa.orchestrator.restapi.wire.BatchOperationResult; +import com.yahoo.vespa.orchestrator.restapi.wire.UpdateHostResponse; +import java.util.List; +import java.util.Optional; + +/** + * @author stiankri + * @author bakksjo + * @author dybis + */ +public class OrchestratorImpl implements Orchestrator { + // TODO: Find a way to avoid duplicating this (present in orchestrator's services.xml also). + private static final String ORCHESTRATOR_PATH_PREFIX = "/orchestrator"; + static final String ORCHESTRATOR_PATH_PREFIX_HOST_API + = ORCHESTRATOR_PATH_PREFIX + HostApi.PATH_PREFIX; + static final String ORCHESTRATOR_PATH_PREFIX_HOST_SUSPENSION_API + = ORCHESTRATOR_PATH_PREFIX + HostSuspensionApi.PATH_PREFIX; + + private final ConfigServerApi configServerApi; + + public OrchestratorImpl(ConfigServerApi configServerApi) { + this.configServerApi = configServerApi; + } + + @Override + public void suspend(final String hostName) { + UpdateHostResponse response; + try { + response = configServerApi.put(getSuspendPath(hostName), + Optional.empty(), /* body */ + UpdateHostResponse.class); + } catch (HttpException.NotFoundException n) { + throw new OrchestratorNotFoundException("Failed to suspend " + hostName + ", host not found"); + } catch (HttpException e) { + throw new OrchestratorException("Failed to suspend " + hostName + ": " + + e.toString()); + } catch (Exception e) { + throw new RuntimeException("Got error on suspend", e); + } + + Optional.ofNullable(response.reason()).ifPresent(reason -> { + throw new OrchestratorException(reason.message()); + }); + } + + @Override + public void suspend(String parentHostName, List hostNames) { + final BatchOperationResult batchOperationResult; + try { + batchOperationResult = configServerApi.put( + ORCHESTRATOR_PATH_PREFIX_HOST_SUSPENSION_API, + Optional.of(new BatchHostSuspendRequest(parentHostName, hostNames)), + BatchOperationResult.class); + } catch (HttpException e) { + throw new OrchestratorException("Failed to batch suspend for " + + parentHostName + ": " + e.toString()); + } catch (Exception e) { + throw new RuntimeException("Got error on batch suspend for " + parentHostName + ", with nodes " + hostNames, e); + } + + batchOperationResult.getFailureReason().ifPresent(reason -> { + throw new OrchestratorException(reason); + }); + } + + @Override + public void resume(final String hostName) { + UpdateHostResponse response; + try { + String path = getSuspendPath(hostName); + response = configServerApi.delete(path, UpdateHostResponse.class); + } catch (HttpException.NotFoundException n) { + throw new OrchestratorNotFoundException("Failed to resume " + hostName + ", host not found"); + } catch (HttpException e) { + throw new OrchestratorException("Failed to suspend " + hostName + ": " + + e.toString()); + } catch (Exception e) { + throw new RuntimeException("Got error on resume", e); + } + + Optional.ofNullable(response.reason()).ifPresent(reason -> { + throw new OrchestratorException(reason.message()); + }); + } + + private String getSuspendPath(String hostName) { + return ORCHESTRATOR_PATH_PREFIX_HOST_API + "/" + hostName + "/suspended"; + } + +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorNotFoundException.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorNotFoundException.java new file mode 100644 index 00000000000..ac39f7c3280 --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorNotFoundException.java @@ -0,0 +1,9 @@ +// 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.node.admin.configserver.orchestrator; + +@SuppressWarnings("serial") +public class OrchestratorNotFoundException extends OrchestratorException { + public OrchestratorNotFoundException(String message) { + super(message); + } +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainer.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainer.java index 2947ef68ba4..a453ea46ffd 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainer.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainer.java @@ -10,7 +10,7 @@ import com.yahoo.vespa.hosted.node.admin.maintenance.acl.iptables.Chain; import com.yahoo.vespa.hosted.node.admin.maintenance.acl.iptables.Command; import com.yahoo.vespa.hosted.node.admin.maintenance.acl.iptables.FlushCommand; import com.yahoo.vespa.hosted.node.admin.maintenance.acl.iptables.PolicyCommand; -import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepository; +import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository; import com.yahoo.vespa.hosted.node.admin.util.PrefixLogger; import java.util.HashMap; diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterImpl.java index ea8c29a561d..c989ca8d67d 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterImpl.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterImpl.java @@ -10,7 +10,7 @@ import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec; import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerClients; import com.yahoo.vespa.hosted.node.admin.maintenance.StorageMaintainer; import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAttributes; -import com.yahoo.vespa.hosted.node.admin.orchestrator.OrchestratorException; +import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.OrchestratorException; import com.yahoo.vespa.hosted.node.admin.provider.NodeAdminStateUpdater; import com.yahoo.vespa.hosted.node.admin.configserver.HttpException; import com.yahoo.vespa.hosted.provision.Node; 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 3308272581a..c882b92db98 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 @@ -18,7 +18,7 @@ import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec; import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerClients; import com.yahoo.vespa.hosted.node.admin.docker.DockerOperations; import com.yahoo.vespa.hosted.node.admin.maintenance.StorageMaintainer; -import com.yahoo.vespa.hosted.node.admin.orchestrator.OrchestratorException; +import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.OrchestratorException; import com.yahoo.vespa.hosted.node.admin.component.Environment; import com.yahoo.vespa.hosted.node.admin.util.PrefixLogger; import com.yahoo.vespa.hosted.provision.Node; diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepository.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepository.java deleted file mode 100644 index be48eec9b2d..00000000000 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepository.java +++ /dev/null @@ -1,26 +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.node.admin.noderepository; - -import com.yahoo.vespa.hosted.node.admin.ContainerAclSpec; -import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec; -import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAttributes; - -import java.util.List; -import java.util.Optional; - -/** - * @author stiankri - */ -public interface NodeRepository { - List getContainersToRun(String baseHostName); - - Optional getContainerNodeSpec(String hostName); - - List getContainerAclSpecs(String hostName); - - void updateNodeAttributes(String hostName, NodeAttributes nodeAttributes); - - void markAsDirty(String hostName); - - void markNodeAvailableForNewAllocation(String hostName); -} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImpl.java deleted file mode 100644 index b73ec2fe7dc..00000000000 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImpl.java +++ /dev/null @@ -1,172 +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.node.admin.noderepository; - -import com.yahoo.vespa.hosted.node.admin.ContainerAclSpec; -import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec; -import com.yahoo.vespa.hosted.dockerapi.ContainerName; -import com.yahoo.vespa.hosted.dockerapi.DockerImage; -import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApi; -import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAttributes; -import com.yahoo.vespa.hosted.node.admin.noderepository.bindings.GetAclResponse; -import com.yahoo.vespa.hosted.node.admin.noderepository.bindings.GetNodesResponse; -import com.yahoo.vespa.hosted.node.admin.noderepository.bindings.NodeMessageResponse; -import com.yahoo.vespa.hosted.node.admin.noderepository.bindings.UpdateNodeAttributesRequestBody; -import com.yahoo.vespa.hosted.node.admin.noderepository.bindings.UpdateNodeAttributesResponse; -import com.yahoo.vespa.hosted.node.admin.configserver.HttpException; -import com.yahoo.vespa.hosted.node.admin.util.PrefixLogger; -import com.yahoo.vespa.hosted.provision.Node; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Collectors; - -/** - * @author stiankri, dybis - */ -public class NodeRepositoryImpl implements NodeRepository { - private static final PrefixLogger NODE_ADMIN_LOGGER = PrefixLogger.getNodeAdminLogger(NodeRepositoryImpl.class); - - private final ConfigServerApi configServerApi; - - public NodeRepositoryImpl(ConfigServerApi configServerApi) { - this.configServerApi = configServerApi; - } - - @Override - public List getContainersToRun(String baseHostName) { - final GetNodesResponse nodesForHost = configServerApi.get( - "/nodes/v2/node/?parentHost=" + baseHostName + "&recursive=true", - GetNodesResponse.class); - - List nodes = new ArrayList<>(nodesForHost.nodes.size()); - for (GetNodesResponse.Node node : nodesForHost.nodes) { - ContainerNodeSpec nodeSpec; - try { - nodeSpec = createContainerNodeSpec(node); - } catch (IllegalArgumentException | NullPointerException e) { - NODE_ADMIN_LOGGER.warning("Bad node received from node repo when requesting children of the " - + baseHostName + " host: " + node, e); - continue; - } - nodes.add(nodeSpec); - } - return nodes; - } - - @Override - public Optional getContainerNodeSpec(String hostName) { - try { - GetNodesResponse.Node nodeResponse = configServerApi.get("/nodes/v2/node/" + hostName, - GetNodesResponse.Node.class); - if (nodeResponse == null) { - return Optional.empty(); - } - return Optional.of(createContainerNodeSpec(nodeResponse)); - } catch (HttpException.NotFoundException e) { - return Optional.empty(); - } - } - - @Override - public List getContainerAclSpecs(String hostName) { - try { - final String path = String.format("/nodes/v2/acl/%s?children=true", hostName); - final GetAclResponse response = configServerApi.get(path, GetAclResponse.class); - return response.trustedNodes.stream() - .map(node -> new ContainerAclSpec( - node.hostname, node.ipAddress, ContainerName.fromHostname(node.trustedBy))) - .collect(Collectors.toList()); - } catch (HttpException.NotFoundException e) { - return Collections.emptyList(); - } - } - - private static ContainerNodeSpec createContainerNodeSpec(GetNodesResponse.Node node) - throws IllegalArgumentException, NullPointerException { - Objects.requireNonNull(node.nodeState, "Unknown node state"); - Node.State nodeState = Node.State.valueOf(node.nodeState); - if (nodeState == Node.State.active) { - Objects.requireNonNull(node.wantedVespaVersion, "Unknown vespa version for active node"); - Objects.requireNonNull(node.wantedDockerImage, "Unknown docker image for active node"); - Objects.requireNonNull(node.wantedRestartGeneration, "Unknown wantedRestartGeneration for active node"); - Objects.requireNonNull(node.currentRestartGeneration, "Unknown currentRestartGeneration for active node"); - } - - String hostName = Objects.requireNonNull(node.hostname, "hostname is null"); - - ContainerNodeSpec.Owner owner = null; - if (node.owner != null) { - owner = new ContainerNodeSpec.Owner(node.owner.tenant, node.owner.application, node.owner.instance); - } - - ContainerNodeSpec.Membership membership = null; - if (node.membership != null) { - membership = new ContainerNodeSpec.Membership(node.membership.clusterType, node.membership.clusterId, - node.membership.group, node.membership.index, node.membership.retired); - } - - return new ContainerNodeSpec( - hostName, - Optional.ofNullable(node.wantedDockerImage).map(DockerImage::new), - Optional.ofNullable(node.currentDockerImage).map(DockerImage::new), - nodeState, - node.nodeType, - node.nodeFlavor, - node.nodeCanonicalFlavor, - Optional.ofNullable(node.wantedVespaVersion), - Optional.ofNullable(node.vespaVersion), - Optional.ofNullable(owner), - Optional.ofNullable(membership), - Optional.ofNullable(node.wantedRestartGeneration), - Optional.ofNullable(node.currentRestartGeneration), - Optional.ofNullable(node.wantedRebootGeneration), - Optional.ofNullable(node.currentRestartGeneration), - node.minCpuCores, - node.minMainMemoryAvailableGb, - node.minDiskAvailableGb, - node.fastDisk, - node.ipAddresses, - Optional.ofNullable(node.hardwareDivergence)); - } - - @Override - public void updateNodeAttributes(final String hostName, final NodeAttributes nodeAttributes) { - UpdateNodeAttributesResponse response = configServerApi.patch( - "/nodes/v2/node/" + hostName, - new UpdateNodeAttributesRequestBody(nodeAttributes), - UpdateNodeAttributesResponse.class); - - if (response.errorCode == null || response.errorCode.isEmpty()) { - return; - } - throw new RuntimeException("Unexpected message " + response.message + " " + response.errorCode); - } - - @Override - public void markAsDirty(String hostName) { - // This will never happen once the new allocation scheme is rolled out. - markNodeToState(hostName, Node.State.dirty.name()); - } - - @Override - public void markNodeAvailableForNewAllocation(final String hostName) { - // TODO replace with call to delete node when everything has been migrated to dynamic docker allocation - markNodeToState(hostName, "availablefornewallocations"); - } - - private void markNodeToState(String hostName, String state) { - NodeMessageResponse response = configServerApi.put( - "/nodes/v2/state/" + state + "/" + hostName, - Optional.empty(), /* body */ - NodeMessageResponse.class); - NODE_ADMIN_LOGGER.info(response.message); - - if (response.errorCode == null || response.errorCode.isEmpty()) { - return; - } - throw new RuntimeException("Unexpected message " + response.message + " " + response.errorCode); - } -} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/GetAclResponse.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/GetAclResponse.java deleted file mode 100644 index 254ab5fa3ba..00000000000 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/GetAclResponse.java +++ /dev/null @@ -1,66 +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.node.admin.noderepository.bindings; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.Collections; -import java.util.List; - -/** - * This class represents a response from the /nodes/v2/acl/ API. - * - * @author mpolden - */ -@JsonIgnoreProperties(ignoreUnknown = true) -public class GetAclResponse { - - @JsonProperty("trustedNodes") - public final List trustedNodes; - - @JsonProperty("trustedNetworks") - public final List trustedNetworks; - - @JsonCreator - public GetAclResponse(@JsonProperty("trustedNodes") List trustedNodes, - @JsonProperty("trustedNetworks") List trustedNetworks) { - this.trustedNodes = trustedNodes == null ? Collections.emptyList() : trustedNodes; - this.trustedNetworks = trustedNetworks == null ? Collections.emptyList() : trustedNetworks; - } - - public static class Node { - - @JsonProperty("hostname") - public final String hostname; - - @JsonProperty("ipAddress") - public final String ipAddress; - - @JsonProperty("trustedBy") - public final String trustedBy; - - @JsonCreator - public Node(@JsonProperty("hostname") String hostname, @JsonProperty("ipAddress") String ipAddress, - @JsonProperty("trustedBy") String trustedBy) { - this.hostname = hostname; - this.ipAddress = ipAddress; - this.trustedBy = trustedBy; - } - } - - public static class Network { - - @JsonProperty("network") - public final String network; - - @JsonProperty("trustedBy") - public final String trustedBy; - - @JsonCreator - public Network(@JsonProperty("network") String network, @JsonProperty("trustedBy") String trustedBy) { - this.network = network; - this.trustedBy = trustedBy; - } - } -} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/GetNodesResponse.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/GetNodesResponse.java deleted file mode 100644 index ce31c3c1b4c..00000000000 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/GetNodesResponse.java +++ /dev/null @@ -1,174 +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.node.admin.noderepository.bindings; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.Collections; -import java.util.List; -import java.util.Set; - -/** - * This class represents a response from the /nodes/v2/node/ API. It is designed to be - * usable by any module, by not depending itself on any module-specific classes. - */ -@JsonIgnoreProperties(ignoreUnknown = true) -public class GetNodesResponse { - - public final List nodes; - - @JsonCreator - public GetNodesResponse(@JsonProperty("nodes") List nodes) { - this.nodes = Collections.unmodifiableList(nodes); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Node { - - public final String hostname; - public final String wantedDockerImage; - public final String currentDockerImage; - public final String nodeState; - public final String nodeType; - public final String nodeFlavor; - public final String nodeCanonicalFlavor; - public final String wantedVespaVersion; - public final String vespaVersion; - public final Owner owner; - public final Membership membership; - public final Long wantedRestartGeneration; - public final Long currentRestartGeneration; - public final Long wantedRebootGeneration; - public final Long currentRebootGeneration; - public final Double minCpuCores; - public final Double minMainMemoryAvailableGb; - public final Double minDiskAvailableGb; - public final Boolean fastDisk; - public final Set ipAddresses; - public final String hardwareDivergence; - - @JsonCreator - public Node(@JsonProperty("id") String hostname, - @JsonProperty("wantedDockerImage") String wantedDockerImage, - @JsonProperty("currentDockerImage") String currentDockerImage, - @JsonProperty("state") String nodeState, - @JsonProperty("type") String nodeType, - @JsonProperty("flavor") String nodeFlavor, - @JsonProperty("canonicalFlavor") String nodeCanonicalFlavor, - @JsonProperty("wantedVespaVersion") String wantedVespaVersion, - @JsonProperty("vespaVersion") String vespaVersion, - @JsonProperty("owner") Owner owner, - @JsonProperty("membership") Membership membership, - @JsonProperty("restartGeneration") Long wantedRestartGeneration, - @JsonProperty("currentRestartGeneration") Long currentRestartGeneration, - @JsonProperty("rebootGeneration") Long wantedRebootGeneration, - @JsonProperty("currentRebootGeneration") Long currentRebootGeneration, - @JsonProperty("minCpuCores") Double minCpuCores, - @JsonProperty("minMainMemoryAvailableGb") Double minMainMemoryAvailableGb, - @JsonProperty("minDiskAvailableGb") Double minDiskAvailableGb, - @JsonProperty("fastDisk") Boolean fastDisk, - @JsonProperty("ipAddresses") Set ipAddresses, - @JsonProperty("hardwareDivergence") String hardwareDivergence) { - this.hostname = hostname; - this.wantedDockerImage = wantedDockerImage; - this.currentDockerImage = currentDockerImage; - this.nodeState = nodeState; - this.nodeType = nodeType; - this.nodeFlavor = nodeFlavor; - this.nodeCanonicalFlavor = nodeCanonicalFlavor; - this.wantedVespaVersion = wantedVespaVersion; - this.vespaVersion = vespaVersion; - this.owner = owner; - this.membership = membership; - this.wantedRestartGeneration = wantedRestartGeneration; - this.currentRestartGeneration = currentRestartGeneration; - this.wantedRebootGeneration = wantedRebootGeneration; - this.currentRebootGeneration = currentRebootGeneration; - this.minCpuCores = minCpuCores; - this.minMainMemoryAvailableGb = minMainMemoryAvailableGb; - this.minDiskAvailableGb = minDiskAvailableGb; - this.fastDisk = fastDisk; - this.ipAddresses = ipAddresses; - this.hardwareDivergence = hardwareDivergence; - } - - public String toString() { - return "Node {" - + " containerHostname = " + hostname - + " wantedDockerImage = " + wantedDockerImage - + " currentDockerImage = " + currentDockerImage - + " nodeState = " + nodeState - + " nodeType = " + nodeType - + " nodeFlavor = " + nodeFlavor - + " wantedVespaVersion = " + wantedVespaVersion - + " vespaVersion = " + vespaVersion - + " owner = " + owner - + " membership = " + membership - + " wantedRestartGeneration = " + wantedRestartGeneration - + " currentRestartGeneration = " + currentRestartGeneration - + " wantedRebootGeneration = " + wantedRebootGeneration - + " currentRebootGeneration = " + currentRebootGeneration - + " minCpuCores = " + minCpuCores - + " minMainMemoryAvailableGb = " + minMainMemoryAvailableGb - + " minDiskAvailableGb = " + minDiskAvailableGb - + " }"; - } - - - public static class Owner { - public final String tenant; - public final String application; - public final String instance; - - public Owner( - @JsonProperty("tenant") String tenant, - @JsonProperty("application") String application, - @JsonProperty("instance") String instance) { - this.tenant = tenant; - this.application = application; - this.instance = instance; - } - - public String toString() { - return "Owner {" + - " tenant = " + tenant + - " application = " + application + - " instance = " + instance + - " }"; - } - } - - public static class Membership { - public final String clusterType; - public final String clusterId; - public final String group; - public final int index; - public final boolean retired; - - public Membership( - @JsonProperty("clustertype") String clusterType, - @JsonProperty("clusterid") String clusterId, - @JsonProperty("group") String group, - @JsonProperty("index") int index, - @JsonProperty("retired") boolean retired) { - this.clusterType = clusterType; - this.clusterId = clusterId; - this.group = group; - this.index = index; - this.retired = retired; - } - - @Override - public String toString() { - return "Membership {" + - " clusterType = " + clusterType + - " clusterId = " + clusterId + - " group = " + group + - " index = " + index + - " retired = " + retired + - " }"; - } - } - } -} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/NodeMessageResponse.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/NodeMessageResponse.java deleted file mode 100644 index b8c903f863d..00000000000 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/NodeMessageResponse.java +++ /dev/null @@ -1,18 +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.node.admin.noderepository.bindings; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Response from PUT /nodes/v2/state/ call to node-repository. - * - * @author dybis - */ -@JsonIgnoreProperties(ignoreUnknown = true) -public class NodeMessageResponse { - @JsonProperty("message") - public String message; - @JsonProperty("error-code") - public String errorCode; -} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/UpdateNodeAttributesRequestBody.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/UpdateNodeAttributesRequestBody.java deleted file mode 100644 index 7acd94a2947..00000000000 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/UpdateNodeAttributesRequestBody.java +++ /dev/null @@ -1,36 +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.node.admin.noderepository.bindings; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAttributes; - -/** - * Automagically handles (de)serialization based on 1:1 message fields and identifier names. - * Instances of this class should serialize as: - *
- *   {
- *     "currentRestartGeneration": 42
- *   }
- * 
- * - * @author bakksjo - */ -@JsonInclude(JsonInclude.Include.NON_NULL) -public class UpdateNodeAttributesRequestBody { - public Long currentRestartGeneration; - public Long currentRebootGeneration; - public String currentDockerImage; - public String currentVespaVersion; - public String hardwareDivergence; - - public UpdateNodeAttributesRequestBody(NodeAttributes nodeAttributes) { - if (nodeAttributes.getDockerImage() != null) { - this.currentDockerImage = nodeAttributes.getDockerImage().asString(); - } - - this.currentRestartGeneration = nodeAttributes.getRestartGeneration(); - this.currentVespaVersion = nodeAttributes.getVespaVersion(); - this.currentRebootGeneration = nodeAttributes.getRebootGeneration(); - this.hardwareDivergence = nodeAttributes.getHardwareDivergence(); - } -} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/UpdateNodeAttributesResponse.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/UpdateNodeAttributesResponse.java deleted file mode 100644 index a11f3bf46a9..00000000000 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/UpdateNodeAttributesResponse.java +++ /dev/null @@ -1,24 +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.node.admin.noderepository.bindings; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Automagically handles (de)serialization based on 1:1 message fields and identifier names. - * Deserializes JSON strings on the form: - *
- *   {
- *     "message": "Updated host.com"
- *   }
- * 
- * - * @author bakksjo - */ -@JsonIgnoreProperties(ignoreUnknown = true) -public class UpdateNodeAttributesResponse { - @JsonProperty("message") - public String message; - @JsonProperty("error-code") - public String errorCode; -} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/Orchestrator.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/Orchestrator.java deleted file mode 100644 index d98378e194c..00000000000 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/Orchestrator.java +++ /dev/null @@ -1,31 +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.node.admin.orchestrator; - -import java.util.List; - -/** - * Abstraction for communicating with Orchestrator. - * - * @author bakksjo - */ -public interface Orchestrator { - /** - * Invokes orchestrator suspend of a host. - * @throws OrchestratorException if suspend was denied. - * @throws OrchestratorNotFoundException if host is unknown to the orchestrator - */ - void suspend(String hostName); - - /** - * Invokes orchestrator resume of a host. - * @throws OrchestratorException if resume was denied - * @throws OrchestratorNotFoundException if host is unknown to the orchestrator - */ - void resume(String hostName); - - /** - * Invokes orchestrator suspend hosts. - * @throws OrchestratorException if batch suspend was denied. - */ - void suspend(String parentHostName, List hostNames); -} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorException.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorException.java deleted file mode 100644 index 51d542622da..00000000000 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorException.java +++ /dev/null @@ -1,9 +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.node.admin.orchestrator; - -@SuppressWarnings("serial") -public class OrchestratorException extends RuntimeException { - public OrchestratorException(String message) { - super(message); - } -} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImpl.java deleted file mode 100644 index 50cfe0a17ee..00000000000 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImpl.java +++ /dev/null @@ -1,99 +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.node.admin.orchestrator; - -import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApi; - -import com.yahoo.vespa.hosted.node.admin.configserver.HttpException; -import com.yahoo.vespa.orchestrator.restapi.HostApi; -import com.yahoo.vespa.orchestrator.restapi.HostSuspensionApi; -import com.yahoo.vespa.orchestrator.restapi.wire.BatchHostSuspendRequest; -import com.yahoo.vespa.orchestrator.restapi.wire.BatchOperationResult; -import com.yahoo.vespa.orchestrator.restapi.wire.UpdateHostResponse; -import java.util.List; -import java.util.Optional; - -/** - * @author stiankri - * @author bakksjo - * @author dybis - */ -public class OrchestratorImpl implements Orchestrator { - // TODO: Find a way to avoid duplicating this (present in orchestrator's services.xml also). - private static final String ORCHESTRATOR_PATH_PREFIX = "/orchestrator"; - static final String ORCHESTRATOR_PATH_PREFIX_HOST_API - = ORCHESTRATOR_PATH_PREFIX + HostApi.PATH_PREFIX; - static final String ORCHESTRATOR_PATH_PREFIX_HOST_SUSPENSION_API - = ORCHESTRATOR_PATH_PREFIX + HostSuspensionApi.PATH_PREFIX; - - private final ConfigServerApi configServerApi; - - public OrchestratorImpl(ConfigServerApi configServerApi) { - this.configServerApi = configServerApi; - } - - @Override - public void suspend(final String hostName) { - UpdateHostResponse response; - try { - response = configServerApi.put(getSuspendPath(hostName), - Optional.empty(), /* body */ - UpdateHostResponse.class); - } catch (HttpException.NotFoundException n) { - throw new OrchestratorNotFoundException("Failed to suspend " + hostName + ", host not found"); - } catch (HttpException e) { - throw new OrchestratorException("Failed to suspend " + hostName + ": " + - e.toString()); - } catch (Exception e) { - throw new RuntimeException("Got error on suspend", e); - } - - Optional.ofNullable(response.reason()).ifPresent(reason -> { - throw new OrchestratorException(reason.message()); - }); - } - - @Override - public void suspend(String parentHostName, List hostNames) { - final BatchOperationResult batchOperationResult; - try { - batchOperationResult = configServerApi.put( - ORCHESTRATOR_PATH_PREFIX_HOST_SUSPENSION_API, - Optional.of(new BatchHostSuspendRequest(parentHostName, hostNames)), - BatchOperationResult.class); - } catch (HttpException e) { - throw new OrchestratorException("Failed to batch suspend for " + - parentHostName + ": " + e.toString()); - } catch (Exception e) { - throw new RuntimeException("Got error on batch suspend for " + parentHostName + ", with nodes " + hostNames, e); - } - - batchOperationResult.getFailureReason().ifPresent(reason -> { - throw new OrchestratorException(reason); - }); - } - - @Override - public void resume(final String hostName) { - UpdateHostResponse response; - try { - String path = getSuspendPath(hostName); - response = configServerApi.delete(path, UpdateHostResponse.class); - } catch (HttpException.NotFoundException n) { - throw new OrchestratorNotFoundException("Failed to resume " + hostName + ", host not found"); - } catch (HttpException e) { - throw new OrchestratorException("Failed to suspend " + hostName + ": " + - e.toString()); - } catch (Exception e) { - throw new RuntimeException("Got error on resume", e); - } - - Optional.ofNullable(response.reason()).ifPresent(reason -> { - throw new OrchestratorException(reason.message()); - }); - } - - private String getSuspendPath(String hostName) { - return ORCHESTRATOR_PATH_PREFIX_HOST_API + "/" + hostName + "/suspended"; - } - -} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorNotFoundException.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorNotFoundException.java deleted file mode 100644 index 088152bd7a3..00000000000 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorNotFoundException.java +++ /dev/null @@ -1,9 +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.node.admin.orchestrator; - -@SuppressWarnings("serial") -public class OrchestratorNotFoundException extends OrchestratorException { - public OrchestratorNotFoundException(String message) { - super(message); - } -} diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeRepositoryImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeRepositoryImplTest.java new file mode 100644 index 00000000000..85e101714e8 --- /dev/null +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeRepositoryImplTest.java @@ -0,0 +1,182 @@ +// 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.node.admin.configserver.noderepository; + +import com.yahoo.application.Networking; +import com.yahoo.application.container.JDisc; +import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec; +import com.yahoo.vespa.hosted.dockerapi.DockerImage; +import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApiImpl; +import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAttributes; +import com.yahoo.vespa.hosted.provision.Node; +import com.yahoo.vespa.hosted.provision.testutils.ContainerConfig; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.URI; +import java.time.Instant; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +/** + * Tests the NodeRepository class used for talking to the node repository. It uses a mock from the node repository + * which already contains some data. + * + * @author dybdahl + */ +public class NodeRepositoryImplTest { + private JDisc container; + private ConfigServerApiImpl configServerApi; + + + private int findRandomOpenPort() throws IOException { + try (ServerSocket socket = new ServerSocket(0)) { + socket.setReuseAddress(true); + return socket.getLocalPort(); + } + } + + /** + * Starts NodeRepository with + * {@link com.yahoo.vespa.hosted.provision.testutils.MockNodeFlavors} + * {@link com.yahoo.vespa.hosted.provision.testutils.MockNodeRepository} + * {@link com.yahoo.vespa.hosted.provision.restapi.v2.NodesApiHandler} + * These classes define some test data that is used in these tests. + */ + @Before + public void startContainer() throws Exception { + Exception lastException = null; + + // This tries to bind a random open port for the node-repo mock, which is a race condition, so try + // a few times before giving up + for (int i = 0; i < 3; i++) { + try { + final int port = findRandomOpenPort(); + container = JDisc.fromServicesXml(ContainerConfig.servicesXmlV2(port), Networking.enable); + configServerApi = new ConfigServerApiImpl(Collections.singleton(URI.create("http://127.0.0.1:" + port))); + return; + } catch (RuntimeException e) { + lastException = e; + } + } + throw new RuntimeException("Failed to bind a port in three attempts, giving up", lastException); + } + + private void waitForJdiscContainerToServe() throws InterruptedException { + Instant start = Instant.now(); + NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(configServerApi); + while (Instant.now().minusSeconds(120).isBefore(start)) { + try { + nodeRepositoryApi.getContainersToRun("foobar"); + return; + } catch (Exception e) { + Thread.sleep(100); + } + } + throw new RuntimeException("Could not get answer from container."); + } + + @After + public void stopContainer() { + if (container != null) { + container.close(); + } + } + + @Test + public void testGetContainersToRunApi() throws InterruptedException { + waitForJdiscContainerToServe(); + NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(configServerApi); + String dockerHostHostname = "dockerhost1.yahoo.com"; + + final List containersToRun = nodeRepositoryApi.getContainersToRun(dockerHostHostname); + assertThat(containersToRun.size(), is(1)); + final ContainerNodeSpec nodeSpec = containersToRun.get(0); + assertThat(nodeSpec.hostname, is("host4.yahoo.com")); + assertThat(nodeSpec.wantedDockerImage.get(), is(new DockerImage("docker-registry.domain.tld:8080/dist/vespa:6.42.0"))); + assertThat(nodeSpec.nodeState, is(Node.State.active)); + assertThat(nodeSpec.wantedRestartGeneration.get(), is(0L)); + assertThat(nodeSpec.currentRestartGeneration.get(), is(0L)); + assertThat(nodeSpec.minCpuCores, is(0.2)); + assertThat(nodeSpec.minMainMemoryAvailableGb, is(0.5)); + assertThat(nodeSpec.minDiskAvailableGb, is(100.0)); + } + + @Test + public void testGetContainer() throws InterruptedException, IOException { + waitForJdiscContainerToServe(); + NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(configServerApi); + String hostname = "host4.yahoo.com"; + Optional nodeSpec = nodeRepositoryApi.getContainerNodeSpec(hostname); + assertThat(nodeSpec.isPresent(), is(true)); + assertThat(nodeSpec.get().hostname, is(hostname)); + } + + @Test + public void testGetContainerForNonExistingNode() throws InterruptedException, IOException { + waitForJdiscContainerToServe(); + NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(configServerApi); + String hostname = "host-that-does-not-exist"; + Optional nodeSpec = nodeRepositoryApi.getContainerNodeSpec(hostname); + assertFalse(nodeSpec.isPresent()); + } + + @Test + public void testUpdateNodeAttributes() throws InterruptedException, IOException { + waitForJdiscContainerToServe(); + NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(configServerApi); + String hostname = "host4.yahoo.com"; + nodeRepositoryApi.updateNodeAttributes( + hostname, + new NodeAttributes() + .withRestartGeneration(1L) + .withDockerImage(new DockerImage("image-1:6.2.3")) + .withVespaVersion("6.2.3")); + } + + @Test(expected = RuntimeException.class) + public void testUpdateNodeAttributesWithBadValue() throws InterruptedException, IOException { + waitForJdiscContainerToServe(); + NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(configServerApi); + String hostname = "host4.yahoo.com"; + nodeRepositoryApi.updateNodeAttributes( + hostname, + new NodeAttributes() + .withRestartGeneration(1L) + .withDockerImage(new DockerImage("image-1")) + .withVespaVersion("6.2.3\n")); + } + + @Test + public void testMarkAsReady() throws InterruptedException, IOException { + NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(configServerApi); + waitForJdiscContainerToServe(); + + nodeRepositoryApi.markAsDirty("host5.yahoo.com"); + nodeRepositoryApi.markNodeAvailableForNewAllocation("host5.yahoo.com"); + + try { + nodeRepositoryApi.markNodeAvailableForNewAllocation("host4.yahoo.com"); + fail("Should not be allowed to be marked ready as it is not registered as provisioned, dirty, failed or parked"); + } catch (RuntimeException ignored) { + // expected + } + + try { + nodeRepositoryApi.markNodeAvailableForNewAllocation("host101.yahoo.com"); + fail("Expected failure because host101 does not exist"); + } catch (RuntimeException ignored) { + // expected + } + } +} diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorImplTest.java new file mode 100644 index 00000000000..2d355c93c09 --- /dev/null +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorImplTest.java @@ -0,0 +1,159 @@ +// 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.node.admin.configserver.orchestrator; + +import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApiImpl; +import com.yahoo.vespa.hosted.node.admin.configserver.HttpException; +import com.yahoo.vespa.orchestrator.restapi.wire.BatchHostSuspendRequest; +import com.yahoo.vespa.orchestrator.restapi.wire.BatchOperationResult; +import com.yahoo.vespa.orchestrator.restapi.wire.HostStateChangeDenialReason; +import com.yahoo.vespa.orchestrator.restapi.wire.UpdateHostResponse; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author freva + */ +public class OrchestratorImplTest { + private static final String hostName = "host123.yahoo.com"; + + private final ConfigServerApiImpl configServerApi = mock(ConfigServerApiImpl.class); + private final OrchestratorImpl orchestrator = new OrchestratorImpl(configServerApi); + + @Test + public void testSuspendCall() { + when(configServerApi.put( + OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_API + "/" + hostName+ "/suspended", + Optional.empty(), + UpdateHostResponse.class + )).thenReturn(new UpdateHostResponse(hostName, null)); + + orchestrator.suspend(hostName); + } + + @Test(expected=OrchestratorException.class) + public void testSuspendCallWithFailureReason() { + when(configServerApi.put( + OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_API + "/" + hostName+ "/suspended", + Optional.empty(), + UpdateHostResponse.class + )).thenReturn(new UpdateHostResponse(hostName, new HostStateChangeDenialReason("hostname", "fail"))); + + orchestrator.suspend(hostName); + } + + @Test(expected=OrchestratorNotFoundException.class) + public void testSuspendCallWithNotFound() { + when(configServerApi.put( + any(String.class), + any(), + any() + )).thenThrow(new HttpException.NotFoundException("Not Found")); + + orchestrator.suspend(hostName); + } + + @Test(expected=RuntimeException.class) + public void testSuspendCallWithSomeOtherException() { + when(configServerApi.put( + any(String.class), + any(), + any() + )).thenThrow(new RuntimeException("Some parameter was wrong")); + + orchestrator.suspend(hostName); + } + + + @Test + public void testResumeCall() { + when(configServerApi.delete( + OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_API + "/" + hostName+ "/suspended", + UpdateHostResponse.class + )).thenReturn(new UpdateHostResponse(hostName, null)); + + orchestrator.resume(hostName); + } + + @Test(expected=OrchestratorException.class) + public void testResumeCallWithFailureReason() { + when(configServerApi.delete( + OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_API + "/" + hostName+ "/suspended", + UpdateHostResponse.class + )).thenReturn(new UpdateHostResponse(hostName, new HostStateChangeDenialReason("hostname", "fail"))); + + orchestrator.resume(hostName); + } + + @Test(expected=OrchestratorNotFoundException.class) + public void testResumeCallWithNotFound() { + when(configServerApi.delete( + any(String.class), + any() + )).thenThrow(new HttpException.NotFoundException("Not Found")); + + orchestrator.resume(hostName); + } + + @Test(expected=RuntimeException.class) + public void testResumeCallWithSomeOtherException() { + when(configServerApi.put( + any(String.class), + any(), + any() + )).thenThrow(new RuntimeException("Some parameter was wrong")); + + orchestrator.suspend(hostName); + } + + + @Test + public void testBatchSuspendCall() { + String parentHostName = "host1.test.yahoo.com"; + List hostNames = Arrays.asList("a1.host1.test.yahoo.com", "a2.host1.test.yahoo.com"); + + when(configServerApi.put( + OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_SUSPENSION_API, + Optional.of(new BatchHostSuspendRequest(parentHostName, hostNames)), + BatchOperationResult.class + )).thenReturn(BatchOperationResult.successResult()); + + orchestrator.suspend(parentHostName, hostNames); + } + + @Test(expected=OrchestratorException.class) + public void testBatchSuspendCallWithFailureReason() { + String parentHostName = "host1.test.yahoo.com"; + List hostNames = Arrays.asList("a1.host1.test.yahoo.com", "a2.host1.test.yahoo.com"); + String failureReason = "Failed to suspend"; + + when(configServerApi.put( + OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_SUSPENSION_API, + Optional.of(new BatchHostSuspendRequest(parentHostName, hostNames)), + BatchOperationResult.class + )).thenReturn(new BatchOperationResult(failureReason)); + + orchestrator.suspend(parentHostName, hostNames); + } + + @Test(expected=RuntimeException.class) + public void testBatchSuspendCallWithSomeException() { + String parentHostName = "host1.test.yahoo.com"; + List hostNames = Arrays.asList("a1.host1.test.yahoo.com", "a2.host1.test.yahoo.com"); + String exceptionMessage = "Exception: Something crashed!"; + + when(configServerApi.put( + OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_SUSPENSION_API, + Optional.of(new BatchHostSuspendRequest(parentHostName, hostNames)), + BatchOperationResult.class + )).thenThrow(new RuntimeException(exceptionMessage)); + + orchestrator.suspend(parentHostName, hostNames); + } +} diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/NodeRepoMock.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/NodeRepoMock.java index 6bb970a7965..8557da75ee9 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/NodeRepoMock.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/NodeRepoMock.java @@ -4,7 +4,7 @@ package com.yahoo.vespa.hosted.node.admin.integrationTests; import com.yahoo.vespa.hosted.node.admin.ContainerAclSpec; import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec; import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAttributes; -import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepository; +import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository; import com.yahoo.vespa.hosted.provision.Node; import java.util.ArrayList; diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/OrchestratorMock.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/OrchestratorMock.java index dc285bb27ce..469022cec56 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/OrchestratorMock.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/OrchestratorMock.java @@ -1,7 +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.hosted.node.admin.integrationTests; -import com.yahoo.vespa.hosted.node.admin.orchestrator.Orchestrator; +import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.Orchestrator; import java.util.List; diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RunInContainerTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RunInContainerTest.java index 2e633859480..02920226b6b 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RunInContainerTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RunInContainerTest.java @@ -20,9 +20,9 @@ import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminImpl; import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdaterImpl; import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgent; import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentImpl; -import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepository; -import com.yahoo.vespa.hosted.node.admin.orchestrator.Orchestrator; -import com.yahoo.vespa.hosted.node.admin.orchestrator.OrchestratorException; +import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository; +import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.Orchestrator; +import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.OrchestratorException; import com.yahoo.vespa.hosted.node.admin.provider.NodeAdminStateUpdater; import com.yahoo.vespa.hosted.node.admin.component.Environment; import com.yahoo.vespa.hosted.provision.Node; diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainerTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainerTest.java index a699377b4c3..d50f869617a 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainerTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainerTest.java @@ -6,7 +6,7 @@ import com.yahoo.vespa.hosted.dockerapi.ContainerName; import com.yahoo.vespa.hosted.dockerapi.DockerImage; import com.yahoo.vespa.hosted.node.admin.ContainerAclSpec; import com.yahoo.vespa.hosted.node.admin.docker.DockerOperations; -import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepository; +import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository; import org.junit.Before; import org.junit.Test; import org.mockito.verification.VerificationMode; diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterImplTest.java index 878ff8427de..dc3ad969e33 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterImplTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterImplTest.java @@ -6,9 +6,9 @@ import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec; import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerClients; import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerClientsImpl; import com.yahoo.vespa.hosted.node.admin.maintenance.StorageMaintainer; -import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepository; -import com.yahoo.vespa.hosted.node.admin.orchestrator.Orchestrator; -import com.yahoo.vespa.hosted.node.admin.orchestrator.OrchestratorException; +import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository; +import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.Orchestrator; +import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.OrchestratorException; import com.yahoo.vespa.hosted.node.admin.provider.NodeAdminStateUpdater; import com.yahoo.vespa.hosted.provision.Node; import org.junit.Test; diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java index 4d527627ac4..24aaa9f6afe 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java @@ -17,8 +17,8 @@ import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerClientsImpl; import com.yahoo.vespa.hosted.node.admin.docker.DockerOperations; import com.yahoo.vespa.hosted.node.admin.maintenance.StorageMaintainer; import com.yahoo.vespa.hosted.node.admin.maintenance.acl.AclMaintainer; -import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepository; -import com.yahoo.vespa.hosted.node.admin.orchestrator.Orchestrator; +import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository; +import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.Orchestrator; import com.yahoo.vespa.hosted.node.admin.component.Environment; import com.yahoo.vespa.hosted.node.admin.util.InetAddressResolver; import com.yahoo.vespa.hosted.node.admin.component.PathResolver; diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImplTest.java deleted file mode 100644 index 0f75562a780..00000000000 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImplTest.java +++ /dev/null @@ -1,182 +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.node.admin.noderepository; - -import com.yahoo.application.Networking; -import com.yahoo.application.container.JDisc; -import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec; -import com.yahoo.vespa.hosted.dockerapi.DockerImage; -import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApiImpl; -import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAttributes; -import com.yahoo.vespa.hosted.provision.Node; -import com.yahoo.vespa.hosted.provision.testutils.ContainerConfig; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.io.IOException; -import java.net.ServerSocket; -import java.net.URI; -import java.time.Instant; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; - -/** - * Tests the NodeRepository class used for talking to the node repository. It uses a mock from the node repository - * which already contains some data. - * - * @author dybdahl - */ -public class NodeRepositoryImplTest { - private JDisc container; - private ConfigServerApiImpl configServerApi; - - - private int findRandomOpenPort() throws IOException { - try (ServerSocket socket = new ServerSocket(0)) { - socket.setReuseAddress(true); - return socket.getLocalPort(); - } - } - - /** - * Starts NodeRepository with - * {@link com.yahoo.vespa.hosted.provision.testutils.MockNodeFlavors} - * {@link com.yahoo.vespa.hosted.provision.testutils.MockNodeRepository} - * {@link com.yahoo.vespa.hosted.provision.restapi.v2.NodesApiHandler} - * These classes define some test data that is used in these tests. - */ - @Before - public void startContainer() throws Exception { - Exception lastException = null; - - // This tries to bind a random open port for the node-repo mock, which is a race condition, so try - // a few times before giving up - for (int i = 0; i < 3; i++) { - try { - final int port = findRandomOpenPort(); - container = JDisc.fromServicesXml(ContainerConfig.servicesXmlV2(port), Networking.enable); - configServerApi = new ConfigServerApiImpl(Collections.singleton(URI.create("http://127.0.0.1:" + port))); - return; - } catch (RuntimeException e) { - lastException = e; - } - } - throw new RuntimeException("Failed to bind a port in three attempts, giving up", lastException); - } - - private void waitForJdiscContainerToServe() throws InterruptedException { - Instant start = Instant.now(); - NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(configServerApi); - while (Instant.now().minusSeconds(120).isBefore(start)) { - try { - nodeRepositoryApi.getContainersToRun("foobar"); - return; - } catch (Exception e) { - Thread.sleep(100); - } - } - throw new RuntimeException("Could not get answer from container."); - } - - @After - public void stopContainer() { - if (container != null) { - container.close(); - } - } - - @Test - public void testGetContainersToRunApi() throws InterruptedException { - waitForJdiscContainerToServe(); - NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(configServerApi); - String dockerHostHostname = "dockerhost1.yahoo.com"; - - final List containersToRun = nodeRepositoryApi.getContainersToRun(dockerHostHostname); - assertThat(containersToRun.size(), is(1)); - final ContainerNodeSpec nodeSpec = containersToRun.get(0); - assertThat(nodeSpec.hostname, is("host4.yahoo.com")); - assertThat(nodeSpec.wantedDockerImage.get(), is(new DockerImage("docker-registry.domain.tld:8080/dist/vespa:6.42.0"))); - assertThat(nodeSpec.nodeState, is(Node.State.active)); - assertThat(nodeSpec.wantedRestartGeneration.get(), is(0L)); - assertThat(nodeSpec.currentRestartGeneration.get(), is(0L)); - assertThat(nodeSpec.minCpuCores, is(0.2)); - assertThat(nodeSpec.minMainMemoryAvailableGb, is(0.5)); - assertThat(nodeSpec.minDiskAvailableGb, is(100.0)); - } - - @Test - public void testGetContainer() throws InterruptedException, IOException { - waitForJdiscContainerToServe(); - NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(configServerApi); - String hostname = "host4.yahoo.com"; - Optional nodeSpec = nodeRepositoryApi.getContainerNodeSpec(hostname); - assertThat(nodeSpec.isPresent(), is(true)); - assertThat(nodeSpec.get().hostname, is(hostname)); - } - - @Test - public void testGetContainerForNonExistingNode() throws InterruptedException, IOException { - waitForJdiscContainerToServe(); - NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(configServerApi); - String hostname = "host-that-does-not-exist"; - Optional nodeSpec = nodeRepositoryApi.getContainerNodeSpec(hostname); - assertFalse(nodeSpec.isPresent()); - } - - @Test - public void testUpdateNodeAttributes() throws InterruptedException, IOException { - waitForJdiscContainerToServe(); - NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(configServerApi); - String hostname = "host4.yahoo.com"; - nodeRepositoryApi.updateNodeAttributes( - hostname, - new NodeAttributes() - .withRestartGeneration(1L) - .withDockerImage(new DockerImage("image-1:6.2.3")) - .withVespaVersion("6.2.3")); - } - - @Test(expected = RuntimeException.class) - public void testUpdateNodeAttributesWithBadValue() throws InterruptedException, IOException { - waitForJdiscContainerToServe(); - NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(configServerApi); - String hostname = "host4.yahoo.com"; - nodeRepositoryApi.updateNodeAttributes( - hostname, - new NodeAttributes() - .withRestartGeneration(1L) - .withDockerImage(new DockerImage("image-1")) - .withVespaVersion("6.2.3\n")); - } - - @Test - public void testMarkAsReady() throws InterruptedException, IOException { - NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(configServerApi); - waitForJdiscContainerToServe(); - - nodeRepositoryApi.markAsDirty("host5.yahoo.com"); - nodeRepositoryApi.markNodeAvailableForNewAllocation("host5.yahoo.com"); - - try { - nodeRepositoryApi.markNodeAvailableForNewAllocation("host4.yahoo.com"); - fail("Should not be allowed to be marked ready as it is not registered as provisioned, dirty, failed or parked"); - } catch (RuntimeException ignored) { - // expected - } - - try { - nodeRepositoryApi.markNodeAvailableForNewAllocation("host101.yahoo.com"); - fail("Expected failure because host101 does not exist"); - } catch (RuntimeException ignored) { - // expected - } - } -} diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImplTest.java deleted file mode 100644 index 2c9eb228d36..00000000000 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImplTest.java +++ /dev/null @@ -1,159 +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.node.admin.orchestrator; - -import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApiImpl; -import com.yahoo.vespa.hosted.node.admin.configserver.HttpException; -import com.yahoo.vespa.orchestrator.restapi.wire.BatchHostSuspendRequest; -import com.yahoo.vespa.orchestrator.restapi.wire.BatchOperationResult; -import com.yahoo.vespa.orchestrator.restapi.wire.HostStateChangeDenialReason; -import com.yahoo.vespa.orchestrator.restapi.wire.UpdateHostResponse; -import org.junit.Test; - -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -/** - * @author freva - */ -public class OrchestratorImplTest { - private static final String hostName = "host123.yahoo.com"; - - private final ConfigServerApiImpl configServerApi = mock(ConfigServerApiImpl.class); - private final OrchestratorImpl orchestrator = new OrchestratorImpl(configServerApi); - - @Test - public void testSuspendCall() { - when(configServerApi.put( - OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_API + "/" + hostName+ "/suspended", - Optional.empty(), - UpdateHostResponse.class - )).thenReturn(new UpdateHostResponse(hostName, null)); - - orchestrator.suspend(hostName); - } - - @Test(expected=OrchestratorException.class) - public void testSuspendCallWithFailureReason() { - when(configServerApi.put( - OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_API + "/" + hostName+ "/suspended", - Optional.empty(), - UpdateHostResponse.class - )).thenReturn(new UpdateHostResponse(hostName, new HostStateChangeDenialReason("hostname", "fail"))); - - orchestrator.suspend(hostName); - } - - @Test(expected=OrchestratorNotFoundException.class) - public void testSuspendCallWithNotFound() { - when(configServerApi.put( - any(String.class), - any(), - any() - )).thenThrow(new HttpException.NotFoundException("Not Found")); - - orchestrator.suspend(hostName); - } - - @Test(expected=RuntimeException.class) - public void testSuspendCallWithSomeOtherException() { - when(configServerApi.put( - any(String.class), - any(), - any() - )).thenThrow(new RuntimeException("Some parameter was wrong")); - - orchestrator.suspend(hostName); - } - - - @Test - public void testResumeCall() { - when(configServerApi.delete( - OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_API + "/" + hostName+ "/suspended", - UpdateHostResponse.class - )).thenReturn(new UpdateHostResponse(hostName, null)); - - orchestrator.resume(hostName); - } - - @Test(expected=OrchestratorException.class) - public void testResumeCallWithFailureReason() { - when(configServerApi.delete( - OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_API + "/" + hostName+ "/suspended", - UpdateHostResponse.class - )).thenReturn(new UpdateHostResponse(hostName, new HostStateChangeDenialReason("hostname", "fail"))); - - orchestrator.resume(hostName); - } - - @Test(expected=OrchestratorNotFoundException.class) - public void testResumeCallWithNotFound() { - when(configServerApi.delete( - any(String.class), - any() - )).thenThrow(new HttpException.NotFoundException("Not Found")); - - orchestrator.resume(hostName); - } - - @Test(expected=RuntimeException.class) - public void testResumeCallWithSomeOtherException() { - when(configServerApi.put( - any(String.class), - any(), - any() - )).thenThrow(new RuntimeException("Some parameter was wrong")); - - orchestrator.suspend(hostName); - } - - - @Test - public void testBatchSuspendCall() { - String parentHostName = "host1.test.yahoo.com"; - List hostNames = Arrays.asList("a1.host1.test.yahoo.com", "a2.host1.test.yahoo.com"); - - when(configServerApi.put( - OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_SUSPENSION_API, - Optional.of(new BatchHostSuspendRequest(parentHostName, hostNames)), - BatchOperationResult.class - )).thenReturn(BatchOperationResult.successResult()); - - orchestrator.suspend(parentHostName, hostNames); - } - - @Test(expected=OrchestratorException.class) - public void testBatchSuspendCallWithFailureReason() { - String parentHostName = "host1.test.yahoo.com"; - List hostNames = Arrays.asList("a1.host1.test.yahoo.com", "a2.host1.test.yahoo.com"); - String failureReason = "Failed to suspend"; - - when(configServerApi.put( - OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_SUSPENSION_API, - Optional.of(new BatchHostSuspendRequest(parentHostName, hostNames)), - BatchOperationResult.class - )).thenReturn(new BatchOperationResult(failureReason)); - - orchestrator.suspend(parentHostName, hostNames); - } - - @Test(expected=RuntimeException.class) - public void testBatchSuspendCallWithSomeException() { - String parentHostName = "host1.test.yahoo.com"; - List hostNames = Arrays.asList("a1.host1.test.yahoo.com", "a2.host1.test.yahoo.com"); - String exceptionMessage = "Exception: Something crashed!"; - - when(configServerApi.put( - OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_SUSPENSION_API, - Optional.of(new BatchHostSuspendRequest(parentHostName, hostNames)), - BatchOperationResult.class - )).thenThrow(new RuntimeException(exceptionMessage)); - - orchestrator.suspend(parentHostName, hostNames); - } -} -- cgit v1.2.3