diff options
author | Martin Polden <mpolden@mpolden.no> | 2018-04-06 09:14:47 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-04-06 09:14:47 +0200 |
commit | af0f1a92e8f74a6de63dd9c95f23147e7a476df1 (patch) | |
tree | 863b890689d228282ee8cde7d96fc368a8c41611 | |
parent | 19ed18bae9e056084cec4a91e887344b7baec218 (diff) | |
parent | d6cabc38ba3255663184b15eacb5bf9903c42c98 (diff) |
Merge pull request #5464 from vespa-engine/mpolden/refactor-tenant
Refactor tenant
29 files changed, 617 insertions, 428 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/Identifier.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/Identifier.java index 2fd75e25498..2067a88e5fb 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/Identifier.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/Identifier.java @@ -99,9 +99,5 @@ public abstract class Identifier { throw new IllegalArgumentException(String.format("Invalid id: %s. %s", id, explanation)); } - public static void throwInvalidId(String id, String explanation, String idName) { - throw new IllegalArgumentException(String.format("Invalid %s id: %s. %s", idName, id, explanation)); - } - } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/TenantId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/TenantId.java index 2c619fb58ef..4974192e213 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/TenantId.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/TenantId.java @@ -26,9 +26,4 @@ public class TenantId extends NonDefaultIdentifier { } } - /** Return true if this is the user tenant of the given user */ - public boolean isTenantFor(UserId userId) { - return id().equals("by-" + userId.id().replace('_', '-')); - } - } 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 f63966703d9..83ae5f5332f 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 @@ -12,14 +12,12 @@ import com.yahoo.config.provision.TenantName; import com.yahoo.vespa.athenz.api.NToken; import com.yahoo.vespa.curator.Lock; import com.yahoo.vespa.hosted.controller.api.ActivateResult; -import com.yahoo.vespa.hosted.controller.api.Tenant; import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions; import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus; import com.yahoo.vespa.hosted.controller.api.application.v4.model.configserverbindings.ConfigChangeActions; import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; import com.yahoo.vespa.hosted.controller.api.identifiers.Hostname; import com.yahoo.vespa.hosted.controller.api.identifiers.RevisionId; -import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory; import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsClient; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerClient; @@ -37,9 +35,11 @@ import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGenerato import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; import com.yahoo.vespa.hosted.controller.application.ApplicationVersion; +import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant; import com.yahoo.vespa.hosted.controller.application.Deployment; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobReport; +import com.yahoo.vespa.hosted.controller.tenant.Tenant; import com.yahoo.vespa.hosted.controller.deployment.DeploymentTrigger; import com.yahoo.vespa.hosted.controller.maintenance.DeploymentExpirer; import com.yahoo.vespa.hosted.controller.persistence.ControllerDb; @@ -140,7 +140,7 @@ public class ApplicationController { /** Returns all applications of a tenant */ public List<Application> asList(TenantName tenant) { - return db.listApplications(new TenantId(tenant.value())); + return db.listApplications(tenant); } /** @@ -239,19 +239,19 @@ 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(new TenantId(id.tenant().value())); + Optional<Tenant> tenant = controller.tenants().tenant(id.tenant()); if ( ! tenant.isPresent()) throw new IllegalArgumentException("Could not create '" + id + "': This tenant does not exist"); if (get(id).isPresent()) throw new IllegalArgumentException("Could not create '" + id + "': Application already exists"); if (get(dashToUnderscore(id)).isPresent()) // VESPA-1945 throw new IllegalArgumentException("Could not create '" + id + "': Application " + dashToUnderscore(id) + " already exists"); - if (id.instance().isDefault() && tenant.get().isAthensTenant()) { // Only create the athens application for "default" instances. + if (id.instance().isDefault() && tenant.get() instanceof AthenzTenant) { // Only create the athenz application for "default" instances. if ( ! token.isPresent()) throw new IllegalArgumentException("Could not create '" + id + "': No NToken provided"); ZmsClient zmsClient = zmsClientFactory.createZmsClientWithAuthorizedServiceToken(token.get()); - zmsClient.addApplication(tenant.get().getAthensDomain().get(), + zmsClient.addApplication(((AthenzTenant) tenant.get()).domain(), new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(id.application().value())); } LockedApplication application = new LockedApplication(new Application(id), lock); @@ -501,14 +501,14 @@ public class ApplicationController { if ( ! application.deployments().isEmpty()) throw new IllegalArgumentException("Could not delete '" + application + "': It has active deployments"); - Tenant tenant = controller.tenants().tenant(new TenantId(id.tenant().value())).get(); - if (tenant.isAthensTenant() && ! token.isPresent()) + Tenant tenant = controller.tenants().tenant(id.tenant()).get(); + if (tenant instanceof AthenzTenant && ! token.isPresent()) throw new IllegalArgumentException("Could not delete '" + application + "': No NToken provided"); // Only delete in Athenz once - if (id.instance().isDefault() && tenant.isAthensTenant()) { + if (id.instance().isDefault() && tenant instanceof AthenzTenant) { zmsClientFactory.createZmsClientWithAuthorizedServiceToken(token.get()) - .deleteApplication(tenant.getAthensDomain().get(), + .deleteApplication(((AthenzTenant) tenant).domain(), new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(id.application().value())); } db.deleteApplication(id); 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 482b6a6f3a9..ac48d9617fa 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 @@ -6,11 +6,12 @@ import com.yahoo.vespa.athenz.api.AthenzDomain; import com.yahoo.vespa.athenz.api.AthenzUser; import com.yahoo.vespa.athenz.api.NToken; import com.yahoo.vespa.curator.Lock; -import com.yahoo.vespa.hosted.controller.api.Tenant; -import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory; import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsClient; +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.persistence.ControllerDb; import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; import com.yahoo.vespa.hosted.controller.persistence.PersistenceException; @@ -20,14 +21,14 @@ import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; -import java.util.function.Predicate; import java.util.logging.Logger; import java.util.stream.Collectors; /** - * A singleton owned by the Controller which contains the methods and state for controlling applications. + * A singleton owned by the Controller which contains the methods and state for controlling tenants. * * @author bratseth + * @author mpolden */ public class TenantController { @@ -52,81 +53,84 @@ public class TenantController { this.athenzClientFactory = athenzClientFactory; } + /** Returns a list of all known tenants */ public List<Tenant> asList() { return db.listTenants(); } + /** Returns a list of all tenants accessible by the given user */ public List<Tenant> asList(UserId user) { + AthenzUser athenzUser = AthenzUser.fromUserId(user.id()); Set<AthenzDomain> userDomains = new HashSet<>(athenzClientFactory.createZtsClientWithServicePrincipal() - .getTenantDomainsForUser(AthenzUser.fromUserId(user.id()))); - Predicate<Tenant> hasUsersDomain = (tenant) -> tenant.getAthensDomain().isPresent() && userDomains.contains(tenant.getAthensDomain().get()); - Predicate<Tenant> isUserTenant = (tenant) -> tenant.getId().equals(user.toTenantId()); - + .getTenantDomainsForUser(athenzUser)); return asList().stream() - .filter(t -> hasUsersDomain.test(t) || isUserTenant.test(t)) - .collect(Collectors.toList()); + .filter(tenant -> tenant instanceof UserTenant|| + userDomains.stream().anyMatch(domain -> inDomain(tenant, domain))) + .collect(Collectors.toList()); } - public Tenant createUserTenant(String userName) { - TenantId userTenantId = new UserId(userName).toTenantId(); - try (Lock lock = lock(userTenantId)) { - Tenant tenant = Tenant.createUserTenant(userTenantId); - internalCreateTenant(tenant, Optional.empty()); - return tenant; + /** Create an user tenant with given username */ + public void create(UserTenant tenant) { + try (Lock lock = lock(tenant.name())) { + requireNonExistent(tenant.name()); + db.createTenant(tenant); + log.info("Created " + tenant); } } - /** Creates an Athens tenant. */ - public void createAthenzTenant(Tenant tenant, NToken token) { - try (Lock lock = lock(tenant.getId())) { - internalCreateTenant(tenant, Optional.of(token)); + /** Create an Athenz tenant */ + public void create(AthenzTenant tenant, NToken token) { + try (Lock lock = lock(tenant.name())) { + requireNonExistent(tenant.name()); + AthenzDomain domain = tenant.domain(); + Optional<Tenant> existingTenantWithDomain = tenantIn(domain); + if (existingTenantWithDomain.isPresent()) { + throw new IllegalArgumentException("Could not create tenant '" + tenant.name().value() + + "': The Athens domain '" + + domain.getName() + "' is already connected to tenant '" + + existingTenantWithDomain.get().name().value() + + "'"); + } + athenzClientFactory.createZmsClientWithAuthorizedServiceToken(token).createTenant(domain); + db.createTenant(tenant); + log.info("Created " + tenant); } } - private void internalCreateTenant(Tenant tenant, Optional<NToken> token) { - TenantId.validate(tenant.getId().id()); - if (tenant(tenant.getId()).isPresent()) - throw new IllegalArgumentException("Tenant '" + tenant.getId() + "' already exists"); - if (tenant(dashToUnderscore(tenant.getId())).isPresent()) - throw new IllegalArgumentException("Could not create " + tenant + ": Tenant " + dashToUnderscore(tenant.getId()) + " already exists"); - if (tenant.isAthensTenant() && ! token.isPresent()) - throw new IllegalArgumentException("Could not create " + tenant + ": No NToken provided"); - - if (tenant.isAthensTenant()) { - AthenzDomain domain = tenant.getAthensDomain().get(); - Optional<Tenant> existingTenantWithDomain = tenantHaving(domain); - if (existingTenantWithDomain.isPresent()) - throw new IllegalArgumentException("Could not create " + tenant + ": The Athens domain '" + domain.getName() + - "' is already connected to " + existingTenantWithDomain.get()); - athenzClientFactory.createZmsClientWithAuthorizedServiceToken(token.get()) - .createTenant(domain); + /** Returns the tenant in the given Athenz domain, or empty if none */ + private Optional<Tenant> tenantIn(AthenzDomain domain) { + return asList().stream() + .filter(tenant -> inDomain(tenant, domain)) + .findFirst(); + } + + /** Find tenant by name */ + public Optional<Tenant> tenant(TenantName name) { + try { + return db.getTenant(name); + } catch (PersistenceException e) { + throw new RuntimeException(e); } - db.createTenant(tenant); - log.info("Created " + tenant); } - /** Returns the tenant having the given Athens domain, or empty if none */ - private Optional<Tenant> tenantHaving(AthenzDomain domain) { - return asList().stream().filter(Tenant::isAthensTenant) - .filter(t -> t.getAthensDomain().get().equals(domain)) - .findAny(); + /** Find tenant by name */ + public Optional<Tenant> tenant(String name) { + return tenant(TenantName.from(name)); } - public Optional<Tenant> tenant(TenantId id) { + /** Find Athenz tenant by name */ + public Optional<AthenzTenant> athenzTenant(TenantName name) { try { - return db.getTenant(id); + return db.getAthenzTenant(name); } catch (PersistenceException e) { throw new RuntimeException(e); } } - public void updateTenant(Tenant updatedTenant, Optional<NToken> token) { - try (Lock lock = lock(updatedTenant.getId())) { - if ( ! tenant(updatedTenant.getId()).isPresent()) - throw new IllegalArgumentException("Could not update " + updatedTenant + ": Tenant does not exist"); - if (updatedTenant.isAthensTenant() && ! token.isPresent()) - throw new IllegalArgumentException("Could not update " + updatedTenant + ": No NToken provided"); - + /** Update Athenz tenant */ + public void updateTenant(AthenzTenant updatedTenant, NToken token) { + try (Lock lock = lock(updatedTenant.name())) { + requireExists(updatedTenant.name()); updateAthenzDomain(updatedTenant, token); db.updateTenant(updatedTenant); log.info("Updated " + updatedTenant); @@ -135,52 +139,68 @@ public class TenantController { } } - private void updateAthenzDomain(Tenant updatedTenant, Optional<NToken> token) { - Tenant existingTenant = tenant(updatedTenant.getId()).get(); - if ( ! existingTenant.isAthensTenant()) return; + /** Delete an user tenant */ + public void deleteTenant(UserTenant tenant) { + try (Lock lock = lock(tenant.name())) { + deleteTenant(tenant.name()); + } + } + + /** Delete an Athenz tenant */ + public void deleteTenant(AthenzTenant tenant, NToken nToken) { + try (Lock lock = lock(tenant.name())) { + deleteTenant(tenant.name()); + athenzClientFactory.createZmsClientWithAuthorizedServiceToken(nToken).deleteTenant(tenant.domain()); + } + } + + private void deleteTenant(TenantName name) { + try { + if ( ! controller.applications().asList(name).isEmpty()) { + throw new IllegalArgumentException("Could not delete tenant '" + name.value() + + "': This tenant has active applications"); + } + db.deleteTenant(name); + log.info("Deleted " + name); + } catch (PersistenceException e) { + throw new RuntimeException(e); + } + } + + private void updateAthenzDomain(AthenzTenant updatedTenant, NToken token) { + Optional<AthenzTenant> existingTenant = athenzTenant(updatedTenant.name()); + if ( ! existingTenant.isPresent()) return; - AthenzDomain existingDomain = existingTenant.getAthensDomain().get(); - AthenzDomain newDomain = updatedTenant.getAthensDomain().get(); + AthenzDomain existingDomain = existingTenant.get().domain(); + AthenzDomain newDomain = updatedTenant.domain(); if (existingDomain.equals(newDomain)) return; - Optional<Tenant> existingTenantWithNewDomain = tenantHaving(newDomain); + Optional<Tenant> existingTenantWithNewDomain = tenantIn(newDomain); if (existingTenantWithNewDomain.isPresent()) throw new IllegalArgumentException("Could not set domain of " + updatedTenant + " to '" + newDomain + "':" + existingTenantWithNewDomain.get() + " already has this domain"); - ZmsClient zmsClient = athenzClientFactory.createZmsClientWithAuthorizedServiceToken(token.get()); + ZmsClient zmsClient = athenzClientFactory.createZmsClientWithAuthorizedServiceToken(token); zmsClient.createTenant(newDomain); - List<Application> applications = controller.applications().asList(TenantName.from(existingTenant.getId().id())); + List<Application> applications = controller.applications().asList(existingTenant.get().name()); applications.forEach(a -> zmsClient.addApplication(newDomain, new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(a.id().application().value()))); applications.forEach(a -> zmsClient.deleteApplication(existingDomain, new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(a.id().application().value()))); zmsClient.deleteTenant(existingDomain); log.info("Updated Athens domain for " + updatedTenant + " from " + existingDomain + " to " + newDomain); } - public void deleteTenant(TenantId id, Optional<NToken> token) { - try (Lock lock = lock(id)) { - if ( ! tenant(id).isPresent()) - throw new NotExistsException(id); // TODO: Change exception and message - if ( ! controller.applications().asList(TenantName.from(id.id())).isEmpty()) - throw new IllegalArgumentException("Could not delete tenant '" + id + "': This tenant has active applications"); - - Tenant tenant = tenant(id).get(); - if (tenant.isAthensTenant() && ! token.isPresent()) - throw new IllegalArgumentException("Could not delete tenant '" + id + "': No NToken provided"); - - try { - db.deleteTenant(id); - } catch (PersistenceException e) { // TODO: Don't allow these to leak out - throw new RuntimeException(e); - } - if (tenant.isAthensTenant()) - athenzClientFactory.createZmsClientWithAuthorizedServiceToken(token.get()) - .deleteTenant(tenant.getAthensDomain().get()); - log.info("Deleted " + tenant); + private void requireNonExistent(TenantName name) { + if (tenant(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()) { + throw new IllegalArgumentException("Tenant '" + name + "' already exists"); } } - private TenantId dashToUnderscore(TenantId id) { - return new TenantId(id.id().replaceAll("-", "_")); + private void requireExists(TenantName name) { + if (!tenant(name).isPresent()) { + throw new IllegalArgumentException("Tenant '" + name + "' does not exist"); + } } /** @@ -188,8 +208,16 @@ public class TenantController { * Any operation which stores a tenant need to first acquire this lock, then read, modify * and store the tenant, and finally release (close) the lock. */ - private Lock lock(TenantId tenant) { + private Lock lock(TenantName tenant) { return curator.lock(tenant, Duration.ofMinutes(10)); } + private static boolean inDomain(Tenant tenant, AthenzDomain domain) { + return tenant instanceof AthenzTenant && ((AthenzTenant) tenant).in(domain); + } + + private static String dashToUnderscore(String s) { + return s.replace('-', '_'); + } + } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/api/Tenant.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/api/Tenant.java deleted file mode 100644 index 0edc63c69f5..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/api/Tenant.java +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.api; - -import com.yahoo.vespa.athenz.api.AthenzDomain; -import com.yahoo.vespa.hosted.controller.api.application.v4.model.TenantType; -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.identifiers.TenantId; - -import java.util.Optional; - -/** - * @author smorgrav - */ -// TODO: Move this and everything it owns to com.yahoo.hosted.controller.Tenant and com.yahoo.hosted.controller.tenant.* -// TODO: Use polymorphism to represent the two tenant types -public class Tenant { - - private final TenantId id; - private final Optional<Property> property; - private final Optional<AthenzDomain> athenzDomain; - private final Optional<PropertyId> propertyId; - - // TODO: Use factory methods. They're down at the bottom! - public Tenant(TenantId id, Optional<Property> property, Optional<AthenzDomain> athenzDomain) { - this(id, property, athenzDomain, Optional.empty()); - } - - public Tenant(TenantId id, Optional<Property> property, Optional<AthenzDomain> athenzDomain, Optional<PropertyId> propertyId) { - if (id.isUser()) { - require( ! property.isPresent(), "User tenant '%s' cannot have a property.", id); - require( ! propertyId.isPresent(), "User tenant '%s' cannot have a property ID.", id); - require( ! athenzDomain.isPresent(), "User tenant '%s' cannot have an athens domain.", id); - } else { - require( property.isPresent(), "Athens tenant '%s' must have a property.", id); - require( athenzDomain.isPresent(), "Athens tenant '%s' must have an athens domain.", id); - } - this.id = id; - this.property = property; - this.athenzDomain = athenzDomain; - this.propertyId = propertyId; // TODO: Check validity after TODO@14. OpsDb tenants have this set in Sherpa, while athens tenants do not. - // TODO: Require PropertyId for non-users, and fetch Property from EntityService (which will be moved to Organization) in the controller. - } - - public boolean isAthensTenant() { return athenzDomain.isPresent(); } - - public TenantType tenantType() { - if (athenzDomain.isPresent()) { - return TenantType.ATHENS; - } else { - return TenantType.USER; - } - } - - public TenantId getId() { - return id; - } - - /** OpsDB property name of the tenant, or Optional.empty() if none is stored. */ - public Optional<Property> getProperty() { - return property; - } - - /** OpsDB property ID of the tenant. Not (yet) required, so returns Optional.empty() if none is stored. */ - public Optional<PropertyId> getPropertyId() { - return propertyId; - } - - public Optional<AthenzDomain> getAthensDomain() { - return athenzDomain; - } - - private void require(boolean statement, String message, TenantId id) { - if (!statement) throw new IllegalArgumentException(String.format(message, id)); - } - - public static Tenant createAthensTenant(TenantId id, AthenzDomain athensDomain, Property property, Optional<PropertyId> propertyId) { - if (id.isUser()) { - throw new IllegalArgumentException("Invalid id for non-user tenant: " + id); - } - return new Tenant(id, Optional.ofNullable(property), Optional.ofNullable(athensDomain), propertyId); - } - - public static Tenant createUserTenant(TenantId id) { - if (!id.isUser()) { - throw new IllegalArgumentException("Invalid id for user tenant: " + id); - } - return new Tenant(id, Optional.empty(), Optional.empty(), Optional.empty()); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Tenant tenant = (Tenant) o; - - if (!id.equals(tenant.id)) return false; - if (!property.equals(tenant.property)) return false; - if (!athenzDomain.equals(tenant.athenzDomain)) return false; - if (!propertyId.equals(tenant.propertyId)) return false; - return true; - } - - @Override - public int hashCode() { - int result = id.hashCode(); - result = 31 * result + property.hashCode(); - result = 31 * result + athenzDomain.hashCode(); - result = 31 * result + propertyId.hashCode(); - return result; - } - - @Override - public String toString() { - return "tenant '" + id + "'"; - } - -} 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 cc977295acf..1a6bce2dba9 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 @@ -4,14 +4,13 @@ package com.yahoo.vespa.hosted.controller.maintenance; import com.yahoo.config.provision.ApplicationId; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Controller; -import com.yahoo.vespa.hosted.controller.api.Tenant; -import com.yahoo.vespa.hosted.controller.api.application.v4.model.TenantType; import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; -import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; -import com.yahoo.vespa.hosted.controller.api.integration.organization.OwnershipIssues; import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId; +import com.yahoo.vespa.hosted.controller.api.integration.organization.OwnershipIssues; import com.yahoo.vespa.hosted.controller.api.integration.organization.User; import com.yahoo.vespa.hosted.controller.application.ApplicationList; +import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant; +import com.yahoo.vespa.hosted.controller.tenant.Tenant; import java.time.Duration; import java.util.NoSuchElementException; @@ -50,9 +49,9 @@ public class ApplicationOwnershipConfirmer extends Maintainer { try { Tenant tenant = ownerOf(application.id()); Optional<IssueId> ourIssueId = application.ownershipIssueId(); - ourIssueId = tenant.tenantType() == TenantType.USER - ? ownershipIssues.confirmOwnership(ourIssueId, application.id(), userFor(tenant)) - : ownershipIssues.confirmOwnership(ourIssueId, application.id(), propertyIdFor(tenant)); + ourIssueId = tenant instanceof AthenzTenant + ? ownershipIssues.confirmOwnership(ourIssueId, application.id(), propertyIdFor((AthenzTenant) tenant)) + : ownershipIssues.confirmOwnership(ourIssueId, application.id(), userFor(tenant)); ourIssueId.ifPresent(issueId -> store(issueId, application.id())); } catch (RuntimeException e) { // Catch errors due to wrong data in the controller, or issues client timeout. @@ -67,7 +66,12 @@ public class ApplicationOwnershipConfirmer extends Maintainer { for (Application application : controller().applications().asList()) application.ownershipIssueId().ifPresent(issueId -> { try { - ownershipIssues.ensureResponse(issueId, ownerOf(application.id()).getPropertyId()); + Optional<PropertyId> propertyId = Optional.of(application.id()) + .map(this::ownerOf) + .filter(t -> t instanceof AthenzTenant) + .map(AthenzTenant.class::cast) + .flatMap(AthenzTenant::propertyId); + ownershipIssues.ensureResponse(issueId, propertyId); } catch (RuntimeException e) { log.log(Level.WARNING, "Exception caught when attempting to escalate issue with id " + issueId, e); @@ -76,17 +80,18 @@ public class ApplicationOwnershipConfirmer extends Maintainer { } private Tenant ownerOf(ApplicationId applicationId) { - return controller().tenants().tenant(new TenantId(applicationId.tenant().value())) + return controller().tenants().tenant(applicationId.tenant()) .orElseThrow(() -> new IllegalStateException("No tenant found for application " + applicationId)); } protected User userFor(Tenant tenant) { - return User.from(tenant.getId().id().replaceFirst("by-", "")); + return User.from(tenant.name().value().replaceFirst(Tenant.userPrefix, "")); } - protected PropertyId propertyIdFor(Tenant tenant) { - return tenant.getPropertyId() - .orElseThrow(() -> new NoSuchElementException("No PropertyId is listed for non-user tenant " + tenant)); + protected PropertyId propertyIdFor(AthenzTenant tenant) { + return tenant.propertyId() + .orElseThrow(() -> new NoSuchElementException("No PropertyId is listed for non-user tenant " + + tenant)); } protected void store(IssueId issueId, ApplicationId applicationId) { 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 e30ccbe7950..d471e553bb9 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 @@ -5,14 +5,13 @@ import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Controller; -import com.yahoo.vespa.hosted.controller.api.Tenant; -import com.yahoo.vespa.hosted.controller.api.application.v4.model.TenantType; import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; -import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; 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; import com.yahoo.vespa.hosted.controller.application.ApplicationList; +import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant; +import com.yahoo.vespa.hosted.controller.tenant.Tenant; import java.time.Duration; import java.util.Collection; @@ -94,17 +93,18 @@ public class DeploymentIssueReporter extends Maintainer { } private Tenant ownerOf(ApplicationId applicationId) { - return controller().tenants().tenant(new TenantId(applicationId.tenant().value())) + return controller().tenants().tenant(applicationId.tenant()) .orElseThrow(() -> new IllegalStateException("No tenant found for application " + applicationId)); } private User userFor(Tenant tenant) { - return User.from(tenant.getId().id().replaceFirst("by-", "")); + return User.from(tenant.name().value().replaceFirst(Tenant.userPrefix, "")); } - private PropertyId propertyIdFor(Tenant tenant) { - return tenant.getPropertyId() - .orElseThrow(() -> new NoSuchElementException("No PropertyId is listed for non-user tenant " + tenant)); + private PropertyId propertyIdFor(AthenzTenant tenant) { + return tenant.propertyId() + .orElseThrow(() -> new NoSuchElementException("No PropertyId is listed for non-user tenant " + + tenant)); } /** File an issue for applicationId, if it doesn't already have an open issue associated with it. */ @@ -112,9 +112,9 @@ public class DeploymentIssueReporter extends Maintainer { try { Tenant tenant = ownerOf(applicationId); Optional<IssueId> ourIssueId = controller().applications().require(applicationId).deploymentJobs().issueId(); - IssueId issueId = tenant.tenantType() == TenantType.USER - ? deploymentIssues.fileUnlessOpen(ourIssueId, applicationId, userFor(tenant)) - : deploymentIssues.fileUnlessOpen(ourIssueId, applicationId, propertyIdFor(tenant)); + IssueId issueId = tenant instanceof AthenzTenant + ? deploymentIssues.fileUnlessOpen(ourIssueId, applicationId, propertyIdFor((AthenzTenant) tenant)) + : deploymentIssues.fileUnlessOpen(ourIssueId, applicationId, userFor(tenant)); store(applicationId, issueId); } catch (RuntimeException e) { // Catch errors due to wrong data in the controller, or issues client timeout. @@ -126,7 +126,12 @@ public class DeploymentIssueReporter extends Maintainer { private void escalateInactiveDeploymentIssues(Collection<Application> applications) { applications.forEach(application -> application.deploymentJobs().issueId().ifPresent(issueId -> { try { - deploymentIssues.escalateIfInactive(issueId, ownerOf(application.id()).getPropertyId(), maxInactivity); + Optional<PropertyId> propertyId = Optional.of(application.id()) + .map(this::ownerOf) + .filter(t -> t instanceof AthenzTenant) + .map(AthenzTenant.class::cast) + .flatMap(AthenzTenant::propertyId); + deploymentIssues.escalateIfInactive(issueId, propertyId, maxInactivity); } catch (RuntimeException e) { log.log(Level.WARNING, "Exception caught when attempting to escalate issue with id " + issueId, e); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ControllerDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ControllerDb.java index fb6608ea643..c2078aa48b6 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ControllerDb.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ControllerDb.java @@ -3,10 +3,11 @@ package com.yahoo.vespa.hosted.controller.persistence; import com.google.common.base.Joiner; import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.TenantName; import com.yahoo.vespa.hosted.controller.Application; -import com.yahoo.vespa.hosted.controller.api.Tenant; -import com.yahoo.vespa.hosted.controller.api.identifiers.Identifier; -import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; +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 java.util.List; import java.util.Optional; @@ -21,14 +22,18 @@ public interface ControllerDb { // --------- Tenants - void createTenant(Tenant tenant); + void createTenant(UserTenant tenant); + + void createTenant(AthenzTenant tenant); // TODO: Remove exception from all signatures - void updateTenant(Tenant tenant) throws PersistenceException; + void updateTenant(AthenzTenant tenant) throws PersistenceException; + + void deleteTenant(TenantName name) throws PersistenceException; - void deleteTenant(TenantId tenantId) throws PersistenceException; + Optional<Tenant> getTenant(TenantName name) throws PersistenceException; - Optional<Tenant> getTenant(TenantId tenantId) throws PersistenceException; + Optional<AthenzTenant> getAthenzTenant(TenantName name) throws PersistenceException; List<Tenant> listTenants(); @@ -45,10 +50,10 @@ public interface ControllerDb { List<Application> listApplications(); /** Returns all applications of a tenant */ - List<Application> listApplications(TenantId tenantId); + List<Application> listApplications(TenantName name); /** Returns the given elements joined by dot "." */ - default String path(Identifier... elements) { + default String path(TenantName... elements) { return Joiner.on(".").join(elements); } 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 bef33e739be..3991d4322a1 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 @@ -4,12 +4,11 @@ package com.yahoo.vespa.hosted.controller.persistence; import com.google.inject.Inject; import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.TenantName; import com.yahoo.path.Path; -import com.yahoo.transaction.NestedTransaction; import com.yahoo.vespa.config.SlimeUtils; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.curator.Lock; -import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs; import com.yahoo.vespa.hosted.controller.versions.VersionStatus; import com.yahoo.vespa.hosted.controller.versions.VespaVersion; @@ -64,8 +63,8 @@ public class CuratorDb { // -------------- Locks -------------------------------------------------- - public Lock lock(TenantId id, Duration timeout) { - return lock(lockPath(id), timeout); + public Lock lock(TenantName name, Duration timeout) { + return lock(lockPath(name), timeout); } public Lock lock(ApplicationId id, Duration timeout) { @@ -236,9 +235,9 @@ public class CuratorDb { // -------------- Paths -------------------------------------------------- - private Path lockPath(TenantId tenant) { + private Path lockPath(TenantName tenant) { Path lockPath = lockRoot - .append(tenant.id()); + .append(tenant.value()); curator.create(lockPath); return lockPath; } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/MemoryControllerDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/MemoryControllerDb.java index 2c5d77c7773..509896dd63c 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/MemoryControllerDb.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/MemoryControllerDb.java @@ -2,11 +2,12 @@ package com.yahoo.vespa.hosted.controller.persistence; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.vespa.hosted.controller.AlreadyExistsException; +import com.yahoo.config.provision.TenantName; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.NotExistsException; -import com.yahoo.vespa.hosted.controller.api.Tenant; -import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; +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 java.util.ArrayList; import java.util.HashMap; @@ -22,35 +23,57 @@ import java.util.stream.Collectors; */ public class MemoryControllerDb implements ControllerDb { - private final Map<TenantId, Tenant> tenants = new HashMap<>(); + private final Map<TenantName, Tenant> tenants = new HashMap<>(); private final Map<String, Application> applications = new HashMap<>(); - @Override - public void createTenant(Tenant tenant) { - if (tenants.containsKey(tenant.getId())) { - throw new AlreadyExistsException(tenant.getId()); + private void createTenant(Tenant tenant) { + if (tenants.containsKey(tenant.name())) { + throw new IllegalArgumentException("Tenant '" + tenant + "' already exists"); } - tenants.put(tenant.getId(), tenant); + tenants.put(tenant.name(), tenant); } - @Override - public void updateTenant(Tenant tenant) { - if (!tenants.containsKey(tenant.getId())) { - throw new NotExistsException(tenant.getId()); + private void updateTenant(Tenant tenant) { + if (!tenants.containsKey(tenant.name())) { + throw new NotExistsException(tenant.name().value()); } - tenants.put(tenant.getId(), tenant); + tenants.put(tenant.name(), tenant); + } + + @Override + public void createTenant(UserTenant tenant) { + createTenant((Tenant) tenant); + } + + @Override + public void createTenant(AthenzTenant tenant) { + createTenant((Tenant) tenant); } @Override - public void deleteTenant(TenantId tenantId) { - if (tenants.remove(tenantId) == null) { - throw new NotExistsException(tenantId); + public void updateTenant(AthenzTenant tenant) { + updateTenant((Tenant) tenant); + } + + @Override + public void deleteTenant(TenantName name) { + if (tenants.remove(name) == null) { + throw new NotExistsException(name.value()); } } @Override - public Optional<Tenant> getTenant(TenantId tenantId) { - return Optional.ofNullable(tenants.get(tenantId)); + public Optional<Tenant> getTenant(TenantName name) { + return getTenant(name, Tenant.class); + } + + @Override + public Optional<AthenzTenant> getAthenzTenant(TenantName name) { + return getTenant(name, AthenzTenant.class); + } + + private <T extends Tenant> Optional<T> getTenant(TenantName name, Class<T> type) { + return Optional.ofNullable(tenants.get(name)).map(type::cast); } @Override @@ -79,9 +102,9 @@ public class MemoryControllerDb implements ControllerDb { } @Override - public List<Application> listApplications(TenantId tenantId) { + public List<Application> listApplications(TenantName name) { return applications.values().stream() - .filter(a -> a.id().tenant().value().equals(tenantId.id())) + .filter(a -> a.id().tenant().equals(name)) .collect(Collectors.toList()); } 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 e85157a57c2..58bd95a22d3 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,7 +30,6 @@ import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.NotExistsException; import com.yahoo.vespa.hosted.controller.api.ActivateResult; -import com.yahoo.vespa.hosted.controller.api.Tenant; import com.yahoo.vespa.hosted.controller.api.application.v4.ApplicationResource; import com.yahoo.vespa.hosted.controller.api.application.v4.EnvironmentResource; import com.yahoo.vespa.hosted.controller.api.application.v4.TenantResource; @@ -76,10 +75,12 @@ import com.yahoo.vespa.hosted.controller.restapi.ResourceResponse; import com.yahoo.vespa.hosted.controller.restapi.SlimeJsonResponse; import com.yahoo.vespa.hosted.controller.restapi.StringResponse; import com.yahoo.vespa.hosted.controller.restapi.filter.SetBouncerPassthruHeaderFilter; +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.serviceview.bindings.ApplicationView; import com.yahoo.yolean.Exceptions; -import javax.ws.rs.BadRequestException; import javax.ws.rs.ForbiddenException; import javax.ws.rs.InternalServerErrorException; import javax.ws.rs.NotAuthorizedException; @@ -253,7 +254,8 @@ public class ApplicationApiHandler extends LoggingRequestHandler { Cursor tenantsArray = response.setArray("tenants"); for (Tenant tenant : tenants) tenantInTenantsListToSlime(tenant, request.getUri(), tenantsArray.addObject()); - response.setBool("tenantExists", tenants.stream().map(Tenant::getId).anyMatch(id -> id.isTenantFor(userId))); + response.setBool("tenantExists", tenants.stream().anyMatch(tenant -> tenant instanceof UserTenant && + ((UserTenant) tenant).is(userId.id()))); return new SlimeJsonResponse(slime); } @@ -314,9 +316,9 @@ public class ApplicationApiHandler extends LoggingRequestHandler { } private HttpResponse tenant(String tenantName, HttpRequest request) { - return controller.tenants().tenant(new TenantId((tenantName))) - .map(tenant -> tenant(tenant, request, true)) - .orElseGet(() -> ErrorResponse.notFoundError("Tenant '" + tenantName + "' does not exist")); + return controller.tenants().tenant(TenantName.from(tenantName)) + .map(tenant -> tenant(tenant, request, true)) + .orElseGet(() -> ErrorResponse.notFoundError("Tenant '" + tenantName + "' does not exist")); } private HttpResponse tenant(Tenant tenant, HttpRequest request, boolean listApplications) { @@ -510,7 +512,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler { private HttpResponse setGlobalRotationOverride(String tenantName, String applicationName, String instanceName, String environment, String region, boolean inService, HttpRequest request) { // Check if request is authorized - Optional<Tenant> existingTenant = controller.tenants().tenant(new TenantId(tenantName)); + Optional<Tenant> existingTenant = controller.tenants().tenant(tenantName); if (!existingTenant.isPresent()) return ErrorResponse.notFoundError("Tenant '" + tenantName + "' does not exist"); @@ -611,57 +613,41 @@ public class ApplicationApiHandler extends LoggingRequestHandler { Optional<UserId> user = getUserId(request); if ( ! user.isPresent() ) throw new ForbiddenException("Not authenticated or not an user."); + String username = UserTenant.normalizeUser(user.get().id()); try { - controller.tenants().createUserTenant(user.get().id()); - return new MessageResponse("Created user '" + user.get() + "'"); + controller.tenants().create(UserTenant.create(username)); + return new MessageResponse("Created user '" + username + "'"); } catch (AlreadyExistsException e) { // Ok - return new MessageResponse("User '" + user + "' already exists"); + return new MessageResponse("User '" + username + "' already exists"); } } private HttpResponse updateTenant(String tenantName, HttpRequest request) { - Optional<Tenant> existingTenant = controller.tenants().tenant(new TenantId(tenantName)); + Optional<AthenzTenant> existingTenant = controller.tenants().athenzTenant(TenantName.from(tenantName)); if ( ! existingTenant.isPresent()) return ErrorResponse.notFoundError("Tenant '" + tenantName + "' does not exist");; Inspector requestData = toSlime(request.getData()).get(); - - Tenant updatedTenant; - switch (existingTenant.get().tenantType()) { - case USER: { - throw new BadRequestException("Cannot set property or OpsDB user group for user tenant"); - } - case ATHENS: { - updatedTenant = Tenant.createAthensTenant(new TenantId(tenantName), - new AthenzDomain(mandatory("athensDomain", requestData).asString()), - new Property(mandatory("property", requestData).asString()), - optional("propertyId", requestData).map(PropertyId::new)); - controller.tenants().updateTenant(updatedTenant, getUserPrincipal(request).getNToken()); - break; - } - default: { - throw new BadRequestException("Unknown tenant type: " + existingTenant.get().tenantType()); - } + AthenzTenant updatedTenant = existingTenant.get() + .with(new AthenzDomain(mandatory("athensDomain", requestData).asString())) + .with(new Property(mandatory("property", requestData).asString())); + Optional<PropertyId> propertyId = optional("propertyId", requestData).map(PropertyId::new); + if (propertyId.isPresent()) { + updatedTenant = updatedTenant.with(propertyId.get()); } + controller.tenants().updateTenant(updatedTenant, requireNToken(request, "Could not update " + tenantName)); return tenant(updatedTenant, request, true); } private HttpResponse createTenant(String tenantName, HttpRequest request) { - if (new TenantId(tenantName).isUser()) - return ErrorResponse.badRequest("Use User API to create user tenants."); - Inspector requestData = toSlime(request.getData()).get(); - Tenant tenant = new Tenant(new TenantId(tenantName), - optional("property", requestData).map(Property::new), - optional("athensDomain", requestData).map(AthenzDomain::new), - optional("propertyId", requestData).map(PropertyId::new)); - if (tenant.isAthensTenant()) - throwIfNotAthenzDomainAdmin(new AthenzDomain(mandatory("athensDomain", requestData).asString()), request); - - NToken token = getUserPrincipal(request).getNToken() - .orElseThrow(() -> new IllegalArgumentException("Could not create " + tenant + ": No NToken provided")); - controller.tenants().createAthenzTenant(tenant, token); + AthenzTenant tenant = AthenzTenant.create(TenantName.from(tenantName), + new AthenzDomain(mandatory("athensDomain", requestData).asString()), + new Property(mandatory("property", requestData).asString()), + optional("propertyId", requestData).map(PropertyId::new)); + throwIfNotAthenzDomainAdmin(tenant.domain(), request); + controller.tenants().create(tenant, requireNToken(request, "Could not create " + tenantName)); return tenant(tenant, request, true); } @@ -784,8 +770,9 @@ public class ApplicationApiHandler extends LoggingRequestHandler { // Validate that domain in identity configuration (deployment.xml) is same as tenant domain applicationPackage.map(ApplicationPackage::deploymentSpec).flatMap(DeploymentSpec::athenzDomain) .ifPresent(identityDomain -> { - Tenant tenant = controller.tenants().tenant(new TenantId(tenantName)).orElseThrow(() -> new IllegalArgumentException("Tenant does not exist")); - AthenzDomain tenantDomain = tenant.getAthensDomain().orElseThrow(() -> new IllegalArgumentException("Identity provider only available to Athenz onboarded tenants")); + AthenzTenant tenant = controller.tenants().athenzTenant(TenantName.from(tenantName)) + .orElseThrow(() -> new IllegalArgumentException("Tenant does not exist")); + AthenzDomain tenantDomain = tenant.domain(); if (! Objects.equals(tenantDomain.getName(), identityDomain.value())) { throw new ForbiddenException( String.format( @@ -798,10 +785,19 @@ public class ApplicationApiHandler extends LoggingRequestHandler { } private HttpResponse deleteTenant(String tenantName, HttpRequest request) { - Optional<Tenant> tenant = controller.tenants().tenant(new TenantId(tenantName)); + Optional<Tenant> tenant = controller.tenants().tenant(tenantName); if ( ! tenant.isPresent()) return ErrorResponse.notFoundError("Could not delete tenant '" + tenantName + "': Tenant not found"); // NOTE: The Jersey implementation would silently ignore this - controller.tenants().deleteTenant(new TenantId(tenantName), getUserPrincipal(request).getNToken()); + + if (tenant.get() instanceof AthenzTenant) { + controller.tenants().deleteTenant((AthenzTenant) tenant.get(), + requireNToken(request, "Could not delete " + tenantName)); + } else if (tenant.get() instanceof UserTenant) { + controller.tenants().deleteTenant((UserTenant) tenant.get()); + } else { + throw new IllegalArgumentException("Don't know how to delete " + tenant.get() + " of type " + + tenant.get().getClass().getSimpleName()); + } // TODO: Change to a message response saying the tenant was deleted return tenant(tenant.get(), request, false); @@ -901,19 +897,24 @@ public class ApplicationApiHandler extends LoggingRequestHandler { } private Tenant getTenantOrThrow(String tenantName) { - return controller.tenants().tenant(new TenantId(tenantName)) - .orElseThrow(() -> new NotExistsException(new TenantId(tenantName))); + return controller.tenants().tenant(tenantName) + .orElseThrow(() -> new NotExistsException(new TenantId(tenantName))); } private void toSlime(Cursor object, Tenant tenant, HttpRequest request, boolean listApplications) { - object.setString("tenant", tenant.getId().id()); - object.setString("type", tenant.tenantType().name()); - tenant.getAthensDomain().ifPresent(a -> object.setString("athensDomain", a.getName())); - tenant.getProperty().ifPresent(p -> object.setString("property", p.id())); - tenant.getPropertyId().ifPresent(p -> object.setString("propertyId", p.toString())); + object.setString("tenant", tenant.name().value()); + object.setString("type", tentantType(tenant)); + Optional<PropertyId> propertyId = Optional.empty(); + if (tenant instanceof AthenzTenant) { + AthenzTenant athenzTenant = (AthenzTenant) tenant; + object.setString("athensDomain", athenzTenant.domain().getName()); + object.setString("property", athenzTenant.property().id()); + propertyId = athenzTenant.propertyId(); + propertyId.ifPresent(id -> object.setString("propertyId", id.toString())); + } Cursor applicationArray = object.setArray("applications"); if (listApplications) { // This cludge is needed because we call this after deleting the tenant. As this call makes another tenant lookup it will fail. TODO is to support lookup on tenant - for (Application application : controller.applications().asList(TenantName.from(tenant.getId().id()))) { + for (Application application : controller.applications().asList(tenant.name())) { if (application.id().instance().isDefault()) {// TODO: Skip non-default applications until supported properly if (recurseOverApplications(request)) toSlime(applicationArray.addObject(), application, request); @@ -922,20 +923,20 @@ public class ApplicationApiHandler extends LoggingRequestHandler { } } } - tenant.getPropertyId().ifPresent(propertyId -> { + propertyId.ifPresent(id -> { try { - object.setString("propertyUrl", controller.organization().propertyUri(propertyId).toString()); - object.setString("contactsUrl", controller.organization().contactsUri(propertyId).toString()); - object.setString("issueCreationUrl", controller.organization().issueCreationUri(propertyId).toString()); + object.setString("propertyUrl", controller.organization().propertyUri(id).toString()); + object.setString("contactsUrl", controller.organization().contactsUri(id).toString()); + object.setString("issueCreationUrl", controller.organization().issueCreationUri(id).toString()); Cursor lists = object.setArray("contacts"); - for (List<? extends User> contactList : controller.organization().contactsFor(propertyId)) { + for (List<? extends User> contactList : controller.organization().contactsFor(id)) { Cursor list = lists.addArray(); for (User contact : contactList) list.addString(contact.displayName()); } } catch (RuntimeException e) { - log.log(Level.WARNING, "Error fetching property info for " + tenant + " with propertyId " + propertyId + ": " + + log.log(Level.WARNING, "Error fetching property info for " + tenant + " with propertyId " + id + ": " + Exceptions.toMessageString(e)); } }); @@ -943,12 +944,15 @@ public class ApplicationApiHandler extends LoggingRequestHandler { // A tenant has different content when in a list ... antipattern, but not solvable before application/v5 private void tenantInTenantsListToSlime(Tenant tenant, URI requestURI, Cursor object) { - object.setString("tenant", tenant.getId().id()); + object.setString("tenant", tenant.name().value()); Cursor metaData = object.setObject("metaData"); - metaData.setString("type", tenant.tenantType().name()); - tenant.getAthensDomain().ifPresent(a -> metaData.setString("athensDomain", a.getName())); - tenant.getProperty().ifPresent(p -> metaData.setString("property", p.id())); - object.setString("url", withPath("/application/v4/tenant/" + tenant.getId().id(), requestURI).toString()); + metaData.setString("type", tentantType(tenant)); + if (tenant instanceof AthenzTenant) { + AthenzTenant athenzTenant = (AthenzTenant) tenant; + metaData.setString("athensDomain", athenzTenant.domain().getName()); + metaData.setString("property", athenzTenant.property().id()); + } + object.setString("url", withPath("/application/v4/tenant/" + tenant.name().value(), requestURI).toString()); } /** Returns a copy of the given URI with the host and port from the given URI and the path set to the given path */ @@ -1212,4 +1216,18 @@ public class ApplicationApiHandler extends LoggingRequestHandler { return ImmutableSet.of("all", "true", "deployment").contains(request.getProperty("recursive")); } + private static String tentantType(Tenant tenant) { + if (tenant instanceof AthenzTenant) { + return "ATHENS"; + } else if (tenant instanceof UserTenant) { + return "USER"; + } + throw new IllegalArgumentException("Unrecognized tenant type: " + tenant.getClass().getSimpleName()); + } + + private static NToken requireNToken(HttpRequest request, String message) { + return getUserPrincipal(request).getNToken().orElseThrow(() -> new IllegalArgumentException( + message + ": No NToken provided")); + } + } 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 e6623fd6508..dc9ead96e0a 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 @@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.restapi.filter; import com.google.inject.Inject; import com.yahoo.config.provision.ApplicationName; +import com.yahoo.config.provision.TenantName; import com.yahoo.jdisc.Response; import com.yahoo.jdisc.handler.ResponseHandler; import com.yahoo.jdisc.http.HttpRequest.Method; @@ -15,13 +16,13 @@ import com.yahoo.vespa.athenz.api.AthenzPrincipal; import com.yahoo.vespa.athenz.api.AthenzUser; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.TenantController; -import com.yahoo.vespa.hosted.controller.api.Tenant; -import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; -import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction; import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory; import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsException; import com.yahoo.vespa.hosted.controller.restapi.Path; +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.yolean.chain.After; import com.yahoo.yolean.chain.Provides; @@ -91,9 +92,9 @@ public class ControllerAuthorizationFilter implements SecurityRequestFilter { } else if (isHostedOperatorOperation(path, method)) { verifyIsHostedOperator(principal); } else if (isTenantAdminOperation(path, method)) { - verifyIsTenantAdmin(principal, getTenantId(path)); + verifyIsTenantAdmin(principal, getTenantName(path)); } else if (isTenantPipelineOperation(path, method)) { - verifyIsTenantPipelineOperator(principal, getTenantId(path), getApplicationName(path)); + verifyIsTenantPipelineOperator(principal, getTenantName(path), getApplicationName(path)); } else { throw new ForbiddenException("No access control is explicitly declared for this api."); } @@ -149,8 +150,8 @@ public class ControllerAuthorizationFilter implements SecurityRequestFilter { .hasHostedOperatorAccess(identity); } - private void verifyIsTenantAdmin(AthenzPrincipal principal, TenantId tenantId) { - tenantController.tenant(tenantId) + private void verifyIsTenantAdmin(AthenzPrincipal principal, TenantName name) { + tenantController.tenant(name) .ifPresent(tenant -> { if (!isTenantAdmin(principal.getIdentity(), tenant)) { throw new ForbiddenException("Tenant admin or Vespa operator role required"); @@ -159,26 +160,23 @@ public class ControllerAuthorizationFilter implements SecurityRequestFilter { } private boolean isTenantAdmin(AthenzIdentity identity, Tenant tenant) { - switch (tenant.tenantType()) { - case ATHENS: - return clientFactory.createZmsClientWithServicePrincipal() - .hasTenantAdminAccess(identity, tenant.getAthensDomain().get()); - case USER: { - if (!(identity instanceof AthenzUser)) { - return false; - } - AthenzUser user = (AthenzUser) identity; - return tenant.getId().equals(new UserId(user.getName()).toTenantId()); + if (tenant instanceof AthenzTenant) { + return clientFactory.createZmsClientWithServicePrincipal() + .hasTenantAdminAccess(identity, ((AthenzTenant) tenant).domain()); + } else if (tenant instanceof UserTenant) { + if (!(identity instanceof AthenzUser)) { + return false; } - default: - throw new InternalServerErrorException("Unknown tenant type: " + tenant.tenantType()); + AthenzUser user = (AthenzUser) identity; + return ((UserTenant) tenant).is(user.getName()); } + throw new InternalServerErrorException("Unknown tenant type: " + tenant.getClass().getSimpleName()); } private void verifyIsTenantPipelineOperator(AthenzPrincipal principal, - TenantId tenantId, + TenantName name, ApplicationName application) { - tenantController.tenant(tenantId) + tenantController.tenant(name) .ifPresent(tenant -> verifyIsTenantPipelineOperator(principal.getIdentity(), tenant, application)); } @@ -193,8 +191,8 @@ public class ControllerAuthorizationFilter implements SecurityRequestFilter { } // NOTE: no fine-grained deploy authorization for non-Athenz tenants - if (tenant.isAthensTenant()) { - AthenzDomain tenantDomain = tenant.getAthensDomain().get(); + if (tenant instanceof AthenzTenant) { + AthenzDomain tenantDomain = ((AthenzTenant) tenant).domain(); if (!hasDeployerAccess(identity, tenantDomain, application)) { throw new ForbiddenException(String.format( "'%1$s' does not have access to '%2$s'. " + @@ -218,10 +216,10 @@ public class ControllerAuthorizationFilter implements SecurityRequestFilter { } } - private static TenantId getTenantId(Path path) { + private static TenantName getTenantName(Path path) { if (!path.matches("/application/v4/tenant/{tenant}/{*}")) throw new InternalServerErrorException("Unable to handle path: " + path.asString()); - return new TenantId(path.get("tenant")); + return TenantName.from(path.get("tenant")); } private static ApplicationName getApplicationName(Path path) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiHandler.java index bcabcf48e91..4374fd0246e 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiHandler.java @@ -27,8 +27,7 @@ import java.util.logging.Level; import java.util.logging.Logger; /** - * This implements a callback API from Screwdriver which lets deployment jobs notify the controller - * on completion. + * This API lists deployment jobs that are queued for execution on Screwdriver. * * @author bratseth * @author mpolden diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java index f38ea14bbd8..c20a8baf06d 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java @@ -21,7 +21,7 @@ import java.util.logging.Level; import java.util.stream.Collectors; /** - * REST API that provides information about Hosted Vespa zones (version 1) + * REST API that provides information about zones in hosted Vespa (version 1) * * @author mpolden */ 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 new file mode 100644 index 00000000000..0ba0eea2dab --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java @@ -0,0 +1,82 @@ +// 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.athenz.api.AthenzDomain; +import com.yahoo.vespa.hosted.controller.api.identifiers.Property; +import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; + +import java.util.Optional; + +/** + * Represents an Athenz tenant in hosted Vespa. + * + * @author mpolden + */ +public class AthenzTenant extends Tenant { + + private final AthenzDomain domain; + private final Property property; + private final Optional<PropertyId> propertyId; + + /** + * This should only be used by serialization. + * Use {@link #create(TenantName, AthenzDomain, Property, Optional)}. + * */ + public AthenzTenant(TenantName name, AthenzDomain domain, Property property, Optional<PropertyId> propertyId) { + super(name); + this.domain = domain; + this.property = property; + this.propertyId = propertyId; + } + + /** Property name of this tenant */ + public Property property() { + return property; + } + + /** Property ID of the tenant, if present */ + public Optional<PropertyId> propertyId() { + return propertyId; + } + + /** Athenz domain of this tenant */ + public AthenzDomain domain() { + return domain; + } + + /** Returns true if tenant is in given domain */ + public boolean in(AthenzDomain domain) { + return this.domain.equals(domain); + } + + @Override + public String toString() { + return "athenz tenant '" + name() + "'"; + } + + public AthenzTenant with(AthenzDomain domain) { + return new AthenzTenant(name(), domain, property(), propertyId()); + } + + public AthenzTenant with(Property property) { + return new AthenzTenant(name(), domain, property, propertyId()); + } + + public AthenzTenant with(PropertyId propertyId) { + return new AthenzTenant(name(), domain, property, Optional.of(propertyId)); + } + + /** Create a new Athenz tenant */ + public static AthenzTenant create(TenantName name, AthenzDomain domain, Property property, + Optional<PropertyId> propertyId) { + return new AthenzTenant(requireName(requireNoPrefix(name)), domain, property, propertyId); + } + + private static TenantName requireNoPrefix(TenantName name) { + if (name.value().startsWith(Tenant.userPrefix)) { + throw new IllegalArgumentException("Athenz tenant name cannot have prefix '" + Tenant.userPrefix + "'"); + } + return name; + } +} 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 new file mode 100644 index 00000000000..aac3fa20d11 --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java @@ -0,0 +1,50 @@ +// 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 java.util.Objects; + +/** + * A tenant in hosted Vespa. + * + * @author mpolden + */ +public abstract class Tenant { + + public static final String userPrefix = "by-"; + + private final TenantName name; + + Tenant(TenantName name) { + this.name = name; + } + + /** Name of this tenant */ + public TenantName name() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Tenant tenant = (Tenant) o; + return Objects.equals(name, tenant.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + static TenantName requireName(TenantName name) { + 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; + } +} 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 new file mode 100644 index 00000000000..e110600639b --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/UserTenant.java @@ -0,0 +1,55 @@ +// 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; + +/** + * Represents an user tenant in hosted Vespa. + * + * @author mpolden + */ +public class UserTenant extends Tenant { + + /** + * This should only be used by serialization. + * Use {@link #create(String)}. + * */ + public UserTenant(TenantName name) { + super(name); + } + + /** Returns true if this is the tenant for the given user name */ + public boolean is(String username) { + return name().value().equals(normalizeUser(username)); + } + + @Override + public String toString() { + return "user tenant '" + name() + "'"; + } + + /** Create a new user tenant */ + public static UserTenant create(String username) { + TenantName name = TenantName.from(username); + return new UserTenant(requireName(requireUser(name))); + } + + /** Normalize given username. E.g. foo_bar becomes by-foo-bar */ + public static String normalizeUser(String username) { + int offset = 0; + if (username.startsWith(Tenant.userPrefix)) { + offset = Tenant.userPrefix.length(); + } + return Tenant.userPrefix + username.substring(offset).replace('_', '-'); + } + + private static TenantName requireUser(TenantName name) { + if (!name.value().startsWith(Tenant.userPrefix)) { + throw new IllegalArgumentException("User tenant must have prefix '" + Tenant.userPrefix + "'"); + } + if (name.value().substring(Tenant.userPrefix.length()).contains("_")) { + throw new IllegalArgumentException("User tenant cannot contain '_'"); + } + return name; + } +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/package-info.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/package-info.java new file mode 100644 index 00000000000..9218bfcd850 --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/package-info.java @@ -0,0 +1,8 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +/** + * @author mpolden + */ +@ExportPackage +package com.yahoo.vespa.hosted.controller.tenant; + +import com.yahoo.osgi.annotation.ExportPackage; 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 4d6de0bf4ef..fd0362e4552 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 @@ -17,7 +17,6 @@ import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus import com.yahoo.vespa.hosted.controller.api.application.v4.model.ScrewdriverBuildJob; import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId; -import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.api.integration.BuildService; import com.yahoo.vespa.hosted.controller.api.integration.dns.Record; import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName; @@ -133,7 +132,7 @@ public class ControllerTest { tester.restartController(); applications = tester.controller().applications(); - assertNotNull(tester.controller().tenants().tenant(new TenantId("tenant1"))); + assertNotNull(tester.controller().tenants().tenant(TenantName.from("tenant1"))); assertNotNull(applications.get(ApplicationId.from(TenantName.from("tenant1"), ApplicationName.from("application1"), InstanceName.from("default")))); @@ -500,7 +499,7 @@ public class ControllerTest { public void testDeployUntestedChangeFails() { DeploymentTester tester = new DeploymentTester(); ApplicationController applications = tester.controller().applications(); - TenantId tenant = tester.controllerTester().createTenant("tenant1", "domain1", 11L); + TenantName tenant = tester.controllerTester().createTenant("tenant1", "domain1", 11L); Application app = tester.controllerTester().createApplication(tenant, "app1", "default", 1); tester.deployCompletely(app, applicationPackage); 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 b4bbc0f68e7..3898b18cd7c 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 @@ -4,12 +4,12 @@ package com.yahoo.vespa.hosted.controller; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; +import com.yahoo.config.provision.TenantName; import com.yahoo.slime.Slime; import com.yahoo.test.ManualClock; import com.yahoo.vespa.athenz.api.AthenzDomain; import com.yahoo.vespa.curator.Lock; import com.yahoo.vespa.curator.mock.MockCurator; -import com.yahoo.vespa.hosted.controller.api.Tenant; import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions; import com.yahoo.vespa.hosted.controller.api.application.v4.model.GitRevision; import com.yahoo.vespa.hosted.controller.api.application.v4.model.ScrewdriverBuildJob; @@ -19,7 +19,6 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.GitRepository; 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.identifiers.ScrewdriverId; -import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.api.integration.chef.ChefMock; import com.yahoo.vespa.hosted.controller.api.integration.deployment.ArtifactRepository; import com.yahoo.vespa.hosted.controller.api.integration.dns.MemoryNameService; @@ -30,6 +29,8 @@ import com.yahoo.vespa.hosted.controller.api.integration.organization.MockOrgani import com.yahoo.vespa.hosted.controller.api.integration.routing.MemoryGlobalRoutingService; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; +import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant; +import com.yahoo.vespa.hosted.controller.tenant.Tenant; import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock; import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzDbMock; import com.yahoo.vespa.hosted.controller.integration.MockMetricsService; @@ -140,7 +141,7 @@ public final class ControllerTester { /** Creates the given tenant and application and deploys it */ public Application createAndDeploy(String tenantName, String domainName, String applicationName, String instanceName, ZoneId zone, long projectId, Long propertyId) { - TenantId tenant = createTenant(tenantName, domainName, propertyId); + TenantName tenant = createTenant(tenantName, domainName, propertyId); Application application = createApplication(tenant, applicationName, instanceName, projectId); deploy(application, zone); return application; @@ -189,20 +190,21 @@ public final class ControllerTester { return domain; } - public TenantId createTenant(String tenantName, String domainName, Long propertyId) { - TenantId id = new TenantId(tenantName); - Optional<Tenant> existing = controller().tenants().tenant(id); - if (existing.isPresent()) return id; - - Tenant tenant = Tenant.createAthensTenant(id, createDomain(domainName), new Property("app1Property"), - propertyId == null ? Optional.empty() : Optional.of(new PropertyId(propertyId.toString()))); - controller().tenants().createAthenzTenant(tenant, TestIdentities.userNToken); - assertNotNull(controller().tenants().tenant(id)); - return id; + public TenantName createTenant(String tenantName, String domainName, Long propertyId) { + TenantName name = TenantName.from(tenantName); + Optional<Tenant> existing = controller().tenants().tenant(name); + if (existing.isPresent()) return name; + AthenzTenant tenant = AthenzTenant.create(name, createDomain(domainName), new Property("app1Property"), + Optional.ofNullable(propertyId) + .map(Object::toString) + .map(PropertyId::new)); + controller().tenants().create(tenant, TestIdentities.userNToken); + assertNotNull(controller().tenants().tenant(name)); + return name; } - public Application createApplication(TenantId tenant, String applicationName, String instanceName, long projectId) { - ApplicationId applicationId = ApplicationId.from(tenant.id(), applicationName, instanceName); + public Application createApplication(TenantName tenant, String applicationName, String instanceName, long projectId) { + ApplicationId applicationId = ApplicationId.from(tenant.value(), applicationName, instanceName); controller().applications().createApplication(applicationId, Optional.of(TestIdentities.userNToken)); controller().applications().lockOrThrow(applicationId, lockedApplication -> controller().applications().store(lockedApplication.withProjectId(projectId))); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java index c4e9240dfcb..27a1da941a0 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.controller.deployment; import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Environment; +import com.yahoo.config.provision.TenantName; import com.yahoo.test.ManualClock; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.ApplicationController; @@ -11,7 +12,6 @@ import com.yahoo.vespa.hosted.controller.ArtifactRepositoryMock; import com.yahoo.vespa.hosted.controller.ConfigServerClientMock; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.ControllerTester; -import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.api.integration.BuildService; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; import com.yahoo.vespa.hosted.controller.application.Change; @@ -109,7 +109,7 @@ public class DeploymentTester { } public Application createApplication(String applicationName, String tenantName, long projectId, Long propertyId) { - TenantId tenant = tester.createTenant(tenantName, UUID.randomUUID().toString(), propertyId); + TenantName tenant = tester.createTenant(tenantName, UUID.randomUUID().toString(), propertyId); return tester.createApplication(tenant, applicationName, "default", projectId); } @@ -122,13 +122,13 @@ public class DeploymentTester { /** Simulate the full lifecycle of an application deployment as declared in given application package */ public Application createAndDeploy(String applicationName, int projectId, ApplicationPackage applicationPackage) { - TenantId tenantId = tester.createTenant("tenant1", "domain1", 1L); - return createAndDeploy(tenantId, applicationName, projectId, applicationPackage); + TenantName tenant = tester.createTenant("tenant1", "domain1", 1L); + return createAndDeploy(tenant, applicationName, projectId, applicationPackage); } /** Simulate the full lifecycle of an application deployment as declared in given application package */ - public Application createAndDeploy(TenantId tenantId, String applicationName, int projectId, ApplicationPackage applicationPackage) { - Application application = tester.createApplication(tenantId, applicationName, "default", projectId); + public Application createAndDeploy(TenantName tenant, String applicationName, int projectId, ApplicationPackage applicationPackage) { + Application application = tester.createApplication(tenant, applicationName, "default", projectId); deployCompletely(application, applicationPackage); return applications().require(application.id()); } @@ -139,8 +139,8 @@ public class DeploymentTester { } /** Simulate the full lifecycle of an application deployment to prod.us-west-1 with the given upgrade policy */ - public Application createAndDeploy(TenantId tenantId, String applicationName, int projectId, String upgradePolicy) { - return createAndDeploy(tenantId, applicationName, projectId, applicationPackage(upgradePolicy)); + public Application createAndDeploy(TenantName tenant, String applicationName, int projectId, String upgradePolicy) { + return createAndDeploy(tenant, applicationName, projectId, applicationPackage(upgradePolicy)); } /** Complete an ongoing deployment */ diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java index dbe6c13bc68..e17fbc912ca 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java @@ -3,12 +3,11 @@ package com.yahoo.vespa.hosted.controller.deployment; import com.yahoo.component.Version; import com.yahoo.config.provision.Environment; -import com.yahoo.config.provision.SystemName; +import com.yahoo.config.provision.TenantName; import com.yahoo.test.ManualClock; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.ControllerTester; import com.yahoo.vespa.hosted.controller.LockedApplication; -import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.api.integration.BuildService; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; @@ -86,7 +85,7 @@ public class DeploymentTriggerTest { public void deploymentSpecDecidesTriggerOrder() { DeploymentTester tester = new DeploymentTester(); DeploymentQueue deploymentQueue = tester.deploymentQueue(); - TenantId tenant = tester.controllerTester().createTenant("tenant1", "domain1", 1L); + TenantName tenant = tester.controllerTester().createTenant("tenant1", "domain1", 1L); Application application = tester.controllerTester().createApplication(tenant, "app1", "default", 1L); ApplicationPackage applicationPackage = new ApplicationPackageBuilder() .environment(Environment.prod) @@ -245,7 +244,7 @@ public class DeploymentTriggerTest { public void testSuccessfulDeploymentApplicationPackageChanged() { DeploymentTester tester = new DeploymentTester(); DeploymentQueue deploymentQueue = tester.deploymentQueue(); - TenantId tenant = tester.controllerTester().createTenant("tenant1", "domain1", 1L); + TenantName tenant = tester.controllerTester().createTenant("tenant1", "domain1", 1L); Application application = tester.controllerTester().createApplication(tenant, "app1", "default", 1L); ApplicationPackage previousApplicationPackage = new ApplicationPackageBuilder() .environment(Environment.prod) @@ -350,7 +349,7 @@ public class DeploymentTriggerTest { public void testHandleMultipleNotificationsFromLastJob() { DeploymentTester tester = new DeploymentTester(); DeploymentQueue deploymentQueue = tester.deploymentQueue(); - TenantId tenant = tester.controllerTester().createTenant("tenant1", "domain1", 1L); + TenantName tenant = tester.controllerTester().createTenant("tenant1", "domain1", 1L); Application application = tester.controllerTester().createApplication(tenant, "app1", "default", 1L); ApplicationPackage applicationPackage = new ApplicationPackageBuilder() .environment(Environment.prod) diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java index 0309eaf7d25..b5941c441e2 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java @@ -2,12 +2,13 @@ package com.yahoo.vespa.hosted.controller.maintenance; import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.TenantName; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; -import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId; import com.yahoo.vespa.hosted.controller.api.integration.organization.OwnershipIssues; import com.yahoo.vespa.hosted.controller.api.integration.organization.User; +import com.yahoo.vespa.hosted.controller.tenant.UserTenant; import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester; import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb; import org.junit.Before; @@ -39,13 +40,13 @@ public class ApplicationOwnershipConfirmerTest { @Test public void testConfirmation() { - TenantId property = tester.controllerTester().createTenant("property", "domain", 1L); + TenantName property = tester.controllerTester().createTenant("property", "domain", 1L); tester.createAndDeploy(property, "application", 1, "default"); Supplier<Application> propertyApp = () -> tester.controller().applications().require(ApplicationId.from("property", "application", "default")); - TenantId user = new TenantId("by-user"); - tester.controller().tenants().createUserTenant("user"); - tester.createAndDeploy(user, "application", 2, "default"); + UserTenant user = UserTenant.create("by-user"); + tester.controller().tenants().create(user); + tester.createAndDeploy(user.name(), "application", 2, "default"); Supplier<Application> userApp = () -> tester.controller().applications().require(ApplicationId.from("by-user", "application", "default")); assertFalse("No issue is initially stored for a new application.", propertyApp.get().ownershipIssueId().isPresent()); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java index 8e60e63e873..f56642ad538 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java @@ -4,13 +4,13 @@ package com.yahoo.vespa.hosted.controller.restapi; import com.yahoo.application.container.JDisc; import com.yahoo.application.container.handler.Request; import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.TenantName; import com.yahoo.vespa.athenz.api.AthenzDomain; import com.yahoo.vespa.athenz.utils.AthenzIdentities; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.ArtifactRepositoryMock; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.TestIdentities; -import com.yahoo.vespa.hosted.controller.api.Tenant; import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions; import com.yahoo.vespa.hosted.controller.api.application.v4.model.GitRevision; import com.yahoo.vespa.hosted.controller.api.application.v4.model.ScrewdriverBuildJob; @@ -20,11 +20,11 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.GitRepository; 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.identifiers.ScrewdriverId; -import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction; import com.yahoo.vespa.hosted.controller.api.integration.athenz.HostedAthenzIdentities; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; +import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs; import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock; import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzDbMock; @@ -75,10 +75,10 @@ public class ContainerControllerTester { public Application createApplication(String athensDomain, String tenant, String application) { AthenzDomain domain1 = addTenantAthenzDomain(athensDomain, "mytenant"); - controller().tenants().createAthenzTenant(Tenant.createAthensTenant(new TenantId(tenant), domain1, - new Property("property1"), - Optional.of(new PropertyId("1234"))), - TestIdentities.userNToken); + controller().tenants().create(AthenzTenant.create(TenantName.from(tenant), domain1, + new Property("property1"), + Optional.of(new PropertyId("1234"))), + TestIdentities.userNToken); ApplicationId app = ApplicationId.from(tenant, application, "default"); return controller().applications().createApplication(app, Optional.of(TestIdentities.userNToken)); } 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 b9eef2069d9..2dacadaaa3e 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 @@ -9,6 +9,7 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; +import com.yahoo.config.provision.TenantName; import com.yahoo.slime.Cursor; import com.yahoo.slime.Slime; import com.yahoo.vespa.athenz.api.AthenzDomain; @@ -18,6 +19,7 @@ import com.yahoo.vespa.athenz.api.NToken; import com.yahoo.vespa.config.SlimeUtils; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.ConfigServerClientMock; +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.identifiers.ScrewdriverId; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; @@ -43,6 +45,7 @@ import com.yahoo.vespa.hosted.controller.deployment.BuildJob; import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester; import com.yahoo.vespa.hosted.controller.restapi.ContainerTester; import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest; +import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant; import org.apache.http.HttpEntity; import org.apache.http.entity.ContentType; import org.apache.http.entity.mime.MultipartEntityBuilder; @@ -331,9 +334,9 @@ public class ApplicationApiTest extends ControllerContainerTest { // PUT (create) the authenticated user byte[] data = new byte[0]; - tester.assertResponse(request("/application/v4/user?user=newuser&domain=by", PUT) + tester.assertResponse(request("/application/v4/user?user=new_user&domain=by", PUT) .data(data) - .userIdentity(new UserId("newuser")), + .userIdentity(new UserId("new_user")), // Normalized to by-new-user by API new File("create-user-response.json")); // OPTIONS return 200 OK tester.assertResponse(request("/application/v4/", Request.Method.OPTIONS), @@ -551,6 +554,22 @@ public class ApplicationApiTest extends ControllerContainerTest { "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Tenant 'tenant1' already exists\"}", 400); + // POST (add) a Athenz tenant with underscore in name + tester.assertResponse(request("/application/v4/tenant/my_tenant_2", POST) + .userIdentity(USER_ID) + .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") + .nToken(N_TOKEN), + "{\"error-code\":\"BAD_REQUEST\",\"message\":\"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.\"}", + 400); + + // POST (add) a Athenz tenant with by- prefix + tester.assertResponse(request("/application/v4/tenant/by-tenant2", POST) + .userIdentity(USER_ID) + .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") + .nToken(N_TOKEN), + "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Athenz tenant name cannot have prefix 'by-'\"}", + 400); + // POST (create) an (empty) application tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST) .userIdentity(USER_ID) @@ -596,7 +615,8 @@ public class ApplicationApiTest extends ControllerContainerTest { // DELETE tenant which has an application tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE) - .userIdentity(USER_ID), + .userIdentity(USER_ID) + .nToken(N_TOKEN), "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Could not delete tenant 'tenant1': This tenant has active applications\"}", 400); @@ -626,6 +646,18 @@ public class ApplicationApiTest extends ControllerContainerTest { .userIdentity(USER_ID), "{\"error-code\":\"INTERNAL_SERVER_ERROR\",\"message\":\"Unable to promote Chef environments for application\"}", 500); + + // Create legancy tenant name containing underscores + tester.controller().tenants().create(new AthenzTenant(TenantName.from("my_tenant"), ATHENZ_TENANT_DOMAIN, + new Property("property1"), Optional.empty()), + N_TOKEN); + // POST (add) a Athenz tenant with dashes duplicates existing one with underscores + tester.assertResponse(request("/application/v4/tenant/my-tenant", POST) + .userIdentity(USER_ID) + .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") + .nToken(N_TOKEN), + "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Tenant 'my-tenant' already exists\"}", + 400); } @Test diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/create-user-response.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/create-user-response.json index 709548e87a7..a6130122650 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/create-user-response.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/create-user-response.json @@ -1,3 +1,3 @@ { - "message":"Created user 'newuser'" -}
\ No newline at end of file + "message":"Created user 'by-new-user'" +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/tenant-without-applications-underscore.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/tenant-without-applications-underscore.json new file mode 100644 index 00000000000..243d5fb20c6 --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/tenant-without-applications-underscore.json @@ -0,0 +1,9 @@ +{ + "tenant": "my_tenant_2", + "type": "ATHENS", + "athensDomain": "domain1", + "property": "property1", + "applications": [ + + ] +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java index 626d480257e..8d511b204e4 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java @@ -2,6 +2,7 @@ package com.yahoo.vespa.hosted.controller.restapi.filter; import com.fasterxml.jackson.databind.ObjectMapper; +import com.yahoo.config.provision.TenantName; import com.yahoo.jdisc.http.HttpRequest.Method; import com.yahoo.jdisc.http.filter.DiscFilterRequest; import com.yahoo.vespa.athenz.api.AthenzDomain; @@ -12,7 +13,6 @@ import com.yahoo.vespa.athenz.api.AthenzUser; import com.yahoo.vespa.hosted.controller.ControllerTester; import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId; import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId; -import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction; import com.yahoo.vespa.hosted.controller.api.integration.athenz.HostedAthenzIdentities; import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock; @@ -48,7 +48,7 @@ public class ControllerAuthorizationFilterTest { private static final AthenzDomain TENANT_DOMAIN = new AthenzDomain("tenantdomain"); private static final AthenzService TENANT_ADMIN = new AthenzService(TENANT_DOMAIN, "adminservice"); private static final AthenzService TENANT_PIPELINE = HostedAthenzIdentities.from(new ScrewdriverId("12345")); - private static final TenantId TENANT = new TenantId("mytenant"); + private static final TenantName TENANT = TenantName.from("mytenant"); private static final ApplicationId APPLICATION = new ApplicationId("myapp"); @Test @@ -80,7 +80,7 @@ public class ControllerAuthorizationFilterTest { public void only_hosted_operator_or_tenant_admin_can_access_tenant_admin_apis() { ControllerTester controllerTester = new ControllerTester(); controllerTester.athenzDb().hostedOperators.add(HOSTED_OPERATOR); - controllerTester.createTenant(TENANT.id(), TENANT_DOMAIN.getName(), null); + controllerTester.createTenant(TENANT.value(), TENANT_DOMAIN.getName(), null); controllerTester.athenzDb().domains.get(TENANT_DOMAIN).admins.add(TENANT_ADMIN); ControllerAuthorizationFilter filter = createFilter(controllerTester); @@ -100,7 +100,7 @@ public class ControllerAuthorizationFilterTest { public void only_hosted_operator_and_screwdriver_project_with_deploy_role_can_access_tenant_pipeline_apis() { ControllerTester controllerTester = new ControllerTester(); controllerTester.athenzDb().hostedOperators.add(HOSTED_OPERATOR); - controllerTester.createTenant(TENANT.id(), TENANT_DOMAIN.getName(), null); + controllerTester.createTenant(TENANT.value(), TENANT_DOMAIN.getName(), null); controllerTester.createApplication(TENANT, APPLICATION.id(), "default", 12345); AthenzDbMock.Domain domainMock = controllerTester.athenzDb().domains.get(TENANT_DOMAIN); domainMock.admins.add(TENANT_ADMIN); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java index 14f5d00ec88..8eee7c2d8c2 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java @@ -5,10 +5,10 @@ import com.google.common.collect.ImmutableSet; import com.yahoo.component.Version; import com.yahoo.component.Vtag; import com.yahoo.config.provision.Environment; +import com.yahoo.config.provision.TenantName; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.ControllerTester; -import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester; @@ -138,7 +138,7 @@ public class VersionStatusTest { Application ignored0 = tester.createApplication("ignored0", "tenant1", 1000, 1000L); // Pull request builds - tester.controllerTester().createApplication(new TenantId("tenant1"), + tester.controllerTester().createApplication(TenantName.from("tenant1"), "ignored1", "43", 1000); |