diff options
author | Jon Bratseth <bratseth@oath.com> | 2020-05-06 13:24:09 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-05-06 13:24:09 +0200 |
commit | 8b107b8a4f56dce7d33371a423f26d2cf154a53d (patch) | |
tree | 38cec30614dab42779b1b7b7bd37190d4b0c01aa | |
parent | ee4fb54e2078961257a9c399f6e90622dc0b2298 (diff) | |
parent | d9095a974d0fde9f65cf66f68aedc04c07d89362 (diff) |
Merge pull request #13166 from vespa-engine/bratseth/propagate-cluster-info
Bratseth/propagate cluster info
20 files changed, 374 insertions, 41 deletions
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java b/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java index bed36d0d5d2..18dc0b217a3 100644 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java @@ -10,6 +10,11 @@ import java.util.Objects; */ public class NodeResources { + // Standard unit cost in dollars per hour + private static final double cpuUnitCost = 12.0; + private static final double memoryUnitCost = 1.2; + private static final double diskUnitCost = 0.045; + public static final NodeResources unspecified = new NodeResources(0, 0, 0, 0); public enum DiskSpeed { @@ -113,6 +118,11 @@ public class NodeResources { public DiskSpeed diskSpeed() { return diskSpeed; } public StorageType storageType() { return storageType; } + /** Returns the standard cost of these resources, in dollars per hour */ + public double cost() { + return vcpu * cpuUnitCost + memoryGb * memoryUnitCost + diskGb * diskUnitCost; + } + public NodeResources withVcpu(double vcpu) { if (vcpu == this.vcpu) return this; return new NodeResources(vcpu, memoryGb, diskGb, bandwidthGbps, diskSpeed, storageType); diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Application.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Application.java new file mode 100644 index 00000000000..1499d5c1b79 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Application.java @@ -0,0 +1,35 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.configserver; + +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.ClusterSpec; + +import java.util.Collection; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * The client-side version of the node repository's view of applications + * + * @author bratseth + */ +public class Application { + + private ApplicationId id; + private Map<ClusterSpec.Id, Cluster> clusters; + + public Application(ApplicationId id, Collection<Cluster> clusters) { + this.id = id; + this.clusters = clusters.stream().collect(Collectors.toUnmodifiableMap(c -> c.id(), c -> c)); + } + + public ApplicationId id() { return id; } + + public Map<ClusterSpec.Id, Cluster> clusters() { return clusters; } + + @Override + public String toString() { + return "application '" + id + "'"; + } + +} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Cluster.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Cluster.java new file mode 100644 index 00000000000..fd339e3bb43 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Cluster.java @@ -0,0 +1,47 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.configserver; + +import com.yahoo.config.provision.ClusterResources; +import com.yahoo.config.provision.ClusterSpec; + +import java.util.Optional; + +/** + * @author bratseth + */ +public class Cluster { + + private final ClusterSpec.Id id; + private final ClusterResources min; + private final ClusterResources max; + private final ClusterResources current; + private final Optional<ClusterResources> target; + private final Optional<ClusterResources> suggested; + + public Cluster(ClusterSpec.Id id, + ClusterResources min, + ClusterResources max, + ClusterResources current, + Optional<ClusterResources> target, + Optional<ClusterResources> suggested) { + this.id = id; + this.min = min; + this.max = max; + this.current = current; + this.target = target; + this.suggested = suggested; + } + + public ClusterSpec.Id id() { return id; } + public ClusterResources min() { return min; } + public ClusterResources max() { return max; } + public ClusterResources current() { return current; } + public Optional<ClusterResources> target() { return target; } + public Optional<ClusterResources> suggested() { return suggested; } + + @Override + public String toString() { + return "cluster '" + id + "'"; + } + +} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeRepository.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeRepository.java index c5a016d53c8..b5d49df7e9c 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeRepository.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeRepository.java @@ -70,6 +70,8 @@ public interface NodeRepository { .collect(Collectors.toList()); } + Application getApplication(ZoneId zone, ApplicationId application); + /** Upgrade all nodes of given type to a new version */ void upgrade(ZoneId zone, NodeType type, Version version); diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ApplicationData.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ApplicationData.java new file mode 100644 index 00000000000..1fc6d7f898a --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ApplicationData.java @@ -0,0 +1,30 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.noderepository; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.vespa.hosted.controller.api.integration.configserver.Application; + +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author bratseth + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ApplicationData { + + @JsonProperty + public String id; + @JsonProperty + public Map<String, ClusterData> clusters; + + public Application toApplication() { + return new Application(ApplicationId.fromFullString(id), + clusters.entrySet().stream().map(e -> e.getValue().toCluster(e.getKey())).collect(Collectors.toList())); + } + +} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ClusterData.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ClusterData.java new file mode 100644 index 00000000000..298928a881d --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ClusterData.java @@ -0,0 +1,39 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.noderepository; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.yahoo.config.provision.ClusterSpec; +import com.yahoo.vespa.hosted.controller.api.integration.configserver.Cluster; + +import java.util.Optional; + +/** + * @author bratseth + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ClusterData { + + @JsonProperty("min") + public ClusterResourcesData min; + @JsonProperty("max") + public ClusterResourcesData max; + @JsonProperty("current") + public ClusterResourcesData current; + @JsonProperty("suggested") + public ClusterResourcesData suggested; + @JsonProperty("target") + public ClusterResourcesData target; + + public Cluster toCluster(String id) { + return new Cluster(ClusterSpec.Id.from(id), + min.toClusterResources(), + max.toClusterResources(), + current.toClusterResources(), + target == null ? Optional.empty() : Optional.of(target.toClusterResources()), + suggested == null ? Optional.empty() : Optional.of(suggested.toClusterResources())); + } + +} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ClusterResourcesData.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ClusterResourcesData.java new file mode 100644 index 00000000000..571af2a80c0 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ClusterResourcesData.java @@ -0,0 +1,29 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.noderepository; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.yahoo.config.provision.ClusterResources; + +import java.util.Optional; + +/** + * @author bratseth + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ClusterResourcesData { + + @JsonProperty + public int nodes; + @JsonProperty + public int groups; + @JsonProperty + public NodeResources resources; + + public ClusterResources toClusterResources() { + return new ClusterResources(nodes, groups, resources.toNodeResources()); + } + +} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/NodeResources.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/NodeResources.java index ded61c7a94e..b46412376c7 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/NodeResources.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/NodeResources.java @@ -73,6 +73,30 @@ public class NodeResources { this.storageType = storageType; } + public com.yahoo.config.provision.NodeResources toNodeResources() { + return new com.yahoo.config.provision.NodeResources(vcpu, memoryGb, diskGb, bandwidthGbps, + toDiskSpeed(diskSpeed), + toStorageType(storageType)); + } + + private com.yahoo.config.provision.NodeResources.DiskSpeed toDiskSpeed(String diskSpeed) { + switch (diskSpeed) { + case "fast" : return com.yahoo.config.provision.NodeResources.DiskSpeed.fast; + case "slow" : return com.yahoo.config.provision.NodeResources.DiskSpeed.slow; + case "any" : return com.yahoo.config.provision.NodeResources.DiskSpeed.any; + default : throw new IllegalArgumentException("Unknown disk speed '" + diskSpeed + "'"); + } + } + + private com.yahoo.config.provision.NodeResources.StorageType toStorageType(String storageType) { + switch (storageType) { + case "remote" : return com.yahoo.config.provision.NodeResources.StorageType.remote; + case "local" : return com.yahoo.config.provision.NodeResources.StorageType.local; + case "any" : return com.yahoo.config.provision.NodeResources.StorageType.any; + default : throw new IllegalArgumentException("Unknown storage type '" + storageType + "'"); + } + } + @Override public String toString() { return "NodeResources{" + diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ProvisionResource.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ProvisionResource.java index 5eeb1f7401e..97720adbcc6 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ProvisionResource.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ProvisionResource.java @@ -53,11 +53,14 @@ public interface ProvisionResource { NodeList listNodes(@QueryParam("recursive") boolean recursive, @QueryParam("hostname") String hostnamesString); - @GET @Path("/node/") NodeList listNodesWithParent(@QueryParam("recursive") boolean recursive, @QueryParam("parentHost") String parentHostname); + @GET + @Path("/application/{application}") + ApplicationData getApplication(@PathParam("application") String applicationId); + @PUT @Path("/state/{state}/{hostname}") String setState(@PathParam("state") NodeState state, @PathParam("hostname") String hostname); diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java index 80de7f802b6..2ed4c057dfd 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java @@ -107,11 +107,13 @@ enum PathGroup { "/application/v4/tenant/{tenant}/application/{application}/instance/{ignored}/deploying/{*}", "/application/v4/tenant/{tenant}/application/{application}/instance/{ignored}/job/{*}", "/application/v4/tenant/{tenant}/application/{application}/instance/{ignored}/environment/{environment}/region/{region}/nodes", + "/application/v4/tenant/{tenant}/application/{application}/instance/{ignored}/environment/{environment}/region/{region}/clusters", "/application/v4/tenant/{tenant}/application/{application}/instance/{ignored}/environment/{environment}/region/{region}/logs", "/application/v4/tenant/{tenant}/application/{application}/instance/{ignored}/environment/{environment}/region/{region}/suspended", "/application/v4/tenant/{tenant}/application/{application}/instance/{ignored}/environment/{environment}/region/{region}/service/{*}", "/application/v4/tenant/{tenant}/application/{application}/instance/{ignored}/environment/{environment}/region/{region}/global-rotation/{*}", "/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{ignored}/nodes", + "/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{ignored}/clusters", "/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{ignored}/logs", "/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{ignored}/suspended", "/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{ignored}/service/{*}", diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java index c5da5755474..4eb2558f498 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java @@ -9,6 +9,7 @@ import com.yahoo.component.Version; import com.yahoo.config.application.api.DeploymentSpec; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ApplicationName; +import com.yahoo.config.provision.ClusterResources; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.NodeResources; @@ -29,7 +30,6 @@ import com.yahoo.slime.Cursor; import com.yahoo.slime.Inspector; import com.yahoo.slime.Slime; import com.yahoo.slime.SlimeUtils; -import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.Instance; import com.yahoo.vespa.hosted.controller.LockedTenant; @@ -44,9 +44,11 @@ import com.yahoo.vespa.hosted.controller.api.application.v4.model.configserverbi import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; import com.yahoo.vespa.hosted.controller.api.identifiers.Hostname; import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; +import com.yahoo.vespa.hosted.controller.api.integration.configserver.Cluster; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException; import com.yahoo.vespa.hosted.controller.api.integration.configserver.Log; import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node; +import com.yahoo.vespa.hosted.controller.api.integration.configserver.Application; import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion; import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobId; import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; @@ -229,6 +231,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler { if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/service")) return services(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request); if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/service/{service}/{*}")) return service(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), path.get("service"), path.getRest(), request); if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/nodes")) return nodes(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region")); + if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/clusters")) return clusters(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region")); if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/logs")) return logs(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request.propertyMap()); if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/global-rotation")) return rotationStatus(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), Optional.ofNullable(request.getProperty("endpointId"))); if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/global-rotation/override")) return getGlobalRotationOverride(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region")); @@ -238,6 +241,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler { if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/service")) return services(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request); if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/service/{service}/{*}")) return service(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), path.get("service"), path.getRest(), request); if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/nodes")) return nodes(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region")); + if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/clusters")) return clusters(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region")); if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/logs")) return logs(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request.propertyMap()); if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/global-rotation")) return rotationStatus(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), Optional.ofNullable(request.getProperty("endpointId"))); if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/global-rotation/override")) return getGlobalRotationOverride(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region")); @@ -408,7 +412,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler { Slime slime = new Slime(); Cursor applicationArray = slime.setArray(); - for (Application application : controller.applications().asList(tenant)) { + for (com.yahoo.vespa.hosted.controller.Application application : controller.applications().asList(tenant)) { if (applicationName.map(application.id().application().value()::equals).orElse(true)) { Cursor applicationObject = applicationArray.addObject(); applicationObject.setString("tenant", application.id().tenant().value()); @@ -587,7 +591,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler { return new MessageResponse(messageBuilder.toString()); } - private Application getApplication(String tenantName, String applicationName) { + private com.yahoo.vespa.hosted.controller.Application getApplication(String tenantName, String applicationName) { TenantAndApplicationId applicationId = TenantAndApplicationId.from(tenantName, applicationName); return controller.applications().getApplication(applicationId) .orElseThrow(() -> new NotExistsException(applicationId + " not found")); @@ -614,12 +618,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler { nodeObject.setString("orchestration", valueOf(node.serviceState())); nodeObject.setString("version", node.currentVersion().toString()); nodeObject.setString("flavor", node.flavor()); - nodeObject.setDouble("vcpu", node.resources().vcpu()); - nodeObject.setDouble("memoryGb", node.resources().memoryGb()); - nodeObject.setDouble("diskGb", node.resources().diskGb()); - nodeObject.setDouble("bandwidthGbps", node.resources().bandwidthGbps()); - nodeObject.setString("diskSpeed", valueOf(node.resources().diskSpeed())); - nodeObject.setString("storageType", valueOf(node.resources().storageType())); + toSlime(node.resources(), nodeObject); nodeObject.setBool("fastDisk", node.resources().diskSpeed() == NodeResources.DiskSpeed.fast); // TODO: Remove nodeObject.setString("clusterId", node.clusterId()); nodeObject.setString("clusterType", valueOf(node.clusterType())); @@ -627,6 +626,24 @@ public class ApplicationApiHandler extends LoggingRequestHandler { return new SlimeJsonResponse(slime); } + private HttpResponse clusters(String tenantName, String applicationName, String instanceName, String environment, String region) { + ApplicationId id = ApplicationId.from(tenantName, applicationName, instanceName); + ZoneId zone = ZoneId.from(environment, region); + Application application = controller.serviceRegistry().configServer().nodeRepository().getApplication(zone, id); + + Slime slime = new Slime(); + Cursor clustersObject = slime.setObject().setObject("clusters"); + for (Cluster cluster : application.clusters().values()) { + Cursor clusterObject = clustersObject.setObject(cluster.id().value()); + toSlime(cluster.min(), clusterObject.setObject("min")); + toSlime(cluster.max(), clusterObject.setObject("max")); + toSlime(cluster.current(), clusterObject.setObject("current")); + cluster.target().ifPresent(target -> toSlime(target, clusterObject.setObject("target"))); + cluster.suggested().ifPresent(suggested -> toSlime(suggested, clusterObject.setObject("suggested"))); + } + return new SlimeJsonResponse(slime); + } + private static String valueOf(Node.State state) { switch (state) { case failed: return "failed"; @@ -716,7 +733,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler { return new MessageResponse(type.jobName() + " for " + id + " resumed"); } - private void toSlime(Cursor object, Application application, HttpRequest request) { + private void toSlime(Cursor object, com.yahoo.vespa.hosted.controller.Application application, HttpRequest request) { object.setString("tenant", application.id().tenant().value()); object.setString("application", application.id().application().value()); object.setString("deployments", withPath("/application/v4" + @@ -854,7 +871,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler { } private void toSlime(Cursor object, Instance instance, DeploymentStatus status, HttpRequest request) { - Application application = status.application(); + com.yahoo.vespa.hosted.controller.Application application = status.application(); object.setString("tenant", instance.id().tenant().value()); object.setString("application", instance.id().application().value()); object.setString("instance", instance.id().instance().value()); @@ -1349,7 +1366,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler { Inspector requestObject = toSlime(request.getData()).get(); TenantAndApplicationId id = TenantAndApplicationId.from(tenantName, applicationName); Credentials credentials = accessControlRequests.credentials(id.tenant(), requestObject, request.getJDiscRequest()); - Application application = controller.applications().createApplication(id, credentials); + com.yahoo.vespa.hosted.controller.Application application = controller.applications().createApplication(id, credentials); Slime slime = new Slime(); toSlime(id, slime.setObject(), request); return new SlimeJsonResponse(slime); @@ -1513,7 +1530,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler { Optional<ApplicationPackage> applicationPackage = Optional.ofNullable(dataParts.get("applicationZip")) .map(ApplicationPackage::new); - Optional<Application> application = controller.applications().getApplication(TenantAndApplicationId.from(applicationId)); + Optional<com.yahoo.vespa.hosted.controller.Application> application = controller.applications().getApplication(TenantAndApplicationId.from(applicationId)); Inspector sourceRevision = deployOptions.field("sourceRevision"); Inspector buildNumber = deployOptions.field("buildNumber"); @@ -1656,7 +1673,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler { private void toSlime(Cursor object, Tenant tenant, HttpRequest request) { object.setString("tenant", tenant.name().value()); object.setString("type", tenantType(tenant)); - List<Application> applications = controller.applications().asList(tenant.name()); + List<com.yahoo.vespa.hosted.controller.Application> applications = controller.applications().asList(tenant.name()); switch (tenant.type()) { case athenz: AthenzTenant athenzTenant = (AthenzTenant) tenant; @@ -1690,7 +1707,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler { } // TODO jonmv: This should list applications, not instances. Cursor applicationArray = object.setArray("applications"); - for (Application application : applications) { + for (com.yahoo.vespa.hosted.controller.Application application : applications) { DeploymentStatus status = controller.jobController().deploymentStatus(application); for (Instance instance : showOnlyProductionInstances(request) ? application.productionInstances().values() : application.instances().values()) @@ -1701,6 +1718,22 @@ public class ApplicationApiHandler extends LoggingRequestHandler { } } + private void toSlime(ClusterResources resources, Cursor object) { + object.setLong("nodes", resources.nodes()); + object.setLong("groups", resources.groups()); + toSlime(resources.nodeResources(), object.setObject("nodeResources")); + object.setDouble("cost", resources.nodes() * resources.nodeResources().cost()); + } + + 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", valueOf(resources.diskSpeed())); + object.setString("storageType", valueOf(resources.storageType())); + } + // A tenant has different content when in a list ... antipattern, but not solvable before application/v5 private void tenantInTenantsListToSlime(Tenant tenant, URI requestURI, Cursor object) { object.setString("tenant", tenant.name().value()); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java index 0fb2451ccba..fff63e1954e 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java @@ -5,6 +5,7 @@ import com.google.inject.Inject; import com.yahoo.component.AbstractComponent; import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.ClusterResources; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.DockerImage; import com.yahoo.config.provision.Environment; @@ -22,6 +23,7 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.Hostname; import com.yahoo.vespa.hosted.controller.api.identifiers.Identifier; import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.api.integration.LogEntry; +import com.yahoo.vespa.hosted.controller.api.integration.configserver.Cluster; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServer; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ContainerEndpoint; import com.yahoo.vespa.hosted.controller.api.integration.configserver.LoadBalancer; @@ -97,7 +99,17 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer } /** Assigns a reserved tenant node to the given deployment, with initial versions. */ - public void provision(ZoneId zone, ApplicationId application) { + public void provision(ZoneId zone, ApplicationId application, ClusterSpec.Id clusterId) { + Cluster cluster = new Cluster(clusterId, + new ClusterResources(2, 1, new NodeResources(1, 4, 20, 1, slow, remote)), + new ClusterResources(2, 1, new NodeResources(4, 16, 90, 1, slow, remote)), + new ClusterResources(2, 1, new NodeResources(2, 8, 50, 1, slow, remote)), + Optional.of(new ClusterResources(2, 1, new NodeResources(3, 8, 50, 1, slow, remote))), + Optional.empty()); + nodeRepository.putApplication(zone, + new com.yahoo.vespa.hosted.controller.api.integration.configserver.Application(application, + List.of(cluster))); + Node parent = nodeRepository().list(zone, SystemApplication.tenantHost.id()).stream().findAny() .orElseThrow(() -> new IllegalStateException("No parent hosts in " + zone)); nodeRepository().putNodes(zone, new Node.Builder().hostname(hostFor(application, zone)) @@ -114,7 +126,7 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer .resources(new NodeResources(2, 8, 50, 1, slow, remote)) .serviceState(Node.ServiceState.unorchestrated) .flavor("d-2-8-50") - .clusterId("cluster") + .clusterId(clusterId.value()) .clusterType(Node.ClusterType.container) .build()); } @@ -326,10 +338,12 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer throw prepareException; } DeploymentId id = new DeploymentId(deployment.instance(), deployment.zone()); + applications.put(id, new Application(id.applicationId(), lastPrepareVersion, new ApplicationPackage(deployment.applicationPackage()))); + ClusterSpec.Id cluster = ClusterSpec.Id.from("default"); if (nodeRepository().list(id.zoneId(), id.applicationId()).isEmpty()) - provision(id.zoneId(), id.applicationId()); + provision(id.zoneId(), id.applicationId(), cluster); this.rotationNames.put( id, @@ -342,7 +356,7 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer if (!deferLoadBalancerProvisioning.contains(id.zoneId().environment())) { putLoadBalancers(id.zoneId(), List.of(new LoadBalancer(UUID.randomUUID().toString(), id.applicationId(), - ClusterSpec.Id.from("default"), + cluster, HostName.from("lb-0--" + id.applicationId().serializedForm() + "--" + id.zoneId().toString()), LoadBalancer.State.active, Optional.of("dns-zone-1")))); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java index 1e535e7e9de..76a30c289b8 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java @@ -8,6 +8,7 @@ import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; +import com.yahoo.vespa.hosted.controller.api.integration.configserver.Application; import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node; import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeRepository; import com.yahoo.vespa.hosted.controller.api.integration.configserver.TargetVersions; @@ -34,6 +35,7 @@ import java.util.stream.Collectors; public class NodeRepositoryMock implements NodeRepository { private final Map<ZoneId, Map<HostName, Node>> nodeRepository = new HashMap<>(); + private final Map<ZoneId, Map<ApplicationId, Application>> applications = new HashMap<>(); private final Map<ZoneId, TargetVersions> targetVersions = new HashMap<>(); /** Add or update given nodes in zone */ @@ -43,6 +45,11 @@ public class NodeRepositoryMock implements NodeRepository { Function.identity()))); } + public void putApplication(ZoneId zone, Application application) { + applications.putIfAbsent(zone, new HashMap<>()); + applications.get(zone).put(application.id(), application); + } + /** Add or update given node in zone */ public void putNodes(ZoneId zone, Node node) { putNodes(zone, Collections.singletonList(node)); @@ -162,6 +169,11 @@ public class NodeRepositoryMock implements NodeRepository { } @Override + public Application getApplication(ZoneId zone, ApplicationId applicationId) { + return applications.get(zone).get(applicationId); + } + + @Override public void upgrade(ZoneId zone, NodeType type, Version version) { this.targetVersions.compute(zone, (ignored, targetVersions) -> { if (targetVersions == null) { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java index a63597ad32b..7c703735dbf 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java @@ -494,9 +494,14 @@ public class ApplicationApiTest extends ControllerContainerTest { // GET nodes tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/nodes", GET) - .userIdentity(USER_ID), + .userIdentity(USER_ID), new File("application-nodes.json")); + // GET clusters + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/clusters", GET) + .userIdentity(USER_ID), + new File("application-clusters.json")); + // GET logs tester.assertResponse(request("/application/v4/tenant/tenant2/application/application1/environment/dev/region/us-central-1/instance/default/logs?from=1233&to=3214", GET) .userIdentity(USER_ID), diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-clusters.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-clusters.json new file mode 100644 index 00000000000..2752d69077b --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-clusters.json @@ -0,0 +1,58 @@ +{ + "clusters": { + "default": { + "min": { + "nodes": 2, + "groups": 1, + "nodeResources": { + "vcpu": 1.0, + "memoryGb": 4.0, + "diskGb": 20.0, + "bandwidthGbps": 1.0, + "diskSpeed": "slow", + "storageType": "remote" + }, + "cost": 35.4 + }, + "max": { + "nodes": 2, + "groups": 1, + "nodeResources": { + "vcpu": 4.0, + "memoryGb": 16.0, + "diskGb": 90.0, + "bandwidthGbps": 1.0, + "diskSpeed": "slow", + "storageType": "remote" + }, + "cost": 142.5 + }, + "current": { + "nodes": 2, + "groups": 1, + "nodeResources": { + "vcpu": 2.0, + "memoryGb": 8.0, + "diskGb": 50.0, + "bandwidthGbps": 1.0, + "diskSpeed": "slow", + "storageType": "remote" + }, + "cost": 71.7 + }, + "target": { + "nodes": 2, + "groups": 1, + "nodeResources": { + "vcpu": 3.0, + "memoryGb": 8.0, + "diskGb": 50.0, + "bandwidthGbps": 1.0, + "diskSpeed": "slow", + "storageType": "remote" + }, + "cost": 95.7 + } + } + } +}
\ No newline at end of file diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-nodes.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-nodes.json index cee2fa7a6d2..0cfb457660c 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-nodes.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-nodes.json @@ -13,7 +13,7 @@ "diskSpeed": "slow", "storageType": "remote", "fastDisk": false, - "clusterId": "cluster", + "clusterId": "default", "clusterType": "container" } ] diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java index c6778af6efb..305b87f96e7 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java @@ -19,11 +19,6 @@ import java.util.Optional; */ public class AllocatableClusterResources { - // We only depend on the ratios between these values - private static final double cpuUnitCost = 12.0; - private static final double memoryUnitCost = 1.2; - private static final double diskUnitCost = 0.045; - /** The node count in the cluster */ private final int nodes; @@ -107,7 +102,7 @@ public class AllocatableClusterResources { public ClusterSpec.Type clusterType() { return clusterType; } - public double cost() { return nodes * costOf(advertisedResources); } + public double cost() { return nodes * advertisedResources.cost(); } /** * Returns the fraction measuring how well the real resources fulfils the ideal: 1 means completely fulfilled, @@ -116,12 +111,6 @@ public class AllocatableClusterResources { */ public double fulfilment() { return fulfilment; } - private static double costOf(NodeResources resources) { - return resources.vcpu() * cpuUnitCost + - resources.memoryGb() * memoryUnitCost + - resources.diskGb() * diskUnitCost; - } - private static double fulfilment(NodeResources realResources, NodeResources idealResources) { double vcpuFulfilment = Math.min(1, realResources.vcpu() / idealResources.vcpu()); double memoryGbFulfilment = Math.min(1, realResources.memoryGb() / idealResources.memoryGb()); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java index 75b1efb649e..8e342a985ca 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java @@ -62,7 +62,7 @@ import static com.yahoo.slime.SlimeUtils.optionalString; /** * The implementation of the /nodes/v2 API. - * See RestApiTest for documentation. + * See NodesV2ApiTest for documentation. * * @author bratseth */ @@ -191,11 +191,6 @@ public class NodesV2ApiHandler extends LoggingRequestHandler { throw new NotFoundException("Nothing at path '" + request.getUri().getPath() + "'"); } - private HttpResponse runJob(String job) { - nodeRepository.jobControl().run(job); - return new MessageResponse("Executed job '" + job + "'"); - } - private HttpResponse handleDELETE(HttpRequest request) { Path path = new Path(request.getUri()); if (path.matches("/nodes/v2/node/{hostname}")) return deleteNode(path.get("hostname")); @@ -205,6 +200,11 @@ public class NodesV2ApiHandler extends LoggingRequestHandler { throw new NotFoundException("Nothing at path '" + request.getUri().getPath() + "'"); } + private HttpResponse runJob(String job) { + nodeRepository.jobControl().run(job); + return new MessageResponse("Executed job '" + job + "'"); + } + private HttpResponse deleteNode(String hostname) { Optional<Node> node = nodeRepository.getNode(hostname); if (node.isEmpty()) throw new NotFoundException("No node with hostname '" + hostname + "'"); diff --git a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/FeedClient.java b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/FeedClient.java index 8cd22fe2e5c..8c2c1405fa0 100644 --- a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/FeedClient.java +++ b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/FeedClient.java @@ -54,7 +54,7 @@ public interface FeedClient extends AutoCloseable { /** * Issues a document operation to the configured cluster(s). * If the pipeline and buffers are full, this call will be blocking, ensuring that operations are not - * produced faster than the can be handled. Transient failured are retried internally by this client. + * produced faster than the can be handled. Transient failures are retried internally by this client. * Exactly one callback will always be received for each (completed) call to this. * * @param documentId the document id of the document diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/FeederSettings.java b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/FeederSettings.java index 874509dd2f8..e6d8a88d10b 100644 --- a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/FeederSettings.java +++ b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/FeederSettings.java @@ -47,6 +47,7 @@ public class FeederSettings { } } { + // TODO: Change default to JSON on Vespa 8 String tmpDataFormat = request.getHeader(Headers.DATA_FORMAT); if (tmpDataFormat != null) { dataFormat = DataFormat.valueOf(tmpDataFormat); |