summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJon Marius Venstad <jonmv@users.noreply.github.com>2019-03-12 11:04:25 +0100
committerGitHub <noreply@github.com>2019-03-12 11:04:25 +0100
commita4cab61c260b1ef166d592ede3b51dcbfbb13445 (patch)
tree60dcf5d4227732de6c44414c99a7052df0b8ded0
parentdb9e6becced1b8326bd3203d22bef823ed12913d (diff)
parentc0a254da00503daffff8f28b57b0b878cbf735e0 (diff)
Merge pull request #8741 from vespa-engine/jvenstad/vaas-tenant-and-application-data
Jvenstad/vaas tenant and application data
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java166
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java44
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java21
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java25
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java24
-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.java122
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java9
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/BillingInfo.java54
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java37
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java20
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/UserTenant.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java28
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java5
20 files changed, 423 insertions, 177 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
index f51bccff5d1..d53d3137991 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
@@ -227,7 +227,7 @@ public class ApplicationController {
if (asList(id.tenant()).stream().noneMatch(application -> application.id().application().equals(id.application())))
com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId.validate(id.application().value());
- Optional<Tenant> tenant = controller.tenants().tenant(id.tenant());
+ Optional<Tenant> tenant = controller.tenants().get(id.tenant());
if ( ! tenant.isPresent())
throw new IllegalArgumentException("Could not create '" + id + "': This tenant does not exist");
if (get(id).isPresent())
@@ -555,7 +555,7 @@ public class ApplicationController {
if ( ! application.get().deployments().isEmpty())
throw new IllegalArgumentException("Could not delete '" + application + "': It has active deployments");
- Tenant tenant = controller.tenants().tenant(id.tenant()).get();
+ Tenant tenant = controller.tenants().get(id.tenant()).get();
if (tenant instanceof AthenzTenant && ! token.isPresent())
throw new IllegalArgumentException("Could not delete '" + application + "': No Okta Access Token provided");
@@ -728,7 +728,7 @@ public class ApplicationController {
public void verifyApplicationIdentityConfiguration(TenantName tenantName, ApplicationPackage applicationPackage, Optional<AthenzIdentity> deployingIdentity) {
applicationPackage.deploymentSpec().athenzDomain()
.ifPresent(identityDomain -> {
- Optional<Tenant> tenant = controller.tenants().tenant(tenantName);
+ Optional<Tenant> tenant = controller.tenants().get(tenantName);
if(!tenant.isPresent()) {
throw new IllegalArgumentException("Tenant does not exist");
} else {
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 590b18f929d..e69e28b6432 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
@@ -6,88 +6,142 @@ import com.yahoo.vespa.athenz.api.AthenzDomain;
import com.yahoo.vespa.curator.Lock;
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.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 java.util.Objects;
import java.util.Optional;
-import java.util.function.Consumer;
+
+import static java.util.Objects.requireNonNull;
/**
* A tenant that has been locked for modification. Provides methods for modifying a tenant's fields.
*
* @author mpolden
+ * @author jonmv
*/
-public class LockedTenant {
-
- private final Lock lock;
- private final TenantName name;
- private AthenzDomain domain;
- private Property property;
- private Optional<PropertyId> propertyId;
- private final Optional<Contact> contact;
- private final boolean isAthenzTenant;
-
- /**
- * Should never be constructed directly.
- *
- * Use {@link TenantController#lockIfPresent(TenantName, Consumer)} or
- * {@link TenantController#lockOrThrow(TenantName, Consumer)}
- */
- LockedTenant(AthenzTenant tenant, Lock lock) {
- this(lock, tenant.name(), tenant.domain(), tenant.property(), tenant.propertyId(), tenant.contact());
- }
+public abstract class LockedTenant {
- LockedTenant(UserTenant tenant, Lock lock) {
- this(lock, tenant.name(), tenant.contact());
- }
+ final TenantName name;
- private LockedTenant(Lock lock, TenantName name, AthenzDomain domain, Property property,
- Optional<PropertyId> propertyId, Optional<Contact> contact) {
- this.lock = Objects.requireNonNull(lock, "lock must be non-null");
- this.name = Objects.requireNonNull(name, "name must be non-null");
- 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");
- this.contact = Objects.requireNonNull(contact, "contact must be non-null");
- this.isAthenzTenant = true;
+ private LockedTenant(TenantName name) {
+ this.name = requireNonNull(name);
}
- private LockedTenant(Lock lock, TenantName name, Optional<Contact> contact) {
- this.lock = Objects.requireNonNull(lock, "lock must be non-null");
- this.name = Objects.requireNonNull(name, "name must be non-null");
- this.contact = Objects.requireNonNull(contact, "contact must be non-null");
- this.isAthenzTenant = false;
+ static LockedTenant of(Tenant tenant, Lock lock) {
+ switch (tenant.type()) {
+ case athenz: return new Athenz((AthenzTenant) tenant);
+ case user: return new User((UserTenant) tenant);
+ case cloud: return new Cloud((CloudTenant) tenant);
+ default: throw new IllegalArgumentException("Unexpected tenant type '" + tenant.getClass().getName() + "'.");
+ }
}
/** Returns a read-only copy of this */
- public Tenant get() {
- if (isAthenzTenant) return new AthenzTenant(name, domain, property, propertyId, contact);
- else return new UserTenant(name, contact);
- }
+ public abstract Tenant get();
- public LockedTenant with(AthenzDomain domain) {
- return new LockedTenant(lock, name, domain, property, propertyId, contact);
+ @Override
+ public String toString() {
+ return "tenant '" + name + "'";
}
- public LockedTenant with(Property property) {
- return new LockedTenant(lock, name, domain, property, propertyId, contact);
- }
- public LockedTenant with(PropertyId propertyId) {
- return new LockedTenant(lock, name, domain, property, Optional.of(propertyId), contact);
+ /** A locked AthenzTenant. */
+ public static class Athenz extends LockedTenant {
+
+ private final AthenzDomain domain;
+ private final Property property;
+ 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);
+ this.domain = domain;
+ this.property = property;
+ this.propertyId = propertyId;
+ this.contact = contact;
+ }
+
+ private Athenz(AthenzTenant tenant) {
+ this(tenant.name(), tenant.domain(), tenant.property(), tenant.propertyId(), tenant.contact());
+ }
+
+ @Override
+ public AthenzTenant get() {
+ return new AthenzTenant(name, domain, property, propertyId, contact);
+ }
+
+ public Athenz with(AthenzDomain domain) {
+ return new Athenz(name, domain, property, propertyId, contact);
+ }
+
+ public Athenz with(Property property) {
+ return new Athenz(name, domain, property, propertyId, contact);
+ }
+
+ public Athenz with(PropertyId propertyId) {
+ return new Athenz(name, domain, property, Optional.of(propertyId), contact);
+ }
+
+ public Athenz with(Contact contact) {
+ return new Athenz(name, domain, property, propertyId, Optional.of(contact));
+ }
+
}
- public LockedTenant with(Contact contact) {
- if (isAthenzTenant) return new LockedTenant(lock, name, domain, property, propertyId, Optional.of(contact));
- return new LockedTenant(lock, name, Optional.of(contact));
+
+ /** A locked UserTenant. */
+ public static class User extends LockedTenant {
+
+ private final Optional<Contact> contact;
+
+ private User(TenantName name, Optional<Contact> contact) {
+ super(name);
+ this.contact = contact;
+ }
+
+ private User(UserTenant tenant) {
+ this(tenant.name(), tenant.contact());
+ }
+
+ @Override
+ public UserTenant get() {
+ return new UserTenant(name, contact);
+ }
+
+ public User with(Contact contact) {
+ return new User(name, Optional.of(contact));
+ }
+
}
- @Override
- public String toString() {
- return "tenant '" + name + "'";
+
+ /** A locked CloudTenant. */
+ public static class Cloud extends LockedTenant {
+
+ private final BillingInfo billingInfo;
+
+ private Cloud(TenantName name, BillingInfo billingInfo) {
+ super(name);
+ this.billingInfo = billingInfo;
+ }
+
+ private Cloud(CloudTenant tenant) {
+ this(tenant.name(), tenant.billingInfo());
+ }
+
+ @Override
+ public CloudTenant get() {
+ return new CloudTenant(name, billingInfo);
+ }
+
+ public Cloud with(BillingInfo billingInfo) {
+ return new Cloud(name, billingInfo);
+ }
+
}
}
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 78099fac34e..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
@@ -60,7 +60,7 @@ public class TenantController {
for (TenantName name : curator.readTenantNames()) {
try (Lock lock = lock(name)) {
// Get while holding lock so that we know we're operating on a current version
- Optional<Tenant> optionalTenant = tenant(name);
+ Optional<Tenant> optionalTenant = get(name);
if (!optionalTenant.isPresent()) continue; // Deleted while updating, skip
Tenant tenant = optionalTenant.get();
@@ -96,27 +96,27 @@ public class TenantController {
.collect(Collectors.toList());
}
- /**
- * Lock a tenant for modification and apply action. Only valid for Athenz tenants as it's the only type that
- * accepts modification.
- */
- public void lockIfPresent(TenantName name, Consumer<LockedTenant> action) {
+ /** Locks a tenant for modification and applies the given action. */
+ public <T extends LockedTenant> void lockIfPresent(TenantName name, Class<T> token, Consumer<T> action) {
try (Lock lock = lock(name)) {
- tenant(name).map(tenant -> {
- tenant = tenant instanceof AthenzTenant ? (AthenzTenant) tenant : (UserTenant) tenant;
- if (tenant instanceof AthenzTenant) return new LockedTenant((AthenzTenant) tenant, lock);
- else return new LockedTenant((UserTenant) tenant, lock);
- }).ifPresent(action);
+ get(name).map(tenant -> LockedTenant.of(tenant, lock))
+ .map(token::cast)
+ .ifPresent(action);
}
}
/** Lock a tenant for modification and apply action. Throws if the tenant does not exist */
- public void lockOrThrow(TenantName name, Consumer<LockedTenant> action) {
+ public <T extends LockedTenant> void lockOrThrow(TenantName name, Class<T> token, Consumer<T> action) {
try (Lock lock = lock(name)) {
- action.accept(new LockedTenant(requireAthenzTenant(name), lock));
+ action.accept(token.cast(LockedTenant.of(require(name), lock)));
}
}
+ /** Returns the tenant with the given name, or throws. */
+ public Tenant require(TenantName name) {
+ return get(name).orElseThrow(() -> new IllegalArgumentException("No such tenant '" + name + "'."));
+ }
+
/** Replace and store any previous version of given tenant */
public void store(LockedTenant tenant) {
curator.writeTenant(tenant.get());
@@ -156,18 +156,20 @@ public class TenantController {
}
/** Find tenant by name */
- public Optional<Tenant> tenant(TenantName name) {
+ public Optional<Tenant> get(TenantName name) {
return curator.readTenant(name);
}
/** Find tenant by name */
- public Optional<Tenant> tenant(String name) {
- return tenant(TenantName.from(name));
+ public Optional<Tenant> get(String name) {
+ return get(TenantName.from(name));
}
/** 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 */
@@ -176,8 +178,8 @@ public class TenantController {
}
/** Update Athenz domain for tenant. Returns the updated tenant which must be explicitly stored */
- public LockedTenant withDomain(LockedTenant tenant, AthenzDomain newDomain, OktaAccessToken token) {
- AthenzTenant athenzTenant = (AthenzTenant) tenant.get();
+ public LockedTenant.Athenz withDomain(LockedTenant.Athenz tenant, AthenzDomain newDomain, OktaAccessToken token) {
+ AthenzTenant athenzTenant = tenant.get();
AthenzDomain existingDomain = athenzTenant.domain();
if (existingDomain.equals(newDomain)) return tenant;
Optional<Tenant> existingTenantWithNewDomain = tenantIn(newDomain);
@@ -219,10 +221,10 @@ public class TenantController {
}
private void requireNonExistent(TenantName name) {
- if (tenant(name).isPresent() ||
+ if (get(name).isPresent() ||
// Underscores are allowed in existing Athenz tenant names, but tenants with - and _ cannot co-exist. E.g.
// my-tenant cannot be created if my_tenant exists.
- tenant(dashToUnderscore(name.value())).isPresent()) {
+ get(dashToUnderscore(name.value())).isPresent()) {
throw new IllegalArgumentException("Tenant '" + name + "' already exists");
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java
index d5c84c38e91..a3b1aab8f40 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java
@@ -52,11 +52,13 @@ public class ApplicationOwnershipConfirmer extends Maintainer {
.forEach(application -> {
try {
Tenant tenant = tenantOf(application.id());
- Optional<IssueId> ourIssueId = application.ownershipIssueId();
- Contact contact = tenant.contact().orElseThrow(RuntimeException::new);
- User assignee = determineAssignee(tenant, application);
- ourIssueId = ownershipIssues.confirmOwnership(ourIssueId, application.id(), assignee, contact);
- ourIssueId.ifPresent(issueId -> store(issueId, application.id()));
+ tenant.contact().ifPresent(contact -> { // TODO jvenstad: Makes sense to require, and run this only in main?
+ ownershipIssues.confirmOwnership(application.ownershipIssueId(),
+ application.id(),
+ determineAssignee(tenant, application),
+ contact)
+ .ifPresent(newIssueId -> store(newIssueId, application.id()));
+ });
}
catch (RuntimeException e) { // Catch errors due to wrong data in the controller, or issues client timeout.
log.log(Level.INFO, "Exception caught when attempting to file an issue for '" + application.id() + "': " + Exceptions.toMessageString(e));
@@ -70,11 +72,8 @@ public class ApplicationOwnershipConfirmer extends Maintainer {
for (Application application : controller().applications().asList())
application.ownershipIssueId().ifPresent(issueId -> {
try {
- Optional<Contact> contact = Optional.of(application.id())
- .map(this::tenantOf)
- .filter(t -> t instanceof AthenzTenant)
- .flatMap(Tenant::contact);
- ownershipIssues.ensureResponse(issueId, contact);
+ Tenant tenant = tenantOf(application.id());
+ ownershipIssues.ensureResponse(issueId, tenant.type() == Tenant.Type.athenz ? tenant.contact() : Optional.empty());
}
catch (RuntimeException e) {
log.log(Level.INFO, "Exception caught when attempting to escalate issue with id '" + issueId + "': " + Exceptions.toMessageString(e));
@@ -105,7 +104,7 @@ public class ApplicationOwnershipConfirmer extends Maintainer {
}
private Tenant tenantOf(ApplicationId applicationId) {
- return controller().tenants().tenant(applicationId.tenant())
+ return controller().tenants().get(applicationId.tenant())
.orElseThrow(() -> new IllegalStateException("No tenant found for application " + applicationId));
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java
index a02f28e371d..7dfbc135df9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java
@@ -1,13 +1,12 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.maintenance;
-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.api.integration.organization.ContactRetriever;
import com.yahoo.config.provision.SystemName;
import com.yahoo.log.LogLevel;
import com.yahoo.vespa.hosted.controller.Controller;
-import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
+import com.yahoo.vespa.hosted.controller.LockedTenant;
+import com.yahoo.vespa.hosted.controller.TenantController;
+import com.yahoo.vespa.hosted.controller.api.integration.organization.ContactRetriever;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import com.yahoo.yolean.Exceptions;
@@ -35,14 +34,19 @@ public class ContactInformationMaintainer extends Maintainer {
@Override
protected void maintain() {
- for (Tenant tenant : controller().tenants().asList()) {
+ TenantController tenants = controller().tenants();
+ for (Tenant tenant : tenants.asList()) {
try {
- Optional<PropertyId> tenantPropertyId = Optional.empty();
- if (tenant instanceof AthenzTenant) {
- tenantPropertyId = ((AthenzTenant) tenant).propertyId();
+ switch (tenant.type()) {
+ case athenz: tenants.lockIfPresent(tenant.name(), LockedTenant.Athenz.class, lockedTenant ->
+ tenants.store(lockedTenant.with(contactRetriever.getContact(lockedTenant.get().propertyId()))));
+ return;
+ case user: tenants.lockIfPresent(tenant.name(), LockedTenant.User.class, lockedTenant ->
+ tenants.store(lockedTenant.with(contactRetriever.getContact(Optional.empty()))));
+ return;
+ case cloud: return;
+ default: throw new IllegalArgumentException("Unexpected tenant type '" + tenant.type() + "'.");
}
- Contact contact = contactRetriever.getContact(tenantPropertyId);
- controller().tenants().lockIfPresent(tenant.name(), lockedTenant -> controller().tenants().store(lockedTenant.with(contact)));
} catch (Exception e) {
log.log(LogLevel.WARNING, "Failed to update contact information for " + tenant + ": " +
Exceptions.toMessageString(e) + ". Retrying in " +
@@ -51,5 +55,4 @@ public class ContactInformationMaintainer extends Maintainer {
}
}
-
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java
index 3f5c2d1f317..48e2702b9e0 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java
@@ -6,8 +6,6 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.SystemName;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
-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.api.integration.organization.DeploymentIssues;
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.User;
@@ -106,7 +104,7 @@ public class DeploymentIssueReporter extends Maintainer {
}
private Tenant ownerOf(ApplicationId applicationId) {
- return controller().tenants().tenant(applicationId.tenant())
+ return controller().tenants().get(applicationId.tenant())
.orElseThrow(() -> new IllegalStateException("No tenant found for application " + applicationId));
}
@@ -118,10 +116,12 @@ public class DeploymentIssueReporter extends Maintainer {
private void fileDeploymentIssueFor(ApplicationId applicationId) {
try {
Tenant tenant = ownerOf(applicationId);
- User asignee = tenant instanceof UserTenant ? userFor(tenant) : null;
- Optional<IssueId> ourIssueId = controller().applications().require(applicationId).deploymentJobs().issueId();
- IssueId issueId = deploymentIssues.fileUnlessOpen(ourIssueId, applicationId, asignee, tenant.contact().get());
- store(applicationId, issueId);
+ tenant.contact().ifPresent(contact -> {
+ User assignee = tenant.type() == Tenant.Type.user ? userFor(tenant) : null;
+ Optional<IssueId> ourIssueId = controller().applications().require(applicationId).deploymentJobs().issueId();
+ IssueId issueId = deploymentIssues.fileUnlessOpen(ourIssueId, applicationId, assignee, contact);
+ store(applicationId, issueId);
+ });
}
catch (RuntimeException e) { // Catch errors due to wrong data in the controller, or issues client timeout.
log.log(Level.INFO, "Exception caught when attempting to file an issue for '" + applicationId + "': " + Exceptions.toMessageString(e));
@@ -132,11 +132,10 @@ public class DeploymentIssueReporter extends Maintainer {
private void escalateInactiveDeploymentIssues(Collection<Application> applications) {
applications.forEach(application -> application.deploymentJobs().issueId().ifPresent(issueId -> {
try {
- AthenzTenant tenant = Optional.of(application.id())
- .map(this::ownerOf)
- .filter(t -> t instanceof AthenzTenant)
- .map(AthenzTenant.class::cast).orElseThrow(RuntimeException::new);
- deploymentIssues.escalateIfInactive(issueId, maxInactivity, tenant.contact());
+ Tenant tenant = ownerOf(application.id());
+ deploymentIssues.escalateIfInactive(issueId,
+ maxInactivity,
+ tenant.type() == Tenant.Type.athenz ? tenant.contact() : Optional.empty());
}
catch (RuntimeException e) {
log.log(Level.INFO, "Exception caught when attempting to escalate issue with id '" + issueId + "': " + Exceptions.toMessageString(e));
@@ -148,4 +147,5 @@ public class DeploymentIssueReporter extends Maintainer {
controller().applications().lockIfPresent(id, application ->
controller().applications().store(application.withDeploymentIssueId(issueId)));
}
+
}
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..91a01435b68 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
@@ -1,7 +1,6 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
-
import com.yahoo.config.provision.TenantName;
import com.yahoo.slime.ArrayTraverser;
import com.yahoo.slime.Cursor;
@@ -13,6 +12,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 +30,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 +42,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 +167,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/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
index 6fc5cc645e8..37c0847f167 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
@@ -30,6 +30,7 @@ import com.yahoo.vespa.config.SlimeUtils;
import com.yahoo.vespa.hosted.controller.AlreadyExistsException;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
+import com.yahoo.vespa.hosted.controller.LockedTenant;
import com.yahoo.vespa.hosted.controller.NotExistsException;
import com.yahoo.vespa.hosted.controller.api.ActivateResult;
import com.yahoo.vespa.hosted.controller.api.application.v4.ApplicationResource;
@@ -333,7 +334,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
private HttpResponse tenant(String tenantName, HttpRequest request) {
- return controller.tenants().tenant(TenantName.from(tenantName))
+ return controller.tenants().get(TenantName.from(tenantName))
.map(tenant -> tenant(tenant, request, true))
.orElseGet(() -> ErrorResponse.notFoundError("Tenant '" + tenantName + "' does not exist"));
}
@@ -756,7 +757,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
Inspector requestData = toSlime(request.getData()).get();
OktaAccessToken token = requireOktaAccessToken(request, "Could not update " + tenantName);
- controller.tenants().lockOrThrow(tenant.get().name(), lockedTenant -> {
+ controller.tenants().lockOrThrow(tenant.get().name(), LockedTenant.Athenz.class, lockedTenant -> {
lockedTenant = lockedTenant.with(new Property(mandatory("property", requestData).asString()));
lockedTenant = controller.tenants().withDomain(
lockedTenant,
@@ -983,7 +984,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
private HttpResponse deleteTenant(String tenantName, HttpRequest request) {
- Optional<Tenant> tenant = controller.tenants().tenant(tenantName);
+ Optional<Tenant> tenant = controller.tenants().get(tenantName);
if ( ! tenant.isPresent()) return ErrorResponse.notFoundError("Could not delete tenant '" + tenantName + "': Tenant not found"); // NOTE: The Jersey implementation would silently ignore this
@@ -1099,7 +1100,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
private Tenant getTenantOrThrow(String tenantName) {
- return controller.tenants().tenant(tenantName)
+ return controller.tenants().get(tenantName)
.orElseThrow(() -> new NotExistsException(new TenantId(tenantName)));
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java
index 77e626509b3..3d0e21617c5 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java
@@ -158,7 +158,7 @@ public class ControllerAuthorizationFilter extends CorsRequestFilterBase {
}
private void verifyIsTenantAdmin(AthenzPrincipal principal, TenantName name) {
- tenantController.tenant(name)
+ tenantController.get(name)
.ifPresent(tenant -> {
if (!isTenantAdmin(principal.getIdentity(), tenant)) {
throw new ForbiddenException("Tenant admin or Vespa operator role required");
@@ -182,7 +182,7 @@ public class ControllerAuthorizationFilter extends CorsRequestFilterBase {
private void verifyIsTenantPipelineOperator(AthenzPrincipal principal,
TenantName name,
ApplicationName application) {
- tenantController.tenant(name)
+ tenantController.get(name)
.ifPresent(tenant -> verifyIsTenantPipelineOperator(principal.getIdentity(), tenant, application));
}
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 abe09090761..f8edeee5939 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
@@ -75,4 +75,10 @@ public class AthenzTenant extends Tenant {
}
return name;
}
+
+ @Override
+ public Type type() {
+ return Type.athenz;
+ }
+
}
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
new file mode 100644
index 00000000000..0eeb331b59f
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/BillingInfo.java
@@ -0,0 +1,54 @@
+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.
+ *
+ * @author jonmv
+ */
+public class BillingInfo {
+
+ private final String customerId;
+ private final String productCode;
+
+ /** 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);
+ }
+
+ 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);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(customerId, productCode);
+ }
+
+}
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
new file mode 100644
index 00000000000..cf68c8f3bf9
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java
@@ -0,0 +1,37 @@
+package com.yahoo.vespa.hosted.controller.tenant;
+
+import com.yahoo.config.provision.TenantName;
+
+import java.util.Optional;
+
+/**
+ * A tenant as vague as its name.
+ *
+ * Only a reference to a cloud identity provider, and some billing info, is known for this tenant type.
+ *
+ * @author jonmv
+ */
+public class CloudTenant extends Tenant {
+
+ private final BillingInfo billingInfo;
+
+ /** Public for the serialization layer — do not use! */
+ public CloudTenant(TenantName name, BillingInfo info) {
+ super(name, Optional.empty());
+ billingInfo = info;
+ }
+
+ /** Creates a tenant with the given name, provided it passes validation. */
+ public static CloudTenant create(TenantName tenantName, BillingInfo billingInfo) {
+ return new CloudTenant(requireName(tenantName), billingInfo);
+ }
+
+ /** Returns the billing info for this tenant. */
+ public BillingInfo billingInfo() { return billingInfo; }
+
+ @Override
+ public Type type() {
+ return Type.cloud;
+ }
+
+}
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 c6ed9f7b559..19b7229515b 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
@@ -1,7 +1,6 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.tenant;
-
import com.yahoo.config.provision.TenantName;
import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact;
@@ -35,6 +34,8 @@ public abstract class Tenant {
return contact;
}
+ public abstract Type type();
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -49,11 +50,26 @@ public abstract class Tenant {
}
static TenantName requireName(TenantName name) {
- if (!name.value().matches("^(?=.{1,20}$)[a-z](-?[a-z0-9]+)*$")) {
+ if ( ! name.value().matches("^(?=.{1,20}$)[a-z](-?[a-z0-9]+)*$")) {
throw new IllegalArgumentException("New tenant or application names must start with a letter, may " +
"contain no more than 20 characters, and may only contain lowercase " +
"letters, digits or dashes, but no double-dashes.");
}
return name;
}
+
+
+ public enum Type {
+
+ /** Tenant authenticated through Athenz. */
+ athenz,
+
+ /** Tenant authenticated through Okta, as a user. */
+ user,
+
+ /** Tenant authenticated through some cloud identity provider. */
+ cloud;
+
+ }
+
}
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 47e5580fbe4..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
@@ -21,6 +21,11 @@ public class UserTenant extends Tenant {
super(name, contact);
}
+ @Override
+ public Type type() {
+ return Type.user;
+ }
+
public UserTenant(TenantName name) {
super(name, Optional.empty());
}
@@ -64,4 +69,5 @@ public class UserTenant extends Tenant {
}
return name;
}
+
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
index 6eb5061056f..cf5f8fac69d 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
@@ -114,7 +114,7 @@ public class ControllerTest {
applications = tester.controller().applications();
- assertNotNull(tester.controller().tenants().tenant(TenantName.from("tenant1")));
+ assertNotNull(tester.controller().tenants().get(TenantName.from("tenant1")));
assertNotNull(applications.get(ApplicationId.from(TenantName.from("tenant1"),
ApplicationName.from("application1"),
InstanceName.from("default"))));
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
index 615fb017363..e573c12af3b 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
@@ -249,14 +249,14 @@ public final class ControllerTester {
public TenantName createTenant(String tenantName, String domainName, Long propertyId, Optional<Contact> contact) {
TenantName name = TenantName.from(tenantName);
- Optional<Tenant> existing = controller().tenants().tenant(name);
+ Optional<Tenant> existing = controller().tenants().get(name);
if (existing.isPresent()) return name;
AthenzTenant tenant = AthenzTenant.create(name, createDomain(domainName), new Property("Property"+propertyId),
Optional.ofNullable(propertyId)
.map(Object::toString)
.map(PropertyId::new), contact);
controller().tenants().create(tenant, new OktaAccessToken("okta-token"));
- assertNotNull(controller().tenants().tenant(name));
+ assertNotNull(controller().tenants().get(name));
return name;
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
index 0b680149bff..dd4558dbe2c 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
@@ -112,7 +112,7 @@ public class ApplicationSerializerTest {
Optional.of(User.from("by-username")),
OptionalInt.of(7),
new MetricsService.ApplicationMetrics(0.5, 0.9),
- Optional.of("---begin---\nKEY\n---end---"),
+ Optional.of("-----BEGIN PUBLIC KEY-----\n∠( ᐛ 」∠)_\n-----END PUBLIC KEY-----"),
Optional.of(new RotationId("my-rotation")),
rotationStatus);
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()
);
}
+
}
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 c0c2d4043d9..705fc8adbac 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
@@ -22,6 +22,7 @@ import com.yahoo.vespa.athenz.api.OktaAccessToken;
import com.yahoo.vespa.config.SlimeUtils;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.ApplicationController;
+import com.yahoo.vespa.hosted.controller.LockedTenant;
import com.yahoo.vespa.hosted.controller.api.application.v4.EnvironmentResource;
import com.yahoo.vespa.hosted.controller.api.identifiers.Property;
import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
@@ -1541,7 +1542,9 @@ public class ApplicationApiTest extends ControllerContainerTest {
private void updateContactInformation() {
Contact contact = new Contact(URI.create("www.contacts.tld/1234"), URI.create("www.properties.tld/1234"), URI.create("www.issues.tld/1234"), List.of(List.of("alice"), List.of("bob")), "queue", Optional.empty());
- tester.controller().tenants().lockIfPresent(TenantName.from("tenant2"), lockedTenant -> tester.controller().tenants().store(lockedTenant.with(contact)));
+ tester.controller().tenants().lockIfPresent(TenantName.from("tenant2"),
+ LockedTenant.Athenz.class,
+ lockedTenant -> tester.controller().tenants().store(lockedTenant.with(contact)));
}
private void registerContact(long propertyId) {