All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.yahoo.vespa.hosted.controller.versions.VersionStatus Maven / Gradle / Ivy

There is a newer version: 8.253.3
Show newest version
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.versions;

import com.yahoo.component.Version;
import com.yahoo.config.provision.HostName;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeFilter;
import com.yahoo.vespa.hosted.controller.application.ApplicationList;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.vespa.hosted.controller.maintenance.SystemUpgrader;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

/**
 * Information about the current platform versions in use.
 * The versions in use are the set of all versions running in current applications, versions
 * of config servers in all zones, and the version of this controller itself.
 * 
 * This is immutable.
 * 
 * @author bratseth
 * @author mpolden
 */
public class VersionStatus {

    private static final Logger log = Logger.getLogger(VersionStatus.class.getName());

    private final List versions;
    
    /** Create a version status. DO NOT USE: Public for testing and serialization only */
    public VersionStatus(List versions) {
        this.versions = List.copyOf(versions);
    }

    /** Returns the current version of controllers in this system */
    public Optional controllerVersion() {
        return versions().stream().filter(VespaVersion::isControllerVersion).findFirst();
    }
    
    /** 
     * Returns the current Vespa version of the system controlled by this, 
     * or empty if we have not currently determined what the system version is in this status.
     */
    public Optional systemVersion() {
        return versions().stream().filter(VespaVersion::isSystemVersion).findFirst();
    }

    /** Returns whether the system is currently upgrading */
    public boolean isUpgrading() {
        return systemVersion().map(VespaVersion::versionNumber).orElse(Version.emptyVersion)
                              .isBefore(controllerVersion().map(VespaVersion::versionNumber)
                                                           .orElse(Version.emptyVersion));
    }

    /** 
     * Lists all currently active Vespa versions, with deployment statistics, 
     * sorted from lowest to highest version number.
     * The returned list is immutable.
     * Calling this is free, but the returned status is slightly out of date.
     */
    public List versions() { return versions; }
    
    /** Returns the given version, or null if it is not present */
    public VespaVersion version(Version version) {
        return versions.stream().filter(v -> v.versionNumber().equals(version)).findFirst().orElse(null);
    }

    /** Returns whether given version is active in this system */
    public boolean isActive(Version version) {
        return version(version) != null;
    }

    /** Create the empty version status */
    public static VersionStatus empty() { return new VersionStatus(List.of()); }

    /** Create a full, updated version status. This is expensive and should be done infrequently */
    public static VersionStatus compute(Controller controller) {
        VersionStatus versionStatus = controller.readVersionStatus();
        List systemApplicationVersions = findSystemApplicationVersions(controller, versionStatus);
        Map> controllerVersions = findControllerVersions(controller);

        Map> infrastructureVersions = new HashMap<>();
        for (var kv : controllerVersions.entrySet()) {
            infrastructureVersions.computeIfAbsent(kv.getKey().version(), (k) -> new ArrayList<>())
                                  .addAll(kv.getValue());
        }
        for (var nodeVersion : systemApplicationVersions) {
            infrastructureVersions.computeIfAbsent(nodeVersion.currentVersion(), (k) -> new ArrayList<>())
                                  .add(nodeVersion.hostname());
        }

        // The system version is the oldest infrastructure version, if that version is newer than the current system
        // version
        Version newSystemVersion = infrastructureVersions.keySet().stream().min(Comparator.naturalOrder()).get();
        Version systemVersion = versionStatus.systemVersion()
                                             .map(VespaVersion::versionNumber)
                                             .orElse(newSystemVersion);
        if (newSystemVersion.isBefore(systemVersion)) {
            log.warning("Refusing to lower system version from " +
                        systemVersion +
                        " to " +
                        newSystemVersion +
                        ", nodes on " + newSystemVersion + ": " +
                        infrastructureVersions.get(newSystemVersion).stream()
                                              .map(HostName::value)
                                              .collect(Collectors.joining(", ")));
        } else {
            systemVersion = newSystemVersion;
        }


        List deploymentStatistics = DeploymentStatistics.compute(infrastructureVersions.keySet(),
                                                                                       controller.jobController().deploymentStatuses(ApplicationList.from(controller.applications().asList())
                                                                                                                                                    .withProjectId()
                                                                                                                                                    .withJobs()));
        List versions = new ArrayList<>();
        List releasedVersions = controller.mavenRepository().metadata().versions();

        for (DeploymentStatistics statistics : deploymentStatistics) {
            if (statistics.version().isEmpty()) continue;

            try {
                boolean isReleased = Collections.binarySearch(releasedVersions, statistics.version()) >= 0;
                List nodeVersions = systemApplicationVersions.stream()
                                                                          .filter(nodeVersion -> nodeVersion.currentVersion().equals(statistics.version()))
                                                                          .collect(Collectors.toList());
                VespaVersion vespaVersion = createVersion(statistics,
                                                          controllerVersions.keySet(),
                                                          systemVersion,
                                                          isReleased,
                                                          nodeVersions,
                                                          controller,
                                                          versionStatus);
                versions.add(vespaVersion);
            } catch (IllegalArgumentException e) {
                log.log(Level.WARNING, "Unable to create VespaVersion for version " +
                                       statistics.version().toFullString(), e);
            }
        }

        Collections.sort(versions);

        return new VersionStatus(versions);
    }

    private static List findSystemApplicationVersions(Controller controller, VersionStatus versionStatus) {
        List nodeVersions = new ArrayList<>();
        for (var zone : controller.zoneRegistry().zones().controllerUpgraded().zones()) {
            for (var application : SystemApplication.notController()) {
                var nodes = controller.serviceRegistry().configServer().nodeRepository()
                                      .list(zone.getId(), NodeFilter.all().applications(application.id())).stream()
                                      .filter(SystemUpgrader::eligibleForUpgrade)
                                      .collect(Collectors.toList());
                if (nodes.isEmpty()) continue;
                boolean configConverged = application.configConvergedIn(zone.getId(), controller, Optional.empty());
                if (!configConverged) {
                    log.log(Level.WARNING, "Config for " + application.id() + " in " + zone.getId() +
                                              " has not converged");
                }
                for (var node : nodes) {
                    // Only use current node version if config has converged
                    var version = configConverged ? node.currentVersion() : controller.systemVersion(versionStatus);
                    var nodeVersion = new NodeVersion(node.hostname(), zone.getId(), version, node.wantedVersion(),
                                                      node.suspendedSince());
                    nodeVersions.add(nodeVersion);
                }
            }
        }
        return nodeVersions;
    }

    private static Map> findControllerVersions(Controller controller) {
        Map> versions = new HashMap<>();
        if (controller.curator().cluster().isEmpty()) { // Use vtag if we do not have cluster
            versions.computeIfAbsent(ControllerVersion.CURRENT, (k) -> new ArrayList<>())
                    .add(controller.hostname());
        } else {
            for (String host : controller.curator().cluster()) {
                HostName hostname = HostName.from(host);
                versions.computeIfAbsent(controller.curator().readControllerVersion(hostname), (k) -> new ArrayList<>())
                        .add(hostname);
            }
        }
        return versions;
    }

    private static VespaVersion createVersion(DeploymentStatistics statistics,
                                              Set controllerVersions,
                                              Version systemVersion,
                                              boolean isReleased,
                                              List nodeVersions,
                                              Controller controller,
                                              VersionStatus versionStatus) {
        ControllerVersion latestVersion = controllerVersions.stream().max(Comparator.naturalOrder()).get();
        ControllerVersion controllerVersion = controllerVersions.stream().min(Comparator.naturalOrder()).get();
        boolean isSystemVersion = statistics.version().equals(systemVersion);
        boolean isControllerVersion = statistics.version().equals(controllerVersion.version());
        VespaVersion.Confidence confidence = controller.curator().readConfidenceOverrides().get(statistics.version());
        boolean confidenceIsOverridden = confidence != null;
        VespaVersion existingVespaVersion = versionStatus.version(statistics.version());

        // Compute confidence
        if (!confidenceIsOverridden) {
            // Always compute confidence for system and controller
            if (isSystemVersion || isControllerVersion) {
                confidence = VespaVersion.confidenceFrom(statistics, controller);
            } else {
                // This is an older version so we preserve the existing confidence, if any
                confidence = versionStatus.versions().stream()
                                          .filter(v -> statistics.version().equals(v.versionNumber()))
                                          .map(VespaVersion::confidence)
                                          .findFirst()
                                          .orElseGet(() -> VespaVersion.confidenceFrom(statistics, controller));
            }
        }

        // Preserve existing commit details if we've previously computed status for this version
        var commitSha = latestVersion.commitSha();
        var commitDate = latestVersion.commitDate();
        if (existingVespaVersion != null) {
            commitSha = existingVespaVersion.releaseCommit();
            commitDate = existingVespaVersion.committedAt();

            // Keep existing confidence if we cannot raise it at this moment in time
            if (!confidenceIsOverridden &&
                !existingVespaVersion.confidence().canChangeTo(confidence,
                                                               controller.serviceRegistry().zoneRegistry().system(),
                                                               controller.clock().instant())) {
                confidence = existingVespaVersion.confidence();
            }
        }

        return new VespaVersion(statistics.version(),
                                commitSha,
                                commitDate,
                                isControllerVersion,
                                isSystemVersion,
                                isReleased,
                                nodeVersions,
                                confidence);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy