diff options
author | Harald Musum <musum@yahooinc.com> | 2023-10-13 11:08:12 +0200 |
---|---|---|
committer | Harald Musum <musum@yahooinc.com> | 2023-10-13 11:08:12 +0200 |
commit | 543b44ab23ec1649c4ff7794e677f29bd92d48bc (patch) | |
tree | f83fce97fee60c2b2342349972b50443b12431ca | |
parent | e7d54515ca2a5eceea0bf17c5b96b969aea8c53a (diff) |
Support calculating and presenting prices per applications and in total
8 files changed, 134 insertions, 56 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/MockPricingController.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/MockPricingController.java index c9df5a72a35..4de4ff29f1d 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/MockPricingController.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/MockPricingController.java @@ -5,6 +5,7 @@ import com.yahoo.config.provision.ClusterResources; import com.yahoo.vespa.hosted.controller.api.integration.billing.Plan; import com.yahoo.vespa.hosted.controller.api.integration.pricing.ApplicationResources; import com.yahoo.vespa.hosted.controller.api.integration.pricing.PriceInformation; +import com.yahoo.vespa.hosted.controller.api.integration.pricing.Prices; import com.yahoo.vespa.hosted.controller.api.integration.pricing.PricingController; import com.yahoo.vespa.hosted.controller.api.integration.pricing.PricingInfo; @@ -33,11 +34,11 @@ public class MockPricingController implements PricingController { BigDecimal volumeDiscount = new BigDecimal("-5.64315634"); BigDecimal committedAmountDiscount = new BigDecimal("-1.23"); BigDecimal totalAmount = listPrice.add(supportLevelCost).add(enclaveDiscount).add(volumeDiscount).add(committedAmountDiscount); - return new PriceInformation(listPriceWithSupport, volumeDiscount, committedAmountDiscount, enclaveDiscount, totalAmount); + return new PriceInformation("default", listPriceWithSupport, volumeDiscount, committedAmountDiscount, enclaveDiscount, totalAmount); } @Override - public PriceInformation priceForApplications(List<ApplicationResources> applicationResources, PricingInfo pricingInfo, Plan plan) { + public Prices priceForApplications(List<ApplicationResources> applicationResources, PricingInfo pricingInfo, Plan plan) { ApplicationResources resources = applicationResources.get(0); BigDecimal listPrice = resources.vcpu().multiply(valueOf(1000)) .add(resources.memoryGb().multiply(valueOf(100))) @@ -53,8 +54,10 @@ public class MockPricingController implements PricingController { BigDecimal committedAmountDiscount = new BigDecimal("-1.23"); BigDecimal totalAmount = listPrice.add(supportLevelCost).add(enclaveDiscount).add(volumeDiscount).add(committedAmountDiscount); - return new PriceInformation(listPriceWithSupport, volumeDiscount, committedAmountDiscount, enclaveDiscount, totalAmount); - } + var appPrice = new PriceInformation("app1", listPriceWithSupport, volumeDiscount, committedAmountDiscount, enclaveDiscount, totalAmount); + var totalPrice = new PriceInformation("total", ZERO, ZERO, committedAmountDiscount, enclaveDiscount, totalAmount); + return new Prices(List.of(appPrice), totalPrice); + } } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/pricing/PriceInformation.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/pricing/PriceInformation.java index 7bb7818c87b..2c37b122b04 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/pricing/PriceInformation.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/pricing/PriceInformation.java @@ -2,16 +2,25 @@ package com.yahoo.vespa.hosted.controller.api.integration.pricing; import java.math.BigDecimal; +import java.util.List; import static java.math.BigDecimal.ZERO; -public record PriceInformation(BigDecimal listPriceWithSupport, BigDecimal volumeDiscount, BigDecimal committedAmountDiscount, - BigDecimal enclaveDiscount, BigDecimal totalAmount) { +public record PriceInformation(String applicationName, BigDecimal listPriceWithSupport, BigDecimal volumeDiscount, + BigDecimal committedAmountDiscount, BigDecimal enclaveDiscount, BigDecimal totalAmount) { - public static PriceInformation empty() { return new PriceInformation(ZERO, ZERO, ZERO, ZERO, ZERO); } + public static PriceInformation empty() { return new PriceInformation("default", ZERO, ZERO, ZERO, ZERO, ZERO); } + + public static PriceInformation sum(List<PriceInformation> priceInformationList) { + var result = PriceInformation.empty(); + for (var prices : priceInformationList) + result = result.add(prices); + return result; + } public PriceInformation add(PriceInformation priceInformation) { - return new PriceInformation(this.listPriceWithSupport().add(priceInformation.listPriceWithSupport()), + return new PriceInformation("accumulated", + this.listPriceWithSupport().add(priceInformation.listPriceWithSupport()), this.volumeDiscount().add(priceInformation.volumeDiscount()), this.committedAmountDiscount().add(priceInformation.committedAmountDiscount()), this.enclaveDiscount().add(priceInformation.enclaveDiscount()), diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/pricing/PriceInformationApplications.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/pricing/PriceInformationApplications.java deleted file mode 100644 index d7da2308a16..00000000000 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/pricing/PriceInformationApplications.java +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.api.integration.pricing; - -import java.math.BigDecimal; - -public record PriceInformationApplications(BigDecimal listPriceWithSupport, BigDecimal volumeDiscount, BigDecimal committedAmountDiscount, - BigDecimal enclaveDiscount, BigDecimal totalAmount) { - -} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/pricing/Prices.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/pricing/Prices.java new file mode 100644 index 00000000000..2ebd6ba5d38 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/pricing/Prices.java @@ -0,0 +1,14 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.pricing; + +import java.util.List; + +public record Prices(List<PriceInformation> priceInformationApplications, PriceInformation totalPriceInformation) { + + public PriceInformation get(String applicationName) { + return priceInformationApplications.stream() + .filter(priceInformation -> priceInformation.applicationName().equals(applicationName)) + .findFirst().orElseThrow(() -> new IllegalArgumentException("Unknown application name " + applicationName)); + } + +} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/pricing/PricingController.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/pricing/PricingController.java index 85d83a32e4c..23d5f81dfb7 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/pricing/PricingController.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/pricing/PricingController.java @@ -13,7 +13,7 @@ import java.util.List; */ public interface PricingController { - // TOD: Legacy, will be removed when not in use anymore + // TODO: Legacy, will be removed when not in use anymore PriceInformation price(List<ClusterResources> clusterResources, PricingInfo pricingInfo, Plan plan); /** @@ -23,6 +23,6 @@ public interface PricingController { * @param plan the plan to use for this calculation * @return a PriceInformation instance */ - PriceInformation priceForApplications(List<ApplicationResources> applicationResources, PricingInfo pricingInfo, Plan plan); + Prices priceForApplications(List<ApplicationResources> applicationResources, PricingInfo pricingInfo, Plan plan); } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/pricing/PricingInfo.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/pricing/PricingInfo.java index 938991e2ed7..1ab149d083f 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/pricing/PricingInfo.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/pricing/PricingInfo.java @@ -1,6 +1,7 @@ // Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.api.integration.pricing; +// TODO: Use BigDecimal for committedHourlyAmount public record PricingInfo(boolean enclave, SupportLevel supportLevel, double committedHourlyAmount) { public enum SupportLevel { BASIC, COMMERCIAL, ENTERPRISE } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/pricing/PricingApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/pricing/PricingApiHandler.java index 82bfabfda23..5a5d26ed943 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/pricing/PricingApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/pricing/PricingApiHandler.java @@ -13,11 +13,13 @@ import com.yahoo.restapi.Path; import com.yahoo.restapi.SlimeJsonResponse; import com.yahoo.slime.Cursor; import com.yahoo.slime.Slime; +import com.yahoo.slime.SlimeUtils; import com.yahoo.text.Text; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.api.integration.billing.Plan; import com.yahoo.vespa.hosted.controller.api.integration.pricing.ApplicationResources; import com.yahoo.vespa.hosted.controller.api.integration.pricing.PriceInformation; +import com.yahoo.vespa.hosted.controller.api.integration.pricing.Prices; import com.yahoo.vespa.hosted.controller.api.integration.pricing.PricingInfo; import com.yahoo.vespa.hosted.controller.restapi.ErrorResponses; import com.yahoo.yolean.Exceptions; @@ -82,16 +84,24 @@ public class PricingApiHandler extends ThreadedHttpRequestHandler { private HttpResponse pricing(HttpRequest request) { String rawQuery = request.getUri().getRawQuery(); var priceParameters = parseQuery(rawQuery); - PriceInformation price = calculatePrice(priceParameters); - return response(price, priceParameters); + boolean isLegacy = priceParameters.appResources() == null; + if (isLegacy) { + PriceInformation price = calculateLegacyPrice(priceParameters); + return legacyResponse(price, priceParameters); + } else { + Prices price = calculatePrice(priceParameters); + return response(price, priceParameters); + } } - private PriceInformation calculatePrice(PriceParameters priceParameters) { + private PriceInformation calculateLegacyPrice(PriceParameters priceParameters) { var priceCalculator = controller.serviceRegistry().pricingController(); - if (priceParameters.appResources == null) - return priceCalculator.price(priceParameters.clusterResources, priceParameters.pricingInfo, priceParameters.plan); - else - return priceCalculator.priceForApplications(priceParameters.appResources, priceParameters.pricingInfo, priceParameters.plan); + return priceCalculator.price(priceParameters.clusterResources, priceParameters.pricingInfo, priceParameters.plan); + } + + private Prices calculatePrice(PriceParameters priceParameters) { + var priceCalculator = controller.serviceRegistry().pricingController(); + return priceCalculator.priceForApplications(priceParameters.appResources, priceParameters.pricingInfo, priceParameters.plan); } private PriceParameters parseQuery(String rawQuery) { @@ -230,7 +240,7 @@ public class PricingApiHandler extends ThreadedHttpRequestHandler { return controller.serviceRegistry().planRegistry().plan(element); } - private static SlimeJsonResponse response(PriceInformation priceInfo, PriceParameters priceParameters) { + private static SlimeJsonResponse legacyResponse(PriceInformation priceInfo, PriceParameters priceParameters) { var slime = new Slime(); Cursor cursor = slime.setObject(); @@ -245,6 +255,35 @@ public class PricingApiHandler extends ThreadedHttpRequestHandler { return new SlimeJsonResponse(slime); } + private static SlimeJsonResponse response(Prices prices, PriceParameters priceParameters) { + var slime = new Slime(); + Cursor cursor = slime.setObject(); + + var applicationsArray = cursor.setArray("applications"); + applicationPrices(applicationsArray, prices.priceInformationApplications(), priceParameters); + + var priceInfoArray = cursor.setArray("priceInfo"); + addItem(priceInfoArray, "Committed spend", prices.totalPriceInformation().committedAmountDiscount()); + + setBigDecimal(cursor, "totalAmount", prices.totalPriceInformation().totalAmount()); + + System.out.println(SlimeUtils.toJson(slime)); + + return new SlimeJsonResponse(slime); + } + + private static void applicationPrices(Cursor applicationPricesArray, List<PriceInformation> applicationPrices, PriceParameters priceParameters) { + applicationPrices.forEach(priceInformation -> { + var element = applicationPricesArray.addObject(); + element.setString("name", priceInformation.applicationName()); + var array = element.setArray("priceInfo"); + addItem(array, supportLevelDescription(priceParameters), priceInformation.listPriceWithSupport()); + addItem(array, "Enclave", priceInformation.enclaveDiscount()); + addItem(array, "Volume discount", priceInformation.volumeDiscount()); + + }); + } + private static String supportLevelDescription(PriceParameters priceParameters) { String supportLevel = priceParameters.pricingInfo.supportLevel().name(); return supportLevel.substring(0,1).toUpperCase() + supportLevel.substring(1).toLowerCase() + " support unit price"; diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/pricing/PricingApiHandlerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/pricing/PricingApiHandlerTest.java index d3767989c2d..8fc8fd25384 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/pricing/PricingApiHandlerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/pricing/PricingApiHandlerTest.java @@ -47,15 +47,22 @@ public class PricingApiHandlerTest extends ControllerContainerCloudTest { var request = request("/pricing/v1/pricing?" + urlEncodedPriceInformation1App(BASIC)); tester.assertJsonResponse(request, """ - { - "priceInfo": [ - {"description": "Basic support unit price", "amount": "2240.00"}, - {"description": "Volume discount", "amount": "-5.64"}, - {"description": "Committed spend", "amount": "-1.23"} - ], - "totalAmount": "2233.13" - } - """, + { + "applications": [ + { + "name": "app1", + "priceInfo": [ + {"description": "Basic support unit price", "amount": "2240.00"}, + {"description": "Volume discount", "amount": "-5.64"} + ] + } + ], + "priceInfo": [ + {"description": "Committed spend", "amount": "-1.23"} + ], + "totalAmount": "2233.13" + } + """, 200); } @@ -66,16 +73,23 @@ public class PricingApiHandlerTest extends ControllerContainerCloudTest { var request = request("/pricing/v1/pricing?" + urlEncodedPriceInformation1AppEnclave(BASIC)); tester.assertJsonResponse(request, """ - { - "priceInfo": [ - {"description": "Basic support unit price", "amount": "2240.00"}, - {"description": "Enclave", "amount": "-15.12"}, - {"description": "Volume discount", "amount": "-5.64"}, - {"description": "Committed spend", "amount": "-1.23"} - ], - "totalAmount": "2218.00" - } - """, + { + "applications": [ + { + "name": "app1", + "priceInfo": [ + {"description": "Basic support unit price", "amount": "2240.00"}, + {"description": "Enclave", "amount": "-15.12"}, + {"description": "Volume discount", "amount": "-5.64"} + ] + } + ], + "priceInfo": [ + {"description": "Committed spend", "amount": "-1.23"} + ], + "totalAmount": "2218.00" + } + """, 200); } @@ -106,16 +120,23 @@ public class PricingApiHandlerTest extends ControllerContainerCloudTest { var request = request("/pricing/v1/pricing?" + urlEncodedPriceInformation1AppEnclave(COMMERCIAL)); tester.assertJsonResponse(request, """ - { - "priceInfo": [ - {"description": "Commercial support unit price", "amount": "3200.00"}, - {"description": "Enclave", "amount": "-15.12"}, - {"description": "Volume discount", "amount": "-5.64"}, - {"description": "Committed spend", "amount": "-1.23"} - ], - "totalAmount": "3178.00" - } - """, + { + "applications": [ + { + "name": "app1", + "priceInfo": [ + {"description": "Commercial support unit price", "amount": "3200.00"}, + {"description": "Enclave", "amount": "-15.12"}, + {"description": "Volume discount", "amount": "-5.64"} + ] + } + ], + "priceInfo": [ + {"description": "Committed spend", "amount": "-1.23"} + ], + "totalAmount": "3178.00" + } + """, 200); } |