summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java31
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java2
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionRepo.java151
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionStateWatcher.java10
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionRepo.java241
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java337
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenant.java29
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRepository.java16
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java30
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java2
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java4
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationContentHandlerTest.java4
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java4
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HostHandlerTest.java4
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java2
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionRepoTest.java106
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionRepoTest.java145
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionRepositoryTest.java221
-rw-r--r--container-core/src/main/java/com/yahoo/container/protect/ProcessTerminator.java2
19 files changed, 721 insertions, 620 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 af5a561aa6e..dd88096c62f 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
@@ -49,9 +49,10 @@ import com.yahoo.vespa.config.server.http.v2.PrepareResult;
import com.yahoo.vespa.config.server.metrics.ApplicationMetricsRetriever;
import com.yahoo.vespa.config.server.provision.HostProvisionerProvider;
import com.yahoo.vespa.config.server.session.LocalSession;
-import com.yahoo.vespa.config.server.session.SessionRepository;
+import com.yahoo.vespa.config.server.session.LocalSessionRepo;
import com.yahoo.vespa.config.server.session.PrepareParams;
import com.yahoo.vespa.config.server.session.RemoteSession;
+import com.yahoo.vespa.config.server.session.RemoteSessionRepo;
import com.yahoo.vespa.config.server.session.Session;
import com.yahoo.vespa.config.server.session.SessionFactory;
import com.yahoo.vespa.config.server.session.SilentDeployLogger;
@@ -304,7 +305,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
if (activeSession == null) return Optional.empty();
TimeoutBudget timeoutBudget = new TimeoutBudget(clock, timeout);
LocalSession newSession = tenant.getSessionFactory().createSessionFromExisting(activeSession, logger, true, timeoutBudget);
- tenant.getSessionRepository().addSession(newSession);
+ tenant.getLocalSessionRepo().addSession(newSession);
return Optional.of(Deployment.unprepared(newSession, this, hostProvisioner, tenant, timeout, clock,
false /* don't validate as this is already deployed */, bootstrap));
@@ -488,7 +489,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
Tenant tenant = tenantRepository.getTenant(applicationId.tenant());
if (tenant == null) throw new NotFoundException("Tenant '" + applicationId.tenant() + "' not found");
long sessionId = getSessionIdForApplication(tenant, applicationId);
- RemoteSession session = tenant.getSessionRepo().getRemoteSession(sessionId);
+ RemoteSession session = tenant.getRemoteSessionRepo().getSession(sessionId);
if (session == null) throw new NotFoundException("Remote session " + sessionId + " not found");
return session.ensureApplicationLoaded().getForVersionOrLatest(version, clock.instant());
} catch (NotFoundException e) {
@@ -525,10 +526,10 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
}
private boolean localSessionHasBeenDeleted(ApplicationId applicationId, long sessionId, Duration waitTime) {
- SessionRepository sessionRepository = tenantRepository.getTenant(applicationId.tenant()).getSessionRepo();
+ RemoteSessionRepo remoteSessionRepo = tenantRepository.getTenant(applicationId.tenant()).getRemoteSessionRepo();
Instant end = Instant.now().plus(waitTime);
do {
- if (sessionRepository.getRemoteSession(sessionId) == null) return true;
+ if (remoteSessionRepo.getSession(sessionId) == null) return true;
try { Thread.sleep(10); } catch (InterruptedException e) { /* ignored */}
} while (Instant.now().isBefore(end));
@@ -634,11 +635,11 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
boolean internalRedeploy,
TimeoutBudget timeoutBudget) {
Tenant tenant = tenantRepository.getTenant(applicationId.tenant());
- SessionRepository sessionRepository = tenant.getSessionRepository();
+ LocalSessionRepo localSessionRepo = tenant.getLocalSessionRepo();
SessionFactory sessionFactory = tenant.getSessionFactory();
RemoteSession fromSession = getExistingSession(tenant, applicationId);
LocalSession session = sessionFactory.createSessionFromExisting(fromSession, logger, internalRedeploy, timeoutBudget);
- sessionRepository.addSession(session);
+ localSessionRepo.addSession(session);
return session.getSessionId();
}
@@ -659,13 +660,13 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
Optional<Long> activeSessionId = tenant.getApplicationRepo().activeSessionOf(applicationId);
LocalSession session = tenant.getSessionFactory().createSession(applicationDirectory, applicationId,
timeoutBudget, activeSessionId);
- tenant.getSessionRepository().addSession(session);
+ tenant.getLocalSessionRepo().addSession(session);
return session.getSessionId();
}
public void deleteExpiredLocalSessions() {
Map<Tenant, List<LocalSession>> sessionsPerTenant = new HashMap<>();
- tenantRepository.getAllTenants().forEach(tenant -> sessionsPerTenant.put(tenant, tenant.getSessionRepository().getSessions()));
+ tenantRepository.getAllTenants().forEach(tenant -> sessionsPerTenant.put(tenant, tenant.getLocalSessionRepo().getSessions()));
Set<ApplicationId> applicationIds = new HashSet<>();
sessionsPerTenant.values().forEach(sessionList -> sessionList.forEach(s -> applicationIds.add(s.getApplicationId())));
@@ -676,7 +677,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
if (activeSession != null)
activeSessions.put(applicationId, activeSession.getSessionId());
});
- sessionsPerTenant.keySet().forEach(tenant -> tenant.getSessionRepository().deleteExpiredSessions(activeSessions));
+ sessionsPerTenant.keySet().forEach(tenant -> tenant.getLocalSessionRepo().deleteExpiredSessions(activeSessions));
}
public int deleteExpiredRemoteSessions(Duration expiryTime) {
@@ -686,7 +687,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
public int deleteExpiredRemoteSessions(Clock clock, Duration expiryTime) {
return tenantRepository.getAllTenants()
.stream()
- .map(tenant -> tenant.getSessionRepo().deleteExpiredRemoteSessions(clock, expiryTime))
+ .map(tenant -> tenant.getRemoteSessionRepo().deleteExpiredSessions(clock, expiryTime))
.mapToInt(i -> i)
.sum();
}
@@ -746,14 +747,14 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
}
private LocalSession getLocalSession(Tenant tenant, long sessionId) {
- LocalSession session = tenant.getSessionRepository().getSession(sessionId);
+ LocalSession session = tenant.getLocalSessionRepo().getSession(sessionId);
if (session == null) throw new NotFoundException("Session " + sessionId + " was not found");
return session;
}
private RemoteSession getRemoteSession(Tenant tenant, long sessionId) {
- RemoteSession session = tenant.getSessionRepo().getRemoteSession(sessionId);
+ RemoteSession session = tenant.getRemoteSessionRepo().getSession(sessionId);
if (session == null) throw new NotFoundException("Session " + sessionId + " was not found");
return session;
@@ -804,7 +805,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
private RemoteSession getActiveSession(Tenant tenant, ApplicationId applicationId) {
TenantApplications applicationRepo = tenant.getApplicationRepo();
if (applicationRepo.activeApplications().contains(applicationId)) {
- return tenant.getSessionRepo().getRemoteSession(applicationRepo.requireActiveSessionOf(applicationId));
+ return tenant.getRemoteSessionRepo().getSession(applicationRepo.requireActiveSessionOf(applicationId));
}
return null;
}
@@ -812,7 +813,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
public LocalSession getActiveLocalSession(Tenant tenant, ApplicationId applicationId) {
TenantApplications applicationRepo = tenant.getApplicationRepo();
if (applicationRepo.activeApplications().contains(applicationId)) {
- return tenant.getSessionRepository().getSession(applicationRepo.requireActiveSessionOf(applicationId));
+ return tenant.getLocalSessionRepo().getSession(applicationRepo.requireActiveSessionOf(applicationId));
}
return null;
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java
index d4fe35c14b1..a4dfec708d6 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java
@@ -213,7 +213,7 @@ public class TenantApplications implements RequestHandler, ReloadHandler, HostVa
break;
}
// We may have lost events and may need to remove applications.
- // New applications are added when session is added, not here. See SessionRepository.
+ // New applications are added when session is added, not here. See RemoteSessionRepo.
removeUnusedApplications();
});
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionRepo.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionRepo.java
new file mode 100644
index 00000000000..e23552dee44
--- /dev/null
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionRepo.java
@@ -0,0 +1,151 @@
+// 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.provision.ApplicationId;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.path.Path;
+import com.yahoo.transaction.NestedTransaction;
+import com.yahoo.vespa.config.server.GlobalComponentRegistry;
+import com.yahoo.vespa.config.server.deploy.TenantFileSystemDirs;
+import com.yahoo.vespa.config.server.tenant.TenantRepository;
+import com.yahoo.vespa.config.server.zookeeper.ConfigCurator;
+import com.yahoo.vespa.curator.Curator;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * Contains state for the local instance of the configserver.
+ *
+ * @author Ulf Lilleengen
+ */
+public class LocalSessionRepo {
+
+ private static final Logger log = Logger.getLogger(LocalSessionRepo.class.getName());
+ private static final FilenameFilter sessionApplicationsFilter = (dir, name) -> name.matches("\\d+");
+
+ private final SessionCache<LocalSession> sessionCache;
+ private final Map<Long, LocalSessionStateWatcher> sessionStateWatchers = new HashMap<>();
+ private final Duration sessionLifetime;
+ private final Clock clock;
+ private final Curator curator;
+ private final Executor zkWatcherExecutor;
+ private final TenantFileSystemDirs tenantFileSystemDirs;
+
+ public LocalSessionRepo(TenantName tenantName, GlobalComponentRegistry componentRegistry, SessionFactory sessionFactory) {
+ sessionCache = new SessionCache<>();
+ this.clock = componentRegistry.getClock();
+ this.curator = componentRegistry.getCurator();
+ this.sessionLifetime = Duration.ofSeconds(componentRegistry.getConfigserverConfig().sessionLifetime());
+ this.zkWatcherExecutor = command -> componentRegistry.getZkWatcherExecutor().execute(tenantName, command);
+ this.tenantFileSystemDirs = new TenantFileSystemDirs(componentRegistry.getConfigServerDB(), tenantName);
+ loadSessions(sessionFactory);
+ }
+
+ public synchronized void addSession(LocalSession session) {
+ sessionCache.addSession(session);
+ Path sessionsPath = TenantRepository.getSessionsPath(session.getTenantName());
+ long sessionId = session.getSessionId();
+ Curator.FileCache fileCache = curator.createFileCache(sessionsPath.append(String.valueOf(sessionId)).append(ConfigCurator.SESSIONSTATE_ZK_SUBPATH).getAbsolute(), false);
+ sessionStateWatchers.put(sessionId, new LocalSessionStateWatcher(fileCache, session, this, zkWatcherExecutor));
+ }
+
+ public LocalSession getSession(long sessionId) {
+ return sessionCache.getSession(sessionId);
+ }
+
+ public List<LocalSession> getSessions() {
+ return sessionCache.getSessions();
+ }
+
+ private void loadSessions(SessionFactory sessionFactory) {
+ File[] sessions = tenantFileSystemDirs.sessionsPath().listFiles(sessionApplicationsFilter);
+ if (sessions == null) {
+ return;
+ }
+ for (File session : sessions) {
+ try {
+ addSession(sessionFactory.createSessionFromId(Long.parseLong(session.getName())));
+ } catch (IllegalArgumentException e) {
+ log.log(Level.WARNING, "Could not load session '" +
+ session.getAbsolutePath() + "':" + e.getMessage() + ", skipping it.");
+ }
+ }
+ }
+
+ public void deleteExpiredSessions(Map<ApplicationId, Long> activeSessions) {
+ log.log(Level.FINE, "Purging old sessions");
+ try {
+ for (LocalSession candidate : sessionCache.getSessions()) {
+ Instant createTime = candidate.getCreateTime();
+ log.log(Level.FINE, "Candidate session for deletion: " + candidate.getSessionId() + ", created: " + createTime);
+
+ // Sessions with state other than ACTIVATED
+ if (hasExpired(candidate) && !isActiveSession(candidate)) {
+ deleteSession(candidate);
+ } else if (createTime.plus(Duration.ofDays(1)).isBefore(clock.instant())) {
+ // Sessions with state ACTIVATE, but which are not actually active
+ ApplicationId applicationId = candidate.getApplicationId();
+ Long activeSession = activeSessions.get(applicationId);
+ if (activeSession == null || activeSession != candidate.getSessionId()) {
+ deleteSession(candidate);
+ log.log(Level.INFO, "Deleted inactive session " + candidate.getSessionId() + " created " +
+ createTime + " for '" + applicationId + "'");
+ }
+ }
+ }
+ // Make sure to catch here, to avoid executor just dying in case of issues ...
+ } catch (Throwable e) {
+ log.log(Level.WARNING, "Error when purging old sessions ", e);
+ }
+ log.log(Level.FINE, "Done purging old sessions");
+ }
+
+ private boolean hasExpired(LocalSession candidate) {
+ return (candidate.getCreateTime().plus(sessionLifetime).isBefore(clock.instant()));
+ }
+
+ private boolean isActiveSession(LocalSession candidate) {
+ return candidate.getStatus() == Session.Status.ACTIVATE;
+ }
+
+ public void deleteSession(LocalSession session) {
+ long sessionId = session.getSessionId();
+ log.log(Level.FINE, "Deleting local session " + sessionId);
+ LocalSessionStateWatcher watcher = sessionStateWatchers.remove(sessionId);
+ if (watcher != null) watcher.close();
+ sessionCache.removeSession(sessionId);
+ NestedTransaction transaction = new NestedTransaction();
+ session.delete(transaction);
+ transaction.commit();
+ }
+
+ public void close() {
+ deleteAllSessions();
+ tenantFileSystemDirs.delete();
+ }
+
+ private void deleteAllSessions() {
+ List<LocalSession> sessions = new ArrayList<>(sessionCache.getSessions());
+ for (LocalSession session : sessions) {
+ deleteSession(session);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getSessions().toString();
+ }
+
+}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionStateWatcher.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionStateWatcher.java
index 1067a373f41..662094fc0ca 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionStateWatcher.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionStateWatcher.java
@@ -21,14 +21,14 @@ public class LocalSessionStateWatcher {
private final Curator.FileCache fileCache;
private final LocalSession session;
- private final SessionRepository sessionRepository;
+ private final LocalSessionRepo localSessionRepo;
private final Executor zkWatcherExecutor;
LocalSessionStateWatcher(Curator.FileCache fileCache, LocalSession session,
- SessionRepository sessionRepository, Executor zkWatcherExecutor) {
+ LocalSessionRepo localSessionRepo, Executor zkWatcherExecutor) {
this.fileCache = fileCache;
this.session = session;
- this.sessionRepository = sessionRepository;
+ this.localSessionRepo = localSessionRepo;
this.zkWatcherExecutor = zkWatcherExecutor;
this.fileCache.start();
this.fileCache.addListener(this::nodeChanged);
@@ -40,9 +40,9 @@ public class LocalSessionStateWatcher {
log.log(status == Session.Status.DELETE ? Level.INFO : Level.FINE,
session.logPre() + "Session change: Local session " + sessionId + " changed status to " + status);
- if (status.equals(Session.Status.DELETE) && sessionRepository.getSession(sessionId) != null) {
+ if (status.equals(Session.Status.DELETE) && localSessionRepo.getSession(sessionId) != null) {
log.log(Level.FINE, session.logPre() + "Deleting session " + sessionId);
- sessionRepository.deleteSession(session);
+ localSessionRepo.deleteSession(session);
}
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionRepo.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionRepo.java
new file mode 100644
index 00000000000..0e538b05931
--- /dev/null
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionRepo.java
@@ -0,0 +1,241 @@
+// 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.google.common.collect.HashMultiset;
+import com.google.common.collect.Multiset;
+import com.yahoo.concurrent.StripedExecutor;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.TenantName;
+
+import java.time.Clock;
+import java.util.logging.Level;
+import com.yahoo.path.Path;
+import com.yahoo.vespa.config.server.GlobalComponentRegistry;
+import com.yahoo.vespa.config.server.ReloadHandler;
+import com.yahoo.vespa.config.server.application.TenantApplications;
+import com.yahoo.vespa.config.server.monitoring.MetricUpdater;
+import com.yahoo.vespa.config.server.monitoring.Metrics;
+import com.yahoo.vespa.config.server.tenant.TenantRepository;
+import com.yahoo.vespa.config.server.zookeeper.ConfigCurator;
+import com.yahoo.vespa.curator.Curator;
+import com.yahoo.vespa.flags.BooleanFlag;
+import com.yahoo.vespa.flags.FlagSource;
+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 java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+/**
+ * Session repository for RemoteSessions. There is one such repo per tenant.
+ * Will watch/prepare sessions (applications) based on watched nodes in ZooKeeper. The zookeeper state watched in
+ * this class is shared between all config servers, so it should not modify any global state, because the operation
+ * will be performed on all servers. The repo can be regarded as read only from the POV of the configserver.
+ *
+ * @author Vegard Havdal
+ * @author Ulf Lilleengen
+ */
+public class RemoteSessionRepo {
+
+ private static final Logger log = Logger.getLogger(RemoteSessionRepo.class.getName());
+
+ private final Curator curator;
+ private final Path sessionsPath;
+ private final SessionFactory sessionFactory;
+ private final Map<Long, RemoteSessionStateWatcher> sessionStateWatchers = new HashMap<>();
+ private final ReloadHandler reloadHandler;
+ private final TenantName tenantName;
+ private final MetricUpdater metrics;
+ private final BooleanFlag distributeApplicationPackage;
+ private final Curator.DirectoryCache directoryCache;
+ private final TenantApplications applicationRepo;
+ private final Executor zkWatcherExecutor;
+ private final SessionCache<RemoteSession> sessionCache;
+
+ public RemoteSessionRepo(GlobalComponentRegistry componentRegistry,
+ SessionFactory sessionFactory,
+ ReloadHandler reloadHandler,
+ TenantName tenantName,
+ TenantApplications applicationRepo,
+ FlagSource flagSource) {
+ this.sessionCache = new SessionCache<>();
+ this.curator = componentRegistry.getCurator();
+ this.sessionsPath = TenantRepository.getSessionsPath(tenantName);
+ this.applicationRepo = applicationRepo;
+ this.sessionFactory = sessionFactory;
+ this.reloadHandler = reloadHandler;
+ this.tenantName = tenantName;
+ this.metrics = componentRegistry.getMetrics().getOrCreateMetricUpdater(Metrics.createDimensions(tenantName));
+ this.distributeApplicationPackage = Flags.CONFIGSERVER_DISTRIBUTE_APPLICATION_PACKAGE.bindTo(flagSource);
+ StripedExecutor<TenantName> zkWatcherExecutor = componentRegistry.getZkWatcherExecutor();
+ this.zkWatcherExecutor = command -> zkWatcherExecutor.execute(tenantName, command);
+ initializeSessions();
+ this.directoryCache = curator.createDirectoryCache(sessionsPath.getAbsolute(), false, false, componentRegistry.getZkCacheExecutor());
+ this.directoryCache.addListener(this::childEvent);
+ this.directoryCache.start();
+ }
+
+ public RemoteSession getSession(long sessionId) {
+ return sessionCache.getSession(sessionId);
+ }
+
+ public List<Long> getSessions() {
+ return getSessionList(curator.getChildren(sessionsPath));
+ }
+
+ public void addSession(RemoteSession session) {
+ sessionCache.addSession(session);
+ metrics.incAddedSessions();
+ }
+
+ public int deleteExpiredSessions(Clock clock, Duration expiryTime) {
+ int deleted = 0;
+ for (long sessionId : getSessions()) {
+ RemoteSession session = sessionCache.getSession(sessionId);
+ if (session == null) continue; // Internal sessions not in synch with zk, continue
+ if (session.getStatus() == Session.Status.ACTIVATE) continue;
+ if (sessionHasExpired(session.getCreateTime(), expiryTime, clock)) {
+ log.log(Level.INFO, "Remote session " + sessionId + " for " + tenantName + " has expired, deleting it");
+ session.delete();
+ deleted++;
+ }
+ }
+ return deleted;
+ }
+
+ private boolean sessionHasExpired(Instant created, Duration expiryTime, Clock clock) {
+ return (created.plus(expiryTime).isBefore(clock.instant()));
+ }
+
+ private List<Long> getSessionListFromDirectoryCache(List<ChildData> children) {
+ return getSessionList(children.stream()
+ .map(child -> Path.fromString(child.getPath()).getName())
+ .collect(Collectors.toList()));
+ }
+
+ private List<Long> getSessionList(List<String> children) {
+ return children.stream().map(Long::parseLong).collect(Collectors.toList());
+ }
+
+ private void initializeSessions() throws NumberFormatException {
+ getSessions().forEach(this::sessionAdded);
+ }
+
+ private synchronized void sessionsChanged() throws NumberFormatException {
+ List<Long> sessions = getSessionListFromDirectoryCache(directoryCache.getCurrentData());
+ checkForRemovedSessions(sessions);
+ checkForAddedSessions(sessions);
+ }
+
+ private void checkForRemovedSessions(List<Long> sessions) {
+ for (RemoteSession session : sessionCache.getSessions())
+ if ( ! sessions.contains(session.getSessionId()))
+ sessionRemoved(session.getSessionId());
+ }
+
+ private void checkForAddedSessions(List<Long> sessions) {
+ for (Long sessionId : sessions)
+ if (sessionCache.getSession(sessionId) == null)
+ sessionAdded(sessionId);
+ }
+
+ /**
+ * A session for which we don't have a watcher, i.e. hitherto unknown to us.
+ *
+ * @param sessionId session id for the new session
+ */
+ private void sessionAdded(long sessionId) {
+ log.log(Level.FINE, () -> "Adding session to RemoteSessionRepo: " + sessionId);
+ RemoteSession session = sessionFactory.createRemoteSession(sessionId);
+ Path sessionPath = sessionsPath.append(String.valueOf(sessionId));
+ Curator.FileCache fileCache = curator.createFileCache(sessionPath.append(ConfigCurator.SESSIONSTATE_ZK_SUBPATH).getAbsolute(), false);
+ fileCache.addListener(this::nodeChanged);
+ loadSessionIfActive(session);
+ addSession(session);
+ sessionStateWatchers.put(sessionId, new RemoteSessionStateWatcher(fileCache, reloadHandler, session, metrics, zkWatcherExecutor));
+ if (distributeApplicationPackage.value())
+ sessionFactory.createLocalSessionUsingDistributedApplicationPackage(sessionId);
+ }
+
+ private void sessionRemoved(long sessionId) {
+ RemoteSessionStateWatcher watcher = sessionStateWatchers.remove(sessionId);
+ if (watcher != null) watcher.close();
+ sessionCache.removeSession(sessionId);
+ metrics.incRemovedSessions();
+ }
+
+ private void loadSessionIfActive(RemoteSession session) {
+ for (ApplicationId applicationId : applicationRepo.activeApplications()) {
+ if (applicationRepo.requireActiveSessionOf(applicationId) == session.getSessionId()) {
+ log.log(Level.FINE, () -> "Found active application for session " + session.getSessionId() + " , loading it");
+ reloadHandler.reloadConfig(session.ensureApplicationLoaded());
+ log.log(Level.INFO, session.logPre() + "Application activated successfully: " + applicationId + " (generation " + session.getSessionId() + ")");
+ return;
+ }
+ }
+ }
+
+ public synchronized void close() {
+ try {
+ if (directoryCache != null) {
+ directoryCache.close();
+ }
+ } catch (Exception e) {
+ log.log(Level.WARNING, "Exception when closing path cache", e);
+ } finally {
+ checkForRemovedSessions(new ArrayList<>());
+ }
+ }
+
+ private void nodeChanged() {
+ zkWatcherExecutor.execute(() -> {
+ Multiset<Session.Status> sessionMetrics = HashMultiset.create();
+ for (RemoteSession session : sessionCache.getSessions()) {
+ sessionMetrics.add(session.getStatus());
+ }
+ metrics.setNewSessions(sessionMetrics.count(Session.Status.NEW));
+ metrics.setPreparedSessions(sessionMetrics.count(Session.Status.PREPARE));
+ metrics.setActivatedSessions(sessionMetrics.count(Session.Status.ACTIVATE));
+ metrics.setDeactivatedSessions(sessionMetrics.count(Session.Status.DEACTIVATE));
+ });
+ }
+
+ @SuppressWarnings("unused")
+ private void childEvent(CuratorFramework ignored, PathChildrenCacheEvent event) {
+ zkWatcherExecutor.execute(() -> {
+ log.log(Level.FINE, () -> "Got child event: " + event);
+ switch (event.getType()) {
+ case CHILD_ADDED:
+ sessionsChanged();
+ synchronizeOnNew(getSessionListFromDirectoryCache(Collections.singletonList(event.getData())));
+ break;
+ case CHILD_REMOVED:
+ sessionsChanged();
+ break;
+ case CONNECTION_RECONNECTED:
+ sessionsChanged();
+ break;
+ }
+ });
+ }
+
+ private void synchronizeOnNew(List<Long> sessionList) {
+ for (long sessionId : sessionList) {
+ RemoteSession session = sessionCache.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();
+ }
+ }
+
+}
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
deleted file mode 100644
index c1e883758c1..00000000000
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java
+++ /dev/null
@@ -1,337 +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.google.common.collect.HashMultiset;
-import com.google.common.collect.Multiset;
-import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.TenantName;
-import com.yahoo.path.Path;
-import com.yahoo.transaction.NestedTransaction;
-import com.yahoo.vespa.config.server.GlobalComponentRegistry;
-import com.yahoo.vespa.config.server.ReloadHandler;
-import com.yahoo.vespa.config.server.application.TenantApplications;
-import com.yahoo.vespa.config.server.deploy.TenantFileSystemDirs;
-import com.yahoo.vespa.config.server.monitoring.MetricUpdater;
-import com.yahoo.vespa.config.server.monitoring.Metrics;
-import com.yahoo.vespa.config.server.tenant.TenantRepository;
-import com.yahoo.vespa.config.server.zookeeper.ConfigCurator;
-import com.yahoo.vespa.curator.Curator;
-import org.apache.curator.framework.CuratorFramework;
-import org.apache.curator.framework.recipes.cache.ChildData;
-import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
-
-import java.io.File;
-import java.io.FilenameFilter;
-import java.time.Clock;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Executor;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import java.util.stream.Collectors;
-
-/**
- * Session repository for config server. Stores session state in zookeeper and file system. There are two
- * different session types (RemoteSession and LocalSession).
- *
- * @author Ulf Lilleengen
- * @author hmusum
- */
-public class SessionRepository {
-
- private static final Logger log = Logger.getLogger(SessionRepository.class.getName());
- private static final FilenameFilter sessionApplicationsFilter = (dir, name) -> name.matches("\\d+");
-
- private final SessionCache<LocalSession> localSessionCache = new SessionCache<>();
- private final SessionCache<RemoteSession> remoteSessionCache = new SessionCache<>();
- private final Map<Long, LocalSessionStateWatcher> localSessionStateWatchers = new HashMap<>();
- private final Map<Long, RemoteSessionStateWatcher> remoteSessionStateWatchers = new HashMap<>();
- private final Duration sessionLifetime;
- private final Clock clock;
- private final Curator curator;
- private final Executor zkWatcherExecutor;
- private final TenantFileSystemDirs tenantFileSystemDirs;
- private final ReloadHandler reloadHandler;
- private final MetricUpdater metrics;
- private final Curator.DirectoryCache directoryCache;
- private final TenantApplications applicationRepo;
- private final Path sessionsPath;
- private final SessionFactory sessionFactory;
- private final TenantName tenantName;
-
-
- public SessionRepository(TenantName tenantName, GlobalComponentRegistry componentRegistry,
- SessionFactory sessionFactory, TenantApplications applicationRepo,
- ReloadHandler reloadHandler) {
- this.tenantName = tenantName;
- this.clock = componentRegistry.getClock();
- this.curator = componentRegistry.getCurator();
- this.sessionLifetime = Duration.ofSeconds(componentRegistry.getConfigserverConfig().sessionLifetime());
- this.zkWatcherExecutor = command -> componentRegistry.getZkWatcherExecutor().execute(tenantName, command);
- this.tenantFileSystemDirs = new TenantFileSystemDirs(componentRegistry.getConfigServerDB(), tenantName);
- this.sessionFactory = sessionFactory;
- this.applicationRepo = applicationRepo;
- loadLocalSessions(sessionFactory);
-
- this.reloadHandler = reloadHandler;
- this.sessionsPath = TenantRepository.getSessionsPath(tenantName);
- this.metrics = componentRegistry.getMetrics().getOrCreateMetricUpdater(Metrics.createDimensions(tenantName));
- initializeRemoteSessions();
- this.directoryCache = curator.createDirectoryCache(sessionsPath.getAbsolute(), false, false, componentRegistry.getZkCacheExecutor());
- this.directoryCache.addListener(this::childEvent);
- this.directoryCache.start();
- }
-
- // ---------------- Local sessions ----------------------------------------------------------------
-
- public synchronized void addSession(LocalSession session) {
- localSessionCache.addSession(session);
- Path sessionsPath = TenantRepository.getSessionsPath(session.getTenantName());
- long sessionId = session.getSessionId();
- Curator.FileCache fileCache = curator.createFileCache(sessionsPath.append(String.valueOf(sessionId)).append(ConfigCurator.SESSIONSTATE_ZK_SUBPATH).getAbsolute(), false);
- localSessionStateWatchers.put(sessionId, new LocalSessionStateWatcher(fileCache, session, this, zkWatcherExecutor));
- }
-
- public LocalSession getSession(long sessionId) {
- return localSessionCache.getSession(sessionId);
- }
-
- public List<LocalSession> getSessions() {
- return localSessionCache.getSessions();
- }
-
- private void loadLocalSessions(SessionFactory sessionFactory) {
- File[] sessions = tenantFileSystemDirs.sessionsPath().listFiles(sessionApplicationsFilter);
- if (sessions == null) {
- return;
- }
- for (File session : sessions) {
- try {
- addSession(sessionFactory.createSessionFromId(Long.parseLong(session.getName())));
- } catch (IllegalArgumentException e) {
- log.log(Level.WARNING, "Could not load session '" +
- session.getAbsolutePath() + "':" + e.getMessage() + ", skipping it.");
- }
- }
- }
-
- public void deleteExpiredSessions(Map<ApplicationId, Long> activeSessions) {
- log.log(Level.FINE, "Purging old sessions");
- try {
- for (LocalSession candidate : localSessionCache.getSessions()) {
- Instant createTime = candidate.getCreateTime();
- log.log(Level.FINE, "Candidate session for deletion: " + candidate.getSessionId() + ", created: " + createTime);
-
- // Sessions with state other than ACTIVATED
- if (hasExpired(candidate) && !isActiveSession(candidate)) {
- deleteSession(candidate);
- } else if (createTime.plus(Duration.ofDays(1)).isBefore(clock.instant())) {
- // Sessions with state ACTIVATE, but which are not actually active
- ApplicationId applicationId = candidate.getApplicationId();
- Long activeSession = activeSessions.get(applicationId);
- if (activeSession == null || activeSession != candidate.getSessionId()) {
- deleteSession(candidate);
- log.log(Level.INFO, "Deleted inactive session " + candidate.getSessionId() + " created " +
- createTime + " for '" + applicationId + "'");
- }
- }
- }
- // Make sure to catch here, to avoid executor just dying in case of issues ...
- } catch (Throwable e) {
- log.log(Level.WARNING, "Error when purging old sessions ", e);
- }
- log.log(Level.FINE, "Done purging old sessions");
- }
-
- private boolean hasExpired(LocalSession candidate) {
- return (candidate.getCreateTime().plus(sessionLifetime).isBefore(clock.instant()));
- }
-
- private boolean isActiveSession(LocalSession candidate) {
- return candidate.getStatus() == Session.Status.ACTIVATE;
- }
-
- public void deleteSession(LocalSession session) {
- long sessionId = session.getSessionId();
- log.log(Level.FINE, "Deleting local session " + sessionId);
- LocalSessionStateWatcher watcher = localSessionStateWatchers.remove(sessionId);
- if (watcher != null) watcher.close();
- localSessionCache.removeSession(sessionId);
- NestedTransaction transaction = new NestedTransaction();
- session.delete(transaction);
- transaction.commit();
- }
-
- public void close() {
- deleteAllSessions();
- tenantFileSystemDirs.delete();
- try {
- if (directoryCache != null) {
- directoryCache.close();
- }
- } catch (Exception e) {
- log.log(Level.WARNING, "Exception when closing path cache", e);
- } finally {
- checkForRemovedSessions(new ArrayList<>());
- }
- }
-
- private void deleteAllSessions() {
- List<LocalSession> sessions = new ArrayList<>(localSessionCache.getSessions());
- for (LocalSession session : sessions) {
- deleteSession(session);
- }
- }
-
- // ---------------- Remote sessions ----------------------------------------------------------------
-
- public RemoteSession getRemoteSession(long sessionId) {
- return remoteSessionCache.getSession(sessionId);
- }
-
- public List<Long> getRemoteSessions() {
- return getRemoteSessionList(curator.getChildren(sessionsPath));
- }
-
- public void addRemoteSession(RemoteSession session) {
- remoteSessionCache.addSession(session);
- }
-
- public int deleteExpiredRemoteSessions(Clock clock, Duration expiryTime) {
- int deleted = 0;
- for (long sessionId : getRemoteSessions()) {
- RemoteSession session = remoteSessionCache.getSession(sessionId);
- if (session == null) continue; // Internal sessions not in synch with zk, continue
- if (session.getStatus() == Session.Status.ACTIVATE) continue;
- if (remoteSessionHasExpired(session.getCreateTime(), expiryTime, clock)) {
- log.log(Level.INFO, "Remote session " + sessionId + " for " + tenantName + " has expired, deleting it");
- session.delete();
- deleted++;
- }
- }
- return deleted;
- }
-
- private boolean remoteSessionHasExpired(Instant created, Duration expiryTime, Clock clock) {
- return (created.plus(expiryTime).isBefore(clock.instant()));
- }
-
- private List<Long> getRemoteSessionListFromDirectoryCache(List<ChildData> children) {
- return getRemoteSessionList(children.stream()
- .map(child -> Path.fromString(child.getPath()).getName())
- .collect(Collectors.toList()));
- }
-
- private List<Long> getRemoteSessionList(List<String> children) {
- return children.stream().map(Long::parseLong).collect(Collectors.toList());
- }
-
- private void initializeRemoteSessions() throws NumberFormatException {
- getRemoteSessions().forEach(this::sessionAdded);
- }
-
- private synchronized void remoteSessionsChanged() throws NumberFormatException {
- List<Long> sessions = getRemoteSessionListFromDirectoryCache(directoryCache.getCurrentData());
- checkForRemovedSessions(sessions);
- checkForAddedSessions(sessions);
- }
-
- private void checkForRemovedSessions(List<Long> sessions) {
- for (RemoteSession session : remoteSessionCache.getSessions())
- if (!sessions.contains(session.getSessionId()))
- sessionRemoved(session.getSessionId());
- }
-
- private void checkForAddedSessions(List<Long> sessions) {
- for (Long sessionId : sessions)
- if (remoteSessionCache.getSession(sessionId) == null)
- sessionAdded(sessionId);
- }
-
- /**
- * A session for which we don't have a watcher, i.e. hitherto unknown to us.
- *
- * @param sessionId session id for the new session
- */
- private void sessionAdded(long sessionId) {
- log.log(Level.FINE, () -> "Adding session " + sessionId);
- RemoteSession session = sessionFactory.createRemoteSession(sessionId);
- Path sessionPath = sessionsPath.append(String.valueOf(sessionId));
- Curator.FileCache fileCache = curator.createFileCache(sessionPath.append(ConfigCurator.SESSIONSTATE_ZK_SUBPATH).getAbsolute(), false);
- fileCache.addListener(this::nodeChanged);
- loadSessionIfActive(session);
- addRemoteSession(session);
- metrics.incAddedSessions();
- remoteSessionStateWatchers.put(sessionId, new RemoteSessionStateWatcher(fileCache, reloadHandler, session, metrics, zkWatcherExecutor));
- }
-
- private void sessionRemoved(long sessionId) {
- RemoteSessionStateWatcher watcher = remoteSessionStateWatchers.remove(sessionId);
- if (watcher != null) watcher.close();
- remoteSessionCache.removeSession(sessionId);
- metrics.incRemovedSessions();
- }
-
- private void loadSessionIfActive(RemoteSession session) {
- for (ApplicationId applicationId : applicationRepo.activeApplications()) {
- if (applicationRepo.requireActiveSessionOf(applicationId) == session.getSessionId()) {
- log.log(Level.FINE, () -> "Found active application for session " + session.getSessionId() + " , loading it");
- reloadHandler.reloadConfig(session.ensureApplicationLoaded());
- log.log(Level.INFO, session.logPre() + "Application activated successfully: " + applicationId + " (generation " + session.getSessionId() + ")");
- return;
- }
- }
- }
-
- private void nodeChanged() {
- zkWatcherExecutor.execute(() -> {
- Multiset<Session.Status> sessionMetrics = HashMultiset.create();
- for (RemoteSession session : remoteSessionCache.getSessions()) {
- sessionMetrics.add(session.getStatus());
- }
- metrics.setNewSessions(sessionMetrics.count(Session.Status.NEW));
- metrics.setPreparedSessions(sessionMetrics.count(Session.Status.PREPARE));
- metrics.setActivatedSessions(sessionMetrics.count(Session.Status.ACTIVATE));
- metrics.setDeactivatedSessions(sessionMetrics.count(Session.Status.DEACTIVATE));
- });
- }
-
- @SuppressWarnings("unused")
- private void childEvent(CuratorFramework ignored, PathChildrenCacheEvent event) {
- zkWatcherExecutor.execute(() -> {
- log.log(Level.FINE, () -> "Got child event: " + event);
- switch (event.getType()) {
- case CHILD_ADDED:
- remoteSessionsChanged();
- synchronizeOnNew(getRemoteSessionListFromDirectoryCache(Collections.singletonList(event.getData())));
- break;
- case CHILD_REMOVED:
- remoteSessionsChanged();
- break;
- case CONNECTION_RECONNECTED:
- remoteSessionsChanged();
- break;
- }
- });
- }
-
- private void synchronizeOnNew(List<Long> sessionList) {
- for (long sessionId : sessionList) {
- 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();
- }
- }
-
- @Override
- public String toString() {
- return getSessions().toString();
- }
-
-}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenant.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenant.java
index 0a539db4e43..f0aab8b2312 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenant.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenant.java
@@ -6,8 +6,9 @@ import com.yahoo.path.Path;
import com.yahoo.vespa.config.server.ReloadHandler;
import com.yahoo.vespa.config.server.RequestHandler;
import com.yahoo.vespa.config.server.application.TenantApplications;
+import com.yahoo.vespa.config.server.session.LocalSessionRepo;
+import com.yahoo.vespa.config.server.session.RemoteSessionRepo;
import com.yahoo.vespa.config.server.session.SessionFactory;
-import com.yahoo.vespa.config.server.session.SessionRepository;
import com.yahoo.vespa.curator.Curator;
import org.apache.zookeeper.data.Stat;
@@ -27,9 +28,10 @@ public class Tenant implements TenantHandlerProvider {
static final String APPLICATIONS = "applications";
private final TenantName name;
+ private final RemoteSessionRepo remoteSessionRepo;
private final Path path;
private final SessionFactory sessionFactory;
- private final SessionRepository sessionRepository;
+ private final LocalSessionRepo localSessionRepo;
private final TenantApplications applicationRepo;
private final RequestHandler requestHandler;
private final ReloadHandler reloadHandler;
@@ -37,7 +39,8 @@ public class Tenant implements TenantHandlerProvider {
Tenant(TenantName name,
SessionFactory sessionFactory,
- SessionRepository sessionRepository,
+ LocalSessionRepo localSessionRepo,
+ RemoteSessionRepo remoteSessionRepo,
RequestHandler requestHandler,
ReloadHandler reloadHandler,
TenantApplications applicationRepo,
@@ -46,8 +49,9 @@ public class Tenant implements TenantHandlerProvider {
this.path = TenantRepository.getTenantPath(name);
this.requestHandler = requestHandler;
this.reloadHandler = reloadHandler;
+ this.remoteSessionRepo = remoteSessionRepo;
this.sessionFactory = sessionFactory;
- this.sessionRepository = sessionRepository;
+ this.localSessionRepo = localSessionRepo;
this.applicationRepo = applicationRepo;
this.curator = curator;
}
@@ -70,8 +74,13 @@ public class Tenant implements TenantHandlerProvider {
return requestHandler;
}
- public SessionRepository getSessionRepo() {
- return sessionRepository;
+ /**
+ * The RemoteSessionRepo for this
+ *
+ * @return repo
+ */
+ public RemoteSessionRepo getRemoteSessionRepo() {
+ return remoteSessionRepo;
}
public TenantName getName() {
@@ -86,8 +95,8 @@ public class Tenant implements TenantHandlerProvider {
return sessionFactory;
}
- public SessionRepository getSessionRepository() {
- return sessionRepository;
+ public LocalSessionRepo getLocalSessionRepo() {
+ return localSessionRepo;
}
@Override
@@ -131,9 +140,9 @@ public class Tenant implements TenantHandlerProvider {
* Called by watchers as a reaction to {@link #delete()}.
*/
void close() {
- sessionRepository.close(); // Closes watchers and clears memory.
+ remoteSessionRepo.close(); // Closes watchers and clears memory.
applicationRepo.close(); // Closes watchers.
- sessionRepository.close(); // Closes watchers, clears memory, and deletes local files and ZK session state.
+ localSessionRepo.close(); // Closes watchers, clears memory, and deletes local files and ZK session state.
}
/** Deletes the tenant tree from ZooKeeper (application and session status for the tenant) and triggers {@link #close()}. */
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRepository.java
index 841407817c6..d34f89a179b 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRepository.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRepository.java
@@ -14,7 +14,8 @@ import com.yahoo.vespa.config.server.RequestHandler;
import com.yahoo.vespa.config.server.application.TenantApplications;
import com.yahoo.vespa.config.server.deploy.TenantFileSystemDirs;
import com.yahoo.vespa.config.server.monitoring.MetricUpdater;
-import com.yahoo.vespa.config.server.session.SessionRepository;
+import com.yahoo.vespa.config.server.session.LocalSessionRepo;
+import com.yahoo.vespa.config.server.session.RemoteSessionRepo;
import com.yahoo.vespa.config.server.session.SessionFactory;
import com.yahoo.vespa.curator.Curator;
import org.apache.curator.framework.CuratorFramework;
@@ -48,7 +49,7 @@ import java.util.stream.Collectors;
* This component will monitor the set of tenants in the config server by watching in ZooKeeper.
* It will set up Tenant objects accordingly, which will manage the config sessions per tenant.
* This class will read the preexisting set of tenants from ZooKeeper at startup. (For now it will also
- * create a default tenant since that will be used for APIs that do no know about tenants or have not yet
+ * create a default tenant since that will be used for API that do no know about tenants or have not yet
* implemented support for it).
*
* This instance is called from two different threads, the http handler threads and the zookeeper watcher threads.
@@ -223,10 +224,15 @@ public class TenantRepository {
if (reloadHandler == null)
reloadHandler = applicationRepo;
SessionFactory sessionFactory = new SessionFactory(componentRegistry, applicationRepo, applicationRepo, tenantName);
- SessionRepository sessionRepository = new SessionRepository(tenantName, componentRegistry, sessionFactory,
- applicationRepo, reloadHandler);
+ LocalSessionRepo localSessionRepo = new LocalSessionRepo(tenantName, componentRegistry, sessionFactory);
+ RemoteSessionRepo remoteSessionRepo = new RemoteSessionRepo(componentRegistry,
+ sessionFactory,
+ reloadHandler,
+ tenantName,
+ applicationRepo,
+ componentRegistry.getFlagSource());
log.log(Level.INFO, "Creating tenant '" + tenantName + "'");
- Tenant tenant = new Tenant(tenantName, sessionFactory, sessionRepository, requestHandler,
+ Tenant tenant = new Tenant(tenantName, sessionFactory, localSessionRepo, remoteSessionRepo, requestHandler,
reloadHandler, applicationRepo, componentRegistry.getCurator());
notifyNewTenant(tenant);
tenants.putIfAbsent(tenantName, tenant);
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java
index ab75f64c92c..a3d072be38b 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java
@@ -37,7 +37,7 @@ import com.yahoo.vespa.config.server.http.InternalServerException;
import com.yahoo.vespa.config.server.http.SessionHandlerTest;
import com.yahoo.vespa.config.server.http.v2.PrepareResult;
import com.yahoo.vespa.config.server.session.LocalSession;
-import com.yahoo.vespa.config.server.session.SessionRepository;
+import com.yahoo.vespa.config.server.session.LocalSessionRepo;
import com.yahoo.vespa.config.server.session.PrepareParams;
import com.yahoo.vespa.config.server.session.RemoteSession;
import com.yahoo.vespa.config.server.session.Session;
@@ -146,7 +146,7 @@ public class ApplicationRepositoryTest {
TenantName tenantName = applicationId().tenant();
Tenant tenant = tenantRepository.getTenant(tenantName);
- LocalSession session = tenant.getSessionRepository().getSession(tenant.getApplicationRepo()
+ LocalSession session = tenant.getLocalSessionRepo().getSession(tenant.getApplicationRepo()
.requireActiveSessionOf(applicationId()));
session.getAllocatedHosts();
}
@@ -178,7 +178,7 @@ public class ApplicationRepositoryTest {
TenantName tenantName = applicationId().tenant();
Tenant tenant = tenantRepository.getTenant(tenantName);
- LocalSession session = tenant.getSessionRepository().getSession(
+ LocalSession session = tenant.getLocalSessionRepo().getSession(
tenant.getApplicationRepo().requireActiveSessionOf(applicationId()));
assertEquals(firstSessionId, session.getMetaData().getPreviousActiveGeneration());
}
@@ -295,17 +295,17 @@ public class ApplicationRepositoryTest {
PrepareResult result = deployApp(testApp);
long sessionId = result.sessionId();
Tenant tenant = tenantRepository.getTenant(applicationId().tenant());
- LocalSession applicationData = tenant.getSessionRepository().getSession(sessionId);
+ LocalSession applicationData = tenant.getLocalSessionRepo().getSession(sessionId);
assertNotNull(applicationData);
assertNotNull(applicationData.getApplicationId());
- assertNotNull(tenant.getSessionRepo().getRemoteSession(sessionId));
+ assertNotNull(tenant.getRemoteSessionRepo().getSession(sessionId));
assertNotNull(applicationRepository.getActiveSession(applicationId()));
// Delete app and verify that it has been deleted from repos and provisioner
assertTrue(applicationRepository.delete(applicationId()));
assertNull(applicationRepository.getActiveSession(applicationId()));
- assertNull(tenant.getSessionRepository().getSession(sessionId));
- assertNull(tenant.getSessionRepo().getRemoteSession(sessionId));
+ assertNull(tenant.getLocalSessionRepo().getSession(sessionId));
+ assertNull(tenant.getRemoteSessionRepo().getSession(sessionId));
assertTrue(provisioner.removed);
assertEquals(tenant.getName(), provisioner.lastApplicationId.tenant());
assertEquals(applicationId(), provisioner.lastApplicationId);
@@ -346,7 +346,7 @@ public class ApplicationRepositoryTest {
RemoteSession activeSession = applicationRepository.getActiveSession(applicationId());
assertNull(activeSession);
Tenant tenant = tenantRepository.getTenant(applicationId().tenant());
- assertNull(tenant.getSessionRepo().getRemoteSession(prepareResult.sessionId()));
+ assertNull(tenant.getRemoteSessionRepo().getSession(prepareResult.sessionId()));
assertTrue(applicationRepository.delete(applicationId()));
}
@@ -379,14 +379,14 @@ public class ApplicationRepositoryTest {
assertNotEquals(activeSessionId, deployment3session);
// No change to active session id
assertEquals(activeSessionId, tester.tenant().getApplicationRepo().requireActiveSessionOf(tester.applicationId()));
- SessionRepository sessionRepository = tester.tenant().getSessionRepository();
- assertEquals(3, sessionRepository.getSessions().size());
+ LocalSessionRepo localSessionRepo = tester.tenant().getLocalSessionRepo();
+ assertEquals(3, localSessionRepo.getSessions().size());
clock.advance(Duration.ofHours(1)); // longer than session lifetime
// All sessions except 3 should be removed after the call to deleteExpiredLocalSessions
tester.applicationRepository().deleteExpiredLocalSessions();
- Collection<LocalSession> sessions = sessionRepository.getSessions();
+ Collection<LocalSession> sessions = localSessionRepo.getSessions();
assertEquals(1, sessions.size());
ArrayList<LocalSession> localSessions = new ArrayList<>(sessions);
LocalSession localSession = localSessions.get(0);
@@ -400,9 +400,9 @@ public class ApplicationRepositoryTest {
assertTrue(deployment4.isPresent());
deployment4.get().prepare(); // session 5 (not activated)
- assertEquals(2, sessionRepository.getSessions().size());
- sessionRepository.deleteSession(localSession);
- assertEquals(1, sessionRepository.getSessions().size());
+ assertEquals(2, localSessionRepo.getSessions().size());
+ localSessionRepo.deleteSession(localSession);
+ assertEquals(1, localSessionRepo.getSessions().size());
// Check that trying to expire when there are no active sessions works
tester.applicationRepository().deleteExpiredLocalSessions();
@@ -457,7 +457,7 @@ public class ApplicationRepositoryTest {
TenantName tenantName = applicationId().tenant();
Tenant tenant = tenantRepository.getTenant(tenantName);
- LocalSession session = tenant.getSessionRepository().getSession(tenant.getApplicationRepo().requireActiveSessionOf(applicationId()));
+ LocalSession session = tenant.getLocalSessionRepo().getSession(tenant.getApplicationRepo().requireActiveSessionOf(applicationId()));
List<NetworkPorts.Allocation> list = new ArrayList<>();
list.add(new NetworkPorts.Allocation(8080, "container", "container/container.0", "http"));
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java
index bc9f45698e9..b2091d6e537 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java
@@ -249,7 +249,7 @@ public class DeployTester {
public AllocatedHosts getAllocatedHostsOf(ApplicationId applicationId) {
Tenant tenant = tenant();
- LocalSession session = tenant.getSessionRepository().getSession(tenant.getApplicationRepo()
+ LocalSession session = tenant.getLocalSessionRepo().getSession(tenant.getApplicationRepo()
.requireActiveSessionOf(applicationId));
return session.getAllocatedHosts();
}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java
index 156200a061c..36467a2ca64 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java
@@ -33,11 +33,11 @@ public class RedeployTest {
assertTrue(deployment.isPresent());
long activeSessionIdBefore = tester.applicationRepository().getActiveSession(tester.applicationId()).getSessionId();
- assertEquals(tester.applicationId(), tester.tenant().getSessionRepository().getSession(activeSessionIdBefore).getApplicationId());
+ assertEquals(tester.applicationId(), tester.tenant().getLocalSessionRepo().getSession(activeSessionIdBefore).getApplicationId());
deployment.get().activate();
long activeSessionIdAfter = tester.applicationRepository().getActiveSession(tester.applicationId()).getSessionId();
assertEquals(activeSessionIdAfter, activeSessionIdBefore + 1);
- assertEquals(tester.applicationId(), tester.tenant().getSessionRepository().getSession(activeSessionIdAfter).getApplicationId());
+ assertEquals(tester.applicationId(), tester.tenant().getLocalSessionRepo().getSession(activeSessionIdAfter).getApplicationId());
}
/** No deployment is done because there is no local active session. */
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationContentHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationContentHandlerTest.java
index 4cf81d22e3c..078dc47af51 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationContentHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationContentHandlerTest.java
@@ -54,13 +54,13 @@ public class ApplicationContentHandlerTest extends ContentHandlerTestBase {
session2 = new MockLocalSession(2, FilesApplicationPackage.fromFile(new File("src/test/apps/content")));
Tenant tenant1 = tenantRepository.getTenant(tenantName1);
- tenant1.getSessionRepository().addSession(session2);
+ tenant1.getLocalSessionRepo().addSession(session2);
tenant1.getApplicationRepo().createApplication(idTenant1);
tenant1.getApplicationRepo().createPutTransaction(idTenant1, 2).commit();
MockLocalSession session3 = new MockLocalSession(3, FilesApplicationPackage.fromFile(new File("src/test/apps/content2")));
Tenant tenant2 = tenantRepository.getTenant(tenantName2);
- tenant2.getSessionRepository().addSession(session3);
+ tenant2.getLocalSessionRepo().addSession(session3);
tenant2.getApplicationRepo().createApplication(idTenant2);
tenant2.getApplicationRepo().createPutTransaction(idTenant2, 3).commit();
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 ec141a61ee6..ff8f7a291ad 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
@@ -286,13 +286,13 @@ public class ApplicationHandlerTest {
private void deleteAndAssertOKResponseMocked(ApplicationId applicationId, boolean fullAppIdInUrl) throws IOException {
long sessionId = tenantRepository.getTenant(applicationId.tenant()).getApplicationRepo().requireActiveSessionOf(applicationId);
deleteAndAssertResponse(applicationId, Zone.defaultZone(), Response.Status.OK, null, fullAppIdInUrl);
- assertNull(tenantRepository.getTenant(applicationId.tenant()).getSessionRepository().getSession(sessionId));
+ assertNull(tenantRepository.getTenant(applicationId.tenant()).getLocalSessionRepo().getSession(sessionId));
}
private void deleteAndAssertOKResponse(Tenant tenant, ApplicationId applicationId) throws IOException {
long sessionId = tenant.getApplicationRepo().requireActiveSessionOf(applicationId);
deleteAndAssertResponse(applicationId, Zone.defaultZone(), Response.Status.OK, null, true);
- assertNull(tenant.getSessionRepository().getSession(sessionId));
+ assertNull(tenant.getLocalSessionRepo().getSession(sessionId));
}
private void deleteAndAssertResponse(ApplicationId applicationId, Zone zone, int expectedStatus, HttpErrorResponse.errorCodes errorCode, boolean fullAppIdInUrl) throws IOException {
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HostHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HostHandlerTest.java
index 457acf8c376..2eaa5d75ba7 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HostHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HostHandlerTest.java
@@ -52,11 +52,11 @@ public class HostHandlerTest {
tenant.getApplicationRepo().createApplication(applicationId);
tenant.getApplicationRepo().createPutTransaction(applicationId, sessionId).commit();
ApplicationPackage app = FilesApplicationPackage.fromFile(testApp);
- tenant.getSessionRepository().addSession(new SessionHandlerTest.MockLocalSession(sessionId, app, applicationId));
+ tenant.getLocalSessionRepo().addSession(new SessionHandlerTest.MockLocalSession(sessionId, app, applicationId));
TestComponentRegistry componentRegistry = new TestComponentRegistry.Builder()
.modelFactoryRegistry(new ModelFactoryRegistry(Collections.singletonList(new VespaModelFactory(new NullConfigModelRegistry()))))
.build();
- tenant.getSessionRepo().addRemoteSession(new RemoteSession(tenant.getName(), sessionId, componentRegistry, new MockSessionZKClient(app)));
+ tenant.getRemoteSessionRepo().addSession(new RemoteSession(tenant.getName(), sessionId, componentRegistry, new MockSessionZKClient(app)));
}
@Before
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java
index f639843ac08..88bf6fb7172 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java
@@ -43,7 +43,7 @@ public class SessionContentHandlerTest extends ContentHandlerTestBase {
public void setupHandler() throws Exception {
tenantRepository = new TenantRepository(componentRegistry, false);
tenantRepository.addTenant(tenant);
- tenantRepository.getTenant(tenant).getSessionRepository().addSession(new MockLocalSession(1L, FilesApplicationPackage.fromFile(createTestApp())));
+ tenantRepository.getTenant(tenant).getLocalSessionRepo().addSession(new MockLocalSession(1L, FilesApplicationPackage.fromFile(createTestApp())));
handler = createHandler();
pathPrefix = "/application/v2/tenant/" + tenant + "/session/";
baseUrl = "http://foo:1337/application/v2/tenant/" + tenant + "/session/1/content/";
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionRepoTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionRepoTest.java
new file mode 100644
index 00000000000..a758698d3b5
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionRepoTest.java
@@ -0,0 +1,106 @@
+// 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.config.model.application.provider.FilesApplicationPackage;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.io.IOUtils;
+import com.yahoo.vespa.config.server.GlobalComponentRegistry;
+import com.yahoo.vespa.config.server.MockReloadHandler;
+import com.yahoo.vespa.config.server.TestComponentRegistry;
+import com.yahoo.vespa.config.server.application.TenantApplications;
+import com.yahoo.vespa.config.server.host.HostRegistry;
+import com.yahoo.vespa.config.server.http.SessionHandlerTest;
+import com.yahoo.vespa.curator.mock.MockCurator;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+/**
+ * @author Ulf Lilleengen
+ */
+public class LocalSessionRepoTest {
+
+ private File testApp = new File("src/test/apps/app");
+ private LocalSessionRepo repo;
+ private static final TenantName tenantName = TenantName.defaultName();
+
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ @Before
+ public void setupSessions() throws Exception {
+ setupSessions(tenantName, true);
+ }
+
+ private void setupSessions(TenantName tenantName, boolean createInitialSessions) throws Exception {
+ File configserverDbDir = temporaryFolder.newFolder().getAbsoluteFile();
+ if (createInitialSessions) {
+ Path sessionsPath = Paths.get(configserverDbDir.getAbsolutePath(), "tenants", tenantName.value(), "sessions");
+ IOUtils.copyDirectory(testApp, sessionsPath.resolve("1").toFile());
+ IOUtils.copyDirectory(testApp, sessionsPath.resolve("2").toFile());
+ IOUtils.copyDirectory(testApp, sessionsPath.resolve("3").toFile());
+ }
+ GlobalComponentRegistry globalComponentRegistry = new TestComponentRegistry.Builder()
+ .curator(new MockCurator())
+ .configServerConfig(new ConfigserverConfig.Builder()
+ .configServerDBDir(configserverDbDir.getAbsolutePath())
+ .configDefinitionsDir(temporaryFolder.newFolder().getAbsolutePath())
+ .sessionLifetime(5)
+ .build())
+ .build();
+ SessionFactory sessionFactory = new SessionFactory(globalComponentRegistry,
+ TenantApplications.create(globalComponentRegistry, tenantName),
+ new HostRegistry<>(),
+ tenantName);
+ repo = new LocalSessionRepo(tenantName, globalComponentRegistry, sessionFactory);
+ }
+
+ @Test
+ public void require_that_sessions_can_be_loaded_from_disk() {
+ assertNotNull(repo.getSession(1L));
+ assertNotNull(repo.getSession(2L));
+ assertNotNull(repo.getSession(3L));
+ assertNull(repo.getSession(4L));
+ }
+
+ @Test
+ public void require_that_all_sessions_are_deleted() {
+ repo.close();
+ assertNull(repo.getSession(1L));
+ assertNull(repo.getSession(2L));
+ assertNull(repo.getSession(3L));
+ }
+
+ @Test
+ public void require_that_sessions_belong_to_a_tenant() {
+ // tenant is "default"
+ assertNotNull(repo.getSession(1L));
+ assertNotNull(repo.getSession(2L));
+ assertNotNull(repo.getSession(3L));
+ assertNull(repo.getSession(4L));
+
+ // tenant is "newTenant"
+ try {
+ setupSessions(TenantName.from("newTenant"), false);
+ } catch (Exception e) {
+ fail();
+ }
+ assertNull(repo.getSession(1L));
+
+ repo.addSession(new SessionHandlerTest.MockLocalSession(1L, FilesApplicationPackage.fromFile(testApp)));
+ repo.addSession(new SessionHandlerTest.MockLocalSession(2L, FilesApplicationPackage.fromFile(testApp)));
+ assertNotNull(repo.getSession(1L));
+ assertNotNull(repo.getSession(2L));
+ assertNull(repo.getSession(3L));
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionRepoTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionRepoTest.java
new file mode 100644
index 00000000000..468dd5a15a7
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionRepoTest.java
@@ -0,0 +1,145 @@
+// 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.provision.TenantName;
+import com.yahoo.path.Path;
+import com.yahoo.text.Utf8;
+import com.yahoo.vespa.config.server.TestComponentRegistry;
+import com.yahoo.vespa.config.server.tenant.Tenant;
+import com.yahoo.vespa.config.server.tenant.TenantRepository;
+import com.yahoo.vespa.config.server.zookeeper.ConfigCurator;
+import com.yahoo.vespa.curator.Curator;
+import com.yahoo.vespa.curator.mock.MockCurator;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.function.LongPredicate;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Ulf Lilleengen
+ */
+public class RemoteSessionRepoTest {
+
+ private static final TenantName tenantName = TenantName.defaultName();
+
+ private RemoteSessionRepo remoteSessionRepo;
+ private Curator curator;
+ TenantRepository tenantRepository;
+
+ @Before
+ public void setupFacade() {
+ curator = new MockCurator();
+ TestComponentRegistry componentRegistry = new TestComponentRegistry.Builder()
+ .curator(curator)
+ .build();
+ tenantRepository = new TenantRepository(componentRegistry, false);
+ tenantRepository.addTenant(tenantName);
+ this.remoteSessionRepo = tenantRepository.getTenant(tenantName).getRemoteSessionRepo();
+ curator.create(TenantRepository.getTenantPath(tenantName).append("/applications"));
+ curator.create(TenantRepository.getSessionsPath(tenantName));
+ createSession(1L, false);
+ createSession(2L, false);
+ }
+
+ private void createSession(long sessionId, boolean wait) {
+ createSession(sessionId, wait, tenantName);
+ }
+
+ private void createSession(long sessionId, boolean wait, TenantName tenantName) {
+ Path sessionsPath = TenantRepository.getSessionsPath(tenantName);
+ SessionZooKeeperClient zkc = new SessionZooKeeperClient(curator, sessionsPath.append(String.valueOf(sessionId)));
+ zkc.createNewSession(Instant.now());
+ if (wait) {
+ Curator.CompletionWaiter waiter = zkc.getUploadWaiter();
+ waiter.awaitCompletion(Duration.ofSeconds(120));
+ }
+ }
+
+ @Test
+ public void testInitialize() {
+ assertSessionExists(1L);
+ assertSessionExists(2L);
+ }
+
+ @Test
+ public void testCreateSession() {
+ createSession(3L, true);
+ assertSessionExists(3L);
+ }
+
+ @Test
+ public void testSessionStateChange() throws Exception {
+ long sessionId = 3L;
+ createSession(sessionId, true);
+ assertSessionStatus(sessionId, Session.Status.NEW);
+ assertStatusChange(sessionId, Session.Status.PREPARE);
+ assertStatusChange(sessionId, Session.Status.ACTIVATE);
+
+ Path session = TenantRepository.getSessionsPath(tenantName).append("" + sessionId);
+ curator.delete(session);
+ assertSessionRemoved(sessionId);
+ assertNull(remoteSessionRepo.getSession(sessionId));
+ }
+
+ // If reading a session throws an exception it should be handled and not prevent other applications
+ // from loading. In this test we just show that we end up with one session in remote session
+ // repo even if it had bad data (by making getSessionIdForApplication() in FailingTenantApplications
+ // throw an exception).
+ @Test
+ public void testBadApplicationRepoOnActivate() {
+ long sessionId = 3L;
+ TenantName mytenant = TenantName.from("mytenant");
+ curator.set(TenantRepository.getApplicationsPath(mytenant).append("mytenant:appX:default"), new byte[0]); // Invalid data
+ tenantRepository.addTenant(mytenant);
+ Tenant tenant = tenantRepository.getTenant(mytenant);
+ curator.create(TenantRepository.getSessionsPath(mytenant));
+ remoteSessionRepo = tenant.getRemoteSessionRepo();
+ assertThat(remoteSessionRepo.getSessions().size(), is(0));
+ createSession(sessionId, true, mytenant);
+ assertThat(remoteSessionRepo.getSessions().size(), is(1));
+ }
+
+ private void assertStatusChange(long sessionId, Session.Status status) throws Exception {
+ Path statePath = TenantRepository.getSessionsPath(tenantName).append("" + sessionId).append(ConfigCurator.SESSIONSTATE_ZK_SUBPATH);
+ curator.create(statePath);
+ curator.framework().setData().forPath(statePath.getAbsolute(), Utf8.toBytes(status.toString()));
+ assertSessionStatus(sessionId, status);
+ }
+
+ private void assertSessionRemoved(long sessionId) {
+ waitFor(p -> remoteSessionRepo.getSession(sessionId) == null, sessionId);
+ assertNull(remoteSessionRepo.getSession(sessionId));
+ }
+
+ private void assertSessionExists(long sessionId) {
+ assertSessionStatus(sessionId, Session.Status.NEW);
+ }
+
+ private void assertSessionStatus(long sessionId, Session.Status status) {
+ waitFor(p -> remoteSessionRepo.getSession(sessionId) != null &&
+ remoteSessionRepo.getSession(sessionId).getStatus() == status, sessionId);
+ assertNotNull(remoteSessionRepo.getSession(sessionId));
+ assertThat(remoteSessionRepo.getSession(sessionId).getStatus(), is(status));
+ }
+
+ private void waitFor(LongPredicate predicate, long sessionId) {
+ long endTime = System.currentTimeMillis() + 60_000;
+ boolean ok;
+ do {
+ ok = predicate.test(sessionId);
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ } while (System.currentTimeMillis() < endTime && !ok);
+ }
+
+}
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
deleted file mode 100644
index dbd71bed581..00000000000
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionRepositoryTest.java
+++ /dev/null
@@ -1,221 +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.config.model.application.provider.FilesApplicationPackage;
-import com.yahoo.config.provision.TenantName;
-import com.yahoo.io.IOUtils;
-import com.yahoo.text.Utf8;
-import com.yahoo.vespa.config.server.GlobalComponentRegistry;
-import com.yahoo.vespa.config.server.TestComponentRegistry;
-import com.yahoo.vespa.config.server.application.TenantApplications;
-import com.yahoo.vespa.config.server.host.HostRegistry;
-import com.yahoo.vespa.config.server.http.SessionHandlerTest;
-import com.yahoo.vespa.config.server.tenant.Tenant;
-import com.yahoo.vespa.config.server.tenant.TenantRepository;
-import com.yahoo.vespa.config.server.zookeeper.ConfigCurator;
-import com.yahoo.vespa.curator.Curator;
-import com.yahoo.vespa.curator.mock.MockCurator;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-
-import java.io.File;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.function.LongPredicate;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
-
-/**
- * @author Ulf Lilleengen
- */
-public class SessionRepositoryTest {
-
- private File testApp = new File("src/test/apps/app");
- private MockCurator curator;
- private SessionRepository sessionRepository;
- private TenantRepository tenantRepository;
- private static final TenantName tenantName = TenantName.defaultName();
-
- @Rule
- public TemporaryFolder temporaryFolder = new TemporaryFolder();
-
- @Before
- public void setupSessions() throws Exception {
- setupSessions(tenantName, true);
- }
-
- private void setupSessions(TenantName tenantName, boolean createInitialSessions) throws Exception {
- File configserverDbDir = temporaryFolder.newFolder().getAbsoluteFile();
- if (createInitialSessions) {
- Path sessionsPath = Paths.get(configserverDbDir.getAbsolutePath(), "tenants", tenantName.value(), "sessions");
- IOUtils.copyDirectory(testApp, sessionsPath.resolve("1").toFile());
- IOUtils.copyDirectory(testApp, sessionsPath.resolve("2").toFile());
- IOUtils.copyDirectory(testApp, sessionsPath.resolve("3").toFile());
- }
- curator = new MockCurator();
- curator.create(TenantRepository.getTenantPath(tenantName).append("/applications"));
- curator.create(TenantRepository.getSessionsPath(tenantName));
- GlobalComponentRegistry globalComponentRegistry = new TestComponentRegistry.Builder()
- .curator(curator)
- .configServerConfig(new ConfigserverConfig.Builder()
- .configServerDBDir(configserverDbDir.getAbsolutePath())
- .configDefinitionsDir(temporaryFolder.newFolder().getAbsolutePath())
- .sessionLifetime(5)
- .build())
- .build();
- tenantRepository = new TenantRepository(globalComponentRegistry, false);
- TenantApplications applicationRepo = TenantApplications.create(globalComponentRegistry, tenantName);
- SessionFactory sessionFactory = new SessionFactory(globalComponentRegistry,
- applicationRepo,
- new HostRegistry<>(),
- tenantName);
- sessionRepository = new SessionRepository(tenantName, globalComponentRegistry, sessionFactory,
- applicationRepo, applicationRepo);
- }
-
- @Test
- public void require_that_sessions_can_be_loaded_from_disk() {
- assertNotNull(sessionRepository.getSession(1L));
- assertNotNull(sessionRepository.getSession(2L));
- assertNotNull(sessionRepository.getSession(3L));
- assertNull(sessionRepository.getSession(4L));
- }
-
- @Test
- public void require_that_all_sessions_are_deleted() {
- sessionRepository.close();
- assertNull(sessionRepository.getSession(1L));
- assertNull(sessionRepository.getSession(2L));
- assertNull(sessionRepository.getSession(3L));
- }
-
- @Test
- public void require_that_sessions_belong_to_a_tenant() {
- // tenant is "default"
- assertNotNull(sessionRepository.getSession(1L));
- assertNotNull(sessionRepository.getSession(2L));
- assertNotNull(sessionRepository.getSession(3L));
- assertNull(sessionRepository.getSession(4L));
-
- // tenant is "newTenant"
- try {
- setupSessions(TenantName.from("newTenant"), false);
- } catch (Exception e) {
- fail();
- }
- assertNull(sessionRepository.getSession(1L));
-
- sessionRepository.addSession(new SessionHandlerTest.MockLocalSession(1L, FilesApplicationPackage.fromFile(testApp)));
- sessionRepository.addSession(new SessionHandlerTest.MockLocalSession(2L, FilesApplicationPackage.fromFile(testApp)));
- assertNotNull(sessionRepository.getSession(1L));
- assertNotNull(sessionRepository.getSession(2L));
- assertNull(sessionRepository.getSession(3L));
- }
-
-
- private void createSession(long sessionId, boolean wait) {
- createSession(sessionId, wait, tenantName);
- }
-
- private void createSession(long sessionId, boolean wait, TenantName tenantName) {
- com.yahoo.path.Path sessionsPath = TenantRepository.getSessionsPath(tenantName);
- SessionZooKeeperClient zkc = new SessionZooKeeperClient(curator, sessionsPath.append(String.valueOf(sessionId)));
- zkc.createNewSession(Instant.now());
- if (wait) {
- Curator.CompletionWaiter waiter = zkc.getUploadWaiter();
- waiter.awaitCompletion(Duration.ofSeconds(120));
- }
- }
-
- @Test
- public void testInitialize() {
- createSession(10L, false);
- createSession(11L, false);
- assertSessionExists(10L);
- assertSessionExists(11L);
- }
-
- @Test
- public void testCreateSession() {
- createSession(12L, true);
- assertSessionExists(12L);
- }
-
- @Test
- public void testSessionStateChange() throws Exception {
- long sessionId = 3L;
- createSession(sessionId, true);
- assertSessionStatus(sessionId, Session.Status.NEW);
- assertStatusChange(sessionId, Session.Status.PREPARE);
- assertStatusChange(sessionId, Session.Status.ACTIVATE);
-
- com.yahoo.path.Path session = TenantRepository.getSessionsPath(tenantName).append("" + sessionId);
- curator.delete(session);
- assertSessionRemoved(sessionId);
- assertNull(sessionRepository.getRemoteSession(sessionId));
- }
-
- // If reading a session throws an exception it should be handled and not prevent other applications
- // from loading. In this test we just show that we end up with one session in remote session
- // repo even if it had bad data (by making getSessionIdForApplication() in FailingTenantApplications
- // throw an exception).
- @Test
- public void testBadApplicationRepoOnActivate() {
- long sessionId = 3L;
- TenantName mytenant = TenantName.from("mytenant");
- curator.set(TenantRepository.getApplicationsPath(mytenant).append("mytenant:appX:default"), new byte[0]); // Invalid data
- tenantRepository.addTenant(mytenant);
- Tenant tenant = tenantRepository.getTenant(mytenant);
- curator.create(TenantRepository.getSessionsPath(mytenant));
- sessionRepository = tenant.getSessionRepo();
- assertThat(sessionRepository.getRemoteSessions().size(), is(0));
- createSession(sessionId, true, mytenant);
- assertThat(sessionRepository.getRemoteSessions().size(), is(1));
- }
-
- private void assertStatusChange(long sessionId, Session.Status status) throws Exception {
- com.yahoo.path.Path statePath = TenantRepository.getSessionsPath(tenantName).append("" + sessionId).append(ConfigCurator.SESSIONSTATE_ZK_SUBPATH);
- curator.create(statePath);
- curator.framework().setData().forPath(statePath.getAbsolute(), Utf8.toBytes(status.toString()));
- assertSessionStatus(sessionId, status);
- }
-
- private void assertSessionRemoved(long sessionId) {
- waitFor(p -> sessionRepository.getRemoteSession(sessionId) == null, sessionId);
- assertNull(sessionRepository.getRemoteSession(sessionId));
- }
-
- private void assertSessionExists(long sessionId) {
- assertSessionStatus(sessionId, Session.Status.NEW);
- }
-
- private void assertSessionStatus(long sessionId, Session.Status status) {
- waitFor(p -> sessionRepository.getRemoteSession(sessionId) != null &&
- sessionRepository.getRemoteSession(sessionId).getStatus() == status, sessionId);
- assertNotNull(sessionRepository.getRemoteSession(sessionId));
- assertThat(sessionRepository.getRemoteSession(sessionId).getStatus(), is(status));
- }
-
- private void waitFor(LongPredicate predicate, long sessionId) {
- long endTime = System.currentTimeMillis() + 60_000;
- boolean ok;
- do {
- ok = predicate.test(sessionId);
- try {
- Thread.sleep(10);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- } while (System.currentTimeMillis() < endTime && !ok);
- }
-
-}
diff --git a/container-core/src/main/java/com/yahoo/container/protect/ProcessTerminator.java b/container-core/src/main/java/com/yahoo/container/protect/ProcessTerminator.java
index 16cf741813c..38f5b72336b 100644
--- a/container-core/src/main/java/com/yahoo/container/protect/ProcessTerminator.java
+++ b/container-core/src/main/java/com/yahoo/container/protect/ProcessTerminator.java
@@ -5,7 +5,7 @@ import com.yahoo.protect.Process;
/**
* An injectable terminator of the Java vm.
- * Components that encounters conditions where the vm should be terminated should
+ * Components that encounters conditions where the vm should be terminator should
* request an instance of this injected. That makes termination testable
* as tests can create subclasses of this which register the termination request
* rather than terminating.