diff options
25 files changed, 407 insertions, 230 deletions
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 2c4d3d99408..82231fbf5d8 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 @@ -1,109 +1,32 @@ // 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.application; -import com.google.common.collect.ImmutableSet; -import com.yahoo.concurrent.ThreadFactoryFactory; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.TenantName; -import com.yahoo.log.LogLevel; -import com.yahoo.path.Path; -import com.yahoo.text.Utf8; import com.yahoo.transaction.Transaction; -import com.yahoo.vespa.config.server.ReloadHandler; -import com.yahoo.vespa.config.server.tenant.TenantRepository; -import com.yahoo.vespa.curator.Curator; -import com.yahoo.vespa.curator.transaction.CuratorOperations; -import com.yahoo.vespa.curator.transaction.CuratorTransaction; -import org.apache.curator.framework.CuratorFramework; -import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; -import java.util.ArrayList; import java.util.List; -import java.util.Optional; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.logging.Logger; /** - * The applications of a tenant, backed by ZooKeeper. - * - * Each application is stored as a single node under /config/v2/tenants/<tenant>/applications/<applications>, - * named the same as the application id and containing the id of the session storing the content of the application. + * The applications of a tenant * * @author Ulf Lilleengen */ -public class TenantApplications { - - private static final Logger log = Logger.getLogger(TenantApplications.class.getName()); - - private final Curator curator; - private final Path applicationsPath; - // One thread pool for all instances of this class - private static final ExecutorService pathChildrenExecutor = - Executors.newCachedThreadPool(ThreadFactoryFactory.getDaemonThreadFactory(TenantApplications.class.getName())); - private final Curator.DirectoryCache directoryCache; - private final ReloadHandler reloadHandler; - private final TenantName tenant; - - private TenantApplications(Curator curator, Path applicationsPath, ReloadHandler reloadHandler, TenantName tenant) { - this.curator = curator; - this.applicationsPath = applicationsPath; - curator.create(applicationsPath); - this.reloadHandler = reloadHandler; - this.tenant = tenant; - this.directoryCache = curator.createDirectoryCache(applicationsPath.getAbsolute(), false, false, pathChildrenExecutor); - this.directoryCache.start(); - this.directoryCache.addListener(this::childEvent); - } - - public static TenantApplications create(Curator curator, ReloadHandler reloadHandler, TenantName tenant) { - try { - return new TenantApplications(curator, TenantRepository.getApplicationsPath(tenant), reloadHandler, tenant); - } catch (Exception e) { - throw new RuntimeException(TenantRepository.logPre(tenant) + "Error creating application repo", e); - } - } +public interface TenantApplications { /** * List the active applications of a tenant in this config server. * - * @return a list of {@link ApplicationId}s that are active. + * @return a list of {@link com.yahoo.config.provision.ApplicationId}s that are active. */ - public List<ApplicationId> listApplications() { - try { - List<String> appNodes = curator.framework().getChildren().forPath(applicationsPath.getAbsolute()); - List<ApplicationId> applicationIds = new ArrayList<>(); - for (String appNode : appNodes) { - parseApplication(appNode).ifPresent(applicationIds::add); - } - return applicationIds; - } catch (Exception e) { - throw new RuntimeException(TenantRepository.logPre(tenant)+"Unable to list applications", e); - } - } - - private Optional<ApplicationId> parseApplication(String appNode) { - try { - return Optional.of(ApplicationId.fromSerializedForm(appNode)); - } catch (IllegalArgumentException e) { - log.log(LogLevel.INFO, TenantRepository.logPre(tenant)+"Unable to parse application with id '" + appNode + "', ignoring."); - return Optional.empty(); - } - } + List<ApplicationId> listApplications(); /** * Register active application and adds it to the repo. If it already exists it is overwritten. * - * @param applicationId An {@link ApplicationId} that represents an active application. + * @param applicationId An {@link com.yahoo.config.provision.ApplicationId} that represents an active application. * @param sessionId Id of the session containing the application package for this id. */ - public Transaction createPutApplicationTransaction(ApplicationId applicationId, long sessionId) { - if (listApplications().contains(applicationId)) { - return new CuratorTransaction(curator).add(CuratorOperations.setData(applicationsPath.append(applicationId.serializedForm()).getAbsolute(), Utf8.toAsciiBytes(sessionId))); - } else { - return new CuratorTransaction(curator).add(CuratorOperations.create(applicationsPath.append(applicationId.serializedForm()).getAbsolute(), Utf8.toAsciiBytes(sessionId))); - } - } + Transaction createPutApplicationTransaction(ApplicationId applicationId, long sessionId); /** * Return the stored session id for a given application. @@ -112,68 +35,24 @@ public class TenantApplications { * @return session id of given application id. * @throws IllegalArgumentException if the application does not exist */ - public long getSessionIdForApplication(ApplicationId applicationId) { - String path = applicationsPath.append(applicationId.serializedForm()).getAbsolute(); - try { - return Long.parseLong(Utf8.toString(curator.framework().getData().forPath(path))); - } catch (Exception e) { - throw new IllegalArgumentException(TenantRepository.logPre(applicationId) + "Unable to read the session id from '" + path + "'", e); - } - } + long getSessionIdForApplication(ApplicationId applicationId); /** * Returns a transaction which deletes this application * * @param applicationId an {@link ApplicationId} to delete. */ - public CuratorTransaction deleteApplication(ApplicationId applicationId) { - Path path = applicationsPath.append(applicationId.serializedForm()); - return CuratorTransaction.from(CuratorOperations.delete(path.getAbsolute()), curator); - } - - /** - * Closes the application repo. Once a repo has been closed, it should not be used again. - */ - public void close() { - directoryCache.close(); - } - - private void childEvent(CuratorFramework client, PathChildrenCacheEvent event) { - switch (event.getType()) { - case CHILD_ADDED: - applicationAdded(ApplicationId.fromSerializedForm(Path.fromString(event.getData().getPath()).getName())); - break; - // Event CHILD_REMOVED will be triggered on all config servers if deleteApplication() above is called on one of them - case CHILD_REMOVED: - applicationRemoved(ApplicationId.fromSerializedForm(Path.fromString(event.getData().getPath()).getName())); - break; - case CHILD_UPDATED: - // do nothing, application just got redeployed - break; - default: - break; - } - // We may have lost events and may need to remove applications. - // New applications are added when session is added, not here. See RemoteSessionRepo. - removeUnusedApplications(); - } - - private void applicationRemoved(ApplicationId applicationId) { - reloadHandler.removeApplication(applicationId); - log.log(LogLevel.INFO, TenantRepository.logPre(applicationId) + "Application removed: " + applicationId); - } - - private void applicationAdded(ApplicationId applicationId) { - log.log(LogLevel.DEBUG, TenantRepository.logPre(applicationId) + "Application added: " + applicationId); - } + Transaction deleteApplication(ApplicationId applicationId); /** * Removes unused applications * */ - public void removeUnusedApplications() { - ImmutableSet<ApplicationId> activeApplications = ImmutableSet.copyOf(listApplications()); - reloadHandler.removeApplicationsExcept(activeApplications); - } + void removeUnusedApplications(); + + /** + * Closes the application repo. Once a repo has been closed, it should not be used again. + */ + void close(); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ZKTenantApplications.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ZKTenantApplications.java new file mode 100644 index 00000000000..466df60d688 --- /dev/null +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ZKTenantApplications.java @@ -0,0 +1,160 @@ +// 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.application; + +import com.google.common.collect.ImmutableSet; +import com.yahoo.concurrent.ThreadFactoryFactory; +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.TenantName; +import com.yahoo.log.LogLevel; +import com.yahoo.path.Path; +import com.yahoo.text.Utf8; +import com.yahoo.transaction.Transaction; +import com.yahoo.vespa.config.server.ReloadHandler; +import com.yahoo.vespa.config.server.tenant.TenantRepository; +import com.yahoo.vespa.curator.Curator; +import com.yahoo.vespa.curator.transaction.CuratorOperations; +import com.yahoo.vespa.curator.transaction.CuratorTransaction; +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; +import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.logging.Logger; + +/** + * The applications of a tenant, backed by ZooKeeper. + * Each application is stored as a single node under /config/v2/tenants/<tenant>/applications/<applications>, + * named the same as the application id and containing the id of the session storing the content of the application. + * + * @author Ulf Lilleengen + */ +// TODO: Merge into interface and separate out curator layer instead +public class ZKTenantApplications implements TenantApplications, PathChildrenCacheListener { + + private static final Logger log = Logger.getLogger(ZKTenantApplications.class.getName()); + + private final Curator curator; + private final Path applicationsPath; + // One thread pool for all instances of this class + private static final ExecutorService pathChildrenExecutor = + Executors.newCachedThreadPool(ThreadFactoryFactory.getDaemonThreadFactory(ZKTenantApplications.class.getName())); + private final Curator.DirectoryCache directoryCache; + private final ReloadHandler reloadHandler; + private final TenantName tenant; + + private ZKTenantApplications(Curator curator, Path applicationsPath, ReloadHandler reloadHandler, TenantName tenant) { + this.curator = curator; + this.applicationsPath = applicationsPath; + curator.create(applicationsPath); + this.reloadHandler = reloadHandler; + this.tenant = tenant; + this.directoryCache = curator.createDirectoryCache(applicationsPath.getAbsolute(), false, false, pathChildrenExecutor); + this.directoryCache.start(); + this.directoryCache.addListener(this); + } + + public static TenantApplications create(Curator curator, ReloadHandler reloadHandler, TenantName tenant) { + try { + return new ZKTenantApplications(curator, TenantRepository.getApplicationsPath(tenant), reloadHandler, tenant); + } catch (Exception e) { + throw new RuntimeException(TenantRepository.logPre(tenant) + "Error creating application repo", e); + } + } + + private long readSessionId(ApplicationId appId, String appNode) { + String path = applicationsPath.append(appNode).getAbsolute(); + try { + return Long.parseLong(Utf8.toString(curator.framework().getData().forPath(path))); + } catch (Exception e) { + throw new IllegalArgumentException(TenantRepository.logPre(appId) + "Unable to read the session id from '" + path + "'", e); + } + } + + @Override + public List<ApplicationId> listApplications() { + try { + List<String> appNodes = curator.framework().getChildren().forPath(applicationsPath.getAbsolute()); + List<ApplicationId> applicationIds = new ArrayList<>(); + for (String appNode : appNodes) { + parseApplication(appNode).ifPresent(applicationIds::add); + } + return applicationIds; + } catch (Exception e) { + throw new RuntimeException(TenantRepository.logPre(tenant)+"Unable to list applications", e); + } + } + + private Optional<ApplicationId> parseApplication(String appNode) { + try { + return Optional.of(ApplicationId.fromSerializedForm(appNode)); + } catch (IllegalArgumentException e) { + log.log(LogLevel.INFO, TenantRepository.logPre(tenant)+"Unable to parse application with id '" + appNode + "', ignoring."); + return Optional.empty(); + } + } + + @Override + public Transaction createPutApplicationTransaction(ApplicationId applicationId, long sessionId) { + if (listApplications().contains(applicationId)) { + return new CuratorTransaction(curator).add(CuratorOperations.setData(applicationsPath.append(applicationId.serializedForm()).getAbsolute(), Utf8.toAsciiBytes(sessionId))); + } else { + return new CuratorTransaction(curator).add(CuratorOperations.create(applicationsPath.append(applicationId.serializedForm()).getAbsolute(), Utf8.toAsciiBytes(sessionId))); + } + } + + @Override + public long getSessionIdForApplication(ApplicationId applicationId) { + return readSessionId(applicationId, applicationId.serializedForm()); + } + + @Override + public CuratorTransaction deleteApplication(ApplicationId applicationId) { + Path path = applicationsPath.append(applicationId.serializedForm()); + return CuratorTransaction.from(CuratorOperations.delete(path.getAbsolute()), curator); + } + + @Override + public void close() { + directoryCache.close(); + } + + @Override + public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) { + switch (event.getType()) { + case CHILD_ADDED: + applicationAdded(ApplicationId.fromSerializedForm(Path.fromString(event.getData().getPath()).getName())); + break; + // Event CHILD_REMOVED will be triggered on all config servers if deleteApplication() above is called on one of them + case CHILD_REMOVED: + applicationRemoved(ApplicationId.fromSerializedForm(Path.fromString(event.getData().getPath()).getName())); + break; + case CHILD_UPDATED: + // do nothing, application just got redeployed + break; + default: + break; + } + // We might have lost events and might need to remove applications (new applications are + // not added by listening for events here, they are added when session is added, see RemoteSessionRepo) + removeUnusedApplications(); + } + + private void applicationRemoved(ApplicationId applicationId) { + reloadHandler.removeApplication(applicationId); + log.log(LogLevel.INFO, TenantRepository.logPre(applicationId) + "Application removed: " + applicationId); + } + + private void applicationAdded(ApplicationId applicationId) { + log.log(LogLevel.DEBUG, TenantRepository.logPre(applicationId) + "Application added: " + applicationId); + } + + public void removeUnusedApplications() { + ImmutableSet<ApplicationId> activeApplications = ImmutableSet.copyOf(listApplications()); + reloadHandler.removeApplicationsExcept(activeApplications); + } + +} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ListApplicationsHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ListApplicationsHandler.java index 6345532d4ff..56895e3516e 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ListApplicationsHandler.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ListApplicationsHandler.java @@ -9,10 +9,10 @@ import com.yahoo.config.provision.Zone; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.jdisc.Response; -import com.yahoo.vespa.config.server.application.TenantApplications; import com.yahoo.vespa.config.server.tenant.Tenant; import com.yahoo.vespa.config.server.tenant.TenantRepository; import com.yahoo.config.provision.ApplicationId; +import com.yahoo.vespa.config.server.application.TenantApplications; import com.yahoo.vespa.config.server.http.HttpHandler; import com.yahoo.vespa.config.server.http.Utils; 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 37082888d70..198f8e8e917 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 @@ -18,7 +18,7 @@ import java.util.logging.Logger; * * @author Harald Musum */ -public class LocalSessionStateWatcher { +public class LocalSessionStateWatcher implements NodeCacheListener { private static final Logger log = Logger.getLogger(LocalSessionStateWatcher.class.getName()); // One thread pool for all instances of this class @@ -33,7 +33,7 @@ public class LocalSessionStateWatcher { this.session = session; this.localSessionRepo = localSessionRepo; this.fileCache.start(); - this.fileCache.addListener(this::nodeChanged); + this.fileCache.addListener(this); } // Will delete session if it exists in local session repo @@ -59,6 +59,7 @@ public class LocalSessionStateWatcher { } } + @Override public void nodeChanged() { executor.execute(() -> { try { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionRepo.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionRepo.java index 15182813a22..fe29c27abbe 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/RemoteSessionRepo.java @@ -17,12 +17,12 @@ import com.yahoo.config.provision.TenantName; import com.yahoo.log.LogLevel; import com.yahoo.path.Path; import com.yahoo.vespa.config.server.application.ApplicationSet; -import com.yahoo.vespa.config.server.application.TenantApplications; import com.yahoo.vespa.config.server.tenant.TenantRepository; import com.yahoo.vespa.curator.Curator; import com.yahoo.yolean.Exceptions; import com.yahoo.vespa.config.server.ReloadHandler; import com.yahoo.config.provision.ApplicationId; +import com.yahoo.vespa.config.server.application.TenantApplications; import com.yahoo.vespa.config.server.monitoring.MetricUpdater; import com.yahoo.vespa.config.server.zookeeper.ConfigCurator; @@ -38,7 +38,7 @@ import org.apache.curator.framework.recipes.cache.*; * @author Vegard Havdal * @author Ulf Lilleengen */ -public class RemoteSessionRepo extends SessionRepo<RemoteSession> { +public class RemoteSessionRepo extends SessionRepo<RemoteSession> implements NodeCacheListener, PathChildrenCacheListener { private static final Logger log = Logger.getLogger(RemoteSessionRepo.class.getName()); // One thread pool for all instances of this class @@ -77,7 +77,7 @@ public class RemoteSessionRepo extends SessionRepo<RemoteSession> { this.metrics = metricUpdater; initializeSessions(); this.directoryCache = curator.createDirectoryCache(sessionsPath.getAbsolute(), false, false, pathChildrenExecutor); - this.directoryCache.addListener(this::childEvent); + this.directoryCache.addListener(this); this.directoryCache.start(); } @@ -172,7 +172,7 @@ public class RemoteSessionRepo extends SessionRepo<RemoteSession> { RemoteSession session = remoteSessionFactory.createSession(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); + fileCache.addListener(this); loadSessionIfActive(session); sessionStateWatchers.put(sessionId, new RemoteSessionStateWatcher(fileCache, reloadHandler, session, metrics)); addSession(session); @@ -215,7 +215,8 @@ public class RemoteSessionRepo extends SessionRepo<RemoteSession> { } } - private void nodeChanged() { + @Override + public void nodeChanged() { Multiset<Session.Status> sessionMetrics = HashMultiset.create(); for (RemoteSession session : listSessions()) { sessionMetrics.add(session.getStatus()); @@ -226,7 +227,8 @@ public class RemoteSessionRepo extends SessionRepo<RemoteSession> { metrics.setDeactivatedSessions(sessionMetrics.count(Session.Status.DEACTIVATE)); } - private void childEvent(CuratorFramework framework, PathChildrenCacheEvent event) { + @Override + public void childEvent(CuratorFramework framework, PathChildrenCacheEvent event) { if (log.isLoggable(LogLevel.DEBUG)) { log.log(LogLevel.DEBUG, "Got child event: " + event); } @@ -252,5 +254,4 @@ public class RemoteSessionRepo extends SessionRepo<RemoteSession> { session.confirmUpload(); } } - } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionStateWatcher.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionStateWatcher.java index ef59e28f458..1a891c65c49 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionStateWatcher.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionStateWatcher.java @@ -20,7 +20,7 @@ import java.util.logging.Logger; * * @author Vegard Havdal */ -public class RemoteSessionStateWatcher { +public class RemoteSessionStateWatcher implements NodeCacheListener { private static final Logger log = Logger.getLogger(RemoteSessionStateWatcher.class.getName()); // One thread pool for all instances of this class @@ -41,7 +41,7 @@ public class RemoteSessionStateWatcher { this.session = session; this.metrics = metrics; this.fileCache.start(); - this.fileCache.addListener(this::nodeChanged); + this.fileCache.addListener(this); } private void sessionChanged(Session.Status status) { @@ -72,6 +72,7 @@ public class RemoteSessionStateWatcher { } } + @Override public void nodeChanged() { executor.execute(() -> { try { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantBuilder.java index 078b6e861a9..95d58ecd5bb 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantBuilder.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantBuilder.java @@ -10,6 +10,7 @@ import com.yahoo.vespa.config.server.host.HostValidator; 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.application.ZKTenantApplications; import com.yahoo.vespa.config.server.deploy.TenantFileSystemDirs; import com.yahoo.vespa.config.server.monitoring.Metrics; import com.yahoo.vespa.config.server.session.*; @@ -120,7 +121,7 @@ public class TenantBuilder { private void createApplicationRepo() { if (applicationRepo == null) { - applicationRepo = TenantApplications.create(componentRegistry.getCurator(), reloadHandler, tenant); + applicationRepo = ZKTenantApplications.create(componentRegistry.getCurator(), reloadHandler, tenant); } } 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 9c74c9c1e67..bcdd14f8344 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRepository.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRepository.java @@ -14,7 +14,9 @@ import com.yahoo.vespa.config.server.monitoring.MetricUpdater; import com.yahoo.vespa.curator.Curator; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; +import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener; import org.apache.curator.framework.state.ConnectionState; +import org.apache.curator.framework.state.ConnectionStateListener; import org.apache.zookeeper.KeeperException; import java.time.Duration; @@ -35,7 +37,6 @@ import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -56,7 +57,7 @@ import java.util.stream.Collectors; * @author Vegard Havdal * @author Ulf Lilleengen */ -public class TenantRepository { +public class TenantRepository implements ConnectionStateListener, PathChildrenCacheListener { public static final TenantName HOSTED_VESPA_TENANT = TenantName.from("hosted-vespa"); private static final TenantName DEFAULT_TENANT = TenantName.defaultName(); @@ -103,7 +104,7 @@ public class TenantRepository { this.curator = globalComponentRegistry.getCurator(); metricUpdater = globalComponentRegistry.getMetrics().getOrCreateMetricUpdater(Collections.emptyMap()); this.tenantListeners.add(globalComponentRegistry.getTenantListener()); - curator.framework().getConnectionStateListenable().addListener(this::stateChanged); + curator.framework().getConnectionStateListenable().addListener(this); curator.create(tenantsPath); createSystemTenants(configserverConfig); @@ -112,7 +113,7 @@ public class TenantRepository { if (useZooKeeperWatchForTenantChanges) { this.directoryCache = Optional.of(curator.createDirectoryCache(tenantsPath.getAbsolute(), false, false, pathChildrenExecutor)); this.directoryCache.get().start(); - this.directoryCache.get().addListener(this::childEvent); + this.directoryCache.get().addListener(this); } else { this.directoryCache = Optional.empty(); } @@ -317,7 +318,8 @@ public class TenantRepository { return ret.toString(); } - private void stateChanged(CuratorFramework framework, ConnectionState connectionState) { + @Override + public void stateChanged(CuratorFramework framework, ConnectionState connectionState) { switch (connectionState) { case CONNECTED: metricUpdater.incZKConnected(); @@ -337,7 +339,8 @@ public class TenantRepository { } } - private void childEvent(CuratorFramework framework, PathChildrenCacheEvent event) { + @Override + public void childEvent(CuratorFramework framework, PathChildrenCacheEvent event) { switch (event.getType()) { case CHILD_ADDED: case CHILD_REMOVED: @@ -348,16 +351,8 @@ public class TenantRepository { public void close() { directoryCache.ifPresent(Curator.DirectoryCache::close); - try { - pathChildrenExecutor.shutdown(); - checkForRemovedApplicationsService.shutdown(); - pathChildrenExecutor.awaitTermination(50, TimeUnit.SECONDS); - checkForRemovedApplicationsService.awaitTermination(50, TimeUnit.SECONDS); - } - catch (InterruptedException e) { - log.log(Level.WARNING, "Interrupted while shutting down.", e); - Thread.currentThread().interrupt(); - } + pathChildrenExecutor.shutdown(); + checkForRemovedApplicationsService.shutdown(); } public boolean checkThatTenantExists(TenantName tenant) { diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/MemoryTenantApplications.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/MemoryTenantApplications.java new file mode 100644 index 00000000000..ce4b5af7650 --- /dev/null +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/MemoryTenantApplications.java @@ -0,0 +1,66 @@ +// 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.application; + +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.transaction.Transaction; +import com.yahoo.vespa.config.server.session.DummyTransaction; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * In memory {@link TenantApplications} to be used when testing. + * + * @author Ulf Lilleengen + * @since 5.1 + */ +public class MemoryTenantApplications implements TenantApplications { + + private final Map<ApplicationId, Long> applications = new LinkedHashMap<>(); + private boolean isOpen = true; + + @Override + public List<ApplicationId> listApplications() { + List<ApplicationId> lst = new ArrayList<>(); + lst.addAll(applications.keySet()); + return lst; + } + + @Override + public Transaction createPutApplicationTransaction(ApplicationId applicationId, long sessionId) { + return new DummyTransaction().add((DummyTransaction.RunnableOperation) () -> { + applications.put(applicationId, sessionId); + }); + } + + @Override + public long getSessionIdForApplication(ApplicationId id) { + if (applications.containsKey(id)) { + return applications.get(id); + } + return 0; + } + + @Override + public Transaction deleteApplication(ApplicationId id) { + return new DummyTransaction().add((DummyTransaction.RunnableOperation) () -> { + applications.remove(id); + }); + } + + @Override + public void removeUnusedApplications() { + // do nothing + } + + @Override + public void close() { + isOpen = false; + } + + public boolean isOpen() { + return isOpen; + } +} diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/TenantApplicationsTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/TenantApplicationsTest.java index a708e4d8ace..3fa1b3fdb5e 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/application/TenantApplicationsTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/TenantApplicationsTest.java @@ -13,6 +13,8 @@ import org.apache.curator.framework.CuratorFramework; import org.junit.Before; import org.junit.Test; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import static org.hamcrest.Matchers.is; @@ -101,6 +103,36 @@ public class TenantApplicationsTest { } @Test + public void require_that_repos_behave_similarly() throws Exception { + TenantApplications zkRepo = createZKAppRepo(); + TenantApplications memRepo = new MemoryTenantApplications(); + for (TenantApplications repo : Arrays.asList(zkRepo, memRepo)) { + ApplicationId id1 = createApplicationId("myapp"); + ApplicationId id2 = createApplicationId("myapp2"); + repo.createPutApplicationTransaction(id1, 4).commit(); + repo.createPutApplicationTransaction(id2, 5).commit(); + List<ApplicationId> lst = repo.listApplications(); + Collections.sort(lst); + assertThat(lst.size(), is(2)); + assertThat(lst.get(0).application(), is(id1.application())); + assertThat(lst.get(1).application(), is(id2.application())); + assertThat(repo.getSessionIdForApplication(id1), is(4l)); + assertThat(repo.getSessionIdForApplication(id2), is(5l)); + repo.createPutApplicationTransaction(id1, 6).commit(); + lst = repo.listApplications(); + Collections.sort(lst); + assertThat(lst.size(), is(2)); + assertThat(lst.get(0).application(), is(id1.application())); + assertThat(lst.get(1).application(), is(id2.application())); + assertThat(repo.getSessionIdForApplication(id1), is(6l)); + assertThat(repo.getSessionIdForApplication(id2), is(5l)); + repo.deleteApplication(id1).commit(); + assertThat(repo.listApplications().size(), is(1)); + repo.deleteApplication(id2).commit(); + } + } + + @Test public void require_that_reload_handler_is_called_when_apps_are_removed() throws Exception { ApplicationId foo = createApplicationId("foo"); writeApplicationData(foo, 3L); @@ -122,7 +154,7 @@ public class TenantApplicationsTest { } private TenantApplications createZKAppRepo(MockReloadHandler reloadHandler) { - return TenantApplications.create(curator, reloadHandler, tenantName); + return ZKTenantApplications.create(curator, reloadHandler, tenantName); } private static ApplicationId createApplicationId(String name) { diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java index 380b76c30af..f384fda8796 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java @@ -20,9 +20,9 @@ import com.yahoo.jdisc.Response; import com.yahoo.jdisc.http.HttpRequest; import com.yahoo.slime.JsonFormat; import com.yahoo.vespa.config.server.ApplicationRepository; -import com.yahoo.vespa.config.server.MockReloadHandler; import com.yahoo.vespa.config.server.SuperModelGenerationCounter; import com.yahoo.vespa.config.server.TestComponentRegistry; +import com.yahoo.vespa.config.server.application.MemoryTenantApplications; import com.yahoo.vespa.config.server.application.OrchestratorMock; import com.yahoo.vespa.config.server.application.TenantApplications; import com.yahoo.vespa.config.server.deploy.TenantFileSystemDirs; @@ -98,22 +98,22 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { @Before public void setup() { remoteSessionRepo = new RemoteSessionRepo(tenantName); + applicationRepo = new MemoryTenantApplications(); curator = new MockCurator(); + localRepo = new LocalSessionRepo(clock, curator); + pathPrefix = "/application/v2/tenant/" + tenantName + "/session/"; + hostProvisioner = new MockProvisioner(); modelFactory = new VespaModelFactory(new NullConfigModelRegistry()); componentRegistry = new TestComponentRegistry.Builder() .curator(curator) .modelFactoryRegistry(new ModelFactoryRegistry(Collections.singletonList(modelFactory))) .build(); - tenantRepository = new TenantRepository(componentRegistry, false); - applicationRepo = TenantApplications.create(curator, new MockReloadHandler(), tenantName); - localRepo = new LocalSessionRepo(clock, curator); - pathPrefix = "/application/v2/tenant/" + tenantName + "/session/"; - hostProvisioner = new MockProvisioner(); TenantBuilder tenantBuilder = TenantBuilder.create(componentRegistry, tenantName) .withSessionFactory(new MockSessionFactory()) .withLocalSessionRepo(localRepo) .withRemoteSessionRepo(remoteSessionRepo) .withApplicationRepo(applicationRepo); + tenantRepository = new TenantRepository(componentRegistry, false); tenantRepository.addTenant(tenantBuilder); handler = createHandler(); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java index 94d3b126bd7..803a87ada1c 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java @@ -7,8 +7,8 @@ import com.yahoo.config.provision.TenantName; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.vespa.config.server.ApplicationRepository; -import com.yahoo.vespa.config.server.MockReloadHandler; import com.yahoo.vespa.config.server.TestComponentRegistry; +import com.yahoo.vespa.config.server.application.MemoryTenantApplications; import com.yahoo.vespa.config.server.application.OrchestratorMock; import com.yahoo.vespa.config.server.application.TenantApplications; import com.yahoo.vespa.config.server.http.CompressedApplicationInputStreamTest; @@ -18,7 +18,6 @@ import com.yahoo.vespa.config.server.http.SessionHandlerTest; import com.yahoo.vespa.config.server.session.LocalSessionRepo; import com.yahoo.vespa.config.server.tenant.TenantBuilder; import com.yahoo.vespa.config.server.tenant.TenantRepository; -import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.curator.mock.MockCurator; import org.junit.Before; import org.junit.Ignore; @@ -71,9 +70,8 @@ public class SessionCreateHandlerTest extends SessionHandlerTest { @Before public void setupRepo() { - Curator curator = new MockCurator(); - applicationRepo = TenantApplications.create(curator, new MockReloadHandler(), tenant); - localSessionRepo = new LocalSessionRepo(Clock.systemUTC(), curator); + applicationRepo = new MemoryTenantApplications(); + localSessionRepo = new LocalSessionRepo(Clock.systemUTC(), new MockCurator()); tenantRepository = new TenantRepository(componentRegistry, false); sessionFactory = new MockSessionFactory(); TenantBuilder tenantBuilder = TenantBuilder.create(componentRegistry, tenant) diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java index 11c0cf057cc..330d6592a2d 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java @@ -19,12 +19,11 @@ import com.yahoo.slime.Slime; import com.yahoo.transaction.NestedTransaction; import com.yahoo.transaction.Transaction; import com.yahoo.vespa.config.server.ApplicationRepository; -import com.yahoo.vespa.config.server.MockReloadHandler; import com.yahoo.vespa.config.server.TestComponentRegistry; import com.yahoo.vespa.config.server.application.ApplicationSet; import com.yahoo.vespa.config.server.application.OrchestratorMock; -import com.yahoo.vespa.config.server.application.TenantApplications; import com.yahoo.vespa.config.server.host.HostRegistry; +import com.yahoo.vespa.config.server.application.MemoryTenantApplications; import com.yahoo.vespa.config.server.configchange.ConfigChangeActions; import com.yahoo.vespa.config.server.configchange.MockRefeedAction; import com.yahoo.vespa.config.server.configchange.MockRestartAction; @@ -87,7 +86,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { .withSessionFactory(new MockSessionFactory()) .withLocalSessionRepo(localRepo) .withRemoteSessionRepo(remoteSessionRepo) - .withApplicationRepo(TenantApplications.create(curator, new MockReloadHandler(), tenant)); + .withApplicationRepo(new MemoryTenantApplications()); tenantRepository.addTenant(tenantBuilder); } 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 index 3626c6269cc..7b9389ada9b 100644 --- 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 @@ -5,9 +5,8 @@ import com.yahoo.config.model.application.provider.FilesApplicationPackage; import com.yahoo.test.ManualClock; import com.yahoo.config.provision.TenantName; 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.application.MemoryTenantApplications; import com.yahoo.vespa.config.server.deploy.TenantFileSystemDirs; import com.yahoo.io.IOUtils; import com.yahoo.vespa.config.server.host.HostRegistry; @@ -55,7 +54,7 @@ public class LocalSessionRepoTest { } clock = new ManualClock(Instant.ofEpochSecond(1)); LocalSessionLoader loader = new SessionFactoryImpl(globalComponentRegistry, - TenantApplications.create(new MockCurator(), new MockReloadHandler(), tenantName), + new MemoryTenantApplications(), tenantFileSystemDirs, new HostRegistry<>(), tenantName); repo = new LocalSessionRepo(tenantFileSystemDirs, loader, clock, 5, globalComponentRegistry.getCurator()); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java index e7db4dcf58f..37784b313b6 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java @@ -15,9 +15,8 @@ import com.yahoo.config.provision.TenantName; import com.yahoo.path.Path; import com.yahoo.slime.Slime; import com.yahoo.transaction.NestedTransaction; -import com.yahoo.vespa.config.server.MockReloadHandler; import com.yahoo.vespa.config.server.SuperModelGenerationCounter; -import com.yahoo.vespa.config.server.application.TenantApplications; +import com.yahoo.vespa.config.server.application.MemoryTenantApplications; import com.yahoo.vespa.config.server.deploy.DeployHandlerLogger; import com.yahoo.vespa.config.server.deploy.TenantFileSystemDirs; import com.yahoo.vespa.config.server.deploy.ZooKeeperClient; @@ -201,7 +200,7 @@ public class LocalSessionTest { FilesApplicationPackage.fromFile(testApp), zkc, sessionDir, - TenantApplications.create(curator, new MockReloadHandler(), tenant), + new MemoryTenantApplications(), new HostRegistry<>(), superModelGenerationCounter, flagSource)); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockSessionZKClient.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockSessionZKClient.java index e22185ae69b..2290ee5890b 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockSessionZKClient.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockSessionZKClient.java @@ -7,7 +7,6 @@ import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.TenantName; import com.yahoo.transaction.Transaction; import com.yahoo.path.Path; -import com.yahoo.vespa.config.server.session.Session.Status; import com.yahoo.vespa.config.server.tenant.TenantRepository; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.curator.mock.MockCurator; @@ -21,12 +20,12 @@ import java.util.Optional; */ public class MockSessionZKClient extends SessionZooKeeperClient { - private ApplicationPackage app; + private ApplicationPackage app = null; private Optional<AllocatedHosts> info = Optional.empty(); - private Status sessionStatus; + private Session.Status sessionStatus; public MockSessionZKClient(Curator curator, TenantName tenantName, long sessionId) { - this(curator, tenantName, sessionId, (ApplicationPackage) null); + this(curator, tenantName, sessionId, (ApplicationPackage)null); } public MockSessionZKClient(Curator curator, TenantName tenantName, long sessionId, Optional<AllocatedHosts> allocatedHosts) { @@ -37,11 +36,11 @@ public class MockSessionZKClient extends SessionZooKeeperClient { public MockSessionZKClient(Curator curator, TenantName tenantName, long sessionId, ApplicationPackage application) { super(curator, TenantRepository.getSessionsPath(tenantName).append(String.valueOf(sessionId))); this.app = application; - curator.create(TenantRepository.getSessionsPath(tenantName).append(String.valueOf(sessionId))); } public MockSessionZKClient(ApplicationPackage app) { - this(new MockCurator(), TenantName.defaultName(), 123, app); + super(new MockCurator(), Path.createRoot()); + this.app = app; } @Override @@ -55,4 +54,15 @@ public class MockSessionZKClient extends SessionZooKeeperClient { return info.orElseThrow(() -> new IllegalStateException("Could not find allocated hosts")); } + @Override + public Transaction createWriteStatusTransaction(Session.Status status) { + return new DummyTransaction().add((DummyTransaction.RunnableOperation) () -> { + sessionStatus = status; + }); + } + + @Override + public Session.Status readStatus() { + return sessionStatus; + } } 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 index 9dda653dbc1..4bbfea48254 100644 --- 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 @@ -6,11 +6,12 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; +import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.TenantName; import com.yahoo.path.Path; import com.yahoo.text.Utf8; +import com.yahoo.transaction.Transaction; -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.tenant.Tenant; @@ -24,6 +25,8 @@ import org.junit.Test; import com.yahoo.vespa.config.server.zookeeper.ConfigCurator; import java.time.Duration; +import java.util.Collections; +import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.LongPredicate; @@ -99,9 +102,8 @@ public class RemoteSessionRepoTest { @Test public void testBadApplicationRepoOnActivate() { long sessionId = 3L; + TenantApplications applicationRepo = new FailingTenantApplications(); TenantName mytenant = TenantName.from("mytenant"); - TenantApplications applicationRepo = TenantApplications.create(curator, new MockReloadHandler(), mytenant); - curator.set(TenantRepository.getApplicationsPath(mytenant).append("mytenant:appX:default"), new byte[0]); // Invalid data Tenant tenant = TenantBuilder.create(new TestComponentRegistry.Builder().curator(curator).build(), mytenant) .withApplicationRepo(applicationRepo) .build(); @@ -149,4 +151,36 @@ public class RemoteSessionRepoTest { } while (System.currentTimeMillis() < endTime && !ok); } + private class FailingTenantApplications implements TenantApplications { + + @Override + public List<ApplicationId> listApplications() { + return Collections.singletonList(ApplicationId.defaultId()); + } + + @Override + public Transaction createPutApplicationTransaction(ApplicationId applicationId, long sessionId) { + return null; + } + + @Override + public long getSessionIdForApplication(ApplicationId applicationId) { + throw new IllegalArgumentException("Bad id " + applicationId); + } + + @Override + public Transaction deleteApplication(ApplicationId applicationId) { + return null; + } + + @Override + public void removeUnusedApplications() { + // do nothing + } + + @Override + public void close() { + + } + } } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java index 9ad90e84d86..ac7d6948b22 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java @@ -15,12 +15,11 @@ import com.yahoo.log.LogLevel; import com.yahoo.path.Path; import com.yahoo.slime.Slime; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.vespa.config.server.MockReloadHandler; import com.yahoo.vespa.config.server.SuperModelGenerationCounter; import com.yahoo.vespa.config.server.TestComponentRegistry; import com.yahoo.vespa.config.server.TimeoutBudgetTest; +import com.yahoo.vespa.config.server.application.MemoryTenantApplications; import com.yahoo.vespa.config.server.application.PermanentApplicationPackage; -import com.yahoo.vespa.config.server.application.TenantApplications; import com.yahoo.vespa.config.server.deploy.DeployHandlerLogger; import com.yahoo.vespa.config.server.host.HostRegistry; import com.yahoo.vespa.config.server.http.InvalidApplicationException; @@ -217,8 +216,7 @@ public class SessionPreparerTest { return new SessionContext(app, new SessionZooKeeperClient(curator, sessionsPath), app.getAppDir(), - TenantApplications.create(curator, new MockReloadHandler(), TenantName.from("tenant")), - new HostRegistry<>(), + new MemoryTenantApplications(), new HostRegistry<>(), new SuperModelGenerationCounter(curator), flagSource); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantTest.java index baab250a508..1b3afeb353b 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantTest.java @@ -3,22 +3,19 @@ package com.yahoo.vespa.config.server.tenant; import com.google.common.testing.EqualsTester; import com.yahoo.config.provision.TenantName; -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.curator.mock.MockCurator; +import com.yahoo.vespa.config.server.application.MemoryTenantApplications; import org.junit.Before; import org.junit.Test; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; +import static org.junit.Assert.*; /** * @author Ulf Lilleengen */ public class TenantTest { - private final TestComponentRegistry componentRegistry = new TestComponentRegistry.Builder().build(); private Tenant t1; @@ -38,7 +35,7 @@ public class TenantTest { TenantRepository tenantRepository = new TenantRepository(componentRegistry, false); TenantName tenantName = TenantName.from(name); TenantBuilder tenantBuilder = TenantBuilder.create(componentRegistry, tenantName) - .withApplicationRepo(TenantApplications.create(new MockCurator(), new MockReloadHandler(), tenantName)); + .withApplicationRepo(new MemoryTenantApplications()); tenantRepository.addTenant(tenantBuilder); return tenantRepository.getTenant(tenantName); } @@ -59,4 +56,11 @@ public class TenantTest { assertThat(t1.hashCode(), is(not(t4.hashCode()))); } + @Test + public void close() { + MemoryTenantApplications repo = (MemoryTenantApplications) t1.getApplicationRepo(); + assertTrue(repo.isOpen()); + t1.close(); + assertFalse(repo.isOpen()); + } } diff --git a/container-core/src/main/java/com/yahoo/container/handler/LogReader.java b/container-core/src/main/java/com/yahoo/container/handler/LogReader.java index 76ea4579244..c6ae20973fb 100644 --- a/container-core/src/main/java/com/yahoo/container/handler/LogReader.java +++ b/container-core/src/main/java/com/yahoo/container/handler/LogReader.java @@ -4,11 +4,11 @@ package com.yahoo.container.handler; import org.json.JSONException; import org.json.JSONObject; +import java.time.Duration; import java.util.Base64; import java.io.File; import java.io.IOException; import java.nio.file.Files; -import java.nio.file.attribute.BasicFileAttributes; public class LogReader { @@ -17,7 +17,7 @@ public class LogReader { protected JSONObject readLogs(String logDirectory, long earliestLogThreshold, long latestLogThreshold) throws IOException, JSONException { this.earliestLogThreshold = earliestLogThreshold; - this.latestLogThreshold = latestLogThreshold; + this.latestLogThreshold = latestLogThreshold + Duration.ofMinutes(5).toMillis(); // Add some time to allow retrieving logs currently being modified JSONObject json = new JSONObject(); File root = new File(logDirectory); traverse_folder(root, json, ""); @@ -27,7 +27,7 @@ public class LogReader { private void traverse_folder(File root, JSONObject json, String filename) throws IOException, JSONException { File[] files = root.listFiles(); for(File child : files) { - long logTime = Files.readAttributes(child.toPath(), BasicFileAttributes.class).creationTime().toMillis(); + long logTime = Files.getLastModifiedTime(child.toPath()).toMillis(); if(child.isFile() && earliestLogThreshold < logTime && logTime < latestLogThreshold) { json.put(filename + child.getName(), Base64.getEncoder().encodeToString(Files.readAllBytes(child.toPath()))); } diff --git a/container-core/src/test/java/com/yahoo/container/handler/LogReaderTest.java b/container-core/src/test/java/com/yahoo/container/handler/LogReaderTest.java index 3b1be62dfb1..d7c802faa3b 100644 --- a/container-core/src/test/java/com/yahoo/container/handler/LogReaderTest.java +++ b/container-core/src/test/java/com/yahoo/container/handler/LogReaderTest.java @@ -6,6 +6,7 @@ import org.junit.Before; import org.junit.Test; import java.io.ByteArrayOutputStream; +import java.time.Instant; import static org.junit.Assert.*; @@ -22,7 +23,7 @@ public class LogReaderTest { public void testThatFilesAreWrittenCorrectlyToOutputStream() throws Exception{ String logDirectory = "src/test/resources/logfolder/"; LogReader logReader = new LogReader(); - JSONObject json = logReader.readLogs(logDirectory, 21, Long.MAX_VALUE); + JSONObject json = logReader.readLogs(logDirectory, 21, Instant.now().toEpochMilli()); String expected = "{\"subfolder-log2.log\":\"VGhpcyBpcyBhbm90aGVyIGxvZyBmaWxl\",\"log1.log\":\"VGhpcyBpcyBvbmUgbG9nIGZpbGU=\"}"; String actual = json.toString(); assertEquals(expected, actual); diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/DefaultSslContextFactoryProvider.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/DefaultSslContextFactoryProvider.java index 03796c551e5..0bbe6207294 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/DefaultSslContextFactoryProvider.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/DefaultSslContextFactoryProvider.java @@ -30,6 +30,6 @@ public class DefaultSslContextFactoryProvider extends AbstractComponent implemen @Override public void deconstruct() { - tlsContext.close(); + if (tlsContext != null) tlsContext.close(); } }
\ No newline at end of file diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ApplicationMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ApplicationMaintainer.java index f729c30de20..5e5b3546ced 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ApplicationMaintainer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ApplicationMaintainer.java @@ -12,15 +12,12 @@ import com.yahoo.vespa.hosted.provision.NodeRepository; import java.time.Duration; import java.time.Instant; -import java.util.LinkedHashSet; -import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; /** * @author bratseth @@ -75,17 +72,8 @@ public abstract class ApplicationMaintainer extends Maintainer { protected Deployer deployer() { return deployer; } - protected Set<ApplicationId> applicationsNeedingMaintenance() { - return nodesNeedingMaintenance().stream() - .map(node -> node.allocation().get().owner()) - .collect(Collectors.toCollection(LinkedHashSet::new)); - } - - /** - * Returns the nodes whose applications should be maintained by this now. - * This should be some subset of the allocated nodes. - */ - protected abstract List<Node> nodesNeedingMaintenance(); + /** Returns the applications that should be maintained by this now. */ + protected abstract Set<ApplicationId> applicationsNeedingMaintenance(); /** Redeploy this application. A lock will be taken for the duration of the deployment activation */ protected final void deployWithLock(ApplicationId application) { diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/OperatorChangeApplicationMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/OperatorChangeApplicationMaintainer.java index 45db9ea90d9..b9e27e5d4ae 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/OperatorChangeApplicationMaintainer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/OperatorChangeApplicationMaintainer.java @@ -3,14 +3,18 @@ package com.yahoo.vespa.hosted.provision.maintenance; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Deployer; +import com.yahoo.config.provision.NodeType; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.node.Agent; +import com.yahoo.vespa.hosted.provision.node.Allocation; import java.time.Clock; import java.time.Duration; import java.time.Instant; -import java.util.List; +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; /** @@ -26,6 +30,8 @@ import java.util.stream.Collectors; */ public class OperatorChangeApplicationMaintainer extends ApplicationMaintainer { + private static final ApplicationId ZONE_APPLICATION_ID = ApplicationId.from("hosted-vespa", "routing", "default"); + private final Clock clock; private Instant previousRun; @@ -38,22 +44,28 @@ public class OperatorChangeApplicationMaintainer extends ApplicationMaintainer { } @Override - protected List<Node> nodesNeedingMaintenance() { + protected Set<ApplicationId> applicationsNeedingMaintenance() { Instant windowEnd = clock.instant(); Instant windowStart = previousRun; previousRun = windowEnd; return nodeRepository().getNodes().stream() - .filter(node -> node.allocation().isPresent()) .filter(node -> hasManualStateChangeSince(windowStart, node)) - .collect(Collectors.toList()); + .flatMap(node -> owner(node).stream()) + .collect(Collectors.toCollection(LinkedHashSet::new)); } - + private boolean hasManualStateChangeSince(Instant instant, Node node) { return node.history().events().stream() .anyMatch(event -> event.agent() == Agent.operator && event.at().isAfter(instant)); } - /** + private Optional<ApplicationId> owner(Node node) { + if (node.allocation().isPresent()) return node.allocation().map(Allocation::owner); + + return node.type() == NodeType.host ? Optional.of(ZONE_APPLICATION_ID) : Optional.empty(); + } + + /** * Deploy in the maintenance thread to avoid scheduling multiple deployments of the same application if it takes * longer to deploy than the (short) maintenance interval of this */ diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainer.java index ff0773fde71..2525dbecca2 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainer.java @@ -75,7 +75,6 @@ public class PeriodicApplicationMaintainer extends ApplicationMaintainer { return clock.instant().isBefore(start.plus(minTimeBetweenRedeployments)); } - @Override protected List<Node> nodesNeedingMaintenance() { return nodeRepository().getNodes(Node.State.active); } |