diff options
30 files changed, 369 insertions, 342 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Contact.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Contact.java index e13b0f982da..9ca83673b8a 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Contact.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Contact.java @@ -1,11 +1,12 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.tenant; +package com.yahoo.vespa.hosted.controller.api.integration.organization; import com.google.common.collect.ImmutableList; import java.net.URI; import java.util.List; import java.util.Objects; +import java.util.Optional; /** * Contact information for a tenant. @@ -18,12 +19,16 @@ public class Contact { private final URI propertyUrl; private final URI issueTrackerUrl; private final List<List<String>> persons; + private final String queue; + private final Optional<String> component; - public Contact(URI url, URI propertyUrl, URI issueTrackerUrl, List<List<String>> persons) { + public Contact(URI url, URI propertyUrl, URI issueTrackerUrl, List<List<String>> persons, String queue, Optional<String> component) { 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")); + this.queue = queue; + this.component = component; } /** URL to this */ @@ -46,6 +51,14 @@ public class Contact { return persons; } + public String queue() { + return queue; + } + + public Optional<String> component() { + return component; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -54,7 +67,9 @@ public class Contact { return Objects.equals(url, contact.url) && Objects.equals(propertyUrl, contact.propertyUrl) && Objects.equals(issueTrackerUrl, contact.issueTrackerUrl) && - Objects.equals(persons, contact.persons); + Objects.equals(persons, contact.persons) && + Objects.equals(queue, contact.queue) && + Objects.equals(component, contact.component); } @Override @@ -69,6 +84,8 @@ public class Contact { ", propertyUrl=" + propertyUrl + ", issueTrackerUrl=" + issueTrackerUrl + ", persons=" + persons + + ", queue=" + queue + + (component.isPresent() ? ", component=" + component.get() : "") + '}'; } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/ContactRetriever.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/ContactRetriever.java new file mode 100644 index 00000000000..9b05234d529 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/ContactRetriever.java @@ -0,0 +1,12 @@ +package com.yahoo.vespa.hosted.controller.api.integration.organization; + +import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; + +import java.util.Optional; + +/** + * @author olaa + */ +public interface ContactRetriever { + Contact getContact(Optional<PropertyId> propertyId); +} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/DeploymentIssues.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/DeploymentIssues.java index 6888e8ac06d..bafedb543f6 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/DeploymentIssues.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/DeploymentIssues.java @@ -3,7 +3,6 @@ package com.yahoo.vespa.hosted.controller.api.integration.organization; import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; import java.time.Duration; import java.util.Collection; @@ -16,12 +15,10 @@ import java.util.Optional; */ public interface DeploymentIssues { - IssueId fileUnlessOpen(Optional<IssueId> issueId, ApplicationId applicationId, PropertyId propertyId); - - IssueId fileUnlessOpen(Optional<IssueId> issueId, ApplicationId applicationId, User assignee); + IssueId fileUnlessOpen(Optional<IssueId> issueId, ApplicationId applicationId, User asignee, Contact contact); IssueId fileUnlessOpen(Collection<ApplicationId> applicationIds, Version version); - void escalateIfInactive(IssueId issueId, Optional<PropertyId> propertyId, Duration maxInactivity); + void escalateIfInactive(IssueId issueId, Duration maxInactivity, Optional<Contact> contact); } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Issue.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Issue.java index a9bc7868f7a..7f42767d931 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Issue.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Issue.java @@ -22,52 +22,49 @@ public class Issue { private final String description; private final List<String> labels; private final User assignee; - private final PropertyId propertyId; private final Type type; + private final String queue; + private final Optional<String> component; - private Issue(String summary, String description, List<String> labels, User assignee, PropertyId propertyId, Type type) { + private Issue(String summary, String description, List<String> labels, User assignee, Type type, String queue, Optional<String> component) { if (summary.isEmpty()) throw new IllegalArgumentException("Issue summary can not be empty!"); if (description.isEmpty()) throw new IllegalArgumentException("Issue description can not be empty!"); - Objects.requireNonNull(propertyId, "An issue must belong to a property!"); this.summary = summary; this.description = description; this.labels = ImmutableList.copyOf(labels); this.assignee = assignee; - this.propertyId = propertyId; this.type = type; + this.queue = queue; + this.component = component; } - public Issue(String summary, String description, PropertyId propertyId) { - this(summary, description, Collections.emptyList(), null, propertyId, Type.defect); + public Issue(String summary, String description, String queue, Optional<String> component) { + this(summary, description, Collections.emptyList(), null, Type.defect, queue, component); } public Issue append(String appendage) { - return new Issue(summary, description + appendage, labels, assignee, propertyId, type); + return new Issue(summary, description + appendage, labels, assignee, type, queue, component); } public Issue with(String label) { List<String> labels = new ArrayList<>(this.labels); labels.add(label); - return new Issue(summary, description, labels, assignee, propertyId, type); + return new Issue(summary, description, labels, assignee, type, queue, component); } public Issue with(List<String> labels) { List<String> newLabels = new ArrayList<>(this.labels); newLabels.addAll(labels); - return new Issue(summary, description, newLabels, assignee, propertyId, type); + return new Issue(summary, description, newLabels, assignee, type, queue, component); } public Issue with(User assignee) { - return new Issue(summary, description, labels, assignee, propertyId, type); - } - - public Issue with(PropertyId propertyId) { - return new Issue(summary, description, labels, assignee, propertyId, type); + return new Issue(summary, description, labels, assignee, type, queue, component); } public Issue with(Type type) { - return new Issue(summary, description, labels, assignee, propertyId, type); + return new Issue(summary, description, labels, assignee, type, queue, component); } public String summary() { @@ -86,15 +83,17 @@ public class Issue { return Optional.ofNullable(assignee); } - public PropertyId propertyId() { - return propertyId; - } - public Type type() { return type; } + public String queue() { + return queue; + } + public Optional<String> component() { + return component; + } public enum Type { defect, // A defect which needs fixing. diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Organization.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/IssueHandler.java index 6dccaec3b7a..db4f0eb5c59 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Organization.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/IssueHandler.java @@ -1,20 +1,14 @@ // 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.integration.organization; -import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; -import java.net.URI; import java.time.Duration; -import java.util.List; import java.util.Optional; /** - * Represents the humans who use this software, and their organization. - * Lets the software report issues to its caretakers, and provides other useful human resource lookups. - * * @author jonmv */ -public interface Organization { +public interface IssueHandler { /** * File an issue with its given property or the default, and with the specific assignee, if present. @@ -85,40 +79,8 @@ public interface Organization { * Escalate an issue filed with the given property. * * @param issueId ID of the issue to escalate. - * @param propertyId PropertyId of the tenant owning the application for which the issue was filed. * @return User that was assigned issue as a result of the escalation, if any */ - default Optional<User> escalate(IssueId issueId, PropertyId propertyId) { - List<? extends List<? extends User>> contacts = contactsFor(propertyId); - - Optional<User> assignee = assigneeOf(issueId); - int assigneeLevel = -1; - if (assignee.isPresent()) - for (int level = contacts.size(); --level > assigneeLevel; ) - if (contacts.get(level).contains(assignee.get())) - assigneeLevel = level; - - for (int level = assigneeLevel + 1; level < contacts.size(); level++) - for (User target : contacts.get(level)) - if (reassign(issueId, target)) - return Optional.of(target); - - return Optional.empty(); - } - - /** - * Returns a nested list where the entries have increasing rank, and where each entry is - * a list of the users of that rank, by decreasing relevance. - * - * @param propertyId ID of the property for which to list contacts. - * @return A sorted, nested, reverse sorted list of contacts. - */ - List<? extends List<? extends User>> contactsFor(PropertyId propertyId); - - URI issueCreationUri(PropertyId propertyId); - - URI contactsUri(PropertyId propertyId); - - URI propertyUri(PropertyId propertyId); + Optional<User> escalate(IssueId issueId, Contact contact); } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/MockContactRetriever.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/MockContactRetriever.java new file mode 100644 index 00000000000..55bad0b77ac --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/MockContactRetriever.java @@ -0,0 +1,36 @@ +// 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.api.integration.organization; + +import com.yahoo.component.AbstractComponent; +import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; + +import java.net.URI; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * @author olaa + */ +public class MockContactRetriever extends AbstractComponent implements ContactRetriever{ + + private final Map<PropertyId, Contact> contacts = new HashMap<>(); + + + + @Override + public Contact getContact(Optional<PropertyId> propertyId) { + return contacts.getOrDefault(propertyId.get(), contact()); + } + + public void addContact(PropertyId propertyId, Contact contact) { + contacts.put(propertyId, contact); + } + + + public Contact contact() { + return new Contact(URI.create("contacts.tld"), URI.create("properties.tld"), URI.create("issues.tld"), Collections.emptyList(), "queue", Optional.of("component")); + } + +} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/MockOrganization.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/MockIssueHandler.java index 82d3be596bc..674523ba26b 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/MockOrganization.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/MockIssueHandler.java @@ -2,7 +2,6 @@ package com.yahoo.vespa.hosted.controller.api.integration.organization; import com.google.inject.Inject; -import com.yahoo.component.AbstractComponent; import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; import java.net.URI; @@ -13,34 +12,32 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.NoSuchElementException; import java.util.Optional; import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; /** * @author jvenstad */ -public class MockOrganization extends AbstractComponent implements Organization { +public class MockIssueHandler implements IssueHandler { private final Clock clock; private final AtomicLong counter = new AtomicLong(); private final Map<IssueId, MockIssue> issues = new HashMap<>(); - private final Map<PropertyId, PropertyInfo> properties = new HashMap<>(); @Inject @SuppressWarnings("unused") - public MockOrganization() { + public MockIssueHandler() { this(Clock.systemUTC()); } - public MockOrganization(Clock clock) { + public MockIssueHandler(Clock clock) { this.clock = clock; } @Override public IssueId file(Issue issue) { - if ( ! properties.containsKey(issue.propertyId())) - throw new NoSuchElementException("Unknown property '" + issue.propertyId() + "'!"); + if (!issue.assignee().isPresent()) throw new RuntimeException(); IssueId issueId = IssueId.from("" + counter.incrementAndGet()); issues.put(issueId, new MockIssue(issue)); return issueId; @@ -49,9 +46,9 @@ public class MockOrganization extends AbstractComponent implements Organization @Override public Optional<IssueId> findBySimilarity(Issue issue) { return issues.entrySet().stream() - .filter(entry -> entry.getValue().issue.summary().equals(issue.summary())) - .findFirst() - .map(Map.Entry::getKey); + .filter(entry -> entry.getValue().issue.summary().equals(issue.summary())) + .findFirst() + .map(Map.Entry::getKey); } @Override @@ -87,62 +84,55 @@ public class MockOrganization extends AbstractComponent implements Organization } @Override - public List<? extends List<? extends User>> contactsFor(PropertyId propertyId) { - return properties.getOrDefault(propertyId, new PropertyInfo()).contacts; - } - - @Override - public URI issueCreationUri(PropertyId propertyId) { - return properties.getOrDefault(propertyId, new PropertyInfo()).issueUrl; - } - - @Override - public URI contactsUri(PropertyId propertyId) { - return properties.getOrDefault(propertyId, new PropertyInfo()).contactsUrl; - } + public Optional<User> escalate(IssueId issueId, Contact contact) { + List<List<User>> contacts = getContactUsers(contact); + Optional<User> assignee = assigneeOf(issueId); + int assigneeLevel = -1; + if (assignee.isPresent()) + for (int level = contacts.size(); --level > assigneeLevel; ) + if (contacts.get(level).contains(assignee.get())) + assigneeLevel = level; - @Override - public URI propertyUri(PropertyId propertyId) { - return properties.getOrDefault(propertyId, new PropertyInfo()).propertyUrl; - } + for (int level = assigneeLevel + 1; level < contacts.size(); level++) + for (User target : contacts.get(level)) + if (reassign(issueId, target)) + return Optional.of(target); - public Map<IssueId, MockIssue> issues() { - return Collections.unmodifiableMap(issues); + return Optional.empty(); } - public MockOrganization close(IssueId issueId) { + public MockIssueHandler close(IssueId issueId) { issues.get(issueId).open = false; touch(issueId); return this; } - public MockOrganization setContactsFor(PropertyId propertyId, List<List<User>> contacts) { - properties.get(propertyId).contacts = contacts; - return this; + public Map<IssueId, MockIssue> issues() { + return issues; } - public MockOrganization setPropertyUrl(PropertyId propertyId, URI url) { - properties.get(propertyId).propertyUrl = url; - return this; + private List<List<User>> getContactUsers(Contact contact) { + return contact.persons().stream() + .map(userList -> + userList.stream().map(user -> + user.split(" ")[0]) + .map(User::from) + .collect(Collectors.toList()) + ).collect(Collectors.toList()); } - public MockOrganization setContactsUrl(PropertyId propertyId, URI url) { - properties.get(propertyId).contactsUrl = url; - return this; - } - public MockOrganization setIssueUrl(PropertyId propertyId, URI url) { - properties.get(propertyId).issueUrl = url; - return this; + private void touch(IssueId issueId) { + issues.get(issueId).updated = clock.instant(); } - public MockOrganization addProperty(PropertyId propertyId) { - properties.put(propertyId, new PropertyInfo()); - return this; - } + private class PropertyInfo { + + private List<List<User>> contacts = Collections.emptyList(); + private URI issueUrl = URI.create("issues.tld"); + private URI contactsUrl = URI.create("contacts.tld"); + private URI propertyUrl = URI.create("properties.tld"); - private void touch(IssueId issueId) { - issues.get(issueId).updated = clock.instant(); } public class MockIssue { @@ -165,14 +155,4 @@ public class MockOrganization extends AbstractComponent implements Organization } - private class PropertyInfo { - - private List<List<User>> contacts = Collections.emptyList(); - private URI issueUrl = URI.create("issues.tld"); - private URI contactsUrl = URI.create("contacts.tld"); - private URI propertyUrl = URI.create("properties.tld"); - - } - } - diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/OwnershipIssues.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/OwnershipIssues.java index ee17859c0fb..6a69eb54d2c 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/OwnershipIssues.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/OwnershipIssues.java @@ -2,7 +2,6 @@ package com.yahoo.vespa.hosted.controller.api.integration.organization; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; import java.util.Optional; @@ -19,31 +18,21 @@ import java.util.Optional; public interface OwnershipIssues { /** - * Ensure ownership of the given application has been recently confirmed by the given property. - * - * @param issueId ID of the previous ownership issue filed for the given application. - * @param applicationId ID of the application for which to file an issue. - * @param propertyId ID of the property responsible for the given application. - * @return ID of the created issue, if one was created. - */ - Optional<IssueId> confirmOwnership(Optional<IssueId> issueId, ApplicationId applicationId, PropertyId propertyId); - - /** * Ensure ownership of the given application has been recently confirmed by the given user. * * @param issueId ID of the previous ownership issue filed for the given application. * @param applicationId ID of the application for which to file an issue. - * @param owner ID of the user responsible for the given application. + * @param asignee Issue asignee + * @param contact Contact info for the application tenant * @return ID of the created issue, if one was created. */ - Optional<IssueId> confirmOwnership(Optional<IssueId> issueId, ApplicationId applicationId, User owner); + Optional<IssueId> confirmOwnership(Optional<IssueId> issueId, ApplicationId applicationId, User asignee, Contact contact); /** * Make sure the given ownership confirmation request is acted upon, unless it is already acknowledged. - * * @param issueId ID of the ownership issue to escalate. - * @param propertyId ID of the property responsible for the issue, if any. + * @param contact Contact information of application tenant */ - void ensureResponse(IssueId issueId, Optional<PropertyId> propertyId); + void ensureResponse(IssueId issueId, Optional<Contact> contact); } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/DummyOwnershipIssues.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/DummyOwnershipIssues.java index 6e4761d1cf8..14f252732fb 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/DummyOwnershipIssues.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/DummyOwnershipIssues.java @@ -2,7 +2,7 @@ package com.yahoo.vespa.hosted.controller.api.integration.stubs; import com.yahoo.config.provision.ApplicationId; -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; @@ -12,17 +12,12 @@ import java.util.Optional; public class DummyOwnershipIssues implements OwnershipIssues { @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 Optional.empty(); } @Override - public Optional<IssueId> confirmOwnership(Optional<IssueId> issueId, ApplicationId applicationId, User owner) { - return Optional.empty(); - } - - @Override - public void ensureResponse(IssueId issueId, Optional<PropertyId> propertyId) { + public void ensureResponse(IssueId issueId, Optional<Contact> contact) { } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/LoggingDeploymentIssues.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/LoggingDeploymentIssues.java index c5efffd979a..5dfbeb5e756 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/LoggingDeploymentIssues.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/LoggingDeploymentIssues.java @@ -6,6 +6,7 @@ import com.google.inject.Inject; import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; 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; @@ -54,12 +55,7 @@ public class LoggingDeploymentIssues implements DeploymentIssues { } @Override - public IssueId fileUnlessOpen(Optional<IssueId> issueId, ApplicationId applicationId, PropertyId propertyId) { - return fileUnlessPresent(issueId, applicationId); - } - - @Override - public IssueId fileUnlessOpen(Optional<IssueId> issueId, ApplicationId applicationId, User assignee) { + public IssueId fileUnlessOpen(Optional<IssueId> issueId, ApplicationId applicationId, User asignee, Contact contact) { return fileUnlessPresent(issueId, applicationId); } @@ -73,7 +69,7 @@ public class LoggingDeploymentIssues implements DeploymentIssues { } @Override - public void escalateIfInactive(IssueId issueId, Optional<PropertyId> propertyId, Duration maxInactivity) { + public void escalateIfInactive(IssueId issueId, Duration maxInactivity, Optional<Contact> contact) { if (issueUpdates.containsKey(issueId) && issueUpdates.get(issueId).isBefore(clock.instant().minus(maxInactivity))) escalateIssue(issueId); } 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/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 |