summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJon Marius Venstad <jonmv@users.noreply.github.com>2020-10-05 12:07:56 +0200
committerGitHub <noreply@github.com>2020-10-05 12:07:56 +0200
commit5341ecd273471ebed963e652c03be587ef85892e (patch)
tree84d342d9bf8503a0f9968b59e60b5b6d593c44cd
parent13092df7574517948618c335a588bf8b46eab049 (diff)
parent15b574feb6be44ec9fd2f9473ff8567fa4bc2f15 (diff)
Merge pull request #14717 from vespa-engine/revert-14711-revert-14690-hmusum/refactor-RemoteSession-take-3
Reapply "Move code out of RemoteSesion"
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java3
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java107
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java4
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java131
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionStateWatcher.java11
-rw-r--r--configserver/src/test/apps/app-major-version-2/deployment.xml1
-rw-r--r--configserver/src/test/apps/app-major-version-2/hosts.xml7
-rw-r--r--configserver/src/test/apps/app-major-version-2/searchdefinitions/music.sd50
-rw-r--r--configserver/src/test/apps/app-major-version-2/services.xml38
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java2
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/rpc/RpcServerTest.java5
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionTest.java299
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionRepositoryTest.java183
13 files changed, 424 insertions, 417 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 5bbe9c967cf..510569355f5 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
@@ -605,7 +605,8 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
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());
+ 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;
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 9417a798d1f..1eeac5c7280 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,21 +1,11 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Verizon Media. 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.Set;
-import java.util.logging.Level;
-import java.util.logging.Logger;
+import java.util.Objects;
+import java.util.Optional;
/**
* A RemoteSession represents a session created on another config server. This session can
@@ -25,91 +15,48 @@ 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.
*
* @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();
- }
-
- void prepare() {
- Curator.CompletionWaiter waiter = sessionZooKeeperClient.getPrepareWaiter();
- ensureApplicationLoaded();
- notifyCompletion(waiter);
- }
-
- private ApplicationSet loadApplication() {
- ApplicationPackage applicationPackage = sessionZooKeeperClient.loadApplicationPackage();
-
- // Read hosts allocated on the config server instance which created this
- SettableOptional<AllocatedHosts> allocatedHosts = new SettableOptional<>(applicationPackage.getAllocatedHosts());
-
- return ApplicationSet.fromList(applicationLoader.buildModels(getApplicationId(),
- sessionZooKeeperClient.readDockerImageRepository(),
- sessionZooKeeperClient.readVespaVersion(),
- applicationPackage,
- allocatedHosts,
- clock.instant()));
+ RemoteSession(TenantName tenant, long sessionId, SessionZooKeeperClient zooKeeperClient) {
+ this(tenant, sessionId, zooKeeperClient, Optional.empty());
}
- public synchronized ApplicationSet ensureApplicationLoaded() {
- return applicationSet == null ? applicationSet = loadApplication() : applicationSet;
+ /**
+ * 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
+ * @param applicationSet current application set for this session
+ */
+ RemoteSession(TenantName tenant, long sessionId, SessionZooKeeperClient zooKeeperClient, Optional<ApplicationSet> applicationSet) {
+ super(tenant, sessionId, zooKeeperClient);
+ this.applicationSet = applicationSet;
}
- public synchronized void deactivate() {
- applicationSet = null;
+ Optional<ApplicationSet> applicationSet() {
+ return applicationSet;
}
- 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());
+ public synchronized RemoteSession activated(ApplicationSet applicationSet) {
+ Objects.requireNonNull(applicationSet, "applicationSet cannot be null");
+ return new RemoteSession(tenant, sessionId, sessionZooKeeperClient, Optional.of(applicationSet));
}
- 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 synchronized RemoteSession deactivated() {
+ return new RemoteSession(tenant, sessionId, sessionZooKeeperClient, Optional.empty());
}
- public void delete() {
- Transaction transaction = sessionZooKeeperClient.deleteTransaction();
- transaction.commit();
- transaction.close();
+ @Override
+ public String toString() {
+ return super.toString() + ",application set=" + applicationSet;
}
}
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 ea17b8a2640..dad9e48ce19 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
@@ -1,4 +1,4 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Verizon Media. 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.component.Version;
@@ -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/SessionRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java
index edc7a070063..bbca0bca5b8 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
@@ -1,4 +1,4 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Verizon Media. 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.google.common.collect.HashMultiset;
@@ -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;
@@ -35,6 +38,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;
@@ -50,6 +54,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.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.logging.Level;
@@ -259,13 +264,25 @@ 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();
+ deleteRemoteSessionFromZooKeeper(session);
deleted++;
}
}
return deleted;
}
+ public void deactivate(RemoteSession remoteSession) {
+ RemoteSession session = remoteSession.deactivated();
+ remoteSessionCache.put(session.getSessionId(), session);
+ }
+
+ public void deleteRemoteSessionFromZooKeeper(RemoteSession session) {
+ SessionZooKeeperClient sessionZooKeeperClient = createSessionZooKeeperClient(session.getSessionId());
+ Transaction transaction = sessionZooKeeperClient.deleteTransaction();
+ transaction.commit();
+ transaction.close();
+ }
+
// TODO: Delete after 7.294 has been rolled out everywhere
public int deleteExpiredLocks(Clock clock, Duration expiryTime) {
int deleted = 0;
@@ -333,7 +350,7 @@ public class SessionRepository {
RemoteSession session = createRemoteSession(sessionId);
if (session.getStatus() == Session.Status.NEW) {
log.log(Level.FINE, () -> session.logPre() + "Confirming upload for session " + sessionId);
- session.confirmUpload();
+ confirmUpload(session);
}
if (distributeApplicationPackage())
createLocalSessionUsingDistributedApplicationPackage(sessionId);
@@ -342,25 +359,21 @@ public class SessionRepository {
void activate(RemoteSession session) {
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();
+ log.log(Level.FINE, () -> session.logPre() + "Getting session from repo: " + session);
+ 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) {
long sessionId = remoteSession.getSessionId();
// TODO: Change log level to FINE when debugging is finished
log.log(Level.INFO, () -> remoteSession.logPre() + "Deactivating and deleting remote session " + sessionId);
- remoteSession.deactivate();
- remoteSession.delete();
+ deactivate(remoteSession);
+ deleteRemoteSessionFromZooKeeper(remoteSession);
remoteSessionCache.remove(sessionId);
LocalSession localSession = getLocalSession(sessionId);
if (localSession != null) {
@@ -370,10 +383,6 @@ public class SessionRepository {
}
}
- void prepare(RemoteSession session) {
- session.prepare();
- }
-
boolean distributeApplicationPackage() {
return distributeApplicationPackage.value();
}
@@ -383,7 +392,7 @@ public class SessionRepository {
if (watcher != null) watcher.close();
RemoteSession session = remoteSessionCache.remove(sessionId);
if (session != null) {
- session.deactivate();
+ deactivate(session);
}
metrics.incRemovedSessions();
}
@@ -392,13 +401,86 @@ 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) {
+
+ if (session.applicationSet().isPresent()) {
+ return session.applicationSet().get();
+ }
+
+ ApplicationSet applicationSet = loadApplication(session);
+ RemoteSession activated = session.activated(applicationSet);
+ long sessionId = activated.getSessionId();
+ remoteSessionCache.put(sessionId, activated);
+ updateSessionStateWatcher(sessionId, activated);
+
+ 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) {
+ log.log(Level.FINE, () -> "Loading application for " + 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
+ SettableOptional<AllocatedHosts> allocatedHosts = new SettableOptional<>(applicationPackage.getAllocatedHosts());
+
+ return ApplicationSet.fromList(builder.buildModels(session.getApplicationId(),
+ sessionZooKeeperClient.readDockerImageRepository(),
+ sessionZooKeeperClient.readVespaVersion(),
+ applicationPackage,
+ allocatedHosts,
+ clock.instant()));
+ }
+
private void nodeChanged() {
zkWatcherExecutor.execute(() -> {
Multiset<Session.Status> sessionMetrics = HashMultiset.create();
@@ -444,10 +526,10 @@ public class SessionRepository {
public synchronized RemoteSession createRemoteSession(long sessionId) {
SessionZooKeeperClient sessionZKClient = createSessionZooKeeperClient(sessionId);
- RemoteSession session = new RemoteSession(tenantName, sessionId, componentRegistry, sessionZKClient);
+ RemoteSession session = new RemoteSession(tenantName, sessionId, sessionZKClient);
remoteSessionCache.put(sessionId, session);
loadSessionIfActive(session);
- addSessionStateWatcher(sessionId, session);
+ updateSessionStateWatcher(sessionId, session);
return session;
}
@@ -566,7 +648,7 @@ public class SessionRepository {
try {
long currentActiveSessionId = applicationRepo.requireActiveSessionOf(appId);
RemoteSession currentActiveSession = getRemoteSession(currentActiveSessionId);
- currentActiveApplicationSet = Optional.ofNullable(currentActiveSession.ensureApplicationLoaded());
+ currentActiveApplicationSet = Optional.ofNullable(ensureApplicationLoaded(currentActiveSession));
} catch (IllegalArgumentException e) {
// Do nothing if we have no currently active session
}
@@ -674,11 +756,14 @@ public class SessionRepository {
return new TenantFileSystemDirs(componentRegistry.getConfigServerDB(), tenantName).getUserApplicationDir(sessionId);
}
- private void addSessionStateWatcher(long sessionId, RemoteSession remoteSession) {
- if ( ! sessionStateWatchers.containsKey(sessionId)) {
+ private void updateSessionStateWatcher(long sessionId, RemoteSession remoteSession) {
+ SessionStateWatcher sessionStateWatcher = sessionStateWatchers.get(sessionId);
+ if (sessionStateWatcher == null) {
Curator.FileCache fileCache = curator.createFileCache(getSessionStatePath(sessionId).getAbsolute(), false);
fileCache.addListener(this::nodeChanged);
sessionStateWatchers.put(sessionId, new SessionStateWatcher(fileCache, remoteSession, metrics, zkWatcherExecutor, this));
+ } else {
+ sessionStateWatcher.updateRemoteSession(remoteSession);
}
}
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 d6d08aaac6c..a5ac0369f1b 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
@@ -7,6 +7,7 @@ import com.yahoo.vespa.curator.Curator;
import org.apache.curator.framework.recipes.cache.ChildData;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -24,7 +25,7 @@ public class SessionStateWatcher {
private static final Logger log = Logger.getLogger(SessionStateWatcher.class.getName());
private final Curator.FileCache fileCache;
- private final RemoteSession session;
+ private volatile RemoteSession session;
private final MetricUpdater metrics;
private final Executor zkWatcherExecutor;
private final SessionRepository sessionRepository;
@@ -43,7 +44,7 @@ public class SessionStateWatcher {
this.sessionRepository = sessionRepository;
}
- private void sessionStatusChanged(Status newStatus) {
+ private synchronized void sessionStatusChanged(Status newStatus) {
long sessionId = session.getSessionId();
switch (newStatus) {
case NEW:
@@ -51,7 +52,7 @@ public class SessionStateWatcher {
break;
case PREPARE:
createLocalSession(sessionId);
- sessionRepository.prepare(session);
+ sessionRepository.prepareRemoteSession(session);
break;
case ACTIVATE:
createLocalSession(sessionId);
@@ -105,4 +106,8 @@ public class SessionStateWatcher {
});
}
+ public synchronized void updateRemoteSession(RemoteSession session) {
+ this.session = session;
+ }
+
}
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 9e4ca8dfb2c..7e41adf6ddc 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
@@ -1,4 +1,4 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.config.server.http.v2;
import com.fasterxml.jackson.databind.ObjectMapper;
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..4d7702eec9e 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
@@ -1,4 +1,4 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.config.server.rpc;
import com.yahoo.cloud.config.ConfigserverConfig;
@@ -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..aeff4029440 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
@@ -1,15 +1,27 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Verizon Media. 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.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;
@@ -49,13 +70,17 @@ public class SessionRepositoryTest {
public TemporaryFolder temporaryFolder = new TemporaryFolder();
public void setup() throws Exception {
- setup(new InMemoryFlagSource());
+ setup(new InMemoryFlagSource());
}
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())
@@ -84,6 +109,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));
@@ -127,8 +158,7 @@ public class SessionRepositoryTest {
assertStatusChange(sessionId, Session.Status.PREPARE);
assertStatusChange(sessionId, Session.Status.ACTIVATE);
- com.yahoo.path.Path session = TenantRepository.getSessionsPath(tenantName).append("" + sessionId);
- curator.delete(session);
+ sessionRepository.delete(sessionRepository.getRemoteSession(sessionId));
assertSessionRemoved(sessionId);
assertNull(sessionRepository.getRemoteSession(sessionId));
}
@@ -150,6 +180,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),
@@ -165,6 +270,7 @@ public class SessionRepositoryTest {
private void assertStatusChange(long sessionId, Session.Status status) throws Exception {
com.yahoo.path.Path statePath = sessionRepository.getSessionStatePath(sessionId);
+ System.out.println("Setting and asserting state for " + statePath);
curator.create(statePath);
curator.framework().setData().forPath(statePath.getAbsolute(), Utf8.toBytes(status.toString()));
assertRemoteSessionStatus(sessionId, status);
@@ -187,7 +293,7 @@ public class SessionRepositoryTest {
}
private void waitFor(LongPredicate predicate, long sessionId) {
- long endTime = System.currentTimeMillis() + 60_000;
+ long endTime = System.currentTimeMillis() + 5_000;
boolean ok;
do {
ok = predicate.test(sessionId);
@@ -204,8 +310,73 @@ 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<>());
+ }
+ }
+
}