summaryrefslogtreecommitdiffstats
path: root/controller-server/src
diff options
context:
space:
mode:
Diffstat (limited to 'controller-server/src')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java396
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java8
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java16
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerTest.java214
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/billing-all-tenants48
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/invoice-creation-response1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/line-item-list9
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/tenant-billing-view38
8 files changed, 12 insertions, 718 deletions
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
deleted file mode 100644
index ccbee15d2c5..00000000000
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java
+++ /dev/null
@@ -1,396 +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.restapi.billing;
-
-import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.TenantName;
-import com.yahoo.container.jdisc.HttpRequest;
-import com.yahoo.container.jdisc.HttpResponse;
-import com.yahoo.container.jdisc.LoggingRequestHandler;
-import com.yahoo.container.logging.AccessLog;
-import com.yahoo.restapi.ErrorResponse;
-import com.yahoo.restapi.JacksonJsonResponse;
-import com.yahoo.restapi.MessageResponse;
-import com.yahoo.restapi.Path;
-import com.yahoo.restapi.SlimeJsonResponse;
-import com.yahoo.restapi.StringResponse;
-import com.yahoo.slime.Cursor;
-import com.yahoo.slime.Inspector;
-import com.yahoo.slime.Slime;
-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.api.identifiers.DeploymentId;
-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;
-import com.yahoo.yolean.Exceptions;
-
-import javax.ws.rs.BadRequestException;
-import javax.ws.rs.ForbiddenException;
-import javax.ws.rs.NotFoundException;
-import java.io.IOException;
-import java.math.BigDecimal;
-import java.security.Principal;
-import java.time.LocalDate;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
-import java.time.format.DateTimeFormatter;
-import java.time.format.DateTimeParseException;
-import java.util.List;
-import java.util.Optional;
-import java.util.concurrent.Executor;
-import java.util.logging.Level;
-
-/**
- * @author andreer
- * @author olaa
- */
-public class BillingApiHandler extends LoggingRequestHandler {
-
- private static final String OPTIONAL_PREFIX = "/api";
- private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
-
-
- private final BillingController billingController;
- private final ApplicationController applicationController;
-
- public BillingApiHandler(Executor executor,
- AccessLog accessLog,
- Controller controller) {
- super(executor, accessLog);
- this.billingController = controller.serviceRegistry().billingController();
- this.applicationController = controller.applications();
- }
-
- @Override
- public HttpResponse handle(HttpRequest request) {
- try {
- Path path = new Path(request.getUri(), OPTIONAL_PREFIX);
- String userId = userIdOrThrow(request);
- switch (request.getMethod()) {
- case GET:
- return handleGET(request, path, userId);
- case PATCH:
- return handlePATCH(request, path, userId);
- case DELETE:
- return handleDELETE(path, userId);
- case POST:
- return handlePOST(path, request, userId);
- default:
- return ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is not supported");
- }
- } catch (NotFoundException e) {
- return ErrorResponse.notFoundError(Exceptions.toMessageString(e));
- } catch (IllegalArgumentException e) {
- return ErrorResponse.badRequest(Exceptions.toMessageString(e));
- } catch (Exception e) {
- log.log(Level.WARNING, "Unexpected error handling '" + request.getUri() + "'", e);
- // Don't expose internal billing details in error message to user
- return ErrorResponse.internalServerError("Internal problem while handling billing API request");
- }
- }
-
- private HttpResponse handleGET(HttpRequest request, Path path, String userId) {
- if (path.matches("/billing/v1/tenant/{tenant}/token")) return getToken(path.get("tenant"), userId);
- if (path.matches("/billing/v1/tenant/{tenant}/instrument")) return getInstruments(path.get("tenant"), userId);
- 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/tenant/{tenant}/line-item")) return getLineItems(path.get("tenant"));
- return ErrorResponse.notFoundError("Nothing at " + path);
- }
-
- private HttpResponse handlePATCH(HttpRequest request, Path path, String userId) {
- if (path.matches("/billing/v1/tenant/{tenant}/instrument")) return patchActiveInstrument(request, path.get("tenant"), userId);
- if (path.matches("/billing/v1/tenant/{tenant}/plan")) return patchPlan(request, path.get("tenant"));
- return ErrorResponse.notFoundError("Nothing at " + path);
-
- }
-
- private HttpResponse handleDELETE(Path path, String userId) {
- if (path.matches("/billing/v1/tenant/{tenant}/instrument/{instrument}")) return deleteInstrument(path.get("tenant"), userId, path.get("instrument"));
- if (path.matches("/billing/v1/invoice/line-item/{line-item-id}")) return deleteLineItem(path.get("line-item-id"));
- return ErrorResponse.notFoundError("Nothing at " + path);
-
- }
-
- private HttpResponse handlePOST(Path path, HttpRequest request, String userId) {
- 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);
-
- }
-
- private HttpResponse getPlan(String tenant) {
- var plan = billingController.getPlan(TenantName.from(tenant));
- var slime = new Slime();
- var root = slime.setObject();
- root.setString("tenant", tenant);
- root.setString("plan", plan.value());
- return new SlimeJsonResponse(slime);
- }
-
- private HttpResponse patchPlan(HttpRequest request, String tenant) {
- var tenantName = TenantName.from(tenant);
- var slime = inspectorOrThrow(request);
- var planId = PlanId.from(slime.field("plan").asString());
- var hasApplications = applicationController.asList(tenantName).size() > 0;
-
- if (billingController.setPlan(tenantName, planId, hasApplications)) {
- return new StringResponse("Plan: " + planId.value());
- } else {
- return ErrorResponse.forbidden("Invalid plan change with active deployments");
- }
-
- }
-
- private HttpResponse getBillingAllTenants(String until) {
- try {
- var untilDate = untilParameter(until);
- var uncommittedInvoices = billingController.createUncommittedInvoices(untilDate);
-
- var slime = new Slime();
- var root = slime.setObject();
- root.setString("until", untilDate.format(DateTimeFormatter.ISO_DATE));
- var tenants = root.setArray("tenants");
-
- uncommittedInvoices.forEach((tenant, invoice) -> {
- var tc = tenants.addObject();
- tc.setString("tenant", tenant.value());
- getPlanForTenant(tc, tenant);
- renderCurrentUsage(tc.setObject("current"), invoice);
- renderAdditionalItems(tc.setObject("additional").setArray("items"), billingController.getUnusedLineItems(tenant));
-
- billingController.getDefaultInstrument(tenant).ifPresent(card ->
- renderInstrument(tc.setObject("payment"), card)
- );
- });
-
- return new SlimeJsonResponse(slime);
- } catch (DateTimeParseException e) {
- return ErrorResponse.badRequest("Could not parse date: " + until);
- }
- }
-
- private HttpResponse addLineItem(HttpRequest request, String tenant) {
- Inspector inspector = inspectorOrThrow(request);
- billingController.addLineItem(
- TenantName.from(tenant),
- getInspectorFieldOrThrow(inspector, "description"),
- new BigDecimal(getInspectorFieldOrThrow(inspector, "amount")),
- userIdOrThrow(request));
- return new MessageResponse("Added line item for tenant " + tenant);
- }
-
- private HttpResponse setInvoiceStatus(HttpRequest request, String invoiceId) {
- Inspector inspector = inspectorOrThrow(request);
- String status = getInspectorFieldOrThrow(inspector, "status");
- billingController.updateInvoiceStatus(Invoice.Id.of(invoiceId), userIdOrThrow(request), status);
- return new MessageResponse("Updated status of invoice " + invoiceId);
- }
-
- private HttpResponse createInvoice(HttpRequest request, String userId) {
- Inspector inspector = inspectorOrThrow(request);
- TenantName tenantName = TenantName.from(getInspectorFieldOrThrow(inspector, "tenant"));
-
- LocalDate startDate = LocalDate.parse(getInspectorFieldOrThrow(inspector, "startTime"));
- LocalDate endDate = LocalDate.parse(getInspectorFieldOrThrow(inspector, "endTime"));
- ZonedDateTime startTime = startDate.atStartOfDay(ZoneId.of("UTC"));
- ZonedDateTime endTime = endDate.atStartOfDay(ZoneId.of("UTC"));
-
- var invoiceId = billingController.createInvoiceForPeriod(tenantName, startTime, endTime, userId);
-
- return new MessageResponse("Created invoice with ID " + invoiceId.value());
- }
-
- private HttpResponse getInstruments(String tenant, String userId) {
- var instrumentListResponse = billingController.listInstruments(TenantName.from(tenant), userId);
- return new JacksonJsonResponse<>(200, instrumentListResponse);
- }
-
- private HttpResponse getToken(String tenant, String userId) {
- return new StringResponse(billingController.createClientToken(tenant, userId));
- }
-
- private HttpResponse getBilling(String tenant, String until) {
- try {
- var untilDate = untilParameter(until);
- var tenantId = TenantName.from(tenant);
- var slimeResponse = new Slime();
- var root = slimeResponse.setObject();
-
- root.setString("until", untilDate.format(DateTimeFormatter.ISO_DATE));
-
- getPlanForTenant(root, tenantId);
- renderCurrentUsage(root.setObject("current"), getCurrentUsageForTenant(tenantId, untilDate));
- renderAdditionalItems(root.setObject("additional").setArray("items"), billingController.getUnusedLineItems(tenantId));
- renderInvoices(root.setArray("bills"), getInvoicesForTenant(tenantId));
-
- billingController.getDefaultInstrument(tenantId).ifPresent( card ->
- renderInstrument(root.setObject("payment"), card)
- );
-
- return new SlimeJsonResponse(slimeResponse);
- } catch (DateTimeParseException e) {
- return ErrorResponse.badRequest("Could not parse date: " + until);
- }
- }
-
- private HttpResponse getLineItems(String tenant) {
- var slimeResponse = new Slime();
- var root = slimeResponse.setObject();
- var lineItems = root.setArray("lineItems");
-
- billingController.getUnusedLineItems(TenantName.from(tenant))
- .forEach(lineItem -> {
- var itemCursor = lineItems.addObject();
- renderLineItemToCursor(itemCursor, lineItem);
- });
-
- return new SlimeJsonResponse(slimeResponse);
- }
-
- private void getPlanForTenant(Cursor cursor, TenantName tenant) {
- cursor.setString("plan", billingController.getPlan(tenant).value());
- }
-
- private void renderInstrument(Cursor cursor, PaymentInstrument instrument) {
- cursor.setString("type", instrument.getType());
- cursor.setString("brand", instrument.getBrand());
- cursor.setString("endingWith", instrument.getEndingWith());
- }
-
- private void renderCurrentUsage(Cursor cursor, Invoice currentUsage) {
- cursor.setString("amount", currentUsage.sum().toPlainString());
- cursor.setString("status", "accrued");
- cursor.setString("from", currentUsage.getStartTime().format(DATE_TIME_FORMATTER));
- var itemsCursor = cursor.setArray("items");
- currentUsage.lineItems().forEach(lineItem -> {
- var itemCursor = itemsCursor.addObject();
- renderLineItemToCursor(itemCursor, lineItem);
- });
- }
-
- private void renderAdditionalItems(Cursor cursor, List<Invoice.LineItem> items) {
- items.forEach(item -> {
- renderLineItemToCursor(cursor.addObject(), item);
- });
- }
-
- private Invoice getCurrentUsageForTenant(TenantName tenant, LocalDate until) {
- return billingController.createUncommittedInvoice(tenant, until);
- }
-
- private List<Invoice> getInvoicesForTenant(TenantName tenant) {
- return billingController.getInvoices(tenant);
- }
-
- private void renderInvoices(Cursor cursor, List<Invoice> invoices) {
- invoices.forEach(invoice -> {
- var invoiceCursor = cursor.addObject();
- renderInvoiceToCursor(invoiceCursor, invoice);
- });
- }
-
- 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));
-
- invoiceCursor.setString("amount", invoice.sum().toString());
- invoiceCursor.setString("status", invoice.status());
- var statusCursor = invoiceCursor.setArray("statusHistory");
- renderStatusHistory(statusCursor, invoice.statusHistory());
-
-
- var lineItemsCursor = invoiceCursor.setArray("items");
- invoice.lineItems().forEach(lineItem -> {
- var itemCursor = lineItemsCursor.addObject();
- renderLineItemToCursor(itemCursor, lineItem);
- });
- }
-
- private void renderStatusHistory(Cursor cursor, Invoice.StatusHistory statusHistory) {
- statusHistory.getHistory()
- .entrySet()
- .stream()
- .forEach(entry -> {
- var c = cursor.addObject();
- c.setString("at", entry.getKey().format(DATE_TIME_FORMATTER));
- c.setString("status", entry.getValue());
- });
- }
-
- private void renderLineItemToCursor(Cursor cursor, Invoice.LineItem lineItem) {
- cursor.setString("id", lineItem.id());
- cursor.setString("description", lineItem.description());
- cursor.setString("amount", lineItem.amount().toString());
- lineItem.applicationId().ifPresent(appId -> {
- cursor.setString("application", appId.application().value());
- });
- }
-
- private HttpResponse deleteInstrument(String tenant, String userId, String instrument) {
- if (billingController.deleteInstrument(TenantName.from(tenant), userId, instrument)) {
- return new StringResponse("OK");
- } else {
- return ErrorResponse.forbidden("Cannot delete payment instrument you don't own");
- }
- }
-
- private HttpResponse deleteLineItem(String lineItemId) {
- billingController.deleteLineItem(lineItemId);
- return new MessageResponse("Succesfully deleted line item " + lineItemId);
- }
-
- private HttpResponse patchActiveInstrument(HttpRequest request, String tenant, String userId) {
- var inspector = inspectorOrThrow(request);
- String instrumentId = getInspectorFieldOrThrow(inspector, "active");
- InstrumentOwner paymentInstrument = new InstrumentOwner(TenantName.from(tenant), userId, instrumentId, true);
- boolean success = billingController.setActivePaymentInstrument(paymentInstrument);
- return success ? new StringResponse("OK") : ErrorResponse.internalServerError("Failed to patch active instrument");
- }
-
- private Inspector inspectorOrThrow(HttpRequest request) {
- try {
- return SlimeUtils.jsonToSlime(request.getData().readAllBytes()).get();
- } catch (IOException e) {
- throw new BadRequestException("Failed to parse request body");
- }
- }
-
- private static String userIdOrThrow(HttpRequest request) {
- return Optional.ofNullable(request.getJDiscRequest().getUserPrincipal())
- .map(Principal::getName)
- .orElseThrow(() -> new ForbiddenException("Must be authenticated to use this API"));
- }
-
- private static String getInspectorFieldOrThrow(Inspector inspector, String field) {
- if (!inspector.field(field).valid())
- throw new BadRequestException("Field " + field + " cannot be null");
- return inspector.field(field).asString();
- }
-
- private DeploymentId getDeploymentIdOrNull(Inspector inspector) {
- if (inspector.field("applicationId").valid() != inspector.field("zoneId").valid() ) {
- throw new BadRequestException("Either both application id and zone id should be set, or neither.");
- }
- if (inspector.field("applicationId").valid()) {
- return new DeploymentId(
- ApplicationId.fromSerializedForm(inspector.field("applicationId").asString()),
- com.yahoo.config.provision.zone.ZoneId.from(inspector.field("zoneId").asString())
- );
- }
- return null;
- }
-
- private LocalDate untilParameter(String until) {
- if (until == null || until.isEmpty() || until.isBlank())
- return LocalDate.now().plusDays(1);
- return LocalDate.parse(until);
- }
-
-}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java
index 13cf992cd52..bd0143ef879 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java
@@ -9,7 +9,7 @@ import com.yahoo.vespa.flags.FlagSource;
import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.api.integration.ServiceRegistry;
-import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingController;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanController;
import com.yahoo.vespa.hosted.controller.api.integration.organization.BillingInfo;
import com.yahoo.vespa.hosted.controller.api.integration.user.Roles;
import com.yahoo.vespa.hosted.controller.api.integration.user.UserId;
@@ -36,13 +36,13 @@ public class CloudAccessControl implements AccessControl {
private final UserManagement userManagement;
private final BooleanFlag enablePublicSignup;
- private final BillingController planController;
+ private final PlanController planController;
@Inject
public CloudAccessControl(UserManagement userManagement, FlagSource flagSource, ServiceRegistry serviceRegistry) {
this.userManagement = userManagement;
this.enablePublicSignup = Flags.ENABLE_PUBLIC_SIGNUP_FLOW.bindTo(flagSource);
- planController = serviceRegistry.billingController();
+ planController = serviceRegistry.planController();
}
@Override
@@ -109,7 +109,7 @@ public class CloudAccessControl implements AccessControl {
}
private boolean isTrial(TenantName tenant) {
- return planController.getPlan(tenant).value().equals("trial");
+ return planController.getPlan(tenant).id().equals("trial");
}
@Override
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 1b21f7db7c4..b7e7c9814e3 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
@@ -12,14 +12,14 @@ import com.yahoo.vespa.hosted.controller.api.integration.aws.MockAwsEventFetcher
import com.yahoo.vespa.hosted.controller.api.integration.aws.MockResourceTagger;
import com.yahoo.vespa.hosted.controller.api.integration.aws.NoopApplicationRoleService;
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.MockBillingController;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanController;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateMock;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServer;
import com.yahoo.vespa.hosted.controller.api.integration.dns.MemoryNameService;
import com.yahoo.vespa.hosted.controller.api.integration.entity.MemoryEntityService;
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.organization.SystemMonitor;
import com.yahoo.vespa.hosted.controller.api.integration.resource.CostReportConsumerMock;
import com.yahoo.vespa.hosted.controller.api.integration.routing.GlobalRoutingService;
import com.yahoo.vespa.hosted.controller.api.integration.routing.MemoryGlobalRoutingService;
@@ -60,7 +60,7 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg
private final MockRunDataStore mockRunDataStore = new MockRunDataStore();
private final MockResourceTagger mockResourceTagger = new MockResourceTagger();
private final ApplicationRoleService applicationRoleService = new NoopApplicationRoleService();
- private final BillingController billingController = new MockBillingController();
+ private final PlanController planController = (tenantName) -> null;
public ServiceRegistryMock(SystemName system) {
this.zoneRegistryMock = new ZoneRegistryMock(system);
@@ -187,11 +187,6 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg
return systemMonitor;
}
- @Override
- public BillingController billingController() {
- return billingController;
- }
-
public ConfigServerMock configServerMock() {
return configServerMock;
}
@@ -208,4 +203,9 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg
return endpointCertificateMock;
}
+ @Override
+ public PlanController planController() {
+ return planController;
+ }
+
}
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
deleted file mode 100644
index 19cfa95c682..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerTest.java
+++ /dev/null
@@ -1,214 +0,0 @@
-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.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;
-import com.yahoo.vespa.hosted.controller.restapi.ContainerTester;
-import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerCloudTest;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.File;
-import java.math.BigDecimal;
-import java.time.LocalDate;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-
-import static com.yahoo.application.container.handler.Request.Method.*;
-import static org.junit.Assert.*;
-
-/**
- * @author olaa
- */
-public class BillingApiHandlerTest extends ControllerContainerCloudTest {
-
- private static final String responseFiles = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/";
- private static final TenantName tenant = TenantName.from("tenant1");
- private static final TenantName tenant2 = TenantName.from("tenant2");
- private static final Set<Role> tenantRole = Set.of(Role.administrator(tenant));
- private static final Set<Role> financeAdmin = Set.of(Role.hostedAccountant());
- private MockBillingController billingController;
-
- private ContainerTester tester;
-
- @Before
- public void setup() {
- tester = new ContainerTester(container, responseFiles);
- billingController = (MockBillingController) tester.serviceRegistry().billingController();
- }
-
- @Override
- protected SystemName system() {
- return SystemName.PublicCd;
- }
-
- @Override
- protected String variablePartXml() {
- return " <component id='com.yahoo.vespa.hosted.controller.security.CloudAccessControlRequests'/>\n" +
- " <component id='com.yahoo.vespa.hosted.controller.security.CloudAccessControl'/>\n" +
-
- " <handler id='com.yahoo.vespa.hosted.controller.restapi.billing.BillingApiHandler'>\n" +
- " <binding>http://*/billing/v1/*</binding>\n" +
- " </handler>\n" +
-
- " <http>\n" +
- " <server id='default' port='8080' />\n" +
- " <filtering>\n" +
- " <request-chain id='default'>\n" +
- " <filter id='com.yahoo.vespa.hosted.controller.restapi.filter.ControllerAuthorizationFilter'/>\n" +
- " <binding>http://*/*</binding>\n" +
- " </request-chain>\n" +
- " </filtering>\n" +
- " </http>\n";
- }
-
- @Test
- public void setting_and_deleting_instrument() {
- assertTrue(billingController.getDefaultInstrument(tenant).isEmpty());
-
- var instrumentRequest = request("/billing/v1/tenant/tenant1/instrument", PATCH)
- .data("{\"active\": \"id-1\"}")
- .roles(tenantRole);
-
- tester.assertResponse(instrumentRequest,"OK");
- assertEquals("id-1", billingController.getDefaultInstrument(tenant).get().getId());
-
- var deleteInstrumentRequest = request("/billing/v1/tenant/tenant1/instrument/id-1", DELETE)
- .roles(tenantRole);
-
- tester.assertResponse(deleteInstrumentRequest,"OK");
- assertTrue(billingController.getDefaultInstrument(tenant).isEmpty());
- }
-
- @Test
- public void response_list_bills() {
- var invoice = createInvoice();
-
- 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);
- tester.assertResponse(request, new File("tenant-billing-view"));
-
- }
-
- @Test
- public void test_invoice_creation() {
- var invoices = billingController.getInvoices(tenant);
- assertEquals(0, invoices.size());
-
- String requestBody = "{\"tenant\":\"tenant1\", \"startTime\":\"2020-04-20\", \"endTime\":\"2020-05-20\"}";
- var request = request("/billing/v1/invoice", POST)
- .data(requestBody)
- .roles(tenantRole);
-
- tester.assertResponse(request, accessDenied, 403);
- request.roles(financeAdmin);
- tester.assertResponse(request, new File("invoice-creation-response"));
-
- invoices = billingController.getInvoices(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
- public void adding_and_listing_line_item() {
-
- var requestBody = "{" +
- "\"description\":\"some description\"," +
- "\"amount\":\"123.45\" " +
- "}";
-
- var request = request("/billing/v1/invoice/tenant/tenant1/line-item", POST)
- .data(requestBody)
- .roles(financeAdmin);
-
- tester.assertResponse(request, "{\"message\":\"Added line item for tenant tenant1\"}");
-
- var lineItems = billingController.getUnusedLineItems(tenant);
- Assert.assertEquals(1, lineItems.size());
- Invoice.LineItem lineItem = lineItems.get(0);
- assertEquals("some description", lineItem.description());
- assertEquals(new BigDecimal("123.45"), lineItem.amount());
-
- request = request("/billing/v1/invoice/tenant/tenant1/line-item")
- .roles(financeAdmin);
-
- tester.assertResponse(request, new File("line-item-list"));
- }
-
- @Test
- public void adding_new_status() {
- billingController.addInvoice(tenant, createInvoice(), true);
-
- var requestBody = "{\"status\":\"DONE\"}";
- var request = request("/billing/v1/invoice/id-1/status", POST)
- .data(requestBody)
- .roles(financeAdmin);
- tester.assertResponse(request, "{\"message\":\"Updated status of invoice id-1\"}");
-
- var invoice = billingController.getInvoices(tenant).get(0);
- assertEquals("DONE", invoice.status());
- }
-
- @Test
- public void list_all_uninvoiced_items() {
- var invoice = createInvoice();
- billingController.setPlan(tenant, PlanId.from("some-plan"), true);
- billingController.setPlan(tenant2, PlanId.from("some-plan"), true);
- billingController.addInvoice(tenant, invoice, false);
- billingController.addLineItem(tenant, "support", new BigDecimal("42"), "Smith");
- billingController.addInvoice(tenant2, invoice, false);
-
-
- var request = request("/billing/v1/billing?until=2020-05-28").roles(financeAdmin);
-
- tester.assertResponse(request, new File("billing-all-tenants"));
- }
-
- @Test
- public void setting_plans() {
- var planRequest = request("/billing/v1/tenant/tenant1/plan", PATCH)
- .data("{\"plan\": \"new-plan\"}")
- .roles(tenantRole);
- tester.assertResponse(planRequest, "Plan: new-plan");
- assertEquals("new-plan", billingController.getPlan(tenant).value());
- }
-
- private Invoice createInvoice() {
- var start = LocalDate.of(2020, 5, 23).atStartOfDay(ZoneId.systemDefault());
- var end = start.plusDays(5);
- var statusHistory = new Invoice.StatusHistory(new TreeMap<>(Map.of(start, "OPEN")));
- return new Invoice(
- Invoice.Id.of("id-1"),
- statusHistory,
- List.of(createLineItem(start)),
- start,
- end
- );
- }
-
-
- private Invoice.LineItem createLineItem(ZonedDateTime addedAt) {
- return new Invoice.LineItem(
- "some-id",
- "description",
- new BigDecimal("123.00"),
- "plan",
- "Smith",
- addedAt
- );
- }
-
-}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/billing-all-tenants b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/billing-all-tenants
deleted file mode 100644
index c5bf0c88c2c..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/billing-all-tenants
+++ /dev/null
@@ -1,48 +0,0 @@
-{
- "until":"2020-05-28",
- "tenants":[
- {
- "tenant":"tenant2",
- "plan":"some-plan",
- "current":{
- "amount":"123.00",
- "status":"accrued",
- "from":"2020-05-23",
- "items":[
- {
- "id":"some-id",
- "description":"description",
- "amount":"123.00"
- }
- ]
- },
- "additional":{"items":[]}
- },
- {
- "tenant":"tenant1",
- "plan":"some-plan",
- "current":{
- "amount":"123.00",
- "status":"accrued",
- "from":"2020-05-23",
- "items":[
- {
- "id":"some-id",
- "description":"description",
- "amount":"123.00"
- }
- ]
- },
- "additional":
- {
- "items":[
- {
- "id":"line-item-id",
- "description":"support",
- "amount":"42.00"
- }
- ]
- }
- }
- ]
-} \ No newline at end of file
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/invoice-creation-response b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/invoice-creation-response
deleted file mode 100644
index 0a92229025b..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/invoice-creation-response
+++ /dev/null
@@ -1 +0,0 @@
-{"message":"Created invoice with ID id-123"} \ No newline at end of file
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/line-item-list b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/line-item-list
deleted file mode 100644
index cd5aec2f8f4..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/line-item-list
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "lineItems":[
- {
- "id":"line-item-id",
- "description":"some description",
- "amount":"123.45"
- }
- ]
-} \ No newline at end of file
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/tenant-billing-view b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/tenant-billing-view
deleted file mode 100644
index 8bc39771b31..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/tenant-billing-view
+++ /dev/null
@@ -1,38 +0,0 @@
-{
- "until":"2020-05-28",
- "plan":"some-plan",
- "current":{
- "amount":"123.00",
- "status":"accrued",
- "from":"2020-05-23",
- "items":[
- {
- "id":"some-id",
- "description":"description",
- "amount":"123.00"
- }
- ]
- },
- "additional":{"items":[]},
- "bills":[
- {
- "id":"id-1",
- "from":"2020-05-23",
- "to":"2020-05-28","amount":"123.00",
- "status":"OPEN",
- "statusHistory":[
- {
- "at":"2020-05-23",
- "status":"OPEN"
- }
- ],
- "items":[
- {
- "id":"some-id",
- "description":"description",
- "amount":"123.00"
- }
- ]
- }
- ]
-} \ No newline at end of file