aboutsummaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorJon Marius Venstad <jvenstad@yahoo-inc.com>2017-11-16 13:07:56 +0100
committerJon Marius Venstad <jvenstad@yahoo-inc.com>2017-11-16 13:07:56 +0100
commit015553855b172438d791c4e40ca0d7e1a65d8d65 (patch)
tree7570542836076a367b581e58216e04c7ed569c36 /controller-server
parent8ce7ce2c7fedc3c0f6069c481acad12b3759d8f5 (diff)
Maintainer which asks for regular confirmation of ownership
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java20
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java36
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java85
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java107
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json3
11 files changed, 248 insertions, 29 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
index d26ff8ad65d..3fafd81d10e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
@@ -8,6 +8,7 @@ import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.Zone;
+import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
@@ -20,7 +21,6 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
-import java.util.stream.Collector;
import java.util.stream.Collectors;
/**
@@ -39,26 +39,27 @@ public class Application {
private final DeploymentJobs deploymentJobs;
private final Optional<Change> deploying;
private final boolean outstandingChange;
+ private final Optional<IssueId> ownershipIssueId;
/** Creates an empty application */
public Application(ApplicationId id) {
this(id, DeploymentSpec.empty, ValidationOverrides.empty, ImmutableMap.of(),
new DeploymentJobs(Optional.empty(), Collections.emptyList(), Optional.empty()),
- Optional.empty(), false);
+ Optional.empty(), false, Optional.empty());
}
/** Used from persistence layer: Do not use */
public Application(ApplicationId id, DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides,
- List<Deployment> deployments,
- DeploymentJobs deploymentJobs, Optional<Change> deploying, boolean outstandingChange) {
+ List<Deployment> deployments, DeploymentJobs deploymentJobs, Optional<Change> deploying,
+ boolean outstandingChange, Optional<IssueId> ownershipIssueId) {
this(id, deploymentSpec, validationOverrides,
deployments.stream().collect(Collectors.toMap(Deployment::zone, d -> d)),
- deploymentJobs, deploying, outstandingChange);
+ deploymentJobs, deploying, outstandingChange, ownershipIssueId);
}
Application(ApplicationId id, DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides,
Map<Zone, Deployment> deployments, DeploymentJobs deploymentJobs, Optional<Change> deploying,
- boolean outstandingChange) {
+ boolean outstandingChange, Optional<IssueId> ownershipIssueId) {
Objects.requireNonNull(id, "id cannot be null");
Objects.requireNonNull(deploymentSpec, "deploymentSpec cannot be null");
Objects.requireNonNull(validationOverrides, "validationOverrides cannot be null");
@@ -72,6 +73,7 @@ public class Application {
this.deploymentJobs = deploymentJobs;
this.deploying = deploying;
this.outstandingChange = outstandingChange;
+ this.ownershipIssueId = ownershipIssueId;
}
public ApplicationId id() { return id; }
@@ -181,5 +183,9 @@ public class Application {
public boolean isBlocked(Instant instant) {
return ! deploymentSpec.canUpgradeAt(instant) || ! deploymentSpec.canChangeRevisionAt(instant);
}
-
+
+ public Optional<IssueId> ownershipIssueId() {
+ return ownershipIssueId;
+ }
+
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
index 56b76260f14..f34583d3998 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
@@ -36,20 +36,20 @@ public class LockedApplication extends Application {
LockedApplication(Application application, Lock lock) {
super(application.id(), application.deploymentSpec(), application.validationOverrides(),
application.deployments(), application.deploymentJobs(), application.deploying(),
- application.hasOutstandingChange());
+ application.hasOutstandingChange(), application.ownershipIssueId());
this.lock = Objects.requireNonNull(lock, "lock cannot be null");
}
public LockedApplication withProjectId(long projectId) {
return new LockedApplication(new Application(id(), deploymentSpec(), validationOverrides(), deployments(),
deploymentJobs().withProjectId(projectId), deploying(),
- hasOutstandingChange()), lock);
+ hasOutstandingChange(), ownershipIssueId()), lock);
}
public LockedApplication with(IssueId issueId) {
return new LockedApplication(new Application(id(), deploymentSpec(), validationOverrides(), deployments(),
deploymentJobs().with(issueId), deploying(),
- hasOutstandingChange()), lock);
+ hasOutstandingChange(), ownershipIssueId()), lock);
}
public LockedApplication withJobCompletion(DeploymentJobs.JobReport report, Instant notificationTime,
@@ -58,7 +58,7 @@ public class LockedApplication extends Application {
deployments(),
deploymentJobs().withCompletion(report, notificationTime,
controller),
- deploying(), hasOutstandingChange()), lock);
+ deploying(), hasOutstandingChange(), ownershipIssueId()), lock);
}
public LockedApplication withJobTriggering(DeploymentJobs.JobType type, Optional<Change> change,
@@ -70,7 +70,7 @@ public class LockedApplication extends Application {
determineTriggerRevision(type, controller),
reason,
triggerTime),
- deploying(), hasOutstandingChange()), lock);
+ deploying(), hasOutstandingChange(), ownershipIssueId()), lock);
}
public LockedApplication with(Deployment deployment) {
@@ -78,13 +78,13 @@ public class LockedApplication extends Application {
deployments.put(deployment.zone(), deployment);
return new LockedApplication(new Application(id(), deploymentSpec(), validationOverrides(),
deployments, deploymentJobs(), deploying(),
- hasOutstandingChange()), lock);
+ hasOutstandingChange(), ownershipIssueId()), lock);
}
public LockedApplication with(DeploymentJobs deploymentJobs) {
return new LockedApplication(new Application(id(), deploymentSpec(), validationOverrides(),
deployments(), deploymentJobs, deploying(),
- hasOutstandingChange()), lock);
+ hasOutstandingChange(), ownershipIssueId()), lock);
}
public LockedApplication withoutDeploymentIn(Zone zone) {
@@ -92,38 +92,44 @@ public class LockedApplication extends Application {
deployments.remove(zone);
return new LockedApplication(new Application(id(), deploymentSpec(), validationOverrides(),
deployments, deploymentJobs(), deploying(),
- hasOutstandingChange()), lock);
+ hasOutstandingChange(), ownershipIssueId()), lock);
}
public LockedApplication withoutDeploymentJob(DeploymentJobs.JobType jobType) {
DeploymentJobs deploymentJobs = deploymentJobs().without(jobType);
return new LockedApplication(new Application(id(), deploymentSpec(), validationOverrides(),
deployments(), deploymentJobs, deploying(),
- hasOutstandingChange()), lock);
+ hasOutstandingChange(), ownershipIssueId()), lock);
}
public LockedApplication with(DeploymentSpec deploymentSpec) {
return new LockedApplication(new Application(id(), deploymentSpec, validationOverrides(),
deployments(), deploymentJobs(), deploying(),
- hasOutstandingChange()), lock);
+ hasOutstandingChange(), ownershipIssueId()), lock);
}
public LockedApplication with(ValidationOverrides validationOverrides) {
return new LockedApplication(new Application(id(), deploymentSpec(), validationOverrides,
deployments(), deploymentJobs(), deploying(),
- hasOutstandingChange()), lock);
+ hasOutstandingChange(), ownershipIssueId()), lock);
}
public LockedApplication withDeploying(Optional<Change> deploying) {
return new LockedApplication(new Application(id(), deploymentSpec(), validationOverrides(),
deployments(), deploymentJobs(), deploying,
- hasOutstandingChange()), lock);
+ hasOutstandingChange(), ownershipIssueId()), lock);
}
public LockedApplication withOutstandingChange(boolean outstandingChange) {
- return new LockedApplication(new Application(id(), deploymentSpec(),
- validationOverrides(), deployments(),
- deploymentJobs(), deploying(), outstandingChange), lock);
+ return new LockedApplication(new Application(id(), deploymentSpec(), validationOverrides(),
+ deployments(), deploymentJobs(), deploying(),
+ outstandingChange, ownershipIssueId()), lock);
+ }
+
+ public LockedApplication withOwnershipIssueId(IssueId issueId) {
+ return new LockedApplication(new Application(id(), deploymentSpec(), validationOverrides(),
+ deployments(), deploymentJobs(), deploying(),
+ hasOutstandingChange(), Optional.of(issueId)), lock);
}
private Version determineTriggerVersion(DeploymentJobs.JobType jobType, Controller controller) {
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
new file mode 100644
index 00000000000..5d9cf291af7
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java
@@ -0,0 +1,85 @@
+package com.yahoo.vespa.hosted.controller.maintenance;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.vespa.curator.Lock;
+import com.yahoo.vespa.hosted.controller.Application;
+import com.yahoo.vespa.hosted.controller.Controller;
+import com.yahoo.vespa.hosted.controller.api.Tenant;
+import com.yahoo.vespa.hosted.controller.api.application.v4.model.TenantType;
+import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId;
+import com.yahoo.vespa.hosted.controller.api.integration.organization.OwnershipIssues;
+import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
+import com.yahoo.vespa.hosted.controller.api.integration.organization.User;
+
+import java.time.Duration;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+import java.util.logging.Level;
+
+public class ApplicationOwnershipConfirmer extends Maintainer {
+
+ private final OwnershipIssues ownershipIssues;
+
+ public ApplicationOwnershipConfirmer(Controller controller, Duration interval, JobControl jobControl, OwnershipIssues ownershipIssues) {
+ super(controller, interval, jobControl);
+ this.ownershipIssues = ownershipIssues;
+ }
+
+ @Override
+ protected void maintain() {
+ confirmApplicationOwnerships();
+ ensureConfirmationResponses();
+ }
+
+ /** File an ownership issue with the owners of all applications we know about. */
+ private void confirmApplicationOwnerships() {
+ for (Application application : controller().applications().asList()) {
+ try {
+ Tenant tenant = ownerOf(application.id());
+ Optional<IssueId> ourIssueId = application.ownershipIssueId();
+ ourIssueId = tenant.tenantType() == TenantType.USER
+ ? ownershipIssues.confirmOwnership(ourIssueId, application.id(), userFor(tenant))
+ : ownershipIssues.confirmOwnership(ourIssueId, application.id(), propertyIdFor(tenant));
+ ourIssueId.ifPresent(issueId -> store(issueId, application.id()));
+ }
+ catch (RuntimeException e) { // Catch errors due to wrong data in the controller, or issues client timeout.
+ log.log(Level.WARNING, "Exception caught when attempting to file an issue for " + application.id(), e);
+ }
+ }
+ }
+
+ /** Escalate ownership issues which have not been closed before a defined amount of time has passed. */
+ private void ensureConfirmationResponses() {
+ for (Application application : controller().applications().asList())
+ application.ownershipIssueId().ifPresent(issueId -> {
+ try {
+ ownershipIssues.ensureResponse(issueId, ownerOf(application.id()).getPropertyId());
+ }
+ catch (RuntimeException e) {
+ log.log(Level.WARNING, "Exception caught when attempting to escalate issue with id " + issueId, e);
+ }
+ });
+ }
+
+ private Tenant ownerOf(ApplicationId applicationId) {
+ return controller().tenants().tenant(new TenantId(applicationId.tenant().value()))
+ .orElseThrow(() -> new IllegalStateException("No tenant found for application " + applicationId));
+ }
+
+ protected User userFor(Tenant tenant) {
+ return User.from(tenant.getId().id().replaceFirst("by-", ""));
+ }
+
+ protected PropertyId propertyIdFor(Tenant tenant) {
+ return tenant.getPropertyId()
+ .orElseThrow(() -> new NoSuchElementException("No PropertyId is listed for non-user tenant " + tenant));
+ }
+
+ protected void store(IssueId issueId, ApplicationId applicationId) {
+ try (Lock lock = controller().applications().lock(applicationId)) {
+ controller().applications().get(applicationId, lock)
+ .ifPresent(application -> controller().applications().store(application.withOwnershipIssueId(issueId)));
+ }
+ }
+}
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 2fdce2802ab..4acda712984 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,6 +4,7 @@ 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.OwnershipIssues;
import com.yahoo.vespa.hosted.controller.api.integration.organization.DeploymentIssues;
import com.yahoo.vespa.hosted.controller.api.integration.chef.Chef;
import com.yahoo.vespa.hosted.controller.maintenance.config.MaintainerConfig;
@@ -34,11 +35,12 @@ public class ControllerMaintenance extends AbstractComponent {
private final ClusterInfoMaintainer clusterInfoMaintainer;
private final ClusterUtilizationMaintainer clusterUtilizationMaintainer;
private final DeploymentMetricsMaintainer deploymentMetricsMaintainer;
+ private final ApplicationOwnershipConfirmer applicationOwnershipConfirmer;
@SuppressWarnings("unused") // instantiated by Dependency Injection
public ControllerMaintenance(MaintainerConfig maintainerConfig, Controller controller, CuratorDb curator,
JobControl jobControl, Metric metric, Chef chefClient,
- DeploymentIssues deploymentIssues) {
+ DeploymentIssues deploymentIssues, OwnershipIssues ownershipIssues) {
Duration maintenanceInterval = Duration.ofMinutes(maintainerConfig.intervalMinutes());
this.jobControl = jobControl;
deploymentExpirer = new DeploymentExpirer(controller, maintenanceInterval, jobControl);
@@ -53,6 +55,7 @@ public class ControllerMaintenance extends AbstractComponent {
clusterInfoMaintainer = new ClusterInfoMaintainer(controller, Duration.ofHours(2), jobControl);
clusterUtilizationMaintainer = new ClusterUtilizationMaintainer(controller, Duration.ofHours(2), jobControl);
deploymentMetricsMaintainer = new DeploymentMetricsMaintainer(controller, Duration.ofMinutes(10), jobControl);
+ applicationOwnershipConfirmer = new ApplicationOwnershipConfirmer(controller, Duration.ofHours(1), jobControl, ownershipIssues);
}
public Upgrader upgrader() { return upgrader; }
@@ -74,6 +77,7 @@ public class ControllerMaintenance extends AbstractComponent {
clusterUtilizationMaintainer.deconstruct();
clusterInfoMaintainer.deconstruct();
deploymentMetricsMaintainer.deconstruct();
+ applicationOwnershipConfirmer.deconstruct();
}
}
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 b4708dccb6b..4ab1426539d 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
@@ -67,7 +67,7 @@ public class DeploymentIssueReporter extends Maintainer {
if (failingApplications.contains(application.id()))
fileDeploymentIssueFor(application.id());
else
- storeIssueId(application.id(), null);
+ store(application.id(), null);
}
/**
@@ -111,7 +111,7 @@ public class DeploymentIssueReporter extends Maintainer {
IssueId issueId = tenant.tenantType() == TenantType.USER
? deploymentIssues.fileUnlessOpen(ourIssueId, applicationId, userFor(tenant))
: deploymentIssues.fileUnlessOpen(ourIssueId, applicationId, propertyIdFor(tenant));
- storeIssueId(applicationId, issueId);
+ store(applicationId, issueId);
}
catch (RuntimeException e) { // Catch errors due to wrong data in the controller, or issues client timeout.
log.log(Level.WARNING, "Exception caught when attempting to file an issue for " + applicationId, e);
@@ -130,7 +130,7 @@ public class DeploymentIssueReporter extends Maintainer {
}));
}
- private void storeIssueId(ApplicationId id, IssueId issueId) {
+ private void store(ApplicationId id, IssueId issueId) {
try (Lock lock = controller().applications().lock(id)) {
controller().applications().get(id, lock).ifPresent(
application -> controller().applications().store(application.with(issueId))
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
index 130d6f92d59..d294ad5dad8 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
@@ -51,6 +51,7 @@ public class ApplicationSerializer {
private final String deploymentJobsField = "deploymentJobs";
private final String deployingField = "deployingField";
private final String outstandingChangeField = "outstandingChangeField";
+ private final String ownershipIssueIdField = "ownershipIssueId";
// Deployment fields
private final String zoneField = "zone";
@@ -123,6 +124,7 @@ public class ApplicationSerializer {
toSlime(application.deploymentJobs(), root.setObject(deploymentJobsField));
toSlime(application.deploying(), root);
root.setBool(outstandingChangeField, application.hasOutstandingChange());
+ application.ownershipIssueId().ifPresent(issueId -> root.setString(ownershipIssueIdField, issueId.value()));
return slime;
}
@@ -257,9 +259,10 @@ public class ApplicationSerializer {
DeploymentJobs deploymentJobs = deploymentJobsFromSlime(root.field(deploymentJobsField));
Optional<Change> deploying = changeFromSlime(root.field(deployingField));
boolean outstandingChange = root.field(outstandingChangeField).asBool();
+ Optional<IssueId> ownershipIssueId = optionalString(root.field(ownershipIssueIdField)).map(IssueId::from);
return new Application(id, deploymentSpec, validationOverrides, deployments,
- deploymentJobs, deploying, outstandingChange);
+ deploymentJobs, deploying, outstandingChange, ownershipIssueId);
}
private List<Deployment> deploymentsFromSlime(Inspector array) {
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
new file mode 100644
index 00000000000..25daf81b319
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java
@@ -0,0 +1,107 @@
+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.ControllerTester;
+import com.yahoo.vespa.hosted.controller.api.Tenant;
+import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
+import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId;
+import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
+import com.yahoo.vespa.hosted.controller.api.integration.organization.OwnershipIssues;
+import com.yahoo.vespa.hosted.controller.api.integration.organization.User;
+import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.time.Duration;
+import java.util.Optional;
+import java.util.function.Supplier;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @#author jvenstad
+ */
+public class ApplicationOwnershipConfirmerTest {
+
+ private MockOwnershipIssues issues;
+ private ApplicationOwnershipConfirmer confirmer;
+ private ControllerTester tester;
+
+ @Before
+ public void setup() {
+ tester = new ControllerTester();
+ issues = new MockOwnershipIssues();
+ confirmer = new ApplicationOwnershipConfirmer(tester.controller(), Duration.ofDays(1), new JobControl(new MockCuratorDb()), issues);
+ }
+
+
+ @Test
+ public void testConfirmation() {
+ TenantId property = tester.createTenant("tenant", "domain", 1L);
+ ApplicationId propertyAppId = tester.createApplication(property, "application", "default", 1).id();
+ Supplier<Application> propertyApp = () -> tester.controller().applications().require(propertyAppId);
+
+ TenantId user = new TenantId("by-user");
+ tester.controller().tenants().addTenant(Tenant.createUserTenant(new TenantId("by-user")), Optional.empty());
+ assertTrue(tester.controller().tenants().tenant(user).isPresent());
+ ApplicationId userAppId = tester.createApplication(user, "application", "default", 1).id();
+ Supplier<Application> userApp = () -> tester.controller().applications().require(userAppId);
+
+ 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);
+
+ // Set response from the issue mock, which will be obtained by the maintainer on issue filing.
+ Optional<IssueId> issueId = Optional.of(IssueId.from("1"));
+ issues.response = issueId;
+ confirmer.maintain();
+ confirmer.maintain();
+
+ assertEquals("Confirmation issue has been filed for property owned application.", propertyApp.get().ownershipIssueId(), issueId);
+ assertEquals("Confirmation issue has been filed for user owned application.", userApp.get().ownershipIssueId(), issueId);
+ assertTrue("Both applications have had their responses ensured.", issues.escalatedForProperty && issues.escalatedForUser);
+
+ // No new issue is created, so return empty now.
+ issues.response = Optional.empty();
+ confirmer.maintain();
+ confirmer.maintain();
+
+ assertEquals("Confirmation issue reference is not updated when no issue id is returned.", propertyApp.get().ownershipIssueId(), issueId);
+
+ // Time has passed, and a new confirmation issue is in order.
+ Optional<IssueId> issueId2 = Optional.of(IssueId.from("2"));
+ issues.response = issueId2;
+ confirmer.maintain();
+ confirmer.maintain();
+
+ assertEquals("A new confirmation issue id is stored when something is returned to the maintainer.", propertyApp.get().ownershipIssueId(), issueId2);
+ }
+
+ private class MockOwnershipIssues implements OwnershipIssues {
+
+ private Optional<IssueId> response;
+ private boolean escalatedForProperty = false;
+ private boolean escalatedForUser = false;
+
+ @Override
+ public Optional<IssueId> confirmOwnership(Optional<IssueId> issueId, ApplicationId applicationId, PropertyId propertyId) {
+ 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;
+ }
+
+ }
+
+}
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 41cf7a331bb..e57edcf6da0 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
@@ -54,7 +54,7 @@ public class DeploymentIssueReporterTest {
public void setup() {
tester = new DeploymentTester();
issues = new MockDeploymentIssues();
- reporter = new DeploymentIssueReporter(tester.controller(), issues, Duration.ofMinutes(5), new JobControl(new MockCuratorDb()));
+ reporter = new DeploymentIssueReporter(tester.controller(), issues, Duration.ofDays(1), new JobControl(new MockCuratorDb()));
}
@Test
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
index daccab8efbf..c737308e247 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
@@ -13,6 +13,7 @@ import com.yahoo.slime.Slime;
import com.yahoo.vespa.config.SlimeUtils;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.ControllerTester;
+import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
import com.yahoo.vespa.hosted.controller.application.ApplicationRevision;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.ClusterInfo;
@@ -82,7 +83,8 @@ public class ApplicationSerializerTest {
validationOverrides,
deployments, deploymentJobs,
Optional.of(new Change.VersionChange(Version.fromString("6.7"))),
- true);
+ true,
+ Optional.of(IssueId.from("1234")));
Application serialized = applicationSerializer.fromSlime(applicationSerializer.toSlime(original));
@@ -108,6 +110,8 @@ public class ApplicationSerializerTest {
assertEquals(original.hasOutstandingChange(), serialized.hasOutstandingChange());
+ assertEquals(original.ownershipIssueId(), serialized.ownershipIssueId());
+
assertEquals(original.deploying(), serialized.deploying());
// Test cluster utilization
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 e6c0ce9027d..25b7d51b84f 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
@@ -43,6 +43,7 @@ public class ControllerContainerTest {
" <component id='com.yahoo.vespa.hosted.controller.api.integration.github.GitHubMock'/>" +
" <component id='com.yahoo.vespa.hosted.controller.api.integration.routing.MemoryGlobalRoutingService'/>" +
" <component id='com.yahoo.vespa.hosted.controller.api.integration.stubs.LoggingDeploymentIssues'/>" +
+ " <component id='com.yahoo.vespa.hosted.controller.api.integration.stubs.DummyOwnershipIssues'/>" +
" <component id='com.yahoo.vespa.hosted.controller.api.integration.organization.MockOrganization'/>" +
" <component id='com.yahoo.vespa.hosted.controller.ConfigServerClientMock'/>" +
" <component id='com.yahoo.vespa.hosted.controller.ZoneRegistryMock'/>" +
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json
index 3633860772b..31353e8a113 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json
@@ -13,6 +13,9 @@
"name": "FailureRedeployer"
},
{
+ "name": "ApplicationOwnershipConfirmer"
+ },
+ {
"name": "VersionStatusUpdater"
},
{