diff options
author | Harald Musum <musum@verizonmedia.com> | 2020-09-09 11:13:30 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-09-09 11:13:30 +0200 |
commit | ce2bff301d48063bb64a2a569dd434414c5c1be7 (patch) | |
tree | ec04e10d9e35dca01052fa477b8c8b87ded38123 /configserver | |
parent | 48a4b9c05e03da3ec9a0e9f4cc94c4b58016aefe (diff) |
Revert "Revert " Move code from RemoteSession to SessionRepository, take 2""
Diffstat (limited to 'configserver')
15 files changed, 404 insertions, 415 deletions
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java index eb13baf3e6b..a4a705c747e 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java @@ -610,9 +610,10 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye try { Tenant tenant = getTenant(applicationId); if (tenant == null) throw new NotFoundException("Tenant '" + applicationId.tenant() + "' not found"); - long sessionId = getSessionIdForApplication(tenant, applicationId); - RemoteSession session = getRemoteSession(tenant, sessionId); - return session.ensureApplicationLoaded().getForVersionOrLatest(version, clock.instant()); + RemoteSession session = getActiveSession(applicationId); + if (session == null) throw new NotFoundException("No active session found for '" + applicationId + "'"); + SessionRepository sessionRepository = tenant.getSessionRepository(); + return sessionRepository.ensureApplicationLoaded(session).getForVersionOrLatest(version, clock.instant()); } catch (NotFoundException e) { log.log(Level.WARNING, "Failed getting application for '" + applicationId + "': " + e.getMessage()); throw e; @@ -951,7 +952,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye try { long currentActiveSessionId = applicationRepo.requireActiveSessionOf(appId); RemoteSession currentActiveSession = getRemoteSession(tenant, currentActiveSessionId); - currentActiveApplicationSet = Optional.ofNullable(currentActiveSession.ensureApplicationLoaded()); + currentActiveApplicationSet = Optional.ofNullable(tenant.getSessionRepository().ensureApplicationLoaded(currentActiveSession)); } catch (IllegalArgumentException e) { // Do nothing if we have no currently active session } 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 7fc6b35722f..6d2ef4028c6 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 @@ -4,7 +4,6 @@ package com.yahoo.vespa.config.server.modelfactory; import com.google.common.collect.ImmutableSet; import com.yahoo.component.Version; 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.ModelContext; import com.yahoo.config.model.api.ModelFactory; @@ -57,7 +56,6 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> { private final ConfigDefinitionRepo configDefinitionRepo; private final Metrics metrics; private final Curator curator; - private final DeployLogger logger; private final FlagSource flagSource; private final SecretStore secretStore; @@ -76,7 +74,6 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> { this.configDefinitionRepo = globalComponentRegistry.getStaticConfigDefinitionRepo(); this.metrics = globalComponentRegistry.getMetrics(); this.curator = globalComponentRegistry.getCurator(); - this.logger = new SilentDeployLogger(); this.flagSource = globalComponentRegistry.getFlagSource(); this.secretStore = globalComponentRegistry.getSecretStore(); } @@ -90,14 +87,14 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> { Optional<AllocatedHosts> ignored // Ignored since we have this in the app package for activated models ) { log.log(Level.FINE, String.format("Loading model version %s for session %s application %s", - modelFactory.version(), appGeneration, applicationId)); + modelFactory.version(), appGeneration, applicationId)); ModelContext.Properties modelContextProperties = createModelContextProperties(applicationId); Provisioned provisioned = new Provisioned(); ModelContext modelContext = new ModelContextImpl( applicationPackage, Optional.empty(), permanentApplicationPackage.applicationPackage(), - logger, + new SilentDeployLogger(), configDefinitionRepo, getForVersionOrLatest(applicationPackage.getFileRegistries(), modelFactory.version()).orElse(new MockFileRegistry()), createStaticProvisioner(applicationPackage.getAllocatedHosts(), 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 36cac87a326..f551fcb6005 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 @@ -1,22 +1,12 @@ // 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.session; -import com.yahoo.config.application.api.ApplicationPackage; -import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.TenantName; -import com.yahoo.lang.SettableOptional; import com.yahoo.transaction.Transaction; -import com.yahoo.vespa.config.server.GlobalComponentRegistry; import com.yahoo.vespa.config.server.application.ApplicationSet; -import com.yahoo.vespa.config.server.modelfactory.ActivatedModelsBuilder; -import com.yahoo.vespa.curator.Curator; -import org.apache.zookeeper.KeeperException; -import java.time.Clock; +import java.util.Objects; import java.util.Optional; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; /** * A RemoteSession represents a session created on another config server. This session can @@ -26,95 +16,49 @@ import java.util.logging.Logger; */ public class RemoteSession extends Session { - private static final Logger log = Logger.getLogger(RemoteSession.class.getName()); - private ApplicationSet applicationSet = null; - private final ActivatedModelsBuilder applicationLoader; - private final Clock clock; + private final Optional<ApplicationSet> applicationSet; /** - * Creates a session. This involves loading the application, validating it and distributing it. + * Creates a remote session, no application set loaded * * @param tenant The name of the tenant creating session * @param sessionId The session id for this session. - * @param componentRegistry a registry of global components * @param zooKeeperClient a SessionZooKeeperClient instance */ - public RemoteSession(TenantName tenant, - long sessionId, - GlobalComponentRegistry componentRegistry, - SessionZooKeeperClient zooKeeperClient) { - super(tenant, sessionId, zooKeeperClient); - this.applicationLoader = new ActivatedModelsBuilder(tenant, sessionId, zooKeeperClient, componentRegistry); - this.clock = componentRegistry.getClock(); + public RemoteSession(TenantName tenant, long sessionId, SessionZooKeeperClient zooKeeperClient) { + this(tenant, sessionId, zooKeeperClient, Optional.empty()); } - void prepare() { - Curator.CompletionWaiter waiter = sessionZooKeeperClient.getPrepareWaiter(); - ensureApplicationLoaded(); - notifyCompletion(waiter); + /** + * Creates a remote session, with application set + * + * @param tenant The name of the tenant creating session + * @param sessionId The session id for this session. + * @param zooKeeperClient a SessionZooKeeperClient instance + */ + private RemoteSession(TenantName tenant, + long sessionId, + SessionZooKeeperClient zooKeeperClient, + Optional<ApplicationSet> applicationSet) { + super(tenant, sessionId, zooKeeperClient); + this.applicationSet = applicationSet; } - private ApplicationSet loadApplication() { - ApplicationPackage applicationPackage = sessionZooKeeperClient.loadApplicationPackage(); - - // Read hosts allocated on the config server instance which created this - Optional<AllocatedHosts> allocatedHosts = applicationPackage.getAllocatedHosts(); - - return ApplicationSet.fromList(applicationLoader.buildModels(getApplicationId(), - sessionZooKeeperClient.readDockerImageRepository(), - sessionZooKeeperClient.readVespaVersion(), - applicationPackage, - new SettableOptional<>(allocatedHosts), - clock.instant())); + Optional<ApplicationSet> applicationSet() { + return applicationSet; } - public synchronized ApplicationSet ensureApplicationLoaded() { - return applicationSet == null ? applicationSet = loadApplication() : applicationSet; + public synchronized RemoteSession activated(ApplicationSet applicationSet) { + Objects.requireNonNull(applicationSet, "applicationSet cannot be null"); + return new RemoteSession(tenant, sessionId, sessionZooKeeperClient, Optional.of(applicationSet)); } - public synchronized void deactivate() { - applicationSet = null; + public synchronized RemoteSession deactivated() { + return new RemoteSession(tenant, sessionId, sessionZooKeeperClient, Optional.empty()); } public Transaction createDeleteTransaction() { return sessionZooKeeperClient.createWriteStatusTransaction(Status.DELETE); } - - void confirmUpload() { - Curator.CompletionWaiter waiter = sessionZooKeeperClient.getUploadWaiter(); - log.log(Level.FINE, "Notifying upload waiter for session " + getSessionId()); - notifyCompletion(waiter); - log.log(Level.FINE, "Done notifying upload for session " + getSessionId()); - } - - void notifyCompletion(Curator.CompletionWaiter completionWaiter) { - try { - completionWaiter.notifyCompletion(); - } catch (RuntimeException e) { - // Throw only if we get something else than NoNodeException or NodeExistsException. - // NoNodeException might happen when the session is no longer in use (e.g. the app using this session - // has been deleted) and this method has not been called yet for the previous session operation on a - // minority of the config servers. - // NodeExistsException might happen if an event for this node is delivered more than once, in that case - // this is a no-op - Set<Class<? extends KeeperException>> acceptedExceptions = Set.of(KeeperException.NoNodeException.class, - KeeperException.NodeExistsException.class); - Class<? extends Throwable> exceptionClass = e.getCause().getClass(); - if (acceptedExceptions.contains(exceptionClass)) - log.log(Level.FINE, "Not able to notify completion for session " + getSessionId() + - " (" + completionWaiter + ")," + - " node " + (exceptionClass.equals(KeeperException.NoNodeException.class) - ? "has been deleted" - : "already exists")); - else - throw e; - } - } - - public void delete() { - Transaction transaction = sessionZooKeeperClient.deleteTransaction(); - transaction.commit(); - transaction.close(); - } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java index b3e35e955de..85f9b575942 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java @@ -28,7 +28,7 @@ import java.util.Optional; */ public abstract class Session implements Comparable<Session> { - private final long sessionId; + protected final long sessionId; protected final TenantName tenant; protected final SessionZooKeeperClient sessionZooKeeperClient; protected final Optional<ApplicationPackage> applicationPackage; diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionCache.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionCache.java index b7d78f11201..9cf0b1e428d 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionCache.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionCache.java @@ -15,7 +15,7 @@ public class SessionCache<SESSIONTYPE extends Session> { private final HashMap<Long, SESSIONTYPE> sessions = new HashMap<>(); - public synchronized void addSession(SESSIONTYPE session) { + public synchronized void putSession(SESSIONTYPE session) { sessions.putIfAbsent(session.getSessionId(), session); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java index cbfa59b26e4..880bd9086d2 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java @@ -8,9 +8,11 @@ import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.model.application.provider.DeployData; import com.yahoo.config.model.application.provider.FilesApplicationPackage; +import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.TenantName; import com.yahoo.io.IOUtils; +import com.yahoo.lang.SettableOptional; import com.yahoo.path.Path; import com.yahoo.transaction.AbstractTransaction; import com.yahoo.transaction.NestedTransaction; @@ -22,6 +24,7 @@ import com.yahoo.vespa.config.server.application.TenantApplications; import com.yahoo.vespa.config.server.configchange.ConfigChangeActions; import com.yahoo.vespa.config.server.deploy.TenantFileSystemDirs; import com.yahoo.vespa.config.server.filedistribution.FileDirectory; +import com.yahoo.vespa.config.server.modelfactory.ActivatedModelsBuilder; import com.yahoo.vespa.config.server.monitoring.MetricUpdater; import com.yahoo.vespa.config.server.monitoring.Metrics; import com.yahoo.vespa.config.server.tenant.TenantRepository; @@ -36,6 +39,7 @@ import com.yahoo.vespa.flags.Flags; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.recipes.cache.ChildData; import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; +import org.apache.zookeeper.KeeperException; import java.io.File; import java.io.FilenameFilter; @@ -51,6 +55,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.Executor; import java.util.logging.Level; import java.util.logging.Logger; @@ -121,7 +126,7 @@ public class SessionRepository { // ---------------- Local sessions ---------------------------------------------------------------- public synchronized void addLocalSession(LocalSession session) { - localSessionCache.addSession(session); + localSessionCache.putSession(session); long sessionId = session.getSessionId(); RemoteSession remoteSession = createRemoteSession(sessionId); addSessionStateWatcher(sessionId, remoteSession, Optional.of(session)); @@ -263,7 +268,7 @@ public class SessionRepository { } public void addRemoteSession(RemoteSession session) { - remoteSessionCache.addSession(session); + remoteSessionCache.putSession(session); metrics.incAddedSessions(); } @@ -275,13 +280,24 @@ public class SessionRepository { if (session.getStatus() == Session.Status.ACTIVATE) continue; if (sessionHasExpired(session.getCreateTime(), expiryTime, clock)) { log.log(Level.FINE, () -> "Remote session " + sessionId + " for " + tenantName + " has expired, deleting it"); - session.delete(); + deleteSession(session); deleted++; } } return deleted; } + public void deactivate(RemoteSession remoteSession) { + remoteSessionCache.putSession(remoteSession.deactivated()); + } + + public void deleteSession(RemoteSession session) { + SessionZooKeeperClient sessionZooKeeperClient = createSessionZooKeeperClient(session.getSessionId()); + Transaction transaction = sessionZooKeeperClient.deleteTransaction(); + transaction.commit(); + transaction.close(); + } + public int deleteExpiredLocks(Clock clock, Duration expiryTime) { int deleted = 0; for (var lock : curator.getChildren(locksPath)) { @@ -358,25 +374,17 @@ public class SessionRepository { long sessionId = session.getSessionId(); Curator.CompletionWaiter waiter = createSessionZooKeeperClient(sessionId).getActiveWaiter(); log.log(Level.FINE, () -> session.logPre() + "Getting session from repo: " + sessionId); - ApplicationSet app = session.ensureApplicationLoaded(); + ApplicationSet app = ensureApplicationLoaded(session); log.log(Level.FINE, () -> session.logPre() + "Reloading config for " + sessionId); applicationRepo.reloadConfig(app); log.log(Level.FINE, () -> session.logPre() + "Notifying " + waiter); - session.notifyCompletion(waiter); + notifyCompletion(waiter, session); log.log(Level.INFO, session.logPre() + "Session activated: " + sessionId); } - public void deactivate(RemoteSession remoteSession) { - remoteSession.deactivate(); - } - - public void delete(RemoteSession remoteSession, Optional<LocalSession> localSession) { + void deleteSession(RemoteSession remoteSession, Optional<LocalSession> localSession) { localSession.ifPresent(this::deleteLocalSession); - remoteSession.deactivate(); - } - - void prepare(RemoteSession session) { - session.prepare(); + deactivate(remoteSession); } boolean distributeApplicationPackage() { @@ -394,13 +402,81 @@ public class SessionRepository { for (ApplicationId applicationId : applicationRepo.activeApplications()) { if (applicationRepo.requireActiveSessionOf(applicationId) == session.getSessionId()) { log.log(Level.FINE, () -> "Found active application for session " + session.getSessionId() + " , loading it"); - applicationRepo.reloadConfig(session.ensureApplicationLoaded()); + applicationRepo.reloadConfig(ensureApplicationLoaded(session)); log.log(Level.INFO, session.logPre() + "Application activated successfully: " + applicationId + " (generation " + session.getSessionId() + ")"); return; } } } + void prepareRemoteSession(RemoteSession session) { + SessionZooKeeperClient sessionZooKeeperClient = createSessionZooKeeperClient(session.getSessionId()); + Curator.CompletionWaiter waiter = sessionZooKeeperClient.getPrepareWaiter(); + ensureApplicationLoaded(session); + notifyCompletion(waiter, session); + } + + public ApplicationSet ensureApplicationLoaded(RemoteSession session) { + try (var lock = lock(session.sessionId)) { + if (session.applicationSet().isPresent()) { + return session.applicationSet().get(); + } + + ApplicationSet applicationSet = loadApplication(session); + remoteSessionCache.putSession(session.activated(applicationSet)); + return applicationSet; + } + } + + void confirmUpload(RemoteSession session) { + Curator.CompletionWaiter waiter = session.getSessionZooKeeperClient().getUploadWaiter(); + long sessionId = session.getSessionId(); + log.log(Level.FINE, "Notifying upload waiter for session " + sessionId); + notifyCompletion(waiter, session); + log.log(Level.FINE, "Done notifying upload for session " + sessionId); + } + + void notifyCompletion(Curator.CompletionWaiter completionWaiter, RemoteSession session) { + try { + completionWaiter.notifyCompletion(); + } catch (RuntimeException e) { + // Throw only if we get something else than NoNodeException or NodeExistsException. + // NoNodeException might happen when the session is no longer in use (e.g. the app using this session + // has been deleted) and this method has not been called yet for the previous session operation on a + // minority of the config servers. + // NodeExistsException might happen if an event for this node is delivered more than once, in that case + // this is a no-op + Set<Class<? extends KeeperException>> acceptedExceptions = Set.of(KeeperException.NoNodeException.class, + KeeperException.NodeExistsException.class); + Class<? extends Throwable> exceptionClass = e.getCause().getClass(); + if (acceptedExceptions.contains(exceptionClass)) + log.log(Level.FINE, "Not able to notify completion for session " + session.getSessionId() + + " (" + completionWaiter + ")," + + " node " + (exceptionClass.equals(KeeperException.NoNodeException.class) + ? "has been deleted" + : "already exists")); + else + throw e; + } + } + + private ApplicationSet loadApplication(RemoteSession session) { + SessionZooKeeperClient sessionZooKeeperClient = createSessionZooKeeperClient(session.getSessionId()); + ApplicationPackage applicationPackage = sessionZooKeeperClient.loadApplicationPackage(); + ActivatedModelsBuilder builder = new ActivatedModelsBuilder(session.getTenantName(), + session.getSessionId(), + sessionZooKeeperClient, + componentRegistry); + // Read hosts allocated on the config server instance which created this + Optional<AllocatedHosts> allocatedHosts = applicationPackage.getAllocatedHosts(); + return ApplicationSet.fromList(builder.buildModels(session.getApplicationId(), + sessionZooKeeperClient.readDockerImageRepository(), + sessionZooKeeperClient.readVespaVersion(), + applicationPackage, + new SettableOptional<>(allocatedHosts), + clock.instant())); + } + private void nodeChanged() { zkWatcherExecutor.execute(() -> { Multiset<Session.Status> sessionMetrics = HashMultiset.create(); @@ -436,7 +512,7 @@ public class SessionRepository { RemoteSession session = remoteSessionCache.getSession(sessionId); if (session == null) continue; // session might have been deleted after getting session list log.log(Level.FINE, () -> session.logPre() + "Confirming upload for session " + sessionId); - session.confirmUpload(); + confirmUpload(session); } } @@ -455,7 +531,7 @@ public class SessionRepository { public RemoteSession createRemoteSession(long sessionId) { SessionZooKeeperClient sessionZKClient = createSessionZooKeeperClient(sessionId); - return new RemoteSession(tenantName, sessionId, componentRegistry, sessionZKClient); + return new RemoteSession(tenantName, sessionId, sessionZKClient); } private void ensureSessionPathDoesNotExist(long sessionId) { @@ -621,7 +697,7 @@ public class SessionRepository { } catch (IllegalArgumentException e) { // We cannot be guaranteed that the file reference exists (it could be that it has not // been downloaded yet), and e.g when bootstrapping we cannot throw an exception in that case - log.log(Level.INFO, "File reference for session id " + sessionId + ": " + fileReference + " not found in " + fileDirectory); + log.log(Level.FINE, () -> "File reference for session id " + sessionId + ": " + fileReference + " not found in " + fileDirectory); return Optional.empty(); } ApplicationId applicationId = sessionZKClient.readApplicationId() diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionStateWatcher.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionStateWatcher.java index 57d9f027447..a00a049a297 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionStateWatcher.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionStateWatcher.java @@ -55,7 +55,7 @@ public class SessionStateWatcher { break; case PREPARE: createLocalSession(sessionId); - sessionRepository.prepare(remoteSession); + sessionRepository.prepareRemoteSession(remoteSession); break; case ACTIVATE: createLocalSession(sessionId); @@ -65,7 +65,7 @@ public class SessionStateWatcher { sessionRepository.deactivate(remoteSession); break; case DELETE: - sessionRepository.delete(remoteSession, localSession); + sessionRepository.deleteSession(remoteSession, localSession); break; default: throw new IllegalStateException("Unknown status " + newStatus); diff --git a/configserver/src/test/apps/app-major-version-2/deployment.xml b/configserver/src/test/apps/app-major-version-2/deployment.xml new file mode 100644 index 00000000000..7523c104b7e --- /dev/null +++ b/configserver/src/test/apps/app-major-version-2/deployment.xml @@ -0,0 +1 @@ +<deployment version='1.0' major-version='2'/> diff --git a/configserver/src/test/apps/app-major-version-2/hosts.xml b/configserver/src/test/apps/app-major-version-2/hosts.xml new file mode 100644 index 00000000000..f4256c9fc81 --- /dev/null +++ b/configserver/src/test/apps/app-major-version-2/hosts.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. --> +<hosts> + <host name="mytesthost"> + <alias>node1</alias> + </host> +</hosts> diff --git a/configserver/src/test/apps/app-major-version-2/searchdefinitions/music.sd b/configserver/src/test/apps/app-major-version-2/searchdefinitions/music.sd new file mode 100644 index 00000000000..7670e78f22b --- /dev/null +++ b/configserver/src/test/apps/app-major-version-2/searchdefinitions/music.sd @@ -0,0 +1,50 @@ +# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +# A basic search definition - called music, should be saved to music.sd +search music { + + # It contains one document type only - called music as well + document music { + + field title type string { + indexing: summary | index # How this field should be indexed + # index-to: title, default # Create two indexes + weight: 75 # Ranking importancy of this field, used by the built in nativeRank feature + } + + field artist type string { + indexing: summary | attribute | index + # index-to: artist, default + + weight: 25 + } + + field year type int { + indexing: summary | attribute + } + + # Increase query + field popularity type int { + indexing: summary | attribute + } + + field url type uri { + indexing: summary | index + } + + } + + rank-profile default inherits default { + first-phase { + expression: nativeRank(title,artist) + attribute(popularity) + } + + } + + rank-profile textmatch inherits default { + first-phase { + expression: nativeRank(title,artist) + } + + } + +} diff --git a/configserver/src/test/apps/app-major-version-2/services.xml b/configserver/src/test/apps/app-major-version-2/services.xml new file mode 100644 index 00000000000..509d7786be0 --- /dev/null +++ b/configserver/src/test/apps/app-major-version-2/services.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. --> +<services version="1.0"> + + <admin version="2.0"> + <adminserver hostalias="node1"/> + <logserver hostalias="node1" /> + </admin> + + <content version="1.0"> + <redundancy>2</redundancy> + <documents> + <document type="music" mode="index"/> + </documents> + <nodes> + <node hostalias="node1" distribution-key="0"/> + </nodes> + + </content> + + <container version="1.0"> + <document-processing compressdocuments="true"> + <chain id="ContainerWrapperTest"> + <documentprocessor id="com.yahoo.vespa.config.AppleDocProc"/> + </chain> + </document-processing> + + <config name="project.specific"> + <value>someval</value> + </config> + + <nodes> + <node hostalias="node1" /> + </nodes> + + </container> + +</services> diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java index d84b81bd8e7..b7310917449 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java @@ -226,7 +226,7 @@ public class ApplicationHandlerTest { ApplicationId unknown = new ApplicationId.Builder().applicationName("unknown").tenant("default").build(); HttpResponse responseForUnknown = fileDistributionStatus(unknown, zone); assertEquals(404, responseForUnknown.getStatus()); - assertEquals("{\"error-code\":\"NOT_FOUND\",\"message\":\"Unknown application id 'default.unknown'\"}", + assertEquals("{\"error-code\":\"NOT_FOUND\",\"message\":\"No active session found for 'default.unknown'\"}", getRenderedString(responseForUnknown)); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/RpcServerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/RpcServerTest.java index ae6bd5feeab..d8d20d37551 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/RpcServerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/RpcServerTest.java @@ -64,7 +64,8 @@ public class RpcServerTest { ApplicationRepository applicationRepository = tester.applicationRepository(); applicationRepository.deploy(testApp, new PrepareParams.Builder().applicationId(applicationId).build()); TenantApplications applicationRepo = tester.tenant().getApplicationRepo(); - applicationRepo.reloadConfig(applicationRepository.getActiveSession(applicationId).ensureApplicationLoaded()); + ApplicationSet applicationSet = tester.tenant().getSessionRepository().ensureApplicationLoaded(applicationRepository.getActiveSession(applicationId)); + applicationRepo.reloadConfig(applicationSet); testPrintStatistics(tester); testGetConfig(tester); testEnabled(tester); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionTest.java deleted file mode 100644 index cda3e09d3a8..00000000000 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionTest.java +++ /dev/null @@ -1,299 +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.vespa.config.server.session; - -import com.yahoo.cloud.config.ConfigserverConfig; -import com.yahoo.component.Version; -import com.yahoo.config.application.api.ApplicationPackage; -import com.yahoo.config.model.NullConfigModelRegistry; -import com.yahoo.config.model.api.Model; -import com.yahoo.config.model.api.ModelContext; -import com.yahoo.config.model.api.ModelCreateResult; -import com.yahoo.config.model.api.ModelFactory; -import com.yahoo.config.model.api.ValidationParameters; -import com.yahoo.config.model.deploy.DeployState; -import com.yahoo.config.model.test.MockApplicationPackage; -import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.TenantName; -import com.yahoo.vespa.config.server.TestComponentRegistry; -import com.yahoo.vespa.config.server.application.ApplicationSet; -import com.yahoo.vespa.config.server.application.PermanentApplicationPackage; -import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry; -import com.yahoo.vespa.curator.Curator; -import com.yahoo.vespa.curator.mock.MockCurator; -import com.yahoo.vespa.model.VespaModel; -import com.yahoo.vespa.model.VespaModelFactory; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.io.IOException; -import java.time.Clock; -import java.time.Instant; -import java.time.LocalDate; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - -/** - * @author Ulf Lilleengen - * @author bratseth - */ -public class RemoteSessionTest { - - private static final TenantName tenantName = TenantName.from("default"); - - private Curator curator; - - @Rule - public TemporaryFolder temporaryFolder = new TemporaryFolder(); - - @Before - public void setupTest() { - curator = new MockCurator(); - } - - @Test - public void require_that_session_is_initialized() { - Clock clock = Clock.systemUTC(); - Session session = createSession(2, clock); - assertThat(session.getSessionId(), is(2L)); - session = createSession(Long.MAX_VALUE, clock); - assertThat(session.getSessionId(), is(Long.MAX_VALUE)); - } - - @Test - public void require_that_applications_are_loaded() { - RemoteSession session = createSession(3, Arrays.asList(new MockModelFactory(), new VespaModelFactory(new NullConfigModelRegistry()))); - session.prepare(); - ApplicationSet applicationSet = session.ensureApplicationLoaded(); - assertNotNull(applicationSet); - assertThat(applicationSet.getApplicationGeneration(), is(3L)); - assertThat(applicationSet.getForVersionOrLatest(Optional.empty(), Instant.now()).getId().application().value(), is("foo")); - assertNotNull(applicationSet.getForVersionOrLatest(Optional.empty(), Instant.now()).getModel()); - session.deactivate(); - - applicationSet = session.ensureApplicationLoaded(); - assertNotNull(applicationSet); - assertThat(applicationSet.getApplicationGeneration(), is(3L)); - assertThat(applicationSet.getForVersionOrLatest(Optional.empty(), Instant.now()).getId().application().value(), is("foo")); - assertNotNull(applicationSet.getForVersionOrLatest(Optional.empty(), Instant.now()).getModel()); - } - - @Test(expected = IllegalArgumentException.class) - public void require_that_new_invalid_application_throws_exception() { - MockModelFactory failingFactory = new MockModelFactory(); - failingFactory.vespaVersion = new Version(1, 2, 0); - failingFactory.throwOnLoad = true; - - MockModelFactory okFactory = new MockModelFactory(); - okFactory.vespaVersion = new Version(1, 1, 0); - okFactory.throwOnLoad = false; - - RemoteSession session = createSession(3, Arrays.asList(okFactory, failingFactory)); - session.prepare(); - } - - @Test - public void require_that_old_invalid_application_does_not_throw_exception_if_skipped_also_across_major_versions() { - MockModelFactory failingFactory = new MockModelFactory(); - failingFactory.vespaVersion = new Version(1, 0, 0); - failingFactory.throwOnLoad = true; - - MockModelFactory okFactory = - new MockModelFactory("<validation-overrides><allow until='2000-01-30'>skip-old-config-models</allow></validation-overrides>"); - okFactory.vespaVersion = new Version(2, 0, 0); - okFactory.throwOnLoad = false; - - RemoteSession session = createSession(3, Arrays.asList(okFactory, failingFactory), failingFactory.clock()); - session.prepare(); - } - - @Test - public void require_that_an_application_package_can_limit_to_one_major_version() { - ApplicationPackage application = - new MockApplicationPackage.Builder().withServices("<services version='1.0'/>") - .withDeploymentSpec("<deployment version='1.0' major-version='2'/>") - .build(); - assertTrue(application.getMajorVersion().isPresent()); - assertEquals(2, (int)application.getMajorVersion().get()); - - MockModelFactory failingFactory = new MockModelFactory(); - failingFactory.vespaVersion = new Version(3, 0, 0); - failingFactory.throwErrorOnLoad = true; - - MockModelFactory okFactory = new MockModelFactory(); - okFactory.vespaVersion = new Version(2, 0, 0); - okFactory.throwErrorOnLoad = false; - - SessionZooKeeperClient zkc = new MockSessionZKClient(curator, tenantName, 3, application); - RemoteSession session = createSession(3, zkc, Arrays.asList(okFactory, failingFactory)); - session.prepare(); - - // Does not cause an error because model version 3 is skipped - } - - @Test - public void require_that_an_application_package_can_limit_to_one_higher_major_version() { - ApplicationPackage application = - new MockApplicationPackage.Builder().withServices("<services version='1.0'/>") - .withDeploymentSpec("<deployment version='1.0' major-version='3'/>") - .build(); - assertTrue(application.getMajorVersion().isPresent()); - assertEquals(3, (int)application.getMajorVersion().get()); - - MockModelFactory failingFactory = new MockModelFactory(); - failingFactory.vespaVersion = new Version(4, 0, 0); - failingFactory.throwErrorOnLoad = true; - - MockModelFactory okFactory = new MockModelFactory(); - okFactory.vespaVersion = new Version(2, 0, 0); - okFactory.throwErrorOnLoad = false; - - SessionZooKeeperClient zkc = new MockSessionZKClient(curator, tenantName, 3, application); - RemoteSession session = createSession(4, zkc, Arrays.asList(okFactory, failingFactory)); - session.prepare(); - - // Does not cause an error because model version 4 is skipped - } - - @Test - public void require_that_session_status_is_updated() { - SessionZooKeeperClient zkc = new MockSessionZKClient(curator, tenantName, 3); - RemoteSession session = createSession(3, zkc, Clock.systemUTC()); - assertThat(session.getStatus(), is(Session.Status.NEW)); - zkc.writeStatus(Session.Status.PREPARE); - assertThat(session.getStatus(), is(Session.Status.PREPARE)); - } - - @Test - public void require_that_permanent_app_is_used() throws IOException { - Optional<PermanentApplicationPackage> permanentApp = Optional.of(new PermanentApplicationPackage( - new ConfigserverConfig(new ConfigserverConfig.Builder() - .applicationDirectory(temporaryFolder.newFolder("appdir").getAbsolutePath())))); - MockModelFactory mockModelFactory = new MockModelFactory(); - try { - int sessionId = 3; - SessionZooKeeperClient zkc = new MockSessionZKClient(curator, tenantName, sessionId); - createSession(sessionId, zkc, Collections.singletonList(mockModelFactory), permanentApp, Clock.systemUTC()).ensureApplicationLoaded(); - } catch (Exception e) { - e.printStackTrace(); - // ignore, we're not interested in deploy errors as long as the below state is OK. - } - assertNotNull(mockModelFactory.modelContext); - assertTrue(mockModelFactory.modelContext.permanentApplicationPackage().isPresent()); - } - - private RemoteSession createSession(long sessionId, Clock clock) { - return createSession(sessionId, Collections.singletonList(new VespaModelFactory(new NullConfigModelRegistry())), clock); - } - - private RemoteSession createSession(long sessionId, SessionZooKeeperClient zkc, Clock clock) { - return createSession(sessionId, zkc, Collections.singletonList(new VespaModelFactory(new NullConfigModelRegistry())), clock); - } - - private RemoteSession createSession(long sessionId, List<ModelFactory> modelFactories) { - SessionZooKeeperClient zkc = new MockSessionZKClient(curator, tenantName, sessionId); - return createSession(sessionId, zkc, modelFactories, Clock.systemUTC()); - } - - private RemoteSession createSession(long sessionId, List<ModelFactory> modelFactories, Clock clock) { - SessionZooKeeperClient zkc = new MockSessionZKClient(curator, tenantName, sessionId); - return createSession(sessionId, zkc, modelFactories, clock); - } - - private RemoteSession createSession(long sessionId, SessionZooKeeperClient zkc, List<ModelFactory> modelFactories) { - return createSession(sessionId, zkc, modelFactories, Optional.empty(), Clock.systemUTC()); - } - - private RemoteSession createSession(long sessionId, SessionZooKeeperClient zkc, List<ModelFactory> modelFactories, Clock clock) { - return createSession(sessionId, zkc, modelFactories, Optional.empty(), clock); - } - - private RemoteSession createSession(long sessionId, SessionZooKeeperClient zkc, - List<ModelFactory> modelFactories, - Optional<PermanentApplicationPackage> permanentApplicationPackage, - Clock clock) { - zkc.writeStatus(Session.Status.NEW); - zkc.writeApplicationId(new ApplicationId.Builder().applicationName("foo").instanceName("bim").build()); - TestComponentRegistry.Builder registryBuilder = new TestComponentRegistry.Builder() - .curator(curator) - .clock(clock) - .modelFactoryRegistry(new ModelFactoryRegistry(modelFactories)); - permanentApplicationPackage.ifPresent(registryBuilder::permanentApplicationPackage); - - return new RemoteSession(tenantName, sessionId, registryBuilder.build(), zkc); - } - - private static class MockModelFactory implements ModelFactory { - - /** Throw a RuntimeException on load - this is handled gracefully during model building */ - boolean throwOnLoad = false; - - /** Throw an Error on load - this is useful to propagate this condition all the way to the test */ - boolean throwErrorOnLoad = false; - - ModelContext modelContext; - public Version vespaVersion = new Version(1, 2, 3); - - /** The validation overrides of this, or null if none */ - private final String validationOverrides; - - private final Clock clock = Clock.fixed(LocalDate.parse("2000-01-01", DateTimeFormatter.ISO_DATE).atStartOfDay().atZone(ZoneOffset.UTC).toInstant(), ZoneOffset.UTC); - - MockModelFactory() { this(null); } - - MockModelFactory(String validationOverrides) { - this.validationOverrides = validationOverrides; - } - - @Override - public Version version() { - return vespaVersion; - } - - /** Returns the clock used by this, which is fixed at the instant 2000-01-01T00:00:00 */ - public Clock clock() { return clock; } - - @Override - public Model createModel(ModelContext modelContext) { - if (throwErrorOnLoad) - throw new Error("Foo"); - if (throwOnLoad) - throw new IllegalArgumentException("Foo"); - this.modelContext = modelContext; - return loadModel(); - } - - Model loadModel() { - try { - ApplicationPackage application = new MockApplicationPackage.Builder().withEmptyHosts().withEmptyServices().withValidationOverrides(validationOverrides).build(); - DeployState deployState = new DeployState.Builder().applicationPackage(application).now(clock.instant()).build(); - return new VespaModel(deployState); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public ModelCreateResult createAndValidateModel(ModelContext modelContext, ValidationParameters validationParameters) { - if (throwErrorOnLoad) - throw new Error("Foo"); - if (throwOnLoad) - throw new IllegalArgumentException("Foo"); - this.modelContext = modelContext; - return new ModelCreateResult(loadModel(), new ArrayList<>()); - } - } - -} diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionRepositoryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionRepositoryTest.java index b89b63aed46..d8adbd398d1 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionRepositoryTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionRepositoryTest.java @@ -2,14 +2,26 @@ package com.yahoo.vespa.config.server.session; import com.yahoo.cloud.config.ConfigserverConfig; +import com.yahoo.component.Version; +import com.yahoo.config.application.api.ApplicationPackage; +import com.yahoo.config.model.api.Model; +import com.yahoo.config.model.api.ModelContext; +import com.yahoo.config.model.api.ModelCreateResult; +import com.yahoo.config.model.api.ModelFactory; +import com.yahoo.config.model.api.ValidationParameters; +import com.yahoo.config.model.deploy.DeployState; +import com.yahoo.config.model.test.MockApplicationPackage; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.TenantName; import com.yahoo.text.Utf8; import com.yahoo.vespa.config.server.ApplicationRepository; import com.yahoo.vespa.config.server.GlobalComponentRegistry; import com.yahoo.vespa.config.server.TestComponentRegistry; +import com.yahoo.vespa.config.server.application.ApplicationSet; import com.yahoo.vespa.config.server.application.OrchestratorMock; +import com.yahoo.vespa.config.server.http.InvalidApplicationException; import com.yahoo.vespa.config.server.http.SessionHandlerTest; +import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry; import com.yahoo.vespa.config.server.tenant.TenantRepository; import com.yahoo.vespa.config.server.zookeeper.ConfigCurator; import com.yahoo.vespa.config.util.ConfigUtils; @@ -17,16 +29,25 @@ import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.curator.mock.MockCurator; import com.yahoo.vespa.flags.FlagSource; import com.yahoo.vespa.flags.InMemoryFlagSource; +import com.yahoo.vespa.model.VespaModel; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import java.io.File; +import java.time.Clock; import java.time.Duration; import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; import java.util.function.LongPredicate; import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; @@ -53,9 +74,13 @@ public class SessionRepositoryTest { } private void setup(FlagSource flagSource) throws Exception { + setup(flagSource, new TestComponentRegistry.Builder()); + } + + private void setup(FlagSource flagSource, TestComponentRegistry.Builder componentRegistryBuilder) throws Exception { curator = new MockCurator(); File configserverDbDir = temporaryFolder.newFolder().getAbsoluteFile(); - GlobalComponentRegistry globalComponentRegistry = new TestComponentRegistry.Builder() + GlobalComponentRegistry globalComponentRegistry = componentRegistryBuilder .curator(curator) .configServerConfig(new ConfigserverConfig.Builder() .configServerDBDir(configserverDbDir.getAbsolutePath()) @@ -75,6 +100,7 @@ public class SessionRepositoryTest { sessionRepository = tenantRepository.getTenant(tenantName).getSessionRepository(); } + @Test public void require_that_local_sessions_are_created_and_deleted() throws Exception { setup(); @@ -84,6 +110,12 @@ public class SessionRepositoryTest { assertNotNull(sessionRepository.getLocalSession(secondSessionId)); assertNull(sessionRepository.getLocalSession(secondSessionId + 1)); + ApplicationSet applicationSet = sessionRepository.ensureApplicationLoaded(sessionRepository.getRemoteSession(firstSessionId)); + assertNotNull(applicationSet); + assertEquals(2, applicationSet.getApplicationGeneration()); + assertEquals(applicationId.application(), applicationSet.getForVersionOrLatest(Optional.empty(), Instant.now()).getId().application()); + assertNotNull(applicationSet.getForVersionOrLatest(Optional.empty(), Instant.now()).getModel()); + sessionRepository.close(); // All created sessions are deleted assertNull(sessionRepository.getLocalSession(firstSessionId)); @@ -150,6 +182,81 @@ public class SessionRepositoryTest { assertThat(sessionRepository.getRemoteSessions().size(), is(1)); } + @Test(expected = InvalidApplicationException.class) + public void require_that_new_invalid_application_throws_exception() throws Exception { + MockModelFactory failingFactory = new MockModelFactory(); + failingFactory.vespaVersion = new Version(1, 2, 0); + failingFactory.throwOnLoad = true; + + MockModelFactory okFactory = new MockModelFactory(); + okFactory.vespaVersion = new Version(1, 1, 0); + okFactory.throwOnLoad = false; + + TestComponentRegistry.Builder registryBuilder = new TestComponentRegistry.Builder() + .modelFactoryRegistry(new ModelFactoryRegistry(List.of(okFactory, failingFactory))); + setup(new InMemoryFlagSource(), registryBuilder); + + deploy(); + } + + @Test + public void require_that_old_invalid_application_does_not_throw_exception_if_skipped_also_across_major_versions() throws Exception { + MockModelFactory failingFactory = new MockModelFactory(); + failingFactory.vespaVersion = new Version(1, 0, 0); + failingFactory.throwOnLoad = true; + + MockModelFactory okFactory = + new MockModelFactory("<validation-overrides><allow until='2000-01-30'>skip-old-config-models</allow></validation-overrides>"); + okFactory.vespaVersion = new Version(2, 0, 0); + okFactory.throwOnLoad = false; + + TestComponentRegistry.Builder registryBuilder = new TestComponentRegistry.Builder() + .modelFactoryRegistry(new ModelFactoryRegistry(List.of(okFactory, failingFactory))); + setup(new InMemoryFlagSource(), registryBuilder); + + deploy(); + } + + @Test + public void require_that_an_application_package_can_limit_to_one_major_version() throws Exception { + MockModelFactory failingFactory = new MockModelFactory(); + failingFactory.vespaVersion = new Version(3, 0, 0); + failingFactory.throwErrorOnLoad = true; + + MockModelFactory okFactory = new MockModelFactory(); + okFactory.vespaVersion = new Version(2, 0, 0); + okFactory.throwErrorOnLoad = false; + + TestComponentRegistry.Builder registryBuilder = new TestComponentRegistry.Builder() + .modelFactoryRegistry(new ModelFactoryRegistry(List.of(okFactory, failingFactory))); + setup(new InMemoryFlagSource(), registryBuilder); + + File testApp = new File("src/test/apps/app-major-version-2"); + deploy(applicationId, testApp); + + // Does not cause an error because model version 3 is skipped + } + + @Test + public void require_that_an_application_package_can_limit_to_one_higher_major_version() throws Exception { + MockModelFactory failingFactory = new MockModelFactory(); + failingFactory.vespaVersion = new Version(3, 0, 0); + failingFactory.throwErrorOnLoad = true; + + MockModelFactory okFactory = new MockModelFactory(); + okFactory.vespaVersion = new Version(1, 0, 0); + okFactory.throwErrorOnLoad = false; + + TestComponentRegistry.Builder registryBuilder = new TestComponentRegistry.Builder() + .modelFactoryRegistry(new ModelFactoryRegistry(List.of(okFactory, failingFactory))); + setup(new InMemoryFlagSource(), registryBuilder); + + File testApp = new File("src/test/apps/app-major-version-2"); + deploy(applicationId, testApp); + + // Does not cause an error because model version 3 is skipped + } + private void createSession(long sessionId, boolean wait) { SessionZooKeeperClient zkc = new SessionZooKeeperClient(curator, ConfigCurator.create(curator), @@ -204,8 +311,74 @@ public class SessionRepositoryTest { } private long deploy(ApplicationId applicationId) { + return deploy(applicationId, testApp); + } + + private long deploy(ApplicationId applicationId, File testApp) { applicationRepository.deploy(testApp, new PrepareParams.Builder().applicationId(applicationId).build()); return applicationRepository.getActiveSession(applicationId).getSessionId(); } + private static class MockModelFactory implements ModelFactory { + + /** Throw a RuntimeException on load - this is handled gracefully during model building */ + boolean throwOnLoad = false; + + /** Throw an Error on load - this is useful to propagate this condition all the way to the test */ + boolean throwErrorOnLoad = false; + + ModelContext modelContext; + public Version vespaVersion = new Version(1, 2, 3); + + /** The validation overrides of this, or null if none */ + private final String validationOverrides; + + private final Clock clock = Clock.fixed(LocalDate.parse("2000-01-01", DateTimeFormatter.ISO_DATE).atStartOfDay().atZone(ZoneOffset.UTC).toInstant(), ZoneOffset.UTC); + + MockModelFactory() { this(null); } + + MockModelFactory(String validationOverrides) { + this.validationOverrides = validationOverrides; + } + + @Override + public Version version() { + return vespaVersion; + } + + /** Returns the clock used by this, which is fixed at the instant 2000-01-01T00:00:00 */ + public Clock clock() { return clock; } + + @Override + public Model createModel(ModelContext modelContext) { + if (throwErrorOnLoad) + throw new Error("error on load"); + if (throwOnLoad) + throw new IllegalArgumentException("exception on load"); + this.modelContext = modelContext; + return loadModel(); + } + + Model loadModel() { + try { + ApplicationPackage application = new MockApplicationPackage.Builder().withEmptyHosts().withEmptyServices().withValidationOverrides(validationOverrides).build(); + DeployState deployState = new DeployState.Builder().applicationPackage(application).now(clock.instant()).build(); + return new VespaModel(deployState); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public ModelCreateResult createAndValidateModel(ModelContext modelContext, ValidationParameters validationParameters) { + if (throwErrorOnLoad) + throw new Error("error on load"); + if (throwOnLoad) + throw new IllegalArgumentException("exception on load"); + this.modelContext = modelContext; + return new ModelCreateResult(loadModel(), new ArrayList<>()); + } + } + + } |