diff options
16 files changed, 163 insertions, 911 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/ServiceRegistry.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/ServiceRegistry.java index 93e3f5585c8..4714b74ba94 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/ServiceRegistry.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/ServiceRegistry.java @@ -7,8 +7,6 @@ import com.yahoo.vespa.hosted.controller.api.integration.aws.RoleService; import com.yahoo.vespa.hosted.controller.api.integration.aws.CloudEventFetcher; import com.yahoo.vespa.hosted.controller.api.integration.aws.ResourceTagger; import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingController; -import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingDatabaseClient; -import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanRegistry; import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateProvider; import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateValidator; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServer; @@ -27,7 +25,6 @@ import com.yahoo.vespa.hosted.controller.api.integration.organization.OwnershipI import com.yahoo.vespa.hosted.controller.api.integration.organization.SystemMonitor; import com.yahoo.vespa.hosted.controller.api.integration.resource.CostReportConsumer; import com.yahoo.vespa.hosted.controller.api.integration.resource.MeteringClient; -import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceDatabaseClient; import com.yahoo.vespa.hosted.controller.api.integration.routing.GlobalRoutingService; import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretService; import com.yahoo.vespa.hosted.controller.api.integration.vcmr.ChangeRequestClient; @@ -91,10 +88,6 @@ public interface ServiceRegistry { BillingController billingController(); - ResourceDatabaseClient resourceDatabase(); - - BillingDatabaseClient billingDatabase(); - ContainerRegistry containerRegistry(); TenantSecretService tenantSecretService(); @@ -106,6 +99,4 @@ public interface ServiceRegistry { AccessControlService accessControlService(); HorizonClient horizonClient(); - - PlanRegistry planRegistry(); } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingController.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingController.java index 61f8844482c..91916975146 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingController.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingController.java @@ -12,112 +12,55 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -/** - * A service that controls creation of bills based on the resource usage of a tenant, controls the quota for a - * tenant, and controls the plan the tenant is on. - * - * @author ogronnesby - * @author olaa - */ public interface BillingController { - /** - * Get the plan ID for the given tenant. - * This method will not fail if the tenant does not exist, it will return the default plan for that tenant instead. - */ PlanId getPlan(TenantName tenant); - /** - * Return the list of tenants with the given plan. - * @param existing All existing tenants in the system - * @param planId The ID of the plan to filter existing tenants on. - * @return The tenants that have the given plan. - */ List<TenantName> tenantsWithPlan(List<TenantName> existing, PlanId planId); - /** The display name of the given plan */ String getPlanDisplayName(PlanId planId); - /** - * The quota for the given tenant. - * This method will return default quota for tenants that do not exist. - */ Quota getQuota(TenantName tenant); /** - * Set the plan for the current tenant. Checks some pre-conditions to see if the tenant is eligible for the - * given plan. - * @param tenant The name of the tenant. - * @param planId The ID of the plan to change to. - * @param hasDeployments Does the tenant have active deployments. * @return String containing error message if something went wrong. Empty otherwise */ PlanResult setPlan(TenantName tenant, PlanId planId, boolean hasDeployments); - /** - * Create a bill of unbilled use for the given tenant in the given time period. - * @param tenant The name of the tenant. - * @param startTime The start of the billing period - * @param endTime The end of the billing period - * @param agent The agent that creates the bill - * @return The ID of the new bill. - */ - Bill.Id createBillForPeriod(TenantName tenant, ZonedDateTime startTime, ZonedDateTime endTime, String agent); + Invoice.Id createInvoiceForPeriod(TenantName tenant, ZonedDateTime startTime, ZonedDateTime endTime, String agent); - /** - * Create an unpersisted bill of unbilled use for the given tenant from the end of last bill until the given date. - * This is used to show "unbilled use" in the Console. - * @param tenant The name of the tenant. - * @param until The end date of the unbilled use period. - * @return A bill with the resource use and cost. - */ - Bill createUncommittedBill(TenantName tenant, LocalDate until); + Invoice createUncommittedInvoice(TenantName tenant, LocalDate until); - /** Run {createUncommittedBill} for all tenants with unbilled use */ - Map<TenantName, Bill> createUncommittedBills(LocalDate until); + Map<TenantName, Invoice> createUncommittedInvoices(LocalDate until); - /** Get line items that have been manually added to a tenant, but is not yet part of a bill */ - List<Bill.LineItem> getUnusedLineItems(TenantName tenant); + List<Invoice.LineItem> getUnusedLineItems(TenantName tenant); - /** Get the payment instrument for the given tenant */ Optional<PaymentInstrument> getDefaultInstrument(TenantName tenant); - /** Get the auth token needed to talk to payment services */ String createClientToken(String tenant, String userId); - /** Delete a payment instrument from the list of the tenant's instruments */ boolean deleteInstrument(TenantName tenant, String userId, String instrumentId); - /** Change the status of the given bill */ - void updateBillStatus(Bill.Id billId, String agent, String status); + void updateInvoiceStatus(Invoice.Id invoiceId, String agent, String status); - /** Add a line item to the given bill */ void addLineItem(TenantName tenant, String description, BigDecimal amount, String agent); - /** Delete a line item - only available for unused line items */ void deleteLineItem(String lineItemId); - /** Set the given payment instrument as the active instrument for the tenant */ boolean setActivePaymentInstrument(InstrumentOwner paymentInstrument); - /** List the payment instruments from the tenant */ InstrumentList listInstruments(TenantName tenant, String userId); - /** Get all bills for the given tenant */ - List<Bill> getBillsForTenant(TenantName tenant); + List<Invoice> getInvoicesForTenant(TenantName tenant); - /** Get all bills from the system */ - List<Bill> getBills(); + List<Invoice> getInvoices(); - /** Delete billing contact information from the tenant */ void deleteBillingInfo(TenantName tenant, Set<User> users, boolean isPrivileged); - /** Get the bill collection method for the given tenant */ default CollectionMethod getCollectionMethod(TenantName tenant) { return CollectionMethod.NONE; } - /** Set the bill collection method for the given tenant */ default CollectionResult setCollectionMethod(TenantName tenant, CollectionMethod method) { return CollectionResult.error("Method not implemented"); } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingDatabaseClient.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingDatabaseClient.java deleted file mode 100644 index 4891fe0ffa7..00000000000 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingDatabaseClient.java +++ /dev/null @@ -1,135 +0,0 @@ -// 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.billing; - -import com.yahoo.config.provision.TenantName; - -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -/** - * Interface that talks about bills in the billing API. It is a layer on top of the SQL - * database where we store data about bills. - * - * @author olaa - * @author ogronnesby - */ -public interface BillingDatabaseClient { - - boolean setActivePaymentInstrument(InstrumentOwner paymentInstrument); - - Optional<InstrumentOwner> getDefaultPaymentInstrumentForTenant(TenantName from); - - /** - * Create a completely new Bill in the open state with no LineItems. - * - * @param tenant The name of the tenant the bill is for - * @param agent The agent that created the bill - * @return The Id of the new bill - */ - Bill.Id createBill(TenantName tenant, ZonedDateTime startTime, ZonedDateTime endTime, String agent); - - /** - * Read the given bill from the data source - * - * @param billId The Id of the bill to retrieve - * @return The Bill if it exists, Optional.empty() if not. - */ - Optional<Bill> readBill(Bill.Id billId); - - /** - * Get all bills for a given tenant, ordered by date - * - * @param tenant The name of the tenant - * @return List of all bills ordered by date - */ - List<Bill> readBillsForTenant(TenantName tenant); - - /** - * Read all bills, ordered by date - * @return List of all bills ordered by date - */ - List<Bill> readBills(); - - /** - * Add a line item to an open bill - * - * @param lineItem - * @param billId The optional ID of the bill this line item is for - * @return The Id of the new line item - * @throws RuntimeException if the bill is not in OPEN state - */ - String addLineItem(TenantName tenantName, Bill.LineItem lineItem, Optional<Bill.Id> billId); - - /** - * Set status for the given bill - * - * @param billId The ID of the bill this status is for - * @param agent The agent that added the status - * @param status The new status of the bill - */ - void setStatus(Bill.Id billId, String agent, String status); - - List<Bill.LineItem> getUnusedLineItems(TenantName tenantName); - - /** - * Delete a line item - * This is only allowed if the line item has not yet been associated with an bill - * - * @param lineItemId The ID of the line item - * @throws RuntimeException if the line item is associated with an bill - */ - void deleteLineItem(String lineItemId); - - /** - * Associate all uncommitted line items to a given bill - * This is only allowed if the line item has not already been associated with an bill - * - * @param tenantName The tenant we want to commit line items for - * @param billId The ID of the line item - * @throws RuntimeException if the line item is already associated with an bill - */ - void commitLineItems(TenantName tenantName, Bill.Id billId); - - /** - * Return the plan for the given tenant - * - * @param tenantName The tenant to retrieve the plan for - * @return Optional.of the plan if present in DN, else Optional.empty - */ - Optional<Plan> getPlan(TenantName tenantName); - - /** - * Return the plan for the given tenants if present. - * If the database does not know of the tenant, the tenant is not included in the result. - */ - Map<TenantName, Optional<Plan>> getPlans(List<TenantName> tenants); - - /** - * Set the current plan for the given tenant - * - * @param tenantName The tenant to set the plan for - * @param plan The plan to use - */ - void setPlan(TenantName tenantName, Plan plan); - - /** - * Deactivates the default payment instrument for a tenant, if it exists. - * Used during tenant deletion - */ - void deactivateDefaultPaymentInstrument(TenantName tenant); - - /** - * Get the current collection method for the tenant - if one has persisted - * @return Optional.empty if no collection method has been persisted for the tenant - */ - Optional<CollectionMethod> getCollectionMethod(TenantName tenantName); - - /** - * Set the collection method for the tenant - * @param tenantName The name of the tenant to set collection method for - * @param collectionMethod The collection method for the tenant - */ - void setCollectionMethod(TenantName tenantName, CollectionMethod collectionMethod); -} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingDatabaseClientMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingDatabaseClientMock.java deleted file mode 100644 index f53025a2e6d..00000000000 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingDatabaseClientMock.java +++ /dev/null @@ -1,178 +0,0 @@ -// 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.billing; - -import com.yahoo.config.provision.TenantName; - -import java.time.Clock; -import java.time.LocalDate; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.stream.Collectors; - -/** - * @author olaa - */ -public class BillingDatabaseClientMock implements BillingDatabaseClient { - private final Clock clock; - private final PlanRegistry planRegistry; - private final Map<TenantName, Plan> tenantPlans = new HashMap<>(); - private final Map<Bill.Id, TenantName> invoices = new HashMap<>(); - private final Map<Bill.Id, List<Bill.LineItem>> lineItems = new HashMap<>(); - private final Map<TenantName, List<Bill.LineItem>> uncommittedLineItems = new HashMap<>(); - - private final Map<Bill.Id, Bill.StatusHistory> statuses = new HashMap<>(); - private final Map<Bill.Id, ZonedDateTime> startTimes = new HashMap<>(); - private final Map<Bill.Id, ZonedDateTime> endTimes = new HashMap<>(); - - private final ZonedDateTime startTime = LocalDate.of(2020, 4, 1).atStartOfDay(ZoneId.of("UTC")); - private final ZonedDateTime endTime = LocalDate.of(2020, 5, 1).atStartOfDay(ZoneId.of("UTC")); - - private final List<InstrumentOwner> paymentInstruments = new ArrayList<>(); - private final Map<TenantName, CollectionMethod> collectionMethods = new HashMap<>(); - - public BillingDatabaseClientMock(Clock clock, PlanRegistry planRegistry) { - this.clock = clock; - this.planRegistry = planRegistry; - } - - @Override - public boolean setActivePaymentInstrument(InstrumentOwner paymentInstrument) { - return paymentInstruments.add(paymentInstrument); - } - - @Override - public Optional<InstrumentOwner> getDefaultPaymentInstrumentForTenant(TenantName tenantName) { - return paymentInstruments.stream() - .filter(paymentInstrument -> paymentInstrument.getTenantName().equals(tenantName)) - .findFirst(); - } - - public String getStatus(Bill.Id invoiceId) { - return statuses.get(invoiceId).current(); - } - - @Override - public Bill.Id createBill(TenantName tenant, ZonedDateTime startTime, ZonedDateTime endTime, String agent) { - var invoiceId = Bill.Id.generate(); - invoices.put(invoiceId, tenant); - statuses.computeIfAbsent(invoiceId, l -> Bill.StatusHistory.open(clock)); - startTimes.put(invoiceId, startTime); - endTimes.put(invoiceId, endTime); - return invoiceId; - } - - @Override - public Optional<Bill> readBill(Bill.Id billId) { - var invoice = Optional.ofNullable(invoices.get(billId)); - var lines = lineItems.getOrDefault(billId, List.of()); - var status = statuses.getOrDefault(billId, Bill.StatusHistory.open(clock)); - var start = startTimes.getOrDefault(billId, startTime); - var end = endTimes.getOrDefault(billId, endTime); - return invoice.map(tenant -> new Bill(billId, tenant, status, lines, start, end)); - } - - @Override - public String addLineItem(TenantName tenantName, Bill.LineItem lineItem, Optional<Bill.Id> invoiceId) { - var lineItemId = UUID.randomUUID().toString(); - invoiceId.ifPresentOrElse( - invoice -> lineItems.computeIfAbsent(invoice, l -> new ArrayList<>()).add(lineItem), - () -> uncommittedLineItems.computeIfAbsent(tenantName, l -> new ArrayList<>()).add(lineItem) - ); - return lineItemId; - } - - @Override - public void setStatus(Bill.Id invoiceId, String agent, String status) { - statuses.computeIfAbsent(invoiceId, k -> Bill.StatusHistory.open(clock)) - .getHistory() - .put(ZonedDateTime.now(), status); - } - - @Override - public List<Bill.LineItem> getUnusedLineItems(TenantName tenantName) { - return uncommittedLineItems.getOrDefault(tenantName, new ArrayList<>()); - } - - @Override - public void deleteLineItem(String lineItemId) { - uncommittedLineItems.values() - .forEach(list -> - list.removeIf(lineItem -> lineItem.id().equals(lineItemId)) - ); - } - - @Override - public void commitLineItems(TenantName tenantName, Bill.Id invoiceId) { - - } - - @Override - public Optional<Plan> getPlan(TenantName tenantName) { - return Optional.ofNullable(tenantPlans.get(tenantName)); - } - - @Override - public Map<TenantName, Optional<Plan>> getPlans(List<TenantName> tenants) { - return tenantPlans.entrySet().stream() - .filter(entry -> tenants.contains(entry.getKey())) - .collect(Collectors.toMap( - entry -> entry.getKey(), - entry -> planRegistry.plan(entry.getValue().id()) - )); - } - - @Override - public void setPlan(TenantName tenantName, Plan plan) { - tenantPlans.put(tenantName, plan); - } - - @Override - public void deactivateDefaultPaymentInstrument(TenantName tenant) { - paymentInstruments.removeIf(instrumentOwner -> instrumentOwner.getTenantName().equals(tenant)); - } - - @Override - public Optional<CollectionMethod> getCollectionMethod(TenantName tenantName) { - return Optional.ofNullable(collectionMethods.get(tenantName)); - } - - @Override - public void setCollectionMethod(TenantName tenantName, CollectionMethod collectionMethod) { - collectionMethods.put(tenantName, collectionMethod); - } - - @Override - public List<Bill> readBillsForTenant(TenantName tenant) { - return invoices.entrySet().stream() - .filter(entry -> entry.getValue().equals(tenant)) - .map(Map.Entry::getKey) - .map(invoiceId -> { - var items = lineItems.getOrDefault(invoiceId, List.of()); - var status = statuses.get(invoiceId); - var start = startTimes.get(invoiceId); - var end = endTimes.get(invoiceId); - return new Bill(invoiceId, tenant, status, items, start, end); - }) - .collect(Collectors.toList()); - } - - @Override - public List<Bill> readBills() { - return invoices.keySet().stream() - .map(invoiceId -> { - var tenant = invoices.get(invoiceId); - var items = lineItems.getOrDefault(invoiceId, List.of()); - var status = statuses.get(invoiceId); - var start = startTimes.get(invoiceId); - var end = endTimes.get(invoiceId); - return new Bill(invoiceId, tenant, status, items, start, end); - }) - .collect(Collectors.toList()); - } -} 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/Invoice.java index d1af5b428de..3789021ae8e 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/Invoice.java @@ -20,18 +20,18 @@ import java.util.function.Function; /** - * An Bill is an identifier with a status (with history) and line items. A line item is the meat and - * potatoes of the content of the bill, and are a history of items. Most line items are connected to + * An Invoice is an identifier with a status (with history) and line items. A line item is the meat and + * potatoes of the content of the invoice, and are a history of items. Most line items are connected to * a given deployment in Vespa Cloud, but they can also be manually added to e.g. give a discount or represent * support. * <p> * All line items have a Plan associated with them - which was used to map from utilization to an actual price. * <p> - * The bill has a status history, but only the latest status is exposed through this API. + * The invoice has a status history, but only the latest status is exposed through this API. * * @author ogronnesby */ -public class Bill { +public class Invoice { private static final BigDecimal SCALED_ZERO = new BigDecimal("0.00"); private final Id id; @@ -41,7 +41,7 @@ public class Bill { private final ZonedDateTime startTime; private final ZonedDateTime endTime; - public Bill(Id id, TenantName tenant, StatusHistory statusHistory, List<LineItem> lineItems, ZonedDateTime startTime, ZonedDateTime endTime) { + public Invoice(Id id, TenantName tenant, StatusHistory statusHistory, List<LineItem> lineItems, ZonedDateTime startTime, ZonedDateTime endTime) { this.id = id; this.tenant = tenant; this.lineItems = List.copyOf(lineItems); @@ -141,8 +141,8 @@ public class Bill { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - Id billId = (Id) o; - return value.equals(billId.value); + Id invoiceId = (Id) o; + return value.equals(invoiceId.value); } @Override @@ -152,14 +152,14 @@ public class Bill { @Override public String toString() { - return "BillId{" + + return "InvoiceId{" + "value='" + value + '\'' + '}'; } } /** - * Represents a chargeable line on a bill. + * Represents a chargeable line on an invoice. */ public static class LineItem { private final String id; diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/MockBillingController.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/MockBillingController.java index f4d3577aeec..8816c4eb57b 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/MockBillingController.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/MockBillingController.java @@ -26,9 +26,9 @@ public class MockBillingController implements BillingController { private final Clock clock; Map<TenantName, PlanId> plans = new HashMap<>(); Map<TenantName, PaymentInstrument> activeInstruments = new HashMap<>(); - Map<TenantName, List<Bill>> committedBills = new HashMap<>(); - Map<TenantName, Bill> uncommittedBills = new HashMap<>(); - Map<TenantName, List<Bill.LineItem>> unusedLineItems = new HashMap<>(); + Map<TenantName, List<Invoice>> committedInvoices = new HashMap<>(); + Map<TenantName, Invoice> uncommittedInvoices = new HashMap<>(); + Map<TenantName, List<Invoice.LineItem>> unusedLineItems = new HashMap<>(); Map<TenantName, CollectionMethod> collectionMethod = new HashMap<>(); public MockBillingController(Clock clock) { @@ -64,32 +64,32 @@ public class MockBillingController implements BillingController { } @Override - public Bill.Id createBillForPeriod(TenantName tenant, ZonedDateTime startTime, ZonedDateTime endTime, String agent) { - var billId = Bill.Id.of("id-123"); - committedBills.computeIfAbsent(tenant, l -> new ArrayList<>()) - .add(new Bill( - billId, + public Invoice.Id createInvoiceForPeriod(TenantName tenant, ZonedDateTime startTime, ZonedDateTime endTime, String agent) { + var invoiceId = Invoice.Id.of("id-123"); + committedInvoices.computeIfAbsent(tenant, l -> new ArrayList<>()) + .add(new Invoice( + invoiceId, tenant, - Bill.StatusHistory.open(clock), + Invoice.StatusHistory.open(clock), List.of(), startTime, endTime )); - return billId; + return invoiceId; } @Override - public Bill createUncommittedBill(TenantName tenant, LocalDate until) { - return uncommittedBills.getOrDefault(tenant, emptyBill()); + public Invoice createUncommittedInvoice(TenantName tenant, LocalDate until) { + return uncommittedInvoices.getOrDefault(tenant, emptyInvoice()); } @Override - public Map<TenantName, Bill> createUncommittedBills(LocalDate until) { - return uncommittedBills; + public Map<TenantName, Invoice> createUncommittedInvoices(LocalDate until) { + return uncommittedInvoices; } @Override - public List<Bill.LineItem> getUnusedLineItems(TenantName tenant) { + public List<Invoice.LineItem> getUnusedLineItems(TenantName tenant) { return unusedLineItems.getOrDefault(tenant, List.of()); } @@ -110,18 +110,18 @@ public class MockBillingController implements BillingController { } @Override - public void updateBillStatus(Bill.Id billId, String agent, String status) { + public void updateInvoiceStatus(Invoice.Id invoiceId, String agent, String status) { var now = clock.instant().atZone(ZoneOffset.UTC); - committedBills.values().stream() + committedInvoices.values().stream() .flatMap(List::stream) - .filter(bill -> billId.equals(bill.id())) - .forEach(bill -> bill.statusHistory().history.put(now, status)); + .filter(invoice -> invoiceId.equals(invoice.id())) + .forEach(invoice -> invoice.statusHistory().history.put(now, status)); } @Override public void addLineItem(TenantName tenant, String description, BigDecimal amount, String agent) { unusedLineItems.computeIfAbsent(tenant, l -> new ArrayList<>()) - .add(new Bill.LineItem( + .add(new Invoice.LineItem( "line-item-id", description, amount, @@ -152,13 +152,13 @@ public class MockBillingController implements BillingController { } @Override - public List<Bill> getBillsForTenant(TenantName tenant) { - return committedBills.getOrDefault(tenant, List.of()); + public List<Invoice> getInvoicesForTenant(TenantName tenant) { + return committedInvoices.getOrDefault(tenant, List.of()); } @Override - public List<Bill> getBills() { - return committedBills.values().stream().flatMap(Collection::stream).collect(Collectors.toList()); + public List<Invoice> getInvoices() { + return committedInvoices.values().stream().flatMap(Collection::stream).collect(Collectors.toList()); } @Override @@ -191,17 +191,17 @@ public class MockBillingController implements BillingController { "country"); } - public void addBill(TenantName tenantName, Bill bill, boolean committed) { + public void addInvoice(TenantName tenantName, Invoice invoice, boolean committed) { if (committed) - committedBills.computeIfAbsent(tenantName, i -> new ArrayList<>()) - .add(bill); + committedInvoices.computeIfAbsent(tenantName, i -> new ArrayList<>()) + .add(invoice); else - uncommittedBills.put(tenantName, bill); + uncommittedInvoices.put(tenantName, invoice); } - private Bill emptyBill() { + private Invoice emptyInvoice() { var start = clock.instant().atZone(ZoneOffset.UTC); var end = clock.instant().atZone(ZoneOffset.UTC); - return new Bill(Bill.Id.of("empty"), TenantName.defaultName(), Bill.StatusHistory.open(clock), List.of(), start, end); + return new Invoice(Invoice.Id.of("empty"), TenantName.defaultName(), Invoice.StatusHistory.open(clock), List.of(), start, end); } } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistry.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistry.java deleted file mode 100644 index d64d6e3ea04..00000000000 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistry.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.yahoo.vespa.hosted.controller.api.integration.billing; - -import java.util.Optional; - -/** - * Registry of all current plans we have support for - * - * @author ogronnesby - */ -public interface PlanRegistry { - - /** Get the default plan */ - Plan defaultPlan(); - - /** Get a plan given a plan ID */ - Optional<Plan> plan(PlanId planId); - - /** Get a plan give a plan ID */ - default Optional<Plan> plan(String planId) { - return plan(PlanId.from(planId)); - } - -} 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 deleted file mode 100644 index 60eddbd24ff..00000000000 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java +++ /dev/null @@ -1,122 +0,0 @@ -package com.yahoo.vespa.hosted.controller.api.integration.billing; - -import com.yahoo.config.provision.NodeResources; -import com.yahoo.vespa.hosted.controller.api.integration.resource.CostInfo; -import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceUsage; - -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.Optional; -import java.util.stream.Stream; - -public class PlanRegistryMock implements PlanRegistry { - - public static final Plan freeTrial = new MockPlan("trial", false, 0, 0, 0, 200, "Free Trial - for testing purposes"); - public static final Plan paidPlan = new MockPlan("paid", true, "0.09", "0.009", "0.0003", 500, "Paid Plan - for testing purposes"); - public static final Plan nonePlan = new MockPlan("none", false, 0, 0, 0, 0, "None Plan - for testing purposes"); - - @Override - public Plan defaultPlan() { - return freeTrial; - } - - @Override - public Optional<Plan> plan(PlanId planId) { - return Stream.of(freeTrial, paidPlan, nonePlan) - .filter(p -> p.id().equals(planId)) - .findAny(); - } - - private static class MockPlan implements Plan { - private final PlanId planId; - private final String description; - private final CostCalculator costCalculator; - private final QuotaCalculator quotaCalculator; - private final boolean billed; - - public MockPlan(String planId, boolean billed, double cpuPrice, double memPrice, double dgbPrice, int quota, String description) { - this(PlanId.from(planId), billed, new MockCostCalculator(cpuPrice, memPrice, dgbPrice), () -> Quota.unlimited().withBudget(quota), description); - } - - public MockPlan(String planId, boolean billed, String cpuPrice, String memPrice, String dgbPrice, int quota, String description) { - this(PlanId.from(planId), billed, new MockCostCalculator(cpuPrice, memPrice, dgbPrice), () -> Quota.unlimited().withBudget(quota), description); - } - - public MockPlan(PlanId planId, boolean billed, MockCostCalculator calculator, QuotaCalculator quota, String description) { - this.planId = planId; - this.billed = billed; - this.costCalculator = calculator; - this.quotaCalculator = quota; - this.description = description; - } - - @Override - public PlanId id() { - return planId; - } - - @Override - public String displayName() { - return description; - } - - @Override - public CostCalculator calculator() { - return costCalculator; - } - - @Override - public QuotaCalculator quota() { - return quotaCalculator; - } - - @Override - public boolean isBilled() { - return billed; - } - } - - private static class MockCostCalculator implements CostCalculator { - private static final BigDecimal millisPerHour = BigDecimal.valueOf(60 * 60 * 1000); - private final BigDecimal cpuHourCost; - private final BigDecimal memHourCost; - private final BigDecimal dgbHourCost; - - public MockCostCalculator(String cpuPrice, String memPrice, String dgbPrice) { - this(new BigDecimal(cpuPrice), new BigDecimal(memPrice), new BigDecimal(dgbPrice)); - } - - public MockCostCalculator(double cpuPrice, double memPrice, double dgbPrice) { - this(BigDecimal.valueOf(cpuPrice), BigDecimal.valueOf(memPrice), BigDecimal.valueOf(dgbPrice)); - } - - public MockCostCalculator(BigDecimal cpuPrice, BigDecimal memPrice, BigDecimal dgbPrice) { - this.cpuHourCost = cpuPrice; - this.memHourCost = memPrice; - this.dgbHourCost = dgbPrice; - } - - @Override - public CostInfo calculate(ResourceUsage usage) { - var cpuCost = usage.getCpuMillis().multiply(cpuHourCost).divide(millisPerHour, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP); - var memCost = usage.getMemoryMillis().multiply(memHourCost).divide(millisPerHour, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP); - var dgbCost = usage.getDiskMillis().multiply(dgbHourCost).divide(millisPerHour, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP); - - return new CostInfo( - usage.getApplicationId(), - usage.getZoneId(), - usage.getCpuMillis().divide(millisPerHour, RoundingMode.HALF_UP), - usage.getMemoryMillis().divide(millisPerHour, RoundingMode.HALF_UP), - usage.getDiskMillis().divide(millisPerHour, RoundingMode.HALF_UP), - cpuCost, - memCost, - dgbCost - ); - } - - @Override - public double calculate(NodeResources resources) { - return resources.cost(); - } - } -} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClient.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClient.java deleted file mode 100644 index 2f277193231..00000000000 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClient.java +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2019 Oath Inc. 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.ApplicationName; -import com.yahoo.config.provision.TenantName; - -import java.time.LocalDate; -import java.time.YearMonth; -import java.time.temporal.ChronoUnit; -import java.util.Collection; -import java.util.List; -import java.util.Set; - -/** - * @author olaa - */ -public interface ResourceDatabaseClient { - - void writeResourceSnapshots(Collection<ResourceSnapshot> snapshots); - - List<ResourceSnapshot> getResourceSnapshotsForMonth(TenantName tenantName, ApplicationName applicationName, YearMonth month); - - List<ResourceUsage> getResourceSnapshotsForPeriod(TenantName tenantName, long startTimestamp, long endTimestamp); - - void refreshMaterializedView(); - - Set<YearMonth> getMonthsWithSnapshotsForTenant(TenantName tenantName); - - List<ResourceSnapshot> getRawSnapshotHistoryForTenant(TenantName tenantName, YearMonth yearMonth); - - Set<TenantName> getTenants(); - - default List<ResourceUsage> getResourceSnapshotsForMonth(TenantName tenantName, YearMonth month) { - return getResourceSnapshotsForPeriod(tenantName, getMonthStartTimeStamp(month), getMonthEndTimeStamp(month)); - } - - private long getMonthStartTimeStamp(YearMonth month) { - LocalDate startOfMonth = LocalDate.of(month.getYear(), month.getMonth(), 1); - return startOfMonth.atStartOfDay(java.time.ZoneId.of("UTC")) - .toInstant() - .toEpochMilli(); - } - private long getMonthEndTimeStamp(YearMonth month) { - LocalDate startOfMonth = LocalDate.of(month.getYear(), month.getMonth(), 1); - return startOfMonth.plus(1, ChronoUnit.MONTHS) - .atStartOfDay(java.time.ZoneId.of("UTC")) - .toInstant() - .toEpochMilli(); - } - -} 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 deleted file mode 100644 index 5a4d250ea9d..00000000000 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClientMock.java +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2019 Oath Inc. 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.ApplicationName; -import com.yahoo.config.provision.TenantName; -import com.yahoo.vespa.hosted.controller.api.integration.billing.Plan; -import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanRegistry; - -import java.math.BigDecimal; -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDate; -import java.time.YearMonth; -import java.time.ZoneId; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -/** - * @author olaa - */ -public class ResourceDatabaseClientMock implements ResourceDatabaseClient { - - PlanRegistry planRegistry; - Map<TenantName, Plan> planMap = new HashMap<>(); - List<ResourceSnapshot> resourceSnapshots = new ArrayList<>(); - private boolean hasRefreshedMaterializedView = false; - - public ResourceDatabaseClientMock(PlanRegistry planRegistry) { - this.planRegistry = planRegistry; - } - - @Override - public void writeResourceSnapshots(Collection<ResourceSnapshot> items) { - this.resourceSnapshots.addAll(items); - } - - @Override - public List<ResourceSnapshot> getResourceSnapshotsForMonth(TenantName tenantName, ApplicationName applicationName, YearMonth month) { - return resourceSnapshots.stream() - .filter(resourceSnapshot -> { - LocalDate snapshotDate = LocalDate.ofInstant(resourceSnapshot.getTimestamp(), ZoneId.of("UTC")); - return YearMonth.from(snapshotDate).equals(month) && - snapshotDate.getYear() == month.getYear() && - resourceSnapshot.getApplicationId().tenant().equals(tenantName) && - resourceSnapshot.getApplicationId().application().equals(applicationName); - }) - .collect(Collectors.toList()); - } - - @Override - public Set<YearMonth> getMonthsWithSnapshotsForTenant(TenantName tenantName) { - return Collections.emptySet(); - } - - @Override - public List<ResourceSnapshot> getRawSnapshotHistoryForTenant(TenantName tenantName, YearMonth yearMonth) { - return resourceSnapshots; - } - - @Override - public Set<TenantName> getTenants() { - return resourceSnapshots.stream() - .map(snapshot -> snapshot.getApplicationId().tenant()) - .collect(Collectors.toSet()); - } - - private List<ResourceUsage> resourceUsageFromSnapshots(Plan plan, List<ResourceSnapshot> snapshots) { - snapshots.sort(Comparator.comparing(ResourceSnapshot::getTimestamp)); - - return IntStream.range(0, snapshots.size()) - .mapToObj(idx -> { - var a = snapshots.get(idx); - var b = (idx + 1) < snapshots.size() ? snapshots.get(idx + 1) : null; - var start = a.getTimestamp(); - var end = Optional.ofNullable(b).map(ResourceSnapshot::getTimestamp).orElse(start.plusSeconds(120)); - var d = BigDecimal.valueOf(Duration.between(start, end).toMillis()); - return new ResourceUsage( - a.getApplicationId(), - a.getZoneId(), - plan, - BigDecimal.valueOf(a.getCpuCores()).multiply(d), - BigDecimal.valueOf(a.getMemoryGb()).multiply(d), - BigDecimal.valueOf(a.getDiskGb()).multiply(d) - ); - }) - .collect(Collectors.toList()); - } - - private ResourceUsage resourceUsageAdd(ResourceUsage a, ResourceUsage b) { - assert a.getApplicationId().equals(b.getApplicationId()); - assert a.getZoneId().equals(b.getZoneId()); - assert a.getPlan().equals(b.getPlan()); - return new ResourceUsage( - a.getApplicationId(), - a.getZoneId(), - a.getPlan(), - a.getCpuMillis().add(b.getCpuMillis()), - a.getMemoryMillis().add(b.getMemoryMillis()), - a.getDiskMillis().add(b.getDiskMillis()) - ); - } - - @Override - public List<ResourceUsage> getResourceSnapshotsForPeriod(TenantName tenantName, long start, long end) { - var tenantPlan = planMap.getOrDefault(tenantName, planRegistry.defaultPlan()); - - var snapshotsPerDeployment = resourceSnapshots.stream() - .filter(snapshot -> snapshot.getTimestamp().isAfter(Instant.ofEpochMilli(start))) - .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()) - )) - .values().stream() - .map(snapshots -> resourceUsageFromSnapshots(tenantPlan, snapshots)) - .map(usages -> usages.stream().reduce(this::resourceUsageAdd)) - .filter(Optional::isPresent) - .map(Optional::get) - .collect(Collectors.toList()); - - return snapshotsPerDeployment; - } - - @Override - public void refreshMaterializedView() { - hasRefreshedMaterializedView = true; - } - - public void setPlan(TenantName tenant, Plan plan) { - planMap.put(tenant, plan); - } - - public boolean hasRefreshedMaterializedView() { - return hasRefreshedMaterializedView; - } -} 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 319b9239ae4..a8d9c4b1f8a 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 @@ -31,10 +31,6 @@ public class ResourceSnapshot { this.zoneId = zoneId; } - public static ResourceSnapshot from(ApplicationId applicationId, int nodes, double cpuCores, double memoryGb, double diskGb, Instant timestamp, ZoneId zoneId) { - return new ResourceSnapshot(applicationId, cpuCores * nodes, memoryGb * nodes, diskGb * nodes, timestamp, zoneId); - } - public static ResourceSnapshot from(List<Node> nodes, Instant timestamp, ZoneId zoneId) { Set<ApplicationId> applicationIds = nodes.stream() .filter(node -> node.owner().isPresent()) 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 b8c8ce4e63a..b595d3fbe17 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 @@ -18,9 +18,9 @@ import com.yahoo.slime.SlimeUtils; import com.yahoo.vespa.hosted.controller.ApplicationController; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.TenantController; -import com.yahoo.vespa.hosted.controller.api.integration.billing.Bill; import com.yahoo.vespa.hosted.controller.api.integration.billing.CollectionMethod; import com.yahoo.vespa.hosted.controller.api.integration.billing.PaymentInstrument; +import com.yahoo.vespa.hosted.controller.api.integration.billing.Invoice; import com.yahoo.vespa.hosted.controller.api.integration.billing.InstrumentOwner; import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingController; import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId; @@ -99,23 +99,23 @@ public class BillingApiHandler extends LoggingRequestHandler { if (path.matches("/billing/v1/tenant/{tenant}/billing")) return getBilling(path.get("tenant"), request.getProperty("until")); if (path.matches("/billing/v1/tenant/{tenant}/plan")) return getPlan(path.get("tenant")); if (path.matches("/billing/v1/billing")) return getBillingAllTenants(request.getProperty("until")); - if (path.matches("/billing/v1/invoice/export")) return getAllBills(); + if (path.matches("/billing/v1/invoice/export")) return getAllInvoices(); if (path.matches("/billing/v1/invoice/tenant/{tenant}/line-item")) return getLineItems(path.get("tenant")); return ErrorResponse.notFoundError("Nothing at " + path); } - private HttpResponse getAllBills() { - var bills = billingController.getBills(); + private HttpResponse getAllInvoices() { + var invoices = billingController.getInvoices(); var headers = new String[]{ "ID", "Tenant", "From", "To", "CpuHours", "MemoryHours", "DiskHours", "Cpu", "Memory", "Disk", "Additional" }; - var rows = bills.stream() - .map(bill -> { + var rows = invoices.stream() + .map(invoice -> { return new Object[] { - bill.id().value(), bill.tenant().value(), - bill.getStartTime().format(DateTimeFormatter.ISO_LOCAL_DATE), - bill.getEndTime().format(DateTimeFormatter.ISO_LOCAL_DATE), - bill.sumCpuHours(), bill.sumMemoryHours(), bill.sumDiskHours(), - bill.sumCpuCost(), bill.sumMemoryCost(), bill.sumDiskCost(), - bill.sumAdditionalCost() + invoice.id().value(), invoice.tenant().value(), + invoice.getStartTime().format(DateTimeFormatter.ISO_LOCAL_DATE), + invoice.getEndTime().format(DateTimeFormatter.ISO_LOCAL_DATE), + invoice.sumCpuHours(), invoice.sumMemoryHours(), invoice.sumDiskHours(), + invoice.sumCpuCost(), invoice.sumMemoryCost(), invoice.sumDiskCost(), + invoice.sumAdditionalCost() }; }) .collect(Collectors.toList()); @@ -138,8 +138,8 @@ public class BillingApiHandler extends LoggingRequestHandler { } private HttpResponse handlePOST(Path path, HttpRequest request, String userId) { - if (path.matches("/billing/v1/invoice")) return createBill(request, userId); - if (path.matches("/billing/v1/invoice/{invoice-id}/status")) return setBillStatus(request, path.get("invoice-id")); + if (path.matches("/billing/v1/invoice")) return createInvoice(request, userId); + if (path.matches("/billing/v1/invoice/{invoice-id}/status")) return setInvoiceStatus(request, path.get("invoice-id")); if (path.matches("/billing/v1/invoice/tenant/{tenant}/line-item")) return addLineItem(request, path.get("tenant")); return ErrorResponse.notFoundError("Nothing at " + path); @@ -190,7 +190,7 @@ public class BillingApiHandler extends LoggingRequestHandler { private HttpResponse getBillingAllTenants(String until) { try { var untilDate = untilParameter(until); - var uncommittedBills = billingController.createUncommittedBills(untilDate); + var uncommittedInvoices = billingController.createUncommittedInvoices(untilDate); var slime = new Slime(); var root = slime.setObject(); @@ -198,12 +198,12 @@ public class BillingApiHandler extends LoggingRequestHandler { var tenants = root.setArray("tenants"); tenantController.asList().stream().sorted(Comparator.comparing(Tenant::name)).forEach(tenant -> { - var bill = uncommittedBills.get(tenant.name()); + var invoice = uncommittedInvoices.get(tenant.name()); var tc = tenants.addObject(); tc.setString("tenant", tenant.name().value()); getPlanForTenant(tc, tenant.name()); getCollectionForTenant(tc, tenant.name()); - renderCurrentUsage(tc.setObject("current"), bill); + renderCurrentUsage(tc.setObject("current"), invoice); renderAdditionalItems(tc.setObject("additional").setArray("items"), billingController.getUnusedLineItems(tenant.name())); billingController.getDefaultInstrument(tenant.name()).ifPresent(card -> @@ -232,14 +232,14 @@ public class BillingApiHandler extends LoggingRequestHandler { return new MessageResponse("Added line item for tenant " + tenant); } - private HttpResponse setBillStatus(HttpRequest request, String billId) { + private HttpResponse setInvoiceStatus(HttpRequest request, String invoiceId) { Inspector inspector = inspectorOrThrow(request); String status = getInspectorFieldOrThrow(inspector, "status"); - billingController.updateBillStatus(Bill.Id.of(billId), userIdOrThrow(request), status); - return new MessageResponse("Updated status of invoice " + billId); + billingController.updateInvoiceStatus(Invoice.Id.of(invoiceId), userIdOrThrow(request), status); + return new MessageResponse("Updated status of invoice " + invoiceId); } - private HttpResponse createBill(HttpRequest request, String userId) { + private HttpResponse createInvoice(HttpRequest request, String userId) { Inspector inspector = inspectorOrThrow(request); TenantName tenantName = TenantName.from(getInspectorFieldOrThrow(inspector, "tenant")); @@ -248,12 +248,12 @@ public class BillingApiHandler extends LoggingRequestHandler { ZonedDateTime startTime = startDate.atStartOfDay(ZoneId.of("UTC")); ZonedDateTime endTime = endDate.atStartOfDay(ZoneId.of("UTC")); - var billId = billingController.createBillForPeriod(tenantName, startTime, endTime, userId); + var invoiceId = billingController.createInvoiceForPeriod(tenantName, startTime, endTime, userId); Slime slime = new Slime(); Cursor root = slime.setObject(); - root.setString("message", "Created invoice with ID " + billId.value()); - root.setString("id", billId.value()); + root.setString("message", "Created invoice with ID " + invoiceId.value()); + root.setString("id", invoiceId.value()); return new SlimeJsonResponse(slime); } @@ -278,7 +278,7 @@ public class BillingApiHandler extends LoggingRequestHandler { getPlanForTenant(root, tenantId); renderCurrentUsage(root.setObject("current"), getCurrentUsageForTenant(tenantId, untilDate)); renderAdditionalItems(root.setObject("additional").setArray("items"), billingController.getUnusedLineItems(tenantId)); - renderBills(root.setArray("bills"), getBillsForTenant(tenantId)); + renderInvoices(root.setArray("bills"), getInvoicesForTenant(tenantId)); billingController.getDefaultInstrument(tenantId).ifPresent( card -> renderInstrument(root.setObject("payment"), card) @@ -328,7 +328,7 @@ public class BillingApiHandler extends LoggingRequestHandler { } - private void renderCurrentUsage(Cursor cursor, Bill currentUsage) { + private void renderCurrentUsage(Cursor cursor, Invoice currentUsage) { if (currentUsage == null) return; cursor.setString("amount", currentUsage.sum().toPlainString()); cursor.setString("status", "accrued"); @@ -340,46 +340,46 @@ public class BillingApiHandler extends LoggingRequestHandler { }); } - private void renderAdditionalItems(Cursor cursor, List<Bill.LineItem> items) { + private void renderAdditionalItems(Cursor cursor, List<Invoice.LineItem> items) { items.forEach(item -> { renderLineItemToCursor(cursor.addObject(), item); }); } - private Bill getCurrentUsageForTenant(TenantName tenant, LocalDate until) { - return billingController.createUncommittedBill(tenant, until); + private Invoice getCurrentUsageForTenant(TenantName tenant, LocalDate until) { + return billingController.createUncommittedInvoice(tenant, until); } - private List<Bill> getBillsForTenant(TenantName tenant) { - return billingController.getBillsForTenant(tenant); + private List<Invoice> getInvoicesForTenant(TenantName tenant) { + return billingController.getInvoicesForTenant(tenant); } - private void renderBills(Cursor cursor, List<Bill> bills) { - bills.forEach(bill -> { - var billCursor = cursor.addObject(); - renderBillToCursor(billCursor, bill); + private void renderInvoices(Cursor cursor, List<Invoice> invoices) { + invoices.forEach(invoice -> { + var invoiceCursor = cursor.addObject(); + renderInvoiceToCursor(invoiceCursor, invoice); }); } - private void renderBillToCursor(Cursor billCursor, Bill bill) { - billCursor.setString("id", bill.id().value()); - billCursor.setString("from", bill.getStartTime().format(DATE_TIME_FORMATTER)); - billCursor.setString("to", bill.getEndTime().format(DATE_TIME_FORMATTER)); + private void renderInvoiceToCursor(Cursor invoiceCursor, Invoice invoice) { + invoiceCursor.setString("id", invoice.id().value()); + invoiceCursor.setString("from", invoice.getStartTime().format(DATE_TIME_FORMATTER)); + invoiceCursor.setString("to", invoice.getEndTime().format(DATE_TIME_FORMATTER)); - billCursor.setString("amount", bill.sum().toString()); - billCursor.setString("status", bill.status()); - var statusCursor = billCursor.setArray("statusHistory"); - renderStatusHistory(statusCursor, bill.statusHistory()); + invoiceCursor.setString("amount", invoice.sum().toString()); + invoiceCursor.setString("status", invoice.status()); + var statusCursor = invoiceCursor.setArray("statusHistory"); + renderStatusHistory(statusCursor, invoice.statusHistory()); - var lineItemsCursor = billCursor.setArray("items"); - bill.lineItems().forEach(lineItem -> { + var lineItemsCursor = invoiceCursor.setArray("items"); + invoice.lineItems().forEach(lineItem -> { var itemCursor = lineItemsCursor.addObject(); renderLineItemToCursor(itemCursor, lineItem); }); } - private void renderStatusHistory(Cursor cursor, Bill.StatusHistory statusHistory) { + private void renderStatusHistory(Cursor cursor, Invoice.StatusHistory statusHistory) { statusHistory.getHistory() .entrySet() .stream() @@ -390,7 +390,7 @@ public class BillingApiHandler extends LoggingRequestHandler { }); } - private void renderLineItemToCursor(Cursor cursor, Bill.LineItem lineItem) { + private void renderLineItemToCursor(Cursor cursor, Invoice.LineItem lineItem) { cursor.setString("id", lineItem.id()); cursor.setString("description", lineItem.description()); cursor.setString("amount", lineItem.amount().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 630305b0ab4..58afed4143f 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 @@ -15,9 +15,9 @@ import com.yahoo.slime.Type; import com.yahoo.vespa.hosted.controller.ApplicationController; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.TenantController; -import com.yahoo.vespa.hosted.controller.api.integration.billing.Bill; import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingController; import com.yahoo.vespa.hosted.controller.api.integration.billing.CollectionMethod; +import com.yahoo.vespa.hosted.controller.api.integration.billing.Invoice; import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId; import com.yahoo.vespa.hosted.controller.api.role.Role; import com.yahoo.vespa.hosted.controller.api.role.SecurityContext; @@ -138,7 +138,7 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler var tenant = tenants.require(tenantName, CloudTenant.class); var slime = new Slime(); - invoicesSummaryToSlime(slime.setObject().setArray("invoices"), billing.getBillsForTenant(tenant.name())); + invoicesSummaryToSlime(slime.setObject().setArray("invoices"), billing.getInvoicesForTenant(tenant.name())); return slime; } @@ -148,7 +148,7 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler var invoiceId = requestContext.pathParameters().getStringOrThrow("invoice"); var format = requestContext.queryParameters().getString("format").orElse("json"); - var invoice = billing.getBillsForTenant(tenant.name()).stream() + var invoice = billing.getInvoicesForTenant(tenant.name()).stream() .filter(inv -> inv.id().value().equals(invoiceId)) .findAny() .orElseThrow(RestApiException.NotFound::new); @@ -178,7 +178,7 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler var tenantName = TenantName.from(requestContext.pathParameters().getStringOrThrow("tenant")); var tenant = tenants.require(tenantName, CloudTenant.class); var untilAt = untilParameter(requestContext); - var usage = billing.createUncommittedBill(tenant.name(), untilAt.atZone(ZoneOffset.UTC).toLocalDate()); + var usage = billing.createUncommittedInvoice(tenant.name(), untilAt.atZone(ZoneOffset.UTC).toLocalDate()); var slime = new Slime(); usageToSlime(slime.setObject(), usage); @@ -189,7 +189,7 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler private Slime accountant(RestApi.RequestContext requestContext) { var untilAt = untilParameter(requestContext); - var usagePerTenant = billing.createUncommittedBills(untilAt.atZone(ZoneOffset.UTC).toLocalDate()); + var usagePerTenant = billing.createUncommittedInvoices(untilAt.atZone(ZoneOffset.UTC).toLocalDate()); var response = new Slime(); var tenantsResponse = response.setObject().setArray("tenants"); @@ -199,8 +199,8 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler tenantResponse.setString("tenant", tenant.name().value()); tenantResponse.setString("plan", billing.getPlan(tenant.name()).value()); tenantResponse.setString("collection", billing.getCollectionMethod(tenant.name()).name()); - tenantResponse.setString("lastBill", usage.map(Bill::getStartTime).map(DateTimeFormatter.ISO_DATE::format).orElse(null)); - tenantResponse.setString("unbilled", usage.map(Bill::sum).map(BigDecimal::toPlainString).orElse("0.00")); + tenantResponse.setString("lastBill", usage.map(Invoice::getStartTime).map(DateTimeFormatter.ISO_DATE::format).orElse(null)); + tenantResponse.setString("unbilled", usage.map(Invoice::sum).map(BigDecimal::toPlainString).orElse("0.00")); }); return response; @@ -211,7 +211,7 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler var tenant = tenants.require(tenantName, CloudTenant.class); var untilAt = untilParameter(requestContext); - var usage = billing.createUncommittedBill(tenant.name(), untilAt.atZone(ZoneOffset.UTC).toLocalDate()); + var usage = billing.createUncommittedInvoice(tenant.name(), untilAt.atZone(ZoneOffset.UTC).toLocalDate()); var slime = new Slime(); toSlime(slime.setObject(), usage); @@ -230,7 +230,7 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler var startAt = LocalDate.parse(getInspectorFieldOrThrow(body, "from")).atStartOfDay(ZoneOffset.UTC); var endAt = LocalDate.parse(getInspectorFieldOrThrow(body, "to")).atStartOfDay(ZoneOffset.UTC); - var invoiceId = billing.createBillForPeriod(tenant.name(), startAt, endAt, security.principal().getName()); + var invoiceId = billing.createInvoiceForPeriod(tenant.name(), startAt, endAt, security.principal().getName()); // TODO: Make a redirect to the bill itself return new MessageResponse("Created bill " + invoiceId.value()); @@ -239,36 +239,36 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler // --------- INVOICE RENDERING ---------- - private void invoicesSummaryToSlime(Cursor slime, List<Bill> bills) { - bills.forEach(invoice -> invoiceSummaryToSlime(slime.addObject(), invoice)); + private void invoicesSummaryToSlime(Cursor slime, List<Invoice> invoices) { + invoices.forEach(invoice -> invoiceSummaryToSlime(slime.addObject(), invoice)); } - private void invoiceSummaryToSlime(Cursor slime, Bill bill) { - slime.setString("id", bill.id().value()); - slime.setString("from", bill.getStartTime().format(DateTimeFormatter.ISO_LOCAL_DATE)); - slime.setString("to", bill.getStartTime().format(DateTimeFormatter.ISO_LOCAL_DATE)); - slime.setString("total", bill.sum().toString()); - slime.setString("status", bill.status()); + private void invoiceSummaryToSlime(Cursor slime, Invoice invoice) { + slime.setString("id", invoice.id().value()); + slime.setString("from", invoice.getStartTime().format(DateTimeFormatter.ISO_LOCAL_DATE)); + slime.setString("to", invoice.getStartTime().format(DateTimeFormatter.ISO_LOCAL_DATE)); + slime.setString("total", invoice.sum().toString()); + slime.setString("status", invoice.status()); } - private void usageToSlime(Cursor slime, Bill bill) { - slime.setString("from", bill.getStartTime().format(DateTimeFormatter.ISO_LOCAL_DATE)); - slime.setString("to", bill.getStartTime().format(DateTimeFormatter.ISO_LOCAL_DATE)); - slime.setString("total", bill.sum().toString()); - toSlime(slime.setArray("items"), bill.lineItems()); + private void usageToSlime(Cursor slime, Invoice invoice) { + slime.setString("from", invoice.getStartTime().format(DateTimeFormatter.ISO_LOCAL_DATE)); + slime.setString("to", invoice.getStartTime().format(DateTimeFormatter.ISO_LOCAL_DATE)); + slime.setString("total", invoice.sum().toString()); + toSlime(slime.setArray("items"), invoice.lineItems()); } - private void toSlime(Cursor slime, Bill bill) { - slime.setString("id", bill.id().value()); - slime.setString("from", bill.getStartTime().format(DateTimeFormatter.ISO_LOCAL_DATE)); - slime.setString("to", bill.getStartTime().format(DateTimeFormatter.ISO_LOCAL_DATE)); - slime.setString("total", bill.sum().toString()); - slime.setString("status", bill.status()); - toSlime(slime.setArray("statusHistory"), bill.statusHistory()); - toSlime(slime.setArray("items"), bill.lineItems()); + private void toSlime(Cursor slime, Invoice invoice) { + slime.setString("id", invoice.id().value()); + slime.setString("from", invoice.getStartTime().format(DateTimeFormatter.ISO_LOCAL_DATE)); + slime.setString("to", invoice.getStartTime().format(DateTimeFormatter.ISO_LOCAL_DATE)); + slime.setString("total", invoice.sum().toString()); + slime.setString("status", invoice.status()); + toSlime(slime.setArray("statusHistory"), invoice.statusHistory()); + toSlime(slime.setArray("items"), invoice.lineItems()); } - private void toSlime(Cursor slime, Bill.StatusHistory history) { + private void toSlime(Cursor slime, Invoice.StatusHistory history) { history.getHistory().forEach((key, value) -> { var c = slime.addObject(); c.setString("at", key.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); @@ -276,11 +276,11 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler }); } - private void toSlime(Cursor slime, List<Bill.LineItem> items) { + private void toSlime(Cursor slime, List<Invoice.LineItem> items) { items.forEach(item -> toSlime(slime.addObject(), item)); } - private void toSlime(Cursor slime, Bill.LineItem item) { + private void toSlime(Cursor slime, Invoice.LineItem item) { slime.setString("id", item.id()); slime.setString("description", item.description()); slime.setString("amount",item.amount().toString()); @@ -304,14 +304,14 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler cost.ifPresent(c -> slime.setString("cost", c.toString())); } - private List<Object[]> toCsv(Bill bill) { + private List<Object[]> toCsv(Invoice invoice) { return List.<Object[]>of(new Object[]{ - bill.id().value(), bill.tenant().value(), - bill.getStartTime().format(DateTimeFormatter.ISO_DATE), - bill.getEndTime().format(DateTimeFormatter.ISO_DATE), - bill.sumCpuHours(), bill.sumMemoryHours(), bill.sumDiskHours(), - bill.sumCpuCost(), bill.sumMemoryCost(), bill.sumDiskCost(), - bill.sumAdditionalCost() + invoice.id().value(), invoice.tenant().value(), + invoice.getStartTime().format(DateTimeFormatter.ISO_DATE), + invoice.getEndTime().format(DateTimeFormatter.ISO_DATE), + invoice.sumCpuHours(), invoice.sumMemoryHours(), invoice.sumDiskHours(), + invoice.sumCpuCost(), invoice.sumMemoryCost(), invoice.sumDiskCost(), + invoice.sumAdditionalCost() }); } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java index f64c54ff24d..6ddf0ec76ed 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java @@ -17,11 +17,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.aws.MockCloudEventFetch import com.yahoo.vespa.hosted.controller.api.integration.aws.MockResourceTagger; import com.yahoo.vespa.hosted.controller.api.integration.aws.ResourceTagger; import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingController; -import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingDatabaseClient; import com.yahoo.vespa.hosted.controller.api.integration.billing.MockBillingController; -import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingDatabaseClientMock; -import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanRegistry; -import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanRegistryMock; import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateMock; import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateValidator; import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateValidatorMock; @@ -32,8 +28,6 @@ import com.yahoo.vespa.hosted.controller.api.integration.horizon.MockHorizonClie import com.yahoo.vespa.hosted.controller.api.integration.organization.MockContactRetriever; import com.yahoo.vespa.hosted.controller.api.integration.organization.MockIssueHandler; import com.yahoo.vespa.hosted.controller.api.integration.resource.CostReportConsumerMock; -import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceDatabaseClientMock; -import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceDatabaseClient; import com.yahoo.vespa.hosted.controller.api.integration.routing.GlobalRoutingService; import com.yahoo.vespa.hosted.controller.api.integration.routing.MemoryGlobalRoutingService; import com.yahoo.vespa.hosted.controller.api.integration.secrets.NoopTenantSecretService; @@ -83,9 +77,6 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg private final MockChangeRequestClient changeRequestClient = new MockChangeRequestClient(); private final AccessControlService accessControlService = new MockAccessControlService(); private final HorizonClient horizonClient = new MockHorizonClient(); - private final PlanRegistry planRegistry = new PlanRegistryMock(); - private final ResourceDatabaseClient resourceDb = new ResourceDatabaseClientMock(planRegistry); - private final BillingDatabaseClient billingDb = new BillingDatabaseClientMock(clock, planRegistry); public ServiceRegistryMock(SystemName system) { this.zoneRegistryMock = new ZoneRegistryMock(system); @@ -252,21 +243,6 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg return horizonClient; } - @Override - public PlanRegistry planRegistry() { - return planRegistry; - } - - @Override - public ResourceDatabaseClient resourceDatabase() { - return resourceDb; - } - - @Override - public BillingDatabaseClient billingDatabase() { - return billingDb; - } - public ConfigServerMock configServerMock() { return configServerMock; } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerTest.java index 3c4ed8bc8df..8bc32d262f1 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerTest.java @@ -3,8 +3,8 @@ package com.yahoo.vespa.hosted.controller.restapi.billing; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.TenantName; -import com.yahoo.vespa.hosted.controller.api.integration.billing.Bill; import com.yahoo.vespa.hosted.controller.api.integration.billing.CollectionMethod; +import com.yahoo.vespa.hosted.controller.api.integration.billing.Invoice; import com.yahoo.vespa.hosted.controller.api.integration.billing.MockBillingController; import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId; import com.yahoo.vespa.hosted.controller.api.role.Role; @@ -19,6 +19,7 @@ import org.junit.Test; import java.io.File; import java.math.BigDecimal; import java.time.LocalDate; +import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.List; @@ -94,10 +95,10 @@ public class BillingApiHandlerTest extends ControllerContainerCloudTest { @Test public void response_list_bills() { - var bill = createBill(); + var invoice = createInvoice(); - billingController.addBill(tenant, bill, true); - billingController.addBill(tenant, bill, false); + billingController.addInvoice(tenant, invoice, true); + billingController.addInvoice(tenant, invoice, false); billingController.setPlan(tenant, PlanId.from("some-plan"), true); var request = request("/billing/v1/tenant/tenant1/billing?until=2020-05-28").roles(tenantRole); @@ -106,9 +107,9 @@ public class BillingApiHandlerTest extends ControllerContainerCloudTest { } @Test - public void test_bill_creation() { - var bills = billingController.getBillsForTenant(tenant); - assertEquals(0, bills.size()); + public void test_invoice_creation() { + var invoices = billingController.getInvoicesForTenant(tenant); + assertEquals(0, invoices.size()); String requestBody = "{\"tenant\":\"tenant1\", \"startTime\":\"2020-04-20\", \"endTime\":\"2020-05-20\"}"; var request = request("/billing/v1/invoice", POST) @@ -119,11 +120,11 @@ public class BillingApiHandlerTest extends ControllerContainerCloudTest { request.roles(financeAdmin); tester.assertResponse(request, new File("invoice-creation-response")); - bills = billingController.getBillsForTenant(tenant); - assertEquals(1, bills.size()); - Bill bill = bills.get(0); - assertEquals(bill.getStartTime().toString(), "2020-04-20T00:00Z[UTC]"); - assertEquals(bill.getEndTime().toString(), "2020-05-20T00:00Z[UTC]"); + invoices = billingController.getInvoicesForTenant(tenant); + assertEquals(1, invoices.size()); + Invoice invoice = invoices.get(0); + assertEquals(invoice.getStartTime().toString(), "2020-04-20T00:00Z[UTC]"); + assertEquals(invoice.getEndTime().toString(), "2020-05-20T00:00Z[UTC]"); } @Test @@ -142,7 +143,7 @@ public class BillingApiHandlerTest extends ControllerContainerCloudTest { var lineItems = billingController.getUnusedLineItems(tenant); Assert.assertEquals(1, lineItems.size()); - Bill.LineItem lineItem = lineItems.get(0); + Invoice.LineItem lineItem = lineItems.get(0); assertEquals("some description", lineItem.description()); assertEquals(new BigDecimal("123.45"), lineItem.amount()); @@ -154,7 +155,7 @@ public class BillingApiHandlerTest extends ControllerContainerCloudTest { @Test public void adding_new_status() { - billingController.addBill(tenant, createBill(), true); + billingController.addInvoice(tenant, createInvoice(), true); var requestBody = "{\"status\":\"DONE\"}"; var request = request("/billing/v1/invoice/id-1/status", POST) @@ -162,21 +163,21 @@ public class BillingApiHandlerTest extends ControllerContainerCloudTest { .roles(financeAdmin); tester.assertResponse(request, "{\"message\":\"Updated status of invoice id-1\"}"); - var bill = billingController.getBillsForTenant(tenant).get(0); - assertEquals("DONE", bill.status()); + var invoice = billingController.getInvoicesForTenant(tenant).get(0); + assertEquals("DONE", invoice.status()); } @Test - public void list_all_unbilled_items() { + public void list_all_uninvoiced_items() { tester.controller().tenants().create(new CloudTenantSpec(tenant, ""), new Auth0Credentials(() -> "foo", Set.of(Role.hostedOperator()))); tester.controller().tenants().create(new CloudTenantSpec(tenant2, ""), new Auth0Credentials(() -> "foo", Set.of(Role.hostedOperator()))); - var bill = createBill(); + var invoice = createInvoice(); billingController.setPlan(tenant, PlanId.from("some-plan"), true); billingController.setPlan(tenant2, PlanId.from("some-plan"), true); - billingController.addBill(tenant, bill, false); + billingController.addInvoice(tenant, invoice, false); billingController.addLineItem(tenant, "support", new BigDecimal("42"), "Smith"); - billingController.addBill(tenant2, bill, false); + billingController.addInvoice(tenant2, invoice, false); var request = request("/billing/v1/billing?until=2020-05-28").roles(financeAdmin); @@ -194,8 +195,8 @@ public class BillingApiHandlerTest extends ControllerContainerCloudTest { @Test public void csv_export() { - var bill = createBill(); - billingController.addBill(tenant, bill, true); + var invoice = createInvoice(); + billingController.addInvoice(tenant, invoice, true); var csvRequest = request("/billing/v1/invoice/export", GET).roles(financeAdmin); tester.assertResponse(csvRequest.get(), new File("billing-all-invoices"), 200, false); } @@ -221,12 +222,12 @@ public class BillingApiHandlerTest extends ControllerContainerCloudTest { assertEquals(CollectionMethod.INVOICE, billingController.getCollectionMethod(tenant)); } - static Bill createBill() { + static Invoice createInvoice() { var start = LocalDate.of(2020, 5, 23).atStartOfDay(ZoneOffset.UTC); var end = start.plusDays(5); - var statusHistory = new Bill.StatusHistory(new TreeMap<>(Map.of(start, "OPEN"))); - return new Bill( - Bill.Id.of("id-1"), + var statusHistory = new Invoice.StatusHistory(new TreeMap<>(Map.of(start, "OPEN"))); + return new Invoice( + Invoice.Id.of("id-1"), TenantName.defaultName(), statusHistory, List.of(createLineItem(start)), @@ -235,8 +236,8 @@ public class BillingApiHandlerTest extends ControllerContainerCloudTest { ); } - static Bill.LineItem createLineItem(ZonedDateTime addedAt) { - return new Bill.LineItem( + static Invoice.LineItem createLineItem(ZonedDateTime addedAt) { + return new Invoice.LineItem( "some-id", "description", new BigDecimal("123.00"), 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 538e568804b..e733f8e27d6 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 @@ -43,7 +43,7 @@ public class BillingApiHandlerV2Test extends ControllerContainerCloudTest { var clock = (ManualClock) tester.controller().serviceRegistry().clock(); clock.setInstant(Instant.parse("2021-04-13T00:00:00Z")); billingController = (MockBillingController) tester.serviceRegistry().billingController(); - billingController.addBill(tenant, BillingApiHandlerTest.createBill(), true); + billingController.addInvoice(tenant, BillingApiHandlerTest.createInvoice(), true); } @Override |