diff options
author | Jon Marius Venstad <jvenstad@yahoo-inc.com> | 2019-03-11 15:23:52 +0100 |
---|---|---|
committer | Jon Marius Venstad <jvenstad@yahoo-inc.com> | 2019-03-11 15:25:13 +0100 |
commit | 090d9df63b006cabd3e2e34175c9fedc7b8d0b84 (patch) | |
tree | fdbb88986af0b80266d9ba4eb2a12d67d6971554 /controller-server | |
parent | d87bbfbf911674f2e4b739ed711028ae8e507d5b (diff) |
(De)serialise cloud tenants too
Diffstat (limited to 'controller-server')
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() ); } + } |