diff options
author | Øyvind Grønnesby <oyving@vespa.ai> | 2023-10-30 21:53:18 +0100 |
---|---|---|
committer | Øyvind Grønnesby <oyving@vespa.ai> | 2023-10-30 21:53:18 +0100 |
commit | 6866757fa3f88588cf0ffe29e6dd656bdf8ac0dc (patch) | |
tree | d9d5177efdf7bb6a3a7718e102b50a0d44709178 /controller-server/src | |
parent | 4bd554338d34e4c4884b7314ab7b06f5f061aa46 (diff) |
Allow for summarizing bills based on dimensions
Diffstat (limited to 'controller-server/src')
2 files changed, 67 insertions, 0 deletions
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 93c13e6ed4c..7a7ef080e17 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 @@ -40,10 +40,14 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; +import java.util.Arrays; import java.util.Comparator; +import java.util.EnumSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.logging.Logger; +import java.util.stream.Collectors; /** * @author ogronnesby @@ -114,6 +118,8 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler .addRoute(RestApi.route("/billing/v2/accountant/tenant/{tenant}/collection") .get(self::accountantTenantCollection) .post(Slime.class, self::setAccountantTenantCollection)) + .addRoute(RestApi.route("/billing/v2/accountant/bill/{invoice}/summary") + .get(self::accountantInvoiceSummary)) .addRoute(RestApi.route("/billing/v2/accountant/bill/{invoice}/export") .put(Slime.class, self::putAccountantInvoiceExport)) .addRoute(RestApi.route("/billing/v2/accountant/plans") @@ -432,6 +438,22 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler return slime; } + private Slime accountantInvoiceSummary(RestApi.RequestContext requestContext) { + var billId = requestContext.pathParameters().getString("invoice").map(Bill.Id::of).orElseThrow(RestApiException.NotFound::new); + var requestParam = requestContext.queryParameters().getString("keys").stream() + .flatMap(s -> Arrays.stream(s.split(","))) + .map(Bill.ItemKeyType::valueOf) + .toList(); + + var requestKeys = Bill.ItemRequest.of(requestParam); + var bill = billing.getBill(billId); + var response = bill.summarizeBy(requestKeys); + + var slime = new Slime(); + toSlime(slime.setObject(), bill, response); + return slime; + } + private MessageResponse setAccountantTenantCollection(RestApi.RequestContext requestContext, Slime body) { var tenantName = TenantName.from(requestContext.pathParameters().getStringOrThrow("tenant")); var tenant = tenants.require(tenantName, CloudTenant.class); @@ -574,6 +596,43 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler } } + private void toSlime(Cursor slime, Bill bill, Map<Bill.ItemKey, Bill.ItemSummary> summaries) { + slime.setString("id", bill.id().toString()); + var summaryCursor = slime.setArray("summary"); + summaries.forEach((key, summary) -> { + toSlime(summaryCursor.addObject(), key, summary); + }); + } + + private void toSlime(Cursor slime, Bill.ItemKey key, Bill.ItemSummary summary) { + toSlime(slime.setObject("key"), key); + toSlime(slime.setObject("summary"), summary); + } + + private void toSlime(Cursor slime, Bill.ItemKey key) { + key.keys().forEach((keyType, keyValue) -> { + slime.setString(keyType.name(), keyValue.toString()); + }); + } + + private void toSlime(Cursor slime, Bill.ItemSummary summary) { + var cpu = slime.setObject("cpu"); + cpu.setString("cost", summary.cpuCost().toPlainString()); + cpu.setString("hours", summary.cpuUsage().toPlainString()); + + var ram = slime.setObject("memory"); + ram.setString("cost", summary.ramCost().toPlainString()); + ram.setString("hours", summary.ramUsage().toPlainString()); + + var disk = slime.setObject("disk"); + disk.setString("cost", summary.diskCost().toPlainString()); + disk.setString("hours", summary.diskUsage().toPlainString()); + + var gpu = slime.setObject("gpu"); + gpu.setString("cost", summary.gpuCost().toPlainString()); + gpu.setString("hours", summary.gpuUsage().toPlainString()); + } + private List<Object[]> toCsv(Bill bill) { return List.<Object[]>of(new Object[]{ bill.id().value(), bill.tenant().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 356076a8d00..8729410d877 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 @@ -258,6 +258,14 @@ public class BillingApiHandlerV2Test extends ControllerContainerCloudTest { tester.assertJsonResponse(req, new File("accepted-countries.json")); } + @Test + void summarize_bill() { + var req = request("/billing/v2/accountant/bill/id-1/summary?keys=plan,architecture") + .roles(Role.hostedAccountant()); + tester.assertResponse(req, """ + {"id":"BillId{value='id-1'}","summary":[{"key":{"plan":"paid","architecture":"Optional.empty"},"summary":{"cpu":{"cost":"0","hours":"0"},"memory":{"cost":"0","hours":"0"},"disk":{"cost":"0","hours":"0"},"gpu":{"cost":"0","hours":"0"}}}]}"""); + } + private static Bill createBill() { var start = LocalDate.of(2020, 5, 23).atStartOfDay(ZoneOffset.UTC); var end = start.toLocalDate().plusDays(6).atStartOfDay(ZoneOffset.UTC); |