summaryrefslogtreecommitdiffstats
path: root/controller-server/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'controller-server/src/main')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java15
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java9
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentJobs.java11
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/JobStatus.java50
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java46
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterUtilizationMaintainer.java53
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java121
-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/DeploymentMetricsMaintainer.java87
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java14
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java46
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java15
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/metrics/MetricForwardingApiHandler.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiHandler.java1
16 files changed, 215 insertions, 278 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
index f046d23c5ce..c69fe5021e7 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
@@ -326,9 +326,18 @@ public class ApplicationController {
? triggered.sourceApplication().orElse(triggered.application())
: triggered.application();
- applicationPackage = application.get().deploymentJobs().deployedInternally()
- ? new ApplicationPackage(applicationStore.getApplicationPackage(application.get().id(), applicationVersion.id()))
- : new ApplicationPackage(artifactRepository.getApplicationPackage(application.get().id(), applicationVersion.id()));
+ try {
+ applicationPackage = application.get().deploymentJobs().deployedInternally()
+ ? new ApplicationPackage(applicationStore.getApplicationPackage(application.get().id(), applicationVersion.id()))
+ : new ApplicationPackage(artifactRepository.getApplicationPackage(application.get().id(), applicationVersion.id()));
+ }
+ catch (RuntimeException e) { // If application has switched deployment pipeline, artifacts stored prior to the switch are in the other artifact store.
+ log.info("Fetching application package for " + applicationId + " from alternate repository; it is now deployed "
+ + (application.get().deploymentJobs().deployedInternally() ? "internally" : "externally"));
+ applicationPackage = application.get().deploymentJobs().deployedInternally()
+ ? new ApplicationPackage(artifactRepository.getApplicationPackage(application.get().id(), applicationVersion.id()))
+ : new ApplicationPackage(applicationStore.getApplicationPackage(application.get().id(), applicationVersion.id()));
+ }
validateRun(application.get(), zone, platformVersion, applicationVersion);
}
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 eddf4e6b3a1..bd10a884213 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
@@ -113,6 +113,12 @@ public class LockedApplication {
ownershipIssueId, metrics, rotation, rotationStatus);
}
+ public LockedApplication withJobPause(JobType jobType, OptionalLong pausedUntil) {
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
+ deploymentJobs.withPause(jobType, pausedUntil), change, outstandingChange,
+ ownershipIssueId, metrics, rotation, rotationStatus);
+ }
+
public LockedApplication withJobCompletion(long projectId, JobType jobType, JobStatus.JobRun completion,
Optional<DeploymentJobs.JobError> jobError) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java
index 1fa579684de..0fb6459611c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java
@@ -69,6 +69,15 @@ public final class Change {
return new Change(platform, Optional.of(applicationVersion));
}
+ /** Returns the change obtained when overwriting elements of the given change with any present in this */
+ public Change onTopOf(Change other) {
+ if (platform.isPresent())
+ other = other.with(platform.get());
+ if (application.isPresent())
+ other = other.with(application.get());
+ return other;
+ }
+
@Override
public int hashCode() { return Objects.hash(platform, application); }
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentJobs.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentJobs.java
index 23826a47931..6e9318042b5 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentJobs.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentJobs.java
@@ -59,7 +59,7 @@ public class DeploymentJobs {
if (job == null) job = JobStatus.initial(jobType);
return job.withCompletion(completion, jobError);
});
- return new DeploymentJobs(OptionalLong.of(projectId), status, issueId, builtInternally);
+ return new DeploymentJobs(jobType == JobType.component ? OptionalLong.of(projectId) : this.projectId, status, issueId, builtInternally);
}
public DeploymentJobs withTriggering(JobType jobType, JobStatus.JobRun jobRun) {
@@ -71,6 +71,15 @@ public class DeploymentJobs {
return new DeploymentJobs(projectId, status, issueId, builtInternally);
}
+ public DeploymentJobs withPause(JobType jobType, OptionalLong pausedUntil) {
+ Map<JobType, JobStatus> status = new LinkedHashMap<>(this.status);
+ status.compute(jobType, (__, job) -> {
+ if (job == null) job = JobStatus.initial(jobType);
+ return job.withPause(pausedUntil);
+ });
+ return new DeploymentJobs(projectId, status, issueId, builtInternally);
+ }
+
public DeploymentJobs withProjectId(OptionalLong projectId) {
return new DeploymentJobs(projectId, status, issueId, builtInternally);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/JobStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/JobStatus.java
index a06a3e00340..59220d38821 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/JobStatus.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/JobStatus.java
@@ -7,6 +7,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import java.time.Instant;
import java.util.Objects;
import java.util.Optional;
+import java.util.OptionalLong;
import static java.util.Objects.requireNonNull;
@@ -25,6 +26,7 @@ public class JobStatus {
private final Optional<JobRun> lastCompleted;
private final Optional<JobRun> firstFailing;
private final Optional<JobRun> lastSuccess;
+ private final OptionalLong pausedUntil;
private final Optional<DeploymentJobs.JobError> jobError;
@@ -34,27 +36,23 @@ public class JobStatus {
*/
public JobStatus(JobType type, Optional<DeploymentJobs.JobError> jobError,
Optional<JobRun> lastTriggered, Optional<JobRun> lastCompleted,
- Optional<JobRun> firstFailing, Optional<JobRun> lastSuccess) {
- requireNonNull(type, "jobType cannot be null");
- requireNonNull(jobError, "jobError cannot be null");
- requireNonNull(lastTriggered, "lastTriggered cannot be null");
- requireNonNull(lastCompleted, "lastCompleted cannot be null");
- requireNonNull(firstFailing, "firstFailing cannot be null");
- requireNonNull(lastSuccess, "lastSuccess cannot be null");
-
- this.type = type;
- this.jobError = jobError;
+ Optional<JobRun> firstFailing, Optional<JobRun> lastSuccess,
+ OptionalLong pausedUntil) {
+ this.type = requireNonNull(type, "jobType cannot be null");
+ this.jobError = requireNonNull(jobError, "jobError cannot be null");
// Never say we triggered component because we don't:
- this.lastTriggered = type == JobType.component ? Optional.empty() : lastTriggered;
- this.lastCompleted = lastCompleted;
- this.firstFailing = firstFailing;
- this.lastSuccess = lastSuccess;
+ this.lastTriggered = type == JobType.component ? Optional.empty() : requireNonNull(lastTriggered, "lastTriggered cannot be null");
+ this.lastCompleted = requireNonNull(lastCompleted, "lastCompleted cannot be null");
+ this.firstFailing = requireNonNull(firstFailing, "firstFailing cannot be null");
+ this.lastSuccess = requireNonNull(lastSuccess, "lastSuccess cannot be null");
+ this.pausedUntil = requireNonNull(pausedUntil, "pausedUntil cannot be null");
+
}
/** Returns an empty job status */
public static JobStatus initial(JobType type) {
- return new JobStatus(type, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty());
+ return new JobStatus(type, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), OptionalLong.empty());
}
public JobStatus withTriggering(Version platform, ApplicationVersion application, Optional<Deployment> deployment, String reason, Instant triggeredAt) {
@@ -62,7 +60,7 @@ public class JobStatus {
}
public JobStatus withTriggering(JobRun jobRun) {
- return new JobStatus(type, jobError, Optional.of(jobRun), lastCompleted, firstFailing, lastSuccess);
+ return new JobStatus(type, jobError, Optional.of(jobRun), lastCompleted, firstFailing, lastSuccess, OptionalLong.empty());
}
public JobStatus withCompletion(long runId, Optional<DeploymentJobs.JobError> jobError, Instant completion) {
@@ -80,7 +78,11 @@ public class JobStatus {
firstFailing = Optional.empty();
}
- return new JobStatus(type, jobError, lastTriggered, Optional.of(completion), firstFailing, lastSuccess);
+ return new JobStatus(type, jobError, lastTriggered, Optional.of(completion), firstFailing, lastSuccess, pausedUntil);
+ }
+
+ public JobStatus withPause(OptionalLong pausedUntil) {
+ return new JobStatus(type, jobError, lastTriggered, lastCompleted, firstFailing, lastSuccess, pausedUntil);
}
public JobType type() { return type; }
@@ -113,17 +115,21 @@ public class JobStatus {
/** Returns the run when this last succeeded, or empty if it has never succeeded */
public Optional<JobRun> lastSuccess() { return lastSuccess; }
+ /** Returns the time until which this job is paused, if currently paused */
+ public OptionalLong pausedUntil() { return pausedUntil; }
+
@Override
public String toString() {
return "job status of " + type + "[ " +
"last triggered: " + lastTriggered.map(JobRun::toString).orElse("(never)") +
", last completed: " + lastCompleted.map(JobRun::toString).orElse("(never)") +
", first failing: " + firstFailing.map(JobRun::toString).orElse("(not failing)") +
- ", lastSuccess: " + lastSuccess.map(JobRun::toString).orElse("(never)") + "]";
+ ", lastSuccess: " + lastSuccess.map(JobRun::toString).orElse("(never)") +
+ ", pausedUntil: " + (pausedUntil.isPresent() ? pausedUntil.getAsLong() : "(not paused)") + "]";
}
@Override
- public int hashCode() { return Objects.hash(type, jobError, lastTriggered, lastCompleted, firstFailing, lastSuccess); }
+ public int hashCode() { return Objects.hash(type, jobError, lastTriggered, lastCompleted, firstFailing, lastSuccess, pausedUntil); }
@Override
public boolean equals(Object o) {
@@ -135,7 +141,8 @@ public class JobStatus {
Objects.equals(lastTriggered, other.lastTriggered) &&
Objects.equals(lastCompleted, other.lastCompleted) &&
Objects.equals(firstFailing, other.firstFailing) &&
- Objects.equals(lastSuccess, other.lastSuccess);
+ Objects.equals(lastSuccess, other.lastSuccess) &&
+ Objects.equals(pausedUntil, other.pausedUntil);
}
/** Information about a particular triggering or completion of a run of a job. This is immutable. */
@@ -202,7 +209,7 @@ public class JobStatus {
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (!(o instanceof JobRun)) return false;
+ if ( ! (o instanceof JobRun)) return false;
JobRun run = (JobRun) o;
@@ -224,6 +231,7 @@ public class JobStatus {
result = 31 * result + at.hashCode();
return result;
}
+
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
index 6dbdefb0913..69a42866a9e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
@@ -66,6 +66,8 @@ import static java.util.stream.Collectors.toList;
*/
public class DeploymentTrigger {
+ public static final Duration maxPause = Duration.ofDays(3);
+
private final static Logger log = Logger.getLogger(DeploymentTrigger.class.getName());
private final Controller controller;
@@ -222,31 +224,33 @@ public class DeploymentTrigger {
.map(Job::jobType).collect(toList());
}
+ /** Prevents jobs of the given type from starting, until the given time. */
+ public void pauseJob(ApplicationId id, JobType jobType, Instant until) {
+ if (until.isAfter(clock.instant().plus(maxPause)))
+ throw new IllegalArgumentException("Pause only allowed for up to " + maxPause);
+
+ applications().lockOrThrow(id, application ->
+ applications().store(application.withJobPause(jobType, OptionalLong.of(until.toEpochMilli()))));
+ }
+
/** Triggers a change of this application, unless it already has a change. */
public void triggerChange(ApplicationId applicationId, Change change) {
applications().lockOrThrow(applicationId, application -> {
- if ( ! application.get().change().isPresent()) {
- if (change.application().isPresent())
- application = application.withOutstandingChange(Change.empty());
-
- applications().store(application.withChange(change));
- }
+ if ( ! application.get().change().isPresent())
+ forceChange(applicationId, change);
});
}
/** Overrides the given application's platform and application changes with any contained in the given change. */
public void forceChange(ApplicationId applicationId, Change change) {
applications().lockOrThrow(applicationId, application -> {
- Change current = application.get().change();
- if (change.platform().isPresent())
- current = current.with(change.platform().get());
if (change.application().isPresent())
- current = current.with(change.application().get());
- applications().store(application.withChange(current));
+ application = application.withOutstandingChange(Change.empty());
+ applications().store(application.withChange(change.onTopOf(application.get().change())));
});
}
- /** Cancels a platform upgrade of the given application, and an application upgrade as well if {@code keepApplicationChange}. */
+ /** Cancels the indicated part of the given application's change. */
public void cancelChange(ApplicationId applicationId, ChangesToCancel cancellation) {
applications().lockOrThrow(applicationId, application -> {
Change change;
@@ -313,7 +317,7 @@ public class DeploymentTrigger {
for (Step step : steps.production()) {
List<JobType> stepJobs = steps.toJobs(step);
List<JobType> remainingJobs = stepJobs.stream().filter(job -> ! isComplete(change, application, job)).collect(toList());
- if (!remainingJobs.isEmpty()) { // Change is incomplete; trigger remaining jobs if ready, or their test jobs if untested.
+ if ( ! remainingJobs.isEmpty()) { // Change is incomplete; trigger remaining jobs if ready, or their test jobs if untested.
for (JobType job : remainingJobs) {
Versions versions = Versions.from(change, application, deploymentFor(application, job),
controller.systemVersion());
@@ -321,7 +325,7 @@ public class DeploymentTrigger {
if (completedAt.isPresent() && canTrigger(job, versions, application, stepJobs)) {
jobs.add(deploymentJob(application, versions, change, job, reason, completedAt.get()));
}
- if (!alreadyTriggered(application, versions)) {
+ if ( ! alreadyTriggered(application, versions)) {
testJobs = emptyList();
}
}
@@ -348,10 +352,7 @@ public class DeploymentTrigger {
}
}
if (testJobs == null) { // If nothing to test, but outstanding commits, test those.
- Change latestChange = application.outstandingChange().application().isPresent()
- ? change.with(application.outstandingChange().application().get())
- : change;
- testJobs = testJobs(application, Versions.from(latestChange,
+ testJobs = testJobs(application, Versions.from(application.outstandingChange().onTopOf(application.change()),
application,
steps.sortedDeployments(application.productionDeployments().values()).stream().findFirst(),
controller.systemVersion()),
@@ -392,11 +393,12 @@ public class DeploymentTrigger {
/** Returns whether the given job can trigger at the given instant */
public boolean triggerAt(Instant instant, JobType job, Versions versions, Application application) {
Optional<JobStatus> jobStatus = application.deploymentJobs().statusOf(job);
- if (!jobStatus.isPresent()) return true;
+ if ( ! jobStatus.isPresent()) return true;
+ if (jobStatus.get().pausedUntil().isPresent() && jobStatus.get().pausedUntil().getAsLong() > clock.instant().toEpochMilli()) return false;
if (jobStatus.get().isSuccess()) return true; // Success
- if (!jobStatus.get().lastCompleted().isPresent()) return true; // Never completed
- if (!jobStatus.get().firstFailing().isPresent()) return true; // Should not happen as firstFailing should be set for an unsuccessful job
- if (!versions.targetsMatch(jobStatus.get().lastCompleted().get())) return true; // Always trigger as targets have changed
+ if ( ! jobStatus.get().lastCompleted().isPresent()) return true; // Never completed
+ if ( ! jobStatus.get().firstFailing().isPresent()) return true; // Should not happen as firstFailing should be set for an unsuccessful job
+ if ( ! versions.targetsMatch(jobStatus.get().lastCompleted().get())) return true; // Always trigger as targets have changed
if (application.deploymentSpec().upgradePolicy() == DeploymentSpec.UpgradePolicy.canary) return true; // Don't throttle canaries
Instant firstFailing = jobStatus.get().firstFailing().get().at();
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterUtilizationMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterUtilizationMaintainer.java
index 44f29eb6113..ac080fe15d7 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterUtilizationMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterUtilizationMaintainer.java
@@ -3,26 +3,17 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterSpec;
-import com.yahoo.slime.Cursor;
-import com.yahoo.slime.Slime;
-import com.yahoo.vespa.config.SlimeUtils;
import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.MetricsService;
+import com.yahoo.vespa.hosted.controller.application.ApplicationList;
import com.yahoo.vespa.hosted.controller.application.ClusterUtilization;
import com.yahoo.vespa.hosted.controller.application.Deployment;
-import com.yahoo.vespa.hosted.controller.authority.config.ApiAuthorityConfig;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.entity.ByteArrayEntity;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClientBuilder;
import java.time.Duration;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
-import java.util.logging.Level;
/**
* Fetch utilization metrics and update applications with this data.
@@ -32,12 +23,10 @@ import java.util.logging.Level;
public class ClusterUtilizationMaintainer extends Maintainer {
private final Controller controller;
- private final List<String> baseUris;
- public ClusterUtilizationMaintainer(Controller controller, Duration duration, JobControl jobControl, ApiAuthorityConfig apiAuthorityConfig) {
+ public ClusterUtilizationMaintainer(Controller controller, Duration duration, JobControl jobControl) {
super(controller, duration, jobControl);
this.controller = controller;
- this.baseUris = apiAuthorityConfig.authorities();
}
private Map<ClusterSpec.Id, ClusterUtilization> getUpdatedClusterUtilizations(ApplicationId app, ZoneId zone) {
@@ -55,45 +44,15 @@ public class ClusterUtilizationMaintainer extends Maintainer {
@Override
protected void maintain() {
- try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
- String uri = baseUris.get(0) + "metricforwarding/v1/clusterutilization"; // For now, we only feed to one controller
- Slime slime = getMetricSlime();
- ByteArrayEntity entity = new ByteArrayEntity(SlimeUtils.toJsonBytes(slime));
- HttpPost httpPost = new HttpPost(uri);
- httpPost.setEntity(entity);
- httpClient.execute(httpPost);
- } catch (Exception e) {
- log.log(Level.WARNING, "Failed to update cluster utilization metrics", e);
- }
-
- }
-
- private Slime getMetricSlime() {
- Slime slime = new Slime();
- Cursor cursor = slime.setArray();
for (Application application : controller().applications().asList()) {
- Cursor applicationCursor = cursor.addObject();
- applicationCursor.setString("applicationId", application.id().serializedForm());
- Cursor deploymentArray = applicationCursor.setArray("deployments");
for (Deployment deployment : application.deployments().values()) {
- Cursor deploymentEntry = deploymentArray.addObject();
- deploymentEntry.setString("zoneId", deployment.zone().value());
- Cursor clusterArray = deploymentEntry.setArray("clusterUtil");
+
Map<ClusterSpec.Id, ClusterUtilization> clusterUtilization = getUpdatedClusterUtilizations(application.id(), deployment.zone());
- fillClusterUtilization(clusterArray, clusterUtilization);
+
+ controller().applications().lockIfPresent(application.id(), lockedApplication ->
+ controller().applications().store(lockedApplication.withClusterUtilization(deployment.zone(), clusterUtilization)));
}
}
- return slime;
}
- private void fillClusterUtilization(Cursor cursor, Map<ClusterSpec.Id, ClusterUtilization> clusterUtilization) {
- for (Map.Entry<ClusterSpec.Id, ClusterUtilization> entry : clusterUtilization.entrySet()) {
- Cursor clusterUtilCursor = cursor.addObject();
- clusterUtilCursor.setString("clusterSpecId", entry.getKey().value());
- clusterUtilCursor.setDouble("cpu", entry.getValue().getCpu());
- clusterUtilCursor.setDouble("memory", entry.getValue().getMemory());
- clusterUtilCursor.setDouble("disk", entry.getValue().getDisk());
- clusterUtilCursor.setDouble("diskBusy", entry.getValue().getDiskBusy());
- }
- }
}
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 3cf3c47a6f3..bf5743e2d3c 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,31 +1,17 @@
// 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.authority.config.ApiAuthorityConfig;
import com.yahoo.config.provision.SystemName;
import com.yahoo.log.LogLevel;
-import com.yahoo.slime.ArrayTraverser;
-import com.yahoo.slime.Cursor;
-import com.yahoo.slime.Inspector;
-import com.yahoo.slime.Slime;
-import com.yahoo.vespa.config.SlimeUtils;
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.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 org.apache.http.HttpResponse;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.entity.ByteArrayEntity;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClientBuilder;
-import org.apache.http.util.EntityUtils;
-import java.io.IOException;
import java.time.Duration;
-import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
@@ -43,107 +29,44 @@ public class ContactInformationMaintainer extends Maintainer {
private static final Logger log = Logger.getLogger(ContactInformationMaintainer.class.getName());
private final Organization organization;
- private final List<String> baseUris;
- private CloseableHttpClient httpClient = HttpClientBuilder.create().build();
- public ContactInformationMaintainer(Controller controller, Duration interval, JobControl jobControl, Organization organization, ApiAuthorityConfig apiAuthorityConfig) {
+ public ContactInformationMaintainer(Controller controller, Duration interval, JobControl jobControl, Organization organization) {
super(controller, interval, jobControl, null, EnumSet.of(SystemName.cd, SystemName.main));
this.organization = Objects.requireNonNull(organization, "organization must be non-null");
- this.baseUris = apiAuthorityConfig.authorities();
}
- // The maintainer will eventually feed contact info to systems other than its own, determined by the baseUris list.
@Override
protected void maintain() {
- for (String baseUri : baseUris) {
- for (String tenantName : getTenantList(baseUri)) {
- Optional<PropertyId> tenantPropertyId = getPropertyId(tenantName, baseUri);
- if (!tenantPropertyId.isPresent())
- continue;
- findContact(tenantPropertyId.get()).ifPresent(contact -> {
- feedContact(tenantName, contact, baseUri);
+ 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)));
});
+ } catch (Exception e) {
+ log.log(LogLevel.WARNING, "Failed to update contact information for " + tenant + ": " +
+ Exceptions.toMessageString(e) + ". Retrying in " +
+ maintenanceInterval());
}
}
}
- private void feedContact(String tenantName, Contact contact, String baseUri) {
- try {
- CloseableHttpClient httpClient = HttpClientBuilder.create().build();
- String uri = baseUri + "contactinfo/v1/tenant/" + tenantName;
- HttpPost httpPost = new HttpPost(uri);
- httpPost.setEntity(contactToByteArrayEntity(contact));
- httpClient.execute(httpPost);
- } catch (Exception e) {
- log.log(LogLevel.WARNING, "Failed to update contact information for " + tenantName + ": " +
- Exceptions.toMessageString(e) + ". Retrying in " +
- maintenanceInterval());
- }
- }
-
- private ByteArrayEntity contactToByteArrayEntity(Contact contact) throws IOException {
- Slime slime = new Slime();
- Cursor cursor = slime.setObject();
- cursor.setString("url", contact.url().toString());
- cursor.setString("issueTrackerUrl", contact.issueTrackerUrl().toString());
- cursor.setString("propertyUrl", contact.propertyUrl().toString());
- Cursor personsCursor = cursor.setArray("persons");
- for (List<String> personList : contact.persons()) {
- Cursor sublist = personsCursor.addArray();
- for(String person : personList) {
- sublist.addString(person);
- }
- }
- return new ByteArrayEntity(SlimeUtils.toJsonBytes(slime));
- }
-
- private List<String> getTenantList(String baseUri) {
- List<String> tenantList = new ArrayList<>();
- HttpGet getRequest = new HttpGet(baseUri + "application/v4/tenant/");
- try {
- HttpResponse response = httpClient.execute(getRequest);
- Slime slime = SlimeUtils.jsonToSlime(EntityUtils.toByteArray(response.getEntity()));
- Inspector inspector = slime.get();
- inspector.traverse((ArrayTraverser) (index, tenant) -> {
- tenantList.add(tenant.field("tenant").asString());
- });
- } catch (IOException e) {
- log.log(LogLevel.WARNING, "Failed to get tenant list from base URI: " + baseUri.toString() +
- Exceptions.toMessageString(e) + ". Retrying in " +
- maintenanceInterval());
- }
- return tenantList;
- }
-
- private Optional<PropertyId> getPropertyId(String tenantName, String baseUri) {
- Optional<PropertyId> propertyId = Optional.empty();
- HttpGet getRequest = new HttpGet(baseUri + "application/v4/tenant/" + tenantName);
- try {
- HttpResponse response = httpClient.execute(getRequest);
- Slime slime = SlimeUtils.jsonToSlime(EntityUtils.toByteArray(response.getEntity()));
- Inspector inspector = slime.get();
- if (!inspector.field("propertyId").valid()) {
- log.log(LogLevel.WARNING, "Unable to get property id for " + tenantName);
- return Optional.empty();
- }
- propertyId = Optional.of(new PropertyId(inspector.field("propertyId").asString()));
- } catch (IOException e) {
- log.log(LogLevel.WARNING, "Unable to get property idfor " + tenantName, e);
- }
- return propertyId;
- }
-
/** Find contact information for given tenant */
- private Optional<Contact> findContact(PropertyId propertyId) {
- List<List<String>> persons = organization.contactsFor(propertyId)
+ 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(propertyId),
- organization.propertyUri(propertyId),
- organization.issueCreationUri(propertyId),
+ 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 b2968270af9..7b17f38bd78 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
@@ -65,15 +65,15 @@ public class ControllerMaintenance extends AbstractComponent {
upgrader = new Upgrader(controller, maintenanceInterval, jobControl, curator);
readyJobsTrigger = new ReadyJobsTrigger(controller, Duration.ofMinutes(1), jobControl);
clusterInfoMaintainer = new ClusterInfoMaintainer(controller, Duration.ofHours(2), jobControl, nodeRepositoryClient);
- clusterUtilizationMaintainer = new ClusterUtilizationMaintainer(controller, Duration.ofHours(2), jobControl, apiAuthorityConfig);
- deploymentMetricsMaintainer = new DeploymentMetricsMaintainer(controller, Duration.ofMinutes(10), jobControl, apiAuthorityConfig);
+ clusterUtilizationMaintainer = new ClusterUtilizationMaintainer(controller, Duration.ofHours(2), jobControl);
+ deploymentMetricsMaintainer = new DeploymentMetricsMaintainer(controller, Duration.ofMinutes(10), jobControl);
applicationOwnershipConfirmer = new ApplicationOwnershipConfirmer(controller, Duration.ofHours(12), jobControl, ownershipIssues);
dnsMaintainer = new DnsMaintainer(controller, Duration.ofHours(12), jobControl, nameService);
systemUpgrader = new SystemUpgrader(controller, Duration.ofMinutes(1), jobControl);
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, apiAuthorityConfig);
+ contactInformationMaintainer = new ContactInformationMaintainer(controller, Duration.ofHours(12), jobControl, organization);
}
public Upgrader upgrader() { return upgrader; }
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java
index 67fb224f1ea..c17d070e92e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java
@@ -2,23 +2,16 @@
package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.config.provision.HostName;
-import com.yahoo.slime.Cursor;
-import com.yahoo.slime.Slime;
-import com.yahoo.vespa.config.SlimeUtils;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.ApplicationController;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.MetricsService;
+import com.yahoo.vespa.hosted.controller.application.ApplicationList;
import com.yahoo.vespa.hosted.controller.application.Deployment;
+import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
import com.yahoo.vespa.hosted.controller.application.RotationStatus;
-import com.yahoo.vespa.hosted.controller.authority.config.ApiAuthorityConfig;
import com.yahoo.yolean.Exceptions;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.entity.ByteArrayEntity;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClientBuilder;
-import java.io.IOException;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
@@ -45,12 +38,10 @@ public class DeploymentMetricsMaintainer extends Maintainer {
private static final int applicationsToUpdateInParallel = 10;
private final ApplicationController applications;
- private final List<String> baseUris;
- public DeploymentMetricsMaintainer(Controller controller, Duration duration, JobControl jobControl, ApiAuthorityConfig apiAuthorityConfig) {
+ public DeploymentMetricsMaintainer(Controller controller, Duration duration, JobControl jobControl) {
super(controller, duration, jobControl);
this.applications = controller.applications();
- baseUris = apiAuthorityConfig.authorities();
}
@Override
@@ -61,27 +52,37 @@ public class DeploymentMetricsMaintainer extends Maintainer {
// Run parallel stream inside a custom ForkJoinPool so that we can control the number of threads used
ForkJoinPool pool = new ForkJoinPool(applicationsToUpdateInParallel);
- Slime slime = new Slime();
- Cursor cursor = slime.setArray();
pool.submit(() -> {
applicationList.parallelStream().forEach(application -> {
- Cursor applicationCursor = cursor.addObject();
- applicationCursor.setString("applicationId", application.id().serializedForm());
- Cursor applicationMetrics = applicationCursor.setObject("applicationMetrics");
- fillApplicationMetrics(applicationMetrics, application);
- Cursor rotationStatus = applicationCursor.setArray("rotationStatus");
- fillRotationStatus(rotationStatus, application);
- Cursor deploymentArray = applicationCursor.setArray("deploymentMetrics");
- for (Deployment deployment : application.deployments().values()) {
- Cursor deploymentEntry = deploymentArray.addObject();
- fillDeploymentMetrics(deploymentEntry, application, deployment);
+ try {
+ applications.lockIfPresent(application.id(), locked ->
+ applications.store(locked.with(controller().metricsService().getApplicationMetrics(application.id()))));
+
+ applications.lockIfPresent(application.id(), locked ->
+ applications.store(locked.withRotationStatus(rotationStatus(application))));
+
+ for (Deployment deployment : application.deployments().values()) {
+ MetricsService.DeploymentMetrics deploymentMetrics = controller().metricsService()
+ .getDeploymentMetrics(application.id(), deployment.zone());
+ DeploymentMetrics newMetrics = new DeploymentMetrics(deploymentMetrics.queriesPerSecond(),
+ deploymentMetrics.writesPerSecond(),
+ deploymentMetrics.documentCount(),
+ deploymentMetrics.queryLatencyMillis(),
+ deploymentMetrics.writeLatencyMillis());
+
+ applications.lockIfPresent(application.id(), locked ->
+ applications.store(locked.with(deployment.zone(), newMetrics)
+ .recordActivityAt(controller().clock().instant(), deployment.zone())));
+ }
+ } catch (Exception e) {
+ failures.incrementAndGet();
+ lastException.set(e);
}
});
});
pool.shutdown();
try {
pool.awaitTermination(30, TimeUnit.MINUTES);
- feedMetrics(slime);
if (lastException.get() != null) {
log.log(Level.WARNING, String.format("Failed to query metrics service for %d/%d applications. Last error: %s. Retrying in %s",
failures.get(),
@@ -91,8 +92,6 @@ public class DeploymentMetricsMaintainer extends Maintainer {
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
- } catch (IOException e) {
- log.log(Level.WARNING, "Unable to feed metrics to API", e);
}
}
@@ -108,40 +107,6 @@ public class DeploymentMetricsMaintainer extends Maintainer {
.orElseGet(Collections::emptyMap);
}
- private void fillApplicationMetrics(Cursor applicationCursor, Application application) {
- MetricsService.ApplicationMetrics metrics = controller().metricsService().getApplicationMetrics(application.id());
- applicationCursor.setDouble("queryServiceQuality", metrics.queryServiceQuality());
- applicationCursor.setDouble("writeServiceQuality", metrics.writeServiceQuality());
- }
-
- private void fillRotationStatus(Cursor rotationStatusCursor, Application application) {
- Map<HostName, RotationStatus> rotationStatus = rotationStatus(application);
- for (Map.Entry<HostName, RotationStatus> entry : rotationStatus.entrySet()) {
- Cursor rotationStatusEntry = rotationStatusCursor.addObject();
- rotationStatusEntry.setString("hostname", entry.getKey().value());
- rotationStatusEntry.setString("rotationStatus", entry.getValue().toString());
- }
- }
-
- private void fillDeploymentMetrics(Cursor deploymentCursor, Application application, Deployment deployment) {
- MetricsService.DeploymentMetrics deploymentMetrics = controller().metricsService()
- .getDeploymentMetrics(application.id(), deployment.zone());
- deploymentCursor.setString("zoneId", deployment.zone().value());
- deploymentCursor.setDouble("queriesPerSecond", deploymentMetrics.queriesPerSecond());
- deploymentCursor.setDouble("writesPerSecond", deploymentMetrics.writesPerSecond());
- deploymentCursor.setDouble("documentCount", deploymentMetrics.documentCount());
- deploymentCursor.setDouble("queryLatencyMillis", deploymentMetrics.queryLatencyMillis());
- deploymentCursor.setDouble("writeLatencyMillis", deploymentMetrics.writeLatencyMillis());
- }
-
- private void feedMetrics(Slime slime) throws IOException {
- String uri = baseUris.get(0) + "/metricforwarding/v1/deploymentmetrics/"; // For now, we only feed to one controller
- CloseableHttpClient httpClient = HttpClientBuilder.create().build();
- HttpPost httpPost = new HttpPost(uri);
- httpPost.setEntity(new ByteArrayEntity(SlimeUtils.toJsonBytes(slime)));
- httpClient.execute(httpPost);
- }
-
private static RotationStatus from(com.yahoo.vespa.hosted.controller.api.integration.routing.RotationStatus status) {
switch (status) {
case IN: return RotationStatus.in;
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 7b615249a65..365d74babb5 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
@@ -95,6 +95,7 @@ public class ApplicationSerializer {
private final String lastCompletedField = "lastCompleted";
private final String firstFailingField = "firstFailing";
private final String lastSuccessField = "lastSuccess";
+ private final String pausedUntilField = "pausedUntil";
// JobRun fields
private final String jobRunIdField = "id";
@@ -252,6 +253,7 @@ public class ApplicationSerializer {
jobStatus.lastCompleted().ifPresent(run -> jobRunToSlime(run, object, lastCompletedField));
jobStatus.lastSuccess().ifPresent(run -> jobRunToSlime(run, object, lastSuccessField));
jobStatus.firstFailing().ifPresent(run -> jobRunToSlime(run, object, firstFailingField));
+ jobStatus.pausedUntil().ifPresent(until -> object.setLong(pausedUntilField, until));
}
private void jobRunToSlime(JobStatus.JobRun jobRun, Cursor parent, String jobRunObjectName) {
@@ -440,11 +442,13 @@ public class ApplicationSerializer {
if (object.field(errorField).valid())
jobError = Optional.of(JobError.valueOf(object.field(errorField).asString()));
- return Optional.of(new JobStatus(jobType.get(), jobError,
- jobRunFromSlime(object.field(lastTriggeredField)),
- jobRunFromSlime(object.field(lastCompletedField)),
- jobRunFromSlime(object.field(firstFailingField)),
- jobRunFromSlime(object.field(lastSuccessField))));
+ return Optional.of(new JobStatus(jobType.get(),
+ jobError,
+ jobRunFromSlime(object.field(lastTriggeredField)),
+ jobRunFromSlime(object.field(lastCompletedField)),
+ jobRunFromSlime(object.field(firstFailingField)),
+ jobRunFromSlime(object.field(lastSuccessField)),
+ optionalLong(object.field(pausedUntilField))));
}
private Optional<JobStatus.JobRun> jobRunFromSlime(Inspector object) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
index 4c6baece7f1..dac65f16832 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
@@ -65,7 +65,7 @@ import com.yahoo.vespa.hosted.controller.application.JobStatus;
import com.yahoo.vespa.hosted.controller.application.RotationStatus;
import com.yahoo.vespa.hosted.controller.application.SourceRevision;
import com.yahoo.vespa.hosted.controller.athenz.impl.ZmsClientFacade;
-import com.yahoo.vespa.hosted.controller.deployment.DeploymentTrigger.ChangesToCancel;
+import com.yahoo.vespa.hosted.controller.deployment.DeploymentTrigger;
import com.yahoo.vespa.hosted.controller.restapi.ErrorResponse;
import com.yahoo.vespa.hosted.controller.restapi.MessageResponse;
import com.yahoo.vespa.hosted.controller.restapi.ResourceResponse;
@@ -89,6 +89,7 @@ import java.net.URISyntaxException;
import java.security.Principal;
import java.time.DayOfWeek;
import java.time.Duration;
+import java.time.Instant;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -99,6 +100,7 @@ import java.util.logging.Level;
import java.util.stream.Collectors;
import static com.yahoo.vespa.hosted.controller.deployment.DeploymentTrigger.ChangesToCancel.ALL;
+import static java.util.stream.Collectors.joining;
/**
* This implements the application/v4 API which is used to deploy and manage applications
@@ -202,6 +204,8 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying")) return deploy(path.get("tenant"), path.get("application"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/jobreport")) return notifyJobCompletion(path.get("tenant"), path.get("application"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/submit")) return submit(path.get("tenant"), path.get("application"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}")) return trigger(appIdFromPath(path), jobTypeFromPath(path), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}/pause")) return pause(appIdFromPath(path), jobTypeFromPath(path));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}")) return deploy(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/deploy")) return deploy(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request); // legacy synonym of the above
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/restart")) return restart(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
@@ -214,6 +218,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}")) return deleteTenant(path.get("tenant"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}")) return deleteApplication(path.get("tenant"), path.get("application"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying")) return cancelDeploy(path.get("tenant"), path.get("application"));
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/submit")) return JobControllerApiHandlerHelper.unregisterResponse(controller.jobController(), path.get("tenant"), path.get("application"));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}")) return JobControllerApiHandlerHelper.abortJobResponse(controller.jobController(), appIdFromPath(path), jobTypeFromPath(path));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}")) return deactivate(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/global-rotation/override"))
@@ -366,6 +371,19 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
return new SlimeJsonResponse(slime);
}
+ private HttpResponse trigger(ApplicationId id, JobType type, HttpRequest request) {
+ String triggered = controller.applications().deploymentTrigger()
+ .forceTrigger(id, type, request.getJDiscRequest().getUserPrincipal().getName())
+ .stream().map(JobType::jobName).collect(joining(", "));
+ return new MessageResponse("Triggered " + triggered + " for " + id);
+ }
+
+ private HttpResponse pause(ApplicationId id, JobType type) {
+ Instant until = controller.clock().instant().plus(DeploymentTrigger.maxPause);
+ controller.applications().deploymentTrigger().pauseJob(id, type, until);
+ return new MessageResponse(type.jobName() + " for " + id + " paused for " + DeploymentTrigger.maxPause);
+ }
+
private HashMap<String, String> getParameters(String query) {
HashMap<String, String> keyValPair = new HashMap<>();
Arrays.stream(query.split("&")).forEach(pair -> {
@@ -738,8 +756,11 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
return new SlimeJsonResponse(slime);
}
- /** Trigger deployment of the last built application package, on a given version */
- // TODO Consider move to API for maintenance related operations
+ /**
+ * Trigger deployment of the given Vespa version if a valid one is given, e.g., "7.8.9",
+ * or the latest known commit of the application if "commit" is given,
+ * or an upgrade to the system version if no data is provided.
+ */
private HttpResponse deploy(String tenantName, String applicationName, HttpRequest request) {
ApplicationId id = ApplicationId.from(tenantName, applicationName, "default");
String requestVersion = readToString(request.getData());
@@ -758,17 +779,16 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
.stream()
.map(VespaVersion::versionNumber)
.map(Version::toString)
- .collect(Collectors.joining(", ")));
+ .collect(joining(", ")));
change = Change.of(version);
}
- controller.applications().deploymentTrigger().forceChange(application.get().id(), change);
- response.append("Triggered " + change + " for " + application);
+ controller.applications().deploymentTrigger().forceChange(id, change);
+ response.append("Triggered " + change + " for " + id);
});
return new MessageResponse(response.toString());
}
/** Cancel any ongoing change for given application */
- // TODO Consider move to API for maintenance related operations
private HttpResponse cancelDeploy(String tenantName, String applicationName) {
ApplicationId id = ApplicationId.from(tenantName, applicationName, "default");
Application application = controller.applications().require(id);
@@ -777,7 +797,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
return new MessageResponse("No deployment in progress for " + application + " at this time");
controller.applications().lockOrThrow(id, lockedApplication ->
- controller.applications().deploymentTrigger().cancelChange(id, ALL));
+ controller.applications().deploymentTrigger().cancelChange(id, ALL));
return new MessageResponse("Cancelled " + change + " for " + application);
}
@@ -895,9 +915,15 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
}
- private HttpResponse notifyJobCompletion(String tenant, String applicationName, HttpRequest request) {
+ private HttpResponse notifyJobCompletion(String tenant, String application, HttpRequest request) {
try {
- controller.applications().deploymentTrigger().notifyOfCompletion(toJobReport(tenant, applicationName, toSlime(request.getData()).get()));
+ DeploymentJobs.JobReport report = toJobReport(tenant, application, toSlime(request.getData()).get());
+ if (controller.applications().require(report.applicationId()).deploymentJobs().deployedInternally())
+ throw new IllegalArgumentException(report.applicationId() + " is set up to be deployed from internally, and no " +
+ "longer accepts reports from Screwdriver v3 jobs. If you need to revert " +
+ "to the old pipeline, please file a ticket at yo/vespa-support and request this.");
+
+ controller.applications().deploymentTrigger().notifyOfCompletion(report);
return new MessageResponse("ok");
} catch (IllegalStateException e) {
return ErrorResponse.badRequest(Exceptions.toMessageString(e));
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
index 0d02c4003e7..fa5d713d6b3 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
@@ -203,6 +203,8 @@ class JobControllerApiHandlerHelper {
private static void jobTypeToSlime(Cursor jobObject, Controller controller, Application application, JobType type, DeploymentSteps steps,
Map<JobType, Versions> pendingProduction, Map<JobType, Run> running, URI baseUriForJob) {
+ application.deploymentJobs().statusOf(type).ifPresent(status -> status.pausedUntil().ifPresent(until ->
+ jobObject.setLong("pausedUntil", until)));
int runs = 0;
Cursor runArray = jobObject.setArray("runs");
if (type.isTest()) {
@@ -239,7 +241,9 @@ class JobControllerApiHandlerHelper {
runObject.setString("status", "pending");
versionsToSlime(runObject, pendingProduction.get(type));
Cursor pendingObject = runObject.setObject("tasks");
- if ( ! controller.applications().deploymentTrigger().triggerAt(controller.clock().instant(), type, versions, application))
+ if (application.deploymentJobs().statusOf(type).map(status -> status.pausedUntil().isPresent()).orElse(false))
+ pendingObject.setString("paused", "pending");
+ else if ( ! controller.applications().deploymentTrigger().triggerAt(controller.clock().instant(), type, versions, application))
pendingObject.setString("cooldown", "failed");
else {
int pending = 0;
@@ -421,5 +425,14 @@ class JobControllerApiHandlerHelper {
return new SlimeJsonResponse(slime);
}
+ /** Unregisters the application from the internal deployment pipeline. */
+ static HttpResponse unregisterResponse(JobController jobs, String tenantName, String applicationName) {
+ ApplicationId id = ApplicationId.from(tenantName, applicationName, "default");
+ jobs.unregister(id);
+ Slime slime = new Slime();
+ slime.setObject().setString("message", "Unregistered '" + id + "' from internal deployment pipeline.");
+ return new SlimeJsonResponse(slime);
+ }
+
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java
index b9f91a35790..b1e3f8799d6 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java
@@ -117,8 +117,7 @@ public class ControllerAuthorizationFilter extends CorsRequestFilterBase {
private static boolean isHostedOperatorOperation(Path path, Method method) {
if (isWhiteListedOperation(path, method)) return false;
- return path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying") ||
- path.matches("/controller/v1/{*}") ||
+ return path.matches("/controller/v1/{*}") ||
path.matches("/provision/v2/{*}") ||
path.matches("/screwdriver/v1/trigger/tenant/{*}") ||
path.matches("/os/v1/{*}") ||
@@ -131,7 +130,8 @@ public class ControllerAuthorizationFilter extends CorsRequestFilterBase {
if (isHostedOperatorOperation(path, method)) return false;
return path.matches("/application/v4/tenant/{tenant}") ||
path.matches("/application/v4/tenant/{tenant}/application/{application}") ||
- path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{job}") ||
+ path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying") ||
+ path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{job}/{*}") ||
path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/dev/{*}") ||
path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/perf/{*}") ||
path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/global-rotation/override");
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/metrics/MetricForwardingApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/metrics/MetricForwardingApiHandler.java
index 91c847d10f1..f3112b09173 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/metrics/MetricForwardingApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/metrics/MetricForwardingApiHandler.java
@@ -26,6 +26,7 @@ import com.yahoo.vespa.hosted.controller.restapi.StringResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
+import java.util.logging.Level;
/**
* This implements the metricforwarding/v1 API which allows feeding
@@ -72,7 +73,8 @@ public class MetricForwardingApiHandler extends LoggingRequestHandler {
});
});
} catch (IOException e) {
- ErrorResponse.badRequest("Unable to parse request for metrics - " + e.getMessage());
+ log.log(Level.WARNING, "Unable to parse request for cluster utilization metrics", e);
+ return ErrorResponse.badRequest("Unable to parse request for cluster utilization metrics - " + e.getMessage());
}
return new StringResponse("Added cluster utilization metrics");
}
@@ -99,7 +101,8 @@ public class MetricForwardingApiHandler extends LoggingRequestHandler {
}
});
} catch (IOException e) {
- ErrorResponse.badRequest("Unable to parse request for metrics - " + e.getMessage());
+ log.log(Level.WARNING, "Unable to parse request for deployment metrics", e);
+ return ErrorResponse.badRequest("Unable to parse request for deployment metrics - " + e.getMessage());
}
return new StringResponse("Added deployment metrics");
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiHandler.java
index 6633eafc509..14bb89520d7 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiHandler.java
@@ -26,6 +26,7 @@ import java.util.logging.Logger;
import static java.util.stream.Collectors.joining;
+// TODO jvenstad: Only useful method has been moved to application API. Delete this when users have updated.
/**
* This API lists deployment jobs that are queued for execution on Screwdriver.
*