diff options
author | Jon Bratseth <bratseth@gmail.com> | 2020-04-23 21:51:07 +0200 |
---|---|---|
committer | Jon Bratseth <bratseth@gmail.com> | 2020-04-23 22:08:11 +0200 |
commit | 6094d7c082d642530f1878f568be3e18e52b56a5 (patch) | |
tree | e44daf9d05913133bbf7cbda075b0372404ffdee | |
parent | afdeb2a86df8a471166252da1f178e5c528631b9 (diff) |
nodes/v2/application responses WIP
5 files changed, 163 insertions, 62 deletions
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/ApplicationSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/ApplicationSerializer.java new file mode 100644 index 00000000000..54f60651795 --- /dev/null +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/ApplicationSerializer.java @@ -0,0 +1,48 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.provision.restapi.v2; + +import com.yahoo.config.provision.ClusterResources; +import com.yahoo.config.provision.NodeResources; +import com.yahoo.slime.Cursor; +import com.yahoo.slime.Slime; +import com.yahoo.vespa.hosted.provision.applications.Application; +import com.yahoo.vespa.hosted.provision.applications.Cluster; + +import java.net.URI; +import java.util.Collection; + +/** + * Serializes application information for nodes/v2/application responses + */ +public class ApplicationSerializer { + + public static Slime toSlime(Application application, URI applicationUri) { + Slime slime = new Slime(); + toSlime(application, slime.setObject(), applicationUri); + return slime; + } + + private static void toSlime(Application application, Cursor object, URI applicationUri) { + object.setString("url", applicationUri.toString()); + object.setString("id", application.id().toFullString()); + clustersToSlime(application.clusters().values(), object.setObject("clusters")); + } + + private static void clustersToSlime(Collection<Cluster> clusters, Cursor clustersObject) { + clusters.forEach(cluster -> toSlime(cluster, clustersObject.setObject(cluster.id().value()))); + } + + private static void toSlime(Cluster cluster, Cursor clusterObject) { + toSlime(cluster.minResources(), clusterObject.setObject("min")); + toSlime(cluster.maxResources(), clusterObject.setObject("max")); + cluster.suggestedResources().ifPresent(suggested -> toSlime(suggested, clusterObject.setObject("suggested"))); + cluster.targetResources().ifPresent(target -> toSlime(target, clusterObject.setObject("target"))); + } + + private static void toSlime(ClusterResources resources, Cursor clusterResourcesObject) { + clusterResourcesObject.setLong("nodes", resources.nodes()); + clusterResourcesObject.setLong("groups", resources.groups()); + NodeResourcesSerializer.toSlime(resources.nodeResources(), clusterResourcesObject.setObject("resources")); + } + +} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodeResourcesSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodeResourcesSerializer.java new file mode 100644 index 00000000000..10b3d6a4db8 --- /dev/null +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodeResourcesSerializer.java @@ -0,0 +1,57 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.provision.restapi.v2; + +import com.yahoo.config.provision.NodeResources; +import com.yahoo.slime.Cursor; + +/** + * @author bratseth + */ +public class NodeResourcesSerializer { + + static void toSlime(NodeResources resources, Cursor object) { + object.setDouble("vcpu", resources.vcpu()); + object.setDouble("memoryGb", resources.memoryGb()); + object.setDouble("diskGb", resources.diskGb()); + object.setDouble("bandwidthGbps", resources.bandwidthGbps()); + object.setString("diskSpeed", toString(resources.diskSpeed())); + object.setString("storageType", toString(resources.storageType())); + } + + public NodeResources.DiskSpeed diskSpeedFrom(String diskSpeed) { + switch (diskSpeed) { + case "fast": return NodeResources.DiskSpeed.fast; + case "slow": return NodeResources.DiskSpeed.slow; + case "any" : return NodeResources.DiskSpeed.any; + default: throw new IllegalArgumentException("Unknown disk speed '" + diskSpeed + "'"); + } + } + + private static String toString(NodeResources.DiskSpeed diskSpeed) { + switch (diskSpeed) { + case fast : return "fast"; + case slow : return "slow"; + case any : return "any"; + default: throw new IllegalArgumentException("Unknown disk speed '" + diskSpeed.name() + "'"); + } + } + + public NodeResources.StorageType storageTypeFrom(String storageType) { + switch (storageType) { + case "local" : return NodeResources.StorageType.local; + case "remote": return NodeResources.StorageType.remote; + case "any" : return NodeResources.StorageType.any; + default: throw new IllegalArgumentException("Unknown storage type '" + storageType + "'"); + } + } + + private static String toString(NodeResources.StorageType storageType) { + switch (storageType) { + case remote : return "remote"; + case local : return "local"; + case any : return "any"; + default: throw new IllegalArgumentException("Unknown storage type '" + storageType.name() + "'"); + } + } + +} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodeSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodeSerializer.java index 492994c2f0e..67f3b293de8 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodeSerializer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodeSerializer.java @@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.provision.restapi.v2; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; +import com.yahoo.slime.Cursor; import com.yahoo.vespa.hosted.provision.Node; /** @@ -12,7 +13,7 @@ import com.yahoo.vespa.hosted.provision.Node; */ public class NodeSerializer { - public Node.State stateFrom(String state) { + public static Node.State stateFrom(String state) { switch (state) { case "active": return Node.State.active; case "dirty": return Node.State.dirty; @@ -27,7 +28,7 @@ public class NodeSerializer { } } - public String toString(Node.State state) { + public static String toString(Node.State state) { switch (state) { case active: return "active"; case dirty: return "dirty"; @@ -42,7 +43,7 @@ public class NodeSerializer { } } - public NodeType typeFrom(String nodeType) { + public static NodeType typeFrom(String nodeType) { switch (nodeType) { case "tenant": return NodeType.tenant; case "host": return NodeType.host; @@ -57,7 +58,7 @@ public class NodeSerializer { } } - public String toString(NodeType type) { + public static String toString(NodeType type) { switch (type) { case tenant: return "tenant"; case host: return "host"; @@ -72,40 +73,4 @@ public class NodeSerializer { } } - public NodeResources.DiskSpeed diskSpeedFrom(String diskSpeed) { - switch (diskSpeed) { - case "fast": return NodeResources.DiskSpeed.fast; - case "slow": return NodeResources.DiskSpeed.slow; - case "any" : return NodeResources.DiskSpeed.any; - default: throw new IllegalArgumentException("Unknown disk speed '" + diskSpeed + "'"); - } - } - - public String toString(NodeResources.DiskSpeed diskSpeed) { - switch (diskSpeed) { - case fast : return "fast"; - case slow : return "slow"; - case any : return "any"; - default: throw new IllegalArgumentException("Unknown disk speed '" + diskSpeed.name() + "'"); - } - } - - public NodeResources.StorageType storageTypeFrom(String storageType) { - switch (storageType) { - case "local" : return NodeResources.StorageType.local; - case "remote": return NodeResources.StorageType.remote; - case "any" : return NodeResources.StorageType.any; - default: throw new IllegalArgumentException("Unknown storage type '" + storageType + "'"); - } - } - - public String toString(NodeResources.StorageType storageType) { - switch (storageType) { - case remote : return "remote"; - case local : return "local"; - case any : return "any"; - default: throw new IllegalArgumentException("Unknown storage type '" + storageType.name() + "'"); - } - } - } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java index 809e4200e7e..9c1b1e03b11 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java @@ -2,6 +2,7 @@ package com.yahoo.vespa.hosted.provision.restapi.v2; import com.yahoo.component.Version; +import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.DockerImage; import com.yahoo.config.provision.Flavor; import com.yahoo.config.provision.HostFilter; @@ -17,7 +18,9 @@ import com.yahoo.restapi.ErrorResponse; import com.yahoo.restapi.MessageResponse; import com.yahoo.restapi.Path; import com.yahoo.restapi.ResourceResponse; +import com.yahoo.restapi.SlimeJsonResponse; import com.yahoo.slime.ArrayTraverser; +import com.yahoo.slime.Cursor; import com.yahoo.slime.Inspector; import com.yahoo.slime.Slime; import com.yahoo.slime.SlimeUtils; @@ -25,6 +28,7 @@ import com.yahoo.slime.Type; import com.yahoo.vespa.hosted.provision.NoSuchNodeException; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeRepository; +import com.yahoo.vespa.hosted.provision.applications.Application; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.node.IP; import com.yahoo.vespa.hosted.provision.node.filter.ApplicationFilter; @@ -42,6 +46,8 @@ import javax.inject.Inject; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -100,17 +106,20 @@ public class NodesApiHandler extends LoggingRequestHandler { } private HttpResponse handleGET(HttpRequest request) { - String path = request.getUri().getPath(); - if (path.equals( "/nodes/v2/")) return new ResourceResponse(request.getUri(), "state", "node", "command", "maintenance", "upgrade"); - if (path.equals( "/nodes/v2/node/")) return new NodesResponse(ResponseType.nodeList, request, orchestrator, nodeRepository); - if (path.startsWith("/nodes/v2/node/")) return new NodesResponse(ResponseType.singleNode, request, orchestrator, nodeRepository); - if (path.equals( "/nodes/v2/state/")) return new NodesResponse(ResponseType.stateList, request, orchestrator, nodeRepository); - if (path.startsWith("/nodes/v2/state/")) return new NodesResponse(ResponseType.nodesInStateList, request, orchestrator, nodeRepository); - if (path.startsWith("/nodes/v2/acl/")) return new NodeAclResponse(request, nodeRepository); - if (path.equals( "/nodes/v2/command/")) return new ResourceResponse(request.getUri(), "restart", "reboot"); - if (path.equals( "/nodes/v2/maintenance/")) return new JobsResponse(nodeRepository.jobControl()); - if (path.equals( "/nodes/v2/upgrade/")) return new UpgradeResponse(nodeRepository.infrastructureVersions(), nodeRepository.osVersions(), nodeRepository.dockerImages()); - if (path.startsWith("/nodes/v2/capacity")) return new HostCapacityResponse(nodeRepository, request); + Path path = new Path(request.getUri()); + String pathS = request.getUri().toString(); + if (pathS.equals( "/nodes/v2/")) return new ResourceResponse(request.getUri(), "state", "node", "command", "maintenance", "upgrade"); + if (pathS.equals( "/nodes/v2/node/")) return new NodesResponse(ResponseType.nodeList, request, orchestrator, nodeRepository); + if (pathS.startsWith("/nodes/v2/node/")) return new NodesResponse(ResponseType.singleNode, request, orchestrator, nodeRepository); + if (pathS.equals( "/nodes/v2/state/")) return new NodesResponse(ResponseType.stateList, request, orchestrator, nodeRepository); + if (pathS.startsWith("/nodes/v2/state/")) return new NodesResponse(ResponseType.nodesInStateList, request, orchestrator, nodeRepository); + if (pathS.startsWith("/nodes/v2/acl/")) return new NodeAclResponse(request, nodeRepository); + if (pathS.equals( "/nodes/v2/command/")) return new ResourceResponse(request.getUri(), "restart", "reboot"); + if (pathS.equals( "/nodes/v2/maintenance/")) return new JobsResponse(nodeRepository.jobControl()); + if (pathS.equals( "/nodes/v2/upgrade/")) return new UpgradeResponse(nodeRepository.infrastructureVersions(), nodeRepository.osVersions(), nodeRepository.dockerImages()); + if (pathS.startsWith("/nodes/v2/capacity")) return new HostCapacityResponse(nodeRepository, request); + if (path.matches("/nodes/v2/application")) return applicationList(request.getUri()); + if (path.matches("/nodes/v2/application/{applicationId}")) return application(path.get("applicationId"), request.getUri()); throw new NotFoundException("Nothing at path '" + path + "'"); } @@ -394,4 +403,35 @@ public class NodesApiHandler extends LoggingRequestHandler { return nodes.stream().map(Node::hostname).sorted().collect(Collectors.joining(", ")); } + private HttpResponse applicationList(URI uri) { + Slime slime = new Slime(); + Cursor root = slime.setObject(); + Cursor applications = root.setArray("applications"); + for (ApplicationId id : nodeRepository.applications().ids()) { + Cursor application = applications.addObject(); + application.setString("url", withPath("nodes/v2/applications/" + id.toFullString(), uri).toString()); + application.setString("id", id.toFullString()); + } + return new SlimeJsonResponse(slime); + } + + private HttpResponse application(String id, URI uri) { + Optional<Application> application = nodeRepository.applications().get(ApplicationId.fromFullString(id)); + if (application.isEmpty()) + return ErrorResponse.notFoundError("No application '" + id + "'"); + Slime slime = ApplicationSerializer.toSlime(application.get(), + withPath("nodes/v2/applications/" + id, uri)); + return new SlimeJsonResponse(slime); + } + + /** Returns a copy of the given URI with the host and port from the given URI and the path set to the given path */ + private URI withPath(String newPath, URI uri) { + try { + return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), newPath, null, null); + } + catch (URISyntaxException e) { + throw new RuntimeException("Will not happen", e); + } + } + } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java index a4412a502aa..fbb9b207c32 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java @@ -148,7 +148,7 @@ class NodesResponse extends HttpResponse { node.reservedTo().ifPresent(reservedTo -> object.setString("reservedTo", reservedTo.value())); if (node.flavor().isConfigured()) object.setDouble("cpuCores", node.flavor().getMinCpuCores()); - toSlime(node.flavor().resources(), object.setObject("resources")); + NodeResourcesSerializer.toSlime(node.flavor().resources(), object.setObject("resources")); if (node.flavor().cost() > 0) object.setLong("cost", node.flavor().cost()); object.setString("environment", node.flavor().getType().name()); @@ -160,7 +160,7 @@ class NodesResponse extends HttpResponse { object.setString("wantedDockerImage", allocation.membership().cluster().dockerImage() .orElseGet(() -> nodeRepository.dockerImage(node).withTag(allocation.membership().cluster().vespaVersion()).asString())); object.setString("wantedVespaVersion", allocation.membership().cluster().vespaVersion().toFullString()); - toSlime(allocation.requestedResources(), object.setObject("requestedResources")); + NodeResourcesSerializer.toSlime(allocation.requestedResources(), object.setObject("requestedResources")); allocation.networkPorts().ifPresent(ports -> NetworkPortsSerializer.toSlime(ports, object.setArray("networkPorts"))); orchestrator.apply(new HostName(node.hostname())) .ifPresent(info -> { @@ -210,15 +210,6 @@ class NodesResponse extends HttpResponse { } } - private void toSlime(NodeResources resources, Cursor object) { - object.setDouble("vcpu", resources.vcpu()); - object.setDouble("memoryGb", resources.memoryGb()); - object.setDouble("diskGb", resources.diskGb()); - object.setDouble("bandwidthGbps", resources.bandwidthGbps()); - object.setString("diskSpeed", serializer.toString(resources.diskSpeed())); - object.setString("storageType", serializer.toString(resources.storageType())); - } - // Hack: For non-docker nodes, return current docker image as default prefix + current Vespa version // TODO: Remove current + wanted docker image from response for non-docker types private Optional<DockerImage> currentDockerImage(Node node) { |