diff options
65 files changed, 748 insertions, 531 deletions
diff --git a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/DeployData.java b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/DeployData.java index d9a784a2dc5..f48dffecb27 100644 --- a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/DeployData.java +++ b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/DeployData.java @@ -5,7 +5,6 @@ package com.yahoo.config.model.application.provider; * A class for holding values generated or computed during deployment * * @author hmusum - * @since 5.1.11 */ public class DeployData { @@ -57,4 +56,5 @@ public class DeployData { public String getApplicationName() { return applicationName; } + } diff --git a/config-application-package/src/test/java/com/yahoo/config/application/OverrideProcessorTest.java b/config-application-package/src/test/java/com/yahoo/config/application/OverrideProcessorTest.java index b80372ec9ff..32a1c4f847f 100644 --- a/config-application-package/src/test/java/com/yahoo/config/application/OverrideProcessorTest.java +++ b/config-application-package/src/test/java/com/yahoo/config/application/OverrideProcessorTest.java @@ -15,8 +15,7 @@ import java.io.IOException; import java.io.StringReader; /** - * @author lulf - * @since 5.22 + * @author Ulf Lilleengen */ public class OverrideProcessorTest { diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationFile.java b/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationFile.java index bcc8d222ca5..0384a5c7a1c 100644 --- a/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationFile.java +++ b/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationFile.java @@ -11,7 +11,7 @@ import java.util.List; * An application file represents a file within an application package. This class can be used to traverse the entire * application package file structure, as well as read and write files to it, and create directories. * - * @author Ulf Lillengen + * @author Ulf Lilleengen */ public abstract class ApplicationFile implements Comparable<ApplicationFile> { diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationPackage.java b/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationPackage.java index 795e87b7690..c1a786194a2 100644 --- a/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationPackage.java +++ b/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationPackage.java @@ -1,7 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.config.application.api; -import com.yahoo.config.provision.ProvisionInfo; +import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.Version; import com.yahoo.config.provision.Zone; import com.yahoo.path.Path; @@ -28,7 +28,7 @@ import java.util.jar.JarFile; * * Anyone wanting to access application data should use this interface. * - * @author vegardh + * @author Vegard Havdal */ public interface ApplicationPackage { @@ -228,10 +228,22 @@ public interface ApplicationPackage { throw new UnsupportedOperationException("This application package cannot write its metadata"); } - default Map<Version, ProvisionInfo> getProvisionInfoMap() { + /** + * Returns the single host allocation info of this, or an empty map if no allocation is available + * + * @deprecated please use #getAllocatedHosts + */ + // TODO: Remove on Vespa 7 + @Deprecated + default Map<Version, AllocatedHosts> getProvisionInfoMap() { return Collections.emptyMap(); } + /** Returns the host allocation info of this, or empty if no allocation is available */ + default Optional<AllocatedHosts> getAllocatedHosts() { + return Optional.empty(); + } + default Map<Version, FileRegistry> getFileRegistryMap() { return Collections.emptyMap(); } diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/HostInfo.java b/config-model-api/src/main/java/com/yahoo/config/model/api/HostInfo.java index dcb875d02b5..589aee50d7c 100644 --- a/config-model-api/src/main/java/com/yahoo/config/model/api/HostInfo.java +++ b/config-model-api/src/main/java/com/yahoo/config/model/api/HostInfo.java @@ -7,7 +7,6 @@ import java.util.Collection; * Contains information about a host and what services are running on it. * * @author lulf - * @since 5.37 */ public class HostInfo { diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/Model.java b/config-model-api/src/main/java/com/yahoo/config/model/api/Model.java index 0c038077fe4..f8f749ef070 100644 --- a/config-model-api/src/main/java/com/yahoo/config/model/api/Model.java +++ b/config-model-api/src/main/java/com/yahoo/config/model/api/Model.java @@ -1,12 +1,11 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.config.model.api; -import com.yahoo.config.provision.ProvisionInfo; +import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.vespa.config.ConfigKey; import com.yahoo.vespa.config.ConfigPayload; import com.yahoo.vespa.config.buildergen.ConfigDefinition; -import java.time.Clock; import java.time.Instant; import java.util.Optional; import java.util.Set; @@ -16,8 +15,7 @@ import java.util.Collection; * A {@link Model} represents the interface towards the model of an entire tenant, and defines methods * for querying this model. * - * @author lulf - * @since 5.1 + * @author Ulf Lilleengen */ public interface Model { @@ -60,9 +58,20 @@ public interface Model { /** * Get the provisioning info for this model. - * @return {@link ProvisionInfo} instance, if available. + * + * @return {@link AllocatedHosts} instance, if available. + * @deprecated use allocatedHosts */ - Optional<ProvisionInfo> getProvisionInfo(); + @Deprecated + // TODO: Remove this (and the implementation below) when no version older than 6.143 is deployed anywhere + default Optional<AllocatedHosts> getProvisionInfo() { + return Optional.of(allocatedHosts()); + } + + @SuppressWarnings("deprecation") + default AllocatedHosts allocatedHosts() { + return getProvisionInfo().get(); + } /** * Returns whether this application allows serving config request for a different version. @@ -71,11 +80,6 @@ public interface Model { */ default boolean allowModelVersionMismatch(Instant now) { return false; } - /** @deprecated pass now. */ - // TODO: Remove this when no version older than 6.115 is deployed anywhere - @Deprecated - default boolean allowModelVersionMismatch() { return allowModelVersionMismatch(Clock.systemUTC().instant()); } - /** * Returns whether old config models should be loaded (default) or not. * Skipping old config models is a validation override which is useful when the old model @@ -88,9 +92,4 @@ public interface Model { */ default boolean skipOldConfigModels(Instant now) { return false; } - /** @deprecated pass now. */ - // TODO: Remove this when no version older than 6.115 is deployed anywhere - @Deprecated - default boolean skipOldConfigModels() { return skipOldConfigModels(Clock.systemUTC().instant()); } - } diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java index 5afc570d81b..5b79415c132 100644 --- a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java +++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java @@ -17,7 +17,7 @@ import java.util.Set; /** * Model context containing state provided to model factories. * - * @author lulf + * @author Ulf Lilleengen */ public interface ModelContext { @@ -31,11 +31,6 @@ public interface ModelContext { Properties properties(); default Optional<File> appDir() { return Optional.empty();} - /** @deprecated TODO: Remove this when no config models older than 6.98 are used */ - @SuppressWarnings("unused") - @Deprecated - default Optional<com.yahoo.config.provision.Version> vespaVersion() { return Optional.empty(); } - /** The Vespa version this model is built for */ Version modelVespaVersion(); @@ -50,4 +45,5 @@ public interface ModelContext { Zone zone(); Set<Rotation> rotations(); } + } diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelState.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelState.java index 10153ca07df..c6ac913ad14 100644 --- a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelState.java +++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelState.java @@ -5,5 +5,7 @@ package com.yahoo.config.model.api; * @author lulf */ public interface ModelState { + Model getModel(); + } diff --git a/config-model/src/main/java/com/yahoo/config/model/provision/HostsXmlProvisioner.java b/config-model/src/main/java/com/yahoo/config/model/provision/HostsXmlProvisioner.java index 85f05116e5b..f909f3864da 100644 --- a/config-model/src/main/java/com/yahoo/config/model/provision/HostsXmlProvisioner.java +++ b/config-model/src/main/java/com/yahoo/config/model/provision/HostsXmlProvisioner.java @@ -14,7 +14,6 @@ import java.util.List; * application if one exists. Pre-condition: A valid hosts file. * * @author hmusum - * @since 5.11 */ public class HostsXmlProvisioner implements HostProvisioner { diff --git a/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java b/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java index 133d94c745b..8b97eb2503e 100644 --- a/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java +++ b/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java @@ -25,6 +25,7 @@ import java.util.*; * @author tonytv */ public class MockApplicationPackage implements ApplicationPackage { + public static final String MUSIC_SEARCHDEFINITION = createSearchDefinition("music", "foo"); public static final String BOOK_SEARCHDEFINITION = createSearchDefinition("book", "bar"); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/HostResource.java b/config-model/src/main/java/com/yahoo/vespa/model/HostResource.java index bc890755ca9..5e74a2ebc8a 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/HostResource.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/HostResource.java @@ -22,7 +22,7 @@ import java.util.stream.Collectors; * TODO: Merge with {@link Host} * Host resources are ordered by their host order. * - * @author Ulf Lillengen + * @author Ulf Lilleengen */ public class HostResource implements Comparable<HostResource> { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/VespaModel.java b/config-model/src/main/java/com/yahoo/vespa/model/VespaModel.java index c61435ca831..53cc8be9e96 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/VespaModel.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/VespaModel.java @@ -20,7 +20,7 @@ import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.producer.AbstractConfigProducer; import com.yahoo.config.model.producer.AbstractConfigProducerRoot; import com.yahoo.config.model.producer.UserConfigRepo; -import com.yahoo.config.provision.ProvisionInfo; +import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.log.LogLevel; import com.yahoo.vespa.config.ConfigDefinitionKey; import com.yahoo.vespa.config.ConfigKey; @@ -83,7 +83,7 @@ public final class VespaModel extends AbstractConfigProducerRoot implements Seri public static final Logger log = Logger.getLogger(VespaModel.class.getPackage().toString()); private ConfigModelRepo configModelRepo = new ConfigModelRepo(); - private final Optional<ProvisionInfo> info; + private final AllocatedHosts allocatedHosts; /** * The config id for the root config producer @@ -146,7 +146,7 @@ public final class VespaModel extends AbstractConfigProducerRoot implements Seri if (complete) { // create a a completed, frozen model configModelRepo.readConfigModels(deployState, builder, root, configModelRegistry); addServiceClusters(deployState.getApplicationPackage(), builder); - this.info = Optional.of(createProvisionInfo()); // must happen after the two lines above + this.allocatedHosts = AllocatedHosts.withHosts(root.getHostSystem().getHostSpecs()); // must happen after the two lines above setupRouting(); this.fileDistributor = root.getFileDistributionConfigProducer().getFileDistributor(); getAdmin().addPerHostServices(getHostSystem().getHosts(), deployState.getProperties()); @@ -157,7 +157,7 @@ public final class VespaModel extends AbstractConfigProducerRoot implements Seri this.deployState = null; } else { // create a model with no services instantiated and the given file distributor - this.info = Optional.of(createProvisionInfo()); + this.allocatedHosts = AllocatedHosts.withHosts(root.getHostSystem().getHostSpecs()); this.fileDistributor = fileDistributor; } } @@ -167,10 +167,6 @@ public final class VespaModel extends AbstractConfigProducerRoot implements Seri return new VespaModel(new NullConfigModelRegistry(), deployState, false, new FileDistributor(deployState.getFileRegistry())); } - private ProvisionInfo createProvisionInfo() { - return ProvisionInfo.withHosts(root.getHostSystem().getHostSpecs()); - } - private void validateWrapExceptions() { try { validate(); @@ -421,8 +417,8 @@ public final class VespaModel extends AbstractConfigProducerRoot implements Seri } @Override - public Optional<ProvisionInfo> getProvisionInfo() { - return info; + public AllocatedHosts allocatedHosts() { + return allocatedHosts; } private static Set<ConfigKey<?>> configsProduced(ConfigProducer cp) { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java b/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java index 64cb415fc7d..fc27f9e8dc7 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java @@ -34,7 +34,7 @@ import java.util.logging.Logger; /** * Factory for creating {@link VespaModel} instances. * - * @author lulf + * @author Ulf Lilleengen */ public class VespaModelFactory implements ModelFactory { @@ -42,9 +42,17 @@ public class VespaModelFactory implements ModelFactory { private final ConfigModelRegistry configModelRegistry; private final Zone zone; private final Clock clock; + private final Version version; + /** Creates a factory for vespa models for this version of the source */ @Inject public VespaModelFactory(ComponentRegistry<ConfigModelPlugin> pluginRegistry, Zone zone) { + this(Version.fromIntValues(VespaVersion.major, VespaVersion.minor, VespaVersion.micro), pluginRegistry, zone); + } + + /** Creates a factory for vespa models of a particular version */ + public VespaModelFactory(Version version, ComponentRegistry<ConfigModelPlugin> pluginRegistry, Zone zone) { + this.version = version; List<ConfigModelBuilder> modelBuilders = new ArrayList<>(); for (ConfigModelPlugin plugin : pluginRegistry.allComponents()) { if (plugin instanceof ConfigModelBuilder) { @@ -55,11 +63,15 @@ public class VespaModelFactory implements ModelFactory { this.zone = zone; this.clock = Clock.systemUTC(); } - + public VespaModelFactory(ConfigModelRegistry configModelRegistry) { this(configModelRegistry, Clock.systemUTC()); } public VespaModelFactory(ConfigModelRegistry configModelRegistry, Clock clock) { + this(Version.fromIntValues(VespaVersion.major, VespaVersion.minor, VespaVersion.micro), configModelRegistry, clock); + } + public VespaModelFactory(Version version, ConfigModelRegistry configModelRegistry, Clock clock) { + this.version = version; if (configModelRegistry == null) { this.configModelRegistry = new NullConfigModelRegistry(); log.info("Will not load config models from plugins, as no registry is available"); @@ -72,9 +84,7 @@ public class VespaModelFactory implements ModelFactory { /** Returns the version this model is build for */ @Override - public Version getVersion() { - return Version.fromIntValues(VespaVersion.major, VespaVersion.minor, VespaVersion.micro); - } + public Version getVersion() { return version; } @Override public Model createModel(ModelContext modelContext) { diff --git a/config-model/src/test/java/com/yahoo/config/model/provision/HostSpecTest.java b/config-model/src/test/java/com/yahoo/config/model/provision/HostSpecTest.java index 6a17f314d26..51b039a7532 100644 --- a/config-model/src/test/java/com/yahoo/config/model/provision/HostSpecTest.java +++ b/config-model/src/test/java/com/yahoo/config/model/provision/HostSpecTest.java @@ -11,10 +11,10 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** - * @author lulf - * @since 5.11 + * @author Ulf Lilleengen */ public class HostSpecTest { + @Test public void testEquals() { HostSpec h1 = new HostSpec("foo", Collections.<String>emptyList()); @@ -42,4 +42,5 @@ public class HostSpecTest { assertFalse(h4.equals(h3)); assertTrue(h4.equals(h4)); } + } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTestCase.java b/config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTestCase.java index 8102f358830..cc3f4a22966 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTestCase.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTestCase.java @@ -18,7 +18,7 @@ import com.yahoo.config.model.provision.InMemoryProvisioner; import com.yahoo.config.model.test.MockApplicationPackage; import com.yahoo.config.model.test.TestDriver; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.ProvisionInfo; +import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.document.config.DocumentmanagerConfig; import com.yahoo.messagebus.MessagebusConfig; import com.yahoo.net.HostName; @@ -286,7 +286,7 @@ public class VespaModelTestCase { .build()) .build(); VespaModel model = new VespaModel(new NullConfigModelRegistry(), deployState); - ProvisionInfo info = model.getProvisionInfo().get(); + AllocatedHosts info = model.allocatedHosts(); assertEquals("Admin version 3 is ignored, and there are no other hosts to borrow for admin services", 0, info.getHosts().size()); } diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/AllocatedHosts.java b/config-provisioning/src/main/java/com/yahoo/config/provision/AllocatedHosts.java new file mode 100644 index 00000000000..13efc2b3337 --- /dev/null +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/AllocatedHosts.java @@ -0,0 +1,116 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.config.provision; + +import com.google.common.collect.ImmutableSet; +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 java.io.IOException; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; + +/** + * The hosts allocated to an application. + * This can be serialized to/from JSON. + * This is immutable. + * + * @author Ulf Lilleengen + * @author bratseth + */ +public class AllocatedHosts { + + private static final String mappingKey = "mapping"; + private static final String hostSpecKey = "hostSpec"; + private static final String hostSpecHostName = "hostName"; + private static final String hostSpecMembership = "membership"; + private static final String hostSpecFlavor = "flavor"; + private static final String hostSpecVespaVersion = "vespaVersion"; + + private final ImmutableSet<HostSpec> hosts; + + AllocatedHosts(Set<HostSpec> hosts) { + this.hosts = ImmutableSet.copyOf(hosts); + } + + public static AllocatedHosts withHosts(Set<HostSpec> hosts) { + return new AllocatedHosts(hosts); + } + + private void toSlime(Cursor cursor) { + Cursor array = cursor.setArray(mappingKey); + for (HostSpec host : hosts) + toSlime(host, array.addObject().setObject(hostSpecKey)); + } + + private void toSlime(HostSpec host, Cursor cursor) { + cursor.setString(hostSpecHostName, host.hostname()); + if (host.membership().isPresent()) { + cursor.setString(hostSpecMembership, host.membership().get().stringValue()); + cursor.setString(hostSpecVespaVersion, host.membership().get().cluster().vespaVersion().toString()); + } + if (host.flavor().isPresent()) + cursor.setString(hostSpecFlavor, host.flavor().get().name()); + } + + /** Returns the hosts of this allocation */ + public Set<HostSpec> getHosts() { return hosts; } + + private static AllocatedHosts fromSlime(Inspector inspector, Optional<NodeFlavors> nodeFlavors) { + Inspector array = inspector.field(mappingKey); + Set<HostSpec> hosts = new LinkedHashSet<>(); + array.traverse(new ArrayTraverser() { + @Override + public void entry(int i, Inspector inspector) { + hosts.add(hostsFromSlime(inspector.field(hostSpecKey), nodeFlavors)); + } + }); + return new AllocatedHosts(hosts); + } + + static HostSpec hostsFromSlime(Inspector object, Optional<NodeFlavors> nodeFlavors) { + Optional<ClusterMembership> membership = + object.field(hostSpecMembership).valid() ? Optional.of(membershipFromSlime(object)) : Optional.empty(); + Optional<Flavor> flavor = + object.field(hostSpecFlavor).valid() ? flavorFromSlime(object, nodeFlavors) : Optional.empty(); + + return new HostSpec(object.field(hostSpecHostName).asString(),Collections.emptyList(), flavor, membership); + } + + private static ClusterMembership membershipFromSlime(Inspector object) { + return ClusterMembership.from(object.field(hostSpecMembership).asString(), + com.yahoo.component.Version.fromString(object.field(hostSpecVespaVersion).asString())); + } + + private static Optional<Flavor> flavorFromSlime(Inspector object, Optional<NodeFlavors> nodeFlavors) { + return nodeFlavors.map(flavorMapper -> flavorMapper.getFlavor(object.field(hostSpecFlavor).asString())) + .orElse(Optional.empty()); + } + + public byte[] toJson() throws IOException { + Slime slime = new Slime(); + toSlime(slime.setObject()); + return SlimeUtils.toJsonBytes(slime); + } + + public static AllocatedHosts fromJson(byte[] json, Optional<NodeFlavors> nodeFlavors) { + return fromSlime(SlimeUtils.jsonToSlime(json).get(), nodeFlavors); + } + + @Override + public boolean equals(Object other) { + if (other == this) return true; + if ( ! (other instanceof AllocatedHosts)) return false; + return ((AllocatedHosts) other).hosts.equals(this.hosts); + } + + @Override + public int hashCode() { + return hosts.hashCode(); + } + +} diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/HostSpec.java b/config-provisioning/src/main/java/com/yahoo/config/provision/HostSpec.java index 5d6b3fcaca4..dd8bc311939 100644 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/HostSpec.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/HostSpec.java @@ -5,11 +5,12 @@ import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Optional; /** * A specification of a host and its role. - * The identity of a host is determined by its name. + * Equality and order is determined by the host name. * * @author hmusum */ @@ -19,7 +20,7 @@ public class HostSpec implements Comparable<HostSpec> { private final String hostname; /** Aliases of this host */ - private final List<String> aliases; + private final ImmutableList<String> aliases; /** The current membership role of this host in the cluster it belongs to */ private final Optional<ClusterMembership> membership; @@ -69,10 +70,11 @@ public class HostSpec implements Comparable<HostSpec> { } @Override - public boolean equals(Object o) { - if ( ! (o instanceof HostSpec)) return false; - HostSpec other = (HostSpec) o; - return this.hostname().equals(other.hostname()); + public boolean equals(Object other) { + if (other == this) return true; + if ( ! (other instanceof HostSpec)) return false; + + return ((HostSpec)other).hostname.equals(this.hostname); } @Override diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ProvisionInfo.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ProvisionInfo.java index 8bef1f7c9b7..dbb1b55aeb9 100644 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/ProvisionInfo.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/ProvisionInfo.java @@ -1,63 +1,35 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.config.provision; 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 java.io.IOException; -import java.util.Collections; import java.util.LinkedHashSet; import java.util.Optional; import java.util.Set; /** - * Information about hosts provisioned for an application, and (de)serialization of this information to/from JSON. - * - * @author lulf - * @since 5.12 + * @author bratseth + * @deprecated use AllocatedHosts */ -public class ProvisionInfo { +// TODO: Remove when no version older than 6.143 is in production anywhere +@Deprecated +@SuppressWarnings("unused") +public class ProvisionInfo extends AllocatedHosts { private static final String mappingKey = "mapping"; private static final String hostSpecKey = "hostSpec"; - private static final String hostSpecHostName = "hostName"; - private static final String hostSpecMembership = "membership"; - private static final String hostSpecFlavor = "flavor"; - private static final String hostSpecVespaVersion = "vespaVersion"; - - private final Set<HostSpec> hosts = new LinkedHashSet<>(); private ProvisionInfo(Set<HostSpec> hosts) { - this.hosts.addAll(hosts); + super(hosts); } public static ProvisionInfo withHosts(Set<HostSpec> hosts) { return new ProvisionInfo(hosts); } - private void toSlime(Cursor cursor) { - Cursor array = cursor.setArray(mappingKey); - for (HostSpec host : hosts) { - Cursor object = array.addObject(); - serializeHostSpec(object.setObject(hostSpecKey), host); - } - } - - private void serializeHostSpec(Cursor cursor, HostSpec host) { - cursor.setString(hostSpecHostName, host.hostname()); - if (host.membership().isPresent()) { - cursor.setString(hostSpecMembership, host.membership().get().stringValue()); - cursor.setString(hostSpecVespaVersion, host.membership().get().cluster().vespaVersion().toString()); - } - if (host.flavor().isPresent()) - cursor.setString(hostSpecFlavor, host.flavor().get().name()); - } - - public Set<HostSpec> getHosts() { - return Collections.unmodifiableSet(hosts); + public static ProvisionInfo fromJson(byte[] json, Optional<NodeFlavors> nodeFlavors) { + return fromSlime(SlimeUtils.jsonToSlime(json).get(), nodeFlavors); } private static ProvisionInfo fromSlime(Inspector inspector, Optional<NodeFlavors> nodeFlavors) { @@ -66,46 +38,10 @@ public class ProvisionInfo { array.traverse(new ArrayTraverser() { @Override public void entry(int i, Inspector inspector) { - hosts.add(deserializeHostSpec(inspector.field(hostSpecKey), nodeFlavors)); + hosts.add(hostsFromSlime(inspector.field(hostSpecKey), nodeFlavors)); } }); return new ProvisionInfo(hosts); } - private static HostSpec deserializeHostSpec(Inspector object, Optional<NodeFlavors> nodeFlavors) { - Optional<ClusterMembership> membership = - object.field(hostSpecMembership).valid() ? Optional.of(readMembership(object)) : Optional.empty(); - Optional<Flavor> flavor = - object.field(hostSpecFlavor).valid() ? readFlavor(object, nodeFlavors) : Optional.empty(); - - return new HostSpec(object.field(hostSpecHostName).asString(),Collections.emptyList(), flavor, membership); - } - - private static ClusterMembership readMembership(Inspector object) { - return ClusterMembership.from(object.field(hostSpecMembership).asString(), - com.yahoo.component.Version.fromString(object.field(hostSpecVespaVersion).asString())); - } - - private static Optional<Flavor> readFlavor(Inspector object, Optional<NodeFlavors> nodeFlavors) { - return nodeFlavors.map(flavorMapper -> flavorMapper.getFlavor(object.field(hostSpecFlavor).asString())) - .orElse(Optional.empty()); - } - - public byte[] toJson() throws IOException { - Slime slime = new Slime(); - toSlime(slime.setObject()); - return SlimeUtils.toJsonBytes(slime); - } - - public static ProvisionInfo fromJson(byte[] json, Optional<NodeFlavors> nodeFlavors) { - return fromSlime(SlimeUtils.jsonToSlime(json).get(), nodeFlavors); - } - - public ProvisionInfo merge(ProvisionInfo provisionInfo) { - Set<HostSpec> mergedSet = new LinkedHashSet<>(); - mergedSet.addAll(this.hosts); - mergedSet.addAll(provisionInfo.getHosts()); - return ProvisionInfo.withHosts(mergedSet); - } - } diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/Provisioner.java b/config-provisioning/src/main/java/com/yahoo/config/provision/Provisioner.java index 980cd4a00b9..6be1d49ebd3 100644 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/Provisioner.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Provisioner.java @@ -9,8 +9,7 @@ import java.util.List; /** * Interface used by the config system to acquire hosts. * - * @author lulf - * @since 5.11 + * @author Ulf Lilleengen */ public interface Provisioner { diff --git a/config-provisioning/src/test/java/com/yahoo/config/provision/AllocatedHostsTest.java b/config-provisioning/src/test/java/com/yahoo/config/provision/AllocatedHostsTest.java new file mode 100644 index 00000000000..675af88596a --- /dev/null +++ b/config-provisioning/src/test/java/com/yahoo/config/provision/AllocatedHostsTest.java @@ -0,0 +1,52 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.config.provision; + +import org.junit.Test; + +import java.io.IOException; +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author Ulf Lilleengen + */ +public class AllocatedHostsTest { + + private final HostSpec h1 = new HostSpec("host1", Optional.empty()); + private final HostSpec h2 = new HostSpec("host2", Optional.empty()); + private final HostSpec h3 = new HostSpec("host3", Optional.of(ClusterMembership.from("container/test/0", com.yahoo.component.Version.fromString("6.73.1")))); + + @Test + public void testAllocatedHostsSerialization() throws IOException { + Set<HostSpec> hosts = new LinkedHashSet<>(); + hosts.add(h1); + hosts.add(h2); + hosts.add(h3); + AllocatedHosts info = AllocatedHosts.withHosts(hosts); + assertAllocatedHosts(info); + } + + private void assertAllocatedHosts(AllocatedHosts info) throws IOException { + AllocatedHosts serializedAllocatedHosts = AllocatedHosts.fromJson(info.toJson(), Optional.empty()); + assertEquals(info.getHosts().size(), serializedAllocatedHosts.getHosts().size()); + assertTrue(serializedAllocatedHosts.getHosts().contains(h1)); + assertTrue(serializedAllocatedHosts.getHosts().contains(h2)); + assertTrue(serializedAllocatedHosts.getHosts().contains(h3)); + assertTrue(!getHost(h1.hostname(), serializedAllocatedHosts.getHosts()).membership().isPresent()); + assertEquals("container/test/0", getHost(h3.hostname(), serializedAllocatedHosts.getHosts()).membership().get().stringValue()); + assertEquals(h3.membership().get().cluster().vespaVersion(), getHost(h3.hostname(), + serializedAllocatedHosts.getHosts()).membership().get().cluster().vespaVersion()); + } + + private HostSpec getHost(String hostname, Set<HostSpec> hosts) { + for (HostSpec host : hosts) + if (host.hostname().equals(hostname)) + return host; + throw new IllegalArgumentException("No host " + hostname + " is present"); + } + +} diff --git a/config-provisioning/src/test/java/com/yahoo/config/provision/ProvisionInfoTest.java b/config-provisioning/src/test/java/com/yahoo/config/provision/ProvisionInfoTest.java deleted file mode 100644 index 4fa69eb77e0..00000000000 --- a/config-provisioning/src/test/java/com/yahoo/config/provision/ProvisionInfoTest.java +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.config.provision; - -import org.junit.Test; - -import java.io.IOException; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -/** - * @author lulf - * @since 5.12 - */ -public class ProvisionInfoTest { - - private final HostSpec h1 = new HostSpec("host1", Optional.empty()); - private final HostSpec h2 = new HostSpec("host2", Optional.empty()); - private final HostSpec h3 = new HostSpec("host3", Optional.of(ClusterMembership.from("container/test/0", com.yahoo.component.Version.fromString("6.73.1")))); - - @Test - public void testProvisionInfoSerialization() throws IOException { - Set<HostSpec> hosts = new LinkedHashSet<>(); - hosts.add(h1); - hosts.add(h2); - hosts.add(h3); - ProvisionInfo info = ProvisionInfo.withHosts(hosts); - assertProvisionInfo(info); - } - - @Test - public void testProvisionInfoMerging() throws IOException { - Set<HostSpec> hostsA = new LinkedHashSet<>(Collections.singleton(h1)); - Set<HostSpec> hostsB = new LinkedHashSet<>(); - hostsB.add(h2); - hostsB.add(h3); - - ProvisionInfo infoA = ProvisionInfo.withHosts(hostsA); - ProvisionInfo infoB = ProvisionInfo.withHosts(hostsB); - assertProvisionInfo(infoA.merge(infoB)); - assertProvisionInfo(infoB.merge(infoA)); - } - - private void assertProvisionInfo(ProvisionInfo info) throws IOException { - ProvisionInfo serializedInfo = ProvisionInfo.fromJson(info.toJson(), Optional.empty()); - assertEquals(info.getHosts().size(), serializedInfo.getHosts().size()); - assertTrue(serializedInfo.getHosts().contains(h1)); - assertTrue(serializedInfo.getHosts().contains(h2)); - assertTrue(serializedInfo.getHosts().contains(h3)); - assertTrue(!getHost(h1.hostname(), serializedInfo.getHosts()).membership().isPresent()); - assertEquals("container/test/0", getHost(h3.hostname(), serializedInfo.getHosts()).membership().get().stringValue()); - assertEquals(h3.membership().get().cluster().vespaVersion(), getHost(h3.hostname(), serializedInfo.getHosts()).membership().get().cluster().vespaVersion()); - } - - private HostSpec getHost(String hostname, Set<HostSpec> hosts) { - for (HostSpec host : hosts) - if (host.hostname().equals(hostname)) - return host; - throw new IllegalArgumentException("No host " + hostname + " is present"); - } - -} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/GlobalComponentRegistry.java b/configserver/src/main/java/com/yahoo/vespa/config/server/GlobalComponentRegistry.java index 1bda8dcb69a..4f26cfa265b 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/GlobalComponentRegistry.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/GlobalComponentRegistry.java @@ -19,8 +19,7 @@ import java.util.Optional; /** * Interface representing all global config server components used within the config server. * - * @author lulf - * @since 5.1 + * @author Ulf Lilleengen */ public interface GlobalComponentRegistry { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistry.java b/configserver/src/main/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistry.java index f88a0ef1a2d..fa5224732f6 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistry.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistry.java @@ -22,8 +22,7 @@ import java.util.Optional; /** * Registry containing all the "static"/"global" components in a config server in one place. * - * @author lulf - * @since 5.1 + * @author Ulf Lilleengen */ public class InjectedGlobalComponentRegistry implements GlobalComponentRegistry { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java index 94abbc10046..0435c8e59db 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java @@ -9,8 +9,7 @@ import java.util.List; /** * The applications of a tenant * - * @author lulf - * @since 5.1 + * @author Ulf Lilleengen */ public interface TenantApplications { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ZKTenantApplications.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ZKTenantApplications.java index ac318aba8e8..5260dd9228c 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ZKTenantApplications.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ZKTenantApplications.java @@ -29,8 +29,7 @@ import java.util.logging.Logger; * Each application is stored as a single file, named the same as the application id and containing the id * of the session storing the content of the application. * - * @author lulf - * @since 5.1 + * @author Ulf Lilleengen */ // TODO: Merge into interface and separate out curator layer instead public class ZKTenantApplications implements TenantApplications, PathChildrenCacheListener { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java index 61382af6a30..51995eb98cf 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java @@ -136,7 +136,7 @@ public class Deployment implements com.yahoo.config.provision.Deployment { transaction.add(deactivateCurrentActivateNew(localSessionRepo.getActiveSession(session.getApplicationId()), session, ignoreSessionStaleFailure)); if (hostProvisioner.isPresent()) { - hostProvisioner.get().activate(transaction, session.getApplicationId(), session.getProvisionInfo().getHosts()); + hostProvisioner.get().activate(transaction, session.getApplicationId(), session.getAllocatedHosts().getHosts()); } transaction.commit(); session.waitUntilActivated(timeoutBudget); 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/deploy/ZooKeeperClient.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClient.java index ea278596e80..69266620e45 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClient.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClient.java @@ -6,7 +6,7 @@ import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.application.api.ApplicationFile; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.application.api.UnparsedConfigDefinition; -import com.yahoo.config.provision.ProvisionInfo; +import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.Version; import com.yahoo.io.reader.NamedReader; import com.yahoo.log.LogLevel; @@ -25,13 +25,12 @@ import java.util.*; * A class used for reading and writing application data to zookeeper. * * @author hmusum - * @since 5.1 */ public class ZooKeeperClient { private final ConfigCurator configCurator; private final DeployLogger logger; - private final boolean trace; + private final boolean logFine; /* This is the generation that will be used for reading and writing application data. (1 more than last deployed application) */ private final Path rootPath; @@ -42,10 +41,10 @@ public class ZooKeeperClient { } }; - public ZooKeeperClient(ConfigCurator configCurator, DeployLogger logger, boolean trace, Path rootPath) { + public ZooKeeperClient(ConfigCurator configCurator, DeployLogger logger, boolean logFine, Path rootPath) { this.configCurator = configCurator; this.logger = logger; - this.trace = trace; + this.logFine = logFine; this.rootPath = rootPath; } @@ -62,7 +61,7 @@ public class ZooKeeperClient { try { while (retries > 0) { try { - trace("Setting up ZooKeeper nodes for this application"); + logFine("Setting up ZooKeeper nodes for this application"); createZooKeeperNodes(); break; } catch (RuntimeException e) { @@ -105,28 +104,28 @@ public class ZooKeeperClient { * * @param app the application package to feed to zookeeper */ - void feedZooKeeper(ApplicationPackage app) { - trace("Feeding application config into ZooKeeper"); + void write(ApplicationPackage app) { + logFine("Feeding application config into ZooKeeper"); // gives lots and lots of debug output: // BasicConfigurator.configure(); try { - trace("zk operations: " + configCurator.getNumberOfOperations()); - trace("zk operations: " + configCurator.getNumberOfOperations()); - trace("Feeding user def files into ZooKeeper"); - feedZKUserDefs(app); - trace("zk operations: " + configCurator.getNumberOfOperations()); - trace("Feeding application package into ZooKeeper"); + logFine("zk operations: " + configCurator.getNumberOfOperations()); + logFine("zk operations: " + configCurator.getNumberOfOperations()); + logFine("Feeding user def files into ZooKeeper"); + writeUserDefs(app); + logFine("zk operations: " + configCurator.getNumberOfOperations()); + logFine("Feeding application package into ZooKeeper"); // TODO 1200 zk operations done in the below method - feedZKAppPkg(app); - feedSearchDefinitions(app); - feedZKUserIncludeDirs(app, app.getUserIncludeDirs()); - trace("zk operations: " + configCurator.getNumberOfOperations()); - trace("zk read operations: " + configCurator.getNumberOfReadOperations()); - trace("zk write operations: " + configCurator.getNumberOfWriteOperations()); - trace("Feeding sd from docproc bundle into ZooKeeper"); - trace("zk operations: " + configCurator.getNumberOfOperations()); - trace("Write application metadata into ZooKeeper"); - feedZKAppMetaData(app.getMetaData()); - trace("zk operations: " + configCurator.getNumberOfOperations()); + writeSomeOf(app); + writeSearchDefinitions(app); + writeUserIncludeDirs(app, app.getUserIncludeDirs()); + logFine("zk operations: " + configCurator.getNumberOfOperations()); + logFine("zk read operations: " + configCurator.getNumberOfReadOperations()); + logFine("zk write operations: " + configCurator.getNumberOfWriteOperations()); + logFine("Feeding sd from docproc bundle into ZooKeeper"); + logFine("zk operations: " + configCurator.getNumberOfOperations()); + logFine("Write application metadata into ZooKeeper"); + write(app.getMetaData()); + logFine("zk operations: " + configCurator.getNumberOfOperations()); } catch (Exception e) { throw new IllegalStateException("Unable to write vespa model to config server(s) " + System.getProperty("configsources") + "\n" + "Please ensure that cloudconfig_server is started on the config server node(s), " + @@ -134,7 +133,7 @@ public class ZooKeeperClient { } } - private void feedSearchDefinitions(ApplicationPackage app) throws IOException { + private void writeSearchDefinitions(ApplicationPackage app) throws IOException { Collection<NamedReader> sds = app.getSearchDefinitions(); if (sds.isEmpty()) { return; @@ -142,7 +141,7 @@ public class ZooKeeperClient { Path zkPath = getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH).append(ApplicationPackage.SEARCH_DEFINITIONS_DIR); configCurator.createNode(zkPath.getAbsolute()); // Ensures that ranking expressions and other files are also fed. - feedDirZooKeeper(app.getFile(ApplicationPackage.SEARCH_DEFINITIONS_DIR), zkPath, false); + writeDir(app.getFile(ApplicationPackage.SEARCH_DEFINITIONS_DIR), zkPath, false); for (NamedReader sd : sds) { String name = sd.getName(); Reader reader = sd.getReader(); @@ -153,12 +152,12 @@ public class ZooKeeperClient { } /** - * Puts the application package files into ZK + * Puts some of the application package files into ZK - see write(app). * * @param app The application package to use as input. * @throws java.io.IOException if not able to write to Zookeeper */ - void feedZKAppPkg(ApplicationPackage app) throws IOException { + void writeSomeOf(ApplicationPackage app) throws IOException { ApplicationFile.PathFilter srFilter = new ApplicationFile.PathFilter() { @Override public boolean accept(Path path) { @@ -167,40 +166,40 @@ public class ZooKeeperClient { }; // Copy app package files and subdirs into zk // TODO: We should have a way of doing this which doesn't require repeating all the content - feedFileZooKeeper(app.getFile(Path.fromString(ApplicationPackage.SERVICES)), - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH)); - feedFileZooKeeper(app.getFile(Path.fromString(ApplicationPackage.HOSTS)), - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH)); - feedFileZooKeeper(app.getFile(Path.fromString(ApplicationPackage.DEPLOYMENT_FILE.getName())), - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH)); - feedFileZooKeeper(app.getFile(Path.fromString(ApplicationPackage.VALIDATION_OVERRIDES.getName())), - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH)); + writeFile(app.getFile(Path.fromString(ApplicationPackage.SERVICES)), + getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH)); + writeFile(app.getFile(Path.fromString(ApplicationPackage.HOSTS)), + getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH)); + writeFile(app.getFile(Path.fromString(ApplicationPackage.DEPLOYMENT_FILE.getName())), + getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH)); + writeFile(app.getFile(Path.fromString(ApplicationPackage.VALIDATION_OVERRIDES.getName())), + getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH)); - feedDirZooKeeper(app.getFile(Path.fromString(ApplicationPackage.TEMPLATES_DIR)), - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH), - true); - feedDirZooKeeper(app.getFile(ApplicationPackage.RULES_DIR), - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH).append(ApplicationPackage.RULES_DIR), - srFilter, true); - feedDirZooKeeper(app.getFile(ApplicationPackage.QUERY_PROFILES_DIR), - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH).append(ApplicationPackage.QUERY_PROFILES_DIR), - xmlFilter, true); - feedDirZooKeeper(app.getFile(ApplicationPackage.PAGE_TEMPLATES_DIR), - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH).append(ApplicationPackage.PAGE_TEMPLATES_DIR), - xmlFilter, true); - feedDirZooKeeper(app.getFile(Path.fromString(ApplicationPackage.SEARCHCHAINS_DIR)), - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH).append(ApplicationPackage.SEARCHCHAINS_DIR), - xmlFilter, true); - feedDirZooKeeper(app.getFile(Path.fromString(ApplicationPackage.DOCPROCCHAINS_DIR)), - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH).append(ApplicationPackage.DOCPROCCHAINS_DIR), - xmlFilter, true); - feedDirZooKeeper(app.getFile(Path.fromString(ApplicationPackage.ROUTINGTABLES_DIR)), - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH).append(ApplicationPackage.ROUTINGTABLES_DIR), - xmlFilter, true); + writeDir(app.getFile(Path.fromString(ApplicationPackage.TEMPLATES_DIR)), + getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH), + true); + writeDir(app.getFile(ApplicationPackage.RULES_DIR), + getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH).append(ApplicationPackage.RULES_DIR), + srFilter, true); + writeDir(app.getFile(ApplicationPackage.QUERY_PROFILES_DIR), + getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH).append(ApplicationPackage.QUERY_PROFILES_DIR), + xmlFilter, true); + writeDir(app.getFile(ApplicationPackage.PAGE_TEMPLATES_DIR), + getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH).append(ApplicationPackage.PAGE_TEMPLATES_DIR), + xmlFilter, true); + writeDir(app.getFile(Path.fromString(ApplicationPackage.SEARCHCHAINS_DIR)), + getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH).append(ApplicationPackage.SEARCHCHAINS_DIR), + xmlFilter, true); + writeDir(app.getFile(Path.fromString(ApplicationPackage.DOCPROCCHAINS_DIR)), + getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH).append(ApplicationPackage.DOCPROCCHAINS_DIR), + xmlFilter, true); + writeDir(app.getFile(Path.fromString(ApplicationPackage.ROUTINGTABLES_DIR)), + getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH).append(ApplicationPackage.ROUTINGTABLES_DIR), + xmlFilter, true); } - private void feedDirZooKeeper(ApplicationFile file, Path zooKeeperAppPath, boolean recurse) throws IOException { - feedDirZooKeeper(file, zooKeeperAppPath, new ApplicationFile.PathFilter() { + private void writeDir(ApplicationFile file, Path zooKeeperAppPath, boolean recurse) throws IOException { + writeDir(file, zooKeeperAppPath, new ApplicationFile.PathFilter() { @Override public boolean accept(Path path) { return true; @@ -208,7 +207,7 @@ public class ZooKeeperClient { }, recurse); } - private void feedDirZooKeeper(ApplicationFile dir, Path path, ApplicationFile.PathFilter filenameFilter, boolean recurse) throws IOException { + private void writeDir(ApplicationFile dir, Path path, ApplicationFile.PathFilter filenameFilter, boolean recurse) throws IOException { if (!dir.isDirectory()) { logger.log(LogLevel.FINE, dir.getPath().getAbsolute()+" is not a directory. Not feeding the files into ZooKeeper."); return; @@ -220,10 +219,10 @@ public class ZooKeeperClient { if (file.isDirectory()) { configCurator.createNode(path.append(name).getAbsolute()); if (recurse) { - feedDirZooKeeper(file, path.append(name), filenameFilter, recurse); + writeDir(file, path.append(name), filenameFilter, recurse); } } else { - feedFileZooKeeper(file, path); + writeFile(file, path); } } } @@ -248,7 +247,7 @@ public class ZooKeeperClient { return ret; } - private void feedFileZooKeeper(ApplicationFile file, Path zkPath) throws IOException { + private void writeFile(ApplicationFile file, Path zkPath) throws IOException { if (!file.exists()) { return; } @@ -260,7 +259,7 @@ public class ZooKeeperClient { } } - private void feedZKUserIncludeDirs(ApplicationPackage applicationPackage, List<String> userIncludeDirs) throws IOException { + private void writeUserIncludeDirs(ApplicationPackage applicationPackage, List<String> userIncludeDirs) throws IOException { // User defined include directories for (String userInclude : userIncludeDirs) { ApplicationFile dir = applicationPackage.getFile(Path.fromString(userInclude)); @@ -268,9 +267,9 @@ public class ZooKeeperClient { if (files == null || files.isEmpty()) { configCurator.createNode(getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH + "/" + userInclude).getAbsolute()); } - feedDirZooKeeper(dir, - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH + "/" + userInclude), - xmlFilter, true); + writeDir(dir, + getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH + "/" + userInclude), + xmlFilter, true); } } @@ -278,22 +277,22 @@ public class ZooKeeperClient { * Feeds all user-defined .def file from the application package into ZooKeeper (both into * /defconfigs and /userdefconfigs */ - private void feedZKUserDefs(ApplicationPackage applicationPackage) { + private void writeUserDefs(ApplicationPackage applicationPackage) { Map<ConfigDefinitionKey, UnparsedConfigDefinition> configDefs = applicationPackage.getAllExistingConfigDefs(); for (Map.Entry<ConfigDefinitionKey, UnparsedConfigDefinition> entry : configDefs.entrySet()) { ConfigDefinitionKey key = entry.getKey(); String contents = entry.getValue().getUnparsedContent(); - feedDefToZookeeper(key.getName(), key.getNamespace(), getZooKeeperAppPath(ConfigCurator.USER_DEFCONFIGS_ZK_SUBPATH).getAbsolute(), contents); - feedDefToZookeeper(key.getName(), key.getNamespace(), getZooKeeperAppPath(ConfigCurator.DEFCONFIGS_ZK_SUBPATH).getAbsolute(), contents); + write(key.getName(), key.getNamespace(), getZooKeeperAppPath(ConfigCurator.USER_DEFCONFIGS_ZK_SUBPATH).getAbsolute(), contents); + write(key.getName(), key.getNamespace(), getZooKeeperAppPath(ConfigCurator.DEFCONFIGS_ZK_SUBPATH).getAbsolute(), contents); } logger.log(LogLevel.FINE, configDefs.size() + " user config definitions"); } - private void feedDefToZookeeper(String name, String namespace, String path, String data) { - feedDefToZookeeper(name, namespace, "", path, com.yahoo.text.Utf8.toBytes(data)); + private void write(String name, String namespace, String path, String data) { + write(name, namespace, "", path, com.yahoo.text.Utf8.toBytes(data)); } - private void feedDefToZookeeper(String name, String namespace, String version, String path, byte[] data) { + private void write(String name, String namespace, String version, String path, byte[] data) { configCurator.putDefData( ("".equals(namespace)) ? name : (namespace + "." + name), version, @@ -301,8 +300,8 @@ public class ZooKeeperClient { data); } - private void feedZKFileRegistry(Version vespaVersion, FileRegistry fileRegistry) { - trace("Feeding file registry data into ZooKeeper"); + private void write(Version vespaVersion, FileRegistry fileRegistry) { + logFine("Feeding file registry data into ZooKeeper"); String exportedRegistry = PreGeneratedFileRegistry.exportRegistry(fileRegistry); configCurator.putData(getZooKeeperAppPath(null).append(ZKApplicationPackage.fileRegistryNode).getAbsolute(), @@ -316,12 +315,12 @@ public class ZooKeeperClient { * * @param metaData The application metadata. */ - private void feedZKAppMetaData(ApplicationMetaData metaData) { + private void write(ApplicationMetaData metaData) { configCurator.putData(getZooKeeperAppPath(ConfigCurator.META_ZK_PATH).getAbsolute(), metaData.asJsonString()); } void cleanupZooKeeper() { - trace("Exception occurred. Cleaning up ZooKeeper"); + logFine("Exception occurred. Cleaning up ZooKeeper"); try { for (String subPath : Arrays.asList( ConfigCurator.DEFCONFIGS_ZK_SUBPATH, @@ -350,26 +349,20 @@ public class ZooKeeperClient { } } - void trace(String msg) { - if (trace) { + void logFine(String msg) { + if (logFine) { logger.log(LogLevel.FINE, msg); } } - private void feedProvisionInfo(Version version, ProvisionInfo info) throws IOException { - byte[] json = info.toJson(); - configCurator.putData(rootPath.append(ZKApplicationPackage.allocatedHostsNode).append(version.toSerializedForm()).getAbsolute(), json); + public void write(AllocatedHosts info) throws IOException { + configCurator.putData(rootPath.append(ZKApplicationPackage.allocatedHostsNode).getAbsolute(), info.toJson()); } - public void feedZKFileRegistries(Map<Version, FileRegistry> fileRegistryMap) { + public void write(Map<Version, FileRegistry> fileRegistryMap) { for (Map.Entry<Version, FileRegistry> versionFileRegistryEntry : fileRegistryMap.entrySet()) { - feedZKFileRegistry(versionFileRegistryEntry.getKey(), versionFileRegistryEntry.getValue()); + write(versionFileRegistryEntry.getKey(), versionFileRegistryEntry.getValue()); } } - public void feedProvisionInfos(Map<Version, ProvisionInfo> provisionInfoMap) throws IOException { - for (Map.Entry<Version, ProvisionInfo> versionProvisionInfoEntry : provisionInfoMap.entrySet()) { - feedProvisionInfo(versionProvisionInfoEntry.getKey(), versionProvisionInfoEntry.getValue()); - } - } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployer.java index 246d7226cfd..22ce952481d 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployer.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployer.java @@ -3,7 +3,7 @@ package com.yahoo.vespa.config.server.deploy; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.application.api.FileRegistry; -import com.yahoo.config.provision.ProvisionInfo; +import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.Version; import java.io.IOException; @@ -14,7 +14,6 @@ import java.util.Map; * Initialize must be called before each deploy. * * @author lulf - * @since 5.1 */ public class ZooKeeperDeployer { @@ -28,15 +27,16 @@ public class ZooKeeperDeployer { * Deploys an application package to zookeeper. initialize() must be called before calling this method. * * @param applicationPackage The application package to persist. - * @param fileRegistryMap The file registries to persist. - * @param provisionInfoMap The provisioning infos to persist. + * @param fileRegistryMap the file registries to persist. + * @param allocatedHosts the provisioning info to persist. * @throws IOException if deploying fails */ - public void deploy(ApplicationPackage applicationPackage, Map<Version, FileRegistry> fileRegistryMap, Map<Version, ProvisionInfo> provisionInfoMap) throws IOException { + public void deploy(ApplicationPackage applicationPackage, Map<Version, FileRegistry> fileRegistryMap, + AllocatedHosts allocatedHosts) throws IOException { zooKeeperClient.setupZooKeeper(); - zooKeeperClient.feedZooKeeper(applicationPackage); - zooKeeperClient.feedZKFileRegistries(fileRegistryMap); - zooKeeperClient.feedProvisionInfos(provisionInfoMap); + zooKeeperClient.write(applicationPackage); + zooKeeperClient.write(fileRegistryMap); + zooKeeperClient.write(allocatedHosts); } public void cleanup() { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/host/HostRegistry.java b/configserver/src/main/java/com/yahoo/vespa/config/server/host/HostRegistry.java index 36e7737163a..e61fc124d53 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/host/HostRegistry.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/host/HostRegistry.java @@ -16,7 +16,6 @@ import com.yahoo.log.LogLevel; * TODO: Is there a generalized version of this pattern? Need some sort mix of Bimap and Multimap * * @author lulf - * @since 5.3 */ public class HostRegistry<T> implements HostValidator<T> { 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 bbd1e189e29..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 @@ -5,12 +5,11 @@ import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.model.api.ConfigDefinitionRepo; -import com.yahoo.config.model.api.HostProvisioner; import com.yahoo.config.model.api.ModelContext; import com.yahoo.config.model.api.ModelFactory; import com.yahoo.config.model.application.provider.MockFileRegistry; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.ProvisionInfo; +import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.Version; import com.yahoo.config.provision.Zone; @@ -24,7 +23,6 @@ import com.yahoo.vespa.config.server.application.PermanentApplicationPackage; import com.yahoo.vespa.config.server.deploy.ModelContextImpl; import com.yahoo.vespa.config.server.monitoring.MetricUpdater; import com.yahoo.vespa.config.server.monitoring.Metrics; -import com.yahoo.vespa.config.server.provision.StaticProvisioner; import com.yahoo.vespa.config.server.session.SessionZooKeeperClient; import com.yahoo.vespa.config.server.session.SilentDeployLogger; import com.yahoo.vespa.curator.Curator; @@ -47,7 +45,6 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> { private final long appGeneration; private final SessionZooKeeperClient zkClient; private final Optional<PermanentApplicationPackage> permanentApplicationPackage; - private final Optional<com.yahoo.config.provision.Provisioner> hostProvisioner; private final ConfigserverConfig configserverConfig; private final ConfigDefinitionRepo configDefinitionRepo; private final Metrics metrics; @@ -56,7 +53,8 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> { private final DeployLogger logger; public ActivatedModelsBuilder(TenantName tenant, long appGeneration, SessionZooKeeperClient zkClient, GlobalComponentRegistry globalComponentRegistry) { - super(globalComponentRegistry.getModelFactoryRegistry()); + super(globalComponentRegistry.getModelFactoryRegistry(), + globalComponentRegistry.getHostProvisioner().isPresent()); this.tenant = tenant; this.appGeneration = appGeneration; this.zkClient = zkClient; @@ -64,17 +62,17 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> { this.configserverConfig = globalComponentRegistry.getConfigserverConfig(); this.configDefinitionRepo = globalComponentRegistry.getConfigDefinitionRepo(); this.metrics = globalComponentRegistry.getMetrics(); - this.hostProvisioner = globalComponentRegistry.getHostProvisioner(); this.curator = globalComponentRegistry.getCurator(); this.zone = globalComponentRegistry.getZone(); this.logger = new SilentDeployLogger(); } @Override - protected Application buildModelVersion(ModelFactory modelFactory, + protected Application buildModelVersion(ModelFactory modelFactory, ApplicationPackage applicationPackage, - ApplicationId applicationId, + ApplicationId applicationId, com.yahoo.component.Version wantedNodeVespaVersion, + 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)); @@ -86,7 +84,7 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> { logger, configDefinitionRepo, getForVersionOrLatest(applicationPackage.getFileRegistryMap(), modelFactory.getVersion()).orElse(new MockFileRegistry()), - createHostProvisioner(getForVersionOrLatest(applicationPackage.getProvisionInfoMap(), modelFactory.getVersion())), + createStaticProvisioner(applicationPackage.getAllocatedHosts()), createModelContextProperties(applicationId), Optional.empty(), new com.yahoo.component.Version(modelFactory.getVersion().toString()), @@ -96,17 +94,6 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> { applicationMetricUpdater, applicationId); } - private Optional<HostProvisioner> createHostProvisioner(Optional<ProvisionInfo> provisionInfo) { - if (hostProvisioner.isPresent() && provisionInfo.isPresent()) { - return Optional.of(createStaticProvisioner(provisionInfo.get())); - } - return Optional.empty(); - } - - private HostProvisioner createStaticProvisioner(ProvisionInfo provisionInfo) { - return new StaticProvisioner(provisionInfo); - } - private static <T> Optional<T> getForVersionOrLatest(Map<Version, T> map, Version version) { if (map.isEmpty()) { return Optional.empty(); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelFactoryRegistry.java b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelFactoryRegistry.java index ab1c84eb5dd..1c2c9c731d1 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelFactoryRegistry.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelFactoryRegistry.java @@ -13,7 +13,7 @@ import java.util.*; * A registry of model factories. Allows querying for a specific version of a {@link ModelFactory} or * simply returning all of them. Keeps track of the latest {@link com.yahoo.config.provision.Version} supported. * - * @author lulf + * @author Ulf Lilleengen */ public class ModelFactoryRegistry { 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 cc7a12801d3..6a4ab40d843 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 @@ -3,16 +3,20 @@ package com.yahoo.vespa.config.server.modelfactory; import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.config.application.api.ApplicationPackage; +import com.yahoo.config.model.api.HostProvisioner; import com.yahoo.config.model.api.ModelContext; import com.yahoo.config.model.api.ModelFactory; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.OutOfCapacityException; +import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.Rotation; import com.yahoo.config.provision.Version; import com.yahoo.config.provision.Zone; +import com.yahoo.lang.SettableOptional; import com.yahoo.vespa.config.server.ConfigServerSpec; import com.yahoo.vespa.config.server.deploy.ModelContextImpl; import com.yahoo.vespa.config.server.http.UnknownVespaVersionException; +import com.yahoo.vespa.config.server.provision.StaticProvisioner; import java.time.Instant; import java.util.ArrayList; @@ -37,13 +41,24 @@ public abstract class ModelsBuilder<MODELRESULT extends ModelResult> { private final ModelFactoryRegistry modelFactoryRegistry; - protected ModelsBuilder(ModelFactoryRegistry modelFactoryRegistry) { + /** True if we are running in hosted mode */ + private final boolean hosted; + + protected ModelsBuilder(ModelFactoryRegistry modelFactoryRegistry, boolean hosted) { this.modelFactoryRegistry = modelFactoryRegistry; + this.hosted = hosted; } + /** + * Builds all applicable model versions + * + * @param allocatedHosts the newest version (major and minor) (which is loaded first) decides the allocated hosts + * and assigns to this SettableOptional such that it can be used after this method returns + */ public List<MODELRESULT> buildModels(ApplicationId applicationId, com.yahoo.component.Version wantedNodeVespaVersion, ApplicationPackage applicationPackage, + SettableOptional<AllocatedHosts> allocatedHosts, Instant now) { Set<Version> versions = modelFactoryRegistry.allVersions(); @@ -52,7 +67,7 @@ public abstract class ModelsBuilder<MODELRESULT extends ModelResult> { if (requestedMajorVersion.isPresent()) versions = filterByMajorVersion(requestedMajorVersion.get(), versions); - // Load models by one major version at the time as new major versions are allowed to be unloadable + // Load models by one major version at the time as new major versions are allowed to be non-loadable // in the case where an existing application is incompatible with a new major version // (which is possible by the definition of major) List<Integer> majorVersions = versions.stream() @@ -65,7 +80,8 @@ public abstract class ModelsBuilder<MODELRESULT extends ModelResult> { for (int i = 0; i < majorVersions.size(); i++) { try { allApplicationModels.addAll(buildModelVersion(filterByMajorVersion(majorVersions.get(i), versions), - applicationId, wantedNodeVespaVersion, applicationPackage, now)); + applicationId, wantedNodeVespaVersion, applicationPackage, + allocatedHosts, now)); // skip old config models if requested after we have found a major version which works if (allApplicationModels.size() > 0 && allApplicationModels.get(0).getModel().skipOldConfigModels(now)) @@ -91,30 +107,43 @@ public abstract class ModelsBuilder<MODELRESULT extends ModelResult> { private List<MODELRESULT> buildModelVersion(Set<Version> versions, ApplicationId applicationId, com.yahoo.component.Version wantedNodeVespaVersion, ApplicationPackage applicationPackage, + SettableOptional<AllocatedHosts> allocatedHosts, 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.asOptional(), now); - if (latestApplicationVersion.getModel().skipOldConfigModels(now)) { - return Collections.singletonList(latestApplicationVersion); - } - else { // load old model versions - List<MODELRESULT> allApplicationVersions = new ArrayList<>(); - allApplicationVersions.add(latestApplicationVersion); - for (Version version : versions) { - if (version.equals(latest)) continue; // already loaded - allApplicationVersions.add(buildModelVersion(modelFactoryRegistry.getFactory(version), - applicationPackage, - applicationId, - wantedNodeVespaVersion, - now)); - } - return allApplicationVersions; + allocatedHosts.set(latestModelVersion.getModel().allocatedHosts()); // Update with additional clusters allocated + + if (latestModelVersion.getModel().skipOldConfigModels(now)) + return Collections.singletonList(latestModelVersion); + + // load old model versions + List<MODELRESULT> allApplicationVersions = new ArrayList<>(); + 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 + // does not. In that case we really want to extend the set of allocated hosts to include those of that + // cluster as well. To do that, create a new provisioner which uses static provisioning for known + // clusters and the node repository provisioner as fallback. + for (Version version : versions) { + if (version.equals(latest)) continue; // already loaded + + MODELRESULT modelVersion = buildModelVersion(modelFactoryRegistry.getFactory(version), + applicationPackage, + applicationId, + wantedNodeVespaVersion, + allocatedHosts.asOptional(), + now); + allocatedHosts.set(modelVersion.getModel().allocatedHosts()); // Update with additional clusters allocated + allApplicationVersions.add(modelVersion); } + return allApplicationVersions; } private Set<Version> filterByMajorVersion(int majorVersion, Set<Version> versions) { @@ -133,6 +162,7 @@ public abstract class ModelsBuilder<MODELRESULT extends ModelResult> { protected abstract MODELRESULT buildModelVersion(ModelFactory modelFactory, ApplicationPackage applicationPackage, ApplicationId applicationId, com.yahoo.component.Version wantedNodeVespaVersion, + Optional<AllocatedHosts> allocatedHosts, Instant now); protected ModelContext.Properties createModelContextProperties(ApplicationId applicationId, @@ -147,4 +177,15 @@ public abstract class ModelsBuilder<MODELRESULT extends ModelResult> { rotations); } + /** + * Returns a host provisioner returning the previously allocated hosts if available and when on hosted Vespa, + * 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> 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 b2dca075c4f..78d660b347e 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 @@ -12,6 +12,7 @@ import com.yahoo.config.model.api.ModelCreateResult; import com.yahoo.config.model.api.ModelFactory; 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.log.LogLevel; import com.yahoo.vespa.config.server.application.ApplicationSet; @@ -21,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; @@ -60,7 +62,7 @@ public class PreparedModelsBuilder extends ModelsBuilder<PreparedModelsBuilder.P PrepareParams params, Optional<ApplicationSet> currentActiveApplicationSet, ModelContext.Properties properties) { - super(modelFactoryRegistry); + super(modelFactoryRegistry, properties.hostedVespa()); this.permanentApplicationPackage = permanentApplicationPackage; this.configDefinitionRepo = configDefinitionRepo; @@ -79,14 +81,17 @@ public class PreparedModelsBuilder extends ModelsBuilder<PreparedModelsBuilder.P protected PreparedModelResult buildModelVersion(ModelFactory modelFactory, ApplicationPackage applicationPackage, ApplicationId applicationId, - com.yahoo.component.Version wantedNodeVespaVersion, Instant now) { + com.yahoo.component.Version wantedNodeVespaVersion, + Optional<AllocatedHosts> allocatedHosts, + Instant now) { Version modelVersion = modelFactory.getVersion(); log.log(LogLevel.DEBUG, "Building model " + modelVersion + " for " + applicationId); FileDistributionProvider fileDistributionProvider = fileDistributionFactory.createProvider( context.getServerDBSessionDir(), applicationId); - Optional<HostProvisioner> hostProvisioner = 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( @@ -109,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 the below :-\ + 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 ? @@ -124,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/HostProvisionerProvider.java b/configserver/src/main/java/com/yahoo/vespa/config/server/provision/HostProvisionerProvider.java index 619ee7499e2..32a641a3bc7 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/provision/HostProvisionerProvider.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/provision/HostProvisionerProvider.java @@ -13,21 +13,18 @@ import java.util.logging.Logger; /** * This class is necessary to support both having and not having a host provisioner. We inject * a component registry here, which then enables us to check whether or not we have a provisioner available. + * We only have a provisioner if we are running in hosted mode. * - * @author lulf - * @since 5.15 + * @author Ulf Lilleengen */ public class HostProvisionerProvider { - private static final Logger log = Logger.getLogger(HostProvisionerProvider.class.getName()); private final Optional<Provisioner> hostProvisioner; public HostProvisionerProvider(ComponentRegistry<Provisioner> hostProvisionerRegistry, ConfigserverConfig configserverConfig) { if (hostProvisionerRegistry.allComponents().isEmpty() || ! configserverConfig.hostedVespa()) { - log.info("Host provisioner is missing, provisioner component count: " + hostProvisionerRegistry.allComponents().size() + ", is hosted Vespa: " + configserverConfig.hostedVespa()); hostProvisioner = Optional.empty(); } else { - log.log(LogLevel.DEBUG, "Host provisioner injected. Will be used for all deployments"); hostProvisioner = Optional.of(hostProvisionerRegistry.allComponents().get(0)); } } @@ -36,6 +33,7 @@ public class HostProvisionerProvider { this(componentRegistry, new ConfigserverConfig(new ConfigserverConfig.Builder())); } + /** Returns the host provisioner, or empty if we are not in hosted mode */ public Optional<Provisioner> getHostProvisioner() { return hostProvisioner; } 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 ad0ee3b3eb9..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,8 +16,7 @@ import java.util.*; * behavior to the config model. Adapts interface from a {@link HostProvisioner} to a * {@link Provisioner}. * - * @author lulf - * @since 5.11 + * @author Ulf Lilleengen */ public class ProvisionerAdapter implements HostProvisioner { @@ -31,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 1cad735879a..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,19 +5,35 @@ 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; /** - * Host provisioning from an existing {@link ProvisionInfo} instance. + * Host provisioning from an existing {@link AllocatedHosts} instance. * * @author bratseth */ public class StaticProvisioner implements HostProvisioner { - private final ProvisionInfo provisionInfo; + private final AllocatedHosts allocatedHosts; + + /** The fallback provisioner to use for unknown clusters, or null to not fall back */ + private final HostProvisioner fallback; - public StaticProvisioner(ProvisionInfo provisionInfo) { - this.provisionInfo = provisionInfo; + /** + * 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 provisionInfo.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/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java index a222182f87a..308ca31f278 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java @@ -6,7 +6,7 @@ import com.yahoo.config.application.api.ApplicationFile; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.application.api.ApplicationMetaData; import com.yahoo.config.application.api.DeployLogger; -import com.yahoo.config.provision.ProvisionInfo; +import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.transaction.AbstractTransaction; import com.yahoo.transaction.NestedTransaction; import com.yahoo.transaction.Transaction; @@ -30,11 +30,10 @@ import java.util.Optional; * prepared. Deleting a local session will ensure that the local filesystem state and global zookeeper state is * cleaned for this session. * - * @author lulf - * @since 5.1 + * @author Ulf Lilleengen */ // This is really the store of an application, whether it is active or in an edit session -// TODO: Separate the "application store" and "session" aspects - the latter belongs in the HTTP layer +// TODO: Separate the "application store" and "session" aspects - the latter belongs in the HTTP layer -bratseth public class LocalSession extends Session implements Comparable<LocalSession> { private final ApplicationPackage applicationPackage; @@ -172,8 +171,8 @@ public class LocalSession extends Session implements Comparable<LocalSession> { public Version getVespaVersion() { return zooKeeperClient.readVespaVersion(); } - public ProvisionInfo getProvisionInfo() { - return zooKeeperClient.getProvisionInfo(); + public AllocatedHosts getAllocatedHosts() { + return zooKeeperClient.getAllocatedHosts(); } @Override diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionRepo.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionRepo.java index 7fff132dd7d..ae15be83062 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionRepo.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionRepo.java @@ -18,7 +18,6 @@ import java.util.logging.Logger; * File-based session repository for LocalSessions. Contains state for the local instance of the configserver. * * @author lulf - * @since 5.1 */ public class LocalSessionRepo extends SessionRepo<LocalSession> { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java index c20f6e0b853..5f3cf46e0de 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java @@ -2,6 +2,7 @@ package com.yahoo.vespa.config.server.session; import com.yahoo.config.provision.*; +import com.yahoo.lang.SettableOptional; import com.yahoo.vespa.config.server.*; import com.yahoo.log.LogLevel; import com.yahoo.vespa.config.server.application.ApplicationSet; @@ -19,8 +20,7 @@ import java.util.logging.Logger; * A RemoteSession represents a session created on another config server. This session can * be regarded as read only, and this interface only allows reading information about a session. * - * @author lulf - * @since 5.1 + * @author Ulf Lilleengen */ public class RemoteSession extends Session { @@ -57,6 +57,7 @@ public class RemoteSession extends Session { return ApplicationSet.fromList(applicationLoader.buildModels(zooKeeperClient.readApplicationId(), zooKeeperClient.readVespaVersion(), zooKeeperClient.loadApplicationPackage(), + new SettableOptional<>(), clock.instant())); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionFactory.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionFactory.java index 0f1e65aaf2b..298acaca901 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionFactory.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionFactory.java @@ -12,8 +12,7 @@ import com.yahoo.vespa.curator.Curator; import java.time.Clock; /** - * @author lulf - * @since 5.1.24 + * @author Ulf Lilleengen */ public class RemoteSessionFactory { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionContext.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionContext.java index a840e5d5a97..323c2667d30 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionContext.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionContext.java @@ -12,8 +12,7 @@ import java.io.File; /** * The dependencies needed for a local session to be edited and prepared. * - * @author lulf - * @since 5.1 + * @author Ulf Lilleengen */ public class SessionContext { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionFactoryImpl.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionFactoryImpl.java index 19651041ea4..c2655d1a1b3 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionFactoryImpl.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionFactoryImpl.java @@ -170,7 +170,8 @@ public class SessionFactoryImpl implements SessionFactory, LocalSessionLoader { defRepo, serverId, nodeFlavors); - SessionContext context = new SessionContext(applicationPackage, sessionZKClient, sessionDir, applicationRepo, hostRegistry, superModelGenerationCounter); + SessionContext context = new SessionContext(applicationPackage, sessionZKClient, sessionDir, applicationRepo, + hostRegistry, superModelGenerationCounter); return new LocalSession(tenant, sessionId, sessionPreparer, context); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java index 0b76c57e142..beb62cf3ac9 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java @@ -10,6 +10,7 @@ import com.yahoo.config.application.api.FileRegistry; import com.yahoo.config.model.api.ConfigDefinitionRepo; import com.yahoo.config.model.api.ModelContext; import com.yahoo.config.provision.*; +import com.yahoo.lang.SettableOptional; import com.yahoo.log.LogLevel; import com.yahoo.path.Path; import com.yahoo.vespa.config.server.application.ApplicationSet; @@ -42,8 +43,7 @@ import javax.xml.transform.TransformerException; /** * A SessionPreparer is responsible for preparing a session given an application package. * - * @author lulf - * @since 5.1 + * @author Ulf Lilleengen */ public class SessionPreparer { @@ -92,8 +92,8 @@ public class SessionPreparer { Preparation preparation = new Preparation(context, logger, params, currentActiveApplicationSet, tenantPath); preparation.preprocess(); try { - preparation.buildModels(now); - preparation.makeResult(); + AllocatedHosts allocatedHosts = preparation.buildModels(now); + preparation.makeResult(allocatedHosts); if ( ! params.isDryRun()) { preparation.writeStateZK(); preparation.writeRotZK(); @@ -177,13 +177,16 @@ public class SessionPreparer { checkTimeout("preprocess"); } - void buildModels(Instant now) { - this.modelResultList = preparedModelsBuilder.buildModels(applicationId, vespaVersion, applicationPackage, now); + AllocatedHosts buildModels(Instant now) { + SettableOptional<AllocatedHosts> allocatedHosts = new SettableOptional<>(); + this.modelResultList = preparedModelsBuilder.buildModels(applicationId, vespaVersion, + applicationPackage, allocatedHosts, now); checkTimeout("build models"); + return allocatedHosts.get(); } - void makeResult() { - this.prepareResult = new PrepareResult(modelResultList); + void makeResult(AllocatedHosts allocatedHosts) { + this.prepareResult = new PrepareResult(allocatedHosts, modelResultList); checkTimeout("making result from models"); } @@ -195,7 +198,7 @@ public class SessionPreparer { vespaVersion, logger, prepareResult.getFileRegistries(), - prepareResult.getProvisionInfos()); + prepareResult.allocatedHosts()); checkTimeout("write state to zookeeper"); } @@ -236,10 +239,10 @@ public class SessionPreparer { com.yahoo.component.Version vespaVersion, DeployLogger deployLogger, Map<Version, FileRegistry> fileRegistryMap, - Map<Version, ProvisionInfo> provisionInfoMap) { + AllocatedHosts allocatedHosts) { ZooKeeperDeployer zkDeployer = zooKeeperClient.createDeployer(deployLogger); try { - zkDeployer.deploy(applicationPackage, fileRegistryMap, provisionInfoMap); + zkDeployer.deploy(applicationPackage, fileRegistryMap, allocatedHosts); zooKeeperClient.writeApplicationId(applicationId); zooKeeperClient.writeVespaVersion(vespaVersion); } catch (RuntimeException | IOException e) { @@ -251,21 +254,19 @@ public class SessionPreparer { /** The result of preparation over all model versions */ private static class PrepareResult { + private final AllocatedHosts allocatedHosts; private final ImmutableList<PreparedModelsBuilder.PreparedModelResult> results; - - public PrepareResult(List<PreparedModelsBuilder.PreparedModelResult> results) { + + public PrepareResult(AllocatedHosts allocatedHosts, List<PreparedModelsBuilder.PreparedModelResult> results) { + this.allocatedHosts = allocatedHosts; this.results = ImmutableList.copyOf(results); } /** Returns the results for each model as an immutable list */ public List<PreparedModelsBuilder.PreparedModelResult> asList() { return results; } - public Map<Version, ProvisionInfo> getProvisionInfos() { - return results.stream() - .filter(result -> result.model.getProvisionInfo().isPresent()) - .collect(Collectors.toMap((prepareResult -> prepareResult.version), - (prepareResult -> prepareResult.model.getProvisionInfo().get()))); - } + /** Returns the host allocations resulting from this preparation. */ + public AllocatedHosts allocatedHosts() { return allocatedHosts; } public Map<Version, FileRegistry> getFileRegistries() { return results.stream() @@ -290,4 +291,31 @@ public class SessionPreparer { } + /** + * During model building each model version will request nodes allocated (from the node allocator) + * for each cluster specified by that model. As allocations are stable this should usually + * result in the same allocations for the same clusters across all model versions, + * otherwise we should fail this preparation as such inconsistencies lead to undefined behavior, + * and there is really just one true allocation (for a given cluster) to be activated in the node repository. + * + * However, these disagreements between allocations in each model version are allowed: + * - A node may be retired in some model version but not another. This allows model versions to change cluster sizes, + * and is ok because the system will converge on the latest version's opinion + * - Clusters may be present on some version but not on another. This does not lead to inconsistency + * and allows new model versions to introduce new clusters. + * + * For each cluster, the newest model version which has that cluster decides the correct retirement status of nodes + * (and all model versions having the cluster must have the same nodes). + * + * This class ensures these constraints and returns a reconciliated set of nodes which should be activated, + * given a set of model activation results. + */ + private static final class ReconciliatedHostAllocations { + + public ReconciliatedHostAllocations(List<PreparedModelsBuilder.PreparedModelResult> results) { + + } + + } + } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java index a677c5cb7f9..09fc83e225d 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java @@ -6,8 +6,7 @@ import com.yahoo.component.Vtag; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.provision.NodeFlavors; -import com.yahoo.config.provision.ProvisionInfo; -import com.yahoo.config.provision.TenantName; +import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.transaction.Transaction; import com.yahoo.log.LogLevel; import com.yahoo.path.Path; @@ -31,8 +30,7 @@ import java.util.concurrent.TimeUnit; * Zookeeper client for a specific session. Can be used to read and write session status * and create and get prepare and active barrier. * - * @author lulf - * @since 5.1 + * @author Ulf Lilleengen */ public class SessionZooKeeperClient { @@ -189,10 +187,9 @@ public class SessionZooKeeperClient { return rootPath.append(CREATE_TIME_PATH).getAbsolute(); } - ProvisionInfo getProvisionInfo() { - return loadApplicationPackage().getProvisionInfoMap().values().stream() - .reduce((infoA, infoB) -> infoA.merge(infoB)) - .orElseThrow(() -> new IllegalStateException("Trying to read provision info, but no provision info exists")); + AllocatedHosts getAllocatedHosts() { + return loadApplicationPackage().getAllocatedHosts() + .orElseThrow(() -> new IllegalStateException("Allocated hosts does not exists")); } public ZooKeeperDeployer createDeployer(DeployLogger logger) { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantBuilder.java index 144bbf10dc6..f4a5e941ac2 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantBuilder.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantBuilder.java @@ -27,8 +27,7 @@ import java.util.concurrent.Executors; /** * Builder for helping out with tenant creation. Each of a tenants dependencies may be overridden for testing. * - * @author lulf - * @since 5.1 + * @author Ulf Lilleengen */ public class TenantBuilder { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java index 28bae587e2a..377b3997e43 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java @@ -48,12 +48,12 @@ import java.util.logging.Logger; * Once a tenant is deleted from zookeeper, the zookeeper watcher thread will get notified on all configservers, and * shutdown and delete any per-configserver state. * - * @author vegardh - * @author lulf - * @since 5.1.26 + * @author Vegard Havdal + * @author Ulf Lilleengen */ //TODO Rename to TenantRepository public class Tenants implements ConnectionStateListener, PathChildrenCacheListener { + public static final TenantName HOSTED_VESPA_TENANT = TenantName.from("hosted-vespa"); private static final TenantName DEFAULT_TENANT = TenantName.defaultName(); private static final List<TenantName> SYSTEM_TENANT_NAMES = Arrays.asList( diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ConfigCurator.java b/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ConfigCurator.java index 80c1c44546f..c09fe5ae4bb 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ConfigCurator.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ConfigCurator.java @@ -146,7 +146,7 @@ public class ConfigCurator { /** * Returns the data at a path, or null if the path does not exist. * - * @param path a String with a pathname. + * @param path a String with a pathname. * @return a byte array with data. */ public byte[] getBytes(String path) { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackage.java b/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackage.java index 4f591278a38..05c301ddba8 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackage.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackage.java @@ -2,6 +2,8 @@ package com.yahoo.vespa.config.server.zookeeper; import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableSet; +import com.yahoo.component.Version; import com.yahoo.config.application.api.ApplicationMetaData; import com.yahoo.config.application.api.ComponentInfo; import com.yahoo.config.application.api.FileRegistry; @@ -10,9 +12,9 @@ import com.yahoo.config.codegen.DefParser; import com.yahoo.config.application.api.ApplicationFile; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.model.application.provider.*; +import com.yahoo.config.provision.HostSpec; import com.yahoo.config.provision.NodeFlavors; -import com.yahoo.config.provision.ProvisionInfo; -import com.yahoo.config.provision.Version; +import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.io.IOUtils; import com.yahoo.path.Path; import com.yahoo.io.reader.NamedReader; @@ -24,20 +26,26 @@ import com.yahoo.vespa.config.util.ConfigUtils; import java.io.File; import java.io.Reader; import java.io.StringReader; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; /** * Represents an application residing in zookeeper. * - * @author tonytv + * @author Tony Vaagenes */ public class ZKApplicationPackage implements ApplicationPackage { private ZKLiveApp liveApp; - private final Map<Version, PreGeneratedFileRegistry> fileRegistryMap = new HashMap<>(); - private final Map<Version, ProvisionInfo> provisionInfoMap = new HashMap<>(); - private static final Version legacyVersion = Version.fromIntValues(0, 0, 0); + private final Map<com.yahoo.config.provision.Version, PreGeneratedFileRegistry> fileRegistryMap = new HashMap<>(); + private final Optional<AllocatedHosts> allocatedHosts; + private static final com.yahoo.config.provision.Version legacyVersion = com.yahoo.config.provision.Version.fromIntValues(0, 0, 0); public static final String fileRegistryNode = "fileregistry"; public static final String allocatedHostsNode = "allocatedHosts"; @@ -48,34 +56,56 @@ public class ZKApplicationPackage implements ApplicationPackage { liveApp = new ZKLiveApp(zk, appPath); metaData = readMetaDataFromLiveApp(liveApp); importFileRegistries(fileRegistryNode); - importProvisionInfos(allocatedHostsNode, nodeFlavors); + allocatedHosts = importAllocatedHosts(allocatedHostsNode, nodeFlavors); } - private void importProvisionInfos(String allocatedHostsNode, Optional<NodeFlavors> nodeFlavors) { - List<String> provisionInfoNodes = liveApp.getChildren(allocatedHostsNode); - if (provisionInfoNodes.isEmpty()) { - Optional<ProvisionInfo> provisionInfo = importProvisionInfo(allocatedHostsNode, nodeFlavors); - provisionInfo.ifPresent(info -> provisionInfoMap.put(legacyVersion, info)); - } else { - provisionInfoNodes.stream() - .forEach(versionStr -> { - Version version = Version.fromString(versionStr); - Optional<ProvisionInfo> provisionInfo = importProvisionInfo(Joiner.on("/").join(allocatedHostsNode, versionStr), - nodeFlavors); - provisionInfo.ifPresent(info -> provisionInfoMap.put(version, info)); - }); + private Optional<AllocatedHosts> importAllocatedHosts(String allocatedHostsPath, Optional<NodeFlavors> nodeFlavors) { + if ( ! liveApp.exists(allocatedHostsPath)) return Optional.empty(); + Optional<AllocatedHosts> allocatedHosts = readAllocatedHosts(allocatedHostsPath, nodeFlavors); + if ( ! allocatedHosts.isPresent()) { // Read from legacy location. TODO: Remove when 6.143 is in production everywhere + List<String> allocatedHostsByVersionNodes = liveApp.getChildren(allocatedHostsPath); + allocatedHosts = merge(readAllocatedHostsByVersion(allocatedHostsByVersionNodes, nodeFlavors)); + } + return allocatedHosts; + } + + private Map<Version, AllocatedHosts> readAllocatedHostsByVersion(List<String> allocatedHostsByVersionNodes, + Optional<NodeFlavors> nodeFlavors) { + Map<Version, AllocatedHosts> allocatedHostsByVersion = new HashMap<>(); + allocatedHostsByVersionNodes.stream() + .forEach(versionStr -> { + Version version = Version.fromString(versionStr); + Optional<AllocatedHosts> allocatedHosts = readAllocatedHosts(Joiner.on("/").join(allocatedHostsNode, versionStr), + nodeFlavors); + allocatedHosts.ifPresent(info -> allocatedHostsByVersion.put(version, info)); + }); + return allocatedHostsByVersion; + } + + private Optional<AllocatedHosts> merge(Map<Version, AllocatedHosts> allocatedHostsByVersion) { + // Merge the allocated hosts in any order. This is wrong but preserves current behavior (modulo order differences) + if (allocatedHostsByVersion.isEmpty()) return Optional.empty(); + + Map<String, HostSpec> merged = new HashMap<>(); + for (Map.Entry<Version, AllocatedHosts> entry : allocatedHostsByVersion.entrySet()) { + for (HostSpec host : entry.getValue().getHosts()) + merged.put(host.hostname(), host); } + return Optional.of(AllocatedHosts.withHosts(ImmutableSet.copyOf(merged.values()))); } - private Optional<ProvisionInfo> importProvisionInfo(String provisionInfoNode, Optional<NodeFlavors> nodeFlavors) { + /** + * Reads allocated hosts at the given node. + * + * @return the allocated hosts at this node or empty if there is no data at this path + */ + private Optional<AllocatedHosts> readAllocatedHosts(String allocatedHostsPath, Optional<NodeFlavors> nodeFlavors) { try { - if (liveApp.exists(provisionInfoNode)) { - return Optional.of(ProvisionInfo.fromJson(liveApp.getBytes(provisionInfoNode), nodeFlavors)); - } else { - return Optional.empty(); - } + byte[] data = liveApp.getBytes(allocatedHostsPath); + if (data.length == 0) return Optional.empty(); // TODO: Remove this line (and make return non-optional) when 6.143 is in production everywhere + return Optional.of(AllocatedHosts.fromJson(data, nodeFlavors)); } catch (Exception e) { - throw new RuntimeException("Unable to read provision info", e); + throw new RuntimeException("Unable to read allocated hosts", e); } } @@ -85,9 +115,9 @@ public class ZKApplicationPackage implements ApplicationPackage { fileRegistryMap.put(legacyVersion, importFileRegistry(fileRegistryNode)); } else { fileRegistryNodes.stream() - .forEach(versionStr -> { - Version version = Version.fromString(versionStr); - fileRegistryMap.put(version, importFileRegistry(Joiner.on("/").join(fileRegistryNode, versionStr))); + .forEach(version -> { + fileRegistryMap.put(com.yahoo.config.provision.Version.fromString(version), + importFileRegistry(Joiner.on("/").join(fileRegistryNode, version))); }); } } @@ -147,16 +177,17 @@ public class ZKApplicationPackage implements ApplicationPackage { return ret; } - public Map<Version, ProvisionInfo> getProvisionInfoMap() { - return Collections.unmodifiableMap(provisionInfoMap); + @Override + public Optional<AllocatedHosts> getAllocatedHosts() { + return allocatedHosts; } @Override - public Map<Version, FileRegistry> getFileRegistryMap() { + public Map<com.yahoo.config.provision.Version, FileRegistry> getFileRegistryMap() { return Collections.unmodifiableMap(fileRegistryMap); } - private Optional<PreGeneratedFileRegistry> getPreGeneratedFileRegistry(Version vespaVersion) { + private Optional<PreGeneratedFileRegistry> getPreGeneratedFileRegistry(com.yahoo.config.provision.Version vespaVersion) { // Assumes at least one file registry, which we always have. Optional<PreGeneratedFileRegistry> fileRegistry = Optional.ofNullable(fileRegistryMap.get(vespaVersion)); if (!fileRegistry.isPresent()) { @@ -243,7 +274,7 @@ public class ZKApplicationPackage implements ApplicationPackage { } @Override - public List<ComponentInfo> getComponentsInfo(Version vespaVersion) { + public List<ComponentInfo> getComponentsInfo(com.yahoo.config.provision.Version vespaVersion) { List<ComponentInfo> components = new ArrayList<>(); PreGeneratedFileRegistry fileRegistry = getPreGeneratedFileRegistry(vespaVersion).get(); for (String path : fileRegistry.getPaths()) { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKLiveApp.java b/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKLiveApp.java index 88748cb7689..8084be1cefa 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKLiveApp.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKLiveApp.java @@ -173,14 +173,11 @@ public class ZKLiveApp { * Returns the full list of children (file names) in the given path. * * @param path a path relative to the currently active application - * @return a list of file names + * @return a list of file names, which is empty (never null) if the path does not exist */ public List<String> getChildren(String path) { String fullPath = getFullPath(path); - if (! zk.exists(fullPath)) { - log.fine("ZKApplicationPackage: " + fullPath + " is not a valid dir"); - return Collections.emptyList(); - } + if (! zk.exists(fullPath)) return Collections.emptyList(); return zk.getChildren(fullPath); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ModelStub.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelStub.java index b892abc67af..2ccce9727a3 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/ModelStub.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelStub.java @@ -4,7 +4,7 @@ package com.yahoo.vespa.config.server; import com.yahoo.config.model.api.FileDistribution; import com.yahoo.config.model.api.HostInfo; import com.yahoo.config.model.api.Model; -import com.yahoo.config.provision.ProvisionInfo; +import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.vespa.config.ConfigKey; import com.yahoo.vespa.config.ConfigPayload; import com.yahoo.vespa.config.buildergen.ConfigDefinition; @@ -44,7 +44,7 @@ public class ModelStub implements Model { } @Override - public Optional<ProvisionInfo> getProvisionInfo() { + public AllocatedHosts allocatedHosts() { return null; } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/MockModel.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/MockModel.java index 5806c7991fc..cf5463b7f4c 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/application/MockModel.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/MockModel.java @@ -7,7 +7,7 @@ import com.yahoo.config.model.api.Model; import com.yahoo.config.model.api.PortInfo; import com.yahoo.config.model.api.ServiceInfo; import com.yahoo.config.provision.ClusterSpec; -import com.yahoo.config.provision.ProvisionInfo; +import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.vespa.config.ConfigKey; import com.yahoo.vespa.config.ConfigPayload; import com.yahoo.vespa.config.buildergen.ConfigDefinition; @@ -93,7 +93,7 @@ class MockModel implements Model { } @Override - public Optional<ProvisionInfo> getProvisionInfo() { + public AllocatedHosts allocatedHosts() { throw new UnsupportedOperationException(); } } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java index c9556425dda..5fea15a7b10 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java @@ -20,7 +20,7 @@ import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.HostFilter; import com.yahoo.config.provision.HostSpec; import com.yahoo.config.provision.InstanceName; -import com.yahoo.config.provision.ProvisionInfo; +import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.ProvisionLogger; import com.yahoo.config.provision.Provisioner; import com.yahoo.config.provision.Version; @@ -69,7 +69,7 @@ public class DeployTester { private ApplicationId id; public DeployTester(String appPath) { - this(appPath, Collections.singletonList(createDefaultModelFactory(Clock.systemUTC()))); + this(appPath, Collections.singletonList(createModelFactory(Clock.systemUTC()))); } public DeployTester(String appPath, List<ModelFactory> modelFactories) { @@ -80,7 +80,7 @@ public class DeployTester { public DeployTester(String appPath, ConfigserverConfig configserverConfig) { this(appPath, - Collections.singletonList(createDefaultModelFactory(Clock.systemUTC())), + Collections.singletonList(createModelFactory(Clock.systemUTC())), configserverConfig); } @@ -99,9 +99,16 @@ public class DeployTester { public Tenant tenant() { return tenants.defaultTenant(); } - /** Create the model factory which will be used in production */ - public static ModelFactory createDefaultModelFactory(Clock clock) { return new VespaModelFactory(new NullConfigModelRegistry(), clock); } - + /** Create a model factory for the version of this source*/ + public static ModelFactory createModelFactory(Clock clock) { + return new VespaModelFactory(new NullConfigModelRegistry(), clock); + } + + /** Create a model factory for a particular version */ + public static ModelFactory createModelFactory(Version version, Clock clock) { + return new VespaModelFactory(version, new NullConfigModelRegistry(), clock); + } + /** Create a model factory which always fails validation */ public static ModelFactory createFailingModelFactory(Version version) { return new FailingModelFactory(version); } @@ -109,20 +116,19 @@ public class DeployTester { * Do the initial "deploy" with the existing API-less code as the deploy API doesn't support first deploys yet. */ public ApplicationId deployApp(String appName, Instant now) { - return deployApp(appName, Optional.empty(), now); + return deployApp(appName, null, now); } /** * Do the initial "deploy" with the existing API-less code as the deploy API doesn't support first deploys yet. */ - public ApplicationId deployApp(String appName, Optional<String> vespaVersion, Instant now) { + public ApplicationId deployApp(String appName, String vespaVersion, Instant now) { Tenant tenant = tenant(); LocalSession session = tenant.getSessionFactory().createSession(testApp, appName, new TimeoutBudget(Clock.systemUTC(), Duration.ofSeconds(60))); ApplicationId id = ApplicationId.from(tenant.getName(), ApplicationName.from(appName), InstanceName.defaultName()); - PrepareParams.Builder paramsBuilder = new PrepareParams.Builder() - .applicationId(id); - if (vespaVersion.isPresent()) - paramsBuilder.vespaVersion(vespaVersion.get()); + PrepareParams.Builder paramsBuilder = new PrepareParams.Builder().applicationId(id); + if (vespaVersion != null) + paramsBuilder.vespaVersion(vespaVersion); session.prepare(new SilentDeployLogger(), paramsBuilder.build(), Optional.empty(), @@ -134,11 +140,11 @@ public class DeployTester { return id; } - public ProvisionInfo getProvisionInfoFromDeployedApp(ApplicationId applicationId) { + public AllocatedHosts getAllocatedHostsOf(ApplicationId applicationId) { Tenant tenant = tenant(); LocalSession session = tenant.getLocalSessionRepo().getSession(tenant.getApplicationRepo() .getSessionIdForApplication(applicationId)); - return session.getProvisionInfo(); + return session.getAllocatedHosts(); } public ApplicationId applicationId() { return id; } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java index 31e92cc9f93..301ae63fb8c 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java @@ -32,7 +32,7 @@ public class HostedDeployTest { @Test public void testRedeployWithVersion() throws InterruptedException, IOException { DeployTester tester = new DeployTester("src/test/apps/hosted/", createConfigserverConfig()); - tester.deployApp("myApp", Optional.of("4.5.6"), Instant.now()); + tester.deployApp("myApp", "4.5.6", Instant.now()); Optional<com.yahoo.config.provision.Deployment> deployment = tester.redeployFromLocalActive(); assertTrue(deployment.isPresent()); @@ -53,11 +53,23 @@ public class HostedDeployTest { } @Test + public void testDeployMultipleVersions() throws InterruptedException, IOException { + ManualClock clock = new ManualClock("2016-10-09T00:00:00"); + List<ModelFactory> modelFactories = new ArrayList<>(); + modelFactories.add(DeployTester.createModelFactory(Version.fromString("6.1.0"), clock)); + modelFactories.add(DeployTester.createModelFactory(Version.fromString("6.2.0"), clock)); + modelFactories.add(DeployTester.createModelFactory(Version.fromString("7.0.0"), clock)); + DeployTester tester = new DeployTester("src/test/apps/hosted/", modelFactories, createConfigserverConfig()); + ApplicationId app = tester.deployApp("myApp", Instant.now()); + assertEquals(3, tester.getAllocatedHostsOf(app).getHosts().size()); + } + + @Test public void testRedeployAfterExpiredValidationOverride() throws InterruptedException, IOException { // Old version of model fails, but application disables loading old models until 2016-10-10, so deployment works ManualClock clock = new ManualClock("2016-10-09T00:00:00"); List<ModelFactory> modelFactories = new ArrayList<>(); - modelFactories.add(DeployTester.createDefaultModelFactory(clock)); + modelFactories.add(DeployTester.createModelFactory(clock)); modelFactories.add(DeployTester.createFailingModelFactory(Version.fromIntValues(1, 0, 0))); // older than default DeployTester tester = new DeployTester("src/test/apps/validationOverride/", modelFactories, createConfigserverConfig()); tester.deployApp("myApp", clock.instant()); @@ -97,19 +109,19 @@ public class HostedDeployTest { public void testDeployWithDockerImage() throws InterruptedException, IOException { final String vespaVersion = "6.51.1"; DeployTester tester = new DeployTester("src/test/apps/hosted/", createConfigserverConfig()); - ApplicationId applicationId = tester.deployApp("myApp", Optional.of(vespaVersion), Instant.now()); - assertProvisionInfo(vespaVersion, tester, applicationId); + ApplicationId applicationId = tester.deployApp("myApp", vespaVersion, Instant.now()); + assertAllocatedHosts(vespaVersion, tester, applicationId); System.out.println("Redeploy"); Optional<com.yahoo.config.provision.Deployment> deployment = tester.redeployFromLocalActive(); assertTrue(deployment.isPresent()); deployment.get().prepare(); deployment.get().activate(); - //assertProvisionInfo(vespaVersion, tester, applicationId); + //assertAllocatedHosts(vespaVersion, tester, applicationId); } - private void assertProvisionInfo(String vespaVersion, DeployTester tester, ApplicationId applicationId) { - tester.getProvisionInfoFromDeployedApp(applicationId).getHosts().stream() + private void assertAllocatedHosts(String vespaVersion, DeployTester tester, ApplicationId applicationId) { + tester.getAllocatedHostsOf(applicationId).getHosts().stream() .forEach(h -> assertEquals(vespaVersion, h.membership().get().cluster().vespaVersion())); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java index 5658e0fb2aa..49e40321321 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java @@ -47,7 +47,7 @@ public class RedeployTest { @Test public void testNoRedeploy() { List<ModelFactory> modelFactories = new ArrayList<>(); - modelFactories.add(DeployTester.createDefaultModelFactory(Clock.systemUTC())); + modelFactories.add(DeployTester.createModelFactory(Clock.systemUTC())); modelFactories.add(DeployTester.createFailingModelFactory(Version.fromIntValues(1, 0, 0))); DeployTester tester = new DeployTester("ignored/app/path", modelFactories); ApplicationId id = ApplicationId.from(TenantName.from("default"), diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java index ac029c12b17..bf7f7038c1a 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java @@ -1,6 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.config.server.deploy; +import com.google.common.collect.ImmutableSet; import com.yahoo.config.application.api.ApplicationMetaData; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.application.api.FileRegistry; @@ -46,8 +47,8 @@ public class ZooKeeperClientTest extends TestWithCurator { Map<Version, FileRegistry> fileRegistries = createFileRegistries(); app.writeMetaData(); zkc.setupZooKeeper(); - zkc.feedZooKeeper(app); - zkc.feedZKFileRegistries(fileRegistries); + zkc.write(app); + zkc.write(fileRegistries); } private Map<Version, FileRegistry> createFileRegistries() { @@ -174,23 +175,15 @@ public class ZooKeeperClientTest extends TestWithCurator { Path app = Path.fromString("/1"); ZooKeeperClient zooKeeperClient = new ZooKeeperClient(zk, logger, true, app); zooKeeperClient.setupZooKeeper(); - zooKeeperClient.feedProvisionInfos(createProvisionInfos()); + HostSpec host1 = new HostSpec("host1.yahoo.com", Collections.emptyList()); + HostSpec host2 = new HostSpec("host2.yahoo.com", Collections.emptyList()); + ImmutableSet<HostSpec> hosts = ImmutableSet.of(host1, host2); + zooKeeperClient.write(AllocatedHosts.withHosts(hosts)); Path hostsPath = app.append(ZKApplicationPackage.allocatedHostsNode); assertTrue(zk.exists(hostsPath.getAbsolute())); - assertEquals(0, zk.getBytes(hostsPath.getAbsolute()).length); // Changed from null - assertTrue(zk.exists(hostsPath.append("1.2.3").getAbsolute())); - assertTrue(zk.exists(hostsPath.append("3.2.1").getAbsolute())); - assertTrue(zk.getBytes(hostsPath.append("1.2.3").getAbsolute()).length > 0); - assertTrue(zk.getBytes(hostsPath.append("3.2.1").getAbsolute()).length > 0); - } - - private Map<Version, ProvisionInfo> createProvisionInfos() { - Map<Version, ProvisionInfo> provisionInfoMap = new HashMap<>(); - ProvisionInfo a = ProvisionInfo.withHosts(Collections.singleton(new HostSpec("host.yahoo.com", Collections.emptyList()))); - ProvisionInfo b = ProvisionInfo.withHosts(Collections.singleton(new HostSpec("host2.yahoo.com", Collections.emptyList()))); - provisionInfoMap.put(Version.fromIntValues(1, 2, 3), a); - provisionInfoMap.put(Version.fromIntValues(3, 2, 1), b); - return provisionInfoMap; + + AllocatedHosts deserialized = AllocatedHosts.fromJson(zk.getBytes(hostsPath.getAbsolute()), Optional.empty()); + assertEquals(hosts, deserialized.getHosts()); } } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployerTest.java index 9c7f12c2147..b256079d259 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployerTest.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.config.server.deploy; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.model.application.provider.*; +import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.Version; import com.yahoo.io.IOUtils; import com.yahoo.path.Path; @@ -52,7 +53,7 @@ public class ZooKeeperDeployerTest { ZooKeeperClient client = new ZooKeeperClient(configCurator, logger, true, appPath); ZooKeeperDeployer deployer = new ZooKeeperDeployer(client); - deployer.deploy(applicationPackage, Collections.singletonMap(Version.fromIntValues(1, 0, 0), new MockFileRegistry()), Collections.emptyMap()); + deployer.deploy(applicationPackage, Collections.singletonMap(Version.fromIntValues(1, 0, 0), new MockFileRegistry()), AllocatedHosts.withHosts(Collections.emptySet())); assertTrue(configCurator.exists(appPath.getAbsolute())); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java index 5e10f364aeb..7fca78b087b 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java @@ -13,7 +13,7 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ApplicationName; import com.yahoo.config.provision.HostSpec; import com.yahoo.config.provision.InstanceName; -import com.yahoo.config.provision.ProvisionInfo; +import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.Zone; import com.yahoo.container.jdisc.HttpResponse; @@ -217,8 +217,8 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { zkClient.writeStatus(status); ZooKeeperClient zkC = new ZooKeeperClient(configCurator, new BaseDeployLogger(), false, pathProvider.getSessionDirs().append(String.valueOf(sessionId))); VespaModelFactory modelFactory = new VespaModelFactory(new NullConfigModelRegistry()); - zkC.feedZKFileRegistries(Collections.singletonMap(modelFactory.getVersion(), new MockFileRegistry())); - zkC.feedProvisionInfos(Collections.singletonMap(modelFactory.getVersion(), ProvisionInfo.withHosts(Collections.emptySet()))); + zkC.write(Collections.singletonMap(modelFactory.getVersion(), new MockFileRegistry())); + zkC.write(AllocatedHosts.withHosts(Collections.emptySet())); TestComponentRegistry componentRegistry = new TestComponentRegistry.Builder() .curator(curator) .configCurator(configCurator) @@ -318,7 +318,7 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { ActivateRequest invoke(boolean createLocalSession) throws Exception { SessionZooKeeperClient zkClient = new MockSessionZKClient(curator, pathProvider.getSessionDirs().append(String.valueOf(sessionId)), - Optional.of(ProvisionInfo.withHosts(Collections.singleton(new HostSpec("bar", Collections.emptyList()))))); + Optional.of(AllocatedHosts.withHosts(Collections.singleton(new HostSpec("bar", Collections.emptyList()))))); session = createRemoteSession(sessionId, initialStatus, zkClient, clock); if (createLocalSession) { LocalSessionRepo repo = addLocalSession(sessionId, deployData, zkClient); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/provision/StaticProvisionerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/provision/StaticProvisionerTest.java index c54514eb097..badcdf53b77 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/provision/StaticProvisionerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/provision/StaticProvisionerTest.java @@ -20,7 +20,7 @@ import java.io.IOException; import static org.junit.Assert.assertEquals; /** - * @author lulf + * @author Ulf Lilleengen */ public class StaticProvisionerTest { @@ -30,7 +30,7 @@ public class StaticProvisionerTest { InMemoryProvisioner inMemoryHostProvisioner = new InMemoryProvisioner(false, "host1.yahoo.com", "host2.yahoo.com", "host3.yahoo.com", "host4.yahoo.com"); VespaModel firstModel = createModel(app, inMemoryHostProvisioner); - StaticProvisioner staticProvisioner = new StaticProvisioner(firstModel.getProvisionInfo().get()); + StaticProvisioner staticProvisioner = new StaticProvisioner(firstModel.allocatedHosts()); VespaModel secondModel = createModel(app, staticProvisioner); assertModelConfig(firstModel, secondModel); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java index f0086eabd26..be98f41c82a 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java @@ -120,19 +120,19 @@ public class LocalSessionTest { @Test(expected = IllegalStateException.class) public void require_that_no_provision_info_throws_exception() throws Exception { - createSession(TenantName.defaultName(), 3).getProvisionInfo(); + createSession(TenantName.defaultName(), 3).getAllocatedHosts(); } @Test public void require_that_provision_info_can_be_read() throws Exception { - ProvisionInfo input = ProvisionInfo.withHosts(Collections.singleton(new HostSpec("myhost", Collections.<String>emptyList()))); + AllocatedHosts input = AllocatedHosts.withHosts(Collections.singleton(new HostSpec("myhost", Collections.<String>emptyList()))); LocalSession session = createSession(TenantName.defaultName(), 3, new SessionTest.MockSessionPreparer(), Optional.of(input)); ApplicationId origId = new ApplicationId.Builder() .tenant("tenant") .applicationName("foo").instanceName("quux").build(); doPrepare(session, new PrepareParams.Builder().applicationId(origId).build(), Instant.now()); - ProvisionInfo info = session.getProvisionInfo(); + AllocatedHosts info = session.getAllocatedHosts(); assertNotNull(info); assertThat(info.getHosts().size(), is(1)); assertTrue(info.getHosts().contains(new HostSpec("myhost", Collections.emptyList()))); @@ -151,18 +151,18 @@ public class LocalSessionTest { } private LocalSession createSession(TenantName tenant, long sessionId, SessionTest.MockSessionPreparer preparer) throws Exception { - return createSession(tenant, sessionId, preparer, Optional.<ProvisionInfo>empty()); + return createSession(tenant, sessionId, preparer, Optional.<AllocatedHosts>empty()); } - private LocalSession createSession(TenantName tenant, long sessionId, SessionTest.MockSessionPreparer preparer, Optional<ProvisionInfo> provisionInfo) throws Exception { + private LocalSession createSession(TenantName tenant, long sessionId, SessionTest.MockSessionPreparer preparer, Optional<AllocatedHosts> allocatedHosts) throws Exception { Path appPath = Path.fromString("/" + sessionId); - SessionZooKeeperClient zkc = new MockSessionZKClient(curator, appPath, provisionInfo); + SessionZooKeeperClient zkc = new MockSessionZKClient(curator, appPath, allocatedHosts); zkc.createWriteStatusTransaction(Session.Status.NEW).commit(); ZooKeeperClient zkClient = new ZooKeeperClient(configCurator, new BaseDeployLogger(), false, appPath); - if (provisionInfo.isPresent()) { - zkClient.feedProvisionInfos(Collections.singletonMap(Version.fromIntValues(0, 0, 0), provisionInfo.get())); + if (allocatedHosts.isPresent()) { + zkClient.write(allocatedHosts.get()); } - zkClient.feedZKFileRegistries(Collections.singletonMap(Version.fromIntValues(0, 0, 0), new MockFileRegistry())); + zkClient.write(Collections.singletonMap(Version.fromIntValues(0, 0, 0), new MockFileRegistry())); File sessionDir = new File(tenantFileSystemDirs.path(), String.valueOf(sessionId)); sessionDir.createNewFile(); return new LocalSession(tenant, sessionId, preparer, new SessionContext(FilesApplicationPackage.fromFile(testApp), zkc, sessionDir, new MemoryTenantApplications(), new HostRegistry<>(), superModelGenerationCounter)); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockSessionZKClient.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockSessionZKClient.java index 9b658a807b9..412e7881a26 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockSessionZKClient.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockSessionZKClient.java @@ -3,7 +3,7 @@ package com.yahoo.vespa.config.server.session; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.model.test.MockApplicationPackage; -import com.yahoo.config.provision.ProvisionInfo; +import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.transaction.Transaction; import com.yahoo.path.Path; import com.yahoo.vespa.curator.Curator; @@ -14,22 +14,21 @@ import java.util.Optional; /** * Overrides application package fetching, because this part is hard to do without feeding a full app. * - * @author lulf - * @since 5.1 + * @author Ulf Lilleengen */ public class MockSessionZKClient extends SessionZooKeeperClient { private ApplicationPackage app = null; - private Optional<ProvisionInfo> info = null; + private Optional<AllocatedHosts> info = null; private Session.Status sessionStatus; public MockSessionZKClient(Curator curator, Path rootPath) { this(curator, rootPath, (ApplicationPackage)null); } - public MockSessionZKClient(Curator curator, Path rootPath, Optional<ProvisionInfo> provisionInfo) { + public MockSessionZKClient(Curator curator, Path rootPath, Optional<AllocatedHosts> allocatedHosts) { this(curator, rootPath); - this.info = provisionInfo; + this.info = allocatedHosts; } public MockSessionZKClient(Curator curator, Path rootPath, ApplicationPackage application) { @@ -49,8 +48,8 @@ public class MockSessionZKClient extends SessionZooKeeperClient { } @Override - ProvisionInfo getProvisionInfo() { - return info.orElseThrow(() -> new IllegalStateException("Trying to read provision info, but no provision info exists")); + AllocatedHosts getAllocatedHosts() { + return info.orElseThrow(() -> new IllegalStateException("Could not find allocated hosts")); } @Override diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRequestHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRequestHandlerTest.java index 65f546e149a..3b67597c43c 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRequestHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRequestHandlerTest.java @@ -8,6 +8,7 @@ import com.yahoo.config.model.application.provider.BaseDeployLogger; import com.yahoo.config.model.application.provider.FilesApplicationPackage; import com.yahoo.config.model.application.provider.MockFileRegistry; import com.yahoo.config.provision.ApplicationName; +import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.Version; import com.yahoo.io.IOUtils; import com.yahoo.path.Path; @@ -94,7 +95,7 @@ public class TenantRequestHandlerTest extends TestWithCurator { File app = tempFolder.newFolder(); IOUtils.copyDirectory(appDir, app); ZooKeeperDeployer deployer = zkc.createDeployer(new BaseDeployLogger()); - deployer.deploy(FilesApplicationPackage.fromFile(appDir), Collections.singletonMap(vespaVersion, new MockFileRegistry()), Collections.emptyMap()); + deployer.deploy(FilesApplicationPackage.fromFile(appDir), Collections.singletonMap(vespaVersion, new MockFileRegistry()), AllocatedHosts.withHosts(Collections.emptySet())); } private ApplicationSet reloadConfig(long id, Clock clock) { diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackageTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackageTest.java index abc77a91c51..adf26dbfa32 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackageTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackageTest.java @@ -16,7 +16,7 @@ import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.provision.Flavor; import com.yahoo.config.provision.HostSpec; import com.yahoo.config.provision.NodeFlavors; -import com.yahoo.config.provision.ProvisionInfo; +import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.Version; import com.yahoo.config.provisioning.FlavorsConfig; import com.yahoo.path.Path; @@ -33,7 +33,7 @@ public class ZKApplicationPackageTest extends TestWithCurator { private static final String APP = "src/test/apps/zkapp"; private static final String TEST_FLAVOR_NAME = "test-flavor"; private static final Optional<Flavor> TEST_FLAVOR = new MockNodeFlavors().getFlavor(TEST_FLAVOR_NAME); - private static final ProvisionInfo provisionInfo = ProvisionInfo.withHosts( + private static final AllocatedHosts ALLOCATED_HOSTS = AllocatedHosts.withHosts( Collections.singleton(new HostSpec("foo.yahoo.com", Collections.emptyList(), TEST_FLAVOR, Optional.empty()))); @Rule @@ -64,9 +64,8 @@ public class ZKApplicationPackageTest extends TestWithCurator { assertTrue(zkApp.getFileRegistryMap().containsKey(goodVersion)); assertFalse(zkApp.getFileRegistryMap().containsKey(Version.fromIntValues(0, 0, 0))); assertThat(zkApp.getFileRegistryMap().get(goodVersion).fileSourceHost(), is("dummyfiles")); - assertTrue(zkApp.getProvisionInfoMap().containsKey(goodVersion)); - ProvisionInfo readInfo = zkApp.getProvisionInfoMap().get(goodVersion); - assertThat(Utf8.toString(readInfo.toJson()), is(Utf8.toString(provisionInfo.toJson()))); + AllocatedHosts readInfo = zkApp.getAllocatedHosts().get(); + assertThat(Utf8.toString(readInfo.toJson()), is(Utf8.toString(ALLOCATED_HOSTS.toJson()))); assertThat(readInfo.getHosts().iterator().next().flavor(), is(TEST_FLAVOR)); assertTrue(zkApp.getDeployment().isPresent()); assertThat(DeploymentSpec.fromXml(zkApp.getDeployment().get()).globalServiceId().get(), is("mydisc")); @@ -78,7 +77,7 @@ public class ZKApplicationPackageTest extends TestWithCurator { String metaData = "{\"deploy\":{\"user\":\"foo\",\"from\":\"bar\",\"timestamp\":1},\"application\":{\"name\":\"foo\",\"checksum\":\"abc\",\"generation\":4,\"previousActiveGeneration\":3}}"; zk.putData("/0", ConfigCurator.META_ZK_PATH, metaData); zk.putData("/0/" + ZKApplicationPackage.fileRegistryNode + "/3.0.0", "dummyfiles"); - zk.putData("/0/" + ZKApplicationPackage.allocatedHostsNode + "/3.0.0", provisionInfo.toJson()); + zk.putData("/0/" + ZKApplicationPackage.allocatedHostsNode + "/3.0.0", ALLOCATED_HOSTS.toJson()); } private static class MockNodeFlavors extends NodeFlavors{ diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java index 914658302b6..e2ff17ba782 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java @@ -24,7 +24,6 @@ public class CapacityPolicies { this.flavors = flavors; } - /** provides capacity defaults for various environments */ public int decideSize(Capacity requestedCapacity) { int requestedNodes = requestedCapacity.nodeCount(); if (requestedCapacity.isRequired()) return requestedNodes; @@ -39,10 +38,10 @@ public class CapacityPolicies { } public Flavor decideFlavor(Capacity requestedCapacity, ClusterSpec cluster, Optional<String> defaultFlavorOverride) { - // for now, always use requested docker flavor when requested + // for now, always use the requested flavor if a docker flavor is requested Optional<String> requestedFlavor = requestedCapacity.flavor(); if (requestedFlavor.isPresent() && - flavors.getFlavorOrThrow(requestedFlavor.get()).getType() == Flavor.Type.DOCKER_CONTAINER) + flavors.getFlavorOrThrow(requestedFlavor.get()).getType() == Flavor.Type.DOCKER_CONTAINER) return flavors.getFlavorOrThrow(requestedFlavor.get()); String defaultFlavorName = defaultFlavorOverride.isPresent() ? diff --git a/vespajlib/src/main/java/com/yahoo/lang/SettableOptional.java b/vespajlib/src/main/java/com/yahoo/lang/SettableOptional.java new file mode 100644 index 00000000000..00ff06b8f01 --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/lang/SettableOptional.java @@ -0,0 +1,41 @@ +package com.yahoo.lang; + +import java.util.NoSuchElementException; +import java.util.Optional; + +/** + * An optional which contains a settable value + * + * @author bratseth + */ +public final class SettableOptional<T> { + + private T value = null; + + /** Creates a new empty settable optional */ + public SettableOptional() {} + + /** Creates a new settable optional with the given value */ + public SettableOptional(T value) { this.value = value; } + + public boolean isPresent() { + return value != null; + } + + public T get() { + if (value == null) + throw new NoSuchElementException("No value present"); + return value; + } + + public void set(T value) { + this.value = value; + } + + public Optional<T> asOptional() { + if (value == null) return Optional.empty(); + return Optional.of(value); + } + +} + |