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

com.yahoo.vespa.config.server.modelfactory.PreparedModelsBuilder Maven / Gradle / Ivy

There is a newer version: 8.441.21
Show newest version
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.config.server.modelfactory;

import com.yahoo.cloud.config.ConfigserverConfig;
import com.yahoo.component.Version;
import com.yahoo.config.application.api.ApplicationPackage;
import com.yahoo.config.application.api.DeployLogger;
import com.yahoo.config.application.api.FileRegistry;
import com.yahoo.config.model.api.ConfigChangeAction;
import com.yahoo.config.model.api.ConfigDefinitionRepo;
import com.yahoo.config.model.api.ContainerEndpoint;
import com.yahoo.config.model.api.EndpointCertificateSecrets;
import com.yahoo.config.model.api.HostInfo;
import com.yahoo.config.model.api.HostProvisioner;
import com.yahoo.config.model.api.Model;
import com.yahoo.config.model.api.ModelContext;
import com.yahoo.config.model.api.ModelCreateResult;
import com.yahoo.config.model.api.ModelFactory;
import com.yahoo.config.model.api.OnnxModelCost;
import com.yahoo.config.model.api.Provisioned;
import com.yahoo.config.model.api.ValidationParameters;
import com.yahoo.config.model.api.ValidationParameters.IgnoreValidationErrors;
import com.yahoo.config.model.application.provider.FilesApplicationPackage;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.provision.AllocatedHosts;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.DockerImage;
import com.yahoo.config.provision.NodeAllocationException;
import com.yahoo.config.provision.Zone;
import com.yahoo.container.jdisc.secretstore.SecretStore;
import com.yahoo.vespa.config.server.application.Application;
import com.yahoo.vespa.config.server.application.ApplicationCuratorDatabase;
import com.yahoo.vespa.config.server.application.ApplicationVersions;
import com.yahoo.vespa.config.server.deploy.ModelContextImpl;
import com.yahoo.vespa.config.server.host.HostValidator;
import com.yahoo.vespa.config.server.provision.HostProvisionerProvider;
import com.yahoo.vespa.config.server.session.PrepareParams;
import com.yahoo.vespa.curator.Curator;
import com.yahoo.vespa.flags.FlagSource;

import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.logging.Level;
import java.util.logging.Logger;

import static com.yahoo.yolean.Exceptions.toMessageString;
import static java.util.logging.Level.FINE;

/**
 * @author bratseth
 */
public class PreparedModelsBuilder extends ModelsBuilder {

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

    private final FlagSource flagSource;
    private final SecretStore secretStore;
    private final List containerEndpoints;
    private final Optional endpointCertificateSecrets;
    private final ConfigDefinitionRepo configDefinitionRepo;
    private final HostValidator hostValidator;
    private final PrepareParams params;
    private final FileRegistry fileRegistry;
    private final Optional activeApplicationVersions;
    private final Curator curator;
    private final ExecutorService executor;
    private final OnnxModelCost onnxModelCost;

    public PreparedModelsBuilder(ModelFactoryRegistry modelFactoryRegistry,
                                 FlagSource flagSource,
                                 SecretStore secretStore,
                                 List containerEndpoints,
                                 Optional endpointCertificateSecrets,
                                 ConfigDefinitionRepo configDefinitionRepo,
                                 FileRegistry fileRegistry,
                                 ExecutorService executor,
                                 HostProvisionerProvider hostProvisionerProvider,
                                 Curator curator,
                                 HostValidator hostValidator,
                                 DeployLogger deployLogger,
                                 PrepareParams params,
                                 Optional activeApplicationVersions,
                                 ConfigserverConfig configserverConfig,
                                 Zone zone,
                                 OnnxModelCost onnxModelCost) {
        super(modelFactoryRegistry, configserverConfig, zone, hostProvisionerProvider, deployLogger);
        this.flagSource = flagSource;
        this.secretStore = secretStore;
        this.containerEndpoints = containerEndpoints;
        this.endpointCertificateSecrets = endpointCertificateSecrets;
        this.configDefinitionRepo = configDefinitionRepo;
        this.fileRegistry = fileRegistry;
        this.hostValidator = hostValidator;
        this.curator = curator;
        this.params = params;
        this.activeApplicationVersions = activeApplicationVersions;
        this.executor = executor;
        this.onnxModelCost = onnxModelCost;
    }

    @Override
    protected PreparedModelResult buildModelVersion(ModelFactory modelFactory,
                                                    ApplicationPackage applicationPackage,
                                                    ApplicationId applicationId,
                                                    Optional wantedDockerImageRepository,
                                                    Version wantedNodeVespaVersion) {
        Version modelVersion = modelFactory.version();
        log.log(FINE, () -> "Building model " + modelVersion + " for " + applicationId);

        // Use empty on non-hosted systems, use already allocated hosts if available, create connection to a host provisioner otherwise
        Provisioned provisioned = new Provisioned();
        ModelContext modelContext = new ModelContextImpl(
                applicationPackage,
                modelOf(modelVersion),
                deployLogger(),
                configDefinitionRepo,
                fileRegistry,
                executor,
                new ApplicationCuratorDatabase(applicationId.tenant(), curator).readReindexingStatus(applicationId),
                createHostProvisioner(applicationPackage, provisioned),
                provisioned,
                createModelContextProperties(modelFactory.version(), applicationPackage),
                getAppDir(applicationPackage),
                onnxModelCost,
                wantedDockerImageRepository,
                modelVersion,
                wantedNodeVespaVersion);

        ModelCreateResult result = createAndValidateModel(modelFactory, applicationId, modelVersion, modelContext);
        return new PreparedModelResult(modelVersion, result.getModel(), fileRegistry, result.getConfigChangeActions());
    }

    private ModelCreateResult createAndValidateModel(ModelFactory modelFactory,
                                                     ApplicationId applicationId,
                                                     Version modelVersion,
                                                     ModelContext modelContext) {
        log.log(FINE, () -> "Create and validate model " + modelVersion + " for " + applicationId +
                ", previous model " + (modelOf(modelVersion).isPresent() ? " exists" : "does not exist"));
        ValidationParameters validationParameters =
                new ValidationParameters(params.ignoreValidationErrors() ? IgnoreValidationErrors.TRUE : IgnoreValidationErrors.FALSE);
        ModelCreateResult result = modelFactory.createAndValidateModel(modelContext, validationParameters);
        validateModelHosts(hostValidator, applicationId, result.getModel());
        log.log(FINE, () -> "Done building model " + modelVersion + " for " + applicationId);
        params.getTimeoutBudget().assertNotTimedOut(() -> "prepare timed out after building model " + modelVersion +
                                                          " (timeout " + params.getTimeoutBudget().timeout() + "): " + applicationId);
        return result;
    }

    private Optional modelOf(Version version) {
        if (activeApplicationVersions.isEmpty()) return Optional.empty();
        return activeApplicationVersions.get().get(version).map(Application::getModel);
    }

    private HostProvisioner createHostProvisioner(ApplicationPackage applicationPackage, Provisioned provisioned) {
        HostProvisioner defaultHostProvisioner = DeployState.getDefaultModelHostProvisioner(applicationPackage);
        // Note: nodeRepositoryProvisioner will always be present when hosted is true
        Optional nodeRepositoryProvisioner = createNodeRepositoryProvisioner(params.getApplicationId(), provisioned);
        Optional allocatedHosts = applicationPackage.getAllocatedHosts();

        if (allocatedHosts.isEmpty()) return nodeRepositoryProvisioner.orElse(defaultHostProvisioner);

        // Nodes are already allocated by a model and we should use them unless this model requests hosts from a
        // previously unallocated cluster. This allows future models to stop allocate certain clusters.
        if (hosted) return createStaticProvisionerForHosted(allocatedHosts.get(), nodeRepositoryProvisioner.get());

        return defaultHostProvisioner;
    }

    private Optional getAppDir(ApplicationPackage applicationPackage) {
        try {
            return applicationPackage instanceof FilesApplicationPackage ?
                   Optional.of(((FilesApplicationPackage) applicationPackage).getAppDir()) :
                   Optional.empty();
        } catch (IOException e) {
            throw new RuntimeException("Could not find app dir", e);
        }
    }

    private void validateModelHosts(HostValidator hostValidator, ApplicationId applicationId, Model model) {
        // Will retry here, since hosts used might not be in sync on all config servers (we wait for 2/3 servers
        // to respond to deployments and deletions).
        Instant end = Instant.now().plus(Duration.ofSeconds(1));
        IllegalArgumentException exception;
        do {
            try {
                hostValidator.verifyHosts(applicationId, model.getHosts().stream()
                                                              .map(HostInfo::getHostname)
                                                              .toList());
                return;
            } catch (IllegalArgumentException e) {
                exception = e;
                log.log(Level.INFO, "Verifying hosts failed, will retry: " + e.getMessage());
                try {
                    Thread.sleep(100);
                } catch (InterruptedException interruptedException) {/* ignore */}
            }
        } while (Instant.now().isBefore(end));

        if (configserverConfig.hostedVespa())
            // Use another exception, as this is not a problem with the application package
            throw new NodeAllocationException(toMessageString(exception), true);

        throw exception;
    }

    private ModelContext.Properties createModelContextProperties(Version modelVersion,
                                                                 ApplicationPackage applicationPackage) {
        return new ModelContextImpl.Properties(params.getApplicationId(),
                                               modelVersion,
                                               configserverConfig,
                                               zone(),
                                               Set.copyOf(containerEndpoints),
                                               params.isBootstrap(),
                                               activeApplicationVersions.isEmpty(),
                                               LegacyFlags.from(applicationPackage, flagSource),
                                               endpointCertificateSecrets,
                                               params.athenzDomain(),
                                               params.quota(),
                                               params.tenantSecretStores(),
                                               secretStore,
                                               params.operatorCertificates(),
                                               params.cloudAccount(),
                                               params.dataplaneTokens());
    }

    /** The result of preparing a single model version */
    public static class PreparedModelResult implements ModelResult {

        public final Version version;
        public final Model model;
        public final FileRegistry fileRegistry;
        public final List actions;

        public PreparedModelResult(Version version,
                                   Model model,
                                   FileRegistry fileRegistry,
                                   List actions) {
            this.version = version;
            this.model = model;
            this.fileRegistry = fileRegistry;
            this.actions = actions;
        }

        @Override
        public Model getModel() {
            return model;
        }

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy