diff options
author | Harald Musum <musum@verizonmedia.com> | 2020-06-10 10:50:04 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-06-10 10:50:04 +0200 |
commit | 3d1b445b10f70771cf7a4bce1ba2d285c553f930 (patch) | |
tree | b924518f3e81d19b96124c51b242e0b4942a7e1c | |
parent | fbc3461127b6edf235e11e231fdbde0b250dd4f4 (diff) | |
parent | c699ecc25a03216a4d6a600b8786d8bda99baa4a (diff) |
Merge pull request #13530 from vespa-engine/musum/configserver-refactoring-7-take-2
Config server refactoring, part 7, take 2
18 files changed, 485 insertions, 549 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 dd88096c62f..af5a561aa6e 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,10 +49,9 @@ 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.LocalSessionRepo; +import com.yahoo.vespa.config.server.session.SessionRepository; 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; @@ -305,7 +304,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.getLocalSessionRepo().addSession(newSession); + tenant.getSessionRepository().addSession(newSession); return Optional.of(Deployment.unprepared(newSession, this, hostProvisioner, tenant, timeout, clock, false /* don't validate as this is already deployed */, bootstrap)); @@ -489,7 +488,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.getRemoteSessionRepo().getSession(sessionId); + RemoteSession session = tenant.getSessionRepo().getRemoteSession(sessionId); if (session == null) throw new NotFoundException("Remote session " + sessionId + " not found"); return session.ensureApplicationLoaded().getForVersionOrLatest(version, clock.instant()); } catch (NotFoundException e) { @@ -526,10 +525,10 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye } private boolean localSessionHasBeenDeleted(ApplicationId applicationId, long sessionId, Duration waitTime) { - RemoteSessionRepo remoteSessionRepo = tenantRepository.getTenant(applicationId.tenant()).getRemoteSessionRepo(); + SessionRepository sessionRepository = tenantRepository.getTenant(applicationId.tenant()).getSessionRepo(); Instant end = Instant.now().plus(waitTime); do { - if (remoteSessionRepo.getSession(sessionId) == null) return true; + if (sessionRepository.getRemoteSession(sessionId) == null) return true; try { Thread.sleep(10); } catch (InterruptedException e) { /* ignored */} } while (Instant.now().isBefore(end)); @@ -635,11 +634,11 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye boolean internalRedeploy, TimeoutBudget timeoutBudget) { Tenant tenant = tenantRepository.getTenant(applicationId.tenant()); - LocalSessionRepo localSessionRepo = tenant.getLocalSessionRepo(); + SessionRepository sessionRepository = tenant.getSessionRepository(); SessionFactory sessionFactory = tenant.getSessionFactory(); RemoteSession fromSession = getExistingSession(tenant, applicationId); LocalSession session = sessionFactory.createSessionFromExisting(fromSession, logger, internalRedeploy, timeoutBudget); - localSessionRepo.addSession(session); + sessionRepository.addSession(session); return session.getSessionId(); } @@ -660,13 +659,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.getLocalSessionRepo().addSession(session); + tenant.getSessionRepository().addSession(session); return session.getSessionId(); } public void deleteExpiredLocalSessions() { Map<Tenant, List<LocalSession>> sessionsPerTenant = new HashMap<>(); - tenantRepository.getAllTenants().forEach(tenant -> sessionsPerTenant.put(tenant, tenant.getLocalSessionRepo().getSessions())); + tenantRepository.getAllTenants().forEach(tenant -> sessionsPerTenant.put(tenant, tenant.getSessionRepository().getSessions())); Set<ApplicationId> applicationIds = new HashSet<>(); sessionsPerTenant.values().forEach(sessionList -> sessionList.forEach(s -> applicationIds.add(s.getApplicationId()))); @@ -677,7 +676,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye if (activeSession != null) activeSessions.put(applicationId, activeSession.getSessionId()); }); - sessionsPerTenant.keySet().forEach(tenant -> tenant.getLocalSessionRepo().deleteExpiredSessions(activeSessions)); + sessionsPerTenant.keySet().forEach(tenant -> tenant.getSessionRepository().deleteExpiredSessions(activeSessions)); } public int deleteExpiredRemoteSessions(Duration expiryTime) { @@ -687,7 +686,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye public int deleteExpiredRemoteSessions(Clock clock, Duration expiryTime) { return tenantRepository.getAllTenants() .stream() - .map(tenant -> tenant.getRemoteSessionRepo().deleteExpiredSessions(clock, expiryTime)) + .map(tenant -> tenant.getSessionRepo().deleteExpiredRemoteSessions(clock, expiryTime)) .mapToInt(i -> i) .sum(); } @@ -747,14 +746,14 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye } private LocalSession getLocalSession(Tenant tenant, long sessionId) { - LocalSession session = tenant.getLocalSessionRepo().getSession(sessionId); + LocalSession session = tenant.getSessionRepository().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.getRemoteSessionRepo().getSession(sessionId); + RemoteSession session = tenant.getSessionRepo().getRemoteSession(sessionId); if (session == null) throw new NotFoundException("Session " + sessionId + " was not found"); return session; @@ -805,7 +804,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.getRemoteSessionRepo().getSession(applicationRepo.requireActiveSessionOf(applicationId)); + return tenant.getSessionRepo().getRemoteSession(applicationRepo.requireActiveSessionOf(applicationId)); } return null; } @@ -813,7 +812,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.getLocalSessionRepo().getSession(applicationRepo.requireActiveSessionOf(applicationId)); + return tenant.getSessionRepository().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 a4dfec708d6..d4fe35c14b1 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 RemoteSessionRepo. + // New applications are added when session is added, not here. See SessionRepository. 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 deleted file mode 100644 index e23552dee44..00000000000 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionRepo.java +++ /dev/null @@ -1,151 +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.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 662094fc0ca..1067a373f41 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 LocalSessionRepo localSessionRepo; + private final SessionRepository sessionRepository; private final Executor zkWatcherExecutor; LocalSessionStateWatcher(Curator.FileCache fileCache, LocalSession session, - LocalSessionRepo localSessionRepo, Executor zkWatcherExecutor) { + SessionRepository sessionRepository, Executor zkWatcherExecutor) { this.fileCache = fileCache; this.session = session; - this.localSessionRepo = localSessionRepo; + this.sessionRepository = sessionRepository; 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) && localSessionRepo.getSession(sessionId) != null) { + if (status.equals(Session.Status.DELETE) && sessionRepository.getSession(sessionId) != null) { log.log(Level.FINE, session.logPre() + "Deleting session " + sessionId); - localSessionRepo.deleteSession(session); + sessionRepository.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/SessionRepository.java index 0e538b05931..9fe725453f4 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionRepo.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java @@ -3,16 +3,14 @@ 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.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; @@ -25,6 +23,9 @@ 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; @@ -33,75 +34,187 @@ 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 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 + * 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 RemoteSessionRepo { +public class SessionRepository { + + private static final Logger log = Logger.getLogger(SessionRepository.class.getName()); + private static final FilenameFilter sessionApplicationsFilter = (dir, name) -> name.matches("\\d+"); - private static final Logger log = Logger.getLogger(RemoteSessionRepo.class.getName()); + private final SessionCache<LocalSession> localSessionCache; + 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 Path sessionsPath; - private final SessionFactory sessionFactory; - private final Map<Long, RemoteSessionStateWatcher> sessionStateWatchers = new HashMap<>(); + private final Executor zkWatcherExecutor; + private final TenantFileSystemDirs tenantFileSystemDirs; + private final BooleanFlag distributeApplicationPackage; 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<>(); + private final Path sessionsPath; + private final SessionFactory sessionFactory; + private final TenantName tenantName; + + public SessionRepository(TenantName tenantName, GlobalComponentRegistry componentRegistry, + SessionFactory sessionFactory, TenantApplications applicationRepo, + ReloadHandler reloadHandler, FlagSource flagSource) { + this.tenantName = tenantName; + localSessionCache = new SessionCache<>(); + this.clock = componentRegistry.getClock(); this.curator = componentRegistry.getCurator(); - this.sessionsPath = TenantRepository.getSessionsPath(tenantName); - this.applicationRepo = applicationRepo; + 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.distributeApplicationPackage = Flags.CONFIGSERVER_DISTRIBUTE_APPLICATION_PACKAGE.bindTo(flagSource); this.reloadHandler = reloadHandler; - this.tenantName = tenantName; + this.sessionsPath = TenantRepository.getSessionsPath(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(); + initializeRemoteSessions(); 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); + // ---------------- 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<>()); + } } - public List<Long> getSessions() { + 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 getSessionList(curator.getChildren(sessionsPath)); } - public void addSession(RemoteSession session) { - sessionCache.addSession(session); + public void addRemoteSession(RemoteSession session) { + remoteSessionCache.addSession(session); metrics.incAddedSessions(); } - public int deleteExpiredSessions(Clock clock, Duration expiryTime) { + public int deleteExpiredRemoteSessions(Clock clock, Duration expiryTime) { int deleted = 0; - for (long sessionId : getSessions()) { - RemoteSession session = sessionCache.getSession(sessionId); + 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 (sessionHasExpired(session.getCreateTime(), expiryTime, clock)) { @@ -127,8 +240,8 @@ public class RemoteSessionRepo { return children.stream().map(Long::parseLong).collect(Collectors.toList()); } - private void initializeSessions() throws NumberFormatException { - getSessions().forEach(this::sessionAdded); + private void initializeRemoteSessions() throws NumberFormatException { + getRemoteSessions().forEach(this::sessionAdded); } private synchronized void sessionsChanged() throws NumberFormatException { @@ -138,14 +251,14 @@ public class RemoteSessionRepo { } private void checkForRemovedSessions(List<Long> sessions) { - for (RemoteSession session : sessionCache.getSessions()) + for (RemoteSession session : remoteSessionCache.getSessions()) if ( ! sessions.contains(session.getSessionId())) sessionRemoved(session.getSessionId()); } - + private void checkForAddedSessions(List<Long> sessions) { for (Long sessionId : sessions) - if (sessionCache.getSession(sessionId) == null) + if (remoteSessionCache.getSession(sessionId) == null) sessionAdded(sessionId); } @@ -155,22 +268,22 @@ public class RemoteSessionRepo { * @param sessionId session id for the new session */ private void sessionAdded(long sessionId) { - log.log(Level.FINE, () -> "Adding session to RemoteSessionRepo: " + sessionId); + log.log(Level.FINE, () -> "Adding session to SessionRepository: " + 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)); + addRemoteSession(session); + remoteSessionStateWatchers.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); + RemoteSessionStateWatcher watcher = remoteSessionStateWatchers.remove(sessionId); if (watcher != null) watcher.close(); - sessionCache.removeSession(sessionId); + remoteSessionCache.removeSession(sessionId); metrics.incRemovedSessions(); } @@ -185,22 +298,10 @@ public class RemoteSessionRepo { } } - 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()) { + for (RemoteSession session : remoteSessionCache.getSessions()) { sessionMetrics.add(session.getStatus()); } metrics.setNewSessions(sessionMetrics.count(Session.Status.NEW)); @@ -231,11 +332,16 @@ public class RemoteSessionRepo { private void synchronizeOnNew(List<Long> sessionList) { for (long sessionId : sessionList) { - RemoteSession session = sessionCache.getSession(sessionId); + 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 f0aab8b2312..097c3146b81 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,7 @@ 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.SessionRepository; import com.yahoo.vespa.config.server.session.SessionFactory; import com.yahoo.vespa.curator.Curator; import org.apache.zookeeper.data.Stat; @@ -28,10 +27,9 @@ 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 LocalSessionRepo localSessionRepo; + private final SessionRepository sessionRepository; private final TenantApplications applicationRepo; private final RequestHandler requestHandler; private final ReloadHandler reloadHandler; @@ -39,8 +37,7 @@ public class Tenant implements TenantHandlerProvider { Tenant(TenantName name, SessionFactory sessionFactory, - LocalSessionRepo localSessionRepo, - RemoteSessionRepo remoteSessionRepo, + SessionRepository sessionRepository, RequestHandler requestHandler, ReloadHandler reloadHandler, TenantApplications applicationRepo, @@ -49,9 +46,8 @@ public class Tenant implements TenantHandlerProvider { this.path = TenantRepository.getTenantPath(name); this.requestHandler = requestHandler; this.reloadHandler = reloadHandler; - this.remoteSessionRepo = remoteSessionRepo; this.sessionFactory = sessionFactory; - this.localSessionRepo = localSessionRepo; + this.sessionRepository = sessionRepository; this.applicationRepo = applicationRepo; this.curator = curator; } @@ -74,13 +70,8 @@ public class Tenant implements TenantHandlerProvider { return requestHandler; } - /** - * The RemoteSessionRepo for this - * - * @return repo - */ - public RemoteSessionRepo getRemoteSessionRepo() { - return remoteSessionRepo; + public SessionRepository getSessionRepo() { + return sessionRepository; } public TenantName getName() { @@ -95,8 +86,8 @@ public class Tenant implements TenantHandlerProvider { return sessionFactory; } - public LocalSessionRepo getLocalSessionRepo() { - return localSessionRepo; + public SessionRepository getSessionRepository() { + return sessionRepository; } @Override @@ -140,9 +131,9 @@ public class Tenant implements TenantHandlerProvider { * Called by watchers as a reaction to {@link #delete()}. */ void close() { - remoteSessionRepo.close(); // Closes watchers and clears memory. + sessionRepository.close(); // Closes watchers and clears memory. applicationRepo.close(); // Closes watchers. - localSessionRepo.close(); // Closes watchers, clears memory, and deletes local files and ZK session state. + sessionRepository.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 d34f89a179b..caf9982e034 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,8 +14,7 @@ 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.LocalSessionRepo; -import com.yahoo.vespa.config.server.session.RemoteSessionRepo; +import com.yahoo.vespa.config.server.session.SessionRepository; import com.yahoo.vespa.config.server.session.SessionFactory; import com.yahoo.vespa.curator.Curator; import org.apache.curator.framework.CuratorFramework; @@ -49,7 +48,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 API that do no know about tenants or have not yet + * create a default tenant since that will be used for APIs 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. @@ -224,15 +223,11 @@ public class TenantRepository { if (reloadHandler == null) reloadHandler = applicationRepo; SessionFactory sessionFactory = new SessionFactory(componentRegistry, applicationRepo, applicationRepo, tenantName); - LocalSessionRepo localSessionRepo = new LocalSessionRepo(tenantName, componentRegistry, sessionFactory); - RemoteSessionRepo remoteSessionRepo = new RemoteSessionRepo(componentRegistry, - sessionFactory, - reloadHandler, - tenantName, - applicationRepo, + SessionRepository sessionRepository = new SessionRepository(tenantName, componentRegistry, sessionFactory, + applicationRepo, reloadHandler, componentRegistry.getFlagSource()); log.log(Level.INFO, "Creating tenant '" + tenantName + "'"); - Tenant tenant = new Tenant(tenantName, sessionFactory, localSessionRepo, remoteSessionRepo, requestHandler, + Tenant tenant = new Tenant(tenantName, sessionFactory, sessionRepository, 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 a3d072be38b..2c07d50b470 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.LocalSessionRepo; +import com.yahoo.vespa.config.server.session.SessionRepository; import com.yahoo.vespa.config.server.session.PrepareParams; import com.yahoo.vespa.config.server.session.RemoteSession; import com.yahoo.vespa.config.server.session.Session; @@ -47,6 +47,9 @@ import com.yahoo.vespa.config.server.tenant.Tenant; import com.yahoo.vespa.config.server.tenant.TenantRepository; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.curator.mock.MockCurator; +import com.yahoo.vespa.flags.FlagSource; +import com.yahoo.vespa.flags.Flags; +import com.yahoo.vespa.flags.InMemoryFlagSource; import com.yahoo.vespa.model.VespaModelFactory; import org.hamcrest.core.Is; import org.jetbrains.annotations.NotNull; @@ -113,16 +116,22 @@ public class ApplicationRepositoryTest { @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); + @Before public void setup() throws IOException { + setup(new InMemoryFlagSource()); + } + + public void setup(FlagSource flagSource) throws IOException { Curator curator = new MockCurator(); TestComponentRegistry componentRegistry = new TestComponentRegistry.Builder() .curator(curator) .configServerConfig(new ConfigserverConfig.Builder() .payloadCompressionType(ConfigserverConfig.PayloadCompressionType.Enum.UNCOMPRESSED) - .configServerDBDir(tempFolder.newFolder("configserverdb").getAbsolutePath()) - .configDefinitionsDir(tempFolder.newFolder("configdefinitions").getAbsolutePath()) + .configServerDBDir(tempFolder.newFolder().getAbsolutePath()) + .configDefinitionsDir(tempFolder.newFolder().getAbsolutePath()) .build()) + .flagSource(flagSource) .build(); tenantRepository = new TenantRepository(componentRegistry, false); tenantRepository.addTenant(TenantRepository.HOSTED_VESPA_TENANT); @@ -146,7 +155,7 @@ public class ApplicationRepositoryTest { TenantName tenantName = applicationId().tenant(); Tenant tenant = tenantRepository.getTenant(tenantName); - LocalSession session = tenant.getLocalSessionRepo().getSession(tenant.getApplicationRepo() + LocalSession session = tenant.getSessionRepository().getSession(tenant.getApplicationRepo() .requireActiveSessionOf(applicationId())); session.getAllocatedHosts(); } @@ -178,7 +187,7 @@ public class ApplicationRepositoryTest { TenantName tenantName = applicationId().tenant(); Tenant tenant = tenantRepository.getTenant(tenantName); - LocalSession session = tenant.getLocalSessionRepo().getSession( + LocalSession session = tenant.getSessionRepository().getSession( tenant.getApplicationRepo().requireActiveSessionOf(applicationId())); assertEquals(firstSessionId, session.getMetaData().getPreviousActiveGeneration()); } @@ -295,17 +304,17 @@ public class ApplicationRepositoryTest { PrepareResult result = deployApp(testApp); long sessionId = result.sessionId(); Tenant tenant = tenantRepository.getTenant(applicationId().tenant()); - LocalSession applicationData = tenant.getLocalSessionRepo().getSession(sessionId); + LocalSession applicationData = tenant.getSessionRepository().getSession(sessionId); assertNotNull(applicationData); assertNotNull(applicationData.getApplicationId()); - assertNotNull(tenant.getRemoteSessionRepo().getSession(sessionId)); + assertNotNull(tenant.getSessionRepo().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.getLocalSessionRepo().getSession(sessionId)); - assertNull(tenant.getRemoteSessionRepo().getSession(sessionId)); + assertNull(tenant.getSessionRepository().getSession(sessionId)); + assertNull(tenant.getSessionRepo().getSession(sessionId)); assertTrue(provisioner.removed); assertEquals(tenant.getName(), provisioner.lastApplicationId.tenant()); assertEquals(applicationId(), provisioner.lastApplicationId); @@ -346,7 +355,7 @@ public class ApplicationRepositoryTest { RemoteSession activeSession = applicationRepository.getActiveSession(applicationId()); assertNull(activeSession); Tenant tenant = tenantRepository.getTenant(applicationId().tenant()); - assertNull(tenant.getRemoteSessionRepo().getSession(prepareResult.sessionId())); + assertNull(tenant.getSessionRepo().getSession(prepareResult.sessionId())); assertTrue(applicationRepository.delete(applicationId())); } @@ -379,14 +388,14 @@ public class ApplicationRepositoryTest { assertNotEquals(activeSessionId, deployment3session); // No change to active session id assertEquals(activeSessionId, tester.tenant().getApplicationRepo().requireActiveSessionOf(tester.applicationId())); - LocalSessionRepo localSessionRepo = tester.tenant().getLocalSessionRepo(); - assertEquals(3, localSessionRepo.getSessions().size()); + SessionRepository sessionRepository = tester.tenant().getSessionRepository(); + assertEquals(3, sessionRepository.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 = localSessionRepo.getSessions(); + Collection<LocalSession> sessions = sessionRepository.getSessions(); assertEquals(1, sessions.size()); ArrayList<LocalSession> localSessions = new ArrayList<>(sessions); LocalSession localSession = localSessions.get(0); @@ -400,9 +409,9 @@ public class ApplicationRepositoryTest { assertTrue(deployment4.isPresent()); deployment4.get().prepare(); // session 5 (not activated) - assertEquals(2, localSessionRepo.getSessions().size()); - localSessionRepo.deleteSession(localSession); - assertEquals(1, localSessionRepo.getSessions().size()); + assertEquals(2, sessionRepository.getSessions().size()); + sessionRepository.deleteSession(localSession); + assertEquals(1, sessionRepository.getSessions().size()); // Check that trying to expire when there are no active sessions works tester.applicationRepository().deleteExpiredLocalSessions(); @@ -457,7 +466,7 @@ public class ApplicationRepositoryTest { TenantName tenantName = applicationId().tenant(); Tenant tenant = tenantRepository.getTenant(tenantName); - LocalSession session = tenant.getLocalSessionRepo().getSession(tenant.getApplicationRepo().requireActiveSessionOf(applicationId())); + LocalSession session = tenant.getSessionRepository().getSession(tenant.getApplicationRepo().requireActiveSessionOf(applicationId())); List<NetworkPorts.Allocation> list = new ArrayList<>(); list.add(new NetworkPorts.Allocation(8080, "container", "container/container.0", "http")); @@ -658,6 +667,14 @@ public class ApplicationRepositoryTest { resolve(SimpletypesConfig.class, requestHandler, applicationId(), vespaVersion); } + @Test + public void testDistributionOfApplicationPackage() throws IOException { + FlagSource flagSource = new InMemoryFlagSource() + .withBooleanFlag(Flags.CONFIGSERVER_DISTRIBUTE_APPLICATION_PACKAGE.id(), true); + setup(flagSource); + applicationRepository.deploy(app1, prepareParams()); + } + private ApplicationRepository createApplicationRepository() { return new ApplicationRepository(tenantRepository, provisioner, diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/TestComponentRegistry.java b/configserver/src/test/java/com/yahoo/vespa/config/server/TestComponentRegistry.java index ec5648757f1..bc16f44b405 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/TestComponentRegistry.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/TestComponentRegistry.java @@ -60,6 +60,7 @@ public class TestComponentRegistry implements GlobalComponentRegistry { private final StripedExecutor<TenantName> zkWatcherExecutor; private final ExecutorService zkCacheExecutor; private final SecretStore secretStore; + private final FlagSource flagSource; private TestComponentRegistry(Curator curator, ConfigCurator configCurator, Metrics metrics, ModelFactoryRegistry modelFactoryRegistry, @@ -74,7 +75,8 @@ public class TestComponentRegistry implements GlobalComponentRegistry { TenantListener tenantListener, Zone zone, Clock clock, - SecretStore secretStore) { + SecretStore secretStore, + FlagSource flagSource) { this.curator = curator; this.configCurator = configCurator; this.metrics = metrics; @@ -94,6 +96,7 @@ public class TestComponentRegistry implements GlobalComponentRegistry { this.zkWatcherExecutor = new StripedExecutor<>(new InThreadExecutorService()); this.zkCacheExecutor = new InThreadExecutorService(); this.secretStore = secretStore; + this.flagSource = flagSource; } public static class Builder { @@ -115,6 +118,7 @@ public class TestComponentRegistry implements GlobalComponentRegistry { private Optional<Provisioner> hostProvisioner = Optional.empty(); private Zone zone = Zone.defaultZone(); private Clock clock = Clock.systemUTC(); + private FlagSource flagSource = new InMemoryFlagSource(); public Builder configServerConfig(ConfigserverConfig configserverConfig) { this.configserverConfig = configserverConfig; @@ -161,6 +165,11 @@ public class TestComponentRegistry implements GlobalComponentRegistry { return this; } + public Builder flagSource(FlagSource flagSource) { + this.flagSource = flagSource; + return this; + } + public TestComponentRegistry build() { final PermanentApplicationPackage permApp = this.permanentApplicationPackage .orElse(new PermanentApplicationPackage(configserverConfig)); @@ -172,11 +181,11 @@ public class TestComponentRegistry implements GlobalComponentRegistry { SessionPreparer sessionPreparer = new SessionPreparer(modelFactoryRegistry, fileDistributionProvider, hostProvisionerProvider, permApp, configserverConfig, defRepo, curator, - zone, new InMemoryFlagSource(), secretStore); + zone, flagSource, secretStore); return new TestComponentRegistry(curator, ConfigCurator.create(curator), metrics, modelFactoryRegistry, permApp, fileDistributionProvider, hostRegistries, configserverConfig, sessionPreparer, hostProvisioner, defRepo, reloadListener, tenantListener, - zone, clock, secretStore); + zone, clock, secretStore, flagSource); } } @@ -221,7 +230,7 @@ public class TestComponentRegistry implements GlobalComponentRegistry { } @Override - public FlagSource getFlagSource() { return new InMemoryFlagSource(); } + public FlagSource getFlagSource() { return flagSource; } @Override public ExecutorService getZkCacheExecutor() { 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 b2091d6e537..bc9f45698e9 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.getLocalSessionRepo().getSession(tenant.getApplicationRepo() + LocalSession session = tenant.getSessionRepository().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 36467a2ca64..156200a061c 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().getLocalSessionRepo().getSession(activeSessionIdBefore).getApplicationId()); + assertEquals(tester.applicationId(), tester.tenant().getSessionRepository().getSession(activeSessionIdBefore).getApplicationId()); deployment.get().activate(); long activeSessionIdAfter = tester.applicationRepository().getActiveSession(tester.applicationId()).getSessionId(); assertEquals(activeSessionIdAfter, activeSessionIdBefore + 1); - assertEquals(tester.applicationId(), tester.tenant().getLocalSessionRepo().getSession(activeSessionIdAfter).getApplicationId()); + assertEquals(tester.applicationId(), tester.tenant().getSessionRepository().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 078dc47af51..4cf81d22e3c 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.getLocalSessionRepo().addSession(session2); + tenant1.getSessionRepository().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.getLocalSessionRepo().addSession(session3); + tenant2.getSessionRepository().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 ff8f7a291ad..ec141a61ee6 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()).getLocalSessionRepo().getSession(sessionId)); + assertNull(tenantRepository.getTenant(applicationId.tenant()).getSessionRepository().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.getLocalSessionRepo().getSession(sessionId)); + assertNull(tenant.getSessionRepository().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 2eaa5d75ba7..457acf8c376 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.getLocalSessionRepo().addSession(new SessionHandlerTest.MockLocalSession(sessionId, app, applicationId)); + tenant.getSessionRepository().addSession(new SessionHandlerTest.MockLocalSession(sessionId, app, applicationId)); TestComponentRegistry componentRegistry = new TestComponentRegistry.Builder() .modelFactoryRegistry(new ModelFactoryRegistry(Collections.singletonList(new VespaModelFactory(new NullConfigModelRegistry())))) .build(); - tenant.getRemoteSessionRepo().addSession(new RemoteSession(tenant.getName(), sessionId, componentRegistry, new MockSessionZKClient(app))); + tenant.getSessionRepo().addRemoteSession(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 88bf6fb7172..f639843ac08 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).getLocalSessionRepo().addSession(new MockLocalSession(1L, FilesApplicationPackage.fromFile(createTestApp()))); + tenantRepository.getTenant(tenant).getSessionRepository().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 deleted file mode 100644 index a758698d3b5..00000000000 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionRepoTest.java +++ /dev/null @@ -1,106 +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.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 deleted file mode 100644 index 468dd5a15a7..00000000000 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionRepoTest.java +++ /dev/null @@ -1,145 +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.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 new file mode 100644 index 00000000000..3c1404b5048 --- /dev/null +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionRepositoryTest.java @@ -0,0 +1,221 @@ +// 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 com.yahoo.vespa.flags.InMemoryFlagSource; +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, new InMemoryFlagSource()); + } + + @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); + } + +} |