aboutsummaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorJon Marius Venstad <jvenstad@yahoo-inc.com>2019-03-11 15:23:52 +0100
committerJon Marius Venstad <jvenstad@yahoo-inc.com>2019-03-11 15:25:13 +0100
commit090d9df63b006cabd3e2e34175c9fedc7b8d0b84 (patch)
treefdbb88986af0b80266d9ba4eb2a12d67d6971554 /controller-server
parentd87bbfbf911674f2e4b739ed711028ae8e507d5b (diff)
(De)serialise cloud tenants too
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java15
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java121
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/BillingInfo.java48
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/UserTenant.java1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java28
6 files changed, 158 insertions, 60 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java
index fab5da7733e..e274fc2fe87 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java
@@ -112,6 +112,7 @@ public class TenantController {
}
}
+ /** Returns the tenant with the given name, or throws. */
public Tenant require(TenantName name) {
return get(name).orElseThrow(() -> new IllegalArgumentException("No such tenant '" + name + "'."));
}
@@ -166,7 +167,9 @@ public class TenantController {
/** Find Athenz tenant by name */
public Optional<AthenzTenant> athenzTenant(TenantName name) {
- return curator.readAthenzTenant(name);
+ return curator.readTenant(name)
+ .filter(AthenzTenant.class::isInstance)
+ .map(AthenzTenant.class::cast);
}
/** Returns Athenz tenant with name or throws if no such tenant exists */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java
index 1648040fc2b..ec34585a950 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java
@@ -21,9 +21,7 @@ import com.yahoo.vespa.hosted.controller.application.RoutingPolicy;
import com.yahoo.vespa.hosted.controller.auditlog.AuditLog;
import com.yahoo.vespa.hosted.controller.deployment.Run;
import com.yahoo.vespa.hosted.controller.deployment.Step;
-import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
-import com.yahoo.vespa.hosted.controller.tenant.UserTenant;
import com.yahoo.vespa.hosted.controller.versions.OsVersion;
import com.yahoo.vespa.hosted.controller.versions.OsVersionStatus;
import com.yahoo.vespa.hosted.controller.versions.VersionStatus;
@@ -312,19 +310,8 @@ public class CuratorDb {
curator.set(tenantPath(tenant.name()), asJson(tenantSerializer.toSlime(tenant)));
}
- public Optional<UserTenant> readUserTenant(TenantName name) {
- return readSlime(tenantPath(name)).map(tenantSerializer::userTenantFrom);
- }
-
- public Optional<AthenzTenant> readAthenzTenant(TenantName name) {
- return readSlime(tenantPath(name)).map(tenantSerializer::athenzTenantFrom);
- }
-
public Optional<Tenant> readTenant(TenantName name) {
- if (name.value().startsWith(Tenant.userPrefix)) {
- return readUserTenant(name).map(Tenant.class::cast);
- }
- return readAthenzTenant(name).map(Tenant.class::cast);
+ return readSlime(tenantPath(name)).map(tenantSerializer::tenantFrom);
}
public List<Tenant> readTenants() {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java
index 245cb0f4dae..3846ba7aaf5 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java
@@ -13,6 +13,8 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.Property;
import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact;
+import com.yahoo.vespa.hosted.controller.tenant.BillingInfo;
+import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import com.yahoo.vespa.hosted.controller.tenant.UserTenant;
@@ -29,6 +31,7 @@ import java.util.Optional;
public class TenantSerializer {
private static final String nameField = "name";
+ private static final String typeField = "type";
private static final String athenzDomainField = "athenzDomain";
private static final String propertyField = "property";
private static final String propertyIdField = "propertyId";
@@ -40,58 +43,91 @@ public class TenantSerializer {
private static final String personField = "person";
private static final String queueField = "queue";
private static final String componentField = "component";
+ private static final String billingInfoField = "billingInfo";
+ private static final String customerIdField = "customerId";
+ private static final String productCodeField = "productCode";
public Slime toSlime(Tenant tenant) {
- if (tenant instanceof AthenzTenant) return toSlime((AthenzTenant) tenant);
- return toSlime((UserTenant) tenant);
+ Slime slime = new Slime();
+ Cursor tenantObject = slime.setObject();
+ tenantObject.setString(nameField, tenant.name().value());
+ tenantObject.setString(typeField, valueOf(tenant.type()));
+
+ switch (tenant.type()) {
+ case athenz: toSlime((AthenzTenant) tenant, tenantObject); break;
+ case user: toSlime((UserTenant) tenant, tenantObject); break;
+ case cloud: toSlime((CloudTenant) tenant, tenantObject); break;
+ default: throw new IllegalArgumentException("Unexpected tenant type '" + tenant.type() + "'.");
+ }
+ return slime;
}
- private Slime toSlime(AthenzTenant tenant) {
- Slime slime = new Slime();
- Cursor root = slime.setObject();
- root.setString(nameField, tenant.name().value());
- root.setString(athenzDomainField, tenant.domain().getName());
- root.setString(propertyField, tenant.property().id());
- tenant.propertyId().ifPresent(propertyId -> root.setString(propertyIdField, propertyId.id()));
+ private void toSlime(AthenzTenant tenant, Cursor tenantObject) {
+ tenantObject.setString(athenzDomainField, tenant.domain().getName());
+ tenantObject.setString(propertyField, tenant.property().id());
+ tenant.propertyId().ifPresent(propertyId -> tenantObject.setString(propertyIdField, propertyId.id()));
tenant.contact().ifPresent(contact -> {
- Cursor contactCursor = root.setObject(contactField);
+ Cursor contactCursor = tenantObject.setObject(contactField);
writeContact(contact, contactCursor);
});
- return slime;
}
- private Slime toSlime(UserTenant tenant) {
- Slime slime = new Slime();
- Cursor root = slime.setObject();
- root.setString(nameField, tenant.name().value());
+ private void toSlime(UserTenant tenant, Cursor tenantObject) {
tenant.contact().ifPresent(contact -> {
- Cursor contactCursor = root.setObject(contactField);
+ Cursor contactCursor = tenantObject.setObject(contactField);
writeContact(contact, contactCursor);
});
- return slime;
}
- public AthenzTenant athenzTenantFrom(Slime slime) {
- Inspector root = slime.get();
- TenantName name = TenantName.from(root.field(nameField).asString());
- AthenzDomain domain = new AthenzDomain(root.field(athenzDomainField).asString());
- Property property = new Property(root.field(propertyField).asString());
- Optional<PropertyId> propertyId = SlimeUtils.optionalString(root.field(propertyIdField)).map(PropertyId::new);
- Optional<Contact> contact = contactFrom(root.field(contactField));
+ private void toSlime(CloudTenant tenant, Cursor root) {
+ toSlime(tenant.billingInfo(), root.setObject(billingInfoField));
+ }
+
+ private void toSlime(BillingInfo billingInfo, Cursor billingInfoObject) {
+ billingInfoObject.setString(customerIdField, billingInfo.customerId());
+ billingInfoObject.setString(productCodeField, billingInfo.productCode());
+ }
+
+ public Tenant tenantFrom(Slime slime) {
+ Inspector tenantObject = slime.get();
+ Tenant.Type type;
+ if (tenantObject.field(typeField).valid())
+ type = typeOf(tenantObject.field(typeField).asString());
+ else // TODO jvenstad: Remove once all tenants are stored on updated format.
+ type = tenantObject.field(nameField).asString().startsWith(Tenant.userPrefix) ? Tenant.Type.user : Tenant.Type.athenz;
+
+ switch (type) {
+ case athenz: return athenzTenantFrom(tenantObject);
+ case user: return userTenantFrom(tenantObject);
+ case cloud: return cloudTenantFrom(tenantObject);
+ default: throw new IllegalArgumentException("Unexpected tenant type '" + type + "'.");
+ }
+ }
+
+ private AthenzTenant athenzTenantFrom(Inspector tenantObject) {
+ TenantName name = TenantName.from(tenantObject.field(nameField).asString());
+ AthenzDomain domain = new AthenzDomain(tenantObject.field(athenzDomainField).asString());
+ Property property = new Property(tenantObject.field(propertyField).asString());
+ Optional<PropertyId> propertyId = SlimeUtils.optionalString(tenantObject.field(propertyIdField)).map(PropertyId::new);
+ Optional<Contact> contact = contactFrom(tenantObject.field(contactField));
return new AthenzTenant(name, domain, property, propertyId, contact);
}
- public UserTenant userTenantFrom(Slime slime) {
- Inspector root = slime.get();
- TenantName name = TenantName.from(root.field(nameField).asString());
- Optional<Contact> contact = contactFrom(root.field(contactField));
+ private UserTenant userTenantFrom(Inspector tenantObject) {
+ TenantName name = TenantName.from(tenantObject.field(nameField).asString());
+ Optional<Contact> contact = contactFrom(tenantObject.field(contactField));
return new UserTenant(name, contact);
}
+ private CloudTenant cloudTenantFrom(Inspector tenantObject) {
+ TenantName name = TenantName.from(tenantObject.field(nameField).asString());
+ BillingInfo billingInfo = billingInfoFrom(tenantObject.field(billingInfoField));
+ return new CloudTenant(name, billingInfo);
+ }
+
private Optional<Contact> contactFrom(Inspector object) {
- if (!object.valid()) {
- return Optional.empty();
- }
+ if ( ! object.valid()) return Optional.empty();
+
URI contactUrl = URI.create(object.field(contactUrlField).asString());
URI propertyUrl = URI.create(object.field(propertyUrlField).asString());
URI issueTrackerUrl = URI.create(object.field(issueTrackerUrlField).asString());
@@ -132,4 +168,27 @@ public class TenantSerializer {
return personLists;
}
+ private BillingInfo billingInfoFrom(Inspector billingInfoObject) {
+ return new BillingInfo(billingInfoObject.field(customerIdField).asString(),
+ billingInfoObject.field(productCodeField).asString());
+ }
+
+ private static Tenant.Type typeOf(String value) {
+ switch (value) {
+ case "athenz": return Tenant.Type.athenz;
+ case "user": return Tenant.Type.user;
+ case "cloud": return Tenant.Type.cloud;
+ default: throw new IllegalArgumentException("Unknown tenant type '" + value + "'.");
+ }
+ }
+
+ private static String valueOf(Tenant.Type type) {
+ switch (type) {
+ case athenz: return "athenz";
+ case user: return "user";
+ case cloud: return "cloud";
+ default: throw new IllegalArgumentException("Unexpected tenant type '" + type + "'.");
+ }
+ }
+
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/BillingInfo.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/BillingInfo.java
index d8fae8bb142..0eeb331b59f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/BillingInfo.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/BillingInfo.java
@@ -1,5 +1,10 @@
package com.yahoo.vespa.hosted.controller.tenant;
+import java.util.Objects;
+import java.util.StringJoiner;
+
+import static java.util.Objects.requireNonNull;
+
/**
* Information pertinent to billing a tenant for use of hosted Vespa services.
*
@@ -7,20 +12,43 @@ package com.yahoo.vespa.hosted.controller.tenant;
*/
public class BillingInfo {
- private final String data;
+ private final String customerId;
+ private final String productCode;
- /** Creates a new BillingInfo with the given data. */
- public BillingInfo(String data) {
- this.data = requireValid(data);
+ /** Creates a new BillingInfo with the given data. Assumes data has already been validated. */
+ public BillingInfo(String customerId, String productCode) {
+ this.customerId = requireNonNull(customerId);
+ this.productCode = requireNonNull(productCode);
}
- /** Returns the data stored in this. */
- public String data() { return data; }
+ public String customerId() {
+ return customerId;
+ }
+
+ public String productCode() {
+ return productCode;
+ }
+
+ @Override
+ public String toString() {
+ return new StringJoiner(", ", BillingInfo.class.getSimpleName() + "[", "]")
+ .add("customerId='" + customerId + "'")
+ .add("productCode='" + productCode + "'")
+ .toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if ( ! (o instanceof BillingInfo)) return false;
+ BillingInfo that = (BillingInfo) o;
+ return Objects.equals(customerId, that.customerId) &&
+ Objects.equals(productCode, that.productCode);
+ }
- static String requireValid(String data) {
- if (data.isBlank())
- throw new IllegalArgumentException("Invalid billing information '" + data + "'.");
- return data;
+ @Override
+ public int hashCode() {
+ return Objects.hash(customerId, productCode);
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/UserTenant.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/UserTenant.java
index ac8b73ff43d..a46d847f6f3 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/UserTenant.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/UserTenant.java
@@ -69,4 +69,5 @@ public class UserTenant extends Tenant {
}
return name;
}
+
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java
index 2e0d7715d7d..b78cff88ccf 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java
@@ -3,10 +3,14 @@ package com.yahoo.vespa.hosted.controller.persistence;// Copyright 2018 Yahoo Ho
import com.yahoo.config.provision.TenantName;
import com.yahoo.vespa.athenz.api.AthenzDomain;
+import com.yahoo.vespa.config.SlimeUtils;
import com.yahoo.vespa.hosted.controller.api.identifiers.Property;
import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
+import com.yahoo.vespa.hosted.controller.tenant.BillingInfo;
+import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
+import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import com.yahoo.vespa.hosted.controller.tenant.UserTenant;
import org.junit.Test;
@@ -32,7 +36,7 @@ public class TenantSerializerTest {
new AthenzDomain("domain1"),
new Property("property1"),
Optional.of(new PropertyId("1")));
- AthenzTenant serialized = serializer.athenzTenantFrom(serializer.toSlime(tenant));
+ AthenzTenant serialized = (AthenzTenant) serializer.tenantFrom(serializer.toSlime(tenant));
assertEquals(tenant.name(), serialized.name());
assertEquals(tenant.domain(), serialized.domain());
assertEquals(tenant.property(), serialized.property());
@@ -46,7 +50,7 @@ public class TenantSerializerTest {
new AthenzDomain("domain1"),
new Property("property1"),
Optional.empty());
- AthenzTenant serialized = serializer.athenzTenantFrom(serializer.toSlime(tenant));
+ AthenzTenant serialized = (AthenzTenant) serializer.tenantFrom(serializer.toSlime(tenant));
assertFalse(serialized.propertyId().isPresent());
assertEquals(tenant.propertyId(), serialized.propertyId());
}
@@ -58,18 +62,33 @@ public class TenantSerializerTest {
new Property("property1"),
Optional.of(new PropertyId("1")),
Optional.of(contact()));
- AthenzTenant serialized = serializer.athenzTenantFrom(serializer.toSlime(tenant));
+ AthenzTenant serialized = (AthenzTenant) serializer.tenantFrom(serializer.toSlime(tenant));
assertEquals(tenant.contact(), serialized.contact());
}
@Test
public void user_tenant() {
UserTenant tenant = UserTenant.create("by-foo", Optional.of(contact()));
- UserTenant serialized = serializer.userTenantFrom(serializer.toSlime(tenant));
+ UserTenant serialized = (UserTenant) serializer.tenantFrom(serializer.toSlime(tenant));
assertEquals(tenant.name(), serialized.name());
assertEquals(contact(), serialized.contact().get());
}
+ @Test
+ public void cloud_tenant() {
+ CloudTenant tenant = CloudTenant.create(TenantName.from("elderly-lady"),
+ new BillingInfo("old cat lady", "vespa"));
+ CloudTenant serialized = (CloudTenant) serializer.tenantFrom(serializer.toSlime(tenant));
+ assertEquals(tenant.name(), serialized.name());
+ assertEquals(tenant.billingInfo(), serialized.billingInfo());
+ }
+
+ @Test
+ public void legacy_deserialization() {
+ UserTenant legayUserTenant = (UserTenant) serializer.tenantFrom(SlimeUtils.jsonToSlime("{\"name\":\"by-someone\"}"));
+ assertTrue(legayUserTenant.is("someone"));
+ }
+
private Contact contact() {
return new Contact(
URI.create("http://contact1.test"),
@@ -83,4 +102,5 @@ public class TenantSerializerTest {
Optional.empty()
);
}
+
}