diff options
author | Valerij Fredriksen <valerij92@gmail.com> | 2021-01-19 10:39:02 +0100 |
---|---|---|
committer | Valerij Fredriksen <valerijf@verizonmedia.com> | 2021-01-19 12:12:53 +0100 |
commit | 93e8a15ae857e901836aba8164e41ea37566e47e (patch) | |
tree | cdc14930e154f5376c1d8da0a4baf6bfd2181e7f /controller-server/src/main/java/com/yahoo | |
parent | 677a35028b3aac1b6b7232b470d1fdf2df772a52 (diff) |
Store createdAt for tenant in ZK
Diffstat (limited to 'controller-server/src/main/java/com/yahoo')
9 files changed, 64 insertions, 40 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java index b998ed29b71..2cb5dbd95db 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java @@ -18,6 +18,7 @@ import com.yahoo.vespa.hosted.controller.tenant.TenantInfo; import java.security.Principal; import java.security.PublicKey; +import java.time.Instant; import java.util.Optional; import static java.util.Objects.requireNonNull; @@ -31,9 +32,11 @@ import static java.util.Objects.requireNonNull; public abstract class LockedTenant { final TenantName name; + final Optional<Instant> createdAt; - private LockedTenant(TenantName name) { + private LockedTenant(TenantName name, Optional<Instant> createdAt) { this.name = requireNonNull(name); + this.createdAt = requireNonNull(createdAt); } static LockedTenant of(Tenant tenant, Lock lock) { @@ -61,8 +64,9 @@ public abstract class LockedTenant { private final Optional<PropertyId> propertyId; private final Optional<Contact> contact; - private Athenz(TenantName name, AthenzDomain domain, Property property, Optional<PropertyId> propertyId, Optional<Contact> contact) { - super(name); + private Athenz(TenantName name, AthenzDomain domain, Property property, Optional<PropertyId> propertyId, + Optional<Contact> contact, Optional<Instant> createdAt) { + super(name, createdAt); this.domain = domain; this.property = property; this.propertyId = propertyId; @@ -70,28 +74,28 @@ public abstract class LockedTenant { } private Athenz(AthenzTenant tenant) { - this(tenant.name(), tenant.domain(), tenant.property(), tenant.propertyId(), tenant.contact()); + this(tenant.name(), tenant.domain(), tenant.property(), tenant.propertyId(), tenant.contact(), tenant.createdAt()); } @Override public AthenzTenant get() { - return new AthenzTenant(name, domain, property, propertyId, contact); + return new AthenzTenant(name, domain, property, propertyId, contact, createdAt); } public Athenz with(AthenzDomain domain) { - return new Athenz(name, domain, property, propertyId, contact); + return new Athenz(name, domain, property, propertyId, contact, createdAt); } public Athenz with(Property property) { - return new Athenz(name, domain, property, propertyId, contact); + return new Athenz(name, domain, property, propertyId, contact, createdAt); } public Athenz with(PropertyId propertyId) { - return new Athenz(name, domain, property, Optional.of(propertyId), contact); + return new Athenz(name, domain, property, Optional.of(propertyId), contact, createdAt); } public Athenz with(Contact contact) { - return new Athenz(name, domain, property, propertyId, Optional.of(contact)); + return new Athenz(name, domain, property, propertyId, Optional.of(contact), createdAt); } } @@ -104,20 +108,20 @@ public abstract class LockedTenant { private final BiMap<PublicKey, Principal> developerKeys; private final TenantInfo info; - private Cloud(TenantName name, Optional<Principal> creator, BiMap<PublicKey, Principal> developerKeys, TenantInfo info) { - super(name); + private Cloud(TenantName name, Optional<Instant> createdAt, Optional<Principal> creator, BiMap<PublicKey, Principal> developerKeys, TenantInfo info) { + super(name, createdAt); this.developerKeys = ImmutableBiMap.copyOf(developerKeys); this.creator = creator; this.info = info; } private Cloud(CloudTenant tenant) { - this(tenant.name(), Optional.empty(), tenant.developerKeys(), tenant.info()); + this(tenant.name(), tenant.createdAt(), Optional.empty(), tenant.developerKeys(), tenant.info()); } @Override public CloudTenant get() { - return new CloudTenant(name, creator, developerKeys, info); + return new CloudTenant(name, createdAt, creator, developerKeys, info); } public Cloud withDeveloperKey(PublicKey key, Principal principal) { @@ -125,17 +129,17 @@ public abstract class LockedTenant { if (keys.containsKey(key)) throw new IllegalArgumentException("Key " + KeyUtils.toPem(key) + " is already owned by " + keys.get(key)); keys.put(key, principal); - return new Cloud(name, creator, keys, info); + return new Cloud(name, createdAt, creator, keys, info); } public Cloud withoutDeveloperKey(PublicKey key) { BiMap<PublicKey, Principal> keys = HashBiMap.create(developerKeys); keys.remove(key); - return new Cloud(name, creator, keys, info); + return new Cloud(name, createdAt, creator, keys, info); } public Cloud withInfo(TenantInfo newInfo) { - return new Cloud(name, creator, developerKeys, newInfo); + return new Cloud(name, createdAt, creator, developerKeys, newInfo); } } 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 5560796a97d..ffb1aae7299 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 @@ -99,7 +99,7 @@ public class TenantController { try (Lock lock = lock(tenantSpec.tenant())) { requireNonExistent(tenantSpec.tenant()); TenantId.validate(tenantSpec.tenant().value()); - curator.writeTenant(accessControl.createTenant(tenantSpec, credentials, asList())); + curator.writeTenant(accessControl.createTenant(tenantSpec, controller.clock().instant(), credentials, asList())); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java index c8cce94d479..49c97ce2b88 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java @@ -6,7 +6,6 @@ import com.google.common.cache.CacheLoader; import com.google.inject.Inject; import com.yahoo.config.provision.ApplicationName; import com.yahoo.config.provision.TenantName; -import java.util.logging.Level; import com.yahoo.vespa.athenz.api.AthenzDomain; import com.yahoo.vespa.athenz.api.AthenzIdentity; import com.yahoo.vespa.athenz.api.AthenzPrincipal; @@ -33,6 +32,7 @@ import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant; import com.yahoo.vespa.hosted.controller.tenant.Tenant; import javax.ws.rs.ForbiddenException; +import java.time.Instant; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -41,6 +41,7 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.function.Predicate; +import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -79,7 +80,7 @@ public class AthenzFacade implements AccessControl { } @Override - public Tenant createTenant(TenantSpec tenantSpec, Credentials credentials, List<Tenant> existing) { + public Tenant createTenant(TenantSpec tenantSpec, Instant createdAt, Credentials credentials, List<Tenant> existing) { AthenzTenantSpec spec = (AthenzTenantSpec) tenantSpec; AthenzCredentials athenzCredentials = (AthenzCredentials) credentials; AthenzDomain domain = spec.domain(); @@ -94,7 +95,8 @@ public class AthenzFacade implements AccessControl { AthenzTenant tenant = AthenzTenant.create(spec.tenant(), domain, spec.property(), - spec.propertyId()); + spec.propertyId(), + Optional.of(createdAt)); if (existingWithSameDomain.isPresent()) { // Throw if domain is already taken. throw new IllegalArgumentException("Could not create tenant '" + spec.tenant().value() + @@ -123,11 +125,16 @@ public class AthenzFacade implements AccessControl { .filter(tenant -> tenant.type() == Tenant.Type.athenz && newDomain.equals(((AthenzTenant) tenant).domain())) .findAny(); + Optional<Instant> createdAt = existing.stream() + .filter(tenant -> tenant.name().equals(spec.tenant())) + .findAny() + .flatMap(Tenant::createdAt); Tenant tenant = AthenzTenant.create(spec.tenant(), newDomain, spec.property(), - spec.propertyId()); + spec.propertyId(), + createdAt); if (existingWithSameDomain.isPresent()) { // Throw if domain taken by someone else, or do nothing if taken by this tenant. if ( ! existingWithSameDomain.get().equals(tenant)) // Equality by name. 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 8b7590e88a9..1cf5c0d9315 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 @@ -26,6 +26,7 @@ import com.yahoo.vespa.hosted.controller.tenant.TenantInfoBillingContact; import java.net.URI; import java.security.Principal; import java.security.PublicKey; +import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -70,6 +71,7 @@ public class TenantSerializer { Cursor tenantObject = slime.setObject(); tenantObject.setString(nameField, tenant.name().value()); tenantObject.setString(typeField, valueOf(tenant.type())); + tenant.createdAt().ifPresent(instant -> tenantObject.setLong(createdAtField, instant.toEpochMilli())); switch (tenant.type()) { case athenz: toSlime((AthenzTenant) tenant, tenantObject); break; @@ -131,15 +133,17 @@ public class TenantSerializer { 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); + Optional<Instant> createdAt = SlimeUtils.optionalLong(tenantObject.field(createdAtField)).map(Instant::ofEpochMilli); + return new AthenzTenant(name, domain, property, propertyId, contact, createdAt); } private CloudTenant cloudTenantFrom(Inspector tenantObject) { TenantName name = TenantName.from(tenantObject.field(nameField).asString()); + Optional<Instant> createdAt = SlimeUtils.optionalLong(tenantObject.field(createdAtField)).map(Instant::ofEpochMilli); Optional<Principal> creator = SlimeUtils.optionalString(tenantObject.field(creatorField)).map(SimplePrincipal::new); BiMap<PublicKey, Principal> developerKeys = developerKeysFromSlime(tenantObject.field(pemDeveloperKeysField)); TenantInfo info = tenantInfoFromSlime(tenantObject.field(tenantInfoField)); - return new CloudTenant(name, creator, developerKeys, info); + return new CloudTenant(name, createdAt, creator, developerKeys, info); } private BiMap<PublicKey, Principal> developerKeysFromSlime(Inspector array) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AccessControl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AccessControl.java index 02387213135..32bb866a5ce 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AccessControl.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AccessControl.java @@ -6,6 +6,7 @@ import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId; import com.yahoo.vespa.hosted.controller.tenant.Tenant; +import java.time.Instant; import java.util.List; /** @@ -22,11 +23,12 @@ public interface AccessControl { * Sets up access control based on the given credentials, and returns a tenant, based on the given specification. * * @param tenantSpec specification for the tenant to create + * @param createdAt instant when the tenant was created * @param credentials the credentials for the entity requesting the creation * @param existing list of existing tenants, to check for conflicts * @return the created tenant, for keeping */ - Tenant createTenant(TenantSpec tenantSpec, Credentials credentials, List<Tenant> existing); + Tenant createTenant(TenantSpec tenantSpec, Instant createdAt, Credentials credentials, List<Tenant> existing); /** * Modifies access control based on the given credentials, and returns a modified tenant, based on the given specification. 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 d37e1e05030..563c230e4f0 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 @@ -24,6 +24,7 @@ import com.yahoo.vespa.hosted.controller.tenant.CloudTenant; import com.yahoo.vespa.hosted.controller.tenant.Tenant; import javax.ws.rs.ForbiddenException; +import java.time.Instant; import java.util.List; import java.util.stream.Collectors; @@ -51,12 +52,12 @@ public class CloudAccessControl implements AccessControl { } @Override - public CloudTenant createTenant(TenantSpec tenantSpec, Credentials credentials, List<Tenant> existing) { + public CloudTenant createTenant(TenantSpec tenantSpec, Instant createdAt, Credentials credentials, List<Tenant> existing) { requireTenantCreationAllowed((Auth0Credentials) credentials); requireTenantTrialLimitNotReached(existing); CloudTenantSpec spec = (CloudTenantSpec) tenantSpec; - CloudTenant tenant = CloudTenant.create(spec.tenant(), credentials.user()); + CloudTenant tenant = CloudTenant.create(spec.tenant(), createdAt, credentials.user()); for (Role role : Roles.tenantRoles(spec.tenant())) { userManagement.createRole(role); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java index 5682d8b69fe..e965759e96a 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java @@ -7,6 +7,7 @@ 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 java.time.Instant; import java.util.Objects; import java.util.Optional; @@ -23,11 +24,11 @@ public class AthenzTenant extends Tenant { /** * This should only be used by serialization. - * Use {@link #create(TenantName, AthenzDomain, Property, Optional)}. + * Use {@link #create(TenantName, AthenzDomain, Property, Optional, Optional)}. * */ public AthenzTenant(TenantName name, AthenzDomain domain, Property property, Optional<PropertyId> propertyId, - Optional<Contact> contact) { - super(name, Objects.requireNonNull(contact, "contact must be non-null")); + Optional<Contact> contact, Optional<Instant> createdAt) { + super(name, createdAt, contact); this.domain = Objects.requireNonNull(domain, "domain must be non-null"); this.property = Objects.requireNonNull(property, "property must be non-null"); this.propertyId = Objects.requireNonNull(propertyId, "propertyId must be non-null"); @@ -60,13 +61,8 @@ public class AthenzTenant extends Tenant { /** Create a new Athenz tenant */ public static AthenzTenant create(TenantName name, AthenzDomain domain, Property property, - Optional<PropertyId> propertyId) { - return new AthenzTenant(requireName(name), domain, property, propertyId, Optional.empty()); - } - - public static AthenzTenant create(TenantName name, AthenzDomain domain, Property property, - Optional<PropertyId> propertyId, Optional<Contact> contact) { - return new AthenzTenant(requireName(name), domain, property, propertyId, contact); + Optional<PropertyId> propertyId, Optional<Instant> createdAt) { + return new AthenzTenant(requireName(name), domain, property, propertyId, Optional.empty(), createdAt); } @Override diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java index 67b285bb24f..d047fce9785 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java @@ -7,6 +7,7 @@ import com.yahoo.config.provision.TenantName; import java.security.Principal; import java.security.PublicKey; +import java.time.Instant; import java.util.Objects; import java.util.Optional; @@ -22,16 +23,17 @@ public class CloudTenant extends Tenant { private final TenantInfo info; /** Public for the serialization layer — do not use! */ - public CloudTenant(TenantName name, Optional<Principal> creator, BiMap<PublicKey, Principal> developerKeys, TenantInfo info) { - super(name, Optional.empty()); + public CloudTenant(TenantName name, Optional<Instant> createdAt, Optional<Principal> creator, BiMap<PublicKey, Principal> developerKeys, TenantInfo info) { + super(name, createdAt, Optional.empty()); this.creator = creator; this.developerKeys = developerKeys; this.info = Objects.requireNonNull(info); } /** Creates a tenant with the given name, provided it passes validation. */ - public static CloudTenant create(TenantName tenantName, Principal creator) { + public static CloudTenant create(TenantName tenantName, Instant createdAt, Principal creator) { return new CloudTenant(requireName(tenantName), + Optional.of(createdAt), Optional.ofNullable(creator), ImmutableBiMap.of(), TenantInfo.EMPTY); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java index b1dc0d8a5d5..980af37c4f0 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.controller.tenant; import com.yahoo.config.provision.TenantName; import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact; +import java.time.Instant; import java.util.Objects; import java.util.Optional; @@ -15,10 +16,12 @@ import java.util.Optional; public abstract class Tenant { private final TenantName name; + private final Optional<Instant> createdAt; private final Optional<Contact> contact; - Tenant(TenantName name, Optional<Contact> contact) { + Tenant(TenantName name, Optional<Instant> createdAt, Optional<Contact> contact) { this.name = name; + this.createdAt = createdAt; this.contact = contact; } @@ -27,6 +30,11 @@ public abstract class Tenant { return name; } + /** Instant when the tenant was created */ + public Optional<Instant> createdAt() { + return createdAt; + } + /** Contact information for this tenant */ public Optional<Contact> contact() { return contact; |