diff options
Diffstat (limited to 'controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java')
-rw-r--r-- | controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java | 300 |
1 files changed, 0 insertions, 300 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java deleted file mode 100644 index edfa4d01d78..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java +++ /dev/null @@ -1,300 +0,0 @@ -// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.restapi.deployment; - -import com.yahoo.component.Version; -import com.yahoo.config.application.api.DeploymentInstanceSpec; -import com.yahoo.config.application.api.DeploymentSpec; -import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.CloudAccount; -import com.yahoo.container.jdisc.EmptyResponse; -import com.yahoo.container.jdisc.HttpRequest; -import com.yahoo.container.jdisc.HttpResponse; -import com.yahoo.container.jdisc.ThreadedHttpRequestHandler; -import com.yahoo.restapi.ErrorResponse; -import com.yahoo.restapi.Path; -import com.yahoo.restapi.SlimeJsonResponse; -import com.yahoo.restapi.UriBuilder; -import com.yahoo.slime.Cursor; -import com.yahoo.slime.Slime; -import com.yahoo.vespa.hosted.controller.Controller; -import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; -import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion; -import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobId; -import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; -import com.yahoo.vespa.hosted.controller.application.ApplicationList; -import com.yahoo.vespa.hosted.controller.application.Change; -import com.yahoo.vespa.hosted.controller.application.Deployment; -import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId; -import com.yahoo.vespa.hosted.controller.deployment.DeploymentStatus; -import com.yahoo.vespa.hosted.controller.deployment.DeploymentStatus.DelayCause; -import com.yahoo.vespa.hosted.controller.deployment.DeploymentStatus.Readiness; -import com.yahoo.vespa.hosted.controller.deployment.Run; -import com.yahoo.vespa.hosted.controller.deployment.RunStatus; -import com.yahoo.vespa.hosted.controller.deployment.Versions; -import com.yahoo.vespa.hosted.controller.restapi.ErrorResponses; -import com.yahoo.vespa.hosted.controller.versions.DeploymentStatistics; -import com.yahoo.vespa.hosted.controller.versions.VespaVersion; -import com.yahoo.yolean.Exceptions; - -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.TreeMap; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static java.util.function.Function.identity; -import static java.util.stream.Collectors.groupingBy; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toMap; -import static java.util.stream.Collectors.toUnmodifiableMap; - -/** - * This implements the deployment/v1 API which provides information about the status of Vespa platform and - * application deployments. - * - * @author bratseth - */ -@SuppressWarnings("unused") // Injected -public class DeploymentApiHandler extends ThreadedHttpRequestHandler { - - private final Controller controller; - - public DeploymentApiHandler(ThreadedHttpRequestHandler.Context parentCtx, Controller controller) { - super(parentCtx); - this.controller = controller; - } - - @Override - public HttpResponse handle(HttpRequest request) { - try { - return switch (request.getMethod()) { - case GET -> handleGET(request); - case OPTIONS -> handleOPTIONS(); - default -> ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is not supported"); - }; - } - catch (IllegalArgumentException e) { - return ErrorResponse.badRequest(Exceptions.toMessageString(e)); - } - catch (RuntimeException e) { - return ErrorResponses.logThrowing(request, log, e); - } - } - - private HttpResponse handleGET(HttpRequest request) { - Path path = new Path(request.getUri()); - if (path.matches("/deployment/v1/")) return root(request); - return ErrorResponse.notFoundError("Nothing at " + path); - } - - private HttpResponse handleOPTIONS() { - // We implement this to avoid redirect loops on OPTIONS requests from browsers, but do not really bother - // spelling out the methods supported at each path, which we should - EmptyResponse response = new EmptyResponse(); - response.headers().put("Allow", "GET,OPTIONS"); - return response; - } - - private HttpResponse root(HttpRequest request) { - Slime slime = new Slime(); - Cursor root = slime.setObject(); - Cursor platformArray = root.setArray("versions"); - var versionStatus = controller.readVersionStatus(); - ApplicationList applications = ApplicationList.from(controller.applications().asList()).withJobs(); - var deploymentStatuses = controller.jobController().deploymentStatuses(applications, versionStatus); - Map<Version, DeploymentStatistics> deploymentStatistics = DeploymentStatistics.compute(versionStatus.versions().stream().map(VespaVersion::versionNumber).toList(), - deploymentStatuses) - .stream().collect(toMap(DeploymentStatistics::version, identity())); - for (VespaVersion version : versionStatus.versions()) { - Cursor versionObject = platformArray.addObject(); - versionObject.setString("version", version.versionNumber().toString()); - versionObject.setString("confidence", version.confidence().name()); - versionObject.setString("commit", version.releaseCommit()); - versionObject.setLong("date", version.committedAt().toEpochMilli()); - versionObject.setBool("controllerVersion", version.isControllerVersion()); - versionObject.setBool("systemVersion", version.isSystemVersion()); - - Cursor configServerArray = versionObject.setArray("configServers"); - for (var nodeVersion : version.nodeVersions()) { - Cursor configServerObject = configServerArray.addObject(); - configServerObject.setString("hostname", nodeVersion.hostname().value()); - } - - DeploymentStatistics statistics = deploymentStatistics.get(version.versionNumber()); - Cursor failingArray = versionObject.setArray("failingApplications"); - for (Run run : statistics.failingUpgrades()) { - Cursor applicationObject = failingArray.addObject(); - toSlime(applicationObject, run.id().application(), request); - applicationObject.setString("failing", run.id().type().jobName()); - applicationObject.setString("status", nameOf(run.status())); - } - - var statusByInstance = deploymentStatuses.asList().stream() - .flatMap(status -> status.instanceJobs().keySet().stream() - .map(instance -> Map.entry(instance, status))) - .collect(toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); - var jobsByInstance = statusByInstance.entrySet().stream() - .collect(toUnmodifiableMap(Map.Entry::getKey, - entry -> entry.getValue().instanceJobs().get(entry.getKey()))); - Cursor productionArray = versionObject.setArray("productionApplications"); - statistics.productionSuccesses().stream() - .collect(groupingBy(run -> run.id().application(), TreeMap::new, toList())) - .forEach((id, runs) -> { - Cursor applicationObject = productionArray.addObject(); - toSlime(applicationObject, id, request); - applicationObject.setLong("productionJobs", jobsByInstance.get(id).production().size()); - applicationObject.setLong("productionSuccesses", runs.size()); - }); - - Cursor runningArray = versionObject.setArray("deployingApplications"); - for (Run run : statistics.runningUpgrade()) { - Cursor applicationObject = runningArray.addObject(); - toSlime(applicationObject, run.id().application(), request); - applicationObject.setString("running", run.id().type().jobName()); - } - - Cursor instancesArray = versionObject.setArray("applications"); - Stream.of(statistics.failingUpgrades().stream().map(run -> new RunInfo(run, true)), - statistics.otherFailing().stream().map(run -> new RunInfo(run, false)), - statistics.runningUpgrade().stream().map(run -> new RunInfo(run, true)), - statistics.otherRunning().stream().map(run -> new RunInfo(run, false)), - statistics.productionSuccesses().stream().map(run -> new RunInfo(run, true))) - .flatMap(identity()) - .collect(Collectors.groupingBy(run -> run.run.id().application(), - LinkedHashMap::new, // Put apps with failing and running jobs first. - groupingBy(run -> run.run.id().type(), - LinkedHashMap::new, - toList()))) - .forEach((instance, runs) -> { - var status = statusByInstance.get(instance); - var jobsToRun = status.jobsToRun(); - Cursor instanceObject = instancesArray.addObject(); - instanceObject.setString("tenant", instance.tenant().value()); - instanceObject.setString("application", instance.application().value()); - instanceObject.setString("instance", instance.instance().value()); - instanceObject.setBool("upgrading", status.application().require(instance.instance()).change().platform().equals(Optional.of(statistics.version()))); - instanceObject.setBool("pinned", status.application().require(instance.instance()).change().isPlatformPinned()); - instanceObject.setBool("platformPinned", status.application().require(instance.instance()).change().isPlatformPinned()); - instanceObject.setBool("revisionPinned", status.application().require(instance.instance()).change().isRevisionPinned()); - DeploymentStatus.StepStatus stepStatus = status.instanceSteps().get(instance.instance()); - if (stepStatus != null) { // Instance may not have any steps, i.e. an empty deployment spec has been submitted - Readiness platformReadiness = stepStatus.blockedUntil(Change.of(statistics.version())); - if (platformReadiness.cause() == DelayCause.changeBlocked) - instanceObject.setLong("blockedUntil", platformReadiness.at().toEpochMilli()); - } - instanceObject.setString("upgradePolicy", toString(status.application().deploymentSpec().instance(instance.instance()) - .map(DeploymentInstanceSpec::upgradePolicy) - .orElse(DeploymentSpec.UpgradePolicy.defaultPolicy))); - status.application().revisions().last().flatMap(ApplicationVersion::compileVersion) - .ifPresent(compiled -> instanceObject.setString("compileVersion", compiled.toFullString())); - Cursor jobsArray = instanceObject.setArray("jobs"); - status.jobSteps().forEach((job, jobStatus) -> { - if ( ! job.application().equals(instance)) return; - Cursor jobObject = jobsArray.addObject(); - jobObject.setString("name", job.type().jobName()); - if (jobsToRun.containsKey(job)) { - Readiness readiness = jobsToRun.get(job).get(0).readiness(); - switch (readiness.cause()) { - case paused -> jobObject.setLong("pausedUntil", readiness.at().toEpochMilli()); - case coolingDown -> jobObject.setLong("coolingDownUntil", readiness.at().toEpochMilli()); - } - List<Versions> versionsOnThisPlatform = jobsToRun.get(job).stream() - .map(DeploymentStatus.Job::versions) - .filter(versions -> versions.targetPlatform().equals(statistics.version())) - .toList(); - if ( ! versionsOnThisPlatform.isEmpty()) - jobObject.setString("pending", versionsOnThisPlatform.stream() - .allMatch(versions -> versions.sourcePlatform() - .map(statistics.version()::equals) - .orElse(true)) - ? "application" : "platform"); - } - }); - Cursor allRunsObject = instanceObject.setObject("allRuns"); - Cursor upgradeRunsObject = instanceObject.setObject("upgradeRuns"); - runs.forEach((type, rs) -> { - Cursor runObject = allRunsObject.setObject(type.jobName()); - Cursor upgradeObject = upgradeRunsObject.setObject(type.jobName()); - CloudAccount cloudAccount = controller.applications().decideCloudAccountOf(new DeploymentId(instance, type.zone()), - status.application().deploymentSpec()) - .orElse(null); - for (RunInfo run : rs) { - toSlime(runObject, run.run, cloudAccount); - if (run.upgrade) - toSlime(upgradeObject, run.run, cloudAccount); - } - }); - }); - } - JobType.allIn(controller.zoneRegistry()).stream() - .filter(job -> ! job.environment().isManuallyDeployed()) - .map(JobType::jobName).forEach(root.setArray("jobs")::addString); - return new SlimeJsonResponse(slime); - } - - private void toSlime(Cursor jobObject, Run run, CloudAccount cloudAccount) { - String key = run.hasFailed() ? "failing" : run.hasEnded() ? "success" : "running"; - Cursor runObject = jobObject.setObject(key); - runObject.setLong("number", run.id().number()); - runObject.setLong("start", run.start().toEpochMilli()); - run.end().ifPresent(end -> runObject.setLong("end", end.toEpochMilli())); - runObject.setString("status", nameOf(run.status())); - if (cloudAccount != null) runObject.setObject("enclave").setString("cloudAccount", cloudAccount.value()); - } - - private void toSlime(Cursor object, ApplicationId id, HttpRequest request) { - object.setString("tenant", id.tenant().value()); - object.setString("application", id.application().value()); - object.setString("instance", id.instance().value()); - object.setString("url", new UriBuilder(request.getUri()).withPath("/application/v4/tenant/" + - id.tenant().value() + - "/application/" + - id.application().value()).toString()); - object.setString("upgradePolicy", toString(controller.applications().requireApplication(TenantAndApplicationId.from(id)) - .deploymentSpec().instance(id.instance()).map(DeploymentInstanceSpec::upgradePolicy) - .orElse(DeploymentSpec.UpgradePolicy.defaultPolicy))); - } - - private static String toString(DeploymentSpec.UpgradePolicy upgradePolicy) { - if (upgradePolicy == DeploymentSpec.UpgradePolicy.defaultPolicy) { - return "default"; - } - return upgradePolicy.name(); - } - - public static String nameOf(RunStatus status) { - return switch (status) { - case reset, running -> "running"; - case cancelled, aborted -> "aborted"; - case error -> "error"; - case testFailure -> "testFailure"; - case noTests -> "noTests"; - case endpointCertificateTimeout -> "endpointCertificateTimeout"; - case nodeAllocationFailure -> "nodeAllocationFailure"; - case installationFailed -> "installationFailed"; - case invalidApplication, deploymentFailed -> "deploymentFailed"; - case success -> "success"; - case quotaExceeded -> "quotaExceeded"; - }; - } - - private static class RunInfo { - final Run run; - final boolean upgrade; - - RunInfo(Run run, boolean upgrade) { - this.run = run; - this.upgrade = upgrade; - } - - @Override - public String toString() { - return run.id().toString(); - } - - } - - -} |