summaryrefslogtreecommitdiffstats
path: root/controller-api
diff options
context:
space:
mode:
authorOla Aunrønning <olaa@verizonmedia.com>2020-06-29 10:26:28 +0200
committerOla Aunrønning <olaa@verizonmedia.com>2020-06-29 10:26:28 +0200
commit71e6628cf3aaa58d8a4dfd8325da5f3e841866a2 (patch)
tree149ad36bf5300bdd96d3f90b239b1e1691c99d19 /controller-api
parent0dbbd3c3be2870681480e73f9cc491e349b06610 (diff)
Revert "Merge pull request #13726 from vespa-engine/revert-13715-olaa/billing-api-handler"
This reverts commit 0dbbd3c3be2870681480e73f9cc491e349b06610, reversing changes made to 0a8b5894dfc442d661836fce4ddb6c870bcc0ec0.
Diffstat (limited to 'controller-api')
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/ServiceRegistry.java4
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingController.java49
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/CostCalculator.java19
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/InstrumentList.java39
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/InstrumentOwner.java67
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Invoice.java266
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/MockBillingController.java144
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PaymentInstrument.java51
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Plan.java23
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanController.java10
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanId.java43
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/ResourceUsage.java54
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java4
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleDefinition.java1
15 files changed, 671 insertions, 108 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 0b5f2538892..a522e26a46d 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
@@ -4,7 +4,7 @@ package com.yahoo.vespa.hosted.controller.api.integration;
import com.yahoo.vespa.hosted.controller.api.integration.aws.ApplicationRoleService;
import com.yahoo.vespa.hosted.controller.api.integration.aws.AwsEventFetcher;
import com.yahoo.vespa.hosted.controller.api.integration.aws.ResourceTagger;
-import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanController;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingController;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateProvider;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServer;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationStore;
@@ -77,6 +77,6 @@ public interface ServiceRegistry {
SystemMonitor systemMonitor();
- PlanController planController();
+ BillingController billingController();
}
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
new file mode 100644
index 00000000000..bd9568fe891
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/BillingController.java
@@ -0,0 +1,49 @@
+// 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 com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.ZonedDateTime;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+public interface BillingController {
+
+ PlanId getPlan(TenantName tenant);
+
+ /**
+ * Returns true if plan was changed
+ */
+ boolean setPlan(TenantName tenant, PlanId planId, boolean hasApplications);
+
+ Invoice.Id createInvoiceForPeriod(TenantName tenant, ZonedDateTime startTime, ZonedDateTime endTime, String agent);
+
+ Invoice createUncommittedInvoice(TenantName tenant, LocalDate until);
+
+ Map<TenantName, Invoice> createUncommittedInvoices(LocalDate until);
+
+ List<Invoice.LineItem> getUnusedLineItems(TenantName tenant);
+
+ Optional<PaymentInstrument> getDefaultInstrument(TenantName tenant);
+
+ String createClientToken(String tenant, String userId);
+
+ boolean deleteInstrument(TenantName tenant, String userId, String instrumentId);
+
+ void updateInvoiceStatus(Invoice.Id invoiceId, String agent, String status);
+
+ void addLineItem(TenantName tenant, String description, BigDecimal amount, String agent);
+
+ void deleteLineItem(String lineItemId);
+
+ boolean setActivePaymentInstrument(InstrumentOwner paymentInstrument);
+
+ InstrumentList listInstruments(TenantName tenant, String userId);
+
+ List<Invoice> getInvoices(TenantName tenant);
+
+} \ 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
deleted file mode 100644
index 628beec8450..00000000000
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/CostCalculator.java
+++ /dev/null
@@ -1,19 +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.NodeResources;
-import com.yahoo.vespa.hosted.controller.api.integration.resource.CostInfo;
-
-
-/**
- * @author ogronnesby
- */
-public interface CostCalculator {
-
- /** Calculate the cost for the given usage */
- CostInfo calculate(ResourceUsage usage);
-
- /** Estimate the cost for the given resources */
- double calculate(NodeResources resources);
-
-}
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
new file mode 100644
index 00000000000..f26261cd157
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/InstrumentList.java
@@ -0,0 +1,39 @@
+// 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 java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author olaa
+ */
+public class InstrumentList {
+
+ private String activeInstrumentId;
+ private List<PaymentInstrument> instruments;
+
+
+ public InstrumentList(List<PaymentInstrument> instruments) {
+ this.instruments = instruments;
+ }
+
+ public void setActiveInstrumentId(String activeInstrumentId) {
+ this.activeInstrumentId = activeInstrumentId;
+ }
+
+ public void addInstrument(PaymentInstrument instrument) {
+ instruments.add(instrument);
+ }
+
+ public void addInstruments(List<PaymentInstrument> instruments) {
+ instruments.addAll(instruments);
+ }
+
+ public String getActiveInstrumentId() {
+ return activeInstrumentId;
+ }
+
+ public List<PaymentInstrument> getInstruments() {
+ return instruments;
+ }
+}
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
new file mode 100644
index 00000000000..45e06b11b2a
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/InstrumentOwner.java
@@ -0,0 +1,67 @@
+// 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.util.Objects;
+
+/**
+ * @author olaa
+ */
+public class InstrumentOwner {
+
+ private final TenantName tenantName;
+ private final String userId;
+ private final String paymentInstrumentId;
+ private final boolean isDefault;
+
+ public InstrumentOwner(TenantName tenantName, String userId, String paymentInstrumentId, boolean isDefault) {
+ this.tenantName = tenantName;
+ this.userId = userId;
+ this.paymentInstrumentId = paymentInstrumentId;
+ this.isDefault = isDefault;
+ }
+
+ public TenantName getTenantName() {
+ return tenantName;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public String getPaymentInstrumentId() {
+ return paymentInstrumentId;
+ }
+
+ public boolean isDefault() {
+ return isDefault;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "Tenant: %s\nCusomer ID: %s\nPayment Instrument ID: %s\nIs default: %s",
+ tenantName.value(),
+ userId,
+ paymentInstrumentId,
+ isDefault
+ );
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ InstrumentOwner other = (InstrumentOwner) o;
+ return this.tenantName.equals(other.getTenantName()) &&
+ this.userId.equals(other.getUserId()) &&
+ this.paymentInstrumentId.equals(other.getPaymentInstrumentId()) &&
+ this.isDefault() == other.isDefault();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(tenantName, userId, paymentInstrumentId, isDefault);
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Invoice.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Invoice.java
new file mode 100644
index 00000000000..31388d24e2e
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Invoice.java
@@ -0,0 +1,266 @@
+// 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.ApplicationId;
+import com.yahoo.config.provision.zone.ZoneId;
+
+import java.math.BigDecimal;
+import java.time.ZonedDateTime;
+import java.util.Collections;
+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;
+
+
+/**
+ * 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 invoice has a status history, but only the latest status is exposed through this API.
+ *
+ * @author ogronnesby
+ */
+public class Invoice {
+ private static final BigDecimal SCALED_ZERO = new BigDecimal("0.00");
+
+ private final Id id;
+ private final List<LineItem> lineItems;
+ private final StatusHistory statusHistory;
+ private final ZonedDateTime startTime;
+ private final ZonedDateTime endTime;
+
+ public Invoice(Id id, StatusHistory statusHistory, List<LineItem> lineItems, ZonedDateTime startTime, ZonedDateTime endTime) {
+ this.id = id;
+ this.lineItems = List.copyOf(lineItems);
+ this.statusHistory = statusHistory;
+ this.startTime = startTime;
+ this.endTime = endTime;
+ }
+
+ public Id id() {
+ return id;
+ }
+
+ public String status() {
+ return statusHistory.current();
+ }
+
+ public StatusHistory statusHistory() {
+ return statusHistory;
+ }
+
+ public List<LineItem> lineItems() {
+ return lineItems;
+ }
+
+ public ZonedDateTime getStartTime() {
+ return startTime;
+ }
+
+ public ZonedDateTime getEndTime() {
+ return endTime;
+ }
+
+ public BigDecimal sum() {
+ return lineItems.stream().map(LineItem::amount).reduce(SCALED_ZERO, BigDecimal::add);
+ }
+
+ public static final class Id {
+ private final String value;
+
+ public static Id of(String value) {
+ Objects.requireNonNull(value);
+ return new Id(value);
+ }
+
+ public static Id generate() {
+ var id = UUID.randomUUID().toString();
+ return new Id(id);
+ }
+
+ private Id(String value) {
+ this.value = value;
+ }
+
+ public String value() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Id invoiceId = (Id) o;
+ return value.equals(invoiceId.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(value);
+ }
+
+ @Override
+ public String toString() {
+ return "InvoiceId{" +
+ "value='" + value + '\'' +
+ '}';
+ }
+ }
+
+ /**
+ * Represents a chargeable line on an invoice.
+ */
+ public static class LineItem {
+ private final String id;
+ private final String description;
+ private final BigDecimal amount;
+ private final String plan;
+ private final String agent;
+ private final ZonedDateTime addedAt;
+ private final Optional<ZonedDateTime> startedAt;
+ private final Optional<ZonedDateTime> endedAt;
+ private final Optional<ApplicationId> applicationId;
+ private final Optional<ZoneId> zoneId;
+
+ public LineItem(String id, String description, BigDecimal amount, String plan, String agent, ZonedDateTime addedAt, ZonedDateTime startedAt, ZonedDateTime endedAt, ApplicationId applicationId, ZoneId zoneId) {
+ this.id = id;
+ this.description = description;
+ this.amount = amount;
+ this.plan = plan;
+ this.agent = agent;
+ this.addedAt = addedAt;
+ this.startedAt = Optional.ofNullable(startedAt);
+ this.endedAt = Optional.ofNullable(endedAt);
+
+ if (applicationId == null && zoneId != null)
+ throw new IllegalArgumentException("Must supply applicationId if zoneId is supplied");
+
+ this.applicationId = Optional.ofNullable(applicationId);
+ this.zoneId = Optional.ofNullable(zoneId);
+ }
+
+ public LineItem(String id, String description, BigDecimal amount, String plan, String agent, ZonedDateTime addedAt) {
+ this(id, description, amount, plan, agent, addedAt, null, null, null, null);
+ }
+
+ /** The opaque ID of this */
+ public String id() {
+ return id;
+ }
+
+ /** The string description of this - used for display purposes */
+ public String description() {
+ return description;
+ }
+
+ /** The dollar amount of this */
+ public BigDecimal amount() {
+ return SCALED_ZERO.add(amount);
+ }
+
+ /** The plan used to calculate amount of this */
+ public String plan() {
+ return plan;
+ }
+
+ /** Who created this line item */
+ public String agent() {
+ return agent;
+ }
+
+ /** When was this line item added */
+ public ZonedDateTime addedAt() {
+ return addedAt;
+ }
+
+ /** What time period is this line item for - time start */
+ public Optional<ZonedDateTime> startedAt() {
+ return startedAt;
+ }
+
+ /** What time period is this line item for - time end */
+ public Optional<ZonedDateTime> endedAt() {
+ return endedAt;
+ }
+
+ /** Optionally - what application is this line item about */
+ public Optional<ApplicationId> applicationId() {
+ return applicationId;
+ }
+
+ /** Optionally - what zone deployment is this line item about */
+ public Optional<ZoneId> zoneId() {
+ return zoneId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ LineItem lineItem = (LineItem) o;
+ return id.equals(lineItem.id) &&
+ description.equals(lineItem.description) &&
+ amount.equals(lineItem.amount) &&
+ plan.equals(lineItem.plan) &&
+ agent.equals(lineItem.agent) &&
+ addedAt.equals(lineItem.addedAt) &&
+ startedAt.equals(lineItem.startedAt) &&
+ endedAt.equals(lineItem.endedAt) &&
+ applicationId.equals(lineItem.applicationId) &&
+ zoneId.equals(lineItem.zoneId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, description, amount, plan, agent, addedAt, startedAt, endedAt, applicationId, zoneId);
+ }
+
+ @Override
+ public String toString() {
+ return "LineItem{" +
+ "id='" + id + '\'' +
+ ", description='" + description + '\'' +
+ ", amount=" + amount +
+ ", plan='" + plan + '\'' +
+ ", agent='" + agent + '\'' +
+ ", addedAt=" + addedAt +
+ ", startedAt=" + startedAt +
+ ", endedAt=" + endedAt +
+ ", applicationId=" + applicationId +
+ ", zoneId=" + zoneId +
+ '}';
+ }
+ }
+
+ public static class StatusHistory {
+ SortedMap<ZonedDateTime, String> history;
+
+ public StatusHistory(SortedMap<ZonedDateTime, String> history) {
+ this.history = history;
+ }
+
+ public static StatusHistory open() {
+ return new StatusHistory(
+ new TreeMap<>(Map.of(ZonedDateTime.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/MockBillingController.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/MockBillingController.java
new file mode 100644
index 00000000000..a4c25e301ba
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/MockBillingController.java
@@ -0,0 +1,144 @@
+// 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 com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * @author olaa
+ */
+public class MockBillingController implements BillingController {
+
+ Map<TenantName, PlanId> plans = new HashMap<>();
+ Map<TenantName, PaymentInstrument> activeInstruments = new HashMap<>();
+ Map<TenantName, List<Invoice>> committedInvoices = new HashMap<>();
+ Map<TenantName, Invoice> uncommittedInvoices = new HashMap<>();
+ Map<TenantName, List<Invoice.LineItem>> unusedLineItems = new HashMap<>();
+
+ @Override
+ public PlanId getPlan(TenantName tenant) {
+ return plans.get(tenant);
+ }
+
+ @Override
+ public boolean setPlan(TenantName tenant, PlanId planId, boolean hasApplications) {
+ plans.put(tenant, planId);
+ return true;
+ }
+
+ @Override
+ 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,
+ Invoice.StatusHistory.open(),
+ List.of(),
+ startTime,
+ endTime
+ ));
+ return invoiceId;
+ }
+
+ @Override
+ public Invoice createUncommittedInvoice(TenantName tenant, LocalDate until) {
+ return uncommittedInvoices.get(tenant);
+ }
+
+ @Override
+ public Map<TenantName, Invoice> createUncommittedInvoices(LocalDate until) {
+ return uncommittedInvoices;
+ }
+
+ @Override
+ public List<Invoice.LineItem> getUnusedLineItems(TenantName tenant) {
+ return unusedLineItems.getOrDefault(tenant, List.of());
+ }
+
+ @Override
+ public Optional<PaymentInstrument> getDefaultInstrument(TenantName tenant) {
+ return Optional.ofNullable(activeInstruments.get(tenant));
+ }
+
+ @Override
+ public String createClientToken(String tenant, String userId) {
+ return "some-token";
+ }
+
+ @Override
+ public boolean deleteInstrument(TenantName tenant, String userId, String instrumentId) {
+ activeInstruments.remove(tenant);
+ return true;
+ }
+
+ @Override
+ public void updateInvoiceStatus(Invoice.Id invoiceId, String agent, String status) {
+ committedInvoices.values().stream()
+ .flatMap(List::stream)
+ .filter(invoice -> invoiceId.equals(invoice.id()))
+ .forEach(invoice -> invoice.statusHistory().history.put(ZonedDateTime.now(), status));
+ }
+
+ @Override
+ public void addLineItem(TenantName tenant, String description, BigDecimal amount, String agent) {
+ unusedLineItems.computeIfAbsent(tenant, l -> new ArrayList<>())
+ .add(new Invoice.LineItem(
+ "line-item-id",
+ description,
+ amount,
+ "some-plan",
+ agent,
+ ZonedDateTime.now()
+ ));
+ }
+
+ @Override
+ public void deleteLineItem(String lineItemId) {
+
+ }
+
+ @Override
+ public boolean setActivePaymentInstrument(InstrumentOwner paymentInstrument) {
+ var instrumentId = paymentInstrument.getPaymentInstrumentId();
+ activeInstruments.put(paymentInstrument.getTenantName(), createInstrument(instrumentId));
+ return true;
+ }
+
+ @Override
+ public InstrumentList listInstruments(TenantName tenant, String userId) {
+ return null;
+ }
+
+ @Override
+ public List<Invoice> getInvoices(TenantName tenant) {
+ return committedInvoices.getOrDefault(tenant, List.of());
+ }
+
+ private PaymentInstrument createInstrument(String id) {
+ return new PaymentInstrument(id,
+ "name",
+ "displayText",
+ "brand",
+ "type",
+ "endingWith",
+ "expiryDate"
+ );
+ }
+
+ public void addInvoice(TenantName tenantName, Invoice invoice, boolean committed) {
+ if (committed)
+ committedInvoices.computeIfAbsent(tenantName, i -> new ArrayList<>())
+ .add(invoice);
+ else
+ uncommittedInvoices.put(tenantName, invoice);
+ }
+}
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
new file mode 100644
index 00000000000..7b8d36f3d4f
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PaymentInstrument.java
@@ -0,0 +1,51 @@
+// 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;
+
+/**
+ * @author olaa
+ */
+public class PaymentInstrument {
+
+ private final String id;
+ private final String nameOnCard;
+ private final String displayText;
+ private final String brand;
+ private final String type;
+ private final String endingWith;
+ private final String expiryDate;
+
+
+ public PaymentInstrument(String id, String nameOnCard, String displayText, String brand, String type, String endingWith, String expiryDate) {
+ this.id = id;
+ this.nameOnCard = nameOnCard;
+ this.displayText = displayText;
+ this.brand = brand;
+ this.type = type;
+ this.endingWith = endingWith;
+ this.expiryDate = expiryDate;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getNameOnCard() {
+ return nameOnCard;
+ }
+
+ public String getDisplayText() {
+ return displayText;
+ }
+
+ public String getBrand() {
+ return brand;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getEndingWith() {
+ return endingWith;
+ }
+}
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
deleted file mode 100644
index 75a88136c45..00000000000
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Plan.java
+++ /dev/null
@@ -1,23 +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;
-
-/**
- * A Plan decides two different things:
- *
- * - How to map from usage to a sum of money that is owed.
- * - Limits on how much resources can be used.
- *
- * @author ogronnesby
- */
-public interface Plan {
-
- /** The ID of the plan as used in APIs and storage systems */
- String id();
-
- /** The calculator used to calculate a bill for usage */
- CostCalculator calculator();
-
- /** The quota limits associated with the plan */
- Object quota();
-
-}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanController.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanController.java
deleted file mode 100644
index f13c251d212..00000000000
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanController.java
+++ /dev/null
@@ -1,10 +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;
-
-public interface PlanController {
-
- Plan getPlan(TenantName tenant);
-
-} \ No newline at end of file
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
new file mode 100644
index 00000000000..68a897c904f
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanId.java
@@ -0,0 +1,43 @@
+// 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 java.util.Objects;
+
+/**
+ * @author olaa
+ */
+public class PlanId {
+
+ private final String value;
+
+ public PlanId(String value) {
+ if (value.isBlank())
+ throw new IllegalArgumentException("Id must be non-blank.");
+ this.value = value;
+ }
+
+ public static PlanId from(String value) {
+ return new PlanId(value);
+ }
+
+ public String value() { return value; }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PlanId id = (PlanId) o;
+ return Objects.equals(value, id.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(value);
+ }
+
+ @Override
+ public String toString() {
+ return "plan '" + value + "'";
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/ResourceUsage.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/ResourceUsage.java
deleted file mode 100644
index cbfd2b6ff50..00000000000
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/ResourceUsage.java
+++ /dev/null
@@ -1,54 +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.ApplicationId;
-import com.yahoo.config.provision.zone.ZoneId;
-
-import java.math.BigDecimal;
-
-/**
- * @author olaa
- */
-public class ResourceUsage {
-
- private final ApplicationId applicationId;
- private final ZoneId zoneId;
- private final Plan plan;
- private final BigDecimal cpuMillis;
- private final BigDecimal memoryMillis;
- private final BigDecimal diskMillis;
-
- public ResourceUsage(ApplicationId applicationId, ZoneId zoneId, Plan plan,
- BigDecimal cpuMillis, BigDecimal memoryMillis, BigDecimal diskMillis) {
- this.applicationId = applicationId;
- this.zoneId = zoneId;
- this.cpuMillis = cpuMillis;
- this.memoryMillis = memoryMillis;
- this.diskMillis = diskMillis;
- this.plan = plan;
- }
-
- public ApplicationId getApplicationId() {
- return applicationId;
- }
-
- public ZoneId getZoneId() {
- return zoneId;
- }
-
- public BigDecimal getCpuMillis() {
- return cpuMillis;
- }
-
- public BigDecimal getMemoryMillis() {
- return memoryMillis;
- }
-
- public BigDecimal getDiskMillis() {
- return diskMillis;
- }
-
- public Plan getPlan() {
- return plan;
- }
-}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java
index 2fdf442dbe0..aaddd3811bc 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java
@@ -72,6 +72,10 @@ enum PathGroup {
PathPrefix.api,
"/billing/v1/tenant/{tenant}/instrument/{*}"),
+ billingPlan(Matcher.tenant,
+ PathPrefix.api,
+ "/billing/v1/tenant/{tenant}/plan/{*}"),
+
billingList(Matcher.tenant,
PathPrefix.api,
"/billing/v1/tenant/{tenant}/billing/{*}"),
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java
index 83adba6f59b..548ad0af484 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java
@@ -158,6 +158,11 @@ enum Policy {
.on(PathGroup.billingToken)
.in(SystemName.PublicCd)),
+ /** Ability to update tenant payment instrument */
+ planUpdate(Privilege.grant(Action.update)
+ .on(PathGroup.billingPlan)
+ .in(SystemName.PublicCd)),
+
/** Read the generated bills */
billingInformationRead(Privilege.grant(Action.read)
.on(PathGroup.billingList)
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleDefinition.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleDefinition.java
index b9d534019db..801661f454e 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleDefinition.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleDefinition.java
@@ -67,6 +67,7 @@ public enum RoleDefinition {
Policy.paymentInstrumentUpdate,
Policy.paymentInstrumentDelete,
Policy.paymentInstrumentCreate,
+ Policy.planUpdate,
Policy.billingInformationRead),
/** Headless — the application specific role identified by deployment keys for production */