aboutsummaryrefslogtreecommitdiffstats
path: root/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing
diff options
context:
space:
mode:
Diffstat (limited to 'controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing')
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Bill.java69
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillStatus.java33
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingController.java7
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingDatabaseClient.java15
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingDatabaseClientMock.java34
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingReporter.java9
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingReporterMock.java28
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/CollectionMethod.java2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/CollectionResult.java4
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/CostCalculator.java15
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/InstrumentList.java2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/InstrumentOwner.java2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/InvoiceUpdate.java45
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/MockBillingController.java18
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PaymentInstrument.java2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Plan.java2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanId.java2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistry.java6
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java24
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanResult.java2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Quota.java2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/QuotaCalculator.java2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/StatusHistory.java61
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/package-info.java2
24 files changed, 313 insertions, 75 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Bill.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Bill.java
index 664669d8e55..e7959d2057a 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Bill.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Bill.java
@@ -1,22 +1,18 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// 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.billing;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneId;
import java.math.BigDecimal;
-import java.time.Clock;
import java.time.LocalDate;
-import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
import java.util.Optional;
-import java.util.SortedMap;
-import java.util.TreeMap;
import java.util.UUID;
import java.util.function.Function;
@@ -42,14 +38,22 @@ public class Bill {
private final StatusHistory statusHistory;
private final ZonedDateTime startTime;
private final ZonedDateTime endTime;
+ private final String exportedId;
- public Bill(Id id, TenantName tenant, StatusHistory statusHistory, List<LineItem> lineItems, ZonedDateTime startTime, ZonedDateTime endTime) {
+ public Bill(Id id, TenantName tenant, StatusHistory statusHistory, List<LineItem> lineItems,
+ ZonedDateTime startTime, ZonedDateTime endTime) {
+ this(id, tenant, statusHistory, lineItems, startTime, endTime, null);
+ }
+
+ public Bill(Id id, TenantName tenant, StatusHistory statusHistory, List<LineItem> lineItems,
+ ZonedDateTime startTime, ZonedDateTime endTime, String exportedId) {
this.id = id;
this.tenant = tenant;
this.lineItems = List.copyOf(lineItems);
this.statusHistory = statusHistory;
this.startTime = startTime;
this.endTime = endTime;
+ this.exportedId = exportedId;
}
public Id id() {
@@ -60,7 +64,7 @@ public class Bill {
return tenant;
}
- public String status() {
+ public BillStatus status() {
return statusHistory.current();
}
@@ -80,6 +84,10 @@ public class Bill {
return endTime;
}
+ public Optional<String> getExportedId() {
+ return Optional.ofNullable(exportedId);
+ }
+
public LocalDate getStartDate() {
return startTime.toLocalDate();
}
@@ -196,6 +204,8 @@ public class Bill {
private BigDecimal gpuCost;
private NodeResources.Architecture architecture;
private int majorVersion;
+ private CloudAccount cloudAccount;
+ private String exportedId;
public LineItem(String id, String description, BigDecimal amount, String plan, String agent, ZonedDateTime addedAt) {
this.id = id;
@@ -204,10 +214,15 @@ public class Bill {
this.plan = plan;
this.agent = agent;
this.addedAt = addedAt;
+ this.cloudAccount = CloudAccount.empty;
}
- public LineItem(String id, String description, BigDecimal amount, String plan, String agent, ZonedDateTime addedAt, ZonedDateTime startedAt, ZonedDateTime endedAt, ApplicationId applicationId, ZoneId zoneId,
- BigDecimal cpuHours, BigDecimal memoryHours, BigDecimal diskHours, BigDecimal gpuHours, BigDecimal cpuCost, BigDecimal memoryCost, BigDecimal diskCost, BigDecimal gpuCost, NodeResources.Architecture architecture, int majorVersion) {
+ public LineItem(String id, String description, BigDecimal amount, String plan, String agent, ZonedDateTime addedAt,
+ ZonedDateTime startedAt, ZonedDateTime endedAt, ApplicationId applicationId, ZoneId zoneId,
+ BigDecimal cpuHours, BigDecimal memoryHours, BigDecimal diskHours, BigDecimal gpuHours, BigDecimal cpuCost,
+ BigDecimal memoryCost, BigDecimal diskCost, BigDecimal gpuCost, NodeResources.Architecture architecture,
+ int majorVersion, CloudAccount cloudAccount, String exportedId)
+ {
this(id, description, amount, plan, agent, addedAt);
this.startedAt = startedAt;
this.endedAt = endedAt;
@@ -227,6 +242,8 @@ public class Bill {
this.architecture = architecture;
this.majorVersion = majorVersion;
this.gpuCost = gpuCost;
+ this.cloudAccount = cloudAccount;
+ this.exportedId = exportedId;
}
/** The opaque ID of this */
@@ -319,6 +336,14 @@ public class Bill {
return majorVersion;
}
+ public CloudAccount getCloudAccount() {
+ return cloudAccount;
+ }
+
+ public Optional<String> getExportedId() {
+ return Optional.ofNullable(exportedId);
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -359,28 +384,4 @@ public class Bill {
}
}
- public static class StatusHistory {
- SortedMap<ZonedDateTime, String> history;
-
- public StatusHistory(SortedMap<ZonedDateTime, String> history) {
- this.history = history;
- }
-
- public static StatusHistory open(Clock clock) {
- var now = clock.instant().atZone(ZoneOffset.UTC);
- return new StatusHistory(
- new TreeMap<>(Map.of(now, "OPEN"))
- );
- }
-
- public String current() {
- return history.get(history.lastKey());
- }
-
- public SortedMap<ZonedDateTime, String> getHistory() {
- return history;
- }
-
- }
-
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillStatus.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillStatus.java
new file mode 100644
index 00000000000..4f35b47219a
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillStatus.java
@@ -0,0 +1,33 @@
+package com.yahoo.vespa.hosted.controller.api.integration.billing;
+
+/**
+ * @author gjoranv
+ */
+public enum BillStatus {
+ OPEN, // All bills start in this state. The bill can be modified and exported/synced to external systems.
+ FROZEN, // Syncing to external systems is switched off. No changes can be made.
+ CLOSED, // End state for a valid bill.
+ VOID; // End state, indicating that the bill is not valid.
+
+ // Legacy states, used by historical bills
+ private static final String LEGACY_ISSUED = "ISSUED";
+ private static final String LEGACY_EXPORTED = "EXPORTED";
+ private static final String LEGACY_CANCELED = "CANCELED";
+
+ private final String value;
+
+ BillStatus() {
+ this.value = name();
+ }
+
+ public String value() {
+ return value;
+ }
+
+ public static BillStatus from(String status) {
+ if (LEGACY_ISSUED.equals(status) || LEGACY_EXPORTED.equals(status)) return OPEN;
+ if (LEGACY_CANCELED.equals(status)) return VOID;
+ return Enum.valueOf(BillStatus.class, status.toUpperCase());
+ }
+
+}
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 dfa0d4dccf2..0aae466feb2 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// 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.billing;
import com.yahoo.config.provision.TenantName;
@@ -90,7 +90,7 @@ public interface BillingController {
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 updateBillStatus(Bill.Id billId, String agent, BillStatus status);
/** Add a line item to the given bill */
void addLineItem(TenantName tenant, String description, BigDecimal amount, Optional<Bill.Id> billId, String agent);
@@ -130,7 +130,4 @@ public interface BillingController {
default void updateCache(List<TenantName> tenants) {}
- default String exportBill(Bill bill, String exportMethod, CloudTenant tenant) {
- return "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
index 13229b650da..c5859cd7d2f 100644
--- 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
@@ -1,4 +1,4 @@
-// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// 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.billing;
import com.yahoo.config.provision.TenantName;
@@ -69,7 +69,7 @@ public interface BillingDatabaseClient {
* @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);
+ void setStatus(Bill.Id billId, String agent, BillStatus status);
List<Bill.LineItem> getUnusedLineItems(TenantName tenantName);
@@ -144,4 +144,15 @@ public interface BillingDatabaseClient {
* Performs necessary maintenance operations
*/
void maintain();
+
+ /**
+ * Set the invoice id from an external system for the given bill
+ */
+ void setExportedInvoiceId(Bill.Id billId, String invoiceId);
+
+ /**
+ * Set the invoice item id from an external system for the given line item
+ */
+ void setExportedInvoiceItemId(String lineItemId, String invoiceItemId);
+
}
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
index 3cee9e1ca2c..a6bcc9bf0ed 100644
--- 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
@@ -1,4 +1,4 @@
-// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// 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.billing;
import com.yahoo.config.provision.TenantName;
@@ -26,9 +26,10 @@ public class BillingDatabaseClientMock implements BillingDatabaseClient {
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, StatusHistory> statuses = new HashMap<>();
private final Map<Bill.Id, ZonedDateTime> startTimes = new HashMap<>();
private final Map<Bill.Id, ZonedDateTime> endTimes = new HashMap<>();
+ private final Map<Bill.Id, String> exportedInvoiceIds = 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"));
@@ -53,7 +54,7 @@ public class BillingDatabaseClientMock implements BillingDatabaseClient {
.findFirst();
}
- public String getStatus(Bill.Id invoiceId) {
+ public BillStatus getStatus(Bill.Id invoiceId) {
return statuses.get(invoiceId).current();
}
@@ -61,7 +62,7 @@ public class BillingDatabaseClientMock implements BillingDatabaseClient {
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));
+ statuses.computeIfAbsent(invoiceId, l -> StatusHistory.open(clock));
startTimes.put(invoiceId, startTime);
endTimes.put(invoiceId, endTime);
return invoiceId;
@@ -71,10 +72,11 @@ public class BillingDatabaseClientMock implements BillingDatabaseClient {
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 status = statuses.getOrDefault(billId, 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));
+ var exportedId = exportedInvoiceId(billId);
+ return invoice.map(tenant -> new Bill(billId, tenant, status, lines, start, end, exportedId));
}
@Override
@@ -88,8 +90,8 @@ public class BillingDatabaseClientMock implements BillingDatabaseClient {
}
@Override
- public void setStatus(Bill.Id invoiceId, String agent, String status) {
- statuses.computeIfAbsent(invoiceId, k -> Bill.StatusHistory.open(clock))
+ public void setStatus(Bill.Id invoiceId, String agent, BillStatus status) {
+ statuses.computeIfAbsent(invoiceId, k -> StatusHistory.open(clock))
.getHistory()
.put(ZonedDateTime.now(), status);
}
@@ -157,7 +159,7 @@ public class BillingDatabaseClientMock implements BillingDatabaseClient {
var status = statuses.get(invoiceId);
var start = startTimes.get(invoiceId);
var end = endTimes.get(invoiceId);
- return new Bill(invoiceId, tenant, status, items, start, end);
+ return new Bill(invoiceId, tenant, status, items, start, end, exportedInvoiceId(invoiceId));
})
.toList();
}
@@ -171,11 +173,23 @@ public class BillingDatabaseClientMock implements BillingDatabaseClient {
var status = statuses.get(invoiceId);
var start = startTimes.get(invoiceId);
var end = endTimes.get(invoiceId);
- return new Bill(invoiceId, tenant, status, items, start, end);
+ return new Bill(invoiceId, tenant, status, items, start, end, exportedInvoiceId(invoiceId));
})
.toList();
}
@Override
public void maintain() {}
+
+ @Override
+ public void setExportedInvoiceId(Bill.Id billId, String invoiceId) {
+ exportedInvoiceIds.put(billId, invoiceId);
+ }
+
+ @Override
+ public void setExportedInvoiceItemId(String lineItemId, String invoiceItemId) { }
+
+ private String exportedInvoiceId(Bill.Id billId) {
+ return exportedInvoiceIds.getOrDefault(billId, null);
+ }
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingReporter.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingReporter.java
index 719d22429b8..676c29cec5d 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingReporter.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingReporter.java
@@ -1,3 +1,4 @@
+// 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.billing;
import com.yahoo.vespa.hosted.controller.tenant.BillingReference;
@@ -5,4 +6,12 @@ import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
public interface BillingReporter {
BillingReference maintainTenant(CloudTenant tenant);
+
+ InvoiceUpdate maintainInvoice(Bill bill);
+
+ /** Export a bill to a payment service. Returns the invoice ID in the external system. */
+ default String exportBill(Bill bill, String exportMethod, CloudTenant tenant) {
+ return "NOT_IMPLEMENTED";
+ }
+
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingReporterMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingReporterMock.java
index 34599f83a8c..689ecc356dc 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingReporterMock.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingReporterMock.java
@@ -1,21 +1,45 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// 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.billing;
import com.yahoo.vespa.hosted.controller.tenant.BillingReference;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
+import java.math.BigDecimal;
import java.time.Clock;
+import java.time.ZonedDateTime;
+import java.util.Optional;
import java.util.UUID;
public class BillingReporterMock implements BillingReporter {
private final Clock clock;
+ private final BillingDatabaseClient dbClient;
- public BillingReporterMock(Clock clock) {
+ public BillingReporterMock(Clock clock, BillingDatabaseClient dbClient) {
this.clock = clock;
+ this.dbClient = dbClient;
}
@Override
public BillingReference maintainTenant(CloudTenant tenant) {
return new BillingReference(UUID.randomUUID().toString(), clock.instant());
}
+
+ @Override
+ public InvoiceUpdate maintainInvoice(Bill bill) {
+ dbClient.addLineItem(bill.tenant(), maintainedMarkerItem(), Optional.of(bill.id()));
+ return new InvoiceUpdate(1,0,0);
+ }
+
+ @Override
+ public String exportBill(Bill bill, String exportMethod, CloudTenant tenant) {
+ // Replace bill with a copy with exportedId set
+ var exportedId = "EXT-ID-123";
+ dbClient.setExportedInvoiceId(bill.id(), exportedId);
+ return exportedId;
+ }
+
+ private static Bill.LineItem maintainedMarkerItem() {
+ return new Bill.LineItem("maintained", "", BigDecimal.valueOf(0.0), "", "", ZonedDateTime.now());
+ }
+
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/CollectionMethod.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/CollectionMethod.java
index 98abe883816..87c9a13d64a 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/CollectionMethod.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/CollectionMethod.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// 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.billing;
public enum CollectionMethod {
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/CollectionResult.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/CollectionResult.java
index 4cd040c7f11..4e83d90f6b0 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/CollectionResult.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/CollectionResult.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// 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.billing;
import java.util.Objects;
@@ -52,4 +52,4 @@ public class CollectionResult {
public int hashCode() {
return Objects.hash(errorMessage);
}
-} \ No newline at end of file
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/CostCalculator.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/CostCalculator.java
index e0a5ad9bdb7..ddcd5308986 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/CostCalculator.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/CostCalculator.java
@@ -1,10 +1,12 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// 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.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;
+
/**
* @author ogronnesby
*/
@@ -16,4 +18,15 @@ public interface CostCalculator {
/** Estimate the cost for the given resources */
double calculate(NodeResources resources);
+ /** CPU unit price */
+ BigDecimal getCpuPrice();
+
+ /** Memory unit price */
+ BigDecimal getMemoryPrice();
+
+ /** Disk unit price */
+ BigDecimal getDiskPrice();
+
+ /** GPU unit price */
+ BigDecimal getGpuPrice();
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/InstrumentList.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/InstrumentList.java
index 7e430bb18bb..559a3e8ee9c 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/InstrumentList.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/InstrumentList.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// 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.billing;
import java.util.ArrayList;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/InstrumentOwner.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/InstrumentOwner.java
index 8a333af0ddb..bc9f82c212b 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/InstrumentOwner.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/InstrumentOwner.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// 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.billing;
import com.yahoo.config.provision.TenantName;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/InvoiceUpdate.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/InvoiceUpdate.java
new file mode 100644
index 00000000000..6ca3cf6ebb1
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/InvoiceUpdate.java
@@ -0,0 +1,45 @@
+package com.yahoo.vespa.hosted.controller.api.integration.billing;
+
+/**
+ * Helper to track changes to an invoice.
+ *
+ * @author gjoranv
+ */
+public record InvoiceUpdate(int itemsAdded, int itemsRemoved, int itemsModified) {
+ public boolean isEmpty() {
+ return itemsAdded == 0 && itemsRemoved == 0 && itemsModified == 0;
+ }
+
+ public static InvoiceUpdate empty() {
+ return new InvoiceUpdate(0, 0, 0);
+ }
+
+ public static class Counter {
+ private int itemsAdded = 0;
+ private int itemsRemoved = 0;
+ private int itemsModified = 0;
+
+ public void addedItem() {
+ itemsAdded++;
+ }
+
+ public void removedItem() {
+ itemsRemoved++;
+ }
+
+ public void modifiedItem() {
+ itemsModified++;
+ }
+
+ public void add(InvoiceUpdate other) {
+ itemsAdded += other.itemsAdded;
+ itemsRemoved += other.itemsRemoved;
+ itemsModified += other.itemsModified;
+ }
+
+ public InvoiceUpdate finish() {
+ return new InvoiceUpdate(itemsAdded, itemsRemoved, itemsModified);
+ }
+ }
+
+}
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 eb20126304e..f8e83b44510 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
@@ -1,7 +1,8 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// 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.billing;
import com.yahoo.config.provision.TenantName;
+import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import java.math.BigDecimal;
import java.time.Clock;
@@ -22,18 +23,20 @@ import java.util.stream.Stream;
public class MockBillingController implements BillingController {
private final Clock clock;
+ private final BillingDatabaseClient dbClient;
PlanId defaultPlan = PlanId.from("trial");
List<TenantName> tenants = new ArrayList<>();
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<>();
+ public Map<TenantName, Bill> uncommittedBills = new HashMap<>();
Map<TenantName, List<Bill.LineItem>> unusedLineItems = new HashMap<>();
Map<TenantName, CollectionMethod> collectionMethod = new HashMap<>();
- public MockBillingController(Clock clock) {
+ public MockBillingController(Clock clock, BillingDatabaseClient dbClient) {
this.clock = clock;
+ this.dbClient = dbClient;
}
@Override
@@ -71,7 +74,7 @@ public class MockBillingController implements BillingController {
.add(new Bill(
billId,
tenant,
- Bill.StatusHistory.open(clock),
+ StatusHistory.open(clock),
List.of(),
startTime,
endTime
@@ -116,7 +119,7 @@ public class MockBillingController implements BillingController {
}
@Override
- public void updateBillStatus(Bill.Id billId, String agent, String status) {
+ public void updateBillStatus(Bill.Id billId, String agent, BillStatus status) {
var now = clock.instant().atZone(ZoneOffset.UTC);
committedBills.values().stream()
.flatMap(List::stream)
@@ -134,7 +137,7 @@ public class MockBillingController implements BillingController {
"line-item-id",
description,
amount,
- "some-plan",
+ "paid",
agent,
ZonedDateTime.now()));
}
@@ -203,6 +206,7 @@ public class MockBillingController implements BillingController {
return count < limit;
}
+
public void setTenants(List<TenantName> tenants) {
this.tenants = tenants;
}
@@ -234,6 +238,6 @@ public class MockBillingController implements BillingController {
private Bill emptyBill() {
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 Bill(Bill.Id.of("empty"), TenantName.defaultName(), StatusHistory.open(clock), List.of(), start, end);
}
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PaymentInstrument.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PaymentInstrument.java
index 4d0f40afc2e..63771c3366a 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PaymentInstrument.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PaymentInstrument.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// 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.billing;
/**
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Plan.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Plan.java
index bd4c3c1a56f..5e964603db1 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Plan.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Plan.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// 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.billing;
/**
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanId.java
index 2ac0505d507..061f8731b41 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanId.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanId.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// 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.billing;
import java.util.Objects;
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
index 6b267e538e7..c0bd0dd29cd 100644
--- 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// 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.billing;
import java.util.List;
@@ -20,6 +20,10 @@ public interface PlanRegistry {
/** Get a set of all plans */
List<Plan> all();
+ default Plan require(String planId) {
+ return plan(planId).orElseThrow();
+ }
+
/** Get a plan give a plan ID */
default Optional<Plan> plan(String planId) {
if (planId == null || planId.isBlank())
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java
index 89226d3309e..5af4d0cff29 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java
@@ -1,3 +1,4 @@
+// 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.billing;
import com.yahoo.config.provision.NodeResources;
@@ -134,7 +135,8 @@ public class PlanRegistryMock implements PlanRegistry {
dgbCost,
gpuCost,
usage.getArchitecture(),
- usage.getMajorVersion()
+ usage.getMajorVersion(),
+ usage.getCloudAccount()
);
}
@@ -142,5 +144,25 @@ public class PlanRegistryMock implements PlanRegistry {
public double calculate(NodeResources resources) {
return resources.cost();
}
+
+ @Override
+ public BigDecimal getCpuPrice() {
+ return cpuHourCost;
+ }
+
+ @Override
+ public BigDecimal getMemoryPrice() {
+ return memHourCost;
+ }
+
+ @Override
+ public BigDecimal getDiskPrice() {
+ return dgbHourCost;
+ }
+
+ @Override
+ public BigDecimal getGpuPrice() {
+ return gpuHourCost;
+ }
}
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanResult.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanResult.java
index b86d1199956..35e7fcbcf6e 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanResult.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanResult.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// 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.billing;
import java.util.Objects;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Quota.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Quota.java
index 0859cc404ce..ca339f9cf15 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Quota.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Quota.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// 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.billing;
import java.math.BigDecimal;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/QuotaCalculator.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/QuotaCalculator.java
index 0608e6d95ac..9511d1109ff 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/QuotaCalculator.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/QuotaCalculator.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// 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.billing;
/**
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/StatusHistory.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/StatusHistory.java
new file mode 100644
index 00000000000..f0c7f806c8c
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/StatusHistory.java
@@ -0,0 +1,61 @@
+package com.yahoo.vespa.hosted.controller.api.integration.billing;
+
+import java.time.Clock;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * @author ogronnesby
+ */
+public class StatusHistory {
+ SortedMap<ZonedDateTime, BillStatus> history;
+
+ public StatusHistory(SortedMap<ZonedDateTime, BillStatus> history) {
+ // Validate the given history
+ var iter = history.values().iterator();
+ BillStatus next = iter.hasNext() ? iter.next() : null;
+ while (iter.hasNext()) {
+ var current = next;
+ next = iter.next();
+ if (! validateStatus(current, next)) {
+ throw new IllegalArgumentException("Invalid transition from " + current + " to " + next);
+ }
+ }
+
+ this.history = history;
+ }
+
+ public static StatusHistory open(Clock clock) {
+ var now = clock.instant().atZone(ZoneOffset.UTC);
+ return new StatusHistory(
+ new TreeMap<>(Map.of(now, BillStatus.OPEN))
+ );
+ }
+
+ public BillStatus current() {
+ return history.get(history.lastKey());
+ }
+
+ public SortedMap<ZonedDateTime, BillStatus> getHistory() {
+ return history;
+ }
+
+ public void checkValidTransition(BillStatus newStatus) {
+ if (! validateStatus(current(), newStatus)) {
+ throw new IllegalArgumentException("Invalid transition from " + current() + " to " + newStatus);
+ }
+ }
+
+ private static boolean validateStatus(BillStatus current, BillStatus newStatus) {
+ return switch(current) {
+ case OPEN -> true;
+ case FROZEN -> newStatus != BillStatus.OPEN; // This could be subject to change.
+ case CLOSED -> newStatus == BillStatus.CLOSED;
+ case VOID -> newStatus == BillStatus.VOID;
+ };
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/package-info.java
index 35b1fc912f0..d05e9aa4c09 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/package-info.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/package-info.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
@ExportPackage
package com.yahoo.vespa.hosted.controller.api.integration.billing;