summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorØyvind Grønnesby <oyving@verizonmedia.com>2019-10-08 13:39:15 +0200
committerGitHub <noreply@github.com>2019-10-08 13:39:15 +0200
commitc8ea72faa033f846292aab8d166fe96626a8198a (patch)
tree1d6653391c68238e86b43076e8ef49c0eae2c062
parent34714094915d9f333df302c24b4cf2f69988106e (diff)
parent70e29a17d51ba7b4da481e273993d863bc3af9c0 (diff)
Merge pull request #10906 from vespa-engine/ogronnesby/tenant-cost-api
Initial tenant cost API that is not wired up to any clients yet
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java43
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java18
3 files changed, 64 insertions, 1 deletions
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 958ded06c78..b0ec1a38824 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
@@ -44,7 +44,9 @@ enum PathGroup {
/** Paths used by tenant administrators. */
tenantInfo(Matcher.tenant,
Optional.of("/api"),
- "/application/v4/tenant/{tenant}/application/"),
+ "/application/v4/tenant/{tenant}/application/",
+ "/application/v4/tenant/{tenant}/cost",
+ "/application/v4/tenant/{tenant}/cost/{date}"),
tenantKeys(Matcher.tenant,
Optional.of("/api"),
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
index c37309b87ad..c36ffd384a8 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
@@ -100,6 +100,7 @@ import java.security.PublicKey;
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.Instant;
+import java.time.LocalDate;
import java.util.Arrays;
import java.util.Base64;
import java.util.Comparator;
@@ -110,6 +111,7 @@ import java.util.Scanner;
import java.util.Set;
import java.util.StringJoiner;
import java.util.logging.Level;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static com.yahoo.jdisc.Response.Status.BAD_REQUEST;
@@ -198,6 +200,8 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
if (path.matches("/application/v4/user")) return authenticatedUser(request);
if (path.matches("/application/v4/tenant")) return tenants(request);
if (path.matches("/application/v4/tenant/{tenant}")) return tenant(path.get("tenant"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/cost")) return tenantCost(path.get("tenant"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/cost/{month}")) return tenantCost(path.get("tenant"), path.get("month"), request);
if (path.matches("/application/v4/tenant/{tenant}/application")) return applications(path.get("tenant"), Optional.empty(), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}")) return application(path.get("tenant"), path.get("application"), "default", request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying")) return deploying(path.get("tenant"), path.get("application"), "default", request);
@@ -354,6 +358,45 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
return new SlimeJsonResponse(slime);
}
+ private HttpResponse tenantCost(String tenantName, HttpRequest request) {
+ return controller.tenants().get(TenantName.from(tenantName))
+ .map(tenant -> tenantCost(tenant, request))
+ .orElseGet(() -> ErrorResponse.notFoundError("Tenant '" + tenantName + "' does not exist"));
+ }
+
+ private HttpResponse tenantCost(Tenant tenant, HttpRequest request) {
+ var slime = new Slime();
+ var objectCursor = slime.setObject();
+ var monthsCursor = objectCursor.setArray("months");
+
+ return new SlimeJsonResponse(slime);
+ }
+
+ private HttpResponse tenantCost(String tenantName, String dateString, HttpRequest request) {
+ return controller.tenants().get(TenantName.from(tenantName))
+ .map(tenant -> tenantCost(tenant, tenantCostParseDate(dateString), request))
+ .orElseGet(() -> ErrorResponse.notFoundError("Tenant '" + tenantName + "' does not exist"));
+ }
+
+ private LocalDate tenantCostParseDate(String dateString) {
+ var monthPattern = Pattern.compile("^(?<year>[0-9]{4})-(?<month>[0-9]{2})$");
+ var matcher = monthPattern.matcher(dateString);
+
+ if (matcher.matches()) {
+ var year = Integer.parseInt(matcher.group("year"));
+ var month = Integer.parseInt(matcher.group("month"));
+ return LocalDate.of(year, month, 1);
+ } else {
+ throw new IllegalArgumentException("Could not parse year-month '" + dateString + "'");
+ }
+ }
+
+ private HttpResponse tenantCost(Tenant tenant, LocalDate month, HttpRequest request) {
+ var slime = new Slime();
+ slime.setObject();
+ return new SlimeJsonResponse(slime);
+ }
+
private HttpResponse applications(String tenantName, Optional<String> applicationName, HttpRequest request) {
TenantName tenant = TenantName.from(tenantName);
Slime slime = new Slime();
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
index 9c957785606..a13bdb3e547 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
@@ -190,6 +190,13 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.assertResponse(request("/application/v4/tenant/", GET).userIdentity(USER_ID),
new File("tenant-list.json"));
+ // GET list of months for a tenant
+ tester.assertResponse(request("/application/v4/tenant/tenant1/cost", GET).userIdentity(USER_ID).oktaAccessToken(OKTA_AT),
+ "{\"months\":[]}");
+
+ // GET cost for a month for a tenant
+ tester.assertResponse(request("/application/v4/tenant/tenant1/cost/2018-01", GET).userIdentity(USER_ID).oktaAccessToken(OKTA_AT),
+ "{}");
// Add another Athens domain, so we can try to create more tenants
createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN_2, USER_ID); // New domain to test tenant w/property ID
@@ -1120,6 +1127,17 @@ public class ApplicationApiTest extends ControllerContainerTest {
"{\"error-code\":\"NOT_FOUND\",\"message\":\"Could not delete instance 'tenant1.application1.instance1': Instance not found\"}",
404);
+ // GET cost of unknown tenant
+ tester.assertResponse(request("/application/v4/tenant/no-such-tenant/cost", GET).userIdentity(USER_ID).oktaAccessToken(OKTA_AT),
+ "{\"error-code\":\"NOT_FOUND\",\"message\":\"Tenant 'no-such-tenant' does not exist\"}", 404);
+
+ tester.assertResponse(request("/application/v4/tenant/no-such-tenant/cost/2018-01-01", GET).userIdentity(USER_ID).oktaAccessToken(OKTA_AT),
+ "{\"error-code\":\"NOT_FOUND\",\"message\":\"Tenant 'no-such-tenant' does not exist\"}", 404);
+
+ // GET cost with invalid date string
+ tester.assertResponse(request("/application/v4/tenant/tenant1/cost/not-a-valid-date", GET).userIdentity(USER_ID).oktaAccessToken(OKTA_AT),
+ "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Could not parse year-month 'not-a-valid-date'\"}", 400);
+
// DELETE tenant
tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE)
.userIdentity(USER_ID)