diff options
7 files changed, 79 insertions, 28 deletions
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java index 820ad8e530c..70b677b4057 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java @@ -18,7 +18,7 @@ import java.util.Set; /** * Implementation of {@link ModelContext} for configserver. * - * @author lulf + * @author Ulf Lilleengen */ public class ModelContextImpl implements ModelContext { @@ -83,6 +83,11 @@ public class ModelContextImpl implements ModelContext { return permanentApplicationPackage; } + /** + * Returns the host provisioner to use, or empty to use the default provisioner, + * creating hosts from the application package defined hosts + */ + // TODO: Don't allow empty here but create the right provisioner when this is set up instead @Override public Optional<HostProvisioner> hostProvisioner() { return hostProvisioner; diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java index ae347f310dd..1301f24788f 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java @@ -13,7 +13,6 @@ import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.Version; import com.yahoo.config.provision.Zone; -import com.yahoo.lang.SettableOptional; import com.yahoo.log.LogLevel; import com.yahoo.vespa.config.server.GlobalComponentRegistry; import com.yahoo.vespa.config.server.tenant.Rotations; @@ -73,7 +72,7 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> { ApplicationPackage applicationPackage, ApplicationId applicationId, com.yahoo.component.Version wantedNodeVespaVersion, - SettableOptional<AllocatedHosts> ignored, // Ignored since we have this in the app package for activated models + Optional<AllocatedHosts> ignored, // Ignored since we have this in the app package for activated models Instant now) { log.log(LogLevel.DEBUG, String.format("Loading model version %s for session %s application %s", modelFactory.getVersion(), appGeneration, applicationId)); @@ -85,7 +84,7 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> { logger, configDefinitionRepo, getForVersionOrLatest(applicationPackage.getFileRegistryMap(), modelFactory.getVersion()).orElse(new MockFileRegistry()), - createHostProvisioner(applicationPackage.getAllocatedHosts()), + createStaticProvisioner(applicationPackage.getAllocatedHosts()), createModelContextProperties(applicationId), Optional.empty(), new com.yahoo.component.Version(modelFactory.getVersion().toString()), diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java index 55b63126f04..bbd59e0628e 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java @@ -112,21 +112,20 @@ public abstract class ModelsBuilder<MODELRESULT extends ModelResult> { Instant now) { Version latest = findLatest(versions); // load latest application version - MODELRESULT latestApplicationVersion = buildModelVersion(modelFactoryRegistry.getFactory(latest), + MODELRESULT latestModelVersion = buildModelVersion(modelFactoryRegistry.getFactory(latest), applicationPackage, applicationId, wantedNodeVespaVersion, - allocatedHosts, + allocatedHosts.asOptional(), now); - if ( ! allocatedHosts.isPresent()) - allocatedHosts.set(latestApplicationVersion.getModel().allocatedHosts()); + allocatedHosts.set(latestModelVersion.getModel().allocatedHosts()); // Update with additional clusters allocated - if (latestApplicationVersion.getModel().skipOldConfigModels(now)) - return Collections.singletonList(latestApplicationVersion); + if (latestModelVersion.getModel().skipOldConfigModels(now)) + return Collections.singletonList(latestModelVersion); // load old model versions List<MODELRESULT> allApplicationVersions = new ArrayList<>(); - allApplicationVersions.add(latestApplicationVersion); + allApplicationVersions.add(latestModelVersion); // TODO: We use the allocated hosts from the newest version when building older model versions. // This is correct except for the case where an old model specifies a cluster which the new version @@ -135,12 +134,15 @@ public abstract class ModelsBuilder<MODELRESULT extends ModelResult> { // clusters and the node repository provisioner as fallback. for (Version version : versions) { if (version.equals(latest)) continue; // already loaded - allApplicationVersions.add(buildModelVersion(modelFactoryRegistry.getFactory(version), + + MODELRESULT modelVersion = buildModelVersion(modelFactoryRegistry.getFactory(version), applicationPackage, applicationId, wantedNodeVespaVersion, - allocatedHosts, - now)); + allocatedHosts.asOptional(), + now); + allocatedHosts.set(modelVersion.getModel().allocatedHosts()); // Update with additional clusters allocated + allApplicationVersions.add(modelVersion); } return allApplicationVersions; } @@ -161,7 +163,7 @@ public abstract class ModelsBuilder<MODELRESULT extends ModelResult> { protected abstract MODELRESULT buildModelVersion(ModelFactory modelFactory, ApplicationPackage applicationPackage, ApplicationId applicationId, com.yahoo.component.Version wantedNodeVespaVersion, - SettableOptional<AllocatedHosts> allocatedHosts, + Optional<AllocatedHosts> allocatedHosts, Instant now); protected ModelContext.Properties createModelContextProperties(ApplicationId applicationId, @@ -178,9 +180,10 @@ public abstract class ModelsBuilder<MODELRESULT extends ModelResult> { /** * Returns a host provisioner returning the previously allocated hosts if available and when on hosted Vespa, - * returns empty otherwise. + * returns empty otherwise, which may either mean that no hosts are allocated or that we are running + * non-hosted and should default to use hosts defined in the application package, depending on context */ - protected Optional<HostProvisioner> createHostProvisioner(Optional<AllocatedHosts> allocatedHosts) { + protected Optional<HostProvisioner> createStaticProvisioner(Optional<AllocatedHosts> allocatedHosts) { if (hosted && allocatedHosts.isPresent()) return Optional.of(new StaticProvisioner(allocatedHosts.get())); return Optional.empty(); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java index cc382eb94d2..d9c25914b6b 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java @@ -14,7 +14,6 @@ import com.yahoo.config.model.application.provider.FilesApplicationPackage; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.Version; -import com.yahoo.lang.SettableOptional; import com.yahoo.log.LogLevel; import com.yahoo.vespa.config.server.application.ApplicationSet; import com.yahoo.vespa.config.server.host.HostValidator; @@ -23,6 +22,7 @@ import com.yahoo.vespa.config.server.deploy.ModelContextImpl; import com.yahoo.vespa.config.server.filedistribution.FileDistributionProvider; import com.yahoo.vespa.config.server.provision.HostProvisionerProvider; import com.yahoo.vespa.config.server.provision.ProvisionerAdapter; +import com.yahoo.vespa.config.server.provision.StaticProvisioner; import com.yahoo.vespa.config.server.session.FileDistributionFactory; import com.yahoo.vespa.config.server.session.PrepareParams; import com.yahoo.vespa.config.server.session.SessionContext; @@ -82,7 +82,7 @@ public class PreparedModelsBuilder extends ModelsBuilder<PreparedModelsBuilder.P ApplicationPackage applicationPackage, ApplicationId applicationId, com.yahoo.component.Version wantedNodeVespaVersion, - SettableOptional<AllocatedHosts> activatedHosts, + Optional<AllocatedHosts> allocatedHosts, Instant now) { Version modelVersion = modelFactory.getVersion(); log.log(LogLevel.DEBUG, "Start building model for Vespa version " + modelVersion); @@ -90,10 +90,8 @@ public class PreparedModelsBuilder extends ModelsBuilder<PreparedModelsBuilder.P context.getServerDBSessionDir(), applicationId); - // Use already allocated hosts if available, create connection to a host provisioner otherwise - Optional<HostProvisioner> hostProvisioner = - activatedHosts.isPresent() ? createHostProvisioner(Optional.of(activatedHosts.get())) : - createHostProvisionerAdapter(properties); + // Use empty on non-hosted systems, use already allocated hosts if available, create connection to a host provisioner otherwise + Optional<HostProvisioner> hostProvisioner = createHostProvisioner(allocatedHosts); Optional<Model> previousModel = currentActiveApplicationSet .map(set -> set.getForVersionOrLatest(Optional.of(modelVersion), now).getModel()); ModelContext modelContext = new ModelContextImpl( @@ -116,6 +114,24 @@ public class PreparedModelsBuilder extends ModelsBuilder<PreparedModelsBuilder.P return new PreparedModelsBuilder.PreparedModelResult(modelVersion, result.getModel(), fileDistributionProvider, result.getConfigChangeActions()); } + // This method is an excellent demonstration of what happens when one is too liberal with Optional + // -bratseth, who had to write this :-/ + private Optional<HostProvisioner> createHostProvisioner(Optional<AllocatedHosts> allocatedHosts) { + Optional<HostProvisioner> nodeRepositoryProvisioner = createNodeRepositoryProvisioner(properties); + if ( ! allocatedHosts.isPresent()) return nodeRepositoryProvisioner; + + Optional<HostProvisioner> staticProvisioner = createStaticProvisioner(allocatedHosts); + if ( ! staticProvisioner.isPresent()) return Optional.empty(); // Since we have hosts allocated this means we are on non-hosted + + // The following option should not be possible, but since there is a right action for it we can take it + if ( ! nodeRepositoryProvisioner.isPresent()) + return Optional.of(new StaticProvisioner(allocatedHosts.get())); + + // 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. + return Optional.of(new StaticProvisioner(allocatedHosts.get(), nodeRepositoryProvisioner.get())); + } + private Optional<File> getAppDir(ApplicationPackage applicationPackage) { try { return applicationPackage instanceof FilesApplicationPackage ? @@ -131,7 +147,7 @@ public class PreparedModelsBuilder extends ModelsBuilder<PreparedModelsBuilder.P .collect(Collectors.toList())); } - private Optional<HostProvisioner> createHostProvisionerAdapter(ModelContext.Properties properties) { + private Optional<HostProvisioner> createNodeRepositoryProvisioner(ModelContext.Properties properties) { return hostProvisionerProvider.getHostProvisioner().map( provisioner -> new ProvisionerAdapter(provisioner, properties.applicationId())); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/provision/ProvisionerAdapter.java b/configserver/src/main/java/com/yahoo/vespa/config/server/provision/ProvisionerAdapter.java index 82e5281135f..32380b296dd 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/provision/ProvisionerAdapter.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/provision/ProvisionerAdapter.java @@ -16,7 +16,7 @@ import java.util.*; * behavior to the config model. Adapts interface from a {@link HostProvisioner} to a * {@link Provisioner}. * - * @author lulf + * @author Ulf Lilleengen */ public class ProvisionerAdapter implements HostProvisioner { @@ -30,6 +30,7 @@ public class ProvisionerAdapter implements HostProvisioner { @Override public HostSpec allocateHost(String alias) { + // Wow. Such mess. TODO: Actually support polymorphy or stop pretending to, see also ModelContextImpl.getHostProvisioner throw new UnsupportedOperationException("Allocating a single host in a hosted environment is not possible"); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/provision/StaticProvisioner.java b/configserver/src/main/java/com/yahoo/vespa/config/server/provision/StaticProvisioner.java index c5e8d4b87ea..7e97690331f 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/provision/StaticProvisioner.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/provision/StaticProvisioner.java @@ -5,6 +5,7 @@ import com.yahoo.config.model.api.HostProvisioner; import com.yahoo.config.provision.*; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; /** @@ -15,9 +16,24 @@ import java.util.stream.Collectors; public class StaticProvisioner implements HostProvisioner { private final AllocatedHosts allocatedHosts; + + /** The fallback provisioner to use for unknown clusters, or null to not fall back */ + private final HostProvisioner fallback; + /** + * Creates a static host provisioner with no fallback + */ public StaticProvisioner(AllocatedHosts allocatedHosts) { + this(allocatedHosts, null); + } + + /** + * Creates a static host provisioner which will fall back to using the given provisioner + * if a request is made for nodes in a cluster which is not present in this allocation. + */ + public StaticProvisioner(AllocatedHosts allocatedHosts, HostProvisioner fallback) { this.allocatedHosts = allocatedHosts; + this.fallback = fallback; } @Override @@ -27,9 +43,14 @@ public class StaticProvisioner implements HostProvisioner { @Override public List<HostSpec> prepare(ClusterSpec cluster, Capacity capacity, int groups, ProvisionLogger logger) { - return allocatedHosts.getHosts().stream() - .filter(host -> host.membership().isPresent() && matches(host.membership().get().cluster(), cluster)) - .collect(Collectors.toList()); + List<HostSpec> hostsAlreadyAllocatedToCluster = + allocatedHosts.getHosts().stream() + .filter(host -> host.membership().isPresent() && matches(host.membership().get().cluster(), cluster)) + .collect(Collectors.toList()); + if ( ! hostsAlreadyAllocatedToCluster.isEmpty()) + return hostsAlreadyAllocatedToCluster; + else + return fallback.prepare(cluster, capacity, groups, logger); } private boolean matches(ClusterSpec nodeCluster, ClusterSpec requestedCluster) { diff --git a/vespajlib/src/main/java/com/yahoo/lang/SettableOptional.java b/vespajlib/src/main/java/com/yahoo/lang/SettableOptional.java index 74abd4101a4..00ff06b8f01 100644 --- a/vespajlib/src/main/java/com/yahoo/lang/SettableOptional.java +++ b/vespajlib/src/main/java/com/yahoo/lang/SettableOptional.java @@ -1,6 +1,7 @@ package com.yahoo.lang; import java.util.NoSuchElementException; +import java.util.Optional; /** * An optional which contains a settable value @@ -30,6 +31,11 @@ public final class SettableOptional<T> { public void set(T value) { this.value = value; } + + public Optional<T> asOptional() { + if (value == null) return Optional.empty(); + return Optional.of(value); + } } |