aboutsummaryrefslogtreecommitdiffstats
path: root/controller-server/src/main/java/com/yahoo
diff options
context:
space:
mode:
authorValerij Fredriksen <valerij92@gmail.com>2021-01-19 10:39:02 +0100
committerValerij Fredriksen <valerijf@verizonmedia.com>2021-01-19 12:12:53 +0100
commit93e8a15ae857e901836aba8164e41ea37566e47e (patch)
treecdc14930e154f5376c1d8da0a4baf6bfd2181e7f /controller-server/src/main/java/com/yahoo
parent677a35028b3aac1b6b7232b470d1fdf2df772a52 (diff)
Store createdAt for tenant in ZK
Diffstat (limited to 'controller-server/src/main/java/com/yahoo')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java36
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java15
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AccessControl.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java16
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java10
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;