diff options
Diffstat (limited to 'controller-server')
21 files changed, 228 insertions, 259 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java index cb3f50d08c7..590b18f929d 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java @@ -7,7 +7,9 @@ import com.yahoo.vespa.curator.Lock; import com.yahoo.vespa.hosted.controller.api.identifiers.Property; import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant; -import com.yahoo.vespa.hosted.controller.tenant.Contact; +import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact; +import com.yahoo.vespa.hosted.controller.tenant.Tenant; +import com.yahoo.vespa.hosted.controller.tenant.UserTenant; import java.util.Objects; import java.util.Optional; @@ -22,10 +24,11 @@ public class LockedTenant { private final Lock lock; private final TenantName name; - private final AthenzDomain domain; - private final Property property; - private final Optional<PropertyId> propertyId; + private AthenzDomain domain; + private Property property; + private Optional<PropertyId> propertyId; private final Optional<Contact> contact; + private final boolean isAthenzTenant; /** * Should never be constructed directly. @@ -37,6 +40,10 @@ public class LockedTenant { this(lock, tenant.name(), tenant.domain(), tenant.property(), tenant.propertyId(), tenant.contact()); } + LockedTenant(UserTenant tenant, Lock lock) { + this(lock, tenant.name(), tenant.contact()); + } + private LockedTenant(Lock lock, TenantName name, AthenzDomain domain, Property property, Optional<PropertyId> propertyId, Optional<Contact> contact) { this.lock = Objects.requireNonNull(lock, "lock must be non-null"); @@ -45,11 +52,20 @@ public class LockedTenant { this.property = Objects.requireNonNull(property, "property must be non-null"); this.propertyId = Objects.requireNonNull(propertyId, "propertyId must be non-null"); this.contact = Objects.requireNonNull(contact, "contact must be non-null"); + this.isAthenzTenant = true; + } + + private LockedTenant(Lock lock, TenantName name, Optional<Contact> contact) { + this.lock = Objects.requireNonNull(lock, "lock must be non-null"); + this.name = Objects.requireNonNull(name, "name must be non-null"); + this.contact = Objects.requireNonNull(contact, "contact must be non-null"); + this.isAthenzTenant = false; } /** Returns a read-only copy of this */ - public AthenzTenant get() { - return new AthenzTenant(name, domain, property, propertyId, contact); + public Tenant get() { + if (isAthenzTenant) return new AthenzTenant(name, domain, property, propertyId, contact); + else return new UserTenant(name, contact); } public LockedTenant with(AthenzDomain domain) { @@ -65,7 +81,8 @@ public class LockedTenant { } public LockedTenant with(Contact contact) { - return new LockedTenant(lock, name, domain, property, propertyId, Optional.of(contact)); + if (isAthenzTenant) return new LockedTenant(lock, name, domain, property, propertyId, Optional.of(contact)); + return new LockedTenant(lock, name, Optional.of(contact)); } @Override 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 f0e13349fbf..78099fac34e 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 @@ -102,7 +102,11 @@ public class TenantController { */ public void lockIfPresent(TenantName name, Consumer<LockedTenant> action) { try (Lock lock = lock(name)) { - athenzTenant(name).map(tenant -> new LockedTenant(tenant, lock)).ifPresent(action); + tenant(name).map(tenant -> { + tenant = tenant instanceof AthenzTenant ? (AthenzTenant) tenant : (UserTenant) tenant; + if (tenant instanceof AthenzTenant) return new LockedTenant((AthenzTenant) tenant, lock); + else return new LockedTenant((UserTenant) tenant, lock); + }).ifPresent(action); } } @@ -173,7 +177,8 @@ public class TenantController { /** Update Athenz domain for tenant. Returns the updated tenant which must be explicitly stored */ public LockedTenant withDomain(LockedTenant tenant, AthenzDomain newDomain, OktaAccessToken token) { - AthenzDomain existingDomain = tenant.get().domain(); + AthenzTenant athenzTenant = (AthenzTenant) tenant.get(); + AthenzDomain existingDomain = athenzTenant.domain(); if (existingDomain.equals(newDomain)) return tenant; Optional<Tenant> existingTenantWithNewDomain = tenantIn(newDomain); if (existingTenantWithNewDomain.isPresent()) 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 219517cfd30..e260848d93a 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,7 +4,7 @@ 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.identifiers.PropertyId; +import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact; 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; @@ -14,7 +14,6 @@ import com.yahoo.vespa.hosted.controller.tenant.Tenant; import com.yahoo.yolean.Exceptions; import java.time.Duration; -import java.util.NoSuchElementException; import java.util.Optional; import java.util.logging.Level; @@ -52,9 +51,7 @@ public class ApplicationOwnershipConfirmer extends Maintainer { try { Tenant tenant = ownerOf(application.id()); Optional<IssueId> ourIssueId = application.ownershipIssueId(); - ourIssueId = tenant instanceof AthenzTenant - ? ownershipIssues.confirmOwnership(ourIssueId, application.id(), propertyIdFor((AthenzTenant) tenant)) - : ownershipIssues.confirmOwnership(ourIssueId, application.id(), userFor(tenant)); + ourIssueId = ownershipIssues.confirmOwnership(ourIssueId, application.id(), userFor(tenant), tenant.contact().orElseThrow(RuntimeException::new)); ourIssueId.ifPresent(issueId -> store(issueId, application.id())); } catch (RuntimeException e) { // Catch errors due to wrong data in the controller, or issues client timeout. @@ -69,12 +66,11 @@ public class ApplicationOwnershipConfirmer extends Maintainer { for (Application application : controller().applications().asList()) application.ownershipIssueId().ifPresent(issueId -> { try { - 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); + Optional<Contact> contact = Optional.of(application.id()) + .map(this::ownerOf) + .filter(t -> t instanceof AthenzTenant) + .flatMap(Tenant::contact); + ownershipIssues.ensureResponse(issueId, contact); } catch (RuntimeException e) { log.log(Level.INFO, "Exception caught when attempting to escalate issue with id '" + issueId + "': " + Exceptions.toMessageString(e)); @@ -91,12 +87,6 @@ public class ApplicationOwnershipConfirmer extends Maintainer { return User.from(tenant.name().value().replaceFirst(Tenant.userPrefix, "")); } - 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) { controller().applications().lockIfPresent(applicationId, application -> controller().applications().store(application.withOwnershipIssueId(issueId))); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java index bf5743e2d3c..8bffa455b7e 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java @@ -1,23 +1,21 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.maintenance; +import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; +import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact; +import com.yahoo.vespa.hosted.controller.api.integration.organization.ContactRetriever; import com.yahoo.config.provision.SystemName; import com.yahoo.log.LogLevel; import com.yahoo.vespa.hosted.controller.Controller; -import com.yahoo.vespa.hosted.controller.api.integration.organization.Organization; -import com.yahoo.vespa.hosted.controller.api.integration.organization.User; import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant; -import com.yahoo.vespa.hosted.controller.tenant.Contact; import com.yahoo.vespa.hosted.controller.tenant.Tenant; import com.yahoo.yolean.Exceptions; import java.time.Duration; import java.util.EnumSet; -import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.logging.Logger; -import java.util.stream.Collectors; /** * Periodically fetch and store contact information for tenants. @@ -28,23 +26,20 @@ public class ContactInformationMaintainer extends Maintainer { private static final Logger log = Logger.getLogger(ContactInformationMaintainer.class.getName()); - private final Organization organization; + private final ContactRetriever contactRetriever; - public ContactInformationMaintainer(Controller controller, Duration interval, JobControl jobControl, Organization organization) { + public ContactInformationMaintainer(Controller controller, Duration interval, JobControl jobControl, ContactRetriever contactRetriever) { super(controller, interval, jobControl, null, EnumSet.of(SystemName.cd, SystemName.main)); - this.organization = Objects.requireNonNull(organization, "organization must be non-null"); + this.contactRetriever = Objects.requireNonNull(contactRetriever, "organization must be non-null"); } @Override protected void maintain() { - for (Tenant t : controller().tenants().asList()) { - if (!(t instanceof AthenzTenant)) continue; // No contact information for non-Athenz tenants - AthenzTenant tenant = (AthenzTenant) t; - if (!tenant.propertyId().isPresent()) continue; // Can only update contact information if property ID is known - try { - findContact(tenant).ifPresent(contact -> { - controller().tenants().lockIfPresent(t.name(), lockedTenant -> controller().tenants().store(lockedTenant.with(contact))); - }); + for (Tenant tenant : controller().tenants().asList()) { + try{ + Optional<PropertyId> tenantPropertyId = tenant instanceof AthenzTenant ? ((AthenzTenant) tenant).propertyId() : Optional.empty(); + Contact contact = contactRetriever.getContact(tenantPropertyId); + controller().tenants().lockIfPresent(tenant.name(), lockedTenant -> controller().tenants().store(lockedTenant.with(contact))); } catch (Exception e) { log.log(LogLevel.WARNING, "Failed to update contact information for " + tenant + ": " + Exceptions.toMessageString(e) + ". Retrying in " + @@ -53,21 +48,5 @@ public class ContactInformationMaintainer extends Maintainer { } } - /** Find contact information for given tenant */ - private Optional<Contact> findContact(AthenzTenant tenant) { - if (!tenant.propertyId().isPresent()) { - return Optional.empty(); - } - List<List<String>> persons = organization.contactsFor(tenant.propertyId().get()) - .stream() - .map(personList -> personList.stream() - .map(User::displayName) - .collect(Collectors.toList())) - .collect(Collectors.toList()); - return Optional.of(new Contact(organization.contactsUri(tenant.propertyId().get()), - organization.propertyUri(tenant.propertyId().get()), - organization.issueCreationUri(tenant.propertyId().get()), - persons)); - } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java index 7b17f38bd78..f6978ef70ac 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java @@ -4,12 +4,12 @@ package com.yahoo.vespa.hosted.controller.maintenance; import com.yahoo.component.AbstractComponent; import com.yahoo.jdisc.Metric; import com.yahoo.vespa.hosted.controller.Controller; +import com.yahoo.vespa.hosted.controller.api.integration.organization.ContactRetriever; import com.yahoo.vespa.hosted.controller.authority.config.ApiAuthorityConfig; import com.yahoo.vespa.hosted.controller.api.integration.chef.Chef; import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService; import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeRepositoryClientInterface; import com.yahoo.vespa.hosted.controller.api.integration.organization.DeploymentIssues; -import com.yahoo.vespa.hosted.controller.api.integration.organization.Organization; import com.yahoo.vespa.hosted.controller.api.integration.organization.OwnershipIssues; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId; import com.yahoo.vespa.hosted.controller.maintenance.config.MaintainerConfig; @@ -54,7 +54,7 @@ public class ControllerMaintenance extends AbstractComponent { JobControl jobControl, Metric metric, Chef chefClient, DeploymentIssues deploymentIssues, OwnershipIssues ownershipIssues, NameService nameService, NodeRepositoryClientInterface nodeRepositoryClient, - Organization organization) { + ContactRetriever contactRetriever) { Duration maintenanceInterval = Duration.ofMinutes(maintainerConfig.intervalMinutes()); this.jobControl = jobControl; deploymentExpirer = new DeploymentExpirer(controller, maintenanceInterval, jobControl); @@ -73,7 +73,7 @@ public class ControllerMaintenance extends AbstractComponent { jobRunner = new JobRunner(controller, Duration.ofMinutes(2), jobControl); osUpgraders = osUpgraders(controller, jobControl); osVersionStatusUpdater = new OsVersionStatusUpdater(controller, maintenanceInterval, jobControl); - contactInformationMaintainer = new ContactInformationMaintainer(controller, Duration.ofHours(12), jobControl, organization); + contactInformationMaintainer = new ContactInformationMaintainer(controller, Duration.ofHours(12), jobControl, contactRetriever); } public Upgrader upgrader() { return upgrader; } 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 1745e013a40..13733b32d86 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 @@ -7,6 +7,7 @@ import com.yahoo.config.provision.SystemName; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; +import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact; import com.yahoo.vespa.hosted.controller.api.integration.organization.DeploymentIssues; import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId; import com.yahoo.vespa.hosted.controller.api.integration.organization.User; @@ -18,7 +19,6 @@ import com.yahoo.yolean.Exceptions; import java.time.Duration; import java.util.Collection; import java.util.List; -import java.util.NoSuchElementException; import java.util.Optional; import java.util.Set; import java.util.logging.Level; @@ -113,20 +113,13 @@ public class DeploymentIssueReporter extends Maintainer { return User.from(tenant.name().value().replaceFirst(Tenant.userPrefix, "")); } - 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. */ private void fileDeploymentIssueFor(ApplicationId applicationId) { try { Tenant tenant = ownerOf(applicationId); + User asignee = userFor(tenant); Optional<IssueId> ourIssueId = controller().applications().require(applicationId).deploymentJobs().issueId(); - IssueId issueId = tenant instanceof AthenzTenant - ? deploymentIssues.fileUnlessOpen(ourIssueId, applicationId, propertyIdFor((AthenzTenant) tenant)) - : deploymentIssues.fileUnlessOpen(ourIssueId, applicationId, userFor(tenant)); + IssueId issueId = deploymentIssues.fileUnlessOpen(ourIssueId, applicationId, asignee, tenant.contact().get()); store(applicationId, issueId); } catch (RuntimeException e) { // Catch errors due to wrong data in the controller, or issues client timeout. @@ -138,12 +131,11 @@ public class DeploymentIssueReporter extends Maintainer { private void escalateInactiveDeploymentIssues(Collection<Application> applications) { applications.forEach(application -> application.deploymentJobs().issueId().ifPresent(issueId -> { try { - 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); + AthenzTenant tenant = Optional.of(application.id()) + .map(this::ownerOf) + .filter(t -> t instanceof AthenzTenant) + .map(AthenzTenant.class::cast).orElseThrow(RuntimeException::new); + deploymentIssues.escalateIfInactive(issueId, maxInactivity, tenant.contact()); } catch (RuntimeException e) { log.log(Level.INFO, "Exception caught when attempting to escalate issue with id '" + issueId + "': " + Exceptions.toMessageString(e)); @@ -155,5 +147,4 @@ public class DeploymentIssueReporter extends Maintainer { controller().applications().lockIfPresent(id, application -> controller().applications().store(application.withDeploymentIssueId(issueId))); } - } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java index 631aaa5a909..9c52d7a2244 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 @@ -280,7 +280,7 @@ public class CuratorDb { // -------------- Tenant -------------------------------------------------- - public void writeTenant(UserTenant tenant) { + public void writeTenant(Tenant tenant) { curator.set(tenantPath(tenant.name()), asJson(tenantSerializer.toSlime(tenant))); } @@ -288,10 +288,6 @@ public class CuratorDb { return readSlime(tenantPath(name)).map(tenantSerializer::userTenantFrom); } - public void writeTenant(AthenzTenant tenant) { - curator.set(tenantPath(tenant.name()), asJson(tenantSerializer.toSlime(tenant))); - } - public Optional<AthenzTenant> readAthenzTenant(TenantName name) { return readSlime(tenantPath(name)).map(tenantSerializer::athenzTenantFrom); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java index 28400b85306..245cb0f4dae 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java @@ -12,7 +12,8 @@ import com.yahoo.vespa.config.SlimeUtils; import com.yahoo.vespa.hosted.controller.api.identifiers.Property; import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant; -import com.yahoo.vespa.hosted.controller.tenant.Contact; +import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact; +import com.yahoo.vespa.hosted.controller.tenant.Tenant; import com.yahoo.vespa.hosted.controller.tenant.UserTenant; import java.net.URI; @@ -37,8 +38,15 @@ public class TenantSerializer { private static final String issueTrackerUrlField = "issueTrackerUrl"; private static final String personsField = "persons"; private static final String personField = "person"; + private static final String queueField = "queue"; + private static final String componentField = "component"; - public Slime toSlime(AthenzTenant tenant) { + public Slime toSlime(Tenant tenant) { + if (tenant instanceof AthenzTenant) return toSlime((AthenzTenant) tenant); + return toSlime((UserTenant) tenant); + } + + private Slime toSlime(AthenzTenant tenant) { Slime slime = new Slime(); Cursor root = slime.setObject(); root.setString(nameField, tenant.name().value()); @@ -46,26 +54,20 @@ public class TenantSerializer { root.setString(propertyField, tenant.property().id()); tenant.propertyId().ifPresent(propertyId -> root.setString(propertyIdField, propertyId.id())); tenant.contact().ifPresent(contact -> { - Cursor contactObject = root.setObject(contactField); - contactObject.setString(contactUrlField, contact.url().toString()); - contactObject.setString(propertyUrlField, contact.propertyUrl().toString()); - contactObject.setString(issueTrackerUrlField, contact.issueTrackerUrl().toString()); - Cursor personsArray = contactObject.setArray(personsField); - contact.persons().forEach(personList -> { - Cursor personArray = personsArray.addArray(); - personList.forEach(person -> { - Cursor personObject = personArray.addObject(); - personObject.setString(personField, person); - }); - }); + Cursor contactCursor = root.setObject(contactField); + writeContact(contact, contactCursor); }); return slime; } - public Slime toSlime(UserTenant tenant) { + private Slime toSlime(UserTenant tenant) { Slime slime = new Slime(); Cursor root = slime.setObject(); root.setString(nameField, tenant.name().value()); + tenant.contact().ifPresent(contact -> { + Cursor contactCursor = root.setObject(contactField); + writeContact(contact, contactCursor); + }); return slime; } @@ -82,17 +84,42 @@ public class TenantSerializer { public UserTenant userTenantFrom(Slime slime) { Inspector root = slime.get(); TenantName name = TenantName.from(root.field(nameField).asString()); - return new UserTenant(name); + Optional<Contact> contact = contactFrom(root.field(contactField)); + return new UserTenant(name, contact); } private Optional<Contact> contactFrom(Inspector object) { if (!object.valid()) { return Optional.empty(); } - return Optional.of(new Contact(URI.create(object.field(contactUrlField).asString()), - URI.create(object.field(propertyUrlField).asString()), - URI.create(object.field(issueTrackerUrlField).asString()), - personsFrom(object.field(personsField)))); + URI contactUrl = URI.create(object.field(contactUrlField).asString()); + URI propertyUrl = URI.create(object.field(propertyUrlField).asString()); + URI issueTrackerUrl = URI.create(object.field(issueTrackerUrlField).asString()); + List<List<String>> persons = personsFrom(object.field(personsField)); + String queue = object.field(queueField).asString(); + Optional<String> component = object.field(componentField).valid() ? Optional.of(object.field(componentField).asString()) : Optional.empty(); + return Optional.of(new Contact(contactUrl, + propertyUrl, + issueTrackerUrl, + persons, + queue, + component)); + } + + private void writeContact(Contact contact, Cursor contactCursor) { + contactCursor.setString(contactUrlField, contact.url().toString()); + contactCursor.setString(propertyUrlField, contact.propertyUrl().toString()); + contactCursor.setString(issueTrackerUrlField, contact.issueTrackerUrl().toString()); + Cursor personsArray = contactCursor.setArray(personsField); + contact.persons().forEach(personList -> { + Cursor personArray = personsArray.addArray(); + personList.forEach(person -> { + Cursor personObject = personArray.addObject(); + personObject.setString(personField, person); + }); + }); + contactCursor.setString(queueField, contact.queue()); + contact.component().ifPresent(component -> contactCursor.setString(componentField, component)); } private List<List<String>> personsFrom(Inspector array) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/contactinfo/ContactInfoHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/contactinfo/ContactInfoHandler.java index 7651381b810..cf9db8fd992 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/contactinfo/ContactInfoHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/contactinfo/ContactInfoHandler.java @@ -17,7 +17,7 @@ import com.yahoo.vespa.hosted.controller.restapi.ErrorResponse; import com.yahoo.vespa.hosted.controller.restapi.SlimeJsonResponse; import com.yahoo.vespa.hosted.controller.restapi.StringResponse; import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant; -import com.yahoo.vespa.hosted.controller.tenant.Contact; +import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact; import com.yahoo.yolean.Exceptions; import java.io.IOException; @@ -117,6 +117,8 @@ public class ContactInfoHandler extends LoggingRequestHandler { sublist.addString(person); } } + cursor.setString("queue", contact.queue()); + contact.component().ifPresent(component -> cursor.setString("component", component)); return slime; } @@ -125,6 +127,8 @@ public class ContactInfoHandler extends LoggingRequestHandler { URI propertyUrl = URI.create(inspector.field("propertyUrl").asString()); URI url = URI.create(inspector.field("url").asString()); URI issueTrackerUrl = URI.create(inspector.field("issueTrackerUrl").asString()); + String queue = inspector.field("queue").asString(); + Optional<String> component = inspector.field("component").valid() ? Optional.of(inspector.field("component").asString()) : Optional.empty(); Inspector personInspector = inspector.field("persons"); List<List<String>> personList = new ArrayList<>(); personInspector.traverse((ArrayTraverser) (index, entry) -> { @@ -134,7 +138,7 @@ public class ContactInfoHandler extends LoggingRequestHandler { }); personList.add(subList); }); - return new Contact(url, propertyUrl, issueTrackerUrl, personList); + return new Contact(url, propertyUrl, issueTrackerUrl, personList, queue, component); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java index 8cbb4e06aca..3879e7f29ca 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java @@ -5,6 +5,7 @@ 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 com.yahoo.vespa.hosted.controller.api.integration.organization.Contact; import java.util.Objects; import java.util.Optional; @@ -19,7 +20,7 @@ public class AthenzTenant extends Tenant { private final AthenzDomain domain; private final Property property; private final Optional<PropertyId> propertyId; - private final Optional<Contact> contact; + /** * This should only be used by serialization. @@ -27,11 +28,10 @@ public class AthenzTenant extends Tenant { * */ public AthenzTenant(TenantName name, AthenzDomain domain, Property property, Optional<PropertyId> propertyId, Optional<Contact> contact) { - super(name); + super(name, Objects.requireNonNull(contact, "contact must be non-null")); this.domain = Objects.requireNonNull(domain, "domain must be non-null"); this.property = Objects.requireNonNull(property, "property must be non-null"); this.propertyId = Objects.requireNonNull(propertyId, "propertyId must be non-null"); - this.contact = Objects.requireNonNull(contact, "contact must be non-null"); } /** Property name of this tenant */ @@ -44,11 +44,6 @@ public class AthenzTenant extends Tenant { return propertyId; } - /** Contact information for this, if any */ - public Optional<Contact> contact() { - return contact; - } - /** Athenz domain of this tenant */ public AthenzDomain domain() { return domain; @@ -70,6 +65,11 @@ public class AthenzTenant extends Tenant { return new AthenzTenant(requireName(requireNoPrefix(name)), domain, property, propertyId, Optional.empty()); } + public static AthenzTenant create(TenantName name, AthenzDomain domain, Property property, + Optional<PropertyId> propertyId, Optional<Contact> contact) { + return new AthenzTenant(requireName(requireNoPrefix(name)), domain, property, propertyId, contact); + } + private static TenantName requireNoPrefix(TenantName name) { if (name.value().startsWith(Tenant.userPrefix)) { throw new IllegalArgumentException("Athenz tenant name cannot have prefix '" + Tenant.userPrefix + "'"); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Contact.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Contact.java deleted file mode 100644 index e13b0f982da..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Contact.java +++ /dev/null @@ -1,75 +0,0 @@ -// 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.google.common.collect.ImmutableList; - -import java.net.URI; -import java.util.List; -import java.util.Objects; - -/** - * Contact information for a tenant. - * - * @author mpolden - */ -public class Contact { - - private final URI url; - private final URI propertyUrl; - private final URI issueTrackerUrl; - private final List<List<String>> persons; - - public Contact(URI url, URI propertyUrl, URI issueTrackerUrl, List<List<String>> persons) { - this.propertyUrl = Objects.requireNonNull(propertyUrl, "propertyUrl must be non-null"); - this.url = Objects.requireNonNull(url, "url must be non-null"); - this.issueTrackerUrl = Objects.requireNonNull(issueTrackerUrl, "issueTrackerUrl must be non-null"); - this.persons = ImmutableList.copyOf(Objects.requireNonNull(persons, "persons must be non-null")); - } - - /** URL to this */ - public URI url() { - return url; - } - - /** URL to information about this property */ - public URI propertyUrl() { - return propertyUrl; - } - - /** URL to this contacts's issue tracker */ - public URI issueTrackerUrl() { - return issueTrackerUrl; - } - - /** Nested list of persons representing this. First level represents that person's rank in the corporate dystopia. */ - public List<List<String>> persons() { - return persons; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Contact contact = (Contact) o; - return Objects.equals(url, contact.url) && - Objects.equals(propertyUrl, contact.propertyUrl) && - Objects.equals(issueTrackerUrl, contact.issueTrackerUrl) && - Objects.equals(persons, contact.persons); - } - - @Override - public int hashCode() { - return Objects.hash(url, propertyUrl, issueTrackerUrl, persons); - } - - @Override - public String toString() { - return "Contact{" + - "url=" + url + - ", propertyUrl=" + propertyUrl + - ", issueTrackerUrl=" + issueTrackerUrl + - ", persons=" + persons + - '}'; - } - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java index aac3fa20d11..98950ca2632 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java @@ -3,8 +3,11 @@ package com.yahoo.vespa.hosted.controller.tenant; import com.yahoo.config.provision.TenantName; +import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact; +import javax.swing.text.html.Option; import java.util.Objects; +import java.util.Optional; /** * A tenant in hosted Vespa. @@ -17,15 +20,31 @@ public abstract class Tenant { private final TenantName name; - Tenant(TenantName name) { + private Optional<Contact> contact; + + Tenant(TenantName name, Optional<Contact> contact) { this.name = name; + this.contact = contact; } + /*Tenant(TenantName name) { + this(name, Optional.empty()); + }*/ + /** Name of this tenant */ public TenantName name() { return name; } + public Optional<Contact> contact() { + return contact; + } + + public Tenant withContact(Optional<Contact> contact) { + this.contact = contact; + return this; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/UserTenant.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/UserTenant.java index e110600639b..47e5580fbe4 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/UserTenant.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/UserTenant.java @@ -2,6 +2,9 @@ package com.yahoo.vespa.hosted.controller.tenant; import com.yahoo.config.provision.TenantName; +import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact; + +import java.util.Optional; /** * Represents an user tenant in hosted Vespa. @@ -14,8 +17,12 @@ public class UserTenant extends Tenant { * This should only be used by serialization. * Use {@link #create(String)}. * */ + public UserTenant(TenantName name, Optional<Contact> contact) { + super(name, contact); + } + public UserTenant(TenantName name) { - super(name); + super(name, Optional.empty()); } /** Returns true if this is the tenant for the given user name */ @@ -34,6 +41,11 @@ public class UserTenant extends Tenant { return new UserTenant(requireName(requireUser(name))); } + public static UserTenant create(String username, Optional<Contact> contact) { + TenantName name = TenantName.from(username); + return new UserTenant(requireName(requireUser(name)), contact); + } + /** Normalize given username. E.g. foo_bar becomes by-foo-bar */ public static String normalizeUser(String username) { int offset = 0; 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 8994c68acf3..639511959df 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 @@ -23,7 +23,9 @@ import com.yahoo.vespa.hosted.controller.api.integration.dns.MemoryNameService; import com.yahoo.vespa.hosted.controller.api.integration.entity.EntityService; import com.yahoo.vespa.hosted.controller.api.integration.entity.MemoryEntityService; import com.yahoo.vespa.hosted.controller.api.integration.github.GitHubMock; -import com.yahoo.vespa.hosted.controller.api.integration.organization.MockOrganization; +import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact; +import com.yahoo.vespa.hosted.controller.api.integration.organization.MockContactRetriever; +import com.yahoo.vespa.hosted.controller.api.integration.organization.MockIssueHandler; import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGenerator; import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockBuildService; import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockRunDataStore; @@ -78,7 +80,8 @@ public final class ControllerTester { private final MockBuildService buildService; private final MetricsServiceMock metricsService; private final RoutingGeneratorMock routingGenerator; - private final MockOrganization organization; + private final MockContactRetriever contactRetriever; + private final MockIssueHandler issueHandler; private Controller controller; @@ -88,7 +91,7 @@ public final class ControllerTester { new ZoneRegistryMock(), new GitHubMock(), curatorDb, rotationsConfig, new MemoryNameService(), new ArtifactRepositoryMock(), new ApplicationStoreMock(), new MemoryEntityService(), new MockBuildService(), - metricsService, new RoutingGeneratorMock(), new MockOrganization(clock)); + metricsService, new RoutingGeneratorMock(), new MockContactRetriever(), new MockIssueHandler(clock)); } public ControllerTester(ManualClock clock) { @@ -114,7 +117,7 @@ public final class ControllerTester { ApplicationStoreMock appStoreMock, EntityService entityService, MockBuildService buildService, MetricsServiceMock metricsService, RoutingGeneratorMock routingGenerator, - MockOrganization organization) { + MockContactRetriever contactRetriever, MockIssueHandler issueHandler) { this.athenzDb = athenzDb; this.clock = clock; this.configServer = configServer; @@ -129,7 +132,8 @@ public final class ControllerTester { this.buildService = buildService; this.metricsService = metricsService; this.routingGenerator = routingGenerator; - this.organization = organization; + this.contactRetriever = contactRetriever; + this.issueHandler = issueHandler; this.controller = createController(curator, rotationsConfig, configServer, clock, gitHub, zoneRegistry, athenzDb, nameService, artifactRepository, appStoreMock, entityService, buildService, metricsService, routingGenerator); @@ -178,8 +182,8 @@ public final class ControllerTester { public RoutingGeneratorMock routingGenerator() { return routingGenerator; } - public MockOrganization organization() { - return organization; + public MockContactRetriever contactRetriever() { + return contactRetriever; } /** Create a new controller instance. Useful to verify that controller state is rebuilt from persistence */ @@ -240,19 +244,23 @@ public final class ControllerTester { return domain; } - public TenantName createTenant(String tenantName, String domainName, Long propertyId) { + public TenantName createTenant(String tenantName, String domainName, Long propertyId, Optional<Contact> contact) { 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)); + .map(PropertyId::new), contact); controller().tenants().create(tenant, new OktaAccessToken("okta-token")); assertNotNull(controller().tenants().tenant(name)); return name; } + public TenantName createTenant(String tenantName, String domainName, Long propertyId) { + return createTenant(tenantName, domainName, propertyId, Optional.empty()); + } + 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(new OktaAccessToken("okta-token"))); 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 2694e205a68..62653f29518 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 @@ -4,7 +4,7 @@ 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.integration.organization.Contact; 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; @@ -40,18 +40,19 @@ public class ApplicationOwnershipConfirmerTest { @Test public void testConfirmation() { - TenantName property = tester.controllerTester().createTenant("property", "domain", 1L); + Optional<Contact> contact = Optional.of(tester.controllerTester().contactRetriever().contact()); + TenantName property = tester.controllerTester().createTenant("property", "domain", 1L, contact); tester.createAndDeploy(property, "application", 1, "default"); Supplier<Application> propertyApp = () -> tester.controller().applications().require(ApplicationId.from("property", "application", "default")); - UserTenant user = UserTenant.create("by-user"); + UserTenant user = UserTenant.create("by-user", contact); 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()); assertFalse("No issue is initially stored for a new application.", userApp.get().ownershipIssueId().isPresent()); - assertFalse("No escalation has been attempted for a new application", issues.escalatedForProperty || issues.escalatedForUser); + assertFalse("No escalation has been attempted for a new application", issues.escalatedToContact || issues.escalatedToTerminator); // Set response from the issue mock, which will be obtained by the maintainer on issue filing. Optional<IssueId> issueId = Optional.of(IssueId.from("1")); @@ -68,7 +69,8 @@ public class ApplicationOwnershipConfirmerTest { assertEquals("Confirmation issue has been filed for property owned application.", issueId, propertyApp.get().ownershipIssueId()); assertEquals("Confirmation issue has been filed for user owned application.", issueId, userApp.get().ownershipIssueId()); - assertTrue("Both applications have had their responses ensured.", issues.escalatedForProperty && issues.escalatedForUser); + assertTrue(issues.escalatedToTerminator); + assertTrue("Both applications have had their responses ensured.", issues.escalatedToContact && issues.escalatedToTerminator); // No new issue is created, so return empty now. issues.response = Optional.empty(); @@ -99,23 +101,18 @@ public class ApplicationOwnershipConfirmerTest { private class MockOwnershipIssues implements OwnershipIssues { private Optional<IssueId> response; - private boolean escalatedForProperty = false; - private boolean escalatedForUser = false; + private boolean escalatedToContact = false; + private boolean escalatedToTerminator = false; @Override - public Optional<IssueId> confirmOwnership(Optional<IssueId> issueId, ApplicationId applicationId, PropertyId propertyId) { + public Optional<IssueId> confirmOwnership(Optional<IssueId> issueId, ApplicationId applicationId, User asignee, Contact contact) { return response; } @Override - public Optional<IssueId> confirmOwnership(Optional<IssueId> issueId, ApplicationId applicationId, User owner) { - return response; - } - - @Override - public void ensureResponse(IssueId issueId, Optional<PropertyId> propertyId) { - if (propertyId.isPresent()) escalatedForProperty = true; - else escalatedForUser = true; + public void ensureResponse(IssueId issueId, Optional<Contact> contact) { + if (contact.isPresent()) escalatedToContact = true; + else escalatedToTerminator = true; } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainerTest.java index cbaa37b15e3..6a7096dbfae 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainerTest.java @@ -6,7 +6,7 @@ import com.yahoo.vespa.hosted.controller.ControllerTester; import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; import com.yahoo.vespa.hosted.controller.api.integration.organization.User; import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant; -import com.yahoo.vespa.hosted.controller.tenant.Contact; +import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact; import org.junit.Before; import org.junit.Test; @@ -15,6 +15,7 @@ import java.time.Duration; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -35,7 +36,7 @@ public class ContactInformationMaintainerTest { tester = new ControllerTester(); maintainer = new ContactInformationMaintainer(tester.controller(), Duration.ofDays(1), new JobControl(tester.controller().curator()), - tester.organization()); + tester.contactRetriever()); } @Test @@ -55,14 +56,7 @@ public class ContactInformationMaintainerTest { private void registerContact(long propertyId, Contact contact) { PropertyId p = new PropertyId(String.valueOf(propertyId)); - tester.organization().addProperty(p) - .setContactsUrl(p, contact.url()) - .setIssueUrl(p, contact.issueTrackerUrl()) - .setPropertyUrl(p, contact.propertyUrl()) - .setContactsFor(p, contact.persons().stream().map(persons -> persons.stream() - .map(User::from) - .collect(Collectors.toList())) - .collect(Collectors.toList())); + tester.contactRetriever().addContact(p, contact); } private static Contact testContact() { @@ -71,7 +65,9 @@ public class ContactInformationMaintainerTest { URI propertyUrl = URI.create("http://property1.test"); List<List<String>> persons = Arrays.asList(Collections.singletonList("alice"), Collections.singletonList("bob")); - return new Contact(contactUrl, propertyUrl, issueTrackerUrl, persons); + String queue = "queue"; + Optional<String> component = Optional.empty(); + return new Contact(contactUrl, propertyUrl, issueTrackerUrl, persons, queue, component); } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java index dcfcb813de6..e97376d1f66 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java @@ -5,6 +5,7 @@ import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Environment; import com.yahoo.vespa.hosted.controller.Application; +import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact; import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId; import com.yahoo.vespa.hosted.controller.api.integration.stubs.LoggingDeploymentIssues; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; @@ -18,6 +19,7 @@ import org.junit.Test; import java.time.Duration; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.component; import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.productionCorpUsEast1; @@ -70,6 +72,11 @@ public class DeploymentIssueReporterTest { tester.upgradeSystem(Version.fromString("6.2")); + Optional<Contact> contact = Optional.of(tester.controllerTester().contactRetriever().contact()); + tester.controllerTester().createTenant("tenant1", "domain1", 1L, contact); + tester.controllerTester().createTenant("tenant2", "domain2", 1L, contact); + tester.controllerTester().createTenant("tenant3", "domain3", 1L, contact); + // Create and deploy one application for each of three tenants. Application app1 = tester.createApplication("application1", "tenant1", projectId1, propertyId1); Application app2 = tester.createApplication("application2", "tenant2", projectId2, propertyId2); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java index 60ff556dca4..f3790e6d291 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java @@ -6,7 +6,7 @@ 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 com.yahoo.vespa.hosted.controller.tenant.AthenzTenant; -import com.yahoo.vespa.hosted.controller.tenant.Contact; +import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact; import com.yahoo.vespa.hosted.controller.tenant.UserTenant; import org.junit.Test; @@ -57,24 +57,30 @@ public class TenantSerializerTest { new AthenzDomain("domain1"), new Property("property1"), Optional.of(new PropertyId("1")), - Optional.of(new Contact( - URI.create("http://contact1.test"), - URI.create("http://property1.test"), - URI.create("http://issue-tracker-1.test"), - Arrays.asList( - Collections.singletonList("person1"), - Collections.singletonList("person2") - ) - ))); + Optional.of(contact())); AthenzTenant serialized = serializer.athenzTenantFrom(serializer.toSlime(tenant)); assertEquals(tenant.contact(), serialized.contact()); } @Test public void user_tenant() { - UserTenant tenant = UserTenant.create("by-foo"); + UserTenant tenant = UserTenant.create("by-foo", Optional.of(contact())); UserTenant serialized = serializer.userTenantFrom(serializer.toSlime(tenant)); assertEquals(tenant.name(), serialized.name()); + assertEquals(contact(), serialized.contact().get()); } + private Contact contact() { + return new Contact( + URI.create("http://contact1.test"), + URI.create("http://property1.test"), + URI.create("http://issue-tracker-1.test"), + Arrays.asList( + Collections.singletonList("person1"), + Collections.singletonList("person2") + ), + "queue", + Optional.empty() + ); + } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java index 6044dd7b8f8..e908777a8b0 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java @@ -71,7 +71,8 @@ public class ControllerContainerTest { " <component id='com.yahoo.vespa.hosted.controller.api.integration.stubs.LoggingDeploymentIssues'/>\n" + " <component id='com.yahoo.vespa.hosted.controller.api.integration.stubs.DummyOwnershipIssues'/>\n" + " <component id='com.yahoo.vespa.hosted.controller.api.integration.stubs.MockRunDataStore'/>\n" + - " <component id='com.yahoo.vespa.hosted.controller.api.integration.organization.MockOrganization'/>\n" + + " <component id='com.yahoo.vespa.hosted.controller.api.integration.organization.MockContactRetriever'/>\n" + + " <component id='com.yahoo.vespa.hosted.controller.api.integration.organization.MockIssueHandler'/>\n" + " <component id='com.yahoo.vespa.hosted.controller.integration.ConfigServerMock'/>\n" + " <component id='com.yahoo.vespa.hosted.controller.integration.NodeRepositoryClientMock'/>\n" + " <component id='com.yahoo.vespa.hosted.controller.integration.ZoneRegistryMock'/>\n" + 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 43d10484335..8b260765423 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 @@ -32,8 +32,7 @@ import com.yahoo.vespa.hosted.controller.athenz.HostedAthenzIdentities; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException; import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId; -import com.yahoo.vespa.hosted.controller.api.integration.organization.MockOrganization; -import com.yahoo.vespa.hosted.controller.api.integration.organization.User; +import com.yahoo.vespa.hosted.controller.api.integration.organization.MockContactRetriever; 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.Change; @@ -58,7 +57,7 @@ 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 com.yahoo.vespa.hosted.controller.tenant.Contact; +import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact; import org.apache.http.HttpEntity; import org.apache.http.entity.ContentType; import org.apache.http.entity.mime.MultipartEntityBuilder; @@ -1314,8 +1313,8 @@ public class ApplicationApiTest extends ControllerContainerTest { return (MetricsServiceMock) tester.container().components().getComponent(MetricsServiceMock.class.getName()); } - private MockOrganization organization() { - return (MockOrganization) tester.container().components().getComponent(MockOrganization.class.getName()); + private MockContactRetriever contactRetriever() { + return (MockContactRetriever) tester.container().components().getComponent(MockContactRetriever.class.getName()); } private void setZoneInRotation(String rotationName, ZoneId zone) { @@ -1341,18 +1340,14 @@ public class ApplicationApiTest extends ControllerContainerTest { } private void updateContactInformation() { - Contact contact = new Contact(URI.create("www.contacts.tld/1234"), URI.create("www.properties.tld/1234"), URI.create("www.issues.tld/1234"), Arrays.asList(Arrays.asList("alice"), Arrays.asList("bob"))); + Contact contact = new Contact(URI.create("www.contacts.tld/1234"), URI.create("www.properties.tld/1234"), URI.create("www.issues.tld/1234"), Arrays.asList(Arrays.asList("alice"), Arrays.asList("bob")), "queue", Optional.empty()); tester.controller().tenants().lockIfPresent(TenantName.from("tenant2"), lockedTenant -> tester.controller().tenants().store(lockedTenant.with(contact))); } private void registerContact(long propertyId) { PropertyId p = new PropertyId(String.valueOf(propertyId)); - organization().addProperty(p) - .setIssueUrl(p, URI.create("www.issues.tld/" + p.id())) - .setContactsUrl(p, URI.create("www.contacts.tld/" + p.id())) - .setPropertyUrl(p, URI.create("www.properties.tld/" + p.id())) - .setContactsFor(p, Arrays.asList(Collections.singletonList(User.from("alice")), - Collections.singletonList(User.from("bob")))); + contactRetriever().addContact(p, new Contact(URI.create("www.issues.tld/" + p.id()), URI.create("www.contacts.tld/" + p.id()), URI.create("www.properties.tld/" + p.id()), Arrays.asList(Collections.singletonList("alice"), + Collections.singletonList("bob")), "queue", Optional.empty())); } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/contactinfo/ContactInfoHandlerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/contactinfo/ContactInfoHandlerTest.java index 459b3a35f85..24506fda31a 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/contactinfo/ContactInfoHandlerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/contactinfo/ContactInfoHandlerTest.java @@ -2,17 +2,11 @@ package com.yahoo.vespa.hosted.controller.restapi.contactinfo; import com.yahoo.application.container.handler.Request; import com.yahoo.application.container.handler.Response; -import com.yahoo.slime.Slime; -import com.yahoo.vespa.config.SlimeUtils; import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester; import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest; -import com.yahoo.vespa.hosted.controller.tenant.Contact; import org.junit.Before; import org.junit.Test; -import java.net.URI; -import java.util.Arrays; - import static org.junit.Assert.*; public class ContactInfoHandlerTest extends ControllerContainerTest { @@ -25,7 +19,7 @@ public class ContactInfoHandlerTest extends ControllerContainerTest { } @Test - public void testGettingAndFeedingContactInfo() throws Exception { + public void testGettingAndFeedingContactInfo() { tester.createApplication(); // No contact information available yet @@ -33,8 +27,8 @@ public class ContactInfoHandlerTest extends ControllerContainerTest { assertResponse(new Request("http://localhost:8080/contactinfo/v1/tenant/tenant1"), 404, notFoundMessage); // Feed contact information for tenant1 - String contactInfo = "{\"url\":\"https://url:4444/\",\"issueTrackerUrl\":\"https://issueTrackerUrl:4444/\",\"propertyUrl\":\"https://propertyUrl:4444/\",\"persons\":[[\"foo\",\"bar\"]]}"; - String expectedResponseMessage = "Added contact info for tenant1 - Contact{url=https://url:4444/, propertyUrl=https://propertyUrl:4444/, issueTrackerUrl=https://issueTrackerUrl:4444/, persons=[[foo, bar]]}"; + String contactInfo = "{\"url\":\"https://url:4444/\",\"issueTrackerUrl\":\"https://issueTrackerUrl:4444/\",\"propertyUrl\":\"https://propertyUrl:4444/\",\"persons\":[[\"foo\",\"bar\"]],\"queue\":\"queue\",\"component\":\"component\"}"; + String expectedResponseMessage = "Added contact info for tenant1 - Contact{url=https://url:4444/, propertyUrl=https://propertyUrl:4444/, issueTrackerUrl=https://issueTrackerUrl:4444/, persons=[[foo, bar]], queue=queue, component=component}"; assertResponse(new Request("http://localhost:8080/contactinfo/v1/tenant/tenant1", contactInfo, Request.Method.POST), 200, expectedResponseMessage); // Get contact information for tenant1 |