diff options
author | Harald Musum <musum@verizonmedia.com> | 2020-10-21 10:10:18 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-10-21 10:10:18 +0200 |
commit | 76c8228dc3331a59b134e3171a60d6eb6ad00559 (patch) | |
tree | 0225b06b3cf2cfcf96d560af4cefe0d24db9167b /configserver | |
parent | b4bcee8ca30b1d03892af747057dbb5de83fa1b8 (diff) | |
parent | 0a0dd27fa223bf7a06e03a1760b4c592aae5b136 (diff) |
Merge pull request #14986 from vespa-engine/revert-14967-revert-14953-revert-14938-hmusum/merge-LocalSession-and-RemoteSession
Revert "Reapply "Merge LocalSession and RemoteSession""
Diffstat (limited to 'configserver')
14 files changed, 362 insertions, 184 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 b454e5b063d..157e1bd838c 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 @@ -50,7 +50,9 @@ import com.yahoo.vespa.config.server.http.v2.ProtonMetricsResponse; import com.yahoo.vespa.config.server.metrics.DeploymentMetricsRetriever; import com.yahoo.vespa.config.server.metrics.ProtonMetricsRetriever; import com.yahoo.vespa.config.server.provision.HostProvisionerProvider; +import com.yahoo.vespa.config.server.session.LocalSession; import com.yahoo.vespa.config.server.session.PrepareParams; +import com.yahoo.vespa.config.server.session.RemoteSession; import com.yahoo.vespa.config.server.session.Session; import com.yahoo.vespa.config.server.session.SessionRepository; import com.yahoo.vespa.config.server.session.SilentDeployLogger; @@ -480,10 +482,10 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye Optional<Long> activeSession = tenantApplications.activeSessionOf(applicationId); if (activeSession.isEmpty()) return false; - // Deleting an application is done by deleting the session, other config + // Deleting an application is done by deleting the remote session, other config // servers will pick this up and clean up through the watcher in this class try { - Session session = getSession(tenant, activeSession.get()); + Session session = getRemoteSession(tenant, activeSession.get()); tenant.getSessionRepository().delete(session); } catch (NotFoundException e) { log.log(Level.INFO, TenantRepository.logPre(applicationId) + "Active session exists, but has not been deleted properly. Trying to cleanup"); @@ -607,7 +609,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye Tenant tenant = getTenant(applicationId); if (tenant == null) throw new NotFoundException("Tenant '" + applicationId.tenant() + "' not found"); long sessionId = getSessionIdForApplication(tenant, applicationId); - Session session = getSession(tenant, sessionId); + RemoteSession session = getRemoteSession(tenant, sessionId); SessionRepository sessionRepository = tenant.getSessionRepository(); return sessionRepository.ensureApplicationLoaded(session).getForVersionOrLatest(version, clock.instant()); } catch (NotFoundException e) { @@ -759,7 +761,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye * * @return the active session, or null if there is no active session for the given application id. */ - public Session getActiveRemoteSession(ApplicationId applicationId) { + public RemoteSession getActiveRemoteSession(ApplicationId applicationId) { Tenant tenant = getTenant(applicationId); if (tenant == null) throw new IllegalArgumentException("Could not find any tenant for '" + applicationId + "'"); return getActiveSession(tenant, applicationId); @@ -779,14 +781,14 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye } public void validateThatSessionIsNotActive(Tenant tenant, long sessionId) { - Session session = getSession(tenant, sessionId); + Session session = getRemoteSession(tenant, sessionId); if (Session.Status.ACTIVATE.equals(session.getStatus())) { throw new IllegalStateException("Session is active: " + sessionId); } } public void validateThatSessionIsPrepared(Tenant tenant, long sessionId) { - Session session = getSession(tenant, sessionId); + Session session = getRemoteSession(tenant, sessionId); if ( ! Session.Status.PREPARE.equals(session.getStatus())) throw new IllegalStateException("Session not prepared: " + sessionId); } @@ -817,9 +819,9 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye return session.getSessionId(); } - public void deleteExpiredSessions() { - Map<Tenant, Collection<Session>> sessionsPerTenant = new HashMap<>(); - tenantRepository.getAllTenants().forEach(tenant -> sessionsPerTenant.put(tenant, tenant.getSessionRepository().getSessions())); + public void deleteExpiredLocalSessions() { + Map<Tenant, Collection<LocalSession>> sessionsPerTenant = new HashMap<>(); + tenantRepository.getAllTenants().forEach(tenant -> sessionsPerTenant.put(tenant, tenant.getSessionRepository().getLocalSessions())); Set<ApplicationId> applicationIds = new HashSet<>(); sessionsPerTenant.values() @@ -837,6 +839,18 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye sessionsPerTenant.keySet().forEach(tenant -> tenant.getSessionRepository().deleteExpiredSessions(activeSessions)); } + public int deleteExpiredRemoteSessions(Duration expiryTime) { + return deleteExpiredRemoteSessions(clock, expiryTime); + } + + public int deleteExpiredRemoteSessions(Clock clock, Duration expiryTime) { + return tenantRepository.getAllTenants() + .stream() + .map(tenant -> tenant.getSessionRepository().deleteExpiredRemoteSessions(clock, expiryTime)) + .mapToInt(i -> i) + .sum(); + } + // ---------------- Tenant operations ---------------------------------------------------------------- @@ -909,14 +923,14 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye } private Session getLocalSession(Tenant tenant, long sessionId) { - Session session = tenant.getSessionRepository().getSession(sessionId); + Session session = tenant.getSessionRepository().getLocalSession(sessionId); if (session == null) throw new NotFoundException("Session " + sessionId + " was not found"); return session; } - private Session getSession(Tenant tenant, long sessionId) { - Session session = tenant.getSessionRepository().getSession(sessionId); + private RemoteSession getRemoteSession(Tenant tenant, long sessionId) { + RemoteSession session = tenant.getSessionRepository().getRemoteSession(sessionId); if (session == null) throw new NotFoundException("Session " + sessionId + " was not found"); return session; @@ -952,13 +966,13 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye // TODO: Merge this and getActiveSession(), they are almost identical private Session getExistingSession(Tenant tenant, ApplicationId applicationId) { TenantApplications applicationRepo = tenant.getApplicationRepo(); - return getSession(tenant, applicationRepo.requireActiveSessionOf(applicationId)); + return getRemoteSession(tenant, applicationRepo.requireActiveSessionOf(applicationId)); } - public Session getActiveSession(Tenant tenant, ApplicationId applicationId) { + public RemoteSession getActiveSession(Tenant tenant, ApplicationId applicationId) { TenantApplications applicationRepo = tenant.getApplicationRepo(); if (applicationRepo.activeApplications().contains(applicationId)) { - return tenant.getSessionRepository().getSession(applicationRepo.requireActiveSessionOf(applicationId)); + return tenant.getSessionRepository().getRemoteSession(applicationRepo.requireActiveSessionOf(applicationId)); } return null; } @@ -966,7 +980,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye public Session getActiveLocalSession(Tenant tenant, ApplicationId applicationId) { TenantApplications applicationRepo = tenant.getApplicationRepo(); if (applicationRepo.activeApplications().contains(applicationId)) { - return tenant.getSessionRepository().getSession(applicationRepo.requireActiveSessionOf(applicationId)); + return tenant.getSessionRepository().getLocalSession(applicationRepo.requireActiveSessionOf(applicationId)); } return null; } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java index 3c22c05ae2d..7d58682947f 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java @@ -94,7 +94,7 @@ public class Deployment implements com.yahoo.config.provision.Deployment { PrepareParams params = this.params.get(); ApplicationId applicationId = params.getApplicationId(); try (ActionTimer timer = applicationRepository.timerFor(applicationId, "deployment.prepareMillis")) { - this.configChangeActions = tenant.getSessionRepository().prepareSession(session, deployLogger, params, clock.instant()); + this.configChangeActions = tenant.getSessionRepository().prepareLocalSession(session, deployLogger, params, clock.instant()); this.prepared = true; } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java index f73afc7c711..aa709b3bf37 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java @@ -70,7 +70,7 @@ public class ApplicationPackageMaintainer extends ConfigServerMaintainer { continue; } } - createSessionIfMissing(applicationId, sessionId); + createLocalSessionIfMissing(applicationId, sessionId); } } } @@ -83,11 +83,11 @@ public class ApplicationPackageMaintainer extends ConfigServerMaintainer { super.close(); } - private void createSessionIfMissing(ApplicationId applicationId, long sessionId) { + private void createLocalSessionIfMissing(ApplicationId applicationId, long sessionId) { Tenant tenant = applicationRepository.getTenant(applicationId); SessionRepository sessionRepository = tenant.getSessionRepository(); - if (sessionRepository.getSession(sessionId) == null) - sessionRepository.createSessionFromDistributedApplicationPackage(sessionId); + if (sessionRepository.getLocalSession(sessionId) == null) + sessionRepository.createLocalSessionFromDistributedApplicationPackage(sessionId); } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/SessionsMaintainer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/SessionsMaintainer.java index 32b067b5731..19534bba810 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/SessionsMaintainer.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/SessionsMaintainer.java @@ -1,6 +1,7 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.config.server.maintenance; +import com.yahoo.log.LogLevel; import com.yahoo.vespa.config.server.ApplicationRepository; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.flags.FlagSource; @@ -15,14 +16,23 @@ import java.time.Duration; * @author hmusum */ public class SessionsMaintainer extends ConfigServerMaintainer { + private final boolean hostedVespa; SessionsMaintainer(ApplicationRepository applicationRepository, Curator curator, Duration interval, FlagSource flagSource) { super(applicationRepository, curator, flagSource, Duration.ofMinutes(1), interval); + this.hostedVespa = applicationRepository.configserverConfig().hostedVespa(); } @Override protected boolean maintain() { - applicationRepository.deleteExpiredSessions(); + applicationRepository.deleteExpiredLocalSessions(); + + if (hostedVespa) { + Duration expiryTime = Duration.ofMinutes(90); + int deleted = applicationRepository.deleteExpiredRemoteSessions(expiryTime); + log.log(LogLevel.FINE, () -> "Deleted " + deleted + " expired remote sessions older than " + expiryTime); + } + return true; } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java new file mode 100644 index 00000000000..f842578b657 --- /dev/null +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java @@ -0,0 +1,28 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.config.server.session; + +import com.yahoo.config.application.api.ApplicationPackage; +import com.yahoo.config.provision.TenantName; + +/** + * A LocalSession is a session that has been created locally on this configserver. A local session can be edited and + * prepared. Deleting a local session will ensure that the local filesystem state and global zookeeper state is + * cleaned for this session. + * + * @author Ulf Lilleengen + */ +// This is really the store of an application, whether it is active or in an edit session +// TODO: Separate the "application store" and "session" aspects - the latter belongs in the HTTP layer -bratseth +public class LocalSession extends Session { + + /** + * Creates a session. This involves loading the application, validating it and distributing it. + * + * @param sessionId The session id for this session. + */ + public LocalSession(TenantName tenant, long sessionId, ApplicationPackage applicationPackage, + SessionZooKeeperClient sessionZooKeeperClient) { + super(tenant, sessionId, sessionZooKeeperClient, applicationPackage); + } + +} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java new file mode 100644 index 00000000000..de5f1392242 --- /dev/null +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java @@ -0,0 +1,61 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.config.server.session; + +import com.yahoo.config.provision.TenantName; +import com.yahoo.vespa.config.server.application.ApplicationSet; + +import java.util.Objects; +import java.util.Optional; + +/** + * A RemoteSession represents a session created on another config server. This session can + * be regarded as read only, and this interface only allows reading information about a session. + * + * @author Ulf Lilleengen + */ +public class RemoteSession extends Session { + + private final Optional<ApplicationSet> applicationSet; + + /** + * Creates a session. This involves loading the application, validating it and distributing it. + * + * @param tenant The name of the tenant creating session + * @param sessionId The session id for this session. + * @param zooKeeperClient a SessionZooKeeperClient instance + */ + RemoteSession(TenantName tenant, long sessionId, SessionZooKeeperClient zooKeeperClient) { + this(tenant, sessionId, zooKeeperClient, Optional.empty()); + } + + /** + * Creates a remote session, with application set + * + * @param tenant The name of the tenant creating session + * @param sessionId The session id for this session. + * @param zooKeeperClient a SessionZooKeeperClient instance + * @param applicationSet current application set for this session + */ + RemoteSession(TenantName tenant, long sessionId, SessionZooKeeperClient zooKeeperClient, Optional<ApplicationSet> applicationSet) { + super(tenant, sessionId, zooKeeperClient); + this.applicationSet = applicationSet; + } + + @Override + Optional<ApplicationSet> applicationSet() { return applicationSet; } + + public synchronized RemoteSession activated(ApplicationSet applicationSet) { + Objects.requireNonNull(applicationSet, "applicationSet cannot be null"); + return new RemoteSession(tenant, sessionId, sessionZooKeeperClient, Optional.of(applicationSet)); + } + + public synchronized RemoteSession deactivated() { + return new RemoteSession(tenant, sessionId, sessionZooKeeperClient, Optional.empty()); + } + + @Override + public String toString() { + return super.toString() + ",application set=" + applicationSet; + } + +} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java index d5e158cd9ac..69dfc4d627d 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java @@ -17,34 +17,38 @@ import com.yahoo.vespa.config.server.application.ApplicationSet; import com.yahoo.vespa.config.server.tenant.TenantRepository; import java.time.Instant; -import java.util.Objects; import java.util.Optional; /** - * A session represents an instance of an application that can be edited, prepared and activated. + * A session represents an instance of an application that can be edited, prepared and activated. This + * class represents the common stuff between sessions working on the local file + * system ({@link LocalSession}s) and sessions working on zookeeper ({@link RemoteSession}s). * * @author Ulf Lilleengen * @author hmusum */ -public class Session implements Comparable<Session> { +public abstract class Session implements Comparable<Session> { protected final long sessionId; protected final TenantName tenant; protected final SessionZooKeeperClient sessionZooKeeperClient; protected final Optional<ApplicationPackage> applicationPackage; - private final Optional<ApplicationSet> applicationSet; - public Session(TenantName tenant, long sessionId, SessionZooKeeperClient sessionZooKeeperClient, ApplicationPackage applicationPackage) { - this(tenant, sessionId, sessionZooKeeperClient, Optional.of(applicationPackage), Optional.empty()); + protected Session(TenantName tenant, long sessionId, SessionZooKeeperClient sessionZooKeeperClient) { + this(tenant, sessionId, sessionZooKeeperClient, Optional.empty()); } - public Session(TenantName tenant, long sessionId, SessionZooKeeperClient sessionZooKeeperClient, - Optional<ApplicationPackage> applicationPackage, Optional<ApplicationSet> applicationSet) { + protected Session(TenantName tenant, long sessionId, SessionZooKeeperClient sessionZooKeeperClient, + ApplicationPackage applicationPackage) { + this(tenant, sessionId, sessionZooKeeperClient, Optional.of(applicationPackage)); + } + + private Session(TenantName tenant, long sessionId, SessionZooKeeperClient sessionZooKeeperClient, + Optional<ApplicationPackage> applicationPackage) { this.tenant = tenant; this.sessionId = sessionId; this.sessionZooKeeperClient = sessionZooKeeperClient; this.applicationPackage = applicationPackage; - this.applicationSet = applicationSet; } public final long getSessionId() { @@ -59,18 +63,9 @@ public class Session implements Comparable<Session> { return sessionZooKeeperClient; } - public synchronized Session activated(ApplicationSet applicationSet) { - Objects.requireNonNull(applicationSet, "applicationSet cannot be null"); - return new Session(tenant, sessionId, sessionZooKeeperClient, applicationPackage, Optional.of(applicationSet)); - } - - public synchronized Session deactivated() { - return new Session(tenant, sessionId, sessionZooKeeperClient, applicationPackage, Optional.empty()); - } - @Override public String toString() { - return "Session,id=" + sessionId + ",application set=" + applicationSet + ",application package=" + applicationPackage; + return "Session,id=" + sessionId; } public long getActiveSessionAtCreate() { @@ -186,14 +181,14 @@ public class Session implements Comparable<Session> { return applicationPackage.orElseThrow(() -> new RuntimeException("No application package found for " + this)); } - public ApplicationFile getApplicationFile(Path relativePath, Session.Mode mode) { + public ApplicationFile getApplicationFile(Path relativePath, LocalSession.Mode mode) { if (mode.equals(Session.Mode.WRITE)) { markSessionEdited(); } return getApplicationPackage().getFile(relativePath); } - Optional<ApplicationSet> applicationSet() { return applicationSet; } + Optional<ApplicationSet> applicationSet() { return Optional.empty(); }; private void markSessionEdited() { setStatus(Session.Status.NEW); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java index 0348ba9f150..59146c339d3 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java @@ -73,7 +73,8 @@ public class SessionRepository { private static final FilenameFilter sessionApplicationsFilter = (dir, name) -> name.matches("\\d+"); private static final long nonExistingActiveSessionId = 0; - private final Map<Long, Session> sessionCache = new ConcurrentHashMap<>(); + private final Map<Long, LocalSession> localSessionCache = new ConcurrentHashMap<>(); + private final Map<Long, RemoteSession> remoteSessionCache = new ConcurrentHashMap<>(); private final Map<Long, SessionStateWatcher> sessionStateWatchers = new HashMap<>(); private final Duration sessionLifetime; private final Clock clock; @@ -103,27 +104,36 @@ public class SessionRepository { this.applicationRepo = applicationRepo; this.sessionPreparer = sessionPreparer; this.metrics = componentRegistry.getMetrics().getOrCreateMetricUpdater(Metrics.createDimensions(tenantName)); - loadAll(); // Needs to be done before creating cache below + loadSessions(); // Needs to be done before creating cache below this.directoryCache = curator.createDirectoryCache(sessionsPath.getAbsolute(), false, false, componentRegistry.getZkCacheExecutor()); this.directoryCache.addListener(this::childEvent); this.directoryCache.start(); } - private void loadAll() { - loadSessionsFromFileSystem(); - loadSessions(); + private void loadSessions() { + loadLocalSessions(); + loadRemoteSessions(); } - public synchronized void addSession(Session session) { + // ---------------- Local sessions ---------------------------------------------------------------- + + public synchronized void addLocalSession(LocalSession session) { long sessionId = session.getSessionId(); - sessionCache.put(sessionId, session); + localSessionCache.put(sessionId, session); + if (remoteSessionCache.get(sessionId) == null) { + createRemoteSession(sessionId); + } + } + + public LocalSession getLocalSession(long sessionId) { + return localSessionCache.get(sessionId); } - public Collection<Session> getSessions() { - return sessionCache.values(); + public Collection<LocalSession> getLocalSessions() { + return localSessionCache.values(); } - private void loadSessionsFromFileSystem() { + private void loadLocalSessions() { File[] sessions = tenantFileSystemDirs.sessionsPath().listFiles(sessionApplicationsFilter); if (sessions == null) return; @@ -137,7 +147,7 @@ public class SessionRepository { } } - public ConfigChangeActions prepareSession(Session session, DeployLogger logger, PrepareParams params, Instant now) { + public ConfigChangeActions prepareLocalSession(Session session, DeployLogger logger, PrepareParams params, Instant now) { applicationRepo.createApplication(params.getApplicationId()); // TODO jvenstad: This is wrong, but it has to be done now, since preparation can change the application ID of a session :( logger.log(Level.FINE, "Created application " + params.getApplicationId()); long sessionId = session.getSessionId(); @@ -161,10 +171,10 @@ public class SessionRepository { * @param timeoutBudget timeout for creating session and waiting for other servers. * @return a new session */ - public Session createSessionFromExisting(Session existingSession, boolean internalRedeploy, TimeoutBudget timeoutBudget) { + public LocalSession createSessionFromExisting(Session existingSession, boolean internalRedeploy, TimeoutBudget timeoutBudget) { ApplicationId existingApplicationId = existingSession.getApplicationId(); File existingApp = getSessionAppDir(existingSession.getSessionId()); - Session session = createSessionFromApplication(existingApp, existingApplicationId, internalRedeploy, timeoutBudget); + LocalSession session = createSessionFromApplication(existingApp, existingApplicationId, internalRedeploy, timeoutBudget); // Note: Setters below need to be kept in sync with calls in SessionPreparer.writeStateToZooKeeper() session.setApplicationId(existingApplicationId); session.setApplicationPackageReference(existingSession.getApplicationPackageReference()); @@ -182,59 +192,93 @@ public class SessionRepository { * @param timeoutBudget Timeout for creating session and waiting for other servers. * @return a new session */ - public Session createSessionFromApplicationPackage(File applicationDirectory, ApplicationId applicationId, TimeoutBudget timeoutBudget) { + public LocalSession createSessionFromApplicationPackage(File applicationDirectory, ApplicationId applicationId, TimeoutBudget timeoutBudget) { applicationRepo.createApplication(applicationId); return createSessionFromApplication(applicationDirectory, applicationId, false, timeoutBudget); } + /** + * This method is used when creating a session based on a remote session and the distributed application package + * It does not wait for session being created on other servers + */ + private void createLocalSession(File applicationFile, ApplicationId applicationId, long sessionId) { + try { + ApplicationPackage applicationPackage = createApplicationPackage(applicationFile, applicationId, sessionId, false); + createLocalSession(sessionId, applicationPackage); + } catch (Exception e) { + throw new RuntimeException("Error creating session " + sessionId, e); + } + } + // Will delete session data in ZooKeeper and file system - public void deleteLocalSession(Session session) { + public void deleteLocalSession(LocalSession session) { long sessionId = session.getSessionId(); - log.log(Level.FINE, () -> "Deleting session " + sessionId); + log.log(Level.FINE, () -> "Deleting local session " + sessionId); SessionStateWatcher watcher = sessionStateWatchers.remove(sessionId); if (watcher != null) watcher.close(); - sessionCache.remove(sessionId); + localSessionCache.remove(sessionId); NestedTransaction transaction = new NestedTransaction(); transaction.add(FileTransaction.from(FileOperations.delete(getSessionAppDir(sessionId).getAbsolutePath()))); transaction.commit(); } private void deleteAllSessions() { - List<Session> sessions = new ArrayList<>(sessionCache.values()); - for (Session session : sessions) { + List<LocalSession> sessions = new ArrayList<>(localSessionCache.values()); + for (LocalSession session : sessions) { deleteLocalSession(session); } } - public Session getSession(long sessionId) { - return sessionCache.get(sessionId); + // ---------------- Remote sessions ---------------------------------------------------------------- + + public RemoteSession getRemoteSession(long sessionId) { + return remoteSessionCache.get(sessionId); } public List<Long> getRemoteSessionsFromZooKeeper() { return getSessionList(curator.getChildren(sessionsPath)); } - public synchronized Session createSession(long sessionId) { + public synchronized RemoteSession createRemoteSession(long sessionId) { SessionZooKeeperClient sessionZKClient = createSessionZooKeeperClient(sessionId); - Session session = new Session(tenantName, sessionId, sessionZKClient, Optional.empty(), Optional.empty()); - sessionCache.put(sessionId, session); + RemoteSession session = new RemoteSession(tenantName, sessionId, sessionZKClient); + remoteSessionCache.put(sessionId, session); loadSessionIfActive(session); updateSessionStateWatcher(sessionId, session); return session; } - public void deactivateAndUpdateCache(Session session) { - Session deactivated = session.deactivated(); - sessionCache.put(deactivated.getSessionId(), deactivated); + public int deleteExpiredRemoteSessions(Clock clock, Duration expiryTime) { + int deleted = 0; + for (long sessionId : getRemoteSessionsFromZooKeeper()) { + Session session = remoteSessionCache.get(sessionId); + if (session == null) continue; // Internal sessions not in sync with zk, continue + if (session.getStatus() == Session.Status.ACTIVATE) continue; + if (sessionHasExpired(session.getCreateTime(), expiryTime, clock)) { + log.log(Level.FINE, () -> "Remote session " + sessionId + " for " + tenantName + " has expired, deleting it"); + deleteRemoteSessionFromZooKeeper(session); + deleted++; + } + } + return deleted; + } + + public void deactivateAndUpdateCache(RemoteSession remoteSession) { + RemoteSession session = remoteSession.deactivated(); + remoteSessionCache.put(session.getSessionId(), session); } - public void deleteSessionFromZooKeeper(Session session) { + public void deleteRemoteSessionFromZooKeeper(Session session) { SessionZooKeeperClient sessionZooKeeperClient = createSessionZooKeeperClient(session.getSessionId()); Transaction transaction = sessionZooKeeperClient.deleteTransaction(); transaction.commit(); transaction.close(); } + 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()) @@ -245,7 +289,7 @@ public class SessionRepository { return children.stream().map(Long::parseLong).collect(Collectors.toList()); } - private void loadSessions() throws NumberFormatException { + private void loadRemoteSessions() throws NumberFormatException { getRemoteSessionsFromZooKeeper().forEach(this::sessionAdded); } @@ -255,18 +299,16 @@ public class SessionRepository { * @param sessionId session id for the new session */ public synchronized void sessionAdded(long sessionId) { - if (sessionCache.containsKey(sessionId)) return; - - log.log(Level.FINE, () -> "Adding session " + sessionId); - Session session = createSession(sessionId); + log.log(Level.FINE, () -> "Adding remote session " + sessionId); + Session session = createRemoteSession(sessionId); if (session.getStatus() == Session.Status.NEW) { log.log(Level.FINE, () -> session.logPre() + "Confirming upload for session " + sessionId); confirmUpload(session); } - createSessionFromDistributedApplicationPackage(sessionId); + createLocalSessionFromDistributedApplicationPackage(sessionId); } - void activate(Session session) { + void activate(RemoteSession session) { long sessionId = session.getSessionId(); Curator.CompletionWaiter waiter = createSessionZooKeeperClient(sessionId).getActiveWaiter(); log.log(Level.FINE, () -> session.logPre() + "Getting session from repo: " + session); @@ -278,21 +320,28 @@ public class SessionRepository { log.log(Level.INFO, session.logPre() + "Session activated: " + sessionId); } - public void delete(Session session) { - long sessionId = session.getSessionId(); - deleteSessionFromZooKeeper(session); - sessionCache.remove(sessionId); - deleteLocalSession(session); + public void delete(Session remoteSession) { + long sessionId = remoteSession.getSessionId(); + // TODO: Change log level to FINE when debugging is finished + log.log(Level.INFO, () -> remoteSession.logPre() + "Deactivating and deleting remote session " + sessionId); + deleteRemoteSessionFromZooKeeper(remoteSession); + remoteSessionCache.remove(sessionId); + LocalSession localSession = getLocalSession(sessionId); + if (localSession != null) { + // TODO: Change log level to FINE when debugging is finished + log.log(Level.INFO, () -> localSession.logPre() + "Deleting local session " + sessionId); + deleteLocalSession(localSession); + } } private void sessionRemoved(long sessionId) { SessionStateWatcher watcher = sessionStateWatchers.remove(sessionId); if (watcher != null) watcher.close(); - sessionCache.remove(sessionId); + remoteSessionCache.remove(sessionId); metrics.incRemovedSessions(); } - private void loadSessionIfActive(Session session) { + 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"); @@ -303,22 +352,22 @@ public class SessionRepository { } } - void prepare(Session session) { + void prepareRemoteSession(RemoteSession session) { SessionZooKeeperClient sessionZooKeeperClient = createSessionZooKeeperClient(session.getSessionId()); Curator.CompletionWaiter waiter = sessionZooKeeperClient.getPrepareWaiter(); ensureApplicationLoaded(session); notifyCompletion(waiter, session); } - public ApplicationSet ensureApplicationLoaded(Session session) { + public ApplicationSet ensureApplicationLoaded(RemoteSession session) { if (session.applicationSet().isPresent()) { return session.applicationSet().get(); } ApplicationSet applicationSet = loadApplication(session); - Session activated = session.activated(applicationSet); + RemoteSession activated = session.activated(applicationSet); long sessionId = activated.getSessionId(); - sessionCache.put(sessionId, activated); + remoteSessionCache.put(sessionId, activated); updateSessionStateWatcher(sessionId, activated); return applicationSet; @@ -378,7 +427,7 @@ public class SessionRepository { private void nodeChanged() { zkWatcherExecutor.execute(() -> { Multiset<Session.Status> sessionMetrics = HashMultiset.create(); - for (Session session : sessionCache.values()) { + for (Session session : remoteSessionCache.values()) { sessionMetrics.add(session.getStatus()); } metrics.setNewSessions(sessionMetrics.count(Session.Status.NEW)); @@ -409,20 +458,20 @@ public class SessionRepository { public void deleteExpiredSessions(Map<ApplicationId, Long> activeSessions) { log.log(Level.FINE, () -> "Purging old sessions for tenant '" + tenantName + "'"); try { - for (Session candidate : sessionCache.values()) { + for (LocalSession candidate : localSessionCache.values()) { Instant createTime = candidate.getCreateTime(); log.log(Level.FINE, () -> "Candidate session for deletion: " + candidate.getSessionId() + ", created: " + createTime); // Sessions with state other than ACTIVATE if (hasExpired(candidate) && !isActiveSession(candidate)) { - delete(candidate); + deleteLocalSession(candidate); } else if (createTime.plus(Duration.ofDays(1)).isBefore(clock.instant())) { // Sessions with state ACTIVATE, but which are not actually active Optional<ApplicationId> applicationId = candidate.getOptionalApplicationId(); if (applicationId.isEmpty()) continue; Long activeSession = activeSessions.get(applicationId.get()); if (activeSession == null || activeSession != candidate.getSessionId()) { - delete(candidate); + deleteLocalSession(candidate); log.log(Level.INFO, "Deleted inactive session " + candidate.getSessionId() + " created " + createTime + " for '" + applicationId + "'"); } @@ -435,11 +484,11 @@ public class SessionRepository { log.log(Level.FINE, () -> "Done purging old sessions"); } - private boolean hasExpired(Session candidate) { - return candidate.getCreateTime().plus(sessionLifetime).isBefore(clock.instant()); + private boolean hasExpired(LocalSession candidate) { + return (candidate.getCreateTime().plus(sessionLifetime).isBefore(clock.instant())); } - private boolean isActiveSession(Session candidate) { + private boolean isActiveSession(LocalSession candidate) { return candidate.getStatus() == Session.Status.ACTIVATE; } @@ -466,10 +515,10 @@ public class SessionRepository { return FilesApplicationPackage.fromFileWithDeployData(configApplicationDir, deployData); } - private Session createSessionFromApplication(File applicationFile, - ApplicationId applicationId, - boolean internalRedeploy, - TimeoutBudget timeoutBudget) { + private LocalSession createSessionFromApplication(File applicationFile, + ApplicationId applicationId, + boolean internalRedeploy, + TimeoutBudget timeoutBudget) { long sessionId = getNextSessionId(); try { ensureSessionPathDoesNotExist(sessionId); @@ -478,9 +527,9 @@ public class SessionRepository { SessionZooKeeperClient sessionZKClient = createSessionZooKeeperClient(sessionId); sessionZKClient.createNewSession(clock.instant()); Curator.CompletionWaiter waiter = sessionZKClient.getUploadWaiter(); - Session session = new Session(tenantName, sessionId, sessionZKClient, app); + LocalSession session = new LocalSession(tenantName, sessionId, app, sessionZKClient); waiter.awaitCompletion(timeoutBudget.timeLeft()); - addSession(session); + addLocalSession(session); return session; } catch (Exception e) { throw new RuntimeException("Error creating session " + sessionId, e); @@ -506,8 +555,8 @@ public class SessionRepository { Optional<ApplicationSet> currentActiveApplicationSet = Optional.empty(); try { long currentActiveSessionId = applicationRepo.requireActiveSessionOf(appId); - Session activeSession = getSession(currentActiveSessionId); - currentActiveApplicationSet = Optional.ofNullable(ensureApplicationLoaded(activeSession)); + RemoteSession currentActiveSession = getRemoteSession(currentActiveSessionId); + currentActiveApplicationSet = Optional.ofNullable(ensureApplicationLoaded(currentActiveSession)); } catch (IllegalArgumentException e) { // Do nothing if we have no currently active session } @@ -541,29 +590,29 @@ public class SessionRepository { void createSessionFromId(long sessionId) { File sessionDir = getAndValidateExistingSessionAppDir(sessionId); ApplicationPackage applicationPackage = FilesApplicationPackage.fromFile(sessionDir); - createSessionWithApplicationPackage(sessionId, applicationPackage); + createLocalSession(sessionId, applicationPackage); } - void createSessionWithApplicationPackage(long sessionId, ApplicationPackage applicationPackage) { + void createLocalSession(long sessionId, ApplicationPackage applicationPackage) { SessionZooKeeperClient sessionZKClient = createSessionZooKeeperClient(sessionId); - Session session = new Session(tenantName, sessionId, sessionZKClient, applicationPackage); - addSession(session); + LocalSession session = new LocalSession(tenantName, sessionId, applicationPackage, sessionZKClient); + addLocalSession(session); } /** - * Returns a new session for the given session id if it does not already exist. - * Will also add the session to the session cache if necessary + * Returns a new local session for the given session id if it does not already exist. + * Will also add the session to the local session cache if necessary */ - public void createSessionFromDistributedApplicationPackage(long sessionId) { + public void createLocalSessionFromDistributedApplicationPackage(long sessionId) { if (applicationRepo.sessionExistsInFileSystem(sessionId)) { - log.log(Level.FINE, () -> "Session " + sessionId + " already exists"); + log.log(Level.FINE, () -> "Local session for session id " + sessionId + " already exists"); createSessionFromId(sessionId); return; } SessionZooKeeperClient sessionZKClient = createSessionZooKeeperClient(sessionId); FileReference fileReference = sessionZKClient.readApplicationPackageReference(); - log.log(Level.FINE, () -> "File reference for session " + sessionId + ": " + fileReference); + log.log(Level.FINE, () -> "File reference for session id " + sessionId + ": " + fileReference); if (fileReference != null) { File rootDir = new File(Defaults.getDefaults().underVespaHome(componentRegistry.getConfigserverConfig().fileReferencesDir())); File sessionDir; @@ -573,18 +622,13 @@ public class SessionRepository { } catch (IllegalArgumentException e) { // We cannot be guaranteed that the file reference exists (it could be that it has not // been downloaded yet), and e.g when bootstrapping we cannot throw an exception in that case - log.log(Level.FINE, "File reference for session " + sessionId + ": " + fileReference + " not found in " + fileDirectory); + log.log(Level.FINE, "File reference for session id " + sessionId + ": " + fileReference + " not found in " + fileDirectory); return; } ApplicationId applicationId = sessionZKClient.readApplicationId() .orElseThrow(() -> new RuntimeException("Could not find application id for session " + sessionId)); - log.log(Level.FINE, () -> "Creating session for tenant '" + tenantName + "' with id " + sessionId); - try { - ApplicationPackage applicationPackage = createApplicationPackage(sessionDir, applicationId, sessionId, false); - createSessionWithApplicationPackage(sessionId, applicationPackage); - } catch (Exception e) { - throw new RuntimeException("Error creating session " + sessionId, e); - } + log.log(Level.FINE, () -> "Creating local session for tenant '" + tenantName + "' with session id " + sessionId); + createLocalSession(sessionDir, applicationId, sessionId); } } @@ -624,20 +668,20 @@ public class SessionRepository { return new TenantFileSystemDirs(componentRegistry.getConfigServerDB(), tenantName).getUserApplicationDir(sessionId); } - private void updateSessionStateWatcher(long sessionId, Session session) { + private void updateSessionStateWatcher(long sessionId, RemoteSession remoteSession) { SessionStateWatcher sessionStateWatcher = sessionStateWatchers.get(sessionId); if (sessionStateWatcher == null) { Curator.FileCache fileCache = curator.createFileCache(getSessionStatePath(sessionId).getAbsolute(), false); fileCache.addListener(this::nodeChanged); - sessionStateWatchers.put(sessionId, new SessionStateWatcher(fileCache, session, metrics, zkWatcherExecutor, this)); + sessionStateWatchers.put(sessionId, new SessionStateWatcher(fileCache, remoteSession, metrics, zkWatcherExecutor, this)); } else { - sessionStateWatcher.updateRemoteSession(session); + sessionStateWatcher.updateRemoteSession(remoteSession); } } @Override public String toString() { - return getSessions().toString(); + return getLocalSessions().toString(); } public Clock clock() { return clock; } @@ -663,14 +707,14 @@ public class SessionRepository { } private void checkForRemovedSessions(List<Long> sessions) { - for (Session session : sessionCache.values()) + for (Session session : remoteSessionCache.values()) if ( ! sessions.contains(session.getSessionId())) sessionRemoved(session.getSessionId()); } private void checkForAddedSessions(List<Long> sessions) { for (Long sessionId : sessions) - if (sessionCache.get(sessionId) == null) + if (remoteSessionCache.get(sessionId) == null) sessionAdded(sessionId); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionStateWatcher.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionStateWatcher.java index f1100b37912..0f433f53c77 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionStateWatcher.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionStateWatcher.java @@ -24,13 +24,13 @@ public class SessionStateWatcher { private static final Logger log = Logger.getLogger(SessionStateWatcher.class.getName()); private final Curator.FileCache fileCache; - private volatile Session session; + private volatile RemoteSession session; private final MetricUpdater metrics; private final Executor zkWatcherExecutor; private final SessionRepository sessionRepository; SessionStateWatcher(Curator.FileCache fileCache, - Session session, + RemoteSession session, MetricUpdater metrics, Executor zkWatcherExecutor, SessionRepository sessionRepository) { @@ -51,7 +51,7 @@ public class SessionStateWatcher { break; case PREPARE: createLocalSession(sessionId); - sessionRepository.prepare(session); + sessionRepository.prepareRemoteSession(session); break; case ACTIVATE: createLocalSession(sessionId); @@ -66,7 +66,7 @@ public class SessionStateWatcher { } private void createLocalSession(long sessionId) { - sessionRepository.createSessionFromDistributedApplicationPackage(sessionId); + sessionRepository.createLocalSessionFromDistributedApplicationPackage(sessionId); } public long getSessionId() { @@ -100,7 +100,7 @@ public class SessionStateWatcher { }); } - public synchronized void updateRemoteSession(Session session) { + public synchronized void updateRemoteSession(RemoteSession session) { this.session = session; } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SilentDeployLogger.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SilentDeployLogger.java index e9d5a7da549..9e2e5ddf698 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SilentDeployLogger.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SilentDeployLogger.java @@ -7,7 +7,7 @@ import java.util.logging.Logger; import com.yahoo.config.application.api.DeployLogger; /** - * The purpose of this is to mute the log messages from model and application building that + * The purpose of this is to mute the log messages from model and application building in {@link RemoteSession} that * is triggered by {@link SessionStateWatcher}, since those messages already have been emitted by the prepare * handler, for the same prepare operation. * 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 988c8e59b92..b71d1b99cf0 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 @@ -33,6 +33,7 @@ import com.yahoo.vespa.config.server.application.OrchestratorMock; import com.yahoo.vespa.config.server.deploy.DeployTester; import com.yahoo.vespa.config.server.deploy.TenantFileSystemDirs; import com.yahoo.vespa.config.server.http.v2.PrepareResult; +import com.yahoo.vespa.config.server.session.LocalSession; import com.yahoo.vespa.config.server.session.PrepareParams; import com.yahoo.vespa.config.server.session.Session; import com.yahoo.vespa.config.server.session.SessionRepository; @@ -308,10 +309,10 @@ public class ApplicationRepositoryTest { { PrepareResult result = deployApp(testApp); long sessionId = result.sessionId(); - Session applicationData = sessionRepository.getSession(sessionId); + Session applicationData = sessionRepository.getLocalSession(sessionId); assertNotNull(applicationData); assertNotNull(applicationData.getApplicationId()); - assertNotNull(sessionRepository.getSession(sessionId)); + assertNotNull(sessionRepository.getLocalSession(sessionId)); assertNotNull(applicationRepository.getActiveSession(applicationId())); String sessionNode = sessionRepository.getSessionPath(sessionId).getAbsolute(); assertTrue(configCurator.exists(sessionNode)); @@ -322,8 +323,8 @@ public class ApplicationRepositoryTest { // Delete app and verify that it has been deleted from repos and provisioner assertTrue(applicationRepository.delete(applicationId())); assertNull(applicationRepository.getActiveSession(applicationId())); - assertNull(sessionRepository.getSession(sessionId)); - assertNull(sessionRepository.getSession(sessionId)); + assertNull(sessionRepository.getLocalSession(sessionId)); + assertNull(sessionRepository.getLocalSession(sessionId)); assertTrue(provisioner.removed()); assertEquals(tenant.getName(), provisioner.lastApplicationId().tenant()); assertEquals(applicationId(), provisioner.lastApplicationId()); @@ -382,48 +383,54 @@ public class ApplicationRepositoryTest { // No change to active session id assertEquals(activeSessionId, tester.tenant().getApplicationRepo().requireActiveSessionOf(tester.applicationId())); SessionRepository sessionRepository = tester.tenant().getSessionRepository(); - assertEquals(3, sessionRepository.getSessions().size()); + assertEquals(3, sessionRepository.getLocalSessions().size()); clock.advance(Duration.ofHours(1)); // longer than session lifetime - // All sessions except 3 should be removed after the call to deleteExpiredSessions - tester.applicationRepository().deleteExpiredSessions(); - Collection<Session> sessions = sessionRepository.getSessions(); + // All sessions except 3 should be removed after the call to deleteExpiredLocalSessions + tester.applicationRepository().deleteExpiredLocalSessions(); + Collection<LocalSession> sessions = sessionRepository.getLocalSessions(); assertEquals(1, sessions.size()); - ArrayList<Session> sessionList = new ArrayList<>(sessions); - Session localSession = sessionList.get(0); + ArrayList<LocalSession> localSessions = new ArrayList<>(sessions); + LocalSession localSession = localSessions.get(0); assertEquals(3, localSession.getSessionId()); + // All sessions except 3 should be removed after the call to deleteExpiredRemoteSessions + assertEquals(2, tester.applicationRepository().deleteExpiredRemoteSessions(clock, Duration.ofSeconds(0))); + ArrayList<Long> remoteSessions = new ArrayList<>(sessionRepository.getRemoteSessionsFromZooKeeper()); + Session remoteSession = sessionRepository.getRemoteSession(remoteSessions.get(0)); + assertEquals(3, remoteSession.getSessionId()); + // Deploy, but do not activate Optional<com.yahoo.config.provision.Deployment> deployment4 = tester.redeployFromLocalActive(); assertTrue(deployment4.isPresent()); deployment4.get().prepare(); // session 5 (not activated) - assertEquals(sessionRepository.getSessions().toString(), 2, sessionRepository.getSessions().size()); + assertEquals(2, sessionRepository.getLocalSessions().size()); sessionRepository.deleteLocalSession(localSession); - assertEquals(1, sessionRepository.getSessions().size()); + assertEquals(1, sessionRepository.getLocalSessions().size()); - // Create a session without any data in zookeeper (corner case seen in production occasionally) - // and check that expiring sessions still works + // Create a local session without any data in zookeeper (corner case seen in production occasionally) + // and check that expiring local sessions still work int sessionId = 6; Files.createDirectory(new TenantFileSystemDirs(serverdb, tenant1).getUserApplicationDir(sessionId).toPath()); - Session localSession2 = new Session(tenant1, - sessionId, - new SessionZooKeeperClient(curator, - configCurator, - tenant1, - sessionId, - ConfigUtils.getCanonicalHostName()), - FilesApplicationPackage.fromFile(testApp)); - sessionRepository.addSession(localSession2); - assertEquals(2, sessionRepository.getSessions().size()); - - // Check that trying to expire session when there exists a session with no zookeeper data works - tester.applicationRepository().deleteExpiredSessions(); - assertEquals(1, sessionRepository.getSessions().size()); + LocalSession localSession2 = new LocalSession(tenant1, + sessionId, + FilesApplicationPackage.fromFile(testApp), + new SessionZooKeeperClient(curator, + configCurator, + tenant1, + sessionId, + ConfigUtils.getCanonicalHostName())); + sessionRepository.addLocalSession(localSession2); + assertEquals(2, sessionRepository.getLocalSessions().size()); + + // Check that trying to expire local session when there exists a local session with no zookeeper data works + tester.applicationRepository().deleteExpiredLocalSessions(); + assertEquals(1, sessionRepository.getLocalSessions().size()); // Check that trying to expire when there are no active sessions works - tester.applicationRepository().deleteExpiredSessions(); + tester.applicationRepository().deleteExpiredLocalSessions(); } @Test 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 352c199cb4c..015cc039a1c 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().getSessionRepository().getLocalSession(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().getSessionRepository().getLocalSession(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/ApplicationHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java index e55a36af386..7276091fed0 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java @@ -1,6 +1,7 @@ // Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.config.server.http.v2; +import com.fasterxml.jackson.databind.ObjectMapper; import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.component.Version; import com.yahoo.config.model.api.ModelFactory; @@ -17,6 +18,7 @@ import com.yahoo.vespa.config.server.MockLogRetriever; import com.yahoo.vespa.config.server.MockProvisioner; import com.yahoo.vespa.config.server.MockTesterClient; import com.yahoo.vespa.config.server.TestComponentRegistry; +import com.yahoo.vespa.config.server.application.ConfigConvergenceChecker; import com.yahoo.vespa.config.server.application.HttpProxy; import com.yahoo.vespa.config.server.application.OrchestratorMock; import com.yahoo.vespa.config.server.deploy.DeployTester; @@ -35,10 +37,12 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import javax.ws.rs.client.Client; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.List; @@ -332,13 +336,13 @@ public class ApplicationHandlerTest { Tenant tenant = applicationRepository.getTenant(applicationId); long sessionId = tenant.getApplicationRepo().requireActiveSessionOf(applicationId); deleteAndAssertResponse(applicationId, Zone.defaultZone(), Response.Status.OK, null, fullAppIdInUrl); - assertNull(tenant.getSessionRepository().getSession(sessionId)); + assertNull(tenant.getSessionRepository().getLocalSession(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.getSessionRepository().getLocalSession(sessionId)); } private void deleteAndAssertResponse(ApplicationId applicationId, Zone zone, int expectedStatus, HttpErrorResponse.errorCodes errorCode, boolean fullAppIdInUrl) throws IOException { @@ -421,6 +425,21 @@ public class ApplicationHandlerTest { return createApplicationHandler().handle(HttpRequest.createTestRequest(restartUrl, GET)); } + private static class MockStateApiFactory implements ConfigConvergenceChecker.StateApiFactory { + boolean createdApi = false; + @Override + public ConfigConvergenceChecker.StateApi createStateApi(Client client, URI serviceUri) { + createdApi = true; + return () -> { + try { + return new ObjectMapper().readTree("{\"config\":{\"generation\":1}}"); + } catch (IOException e) { + throw new RuntimeException(e); + } + }; + } + } + private ApplicationHandler createApplicationHandler() { return createApplicationHandler(applicationRepository); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionRepositoryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionRepositoryTest.java index adba5093b59..bfcfc7d6e43 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionRepositoryTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionRepositoryTest.java @@ -105,11 +105,11 @@ public class SessionRepositoryTest { setup(); long firstSessionId = deploy(); long secondSessionId = deploy(); - assertNotNull(sessionRepository.getSession(firstSessionId)); - assertNotNull(sessionRepository.getSession(secondSessionId)); - assertNull(sessionRepository.getSession(secondSessionId + 1)); + assertNotNull(sessionRepository.getLocalSession(firstSessionId)); + assertNotNull(sessionRepository.getLocalSession(secondSessionId)); + assertNull(sessionRepository.getLocalSession(secondSessionId + 1)); - ApplicationSet applicationSet = sessionRepository.ensureApplicationLoaded(sessionRepository.getSession(firstSessionId)); + ApplicationSet applicationSet = sessionRepository.ensureApplicationLoaded(sessionRepository.getRemoteSession(firstSessionId)); assertNotNull(applicationSet); assertEquals(2, applicationSet.getApplicationGeneration()); assertEquals(applicationId.application(), applicationSet.getForVersionOrLatest(Optional.empty(), Instant.now()).getId().application()); @@ -117,8 +117,8 @@ public class SessionRepositoryTest { sessionRepository.close(); // All created sessions are deleted - assertNull(sessionRepository.getSession(firstSessionId)); - assertNull(sessionRepository.getSession(secondSessionId)); + assertNull(sessionRepository.getLocalSession(firstSessionId)); + assertNull(sessionRepository.getLocalSession(secondSessionId)); } @Test @@ -128,16 +128,16 @@ public class SessionRepositoryTest { long firstSessionId = deploy(); long secondSessionId = deploy(); - assertNotNull(sessionRepository.getSession(firstSessionId)); - assertNotNull(sessionRepository.getSession(secondSessionId)); - assertNull(sessionRepository.getSession(secondSessionId + 1)); + assertNotNull(sessionRepository.getLocalSession(firstSessionId)); + assertNotNull(sessionRepository.getLocalSession(secondSessionId)); + assertNull(sessionRepository.getLocalSession(secondSessionId + 1)); // tenant is "newTenant" TenantName newTenant = TenantName.from("newTenant"); tenantRepository.addTenant(newTenant); long sessionId = deploy(ApplicationId.from(newTenant.value(), "testapp", "default")); SessionRepository sessionRepository2 = tenantRepository.getTenant(newTenant).getSessionRepository(); - assertNotNull(sessionRepository2.getSession(sessionId)); + assertNotNull(sessionRepository2.getLocalSession(sessionId)); } @Test @@ -158,9 +158,9 @@ public class SessionRepositoryTest { assertStatusChange(sessionId, Session.Status.PREPARE); assertStatusChange(sessionId, Session.Status.ACTIVATE); - sessionRepository.delete(sessionRepository.getSession(sessionId)); + sessionRepository.delete(sessionRepository.getRemoteSession(sessionId)); assertSessionRemoved(sessionId); - assertNull(sessionRepository.getSession(sessionId)); + assertNull(sessionRepository.getRemoteSession(sessionId)); } // If reading a session throws an exception it should be handled and not prevent other applications @@ -277,8 +277,8 @@ public class SessionRepositoryTest { } private void assertSessionRemoved(long sessionId) { - waitFor(p -> sessionRepository.getSession(sessionId) == null, sessionId); - assertNull(sessionRepository.getSession(sessionId)); + waitFor(p -> sessionRepository.getRemoteSession(sessionId) == null, sessionId); + assertNull(sessionRepository.getRemoteSession(sessionId)); } private void assertRemoteSessionExists(long sessionId) { @@ -286,10 +286,10 @@ public class SessionRepositoryTest { } private void assertRemoteSessionStatus(long sessionId, Session.Status status) { - waitFor(p -> sessionRepository.getSession(sessionId) != null && - sessionRepository.getSession(sessionId).getStatus() == status, sessionId); - assertNotNull(sessionRepository.getSession(sessionId)); - assertThat(sessionRepository.getSession(sessionId).getStatus(), is(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) { |