summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHarald Musum <musum@yahooinc.com>2023-10-13 11:08:12 +0200
committerHarald Musum <musum@yahooinc.com>2023-10-13 11:08:12 +0200
commit543b44ab23ec1649c4ff7794e677f29bd92d48bc (patch)
treef83fce97fee60c2b2342349972b50443b12431ca
parente7d54515ca2a5eceea0bf17c5b96b969aea8c53a (diff)
Support calculating and presenting prices per applications and in total
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/MockPricingController.java11
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/pricing/PriceInformation.java17
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/pricing/PriceInformationApplications.java9
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/pricing/Prices.java14
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/pricing/PricingController.java4
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/pricing/PricingInfo.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/pricing/PricingApiHandler.java55
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/pricing/PricingApiHandlerTest.java79
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);
}