From e97115b107f378e60aa060af4ac4d4fafc83ab46 Mon Sep 17 00:00:00 2001 From: Øyvind Grønnesby Date: Wed, 22 Feb 2023 15:08:58 +0100 Subject: Add major version to billing --- .../controller/api/integration/billing/Bill.java | 13 ++++++++++--- .../api/integration/billing/PlanRegistryMock.java | 3 ++- .../controller/api/integration/resource/CostInfo.java | 8 +++++++- .../resource/ResourceDatabaseClientMock.java | 4 +++- .../api/integration/resource/ResourceSnapshot.java | 19 ++++++++++++++----- .../api/integration/resource/ResourceUsage.java | 9 ++++++++- .../controller/restapi/billing/BillingApiHandler.java | 3 --- .../maintenance/ResourceMeterMaintainerTest.java | 12 ++++++------ .../restapi/controller/ControllerApiTest.java | 5 +++-- 9 files changed, 53 insertions(+), 23 deletions(-) diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Bill.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Bill.java index 01488711f59..854413a4adb 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Bill.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Bill.java @@ -189,6 +189,7 @@ public class Bill { private BigDecimal memoryCost; private BigDecimal diskCost; private NodeResources.Architecture architecture; + private int majorVersion; public LineItem(String id, String description, BigDecimal amount, String plan, String agent, ZonedDateTime addedAt) { this.id = id; @@ -200,7 +201,7 @@ public class Bill { } public LineItem(String id, String description, BigDecimal amount, String plan, String agent, ZonedDateTime addedAt, ZonedDateTime startedAt, ZonedDateTime endedAt, ApplicationId applicationId, ZoneId zoneId, - BigDecimal cpuHours, BigDecimal memoryHours, BigDecimal diskHours, BigDecimal cpuCost, BigDecimal memoryCost, BigDecimal diskCost, NodeResources.Architecture architecture) { + BigDecimal cpuHours, BigDecimal memoryHours, BigDecimal diskHours, BigDecimal cpuCost, BigDecimal memoryCost, BigDecimal diskCost, NodeResources.Architecture architecture, int majorVersion) { this(id, description, amount, plan, agent, addedAt); this.startedAt = startedAt; this.endedAt = endedAt; @@ -217,6 +218,7 @@ public class Bill { this.memoryCost = memoryCost; this.diskCost = diskCost; this.architecture = architecture; + this.majorVersion = majorVersion; } /** The opaque ID of this */ @@ -297,6 +299,10 @@ public class Bill { return Optional.ofNullable(architecture); } + public int getMajorVersion() { + return majorVersion; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -311,12 +317,13 @@ public class Bill { startedAt.equals(lineItem.startedAt) && endedAt.equals(lineItem.endedAt) && applicationId.equals(lineItem.applicationId) && - zoneId.equals(lineItem.zoneId); + zoneId.equals(lineItem.zoneId) && + majorVersion == lineItem.majorVersion; } @Override public int hashCode() { - return Objects.hash(id, description, amount, plan, agent, addedAt, startedAt, endedAt, applicationId, zoneId); + return Objects.hash(id, description, amount, plan, agent, addedAt, startedAt, endedAt, applicationId, zoneId, majorVersion); } @Override diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java index 723ffa383eb..ecce9b7ccfa 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java @@ -124,7 +124,8 @@ public class PlanRegistryMock implements PlanRegistry { cpuCost, memCost, dgbCost, - usage.getArchitecture() + usage.getArchitecture(), + usage.getMajorVersion() ); } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/CostInfo.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/CostInfo.java index c756100e563..571a7b5a58e 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/CostInfo.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/CostInfo.java @@ -21,11 +21,12 @@ public class CostInfo { private final BigDecimal memoryCost; private final BigDecimal diskCost; private final NodeResources.Architecture architecture; + private final int majorVersion; public CostInfo(ApplicationId applicationId, ZoneId zoneId, BigDecimal cpuHours, BigDecimal memoryHours, BigDecimal diskHours, - BigDecimal cpuCost, BigDecimal memoryCost, BigDecimal diskCost, NodeResources.Architecture architecture) { + BigDecimal cpuCost, BigDecimal memoryCost, BigDecimal diskCost, NodeResources.Architecture architecture, int majorVersion) { this.applicationId = applicationId; this.zoneId = zoneId; this.cpuHours = cpuHours; @@ -35,6 +36,7 @@ public class CostInfo { this.memoryCost = memoryCost; this.diskCost = diskCost; this.architecture = architecture; + this.majorVersion = majorVersion; } public ApplicationId getApplicationId() { @@ -77,4 +79,8 @@ public class CostInfo { return architecture; } + public int getMajorVersion() { + return majorVersion; + } + } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClientMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClientMock.java index 542bdd52477..d04d400d36f 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClientMock.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClientMock.java @@ -79,6 +79,7 @@ public class ResourceDatabaseClientMock implements ResourceDatabaseClient { a.getZoneId(), plan, a.getArchitecture(), + a.getVersion().getMajor(), BigDecimal.valueOf(a.getCpuCores()).multiply(d), BigDecimal.valueOf(a.getMemoryGb()).multiply(d), BigDecimal.valueOf(a.getDiskGb()).multiply(d) @@ -97,6 +98,7 @@ public class ResourceDatabaseClientMock implements ResourceDatabaseClient { a.getZoneId(), a.getPlan(), a.getArchitecture(), + a.getMajorVersion(), a.getCpuMillis().add(b.getCpuMillis()), a.getMemoryMillis().add(b.getMemoryMillis()), a.getDiskMillis().add(b.getDiskMillis()) @@ -112,7 +114,7 @@ public class ResourceDatabaseClientMock implements ResourceDatabaseClient { .filter(snapshot -> snapshot.getTimestamp().isBefore(Instant.ofEpochMilli(end))) .filter(snapshot -> snapshot.getApplicationId().tenant().equals(tenantName)) .collect(Collectors.groupingBy( - usage -> Objects.hash(usage.getApplicationId(), usage.getZoneId(), tenantPlan.id().value()) + usage -> Objects.hash(usage.getApplicationId(), usage.getZoneId(), tenantPlan.id().value(), usage.getArchitecture(), usage.getVersion().getMajor()) )) .values().stream() .map(snapshots -> resourceUsageFromSnapshots(tenantPlan, snapshots)) diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceSnapshot.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceSnapshot.java index 85ee23f4df0..4ff79706683 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceSnapshot.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceSnapshot.java @@ -1,6 +1,7 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.api.integration.resource; +import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.zone.ZoneId; @@ -23,16 +24,18 @@ public class ResourceSnapshot { private final ResourceAllocation resourceAllocation; private final Instant timestamp; private final ZoneId zoneId; + private final Version version; - public ResourceSnapshot(ApplicationId applicationId, double cpuCores, double memoryGb, double diskGb, NodeResources.Architecture architecture, Instant timestamp, ZoneId zoneId) { + public ResourceSnapshot(ApplicationId applicationId, double cpuCores, double memoryGb, double diskGb, NodeResources.Architecture architecture, Instant timestamp, ZoneId zoneId, Version version) { this.applicationId = applicationId; this.resourceAllocation = new ResourceAllocation(cpuCores, memoryGb, diskGb, architecture); this.timestamp = timestamp; this.zoneId = zoneId; + this.version = version; } public static ResourceSnapshot from(ApplicationId applicationId, int nodes, double cpuCores, double memoryGb, double diskGb, NodeResources.Architecture architecture, Instant timestamp, ZoneId zoneId) { - return new ResourceSnapshot(applicationId, cpuCores * nodes, memoryGb * nodes, diskGb * nodes, architecture, timestamp, zoneId); + return new ResourceSnapshot(applicationId, cpuCores * nodes, memoryGb * nodes, diskGb * nodes, architecture, timestamp, zoneId, Version.emptyVersion); } public static ResourceSnapshot from(List nodes, Instant timestamp, ZoneId zoneId) { @@ -50,7 +53,8 @@ public class ResourceSnapshot { nodes.stream().map(Node::resources).mapToDouble(NodeResources::diskGb).sum(), nodes.stream().map(node -> node.resources().architecture()).findFirst().orElse(NodeResources.Architecture.getDefault()), timestamp, - zoneId + zoneId, + nodes.stream().map(Node::currentVersion).min(Version::compareTo).orElse(Version.emptyVersion) ); } @@ -82,6 +86,10 @@ public class ResourceSnapshot { return zoneId; } + public Version getVersion() { + return version; + } + public NodeResources.Architecture getArchitecture() { return resourceAllocation.getArchitecture(); } @@ -95,11 +103,12 @@ public class ResourceSnapshot { return this.applicationId.equals(other.applicationId) && this.resourceAllocation.equals(other.resourceAllocation) && this.timestamp.equals(other.timestamp) && - this.zoneId.equals(other.zoneId); + this.zoneId.equals(other.zoneId) && + this.version.equals(other.version); } @Override public int hashCode(){ - return Objects.hash(applicationId, resourceAllocation, timestamp, zoneId); + return Objects.hash(applicationId, resourceAllocation, timestamp, zoneId, version); } } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceUsage.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceUsage.java index 850ca040d03..bd1252d0879 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceUsage.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceUsage.java @@ -1,6 +1,7 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.api.integration.resource; +import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.zone.ZoneId; @@ -17,9 +18,10 @@ public class ResourceUsage { private final BigDecimal memoryMillis; private final BigDecimal diskMillis; private final NodeResources.Architecture architecture; + private final int majorVersion; public ResourceUsage(ApplicationId applicationId, ZoneId zoneId, Plan plan, NodeResources.Architecture architecture, - BigDecimal cpuMillis, BigDecimal memoryMillis, BigDecimal diskMillis) { + int majorVersion, BigDecimal cpuMillis, BigDecimal memoryMillis, BigDecimal diskMillis) { this.applicationId = applicationId; this.zoneId = zoneId; this.cpuMillis = cpuMillis; @@ -27,6 +29,7 @@ public class ResourceUsage { this.diskMillis = diskMillis; this.plan = plan; this.architecture = architecture; + this.majorVersion = majorVersion; } public ApplicationId getApplicationId() { @@ -53,6 +56,10 @@ public class ResourceUsage { return plan; } + public int getMajorVersion() { + return majorVersion; + } + public NodeResources.Architecture getArchitecture() { return architecture; } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java index 307880682d9..703e95d09f2 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java @@ -35,8 +35,6 @@ import java.io.IOException; import java.math.BigDecimal; import java.security.Principal; import java.time.LocalDate; -import java.time.ZoneId; -import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.Comparator; @@ -44,7 +42,6 @@ import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.Executor; -import java.util.stream.Collectors; /** * @author andreer diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java index 157abe17841..cecf0b1a2cf 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java @@ -61,18 +61,18 @@ public class ResourceMeterMaintainerTest { .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().cost().getAsDouble()))); List resourceSnapshots = List.of( - new ResourceSnapshot(app1, 12, 34, 56, NodeResources.Architecture.getDefault(), Instant.EPOCH, z1), - new ResourceSnapshot(app1, 23, 45, 67, NodeResources.Architecture.getDefault(), Instant.EPOCH, z2), - new ResourceSnapshot(app2, 34, 56, 78, NodeResources.Architecture.getDefault(), Instant.EPOCH, z1)); + new ResourceSnapshot(app1, 12, 34, 56, NodeResources.Architecture.getDefault(), Instant.EPOCH, z1, Version.emptyVersion), + new ResourceSnapshot(app1, 23, 45, 67, NodeResources.Architecture.getDefault(), Instant.EPOCH, z2, Version.emptyVersion), + new ResourceSnapshot(app2, 34, 56, 78, NodeResources.Architecture.getDefault(), Instant.EPOCH, z1, Version.emptyVersion)); maintainer.updateDeploymentCost(resourceSnapshots); assertCost.accept(app1, Map.of(z1, 1.72, z2, 3.05)); assertCost.accept(app2, Map.of(z1, 4.39)); // Remove a region from app1 and add region to app2 resourceSnapshots = List.of( - new ResourceSnapshot(app1, 23, 45, 67, NodeResources.Architecture.getDefault(), Instant.EPOCH, z2), - new ResourceSnapshot(app2, 34, 56, 78, NodeResources.Architecture.getDefault(), Instant.EPOCH, z1), - new ResourceSnapshot(app2, 45, 67, 89, NodeResources.Architecture.getDefault(), Instant.EPOCH, z2)); + new ResourceSnapshot(app1, 23, 45, 67, NodeResources.Architecture.getDefault(), Instant.EPOCH, z2, Version.emptyVersion), + new ResourceSnapshot(app2, 34, 56, 78, NodeResources.Architecture.getDefault(), Instant.EPOCH, z1, Version.emptyVersion), + new ResourceSnapshot(app2, 45, 67, 89, NodeResources.Architecture.getDefault(), Instant.EPOCH, z2, Version.emptyVersion)); maintainer.updateDeploymentCost(resourceSnapshots); assertCost.accept(app1, Map.of(z2, 3.05)); assertCost.accept(app2, Map.of(z1, 4.39, z2, 5.72)); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java index e3a0684771c..9adfc83da5c 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java @@ -2,6 +2,7 @@ package com.yahoo.vespa.hosted.controller.restapi.controller; import com.yahoo.application.container.handler.Request; +import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.zone.ZoneId; @@ -158,8 +159,8 @@ public class ControllerApiTest extends ControllerContainerTest { Instant timestamp = Instant.ofEpochMilli(123456789); ZoneId zoneId = ZoneId.defaultId(); List snapshots = List.of( - new ResourceSnapshot(applicationId, 12, 48, 1200, NodeResources.Architecture.arm64, timestamp, zoneId), - new ResourceSnapshot(applicationId, 24, 96, 2400, NodeResources.Architecture.x86_64, timestamp, zoneId) + new ResourceSnapshot(applicationId, 12, 48, 1200, NodeResources.Architecture.arm64, timestamp, zoneId, Version.emptyVersion), + new ResourceSnapshot(applicationId, 24, 96, 2400, NodeResources.Architecture.x86_64, timestamp, zoneId, Version.emptyVersion) ); tester.controller().serviceRegistry().resourceDatabase().writeResourceSnapshots(snapshots); tester.assertResponse( -- cgit v1.2.3 From 88a602d98d87475d1fb3757ae93295e84b936ee5 Mon Sep 17 00:00:00 2001 From: Øyvind Grønnesby Date: Thu, 23 Feb 2023 09:05:28 +0100 Subject: Add majorVersion and architecture to API output --- .../hosted/controller/restapi/billing/BillingApiHandler.java | 6 ++++++ .../hosted/controller/restapi/billing/BillingApiHandlerV2.java | 2 ++ .../controller/restapi/billing/BillingApiHandlerV2Test.java | 2 +- .../restapi/billing/responses/billing-all-tenants.json | 9 ++++++--- .../controller/restapi/billing/responses/line-item-list.json | 3 ++- .../restapi/billing/responses/tenant-billing-view.json | 6 ++++-- 6 files changed, 21 insertions(+), 7 deletions(-) diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java index 703e95d09f2..5928a50c907 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java @@ -413,6 +413,12 @@ public class BillingApiHandler extends ThreadedHttpRequestHandler { cursor.setString("zone", zoneId.value()) ); + lineItem.getArchitecture().ifPresent(architecture -> { + cursor.setString("architecture", architecture.name()); + }); + + cursor.setLong("majorVersion", lineItem.getMajorVersion()); + lineItem.getCpuHours().ifPresent(cpuHours -> cursor.setString("cpuHours", cpuHours.toString()) ); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2.java index 0ddaa409ef8..67dd172fd83 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2.java @@ -311,6 +311,8 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler new RuntimeException("No such plan: '" + item.plan() + "'"))); + item.getArchitecture().ifPresent(arch -> slime.setString("architecture", arch.name())); + slime.setLong("majorVersion", item.getMajorVersion()); item.applicationId().ifPresent(appId -> { slime.setString("application", appId.application().value()); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2Test.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2Test.java index 857dcbac6fd..43271277ce9 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2Test.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2Test.java @@ -103,7 +103,7 @@ public class BillingApiHandlerV2Test extends ControllerContainerCloudTest { var singleRequest = request("/billing/v2/tenant/" + tenant + "/bill/id-1").roles(tenantReader); tester.assertResponse(singleRequest, """ - {"id":"id-1","from":"2020-05-23","to":"2020-05-28","total":"123.00","status":"OPEN","statusHistory":[{"at":"2020-05-23T00:00:00Z","status":"OPEN"}],"items":[{"id":"some-id","description":"description","amount":"123.00","plan":{"id":"paid","name":"Paid Plan - for testing purposes"},"cpu":{},"memory":{},"disk":{}}]}"""); + {"id":"id-1","from":"2020-05-23","to":"2020-05-28","total":"123.00","status":"OPEN","statusHistory":[{"at":"2020-05-23T00:00:00Z","status":"OPEN"}],"items":[{"id":"some-id","description":"description","amount":"123.00","plan":{"id":"paid","name":"Paid Plan - for testing purposes"},"majorVersion":0,"cpu":{},"memory":{},"disk":{}}]}"""); } @Test diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/billing-all-tenants.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/billing-all-tenants.json index e9b18a879b9..d761439667a 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/billing-all-tenants.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/billing-all-tenants.json @@ -16,7 +16,8 @@ "description": "description", "amount": "123.00", "plan": "paid", - "planName": "Plan with id: paid" + "planName": "Plan with id: paid", + "majorVersion": 0 } ] }, @@ -27,7 +28,8 @@ "description": "support", "amount": "42.00", "plan": "some-plan", - "planName": "Plan with id: some-plan" + "planName": "Plan with id: some-plan", + "majorVersion": 0 } ] } @@ -47,7 +49,8 @@ "description": "description", "amount": "123.00", "plan": "paid", - "planName": "Plan with id: paid" + "planName": "Plan with id: paid", + "majorVersion": 0 } ] }, diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/line-item-list.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/line-item-list.json index e8404b12dd8..fbfc5ce09ee 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/line-item-list.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/line-item-list.json @@ -5,7 +5,8 @@ "description": "some description", "amount": "123.45", "plan": "some-plan", - "planName": "Plan with id: some-plan" + "planName": "Plan with id: some-plan", + "majorVersion": 0 } ] } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/tenant-billing-view.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/tenant-billing-view.json index adb319a3642..4e255205e19 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/tenant-billing-view.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/tenant-billing-view.json @@ -12,7 +12,8 @@ "description": "description", "amount": "123.00", "plan": "paid", - "planName": "Plan with id: paid" + "planName": "Plan with id: paid", + "majorVersion": 0 } ] }, @@ -38,7 +39,8 @@ "description": "description", "amount": "123.00", "plan": "paid", - "planName": "Plan with id: paid" + "planName": "Plan with id: paid", + "majorVersion": 0 } ] } -- cgit v1.2.3 From 3204ecd71bed82fe25bbc50688aa5516b55b989a Mon Sep 17 00:00:00 2001 From: Øyvind Grønnesby Date: Thu, 23 Feb 2023 14:06:25 +0100 Subject: Use ResourceAllocation less. NodeResources does the job --- .../api/integration/resource/MeteringData.java | 42 ----------------- .../resource/ResourceDatabaseClientMock.java | 10 ++-- .../api/integration/resource/ResourceSnapshot.java | 54 ++++++++-------------- .../maintenance/ResourceMeterMaintainer.java | 20 ++++---- .../restapi/controller/MeteringResponse.java | 8 ++-- .../maintenance/ResourceMeterMaintainerTest.java | 30 +++++++----- .../restapi/controller/ControllerApiTest.java | 10 ++-- 7 files changed, 64 insertions(+), 110 deletions(-) delete mode 100644 controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/MeteringData.java diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/MeteringData.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/MeteringData.java deleted file mode 100644 index 59ea8440701..00000000000 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/MeteringData.java +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.api.integration.resource; - -import com.yahoo.config.provision.ApplicationId; - -import java.util.List; -import java.util.Map; - -/** - * @author olaa - */ -public class MeteringData { - - private final ResourceAllocation thisMonth; - private final ResourceAllocation lastMonth; - private final ResourceAllocation currentSnapshot; - Map> snapshotHistory; - - public MeteringData(ResourceAllocation thisMonth, ResourceAllocation lastMonth, ResourceAllocation currentSnapshot, Map> snapshotHistory) { - this.thisMonth = thisMonth; - this.lastMonth = lastMonth; - this.currentSnapshot = currentSnapshot; - this.snapshotHistory = snapshotHistory; - } - - public ResourceAllocation getThisMonth() { - return thisMonth; - } - - public ResourceAllocation getLastMonth() { - return lastMonth; - } - - public ResourceAllocation getCurrentSnapshot() { - return currentSnapshot; - } - - public Map> getSnapshotHistory() { - return snapshotHistory; - } - -} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClientMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClientMock.java index d04d400d36f..c1d210e990d 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClientMock.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClientMock.java @@ -78,11 +78,11 @@ public class ResourceDatabaseClientMock implements ResourceDatabaseClient { a.getApplicationId(), a.getZoneId(), plan, - a.getArchitecture(), + a.resources().architecture(), a.getVersion().getMajor(), - BigDecimal.valueOf(a.getCpuCores()).multiply(d), - BigDecimal.valueOf(a.getMemoryGb()).multiply(d), - BigDecimal.valueOf(a.getDiskGb()).multiply(d) + BigDecimal.valueOf(a.resources().vcpu()).multiply(d), + BigDecimal.valueOf(a.resources().memoryGb()).multiply(d), + BigDecimal.valueOf(a.resources().diskGb()).multiply(d) ); }) .toList(); @@ -114,7 +114,7 @@ public class ResourceDatabaseClientMock implements ResourceDatabaseClient { .filter(snapshot -> snapshot.getTimestamp().isBefore(Instant.ofEpochMilli(end))) .filter(snapshot -> snapshot.getApplicationId().tenant().equals(tenantName)) .collect(Collectors.groupingBy( - usage -> Objects.hash(usage.getApplicationId(), usage.getZoneId(), tenantPlan.id().value(), usage.getArchitecture(), usage.getVersion().getMajor()) + usage -> Objects.hash(usage.getApplicationId(), usage.getZoneId(), tenantPlan.id().value(), usage.resources().architecture(), usage.getVersion().getMajor()) )) .values().stream() .map(snapshots -> resourceUsageFromSnapshots(tenantPlan, snapshots)) diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceSnapshot.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceSnapshot.java index 4ff79706683..4a849b831b7 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceSnapshot.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceSnapshot.java @@ -21,21 +21,21 @@ import java.util.stream.Collectors; public class ResourceSnapshot { private final ApplicationId applicationId; - private final ResourceAllocation resourceAllocation; + private final NodeResources resources; private final Instant timestamp; private final ZoneId zoneId; private final Version version; - public ResourceSnapshot(ApplicationId applicationId, double cpuCores, double memoryGb, double diskGb, NodeResources.Architecture architecture, Instant timestamp, ZoneId zoneId, Version version) { + public ResourceSnapshot(ApplicationId applicationId, NodeResources resources, Instant timestamp, ZoneId zoneId, Version version) { this.applicationId = applicationId; - this.resourceAllocation = new ResourceAllocation(cpuCores, memoryGb, diskGb, architecture); + this.resources = resources; this.timestamp = timestamp; this.zoneId = zoneId; this.version = version; } - public static ResourceSnapshot from(ApplicationId applicationId, int nodes, double cpuCores, double memoryGb, double diskGb, NodeResources.Architecture architecture, Instant timestamp, ZoneId zoneId) { - return new ResourceSnapshot(applicationId, cpuCores * nodes, memoryGb * nodes, diskGb * nodes, architecture, timestamp, zoneId, Version.emptyVersion); + public static ResourceSnapshot from(ApplicationId applicationId, int nodes, NodeResources resources, Instant timestamp, ZoneId zoneId) { + return new ResourceSnapshot(applicationId, resources.multipliedBy(nodes), timestamp, zoneId, Version.emptyVersion); } public static ResourceSnapshot from(List nodes, Instant timestamp, ZoneId zoneId) { @@ -44,38 +44,26 @@ public class ResourceSnapshot { .map(node -> node.owner().get()) .collect(Collectors.toSet()); + Set versions = nodes.stream() + .map(Node::currentVersion) + .collect(Collectors.toSet()); + if (applicationIds.size() != 1) throw new IllegalArgumentException("List of nodes can only represent one application"); + if (versions.size() != 1) throw new IllegalArgumentException("List of nodes can only represent one version"); + + var resources = nodes.stream() + .map(Node::resources) + .reduce(NodeResources.zero(), NodeResources::add); - return new ResourceSnapshot( - applicationIds.iterator().next(), - nodes.stream().map(Node::resources).mapToDouble(NodeResources::vcpu).sum(), - nodes.stream().map(Node::resources).mapToDouble(NodeResources::memoryGb).sum(), - nodes.stream().map(Node::resources).mapToDouble(NodeResources::diskGb).sum(), - nodes.stream().map(node -> node.resources().architecture()).findFirst().orElse(NodeResources.Architecture.getDefault()), - timestamp, - zoneId, - nodes.stream().map(Node::currentVersion).min(Version::compareTo).orElse(Version.emptyVersion) - ); + return new ResourceSnapshot(applicationIds.iterator().next(), resources, timestamp, zoneId, versions.iterator().next()); } public ApplicationId getApplicationId() { return applicationId; } - public ResourceAllocation allocation() { - return resourceAllocation; - } - - public double getCpuCores() { - return resourceAllocation.getCpuCores(); - } - - public double getMemoryGb() { - return resourceAllocation.getMemoryGb(); - } - - public double getDiskGb() { - return resourceAllocation.getDiskGb(); + public NodeResources resources() { + return resources; } public Instant getTimestamp() { @@ -90,10 +78,6 @@ public class ResourceSnapshot { return version; } - public NodeResources.Architecture getArchitecture() { - return resourceAllocation.getArchitecture(); - } - @Override public boolean equals(Object o) { if (this == o) return true; @@ -101,7 +85,7 @@ public class ResourceSnapshot { ResourceSnapshot other = (ResourceSnapshot) o; return this.applicationId.equals(other.applicationId) && - this.resourceAllocation.equals(other.resourceAllocation) && + this.resources.equals(other.resources) && this.timestamp.equals(other.timestamp) && this.zoneId.equals(other.zoneId) && this.version.equals(other.version); @@ -109,6 +93,6 @@ public class ResourceSnapshot { @Override public int hashCode(){ - return Objects.hash(applicationId, resourceAllocation, timestamp, zoneId, version); + return Objects.hash(applicationId, resources, timestamp, zoneId, version); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java index d47b82a231f..059986072f3 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java @@ -120,7 +120,7 @@ public class ResourceMeterMaintainer extends ControllerMaintainer { Map deploymentCosts = snapshotsByInstance.getOrDefault(instanceName, List.of()).stream() .collect(Collectors.toUnmodifiableMap( ResourceSnapshot::getZoneId, - snapshot -> cost(snapshot.allocation(), systemName), + snapshot -> cost(snapshot.resources(), systemName), Double::sum)); locked = locked.with(instanceName, i -> i.withDeploymentCosts(deploymentCosts)); updateCostMetrics(tenantAndApplication.instance(instanceName), deploymentCosts); @@ -234,10 +234,14 @@ public class ResourceMeterMaintainer extends ControllerMaintainer { } private static double cost(ResourceAllocation allocation, SystemName systemName) { + var resources = new NodeResources(allocation.getCpuCores(), allocation.getMemoryGb(), allocation.getDiskGb(), 0); + return cost(resources, systemName); + } + + private static double cost(NodeResources resources, SystemName systemName) { // Divide cost by 3 in non-public zones to show approx. AWS equivalent cost double costDivisor = systemName.isPublic() ? 1.0 : 3.0; - double cost = new NodeResources(allocation.getCpuCores(), allocation.getMemoryGb(), allocation.getDiskGb(), 0).cost(); - return Math.round(cost * 100.0 / costDivisor) / 100.0; + return Math.round(resources.cost() * 100.0 / costDivisor) / 100.0; } private void updateMeteringMetrics(Collection resourceSnapshots) { @@ -245,14 +249,14 @@ public class ResourceMeterMaintainer extends ControllerMaintainer { // total metered resource usage, for alerting on drastic changes metric.set(METERING_TOTAL_REPORTED, resourceSnapshots.stream() - .mapToDouble(r -> r.getCpuCores() + r.getMemoryGb() + r.getDiskGb()).sum(), + .mapToDouble(r -> r.resources().vcpu() + r.resources().memoryGb() + r.resources().diskGb()).sum(), metric.createContext(Collections.emptyMap())); resourceSnapshots.forEach(snapshot -> { var context = getMetricContext(snapshot); - metric.set("metering.vcpu", snapshot.getCpuCores(), context); - metric.set("metering.memoryGB", snapshot.getMemoryGb(), context); - metric.set("metering.diskGB", snapshot.getDiskGb(), context); + metric.set("metering.vcpu", snapshot.resources().vcpu(), context); + metric.set("metering.memoryGB", snapshot.resources().memoryGb(), context); + metric.set("metering.diskGB", snapshot.resources().diskGb(), context); }); } @@ -276,7 +280,7 @@ public class ResourceMeterMaintainer extends ControllerMaintainer { "tenant", snapshot.getApplicationId().tenant().value(), "applicationId", snapshot.getApplicationId().toFullString(), "zoneId", snapshot.getZoneId().value(), - "architecture", snapshot.getArchitecture() + "architecture", snapshot.resources().architecture() )); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/MeteringResponse.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/MeteringResponse.java index 17461aafd02..7df216d6c9c 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/MeteringResponse.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/MeteringResponse.java @@ -29,10 +29,10 @@ public class MeteringResponse extends SlimeJsonResponse { object.setString("applicationId", snapshot.getApplicationId().toShortString()); object.setLong("timestamp", snapshot.getTimestamp().toEpochMilli()); object.setString("zoneId", snapshot.getZoneId().value()); - object.setDouble("cpu", snapshot.getCpuCores()); - object.setDouble("memory", snapshot.getMemoryGb()); - object.setDouble("disk", snapshot.getDiskGb()); - object.setString("architecture", snapshot.getArchitecture().name()); + object.setDouble("cpu", snapshot.resources().vcpu()); + object.setDouble("memory", snapshot.resources().memoryGb()); + object.setDouble("disk", snapshot.resources().diskGb()); + object.setString("architecture", snapshot.resources().architecture().name()); }); return slime; } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java index cecf0b1a2cf..3034c93e593 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java @@ -61,18 +61,20 @@ public class ResourceMeterMaintainerTest { .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().cost().getAsDouble()))); List resourceSnapshots = List.of( - new ResourceSnapshot(app1, 12, 34, 56, NodeResources.Architecture.getDefault(), Instant.EPOCH, z1, Version.emptyVersion), - new ResourceSnapshot(app1, 23, 45, 67, NodeResources.Architecture.getDefault(), Instant.EPOCH, z2, Version.emptyVersion), - new ResourceSnapshot(app2, 34, 56, 78, NodeResources.Architecture.getDefault(), Instant.EPOCH, z1, Version.emptyVersion)); + new ResourceSnapshot(app1, resources(12, 34, 56), Instant.EPOCH, z1, Version.emptyVersion), + new ResourceSnapshot(app1, resources(23, 45, 67), Instant.EPOCH, z2, Version.emptyVersion), + new ResourceSnapshot(app2, resources(34, 56, 78), Instant.EPOCH, z1, Version.emptyVersion)); + maintainer.updateDeploymentCost(resourceSnapshots); assertCost.accept(app1, Map.of(z1, 1.72, z2, 3.05)); assertCost.accept(app2, Map.of(z1, 4.39)); // Remove a region from app1 and add region to app2 resourceSnapshots = List.of( - new ResourceSnapshot(app1, 23, 45, 67, NodeResources.Architecture.getDefault(), Instant.EPOCH, z2, Version.emptyVersion), - new ResourceSnapshot(app2, 34, 56, 78, NodeResources.Architecture.getDefault(), Instant.EPOCH, z1, Version.emptyVersion), - new ResourceSnapshot(app2, 45, 67, 89, NodeResources.Architecture.getDefault(), Instant.EPOCH, z2, Version.emptyVersion)); + new ResourceSnapshot(app1, resources(23, 45, 67), Instant.EPOCH, z2, Version.emptyVersion), + new ResourceSnapshot(app2, resources(34, 56, 78), Instant.EPOCH, z1, Version.emptyVersion), + new ResourceSnapshot(app2, resources(45, 67, 89), Instant.EPOCH, z2, Version.emptyVersion)); + maintainer.updateDeploymentCost(resourceSnapshots); assertCost.accept(app1, Map.of(z2, 3.05)); assertCost.accept(app2, Map.of(z1, 4.39, z2, 5.72)); @@ -97,13 +99,13 @@ public class ResourceMeterMaintainerTest { ResourceSnapshot app1 = consumedResources.stream().filter(snapshot -> snapshot.getApplicationId().equals(ApplicationId.from("tenant1", "app1", "default"))).findFirst().orElseThrow(); ResourceSnapshot app2 = consumedResources.stream().filter(snapshot -> snapshot.getApplicationId().equals(ApplicationId.from("tenant2", "app2", "default"))).findFirst().orElseThrow(); - assertEquals(24, app1.getCpuCores(), Double.MIN_VALUE); - assertEquals(24, app1.getMemoryGb(), Double.MIN_VALUE); - assertEquals(500, app1.getDiskGb(), Double.MIN_VALUE); + assertEquals(24, app1.resources().vcpu(), Double.MIN_VALUE); + assertEquals(24, app1.resources().memoryGb(), Double.MIN_VALUE); + assertEquals(500, app1.resources().diskGb(), Double.MIN_VALUE); - assertEquals(40, app2.getCpuCores(), Double.MIN_VALUE); - assertEquals(24, app2.getMemoryGb(), Double.MIN_VALUE); - assertEquals(500, app2.getDiskGb(), Double.MIN_VALUE); + assertEquals(40, app2.resources().vcpu(), Double.MIN_VALUE); + assertEquals(24, app2.resources().memoryGb(), Double.MIN_VALUE); + assertEquals(500, app2.resources().diskGb(), Double.MIN_VALUE); assertEquals(tester.clock().millis() / 1000, metrics.getMetric("metering_last_reported")); assertEquals(2224.0d, (Double) metrics.getMetric("metering_total_reported"), Double.MIN_VALUE); @@ -154,4 +156,8 @@ public class ResourceMeterMaintainerTest { .build()) .toList(); } + + private NodeResources resources(double cpu, double ram, double disk) { + return new NodeResources(cpu, ram, disk, 0, NodeResources.DiskSpeed.getDefault(), NodeResources.StorageType.getDefault(), NodeResources.Architecture.getDefault(), NodeResources.GpuResources.getDefault()); + } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java index 9adfc83da5c..ac7287b7e27 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java @@ -158,10 +158,12 @@ public class ControllerApiTest extends ControllerContainerTest { ApplicationId applicationId = ApplicationId.from("tenant", "app", "instance"); Instant timestamp = Instant.ofEpochMilli(123456789); ZoneId zoneId = ZoneId.defaultId(); - List snapshots = List.of( - new ResourceSnapshot(applicationId, 12, 48, 1200, NodeResources.Architecture.arm64, timestamp, zoneId, Version.emptyVersion), - new ResourceSnapshot(applicationId, 24, 96, 2400, NodeResources.Architecture.x86_64, timestamp, zoneId, Version.emptyVersion) - ); + var resources = List.of( + new NodeResources(12, 48, 1200, 0, NodeResources.DiskSpeed.any, NodeResources.StorageType.any, NodeResources.Architecture.arm64), + new NodeResources(24, 96, 2400, 0, NodeResources.DiskSpeed.any, NodeResources.StorageType.any, NodeResources.Architecture.x86_64)); + + var snapshots = resources.stream().map(x -> new ResourceSnapshot(applicationId, x, timestamp, zoneId, Version.emptyVersion)).toList(); + tester.controller().serviceRegistry().resourceDatabase().writeResourceSnapshots(snapshots); tester.assertResponse( operatorRequest("http://localhost:8080/controller/v1/metering/tenant/tenantName/month/2020-02", "", Request.Method.GET), -- cgit v1.2.3 From 3c325c2ee25bc1d39190dfe7ee5b43eadc6225c0 Mon Sep 17 00:00:00 2001 From: Øyvind Grønnesby Date: Tue, 28 Feb 2023 11:01:40 +0100 Subject: Consider major version in node -> snapshot conversion --- .../maintenance/ResourceMeterMaintainer.java | 32 ++++++++++++---------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java index 059986072f3..26edd210075 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java @@ -201,10 +201,11 @@ public class ResourceMeterMaintainer extends ControllerMaintainer { // Grouping by ApplicationId -> Architecture -> ResourceSnapshot .collect(Collectors.groupingBy(node -> node.owner().get(), - groupSnapshotsByArchitecture(zoneId))) + groupSnapshotsByArchitectureAndMajorVersion(zoneId))) .values() .stream() - .flatMap(list -> list.values().stream()) + .flatMap(byArch -> byArch.values().stream()) + .flatMap(byMajor -> byMajor.values().stream()) .toList(); } @@ -284,19 +285,22 @@ public class ResourceMeterMaintainer extends ControllerMaintainer { )); } - private Collector> groupSnapshotsByArchitecture(ZoneId zoneId) { - return Collectors.collectingAndThen( - Collectors.groupingBy(node -> node.resources().architecture()), - convertNodeListToResourceSnapshot(zoneId) - ); + private Collector>> groupSnapshotsByArchitectureAndMajorVersion(ZoneId zoneId) { + return Collectors.groupingBy( + (Node node) -> node.resources().architecture(), + Collectors.collectingAndThen( + Collectors.groupingBy( + (Node node) -> node.currentVersion().getMajor(), + Collectors.toList()), + convertNodeListToResourceSnapshot(zoneId))); } - private Function>, Map> convertNodeListToResourceSnapshot(ZoneId zoneId) { - return nodeMap -> nodeMap.entrySet() - .stream() - .collect(Collectors.toMap( - Map.Entry::getKey, - entry -> ResourceSnapshot.from(entry.getValue(), clock.instant(), zoneId)) - ); + private Function>, Map> convertNodeListToResourceSnapshot(ZoneId zoneId) { + return nodesByMajor -> { + return nodesByMajor.entrySet().stream() + .collect(Collectors.toMap( + entry -> entry.getKey(), + entry -> ResourceSnapshot.from(entry.getValue(), clock.instant(), zoneId))); + }; } } -- cgit v1.2.3