diff options
59 files changed, 1056 insertions, 837 deletions
diff --git a/build_settings.cmake b/build_settings.cmake index 0028935ad18..d7dd26f5ee7 100644 --- a/build_settings.cmake +++ b/build_settings.cmake @@ -55,7 +55,9 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" ST else() set(CXX_SPECIFIC_WARN_OPTS "-Wsuggest-override -Wnon-virtual-dtor -Wformat-security") if(VESPA_OS_DISTRO_COMBINED STREQUAL "centos 8" OR - VESPA_OS_DISTRO_COMBINED STREQUAL "rhel 8.1") + (VESPA_OS_DISTRO STREQUAL "rhel" AND + VESPA_OS_DISTRO_VERSION VERSION_GREATER_EQUAL "8" AND + VESPA_OS_DISTRO_VERSION VERSION_LESS "9")) set(VESPA_ATOMIC_LIB "") else() set(VESPA_ATOMIC_LIB "atomic") diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintenance.java b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintenance.java index 502cf280e60..afcfe04f4ac 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintenance.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintenance.java @@ -34,7 +34,7 @@ public class ConfigServerMaintenance extends AbstractComponent { // TODO: Disabled until we have application metadata //tenantsMaintainer = new TenantsMaintainer(applicationRepository, curator, defaults.tenantsMaintainerInterval); fileDistributionMaintainer = new FileDistributionMaintainer(applicationRepository, curator, defaults.defaultInterval, configserverConfig); - sessionsMaintainer = new SessionsMaintainer(applicationRepository, curator, defaults.defaultInterval, flagSource); + sessionsMaintainer = new SessionsMaintainer(applicationRepository, curator, defaults.defaultInterval); } @Override 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 250548d5e91..4975b82a801 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 @@ -4,9 +4,6 @@ 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; -import com.yahoo.vespa.flags.Flags; -import com.yahoo.vespa.flags.LongFlag; import java.time.Duration; @@ -19,14 +16,12 @@ import java.time.Duration; */ public class SessionsMaintainer extends ConfigServerMaintainer { private final boolean hostedVespa; - private final LongFlag expiryTimeFlag; - SessionsMaintainer(ApplicationRepository applicationRepository, Curator curator, Duration interval, FlagSource flagSource) { + SessionsMaintainer(ApplicationRepository applicationRepository, Curator curator, Duration interval) { // Start this maintainer immediately. It frees disk space, so if disk goes full and config server // restarts this makes sure that cleanup will happen as early as possible super(applicationRepository, curator, Duration.ZERO, interval); this.hostedVespa = applicationRepository.configserverConfig().hostedVespa(); - this.expiryTimeFlag = Flags.CONFIGSERVER_SESSIONS_EXPIRY_INTERVAL_IN_DAYS.bindTo(flagSource); } @Override @@ -36,7 +31,7 @@ public class SessionsMaintainer extends ConfigServerMaintainer { // Expired remote sessions are sessions that belong to an application that have external deployments that // are no longer active if (hostedVespa) { - Duration expiryTime = Duration.ofDays(expiryTimeFlag.value()); + Duration expiryTime = Duration.ofDays(1); int deleted = applicationRepository.deleteExpiredRemoteSessions(expiryTime); log.log(LogLevel.FINE, "Deleted " + deleted + " expired remote sessions, expiry time " + expiryTime); } 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 index 831f4ba3679..56e32f7d802 100644 --- 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 @@ -5,7 +5,6 @@ import com.yahoo.config.application.api.ApplicationFile; import com.yahoo.config.application.api.ApplicationMetaData; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.application.api.DeployLogger; -import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.TenantName; import com.yahoo.io.IOUtils; @@ -18,7 +17,6 @@ import com.yahoo.vespa.config.server.application.ApplicationSet; import com.yahoo.vespa.config.server.application.TenantApplications; import com.yahoo.vespa.config.server.configchange.ConfigChangeActions; import com.yahoo.vespa.config.server.host.HostValidator; -import com.yahoo.vespa.config.server.tenant.TenantRepository; import com.yahoo.vespa.curator.Curator; import java.io.File; @@ -35,7 +33,7 @@ import java.util.logging.Level; */ // 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 implements Comparable<LocalSession> { +public class LocalSession extends Session { protected final ApplicationPackage applicationPackage; private final TenantApplications applicationRepo; @@ -118,13 +116,6 @@ public class LocalSession extends Session implements Comparable<LocalSession> { transaction.add(FileTransaction.from(FileOperations.delete(serverDBSessionDir.getAbsolutePath()))); } - @Override - public int compareTo(LocalSession rhs) { - Long lhsId = getSessionId(); - Long rhsId = rhs.getSessionId(); - return lhsId.compareTo(rhsId); - } - public void waitUntilActivated(TimeoutBudget timeoutBudget) { zooKeeperClient.getActiveWaiter().awaitCompletion(timeoutBudget.timeLeft()); } @@ -137,21 +128,6 @@ public class LocalSession extends Session implements Comparable<LocalSession> { return applicationPackage.getMetaData(); } - public AllocatedHosts getAllocatedHosts() { - return zooKeeperClient.getAllocatedHosts(); - } - - public TenantName getTenantName() { return tenant; } - - @Override - public String logPre() { - if (getApplicationId().equals(ApplicationId.defaultId())) { - return TenantRepository.logPre(getTenant()); - } else { - return TenantRepository.logPre(getApplicationId()); - } - } - // The rest of this class should be moved elsewhere ... private static class FileTransaction extends AbstractTransaction { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionLoader.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionLoader.java deleted file mode 100644 index b82ac22e88e..00000000000 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionLoader.java +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.config.server.session; - -/** - * Interface of a component that is able to load a session given a session id. - * - * @author Ulf Lilleengen - */ -public interface LocalSessionLoader { - - LocalSession loadSession(long sessionId); - -} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionRepo.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionRepo.java index 3f20d3669cb..b6a9c8c0854 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionRepo.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionRepo.java @@ -46,7 +46,7 @@ public class LocalSessionRepo { private final TenantFileSystemDirs tenantFileSystemDirs; private final LongFlag expiryTimeFlag; - public LocalSessionRepo(TenantName tenantName, GlobalComponentRegistry componentRegistry, LocalSessionLoader loader) { + public LocalSessionRepo(TenantName tenantName, GlobalComponentRegistry componentRegistry, SessionFactory sessionFactory) { sessionCache = new SessionCache<>(); this.clock = componentRegistry.getClock(); this.curator = componentRegistry.getCurator(); @@ -54,7 +54,7 @@ public class LocalSessionRepo { this.zkWatcherExecutor = command -> componentRegistry.getZkWatcherExecutor().execute(tenantName, command); this.tenantFileSystemDirs = new TenantFileSystemDirs(componentRegistry.getConfigServerDB(), tenantName); this.expiryTimeFlag = Flags.CONFIGSERVER_LOCAL_SESSIONS_EXPIRY_INTERVAL_IN_DAYS.bindTo(componentRegistry.getFlagSource()); - loadSessions(loader); + loadSessions(sessionFactory); } public synchronized void addSession(LocalSession session) { @@ -73,14 +73,14 @@ public class LocalSessionRepo { return sessionCache.getSessions(); } - private void loadSessions(LocalSessionLoader loader) { + private void loadSessions(SessionFactory sessionFactory) { File[] sessions = tenantFileSystemDirs.sessionsPath().listFiles(sessionApplicationsFilter); if (sessions == null) { return; } for (File session : sessions) { try { - addSession(loader.loadSession(Long.parseLong(session.getName()))); + addSession(sessionFactory.createSessionFromId(Long.parseLong(session.getName()))); } catch (IllegalArgumentException e) { log.log(Level.WARNING, "Could not load session '" + session.getAbsolutePath() + "':" + e.getMessage() + ", skipping it."); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java index d0082d34114..c1179a2dd17 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java @@ -4,7 +4,6 @@ package com.yahoo.vespa.config.server.session; import com.yahoo.config.application.api.ApplicationMetaData; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.provision.AllocatedHosts; -import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.TenantName; import com.yahoo.lang.SettableOptional; import com.yahoo.transaction.Transaction; @@ -12,7 +11,6 @@ import com.yahoo.vespa.config.server.GlobalComponentRegistry; import com.yahoo.vespa.config.server.ReloadHandler; import com.yahoo.vespa.config.server.application.ApplicationSet; import com.yahoo.vespa.config.server.modelfactory.ActivatedModelsBuilder; -import com.yahoo.vespa.config.server.tenant.TenantRepository; import com.yahoo.vespa.curator.Curator; import org.apache.zookeeper.KeeperException; @@ -75,10 +73,6 @@ public class RemoteSession extends Session { return applicationSet == null ? applicationSet = loadApplication() : applicationSet; } - public Session.Status getStatus() { - return zooKeeperClient.readStatus(); - } - public synchronized void deactivate() { applicationSet = null; } @@ -98,15 +92,6 @@ public class RemoteSession extends Session { log.log(Level.INFO, logPre() + "Session activated: " + getSessionId()); } - @Override - public String logPre() { - if (getApplicationId().equals(ApplicationId.defaultId())) { - return TenantRepository.logPre(getTenant()); - } else { - return TenantRepository.logPre(getApplicationId()); - } - } - void confirmUpload() { Curator.CompletionWaiter waiter = zooKeeperClient.getUploadWaiter(); log.log(Level.FINE, "Notifying upload waiter for session " + getSessionId()); @@ -136,10 +121,6 @@ public class RemoteSession extends Session { transaction.close(); } - public AllocatedHosts getAllocatedHosts() { - return zooKeeperClient.getAllocatedHosts(); - } - public ApplicationMetaData getMetaData() { return zooKeeperClient.loadApplicationPackage().getMetaData(); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionFactory.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionFactory.java deleted file mode 100644 index 0707260dffd..00000000000 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionFactory.java +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.config.server.session; - -import com.yahoo.config.provision.TenantName; -import com.yahoo.path.Path; -import com.yahoo.cloud.config.ConfigserverConfig; -import com.yahoo.vespa.config.server.GlobalComponentRegistry; -import com.yahoo.vespa.config.server.tenant.TenantRepository; -import com.yahoo.vespa.config.server.zookeeper.ConfigCurator; -import com.yahoo.vespa.curator.Curator; - -/** - * @author Ulf Lilleengen - */ -public class RemoteSessionFactory { - - private final GlobalComponentRegistry componentRegistry; - private final Curator curator; - private final ConfigCurator configCurator; - private final Path sessionsPath; - private final TenantName tenant; - private final ConfigserverConfig configserverConfig; - - public RemoteSessionFactory(GlobalComponentRegistry componentRegistry, TenantName tenant) { - this.componentRegistry = componentRegistry; - this.curator = componentRegistry.getCurator(); - this.configCurator = componentRegistry.getConfigCurator(); - this.sessionsPath = TenantRepository.getSessionsPath(tenant); - this.tenant = tenant; - this.configserverConfig = componentRegistry.getConfigserverConfig(); - } - - public RemoteSession createSession(long sessionId) { - Path sessionPath = this.sessionsPath.append(String.valueOf(sessionId)); - SessionZooKeeperClient sessionZKClient = new SessionZooKeeperClient(curator, - configCurator, - sessionPath, - configserverConfig.serverId(), - componentRegistry.getZone().nodeFlavors()); - return new RemoteSession(tenant, sessionId, componentRegistry, sessionZKClient); - } - -} 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 de5af7994ec..316f7f7778d 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 @@ -48,7 +48,7 @@ public class RemoteSessionRepo { private final Curator curator; private final Path sessionsPath; - private final RemoteSessionFactory remoteSessionFactory; + private final SessionFactory sessionFactory; private final Map<Long, RemoteSessionStateWatcher> sessionStateWatchers = new HashMap<>(); private final ReloadHandler reloadHandler; private final TenantName tenantName; @@ -59,7 +59,7 @@ public class RemoteSessionRepo { private final SessionCache<RemoteSession> sessionCache; public RemoteSessionRepo(GlobalComponentRegistry componentRegistry, - RemoteSessionFactory remoteSessionFactory, + SessionFactory sessionFactory, ReloadHandler reloadHandler, TenantName tenantName, TenantApplications applicationRepo) { @@ -67,7 +67,7 @@ public class RemoteSessionRepo { this.curator = componentRegistry.getCurator(); this.sessionsPath = TenantRepository.getSessionsPath(tenantName); this.applicationRepo = applicationRepo; - this.remoteSessionFactory = remoteSessionFactory; + this.sessionFactory = sessionFactory; this.reloadHandler = reloadHandler; this.tenantName = tenantName; this.metrics = componentRegistry.getMetrics().getOrCreateMetricUpdater(Metrics.createDimensions(tenantName)); @@ -149,7 +149,7 @@ public class RemoteSessionRepo { */ private void sessionAdded(long sessionId) { log.log(Level.FINE, () -> "Adding session to RemoteSessionRepo: " + sessionId); - RemoteSession session = remoteSessionFactory.createSession(sessionId); + RemoteSession session = sessionFactory.createRemoteSession(sessionId); Path sessionPath = sessionsPath.append(String.valueOf(sessionId)); Curator.FileCache fileCache = curator.createFileCache(sessionPath.append(ConfigCurator.SESSIONSTATE_ZK_SUBPATH).getAbsolute(), false); fileCache.addListener(this::nodeChanged); 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 7803bd05e0a..8b078f152f3 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 @@ -21,7 +21,7 @@ import java.util.Optional; * * @author Ulf Lilleengen */ -public abstract class Session { +public abstract class Session implements Comparable<Session> { private final long sessionId; protected final TenantName tenant; @@ -64,17 +64,19 @@ public abstract class Session { return Status.NEW; } } - - public TenantName getTenant() { - return tenant; - } + + public TenantName getTenantName() { return tenant; } /** * Helper to provide a log message preamble for code dealing with sessions * @return log preamble */ public String logPre() { - return TenantRepository.logPre(getTenant()); + if (getApplicationId().equals(ApplicationId.defaultId())) { + return TenantRepository.logPre(getTenantName()); + } else { + return TenantRepository.logPre(getApplicationId()); + } } public Instant getCreateTime() { @@ -128,4 +130,11 @@ public abstract class Session { // Note: Assumes monotonically increasing session ids public boolean isNewerThan(long sessionId) { return getSessionId() > sessionId; } + @Override + public int compareTo(Session rhs) { + Long lhsId = getSessionId(); + Long rhsId = rhs.getSessionId(); + return lhsId.compareTo(rhsId); + } + } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionFactory.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionFactory.java index 6b51abb7cca..16bb32a19f2 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionFactory.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionFactory.java @@ -1,18 +1,76 @@ // 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.application.api.DeployLogger; +import com.yahoo.config.model.application.provider.DeployData; +import com.yahoo.config.model.application.provider.FilesApplicationPackage; import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.NodeFlavors; +import com.yahoo.config.provision.TenantName; +import com.yahoo.io.IOUtils; +import com.yahoo.path.Path; +import com.yahoo.vespa.config.server.GlobalComponentRegistry; import com.yahoo.vespa.config.server.TimeoutBudget; +import com.yahoo.vespa.config.server.application.TenantApplications; +import com.yahoo.vespa.config.server.deploy.TenantFileSystemDirs; +import com.yahoo.vespa.config.server.host.HostValidator; +import com.yahoo.vespa.config.server.tenant.TenantRepository; +import com.yahoo.vespa.config.server.zookeeper.ConfigCurator; +import com.yahoo.vespa.config.server.zookeeper.SessionCounter; +import com.yahoo.vespa.curator.Curator; +import com.yahoo.vespa.flags.BooleanFlag; +import com.yahoo.vespa.flags.Flags; import java.io.File; +import java.time.Clock; +import java.util.List; +import java.util.Optional; +import java.util.logging.Level; +import java.util.logging.Logger; /** - * A session factory responsible for creating deploy sessions. + * Serves as the factory of sessions. Takes care of copying files to the correct folder and initializing the + * session state. * * @author Ulf Lilleengen */ -public interface SessionFactory { +public class SessionFactory { + + private static final Logger log = Logger.getLogger(SessionFactory.class.getName()); + private static final long nonExistingActiveSession = 0; + + private final SessionPreparer sessionPreparer; + private final Curator curator; + private final ConfigCurator configCurator; + private final TenantApplications applicationRepo; + private final Path sessionsPath; + private final GlobalComponentRegistry componentRegistry; + private final HostValidator<ApplicationId> hostRegistry; + private final TenantName tenant; + private final String serverId; + private final Optional<NodeFlavors> nodeFlavors; + private final Clock clock; + private final BooleanFlag distributeApplicationPackage; + + public SessionFactory(GlobalComponentRegistry globalComponentRegistry, + TenantApplications applicationRepo, + HostValidator<ApplicationId> hostRegistry, + TenantName tenant) { + this.hostRegistry = hostRegistry; + this.tenant = tenant; + this.sessionPreparer = globalComponentRegistry.getSessionPreparer(); + this.curator = globalComponentRegistry.getCurator(); + this.configCurator = globalComponentRegistry.getConfigCurator(); + this.sessionsPath = TenantRepository.getSessionsPath(tenant); + this.applicationRepo = applicationRepo; + this.componentRegistry = globalComponentRegistry; + this.serverId = globalComponentRegistry.getConfigserverConfig().serverId(); + this.nodeFlavors = globalComponentRegistry.getZone().nodeFlavors(); + this.clock = globalComponentRegistry.getClock(); + this.distributeApplicationPackage = Flags.CONFIGSERVER_DISTRIBUTE_APPLICATION_PACKAGE + .bindTo(globalComponentRegistry.getFlagSource()); + } /** * Creates a new deployment session from an application package. @@ -22,7 +80,52 @@ public interface SessionFactory { * @param timeoutBudget Timeout for creating session and waiting for other servers. * @return a new session */ - LocalSession createSession(File applicationDirectory, ApplicationId applicationId, TimeoutBudget timeoutBudget); + public LocalSession createSession(File applicationDirectory, ApplicationId applicationId, TimeoutBudget timeoutBudget) { + return create(applicationDirectory, applicationId, nonExistingActiveSession, false, timeoutBudget); + } + + + public RemoteSession createRemoteSession(long sessionId) { + Path sessionPath = sessionsPath.append(String.valueOf(sessionId)); + SessionZooKeeperClient sessionZKClient = createSessionZooKeeperClient(sessionPath); + return new RemoteSession(tenant, sessionId, componentRegistry, sessionZKClient); + } + + private void ensureSessionPathDoesNotExist(long sessionId) { + Path sessionPath = getSessionPath(sessionId); + if (configCurator.exists(sessionPath.getAbsolute())) { + throw new IllegalArgumentException("Path " + sessionPath.getAbsolute() + " already exists in ZooKeeper"); + } + } + + private ApplicationPackage createApplication(File userDir, + File configApplicationDir, + ApplicationId applicationId, + long sessionId, + long currentlyActiveSessionId, + boolean internalRedeploy) { + long deployTimestamp = System.currentTimeMillis(); + String user = System.getenv("USER"); + if (user == null) { + user = "unknown"; + } + DeployData deployData = new DeployData(user, userDir.getAbsolutePath(), applicationId, deployTimestamp, internalRedeploy, sessionId, currentlyActiveSessionId); + return FilesApplicationPackage.fromFileWithDeployData(configApplicationDir, deployData); + } + + private LocalSession createSessionFromApplication(ApplicationPackage applicationPackage, + long sessionId, + SessionZooKeeperClient sessionZKClient, + TimeoutBudget timeoutBudget, + Clock clock) { + log.log(Level.FINE, TenantRepository.logPre(tenant) + "Creating session " + sessionId + " in ZooKeeper"); + sessionZKClient.createNewSession(clock.instant()); + Curator.CompletionWaiter waiter = sessionZKClient.getUploadWaiter(); + LocalSession session = new LocalSession(tenant, sessionId, sessionPreparer, applicationPackage, sessionZKClient, + getSessionAppDir(sessionId), applicationRepo, hostRegistry); + waiter.awaitCompletion(timeoutBudget.timeLeft()); + return session; + } /** * Creates a new deployment session from an already existing session. @@ -33,7 +136,90 @@ public interface SessionFactory { * @param timeoutBudget timeout for creating session and waiting for other servers. * @return a new session */ - LocalSession createSessionFromExisting(Session existingSession, DeployLogger logger, - boolean internalRedeploy, TimeoutBudget timeoutBudget); + public LocalSession createSessionFromExisting(Session existingSession, + DeployLogger logger, + boolean internalRedeploy, + TimeoutBudget timeoutBudget) { + File existingApp = getSessionAppDir(existingSession.getSessionId()); + ApplicationId existingApplicationId = existingSession.getApplicationId(); + + long activeSessionId = getActiveSessionId(existingApplicationId); + logger.log(Level.FINE, "Create new session for application id '" + existingApplicationId + "' from existing active session " + activeSessionId); + LocalSession session = create(existingApp, existingApplicationId, activeSessionId, internalRedeploy, timeoutBudget); + // Note: Needs to be kept in sync with calls in SessionPreparer.writeStateToZooKeeper() + session.setApplicationId(existingApplicationId); + if (distributeApplicationPackage.value() && existingSession.getApplicationPackageReference() != null) { + session.setApplicationPackageReference(existingSession.getApplicationPackageReference()); + } + session.setVespaVersion(existingSession.getVespaVersion()); + session.setDockerImageRepository(existingSession.getDockerImageRepository()); + session.setAthenzDomain(existingSession.getAthenzDomain()); + return session; + } + + private LocalSession create(File applicationFile, ApplicationId applicationId, long currentlyActiveSessionId, + boolean internalRedeploy, TimeoutBudget timeoutBudget) { + long sessionId = getNextSessionId(); + try { + ensureSessionPathDoesNotExist(sessionId); + SessionZooKeeperClient sessionZooKeeperClient = createSessionZooKeeperClient(getSessionPath(sessionId)); + File userApplicationDir = getSessionAppDir(sessionId); + IOUtils.copyDirectory(applicationFile, userApplicationDir); + ApplicationPackage applicationPackage = createApplication(applicationFile, + userApplicationDir, + applicationId, + sessionId, + currentlyActiveSessionId, + internalRedeploy); + applicationPackage.writeMetaData(); + return createSessionFromApplication(applicationPackage, sessionId, sessionZooKeeperClient, timeoutBudget, clock); + } catch (Exception e) { + throw new RuntimeException("Error creating session " + sessionId, e); + } + } + + /** + * Returns a new session instance for the given session id. + */ + LocalSession createSessionFromId(long sessionId) { + File sessionDir = getAndValidateExistingSessionAppDir(sessionId); + ApplicationPackage applicationPackage = FilesApplicationPackage.fromFile(sessionDir); + Path sessionIdPath = sessionsPath.append(String.valueOf(sessionId)); + SessionZooKeeperClient sessionZKClient = createSessionZooKeeperClient(sessionIdPath); + return new LocalSession(tenant, sessionId, sessionPreparer, applicationPackage, sessionZKClient, + getSessionAppDir(sessionId), applicationRepo, hostRegistry); + } + + private long getActiveSessionId(ApplicationId applicationId) { + List<ApplicationId> applicationIds = applicationRepo.activeApplications(); + if (applicationIds.contains(applicationId)) { + return applicationRepo.requireActiveSessionOf(applicationId); + } + return nonExistingActiveSession; + } + + private long getNextSessionId() { + return new SessionCounter(componentRegistry.getConfigCurator(), tenant).nextSessionId(); + } + + private Path getSessionPath(long sessionId) { + return sessionsPath.append(String.valueOf(sessionId)); + } + + private SessionZooKeeperClient createSessionZooKeeperClient(Path sessionPath) { + return new SessionZooKeeperClient(curator, configCurator, sessionPath, serverId, nodeFlavors); + } + + private File getAndValidateExistingSessionAppDir(long sessionId) { + File appDir = getSessionAppDir(sessionId); + if (!appDir.exists() || !appDir.isDirectory()) { + throw new IllegalArgumentException("Unable to find correct application directory for session " + sessionId); + } + return appDir; + } + + private File getSessionAppDir(long sessionId) { + return new TenantFileSystemDirs(componentRegistry.getConfigServerDB(), tenant).getUserApplicationDir(sessionId); + } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionFactoryImpl.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionFactoryImpl.java deleted file mode 100644 index 558b17131a3..00000000000 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionFactoryImpl.java +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.config.server.session; - -import com.yahoo.config.application.api.ApplicationPackage; -import com.yahoo.config.application.api.DeployLogger; -import com.yahoo.config.model.application.provider.*; -import com.yahoo.config.provision.NodeFlavors; -import com.yahoo.io.IOUtils; -import java.util.logging.Level; -import com.yahoo.path.Path; -import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.TenantName; -import com.yahoo.vespa.config.server.GlobalComponentRegistry; -import com.yahoo.vespa.config.server.TimeoutBudget; -import com.yahoo.vespa.config.server.application.TenantApplications; -import com.yahoo.vespa.config.server.deploy.TenantFileSystemDirs; -import com.yahoo.vespa.config.server.host.HostValidator; -import com.yahoo.vespa.config.server.tenant.TenantRepository; -import com.yahoo.vespa.config.server.zookeeper.SessionCounter; -import com.yahoo.vespa.config.server.zookeeper.ConfigCurator; -import com.yahoo.vespa.curator.Curator; -import com.yahoo.vespa.flags.BooleanFlag; -import com.yahoo.vespa.flags.Flags; - -import java.io.File; -import java.time.Clock; -import java.util.List; -import java.util.Optional; -import java.util.logging.Logger; - -/** - * Serves as the factory of sessions. Takes care of copying files to the correct folder and initializing the - * session state. - * - * @author Ulf Lilleengen - */ -public class SessionFactoryImpl implements SessionFactory, LocalSessionLoader { - - private static final Logger log = Logger.getLogger(SessionFactoryImpl.class.getName()); - private static final long nonExistingActiveSession = 0; - - private final SessionPreparer sessionPreparer; - private final Curator curator; - private final ConfigCurator configCurator; - private final TenantApplications applicationRepo; - private final Path sessionsPath; - private final GlobalComponentRegistry componentRegistry; - private final HostValidator<ApplicationId> hostRegistry; - private final TenantName tenant; - private final String serverId; - private final Optional<NodeFlavors> nodeFlavors; - private final Clock clock; - private final BooleanFlag distributeApplicationPackage; - - public SessionFactoryImpl(GlobalComponentRegistry globalComponentRegistry, - TenantApplications applicationRepo, - HostValidator<ApplicationId> hostRegistry, - TenantName tenant) { - this.hostRegistry = hostRegistry; - this.tenant = tenant; - this.sessionPreparer = globalComponentRegistry.getSessionPreparer(); - this.curator = globalComponentRegistry.getCurator(); - this.configCurator = globalComponentRegistry.getConfigCurator(); - this.sessionsPath = TenantRepository.getSessionsPath(tenant); - this.applicationRepo = applicationRepo; - this.componentRegistry = globalComponentRegistry; - this.serverId = globalComponentRegistry.getConfigserverConfig().serverId(); - this.nodeFlavors = globalComponentRegistry.getZone().nodeFlavors(); - this.clock = globalComponentRegistry.getClock(); - this.distributeApplicationPackage = Flags.CONFIGSERVER_DISTRIBUTE_APPLICATION_PACKAGE - .bindTo(globalComponentRegistry.getFlagSource()); - } - - /** Create a session for a true application package change */ - @Override - public LocalSession createSession(File applicationFile, ApplicationId applicationId, TimeoutBudget timeoutBudget) { - return create(applicationFile, applicationId, nonExistingActiveSession, false, timeoutBudget); - } - - private void ensureSessionPathDoesNotExist(long sessionId) { - Path sessionPath = getSessionPath(sessionId); - if (configCurator.exists(sessionPath.getAbsolute())) { - throw new IllegalArgumentException("Path " + sessionPath.getAbsolute() + " already exists in ZooKeeper"); - } - } - - private ApplicationPackage createApplication(File userDir, - File configApplicationDir, - ApplicationId applicationId, - long sessionId, - long currentlyActiveSessionId, - boolean internalRedeploy) { - long deployTimestamp = System.currentTimeMillis(); - String user = System.getenv("USER"); - if (user == null) { - user = "unknown"; - } - DeployData deployData = new DeployData(user, userDir.getAbsolutePath(), applicationId, deployTimestamp, internalRedeploy, sessionId, currentlyActiveSessionId); - return FilesApplicationPackage.fromFileWithDeployData(configApplicationDir, deployData); - } - - private LocalSession createSessionFromApplication(ApplicationPackage applicationPackage, - long sessionId, - SessionZooKeeperClient sessionZKClient, - TimeoutBudget timeoutBudget, - Clock clock) { - log.log(Level.FINE, TenantRepository.logPre(tenant) + "Creating session " + sessionId + " in ZooKeeper"); - sessionZKClient.createNewSession(clock.instant()); - Curator.CompletionWaiter waiter = sessionZKClient.getUploadWaiter(); - LocalSession session = new LocalSession(tenant, sessionId, sessionPreparer, applicationPackage, sessionZKClient, - getSessionAppDir(sessionId), applicationRepo, hostRegistry); - waiter.awaitCompletion(timeoutBudget.timeLeft()); - return session; - } - - @Override - public LocalSession createSessionFromExisting(Session existingSession, - DeployLogger logger, - boolean internalRedeploy, - TimeoutBudget timeoutBudget) { - File existingApp = getSessionAppDir(existingSession.getSessionId()); - ApplicationId existingApplicationId = existingSession.getApplicationId(); - - long activeSessionId = getActiveSessionId(existingApplicationId); - logger.log(Level.FINE, "Create new session for application id '" + existingApplicationId + "' from existing active session " + activeSessionId); - LocalSession session = create(existingApp, existingApplicationId, activeSessionId, internalRedeploy, timeoutBudget); - // Note: Needs to be kept in sync with calls in SessionPreparer.writeStateToZooKeeper() - session.setApplicationId(existingApplicationId); - if (distributeApplicationPackage.value() && existingSession.getApplicationPackageReference() != null) { - session.setApplicationPackageReference(existingSession.getApplicationPackageReference()); - } - session.setVespaVersion(existingSession.getVespaVersion()); - session.setDockerImageRepository(existingSession.getDockerImageRepository()); - session.setAthenzDomain(existingSession.getAthenzDomain()); - return session; - } - - private LocalSession create(File applicationFile, ApplicationId applicationId, long currentlyActiveSessionId, - boolean internalRedeploy, TimeoutBudget timeoutBudget) { - long sessionId = getNextSessionId(); - try { - ensureSessionPathDoesNotExist(sessionId); - SessionZooKeeperClient sessionZooKeeperClient = - new SessionZooKeeperClient(curator, configCurator, getSessionPath(sessionId), serverId, nodeFlavors); - File userApplicationDir = getSessionAppDir(sessionId); - IOUtils.copyDirectory(applicationFile, userApplicationDir); - ApplicationPackage applicationPackage = createApplication(applicationFile, - userApplicationDir, - applicationId, - sessionId, - currentlyActiveSessionId, - internalRedeploy); - applicationPackage.writeMetaData(); - return createSessionFromApplication(applicationPackage, sessionId, sessionZooKeeperClient, timeoutBudget, clock); - } catch (Exception e) { - throw new RuntimeException("Error creating session " + sessionId, e); - } - } - - private File getAndValidateExistingSessionAppDir(long sessionId) { - File appDir = getSessionAppDir(sessionId); - if (!appDir.exists() || !appDir.isDirectory()) { - throw new IllegalArgumentException("Unable to find correct application directory for session " + sessionId); - } - return appDir; - } - - private File getSessionAppDir(long sessionId) { - return new TenantFileSystemDirs(componentRegistry.getConfigServerDB(), tenant).getUserApplicationDir(sessionId); - } - - @Override - public LocalSession loadSession(long sessionId) { - File sessionDir = getAndValidateExistingSessionAppDir(sessionId); - ApplicationPackage applicationPackage = FilesApplicationPackage.fromFile(sessionDir); - Path sessionIdPath = sessionsPath.append(String.valueOf(sessionId)); - SessionZooKeeperClient sessionZKClient = new SessionZooKeeperClient(curator, - configCurator, - sessionIdPath, - serverId, - nodeFlavors); - return new LocalSession(tenant, sessionId, sessionPreparer, applicationPackage, sessionZKClient, - getSessionAppDir(sessionId), applicationRepo, hostRegistry); - } - - private long getActiveSessionId(ApplicationId applicationId) { - List<ApplicationId> applicationIds = applicationRepo.activeApplications(); - if (applicationIds.contains(applicationId)) { - return applicationRepo.requireActiveSessionOf(applicationId); - } - return nonExistingActiveSession; - } - - long getNextSessionId() { - return new SessionCounter(componentRegistry.getConfigCurator(), tenant).nextSessionId(); - } - - Path getSessionPath(long sessionId) { - return sessionsPath.append(String.valueOf(sessionId)); - } - -} 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 457d5538d5c..43b25826507 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 @@ -15,12 +15,9 @@ import com.yahoo.vespa.config.server.application.TenantApplications; import com.yahoo.vespa.config.server.host.HostValidator; import com.yahoo.vespa.config.server.monitoring.MetricUpdater; import com.yahoo.vespa.config.server.rpc.ConfigResponseFactory; -import com.yahoo.vespa.config.server.session.LocalSessionLoader; import com.yahoo.vespa.config.server.session.LocalSessionRepo; -import com.yahoo.vespa.config.server.session.RemoteSessionFactory; import com.yahoo.vespa.config.server.session.RemoteSessionRepo; import com.yahoo.vespa.config.server.session.SessionFactory; -import com.yahoo.vespa.config.server.session.SessionFactoryImpl; import com.yahoo.vespa.curator.Curator; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; @@ -231,11 +228,10 @@ public class TenantRepository { reloadHandler, tenantName); - SessionFactory sessionFactory = new SessionFactoryImpl(globalComponentRegistry, applicationRepo, hostValidator, tenantName); - // TODO: Fix the casting - LocalSessionRepo localSessionRepo = new LocalSessionRepo(tenantName, globalComponentRegistry, (LocalSessionLoader) sessionFactory); + SessionFactory sessionFactory = new SessionFactory(globalComponentRegistry, applicationRepo, hostValidator, tenantName); + LocalSessionRepo localSessionRepo = new LocalSessionRepo(tenantName, globalComponentRegistry, sessionFactory); RemoteSessionRepo remoteSessionRepo = new RemoteSessionRepo(globalComponentRegistry, - new RemoteSessionFactory(globalComponentRegistry, tenantName), + sessionFactory, reloadHandler, tenantName, applicationRepo); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java index b939f1ab4c5..91a40bd6083 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java @@ -25,6 +25,7 @@ import com.yahoo.vespa.config.server.session.DummyTransaction; import com.yahoo.vespa.config.server.session.LocalSession; import com.yahoo.vespa.config.server.session.MockSessionZKClient; 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 java.io.ByteArrayOutputStream; @@ -86,7 +87,7 @@ public class SessionHandlerTest { return baos.toString(StandardCharsets.UTF_8); } - public static class MockSession extends LocalSession { + public static class MockLocalSession extends LocalSession { public Session.Status status; private ConfigChangeActions actions = new ConfigChangeActions(); @@ -94,11 +95,11 @@ public class SessionHandlerTest { private ApplicationId applicationId; private Optional<DockerImage> dockerImageRepository; - public MockSession(long sessionId, ApplicationPackage app) { + public MockLocalSession(long sessionId, ApplicationPackage app) { super(TenantName.defaultName(), sessionId, null, app, new MockSessionZKClient(app), null, null, new HostRegistry<>()); } - public MockSession(long sessionId, ApplicationPackage app, ApplicationId applicationId) { + public MockLocalSession(long sessionId, ApplicationPackage app, ApplicationId applicationId) { this(sessionId, app); this.applicationId = applicationId; } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationContentHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationContentHandlerTest.java index 09e84deb1a5..078dc47af51 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationContentHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationContentHandlerTest.java @@ -44,7 +44,7 @@ public class ApplicationContentHandlerTest extends ContentHandlerTestBase { private ApplicationId idTenant2 = new ApplicationId.Builder() .tenant(tenantName2) .applicationName("foo").instanceName("quux").build(); - private MockSession session2; + private MockLocalSession session2; @Before public void setupHandler() { @@ -52,13 +52,13 @@ public class ApplicationContentHandlerTest extends ContentHandlerTestBase { tenantRepository.addTenant(tenantName1); tenantRepository.addTenant(tenantName2); - session2 = new MockSession(2, FilesApplicationPackage.fromFile(new File("src/test/apps/content"))); + session2 = new MockLocalSession(2, FilesApplicationPackage.fromFile(new File("src/test/apps/content"))); Tenant tenant1 = tenantRepository.getTenant(tenantName1); tenant1.getLocalSessionRepo().addSession(session2); tenant1.getApplicationRepo().createApplication(idTenant1); tenant1.getApplicationRepo().createPutTransaction(idTenant1, 2).commit(); - MockSession session3 = new MockSession(3, FilesApplicationPackage.fromFile(new File("src/test/apps/content2"))); + MockLocalSession session3 = new MockLocalSession(3, FilesApplicationPackage.fromFile(new File("src/test/apps/content2"))); Tenant tenant2 = tenantRepository.getTenant(tenantName2); tenant2.getLocalSessionRepo().addSession(session3); tenant2.getApplicationRepo().createApplication(idTenant2); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HostHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HostHandlerTest.java index 28fe4a7aa2c..2eaa5d75ba7 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HostHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HostHandlerTest.java @@ -52,7 +52,7 @@ public class HostHandlerTest { tenant.getApplicationRepo().createApplication(applicationId); tenant.getApplicationRepo().createPutTransaction(applicationId, sessionId).commit(); ApplicationPackage app = FilesApplicationPackage.fromFile(testApp); - tenant.getLocalSessionRepo().addSession(new SessionHandlerTest.MockSession(sessionId, app, applicationId)); + tenant.getLocalSessionRepo().addSession(new SessionHandlerTest.MockLocalSession(sessionId, app, applicationId)); TestComponentRegistry componentRegistry = new TestComponentRegistry.Builder() .modelFactoryRegistry(new ModelFactoryRegistry(Collections.singletonList(new VespaModelFactory(new NullConfigModelRegistry())))) .build(); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java index bc1650ce923..88bf6fb7172 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java @@ -43,7 +43,7 @@ public class SessionContentHandlerTest extends ContentHandlerTestBase { public void setupHandler() throws Exception { tenantRepository = new TenantRepository(componentRegistry, false); tenantRepository.addTenant(tenant); - tenantRepository.getTenant(tenant).getLocalSessionRepo().addSession(new MockSession(1L, FilesApplicationPackage.fromFile(createTestApp()))); + tenantRepository.getTenant(tenant).getLocalSessionRepo().addSession(new MockLocalSession(1L, FilesApplicationPackage.fromFile(createTestApp()))); handler = createHandler(); pathPrefix = "/application/v2/tenant/" + tenant + "/session/"; baseUrl = "http://foo:1337/application/v2/tenant/" + tenant + "/session/1/content/"; diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionRepoTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionRepoTest.java index 69132186abc..2c119a119b6 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 @@ -58,11 +58,11 @@ public class LocalSessionRepoTest { .sessionLifetime(5) .build()) .build(); - LocalSessionLoader loader = new SessionFactoryImpl(globalComponentRegistry, + SessionFactory sessionFactory = new SessionFactory(globalComponentRegistry, TenantApplications.create(globalComponentRegistry, new MockReloadHandler(), tenantName), new HostRegistry<>(), tenantName); - repo = new LocalSessionRepo(tenantName, globalComponentRegistry, loader); + repo = new LocalSessionRepo(tenantName, globalComponentRegistry, sessionFactory); } @Test @@ -97,8 +97,8 @@ public class LocalSessionRepoTest { } assertNull(repo.getSession(1L)); - repo.addSession(new SessionHandlerTest.MockSession(1L, FilesApplicationPackage.fromFile(testApp))); - repo.addSession(new SessionHandlerTest.MockSession(2L, FilesApplicationPackage.fromFile(testApp))); + repo.addSession(new SessionHandlerTest.MockLocalSession(1L, FilesApplicationPackage.fromFile(testApp))); + repo.addSession(new SessionHandlerTest.MockLocalSession(2L, FilesApplicationPackage.fromFile(testApp))); assertNotNull(repo.getSession(1L)); assertNotNull(repo.getSession(2L)); assertNull(repo.getSession(3L)); diff --git a/container-core/src/main/java/com/yahoo/container/handler/ThreadPoolProvider.java b/container-core/src/main/java/com/yahoo/container/handler/ThreadPoolProvider.java index 0e786cfbc8f..958e958456c 100644 --- a/container-core/src/main/java/com/yahoo/container/handler/ThreadPoolProvider.java +++ b/container-core/src/main/java/com/yahoo/container/handler/ThreadPoolProvider.java @@ -1,26 +1,13 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.container.handler; -import com.google.common.util.concurrent.ForwardingExecutorService; -import com.google.inject.Inject; import com.yahoo.component.AbstractComponent; -import com.yahoo.concurrent.ThreadFactoryFactory; import com.yahoo.container.di.componentgraph.Provider; +import com.yahoo.container.handler.threadpool.ContainerThreadPool; import com.yahoo.container.protect.ProcessTerminator; import com.yahoo.jdisc.Metric; -import java.util.Map; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; /** * A configurable thread pool provider. This provides the worker threads used for normal request processing. @@ -32,40 +19,14 @@ import java.util.concurrent.atomic.AtomicLong; */ public class ThreadPoolProvider extends AbstractComponent implements Provider<Executor> { - private final ExecutorServiceWrapper threadpool; + private final ContainerThreadPool threadpool; - private static BlockingQueue<Runnable> createQ(int queueSize, int maxThreads) { - return (queueSize == 0) - ? new SynchronousQueue<>(false) - : (queueSize < 0) - ? new ArrayBlockingQueue<>(maxThreads*4) - : new ArrayBlockingQueue<>(queueSize); - } - - private static int computeThreadPoolSize(int maxNumThreads) { - return (maxNumThreads <= 0) - ? Runtime.getRuntime().availableProcessors() * 4 - : maxNumThreads; - } - @Inject public ThreadPoolProvider(ThreadpoolConfig threadpoolConfig, Metric metric) { - this(threadpoolConfig, metric, new ProcessTerminator()); + this.threadpool = new ContainerThreadPool(threadpoolConfig, metric); } public ThreadPoolProvider(ThreadpoolConfig threadpoolConfig, Metric metric, ProcessTerminator processTerminator) { - int maxNumThreads = computeThreadPoolSize(threadpoolConfig.maxthreads()); - WorkerCompletionTimingThreadPoolExecutor executor = - new WorkerCompletionTimingThreadPoolExecutor(maxNumThreads, maxNumThreads, - 0L, TimeUnit.SECONDS, - createQ(threadpoolConfig.queueSize(), maxNumThreads), - ThreadFactoryFactory.getThreadFactory("threadpool"), - metric); - // Prestart needed, if not all threads will be created by the fist N tasks and hence they might also - // get the dreaded thread locals initialized even if they will never run. - // That counters what we we want to achieve with the Q that will prefer thread locality. - executor.prestartAllCoreThreads(); - threadpool = new ExecutorServiceWrapper(executor, metric, processTerminator, - threadpoolConfig.maxThreadExecutionTimeSeconds() * 1000L); + this.threadpool = new ContainerThreadPool(threadpoolConfig, metric, processTerminator); } /** @@ -75,7 +36,7 @@ public class ThreadPoolProvider extends AbstractComponent implements Provider<Ex * @return a possibly shared executor */ @Override - public Executor get() { return threadpool; } + public Executor get() { return threadpool.executor(); } /** * Shutdown the thread pool, give a grace period of 1 second before forcibly @@ -83,142 +44,7 @@ public class ThreadPoolProvider extends AbstractComponent implements Provider<Ex */ @Override public void deconstruct() { - boolean terminated; - - super.deconstruct(); - threadpool.shutdown(); - try { - terminated = threadpool.awaitTermination(1, TimeUnit.SECONDS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return; - } - if (!terminated) { - threadpool.shutdownNow(); - } - } - - /** - * A service executor wrapper which emits metrics and - * shuts down the vm when no workers are available for too long to avoid containers lingering in a blocked state. - * Package private for testing - */ - final static class ExecutorServiceWrapper extends ForwardingExecutorService { - - private final WorkerCompletionTimingThreadPoolExecutor wrapped; - private final Metric metric; - private final ProcessTerminator processTerminator; - private final long maxThreadExecutionTimeMillis; - private final Thread metricReporter; - private final AtomicBoolean closed = new AtomicBoolean(false); - - private ExecutorServiceWrapper(WorkerCompletionTimingThreadPoolExecutor wrapped, - Metric metric, ProcessTerminator processTerminator, - long maxThreadExecutionTimeMillis) { - this.wrapped = wrapped; - this.metric = metric; - this.processTerminator = processTerminator; - this.maxThreadExecutionTimeMillis = maxThreadExecutionTimeMillis; - - metric.set(MetricNames.THREAD_POOL_SIZE, wrapped.getPoolSize(), null); - metric.set(MetricNames.ACTIVE_THREADS, wrapped.getActiveCount(), null); - metric.add(MetricNames.REJECTED_REQUEST, 0, null); - metricReporter = new Thread(this::reportMetrics); - metricReporter.setDaemon(true); - metricReporter.start(); - } - - private final void reportMetrics() { - try { - while (!closed.get()) { - metric.set(MetricNames.THREAD_POOL_SIZE, wrapped.getPoolSize(), null); - metric.set(MetricNames.ACTIVE_THREADS, wrapped.getActiveCount(), null); - Thread.sleep(100); - } - } catch (InterruptedException e) { } - } - - @Override - public void shutdown() { - super.shutdown(); - closed.set(true); - } - - /** - * Tracks all instances of RejectedExecutionException. - * ThreadPoolProvider returns an executor, so external uses will not - * have access to the methods declared by ExecutorService. - * (execute(Runnable) is declared by Executor.) - */ - @Override - public void execute(Runnable command) { - try { - super.execute(command); - } catch (RejectedExecutionException e) { - metric.add(MetricNames.REJECTED_REQUEST, 1, null); - long timeSinceLastReturnedThreadMillis = System.currentTimeMillis() - wrapped.lastThreadAssignmentTimeMillis; - if (timeSinceLastReturnedThreadMillis > maxThreadExecutionTimeMillis) - processTerminator.logAndDie("No worker threads have been available for " + - timeSinceLastReturnedThreadMillis + " ms. Shutting down.", true); - throw e; - } - } - - @Override - protected ExecutorService delegate() { return wrapped; } - - private static final class MetricNames { - private static final String REJECTED_REQUEST = "serverRejectedRequests"; - private static final String THREAD_POOL_SIZE = "serverThreadPoolSize"; - private static final String ACTIVE_THREADS = "serverActiveThreads"; - } - - } - - /** - * A thread pool executor which maintains the last time a worker completed - * package private for testing - **/ - final static class WorkerCompletionTimingThreadPoolExecutor extends ThreadPoolExecutor { - - private static final String UNHANDLED_EXCEPTIONS_METRIC = "jdisc.thread_pool.unhandled_exceptions"; - - volatile long lastThreadAssignmentTimeMillis = System.currentTimeMillis(); - private final AtomicLong startedCount = new AtomicLong(0); - private final AtomicLong completedCount = new AtomicLong(0); - private final Metric metric; - - public WorkerCompletionTimingThreadPoolExecutor(int corePoolSize, - int maximumPoolSize, - long keepAliveTime, - TimeUnit unit, - BlockingQueue<Runnable> workQueue, - ThreadFactory threadFactory, - Metric metric) { - super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory); - this.metric = metric; - } - - @Override - protected void beforeExecute(Thread t, Runnable r) { - super.beforeExecute(t, r); - lastThreadAssignmentTimeMillis = System.currentTimeMillis(); - startedCount.incrementAndGet(); - } - - @Override - protected void afterExecute(Runnable r, Throwable t) { - super.afterExecute(r, t); - completedCount.incrementAndGet(); - if (t != null) { - metric.add(UNHANDLED_EXCEPTIONS_METRIC, 1L, metric.createContext(Map.of("exception", t.getClass().getSimpleName()))); - } - } - - @Override - public int getActiveCount() { - return (int)(startedCount.get() - completedCount.get()); - } + threadpool.deconstruct(); } } diff --git a/container-core/src/main/java/com/yahoo/container/handler/threadpool/ContainerThreadPool.java b/container-core/src/main/java/com/yahoo/container/handler/threadpool/ContainerThreadPool.java new file mode 100644 index 00000000000..0f3be65f85f --- /dev/null +++ b/container-core/src/main/java/com/yahoo/container/handler/threadpool/ContainerThreadPool.java @@ -0,0 +1,87 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.handler.threadpool; + +import com.google.inject.Inject; +import com.yahoo.component.AbstractComponent; +import com.yahoo.concurrent.ThreadFactoryFactory; +import com.yahoo.container.handler.ThreadpoolConfig; +import com.yahoo.container.protect.ProcessTerminator; +import com.yahoo.jdisc.Metric; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.TimeUnit; + +/** + * A configurable thread pool. This provides the worker threads used for normal request processing. + * + * @author Steinar Knutsen + * @author baldersheim + * @author bratseth + * @author bjorncs + */ +public class ContainerThreadPool extends AbstractComponent implements AutoCloseable { + + private final ExecutorServiceWrapper threadpool; + + @Inject + public ContainerThreadPool(ThreadpoolConfig config, Metric metric) { + this(config, metric, new ProcessTerminator()); + } + + public ContainerThreadPool(ThreadpoolConfig threadpoolConfig, Metric metric, ProcessTerminator processTerminator) { + int maxNumThreads = computeThreadPoolSize(threadpoolConfig.maxthreads()); + WorkerCompletionTimingThreadPoolExecutor executor = + new WorkerCompletionTimingThreadPoolExecutor(maxNumThreads, maxNumThreads, + 0L, TimeUnit.SECONDS, + createQ(threadpoolConfig.queueSize(), maxNumThreads), + ThreadFactoryFactory.getThreadFactory("threadpool"), + metric); + // Prestart needed, if not all threads will be created by the fist N tasks and hence they might also + // get the dreaded thread locals initialized even if they will never run. + // That counters what we we want to achieve with the Q that will prefer thread locality. + executor.prestartAllCoreThreads(); + threadpool = new ExecutorServiceWrapper(executor, metric, processTerminator, + threadpoolConfig.maxThreadExecutionTimeSeconds() * 1000L); + } + + public Executor executor() { return threadpool; } + @Override public void deconstruct() { closeInternal(); } + @Override public void close() { closeInternal(); } + + /** + * Shutdown the thread pool, give a grace period of 1 second before forcibly + * shutting down all worker threads. + */ + private void closeInternal() { + boolean terminated; + + super.deconstruct(); + threadpool.shutdown(); + try { + terminated = threadpool.awaitTermination(1, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + if (!terminated) { + threadpool.shutdownNow(); + } + } + + private static BlockingQueue<Runnable> createQ(int queueSize, int maxThreads) { + return (queueSize == 0) + ? new SynchronousQueue<>(false) + : (queueSize < 0) + ? new ArrayBlockingQueue<>(maxThreads*4) + : new ArrayBlockingQueue<>(queueSize); + } + + private static int computeThreadPoolSize(int maxNumThreads) { + return (maxNumThreads <= 0) + ? Runtime.getRuntime().availableProcessors() * 4 + : maxNumThreads; + } +} diff --git a/container-core/src/main/java/com/yahoo/container/handler/threadpool/ExecutorServiceWrapper.java b/container-core/src/main/java/com/yahoo/container/handler/threadpool/ExecutorServiceWrapper.java new file mode 100644 index 00000000000..f7b0a22120a --- /dev/null +++ b/container-core/src/main/java/com/yahoo/container/handler/threadpool/ExecutorServiceWrapper.java @@ -0,0 +1,94 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.handler.threadpool; + +import com.google.common.util.concurrent.ForwardingExecutorService; +import com.yahoo.container.protect.ProcessTerminator; +import com.yahoo.jdisc.Metric; + +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A service executor wrapper which emits metrics and + * shuts down the vm when no workers are available for too long to avoid containers lingering in a blocked state. + * Package private for testing + * + * @author Steinar Knutsen + * @author baldersheim + * @author bratseth + */ +class ExecutorServiceWrapper extends ForwardingExecutorService { + + private final WorkerCompletionTimingThreadPoolExecutor wrapped; + private final Metric metric; + private final ProcessTerminator processTerminator; + private final long maxThreadExecutionTimeMillis; + private final Thread metricReporter; + private final AtomicBoolean closed = new AtomicBoolean(false); + + ExecutorServiceWrapper( + WorkerCompletionTimingThreadPoolExecutor wrapped, + Metric metric, ProcessTerminator processTerminator, + long maxThreadExecutionTimeMillis) { + this.wrapped = wrapped; + this.metric = metric; + this.processTerminator = processTerminator; + this.maxThreadExecutionTimeMillis = maxThreadExecutionTimeMillis; + + metric.set(MetricNames.THREAD_POOL_SIZE, wrapped.getPoolSize(), null); + metric.set(MetricNames.ACTIVE_THREADS, wrapped.getActiveCount(), null); + metric.add(MetricNames.REJECTED_REQUEST, 0, null); + metricReporter = new Thread(this::reportMetrics); + metricReporter.setDaemon(true); + metricReporter.start(); + } + + private final void reportMetrics() { + try { + while (!closed.get()) { + metric.set(MetricNames.THREAD_POOL_SIZE, wrapped.getPoolSize(), null); + metric.set(MetricNames.ACTIVE_THREADS, wrapped.getActiveCount(), null); + Thread.sleep(100); + } + } catch (InterruptedException e) { } + } + + @Override + public void shutdown() { + super.shutdown(); + closed.set(true); + } + + /** + * Tracks all instances of {@link RejectedExecutionException}. + * {@link ContainerThreadPool} returns an executor, so external uses will not + * have access to the methods declared by {@link ExecutorService}. + * ({@link Executor#execute(Runnable)} is declared by {@link Executor}.) + */ + @Override + public void execute(Runnable command) { + try { + super.execute(command); + } catch (RejectedExecutionException e) { + metric.add(MetricNames.REJECTED_REQUEST, 1, null); + long timeSinceLastReturnedThreadMillis = System.currentTimeMillis() - wrapped.lastThreadAssignmentTimeMillis; + if (timeSinceLastReturnedThreadMillis > maxThreadExecutionTimeMillis) + processTerminator.logAndDie("No worker threads have been available for " + + timeSinceLastReturnedThreadMillis + " ms. Shutting down.", true); + throw e; + } + } + + @Override + protected ExecutorService delegate() { return wrapped; } + + private static final class MetricNames { + private static final String REJECTED_REQUEST = "serverRejectedRequests"; + private static final String THREAD_POOL_SIZE = "serverThreadPoolSize"; + private static final String ACTIVE_THREADS = "serverActiveThreads"; + } + +} + diff --git a/container-core/src/main/java/com/yahoo/container/handler/threadpool/WorkerCompletionTimingThreadPoolExecutor.java b/container-core/src/main/java/com/yahoo/container/handler/threadpool/WorkerCompletionTimingThreadPoolExecutor.java new file mode 100644 index 00000000000..9742e7ecfc3 --- /dev/null +++ b/container-core/src/main/java/com/yahoo/container/handler/threadpool/WorkerCompletionTimingThreadPoolExecutor.java @@ -0,0 +1,63 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.handler.threadpool; + +import com.yahoo.jdisc.Metric; + +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +/** + * A thread pool executor which maintains the last time a worker completed + * package private for testing + * + * @author Steinar Knutsen + * @author baldersheim + * @author bratseth + */ +class WorkerCompletionTimingThreadPoolExecutor extends ThreadPoolExecutor { + + private static final String UNHANDLED_EXCEPTIONS_METRIC = "jdisc.thread_pool.unhandled_exceptions"; + + volatile long lastThreadAssignmentTimeMillis = System.currentTimeMillis(); + private final AtomicLong startedCount = new AtomicLong(0); + private final AtomicLong completedCount = new AtomicLong(0); + private final Metric metric; + + WorkerCompletionTimingThreadPoolExecutor( + int corePoolSize, + int maximumPoolSize, + long keepAliveTime, + TimeUnit unit, + BlockingQueue<Runnable> workQueue, + ThreadFactory threadFactory, + Metric metric) { + super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory); + this.metric = metric; + } + + @Override + protected void beforeExecute(Thread t, Runnable r) { + super.beforeExecute(t, r); + lastThreadAssignmentTimeMillis = System.currentTimeMillis(); + startedCount.incrementAndGet(); + } + + @Override + protected void afterExecute(Runnable r, Throwable t) { + super.afterExecute(r, t); + completedCount.incrementAndGet(); + if (t != null) { + metric.add(UNHANDLED_EXCEPTIONS_METRIC, 1L, metric.createContext(Map.of("exception", t.getClass().getSimpleName()))); + } + } + + @Override + public int getActiveCount() { + return (int)(startedCount.get() - completedCount.get()); + } +} + diff --git a/container-core/src/main/java/com/yahoo/container/handler/threadpool/package-info.java b/container-core/src/main/java/com/yahoo/container/handler/threadpool/package-info.java new file mode 100644 index 00000000000..6a94cea49da --- /dev/null +++ b/container-core/src/main/java/com/yahoo/container/handler/threadpool/package-info.java @@ -0,0 +1,8 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +/** + * @author bjorncs + */ +@ExportPackage +package com.yahoo.container.handler.threadpool; + +import com.yahoo.osgi.annotation.ExportPackage;
\ No newline at end of file diff --git a/container-core/src/test/java/com/yahoo/container/handler/ThreadPoolProviderTestCase.java b/container-core/src/test/java/com/yahoo/container/handler/threadpool/ContainerThreadPoolTest.java index 761ed40763c..7998bbc4872 100644 --- a/container-core/src/test/java/com/yahoo/container/handler/ThreadPoolProviderTestCase.java +++ b/container-core/src/test/java/com/yahoo/container/handler/threadpool/ContainerThreadPoolTest.java @@ -1,37 +1,33 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.container.handler; - -import static org.junit.Assert.fail; - -import java.util.concurrent.Executor; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.ThreadPoolExecutor; +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.handler.threadpool; +import com.yahoo.collections.Tuple2; +import com.yahoo.concurrent.Receiver; +import com.yahoo.container.handler.ThreadpoolConfig; import com.yahoo.container.protect.ProcessTerminator; +import com.yahoo.jdisc.Metric; import org.junit.Ignore; import org.junit.Test; import org.mockito.Mockito; -import com.yahoo.concurrent.Receiver; -import com.yahoo.concurrent.Receiver.MessageState; -import com.yahoo.collections.Tuple2; -import com.yahoo.jdisc.Metric; +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ThreadPoolExecutor; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; /** - * Check threadpool provider accepts tasks and shuts down properly. - * - * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a> + * @author Steinar Knutsen + * @author bjorncs */ -public class ThreadPoolProviderTestCase { - +public class ContainerThreadPoolTest { @Test - public final void testThreadPoolProvider() throws InterruptedException { + public final void testThreadPool() throws InterruptedException { ThreadpoolConfig config = new ThreadpoolConfig(new ThreadpoolConfig.Builder().maxthreads(1)); - ThreadPoolProvider provider = new ThreadPoolProvider(config, Mockito.mock(Metric.class)); - Executor exec = provider.get(); - Tuple2<MessageState, Boolean> reply; + ContainerThreadPool threadPool = new ContainerThreadPool(config, Mockito.mock(Metric.class)); + Executor exec = threadPool.executor(); + Tuple2<Receiver.MessageState, Boolean> reply; FlipIt command = new FlipIt(); for (boolean done = false; !done;) { try { @@ -42,13 +38,13 @@ public class ThreadPoolProviderTestCase { } } reply = command.didItRun.get(5 * 60 * 1000); - if (reply.first != MessageState.VALID) { + if (reply.first != Receiver.MessageState.VALID) { fail("Executor task probably timed out, five minutes should be enough to flip a boolean."); } if (reply.second != Boolean.TRUE) { fail("Executor task seemed to run, but did not get correct value."); } - provider.deconstruct(); + threadPool.deconstruct(); command = new FlipIt(); try { exec.execute(command); @@ -61,9 +57,9 @@ public class ThreadPoolProviderTestCase { private ThreadPoolExecutor createPool(int maxThreads, int queueSize) { ThreadpoolConfig config = new ThreadpoolConfig(new ThreadpoolConfig.Builder().maxthreads(maxThreads).queueSize(queueSize)); - ThreadPoolProvider provider = new ThreadPoolProvider(config, Mockito.mock(Metric.class)); - ThreadPoolProvider.ExecutorServiceWrapper wrapper = (ThreadPoolProvider.ExecutorServiceWrapper) provider.get(); - ThreadPoolProvider.WorkerCompletionTimingThreadPoolExecutor executor = (ThreadPoolProvider.WorkerCompletionTimingThreadPoolExecutor)wrapper.delegate(); + ContainerThreadPool threadPool = new ContainerThreadPool(config, Mockito.mock(Metric.class)); + ExecutorServiceWrapper wrapper = (ExecutorServiceWrapper) threadPool.executor(); + WorkerCompletionTimingThreadPoolExecutor executor = (WorkerCompletionTimingThreadPoolExecutor)wrapper.delegate(); return executor; } @@ -103,37 +99,37 @@ public class ThreadPoolProviderTestCase { @Test @Ignore // Ignored because it depends on the system time and so is unstable on factory - public void testThreadPoolProviderTerminationOnBreakdown() throws InterruptedException { + public void testThreadPoolTerminationOnBreakdown() throws InterruptedException { ThreadpoolConfig config = new ThreadpoolConfig(new ThreadpoolConfig.Builder().maxthreads(2) - .maxThreadExecutionTimeSeconds(1)); + .maxThreadExecutionTimeSeconds(1)); MockProcessTerminator terminator = new MockProcessTerminator(); - ThreadPoolProvider provider = new ThreadPoolProvider(config, Mockito.mock(Metric.class), terminator); + ContainerThreadPool threadPool = new ContainerThreadPool(config, Mockito.mock(Metric.class), terminator); // No dying when threads hang shorter than max thread execution time - provider.get().execute(new Hang(500)); - provider.get().execute(new Hang(500)); + threadPool.executor().execute(new Hang(500)); + threadPool.executor().execute(new Hang(500)); assertEquals(0, terminator.dieRequests); - assertRejected(provider, new Hang(500)); // no more threads + assertRejected(threadPool, new Hang(500)); // no more threads assertEquals(0, terminator.dieRequests); // ... but not for long enough yet try { Thread.sleep(1500); } catch (InterruptedException e) {} - provider.get().execute(new Hang(1)); + threadPool.executor().execute(new Hang(1)); assertEquals(0, terminator.dieRequests); try { Thread.sleep(50); } catch (InterruptedException e) {} // Make sure both threads are available // Dying when hanging both thread pool threads for longer than max thread execution time - provider.get().execute(new Hang(2000)); - provider.get().execute(new Hang(2000)); + threadPool.executor().execute(new Hang(2000)); + threadPool.executor().execute(new Hang(2000)); assertEquals(0, terminator.dieRequests); - assertRejected(provider, new Hang(2000)); // no more threads + assertRejected(threadPool, new Hang(2000)); // no more threads assertEquals(0, terminator.dieRequests); // ... but not for long enough yet try { Thread.sleep(1500); } catch (InterruptedException e) {} - assertRejected(provider, new Hang(2000)); // no more threads + assertRejected(threadPool, new Hang(2000)); // no more threads assertEquals(1, terminator.dieRequests); // ... for longer than maxThreadExecutionTime } - private void assertRejected(ThreadPoolProvider provider, Runnable task) { + private void assertRejected(ContainerThreadPool threadPool, Runnable task) { try { - provider.get().execute(task); + threadPool.executor().execute(task); fail("Expected execution rejected"); } catch (final RejectedExecutionException expected) { } @@ -165,4 +161,4 @@ public class ThreadPoolProviderTestCase { } -} +}
\ No newline at end of file diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java index 5cd14b3fac5..c8cce94d479 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java @@ -247,6 +247,10 @@ public class AthenzFacade implements AccessControl { return hasAccess("callback", new AthenzResourceName(service.getDomain().getName(), "payment-notification-resource").toResourceNameString(), identity); } + public boolean hasAccountingAccess(AthenzIdentity identity) { + return hasAccess("modify", new AthenzResourceName(service.getDomain().getName(), "hosted-accounting-resource").toResourceNameString(), identity); + } + /** * Used when creating tenancies. As there are no tenancy policies at this point, * we cannot use {@link #hasTenantAdminAccess(AthenzIdentity, AthenzDomain)} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java index 25ee95e6d80..b9cf5ca4f4d 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java @@ -125,6 +125,11 @@ public class AthenzRoleFilter extends JsonSecurityRequestFilterBase { roleMemberships.add(Role.paymentProcessor()); })); + futures.add(executor.submit(() -> { + if (athenz.hasAccountingAccess(identity)) + roleMemberships.add(Role.hostedAccountant()); + })); + // Run last request in handler thread to avoid creating extra thread. if (athenz.hasSystemFlagsAccess(identity, /*dryrun*/true)) roleMemberships.add(Role.systemFlagsDryrunner()); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilterTest.java index 5e50e80b7a7..3da662ee373 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilterTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilterTest.java @@ -70,13 +70,13 @@ public class AthenzRoleFilterTest { public void testTranslations() throws Exception { // Hosted operators are always members of the hostedOperator role. - assertEquals(Set.of(Role.hostedOperator(), Role.systemFlagsDeployer(), Role.systemFlagsDryrunner(), Role.paymentProcessor(), Role.hostedSupporter()), + assertEquals(Set.of(Role.hostedOperator(), Role.systemFlagsDeployer(), Role.systemFlagsDryrunner(), Role.paymentProcessor(), Role.hostedAccountant(), Role.hostedSupporter()), filter.roles(HOSTED_OPERATOR, NO_CONTEXT_PATH)); - assertEquals(Set.of(Role.hostedOperator(), Role.systemFlagsDeployer(), Role.systemFlagsDryrunner(), Role.paymentProcessor(), Role.hostedSupporter()), + assertEquals(Set.of(Role.hostedOperator(), Role.systemFlagsDeployer(), Role.systemFlagsDryrunner(), Role.paymentProcessor(), Role.hostedAccountant(), Role.hostedSupporter()), filter.roles(HOSTED_OPERATOR, TENANT_CONTEXT_PATH)); - assertEquals(Set.of(Role.hostedOperator(), Role.systemFlagsDeployer(), Role.systemFlagsDryrunner(), Role.paymentProcessor(), Role.hostedSupporter()), + assertEquals(Set.of(Role.hostedOperator(), Role.systemFlagsDeployer(), Role.systemFlagsDryrunner(), Role.paymentProcessor(), Role.hostedAccountant(), Role.hostedSupporter()), filter.roles(HOSTED_OPERATOR, APPLICATION_CONTEXT_PATH)); // Tenant admins are members of the athenzTenantAdmin role within their tenant subtree. diff --git a/default_build_settings.cmake b/default_build_settings.cmake index 64c918370aa..cc51bbde852 100644 --- a/default_build_settings.cmake +++ b/default_build_settings.cmake @@ -18,7 +18,7 @@ endfunction() function(setup_vespa_default_build_settings_rhel_8) message("-- Setting up default build settings for rhel 8") set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_DEPS}/include" "/usr/include/openblas" PARENT_SCOPE) - set(DEFAULT_VESPA_LLVM_VERSION "8" PARENT_SCOPE) + set(DEFAULT_VESPA_LLVM_VERSION "9" PARENT_SCOPE) endfunction() function(setup_vespa_default_build_settings_centos_7) diff --git a/dist/vespa.spec b/dist/vespa.spec index a887fd0645b..690d4123de4 100644 --- a/dist/vespa.spec +++ b/dist/vespa.spec @@ -62,7 +62,11 @@ BuildRequires: vespa-icu-devel >= 65.1.0-1 %endif %if 0%{?el8} BuildRequires: cmake >= 3.11.4-3 +%if 0%{?centos} BuildRequires: llvm-devel >= 8.0.1 +%else +BuildRequires: llvm-devel >= 9.0.1 +%endif BuildRequires: boost-devel >= 1.66 BuildRequires: openssl-devel BuildRequires: vespa-gtest >= 1.8.1-1 @@ -162,10 +166,15 @@ Requires: vespa-telegraf >= 1.1.1-1 %define _extra_include_directory /usr/include/llvm7.0;%{_vespa_deps_prefix}/include;/usr/include/openblas %endif %if 0%{?el8} +%if 0%{?centos} Requires: llvm-libs >= 8.0.1 +%define _vespa_llvm_version 8 +%else +Requires: llvm-libs >= 9.0.1 +%define _vespa_llvm_version 9 +%endif Requires: vespa-protobuf >= 3.7.0-4 Requires: openssl-libs -%define _vespa_llvm_version 8 %define _extra_link_directory %{_vespa_deps_prefix}/lib64 %define _extra_include_directory %{_vespa_deps_prefix}/include;/usr/include/openblas %endif diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java index 838e55910e1..efe86bb6d55 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java @@ -246,13 +246,8 @@ public class Flags { "Takes effect on next application redeploy", APPLICATION_ID); - public static final UnboundLongFlag CONFIGSERVER_SESSIONS_EXPIRY_INTERVAL_IN_DAYS = defineLongFlag( - "configserver-sessions-expiry-interval-in-days", 1, - "Expiry time for unused sessions in config server", - "Takes effect on next run of config server maintainer SessionsMaintainer"); - public static final UnboundLongFlag CONFIGSERVER_LOCAL_SESSIONS_EXPIRY_INTERVAL_IN_DAYS = defineLongFlag( - "configserver-local-sessions-expiry-interval-in-days", 21, + "configserver-local-sessions-expiry-interval-in-days", 1, "Expiry time for expired local sessions in config server", "Takes effect on next run of config server maintainer SessionsMaintainer"); diff --git a/model-integration/src/main/java/ai/vespa/rankingexpression/importer/operations/IntermediateOperation.java b/model-integration/src/main/java/ai/vespa/rankingexpression/importer/operations/IntermediateOperation.java index af134fac6cf..6e637c72d0f 100644 --- a/model-integration/src/main/java/ai/vespa/rankingexpression/importer/operations/IntermediateOperation.java +++ b/model-integration/src/main/java/ai/vespa/rankingexpression/importer/operations/IntermediateOperation.java @@ -59,7 +59,7 @@ public abstract class IntermediateOperation { IntermediateOperation(String modelName, String name, List<IntermediateOperation> inputs) { this.name = name; - this.modelName = modelName; + this.modelName = ensureValidAsDimensionName(modelName); this.inputs = new ArrayList<>(inputs); this.inputs.forEach(i -> i.outputs.add(this)); } @@ -351,6 +351,11 @@ public abstract class IntermediateOperation { public abstract String operationName(); + /** Required due to tensor dimension name restrictions */ + private static String ensureValidAsDimensionName(String modelName) { + return modelName.replaceAll("[^\\w\\d\\$@_]", "_"); + } + @Override public String toString() { return operationName() + "(" + diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java index 45ed8db3491..a9861497ca3 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java @@ -168,17 +168,25 @@ public final class Node { public Optional<TenantName> reservedTo() { return reservedTo; } /** - * Returns a copy of this node with wantToRetire set to the given value and updated history. - * If given wantToRetire is equal to the current, the method is no-op. + * Returns a copy of this node with wantToRetire and wantToDeprovision set to the given values and updated history. + * + * If both given wantToRetire and wantToDeprovision are equal to the current values, the method is no-op. */ - public Node withWantToRetire(boolean wantToRetire, Agent agent, Instant at) { - if (wantToRetire == status.wantToRetire()) return this; - Node node = this.with(status.withWantToRetire(wantToRetire)); + public Node withWantToRetire(boolean wantToRetire, boolean wantToDeprovision, Agent agent, Instant at) { + if (!type.isDockerHost() && wantToDeprovision) + throw new IllegalArgumentException("wantToDeprovision can only be set for hosts"); + if (wantToRetire == status.wantToRetire() && + wantToDeprovision == status.wantToDeprovision()) return this; + Node node = this.with(status.withWantToRetire(wantToRetire, wantToDeprovision)); if (wantToRetire) node = node.with(history.with(new History.Event(History.Event.Type.wantToRetire, agent, at))); return node; } + public Node withWantToRetire(boolean wantToRetire, Agent agent, Instant at) { + return withWantToRetire(wantToRetire, status.wantToDeprovision(), agent, at); + } + /** * Returns a copy of this node which is retired. * If the node was already retired it is returned as-is. diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java index cc485374340..b5e36abd076 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java @@ -434,7 +434,7 @@ public class NodeRepository extends AbstractComponent { .map(node -> { if (node.state() != State.provisioned && node.state() != State.dirty) illegal("Can not set " + node + " ready. It is not provisioned or dirty."); - return node.with(node.status().withWantToRetire(false).withWantToDeprovision(false)); + return node.withWantToRetire(false, false, Agent.system, clock.instant()); }) .collect(Collectors.toList()); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveExpirer.java index b2c57e984eb..3cb7cc218a7 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveExpirer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveExpirer.java @@ -5,6 +5,7 @@ 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.History; +import com.yahoo.vespa.hosted.provision.node.Status; import java.time.Clock; import java.time.Duration; @@ -14,15 +15,19 @@ import java.util.List; * Maintenance job which moves inactive nodes to dirty or parked after timeout. * * The timeout is in place for two reasons: - * <ul> - * <li>To ensure that the new application configuration has time to - * propagate before the node is used for something else - * <li>To provide a grace period in which nodes can be brought back to active - * if they were deactivated in error. As inactive nodes retain their state - * they can be brought back to active and correct state faster than a new node. - * </ul> * - * Nodes with the retired flag should not be reused and will be moved to parked instead of dirty. + * - To ensure that the new application configuration has time to + * propagate before the node is used for something else. + * + * - To provide a grace period in which nodes can be brought back to active + * if they were deactivated in error. As inactive nodes retain their state + * they can be brought back to active and correct state faster than a new node. + * + * Nodes with following flags set are not reusable and will be moved to parked + * instead of dirty: + * + * - {@link Status#wantToRetire()} (when set by an operator) + * - {@link Status#wantToDeprovision()} * * @author bratseth * @author mpolden diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Status.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Status.java index 6b52bd68e73..c289edfc19e 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Status.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Status.java @@ -36,6 +36,11 @@ public class Status { this.vespaVersion = Objects.requireNonNull(vespaVersion, "Vespa version must be non-null").filter(v -> !Version.emptyVersion.equals(v)); this.dockerImage = Objects.requireNonNull(dockerImage, "Docker image must be non-null").filter(d -> !DockerImage.EMPTY.equals(d)); this.failCount = failCount; + if (wantToDeprovision && !wantToRetire) { + // TODO(mpolden): Throw when persisted nodes have been rewritten + wantToRetire = true; + //throw new IllegalArgumentException("Node cannot be marked wantToDeprovision unless it's also marked wantToRetire"); + } this.wantToRetire = wantToRetire; this.wantToDeprovision = wantToDeprovision; this.osVersion = Objects.requireNonNull(osVersion, "OS version must be non-null"); @@ -69,8 +74,8 @@ public class Status { /** Returns how many times this node has been moved to the failed state. */ public int failCount() { return failCount; } - /** Returns a copy of this with the want to retire flag changed */ - public Status withWantToRetire(boolean wantToRetire) { + /** Returns a copy of this with the want to retire/deprovision flags changed */ + public Status withWantToRetire(boolean wantToRetire, boolean wantToDeprovision) { return new Status(reboot, vespaVersion, dockerImage, failCount, wantToRetire, wantToDeprovision, osVersion, firmwareVerifiedAt); } @@ -82,11 +87,6 @@ public class Status { return wantToRetire; } - /** Returns a copy of this with the want to de-provision flag changed */ - public Status withWantToDeprovision(boolean wantToDeprovision) { - return new Status(reboot, vespaVersion, dockerImage, failCount, wantToRetire, wantToDeprovision, osVersion, firmwareVerifiedAt); - } - /** * Returns whether this node should be de-provisioned when possible. */ diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/RetiringUpgrader.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/RetiringUpgrader.java index 6bd2545b153..b2b83b6d064 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/RetiringUpgrader.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/RetiringUpgrader.java @@ -72,10 +72,10 @@ public class RetiringUpgrader implements Upgrader { LOG.info("Retiring and deprovisioning " + host + ": On stale OS version " + host.status().osVersion().current().map(Version::toFullString).orElse("<unset>") + ", want " + target); - nodesToRetire.add(host.with(host.status() - .withWantToDeprovision(true) - .withOsVersion(host.status().osVersion().withWanted(Optional.of(target)))) - .withWantToRetire(true, Agent.RetiringUpgrader, now)); + + host = host.withWantToRetire(true, true, Agent.RetiringUpgrader, now); + host = host.with(host.status().withOsVersion(host.status().osVersion().withWanted(Optional.of(target)))); + nodesToRetire.add(host); nodeRepository.write(nodesToRetire, lock); nodeRepository.osVersions().writeChange((change) -> change.withRetirementAt(now, nodeType)); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodePatcher.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodePatcher.java index 8b5639cc514..897af634d49 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodePatcher.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodePatcher.java @@ -6,13 +6,12 @@ import com.yahoo.config.provision.DockerImage; import com.yahoo.config.provision.Flavor; import com.yahoo.config.provision.NodeFlavors; import com.yahoo.config.provision.NodeResources; -import com.yahoo.config.provision.NodeType; import com.yahoo.config.provision.TenantName; import com.yahoo.io.IOUtils; import com.yahoo.slime.Inspector; import com.yahoo.slime.ObjectTraverser; -import com.yahoo.slime.Type; import com.yahoo.slime.SlimeUtils; +import com.yahoo.slime.Type; import com.yahoo.vespa.hosted.provision.LockedNodeList; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.node.Agent; @@ -48,6 +47,7 @@ import static com.yahoo.config.provision.NodeResources.StorageType.remote; public class NodePatcher { private static final String WANT_TO_RETIRE = "wantToRetire"; + private static final String WANT_TO_DEPROVISION = "wantToDeprovision"; private final NodeFlavors nodeFlavors; private final Inspector inspector; @@ -77,13 +77,13 @@ public class NodePatcher { List<Node> patchedNodes = new ArrayList<>(); inspector.traverse((String name, Inspector value) -> { try { - node = applyField(node, name, value); + node = applyField(node, name, value, inspector); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("Could not set field '" + name + "'", e); } try { - patchedNodes.addAll(applyFieldRecursive(name, value)); + patchedNodes.addAll(applyFieldRecursive(name, value, inspector)); } catch (IllegalArgumentException e) { // Non recursive field, ignore } @@ -93,12 +93,12 @@ public class NodePatcher { return patchedNodes; } - private List<Node> applyFieldRecursive(String name, Inspector value) { + private List<Node> applyFieldRecursive(String name, Inspector value, Inspector root) { switch (name) { case WANT_TO_RETIRE: List<Node> childNodes = node.type().isDockerHost() ? nodes.get().childrenOf(node).asList() : List.of(); return childNodes.stream() - .map(child -> applyField(child, name, value)) + .map(child -> applyField(child, name, value, root)) .collect(Collectors.toList()); default : @@ -106,7 +106,7 @@ public class NodePatcher { } } - private Node applyField(Node node, String name, Inspector value) { + private Node applyField(Node node, String name, Inspector value, Inspector root) { switch (name) { case "currentRebootGeneration" : return node.withCurrentRebootGeneration(asLong(value), clock.instant()); @@ -134,11 +134,10 @@ public class NodePatcher { case "additionalIpAddresses" : return IP.Config.verify(node.with(node.ipConfig().with(IP.Pool.of(asStringSet(value)))), nodes.get()); case WANT_TO_RETIRE : - return node.withWantToRetire(asBoolean(value), Agent.operator, clock.instant()); - case "wantToDeprovision" : - if (node.type() != NodeType.host && asBoolean(value)) - throw new IllegalArgumentException("wantToDeprovision can only be set for hosts"); - return node.with(node.status().withWantToDeprovision(asBoolean(value))); + case WANT_TO_DEPROVISION : + boolean wantToRetire = asOptionalBoolean(root.field(WANT_TO_RETIRE)).orElse(node.status().wantToRetire()); + boolean wantToDeprovision = asOptionalBoolean(root.field(WANT_TO_DEPROVISION)).orElse(node.status().wantToDeprovision()); + return node.withWantToRetire(wantToRetire, wantToDeprovision, Agent.operator, clock.instant()); case "reports" : return nodeWithPatchedReports(node, value); case "openStackId" : @@ -202,7 +201,7 @@ public class NodePatcher { if ((hasHardFailReports && node.state() == Node.State.failed) || node.state() == Node.State.parked) return patchedNode; - patchedNode = patchedNode.with(patchedNode.status().withWantToDeprovision(hasHardFailReports)); + patchedNode = patchedNode.withWantToRetire(hasHardFailReports, hasHardFailReports, Agent.system, clock.instant()); } return patchedNode; @@ -252,19 +251,14 @@ public class NodePatcher { return field.asString(); } - private Optional<String> asOptionalString(Inspector field) { - return field.type().equals(Type.NIX) ? Optional.empty() : Optional.of(asString(field)); - } - - // Allows us to clear optional flags by passing "null" as slime does not have an empty (but present) representation - private Optional<String> removeQuotedNulls(Optional<String> value) { - return value.filter(v -> !v.equals("null")); - } - private boolean asBoolean(Inspector field) { if ( ! field.type().equals(Type.BOOL)) throw new IllegalArgumentException("Expected a BOOL value, got a " + field.type()); return field.asBool(); } + private Optional<Boolean> asOptionalBoolean(Inspector field) { + return Optional.of(field).filter(Inspector::valid).map(this::asBoolean); + } + } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java index 94b97d91312..151bd80a7b7 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java @@ -117,7 +117,7 @@ public class MockNodeRepository extends NodeRepository { Node node55 = createNode("node55", "host55.yahoo.com", ipConfig(55), Optional.empty(), new Flavor(new NodeResources(2, 8, 50, 1, fast, local)), Optional.empty(), NodeType.tenant); - nodes.add(node55.with(node55.status().withWantToRetire(true).withWantToDeprovision(true))); + nodes.add(node55.with(node55.status().withWantToRetire(true, true))); /* Setup docker hosts (two of these will be reserved for spares */ nodes.add(createNode("dockerhost1", "dockerhost1.yahoo.com", ipConfig(100, 1, 3), Optional.empty(), diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTest.java index 7bb80fe2a21..97f1eda866a 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTest.java @@ -2,7 +2,6 @@ package com.yahoo.vespa.hosted.provision; import com.yahoo.config.provision.NodeType; -import com.yahoo.test.ManualClock; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.node.History; import com.yahoo.vespa.hosted.provision.node.IP; @@ -13,7 +12,6 @@ import org.junit.Test; import java.time.Duration; import java.time.Instant; import java.util.Arrays; -import java.util.EnumSet; import java.util.HashSet; import java.util.Set; import java.util.function.Predicate; @@ -165,8 +163,7 @@ public class NodeRepositoryTest { // Set host 1 properties and deprovision it Node host1 = tester.nodeRepository().getNode("host1").get(); - host1 = host1.withWantToRetire(true, Agent.system, tester.nodeRepository().clock().instant()); - host1 = host1.with(host1.status().withWantToDeprovision(true)); + host1 = host1.withWantToRetire(true, true, Agent.system, tester.nodeRepository().clock().instant()); host1 = host1.withFirmwareVerifiedAt(tester.clock().instant()); host1 = host1.with(host1.status().withIncreasedFailCount()); host1 = host1.with(host1.reports().withReport(Report.basicReport("id", Report.Type.HARD_FAIL, tester.clock().instant(), "Test report"))); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java index e42e8e57b8c..89e43f80479 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java @@ -191,11 +191,11 @@ public class InactiveAndFailedExpirerTest { @Test public void nodes_marked_for_deprovisioning_move_to_parked() { ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build(); - tester.makeReadyNodes(5, nodeResources); + tester.makeReadyHosts(2, nodeResources); // Activate and deallocate ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test")).vespaVersion("6.42").build(); - List<HostSpec> preparedNodes = tester.prepare(applicationId, cluster, Capacity.from(new ClusterResources(2, 1, nodeResources))); + List<HostSpec> preparedNodes = tester.prepare(applicationId, cluster, Capacity.fromRequiredNodeType(NodeType.host)); tester.activate(applicationId, new HashSet<>(preparedNodes)); assertEquals(2, tester.getNodes(applicationId, Node.State.active).size()); tester.deactivate(applicationId); @@ -204,7 +204,7 @@ public class InactiveAndFailedExpirerTest { // Nodes marked for deprovisioning are moved to parked tester.nodeRepository().write(inactiveNodes.stream() - .map(node -> node.with(node.status().withWantToDeprovision(true))) + .map(node -> node.withWantToRetire(true, true, Agent.system, tester.clock().instant())) .collect(Collectors.toList()), () -> {}); tester.advanceTime(Duration.ofMinutes(11)); new InactiveExpirer(tester.nodeRepository(), tester.clock(), Duration.ofMinutes(10)).run(); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/InPlaceResizeProvisionTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/InPlaceResizeProvisionTest.java index 16fba824300..b2ee298c19d 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/InPlaceResizeProvisionTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/InPlaceResizeProvisionTest.java @@ -205,7 +205,7 @@ public class InPlaceResizeProvisionTest { // ... same with setting a node to want to retire Node nodeToWantoToRetire = listCluster(content1).not().retired().asList().get(0); - tester.nodeRepository().write(nodeToWantoToRetire.with(nodeToWantoToRetire.status().withWantToRetire(true)), + tester.nodeRepository().write(nodeToWantoToRetire.withWantToRetire(true, Agent.system, tester.clock().instant()), tester.nodeRepository().lock(nodeToWantoToRetire)); new PrepareHelper(tester, app).prepare(content1, 8, 1, halvedResources).activate(); assertTrue(listCluster(content1).retired().stream().anyMatch(n -> n.equals(nodeToWantoToRetire))); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/NodeTypeProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/NodeTypeProvisioningTest.java index 98133898cf6..11e7af512c3 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/NodeTypeProvisioningTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/NodeTypeProvisioningTest.java @@ -113,7 +113,7 @@ public class NodeTypeProvisioningTest { Node nodeToRetire = tester.nodeRepository().getNodes(NodeType.proxy, Node.State.active).get(5); { // Pick out a node and retire it - tester.nodeRepository().write(nodeToRetire.with(nodeToRetire.status().withWantToRetire(true)), () -> {}); + tester.nodeRepository().write(nodeToRetire.withWantToRetire(true, Agent.system, tester.clock().instant()), () -> {}); List<HostSpec> hosts = deployProxies(application, tester); assertEquals(11, hosts.size()); @@ -186,7 +186,7 @@ public class NodeTypeProvisioningTest { String currentyRetiringHostname; { nodesToRetire.forEach(nodeToRetire -> - tester.nodeRepository().write(nodeToRetire.with(nodeToRetire.status().withWantToRetire(true)), () -> {})); + tester.nodeRepository().write(nodeToRetire.withWantToRetire(true, Agent.system, tester.clock().instant()), () -> {})); List<HostSpec> hosts = deployProxies(application, tester); assertEquals(11, hosts.size()); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java index 7a7f8a7d891..607ca963cef 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java @@ -633,7 +633,7 @@ public class ProvisioningTest { ApplicationId application = tester.makeApplicationId(); // Flag all nodes for retirement List<Node> readyNodes = tester.makeReadyNodes(5, defaultResources); - readyNodes.forEach(node -> tester.patchNode(node.with(node.status().withWantToRetire(true)))); + readyNodes.forEach(node -> tester.patchNode(node.withWantToRetire(true, Agent.system, tester.clock().instant()))); try { prepare(application, 2, 0, 2, 0, defaultResources, tester); @@ -661,7 +661,7 @@ public class ProvisioningTest { assertEquals(0, NodeList.copyOf(tester.nodeRepository().getNodes(application, Node.State.active)).retired().size()); // Mark the nodes as want to retire - tester.nodeRepository().getNodes(application, Node.State.active).forEach(node -> tester.patchNode(node.with(node.status().withWantToRetire(true)))); + tester.nodeRepository().getNodes(application, Node.State.active).forEach(node -> tester.patchNode(node.withWantToRetire(true, Agent.system, tester.clock().instant()))); // redeploy without allow failing tester.activate(application, tester.prepare(application, cluster, capacityFORCED)); @@ -724,7 +724,7 @@ public class ProvisioningTest { // Retire some nodes and redeploy { List<Node> nodesToRetire = tester.getNodes(application, Node.State.active).asList().subList(0, 2); - nodesToRetire.forEach(node -> tester.patchNode(node.with(node.status().withWantToRetire(true)))); + nodesToRetire.forEach(node -> tester.patchNode(node.withWantToRetire(true, Agent.system, tester.clock().instant()))); SystemState state = prepare(application, 2, 0, 2, 0, defaultResources, tester); tester.activate(application, state.allHosts); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java index 29dac58e10c..1e788e2c70e 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java @@ -207,8 +207,17 @@ public class NodesV2ApiTest { Utf8.toBytes("{\"modelName\": \"foo\"}"), Request.Method.PATCH), "{\"message\":\"Updated dockerhost1.yahoo.com\"}"); assertResponse(new Request("http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com", - Utf8.toBytes("{\"wantToDeprovision\": true}"), Request.Method.PATCH), + Utf8.toBytes("{\"wantToRetire\": true}"), Request.Method.PATCH), "{\"message\":\"Updated dockerhost1.yahoo.com\"}"); + assertResponse(new Request("http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com", + Utf8.toBytes("{\"wantToDeprovision\": true}"), Request.Method.PATCH), + "{\"message\":\"Updated dockerhost1.yahoo.com\"}"); + assertResponse(new Request("http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com", + Utf8.toBytes("{\"wantToDeprovision\": false, \"wantToRetire\": false}"), Request.Method.PATCH), + "{\"message\":\"Updated dockerhost1.yahoo.com\"}"); + assertResponse(new Request("http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com", + Utf8.toBytes("{\"wantToDeprovision\": true, \"wantToRetire\": true}"), Request.Method.PATCH), + "{\"message\":\"Updated dockerhost1.yahoo.com\"}"); tester.assertResponseContains(new Request("http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com"), "\"modelName\":\"foo\""); assertResponse(new Request("http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com", Utf8.toBytes("{\"modelName\": null}"), Request.Method.PATCH), @@ -379,7 +388,7 @@ public class NodesV2ApiTest { @Test public void fails_to_ready_node_with_hard_fail() throws Exception { assertResponse(new Request("http://localhost:8080/nodes/v2/node", - ("[" + asNodeJson("host12.yahoo.com", "default") + "]"). + ("[" + asHostJson("host12.yahoo.com", "default", Optional.empty()) + "]"). getBytes(StandardCharsets.UTF_8), Request.Method.POST), "{\"message\":\"Added 1 nodes to the provisioned state\"}"); @@ -563,7 +572,7 @@ public class NodesV2ApiTest { @Test public void test_reports_patching() throws IOException { // Add report - assertResponse(new Request("http://localhost:8080/nodes/v2/node/host6.yahoo.com", + assertResponse(new Request("http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com", Utf8.toBytes("{" + " \"reports\": {" + " \"actualCpuCores\": {" + @@ -584,19 +593,19 @@ public class NodesV2ApiTest { " }" + "}"), Request.Method.PATCH), - "{\"message\":\"Updated host6.yahoo.com\"}"); - assertFile(new Request("http://localhost:8080/nodes/v2/node/host6.yahoo.com"), "node6-reports.json"); + "{\"message\":\"Updated dockerhost1.yahoo.com\"}"); + assertFile(new Request("http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com"), "docker-node1-reports.json"); // Patching with an empty reports is no-op - tester.assertResponse(new Request("http://localhost:8080/nodes/v2/node/host6.yahoo.com", + tester.assertResponse(new Request("http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com", Utf8.toBytes("{\"reports\": {}}"), Request.Method.PATCH), 200, - "{\"message\":\"Updated host6.yahoo.com\"}"); - assertFile(new Request("http://localhost:8080/nodes/v2/node/host6.yahoo.com"), "node6-reports.json"); + "{\"message\":\"Updated dockerhost1.yahoo.com\"}"); + assertFile(new Request("http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com"), "docker-node1-reports.json"); // Patching existing report overwrites - tester.assertResponse(new Request("http://localhost:8080/nodes/v2/node/host6.yahoo.com", + tester.assertResponse(new Request("http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com", Utf8.toBytes("{" + " \"reports\": {" + " \"actualCpuCores\": {" + @@ -606,22 +615,22 @@ public class NodesV2ApiTest { "}"), Request.Method.PATCH), 200, - "{\"message\":\"Updated host6.yahoo.com\"}"); - assertFile(new Request("http://localhost:8080/nodes/v2/node/host6.yahoo.com"), "node6-reports-2.json"); + "{\"message\":\"Updated dockerhost1.yahoo.com\"}"); + assertFile(new Request("http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com"), "docker-node1-reports-2.json"); // Clearing one report - assertResponse(new Request("http://localhost:8080/nodes/v2/node/host6.yahoo.com", + assertResponse(new Request("http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com", Utf8.toBytes("{\"reports\": { \"diskSpace\": null } }"), Request.Method.PATCH), - "{\"message\":\"Updated host6.yahoo.com\"}"); - assertFile(new Request("http://localhost:8080/nodes/v2/node/host6.yahoo.com"), "node6-reports-3.json"); + "{\"message\":\"Updated dockerhost1.yahoo.com\"}"); + assertFile(new Request("http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com"), "docker-node1-reports-3.json"); // Clearing all reports - assertResponse(new Request("http://localhost:8080/nodes/v2/node/host6.yahoo.com", + assertResponse(new Request("http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com", Utf8.toBytes("{\"reports\": null }"), Request.Method.PATCH), - "{\"message\":\"Updated host6.yahoo.com\"}"); - assertFile(new Request("http://localhost:8080/nodes/v2/node/host6.yahoo.com"), "node6.json"); + "{\"message\":\"Updated dockerhost1.yahoo.com\"}"); + assertFile(new Request("http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com"), "docker-node1-reports-4.json"); } @Test diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node6-reports-2.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports-2.json index a3d53798d7c..220fdbd8654 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node6-reports-2.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports-2.json @@ -1,35 +1,50 @@ { - "url": "http://localhost:8080/nodes/v2/node/host6.yahoo.com", - "id": "host6.yahoo.com", + "url": "http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com", + "id": "dockerhost1.yahoo.com", "state": "active", - "type": "tenant", - "hostname": "host6.yahoo.com", - "openStackId": "node6", - "flavor": "[vcpu: 2.0, memory: 8.0 Gb, disk 50.0 Gb, bandwidth: 1.0 Gbps, storage type: local]", - "resources":{"vcpu":2.0,"memoryGb":8.0,"diskGb":50.0,"bandwidthGbps":1.0,"diskSpeed":"fast","storageType":"local"}, - "environment": "DOCKER_CONTAINER", + "type": "host", + "hostname": "dockerhost1.yahoo.com", + "openStackId": "dockerhost1", + "flavor": "large", + "cpuCores": 4.0, + "resources": { + "vcpu": 4.0, + "memoryGb": 32.0, + "diskGb": 1600.0, + "bandwidthGbps": 20.0, + "diskSpeed": "fast", + "storageType": "remote" + }, + "environment": "BARE_METAL", "owner": { - "tenant": "tenant2", - "application": "application2", - "instance": "instance2" + "tenant": "zoneapp", + "application": "zoneapp", + "instance": "zoneapp" }, "membership": { - "clustertype": "content", - "clusterid": "id2", + "clustertype": "container", + "clusterid": "node-admin", "group": "0", - "index": 1, + "index": 0, "retired": false }, "restartGeneration": 0, "currentRestartGeneration": 0, "wantedDockerImage": "docker-registry.domain.tld:8080/dist/vespa:6.42.0", "wantedVespaVersion": "6.42.0", - "requestedResources": { "vcpu":2.0, "memoryGb":8.0, "diskGb":50.0, "bandwidthGbps":1.0, "diskSpeed":"fast", "storageType":"any" }, + "requestedResources": { + "vcpu": 4.0, + "memoryGb": 32.0, + "diskGb": 1600.0, + "bandwidthGbps": 20.0, + "diskSpeed": "fast", + "storageType": "remote" + }, "allowedToBeDown": false, - "rebootGeneration": 1, + "rebootGeneration": 0, "currentRebootGeneration": 0, "failCount": 0, - "wantToRetire": false, + "wantToRetire": true, "wantToDeprovision": true, "history": [ { @@ -51,13 +66,22 @@ "event": "activated", "at": 123, "agent": "application" + }, + { + "event": "wantToRetire", + "at": 123, + "agent": "system" } ], "ipAddresses": [ - "127.0.6.1", - "::6:1" + "127.0.100.1", + "::100:1" + ], + "additionalIpAddresses": [ + "::100:2", + "::100:3", + "::100:4" ], - "additionalIpAddresses": [], "reports": { "actualCpuCores": { "createdMillis": 3 @@ -65,7 +89,7 @@ "diskSpace": { "createdMillis": 2, "description": "Actual disk space (2TB) differs from spec (3TB)", - "type":"HARD_FAIL", + "type": "HARD_FAIL", "details": { "inGib": 3, "disks": [ diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports-3.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports-3.json new file mode 100644 index 00000000000..d2474f21c55 --- /dev/null +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports-3.json @@ -0,0 +1,90 @@ +{ + "url": "http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com", + "id": "dockerhost1.yahoo.com", + "state": "active", + "type": "host", + "hostname": "dockerhost1.yahoo.com", + "openStackId": "dockerhost1", + "flavor": "large", + "cpuCores": 4.0, + "resources": { + "vcpu": 4.0, + "memoryGb": 32.0, + "diskGb": 1600.0, + "bandwidthGbps": 20.0, + "diskSpeed": "fast", + "storageType": "remote" + }, + "environment": "BARE_METAL", + "owner": { + "tenant": "zoneapp", + "application": "zoneapp", + "instance": "zoneapp" + }, + "membership": { + "clustertype": "container", + "clusterid": "node-admin", + "group": "0", + "index": 0, + "retired": false + }, + "restartGeneration": 0, + "currentRestartGeneration": 0, + "wantedDockerImage": "docker-registry.domain.tld:8080/dist/vespa:6.42.0", + "wantedVespaVersion": "6.42.0", + "requestedResources": { + "vcpu": 4.0, + "memoryGb": 32.0, + "diskGb": 1600.0, + "bandwidthGbps": 20.0, + "diskSpeed": "fast", + "storageType": "remote" + }, + "allowedToBeDown": false, + "rebootGeneration": 0, + "currentRebootGeneration": 0, + "failCount": 0, + "wantToRetire": false, + "wantToDeprovision": false, + "history": [ + { + "event": "provisioned", + "at": 123, + "agent": "system" + }, + { + "event": "readied", + "at": 123, + "agent": "system" + }, + { + "event": "reserved", + "at": 123, + "agent": "application" + }, + { + "event": "activated", + "at": 123, + "agent": "application" + }, + { + "event": "wantToRetire", + "at": 123, + "agent": "system" + } + ], + "ipAddresses": [ + "127.0.100.1", + "::100:1" + ], + "additionalIpAddresses": [ + "::100:2", + "::100:3", + "::100:4" + ], + "reports": { + "actualCpuCores": { + "createdMillis": 3 + } + } +} diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports-4.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports-4.json new file mode 100644 index 00000000000..cbf02795d73 --- /dev/null +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports-4.json @@ -0,0 +1,85 @@ +{ + "url": "http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com", + "id": "dockerhost1.yahoo.com", + "state": "active", + "type": "host", + "hostname": "dockerhost1.yahoo.com", + "openStackId": "dockerhost1", + "flavor": "large", + "cpuCores": 4.0, + "resources": { + "vcpu": 4.0, + "memoryGb": 32.0, + "diskGb": 1600.0, + "bandwidthGbps": 20.0, + "diskSpeed": "fast", + "storageType": "remote" + }, + "environment": "BARE_METAL", + "owner": { + "tenant": "zoneapp", + "application": "zoneapp", + "instance": "zoneapp" + }, + "membership": { + "clustertype": "container", + "clusterid": "node-admin", + "group": "0", + "index": 0, + "retired": false + }, + "restartGeneration": 0, + "currentRestartGeneration": 0, + "wantedDockerImage": "docker-registry.domain.tld:8080/dist/vespa:6.42.0", + "wantedVespaVersion": "6.42.0", + "requestedResources": { + "vcpu": 4.0, + "memoryGb": 32.0, + "diskGb": 1600.0, + "bandwidthGbps": 20.0, + "diskSpeed": "fast", + "storageType": "remote" + }, + "allowedToBeDown": false, + "rebootGeneration": 0, + "currentRebootGeneration": 0, + "failCount": 0, + "wantToRetire": false, + "wantToDeprovision": false, + "history": [ + { + "event": "provisioned", + "at": 123, + "agent": "system" + }, + { + "event": "readied", + "at": 123, + "agent": "system" + }, + { + "event": "reserved", + "at": 123, + "agent": "application" + }, + { + "event": "activated", + "at": 123, + "agent": "application" + }, + { + "event": "wantToRetire", + "at": 123, + "agent": "system" + } + ], + "ipAddresses": [ + "127.0.100.1", + "::100:1" + ], + "additionalIpAddresses": [ + "::100:2", + "::100:3", + "::100:4" + ] +} diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node6-reports.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports.json index 67b8d67c7f1..c00c06634b5 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node6-reports.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports.json @@ -1,35 +1,50 @@ { - "url": "http://localhost:8080/nodes/v2/node/host6.yahoo.com", - "id": "host6.yahoo.com", + "url": "http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com", + "id": "dockerhost1.yahoo.com", "state": "active", - "type": "tenant", - "hostname": "host6.yahoo.com", - "openStackId": "node6", - "flavor": "[vcpu: 2.0, memory: 8.0 Gb, disk 50.0 Gb, bandwidth: 1.0 Gbps, storage type: local]", - "resources":{"vcpu":2.0,"memoryGb":8.0,"diskGb":50.0,"bandwidthGbps":1.0,"diskSpeed":"fast","storageType":"local"}, - "environment": "DOCKER_CONTAINER", + "type": "host", + "hostname": "dockerhost1.yahoo.com", + "openStackId": "dockerhost1", + "flavor": "large", + "cpuCores": 4.0, + "resources": { + "vcpu": 4.0, + "memoryGb": 32.0, + "diskGb": 1600.0, + "bandwidthGbps": 20.0, + "diskSpeed": "fast", + "storageType": "remote" + }, + "environment": "BARE_METAL", "owner": { - "tenant": "tenant2", - "application": "application2", - "instance": "instance2" + "tenant": "zoneapp", + "application": "zoneapp", + "instance": "zoneapp" }, "membership": { - "clustertype": "content", - "clusterid": "id2", + "clustertype": "container", + "clusterid": "node-admin", "group": "0", - "index": 1, + "index": 0, "retired": false }, "restartGeneration": 0, "currentRestartGeneration": 0, "wantedDockerImage": "docker-registry.domain.tld:8080/dist/vespa:6.42.0", "wantedVespaVersion": "6.42.0", - "requestedResources": { "vcpu":2.0, "memoryGb":8.0, "diskGb":50.0, "bandwidthGbps":1.0, "diskSpeed":"fast", "storageType":"any" }, + "requestedResources": { + "vcpu": 4.0, + "memoryGb": 32.0, + "diskGb": 1600.0, + "bandwidthGbps": 20.0, + "diskSpeed": "fast", + "storageType": "remote" + }, "allowedToBeDown": false, - "rebootGeneration": 1, + "rebootGeneration": 0, "currentRebootGeneration": 0, "failCount": 0, - "wantToRetire": false, + "wantToRetire": true, "wantToDeprovision": true, "history": [ { @@ -51,24 +66,33 @@ "event": "activated", "at": 123, "agent": "application" + }, + { + "event": "wantToRetire", + "at": 123, + "agent": "system" } ], "ipAddresses": [ - "127.0.6.1", - "::6:1" + "127.0.100.1", + "::100:1" + ], + "additionalIpAddresses": [ + "::100:2", + "::100:3", + "::100:4" ], - "additionalIpAddresses": [], "reports": { "actualCpuCores": { "createdMillis": 1, "description": "Actual number of CPU cores (2) differs from spec (4)", - "type":"HARD_FAIL", + "type": "HARD_FAIL", "value": 2 }, "diskSpace": { "createdMillis": 2, "description": "Actual disk space (2TB) differs from spec (3TB)", - "type":"HARD_FAIL", + "type": "HARD_FAIL", "details": { "inGib": 3, "disks": [ diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node6-reports-3.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node6-reports-3.json deleted file mode 100644 index 7f0c3a5f706..00000000000 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node6-reports-3.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "url": "http://localhost:8080/nodes/v2/node/host6.yahoo.com", - "id": "host6.yahoo.com", - "state": "active", - "type": "tenant", - "hostname": "host6.yahoo.com", - "openStackId": "node6", - "flavor": "[vcpu: 2.0, memory: 8.0 Gb, disk 50.0 Gb, bandwidth: 1.0 Gbps, storage type: local]", - "resources":{"vcpu":2.0,"memoryGb":8.0,"diskGb":50.0,"bandwidthGbps":1.0,"diskSpeed":"fast","storageType":"local"}, - "environment": "DOCKER_CONTAINER", - "owner": { - "tenant": "tenant2", - "application": "application2", - "instance": "instance2" - }, - "membership": { - "clustertype": "content", - "clusterid": "id2", - "group": "0", - "index": 1, - "retired": false - }, - "restartGeneration": 0, - "currentRestartGeneration": 0, - "wantedDockerImage": "docker-registry.domain.tld:8080/dist/vespa:6.42.0", - "wantedVespaVersion": "6.42.0", - "requestedResources": { "vcpu":2.0, "memoryGb":8.0, "diskGb":50.0, "bandwidthGbps":1.0, "diskSpeed":"fast", "storageType":"any" }, - "allowedToBeDown": false, - "rebootGeneration": 1, - "currentRebootGeneration": 0, - "failCount": 0, - "wantToRetire": false, - "wantToDeprovision": false, - "history": [ - { - "event": "provisioned", - "at": 123, - "agent": "system" - }, - { - "event": "readied", - "at": 123, - "agent": "system" - }, - { - "event": "reserved", - "at": 123, - "agent": "application" - }, - { - "event": "activated", - "at": 123, - "agent": "application" - } - ], - "ipAddresses": [ - "127.0.6.1", - "::6:1" - ], - "additionalIpAddresses": [], - "reports": { - "actualCpuCores": { - "createdMillis": 3 - } - } -} diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.cpp b/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.cpp index 6016d60b880..ea71cafb73a 100644 --- a/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.cpp +++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.cpp @@ -788,7 +788,7 @@ DocumentMetaStore::getLidUsageStats() const Blueprint::UP DocumentMetaStore::createWhiteListBlueprint() const { - return _lidAlloc.createWhiteListBlueprint(getCommittedDocIdLimit()); + return _lidAlloc.createWhiteListBlueprint(); } AttributeVector::SearchContext::UP diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.cpp b/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.cpp index dec83b9bca7..31361e40c68 100644 --- a/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.cpp +++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.cpp @@ -196,17 +196,15 @@ class WhiteListBlueprint : public SimpleLeafBlueprint, public WhiteListProvider { private: const search::GrowableBitVector &_activeLids; - const uint32_t _docIdLimit; mutable std::mutex _lock; mutable std::vector<search::fef::TermFieldMatchData *> _matchDataVector; search::BitVector::UP get_white_list_filter() const override { - return search::BitVector::create(_activeLids); + return search::BitVector::create(_activeLids, 0, get_docid_limit()); } SearchIterator::UP - createLeafSearch(const TermFieldMatchDataArray &tfmda, - bool strict) const override + createLeafSearch(const TermFieldMatchDataArray &tfmda, bool strict) const override { assert(tfmda.size() == 0); (void) tfmda; @@ -219,14 +217,13 @@ private: std::lock_guard<std::mutex> lock(_lock); _matchDataVector.push_back(tfmd); } - return search::BitVectorIterator::create(&_activeLids, _docIdLimit, *tfmd, strict); + return search::BitVectorIterator::create(&_activeLids, get_docid_limit(), *tfmd, strict); } public: - WhiteListBlueprint(const search::GrowableBitVector &activeLids, uint32_t docIdLimit) + WhiteListBlueprint(const search::GrowableBitVector &activeLids) : SimpleLeafBlueprint(FieldSpecBaseList()), _activeLids(activeLids), - _docIdLimit(docIdLimit), _matchDataVector() { setEstimate(HitEstimate(_activeLids.size(), false)); @@ -244,9 +241,9 @@ public: } Blueprint::UP -LidAllocator::createWhiteListBlueprint(uint32_t docIdLimit) const +LidAllocator::createWhiteListBlueprint() const { - return std::make_unique<WhiteListBlueprint>(_activeLids.getBitVector(), docIdLimit); + return std::make_unique<WhiteListBlueprint>(_activeLids.getBitVector()); } void diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.h b/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.h index ad41c6a8224..ccf9ef4513e 100644 --- a/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.h +++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.h @@ -35,8 +35,7 @@ public: DocId getFreeLid(DocId lidLimit); DocId peekFreeLid(DocId lidLimit); - void ensureSpace(uint32_t newSize, - uint32_t newCapacity); + void ensureSpace(uint32_t newSize, uint32_t newCapacity); void registerLid(DocId lid) { _usedLids.setBit(lid); } void unregisterLid(DocId lid); size_t getUsedLidsSize() const; @@ -48,7 +47,7 @@ public: generation_t currentGeneration); bool holdLidOK(DocId lid, DocId lidLimit) const; void constructFreeList(DocId lidLimit); - search::queryeval::Blueprint::UP createWhiteListBlueprint(uint32_t docIdLimit) const; + search::queryeval::Blueprint::UP createWhiteListBlueprint() const; void updateActiveLids(DocId lid, bool active); void clearDocs(DocId lidLow, DocId lidLimit); void shrinkLidSpace(DocId committedDocIdLimit); diff --git a/searchcore/src/vespa/searchcore/proton/server/matchview.cpp b/searchcore/src/vespa/searchcore/proton/server/matchview.cpp index 7ba9b971715..61b37a47d09 100644 --- a/searchcore/src/vespa/searchcore/proton/server/matchview.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/matchview.cpp @@ -58,7 +58,6 @@ MatchView::getMatcher(const vespalib::string & rankProfile) const return retval; } - MatchContext::UP MatchView::createContext() const { IAttributeContext::UP attrCtx = _attrMgr->createContext(); @@ -66,7 +65,6 @@ MatchView::createContext() const { return std::make_unique<MatchContext>(std::move(attrCtx), std::move(searchCtx)); } - std::unique_ptr<SearchReply> MatchView::match(std::shared_ptr<const ISearchHandler> searchHandler, const SearchRequest &req, vespalib::ThreadBundle &threadBundle) const @@ -74,13 +72,12 @@ MatchView::match(std::shared_ptr<const ISearchHandler> searchHandler, const Sear Matcher::SP matcher = getMatcher(req.ranking); SearchSession::OwnershipBundle owned_objects; owned_objects.search_handler = std::move(searchHandler); + owned_objects.readGuard = _metaStore->getReadGuard(); owned_objects.context = createContext(); - owned_objects.readGuard = _metaStore->getReadGuard();; MatchContext *ctx = owned_objects.context.get(); const search::IDocumentMetaStore & dms = owned_objects.readGuard->get(); return matcher->match(req, threadBundle, ctx->getSearchContext(), ctx->getAttributeContext(), *_sessionMgr, dms, std::move(owned_objects)); } - } // namespace proton diff --git a/searchlib/src/tests/nativerank/nativerank.cpp b/searchlib/src/tests/nativerank/nativerank.cpp index e5482d95d02..b28e385b597 100644 --- a/searchlib/src/tests/nativerank/nativerank.cpp +++ b/searchlib/src/tests/nativerank/nativerank.cpp @@ -170,7 +170,8 @@ Test::testNativeFieldMatch() f.firstOccTable = &t; f.numOccTable = &t; p.vector.push_back(f); - NativeFieldMatchExecutor nfme(ft.getQueryEnv(), p); + NativeFieldMatchExecutorSharedState nfmess(ft.getQueryEnv(), p); + NativeFieldMatchExecutor nfme(nfmess); EXPECT_EQUAL(p.minFieldLength, 6u); EXPECT_EQUAL(nfme.getFirstOccBoost(0, 0, 4), 0); EXPECT_EQUAL(nfme.getFirstOccBoost(0, 1, 4), 1); diff --git a/searchlib/src/vespa/searchlib/common/allocatedbitvector.cpp b/searchlib/src/vespa/searchlib/common/allocatedbitvector.cpp index 3add7d1a328..659e3718a13 100644 --- a/searchlib/src/vespa/searchlib/common/allocatedbitvector.cpp +++ b/searchlib/src/vespa/searchlib/common/allocatedbitvector.cpp @@ -17,6 +17,28 @@ size_t computeCapacity(size_t capacity, size_t allocatedBytes) { return possibleCapacity; } +// This is to ensure that we only read size and capacity once during copy +// to ensure that they do not change unexpectedly under our feet due to resizing in different thread. +std::pair<BitVector::Index, BitVector::Index> +extract_size_size(const BitVector & bv) { + BitVector::Index size = bv.size(); + return std::pair<BitVector::Index, BitVector::Index>(size, size); +} + +std::pair<BitVector::Index, BitVector::Index> +extract_size_capacity(const AllocatedBitVector & bv) { + BitVector::Index size = bv.size(); + BitVector::Index capacity = bv.capacity(); + while (capacity < size) { + // Since size and capacity might be changed in another thread we need + // this fallback to avoid inconsistency during shrink. + std::atomic_thread_fence(std::memory_order_seq_cst); + size = bv.size(); + capacity = bv.capacity(); + } + return std::pair<BitVector::Index, BitVector::Index>(size, capacity); +} + } AllocatedBitVector::AllocatedBitVector(Index numberOfElements) : @@ -56,21 +78,21 @@ AllocatedBitVector::AllocatedBitVector(Index numberOfElements, Index capacityBit } AllocatedBitVector::AllocatedBitVector(const AllocatedBitVector & rhs) : - AllocatedBitVector(rhs, rhs.capacity()) + AllocatedBitVector(rhs, extract_size_capacity(rhs)) { } AllocatedBitVector::AllocatedBitVector(const BitVector & rhs) : - AllocatedBitVector(rhs, rhs.size()) + AllocatedBitVector(rhs, extract_size_size(rhs)) { } -AllocatedBitVector::AllocatedBitVector(const BitVector & rhs, Index capacity_) : +AllocatedBitVector::AllocatedBitVector(const BitVector & rhs, std::pair<Index, Index> size_capacity) : BitVector(), - _capacityBits(capacity_), - _alloc(allocatePaddedAndAligned(0, rhs.size(), capacity_)) + _capacityBits(size_capacity.second), + _alloc(allocatePaddedAndAligned(0, size_capacity.first, size_capacity.second)) { _capacityBits = computeCapacity(_capacityBits, _alloc.size()); - memcpy(_alloc.get(), rhs.getStart(), rhs.sizeBytes()); - init(_alloc.get(), 0, rhs.size()); + memcpy(_alloc.get(), rhs.getStart(), numBytes(size_capacity.first - rhs.getStartIndex())); + init(_alloc.get(), 0, size_capacity.first); setBit(size()); updateCount(); } diff --git a/searchlib/src/vespa/searchlib/common/allocatedbitvector.h b/searchlib/src/vespa/searchlib/common/allocatedbitvector.h index c52c52354a1..5a7d2e634ea 100644 --- a/searchlib/src/vespa/searchlib/common/allocatedbitvector.h +++ b/searchlib/src/vespa/searchlib/common/allocatedbitvector.h @@ -73,7 +73,7 @@ private: BitVector::swap(rhs); } - AllocatedBitVector(const BitVector &other, Index capacity); + AllocatedBitVector(const BitVector &other, std::pair<Index, Index> size_capacity); /** * Prepare for potential reuse where new value might be filled in by diff --git a/searchlib/src/vespa/searchlib/features/nativefieldmatchfeature.cpp b/searchlib/src/vespa/searchlib/features/nativefieldmatchfeature.cpp index 64ae94ddc90..376ed8cd3d3 100644 --- a/searchlib/src/vespa/searchlib/features/nativefieldmatchfeature.cpp +++ b/searchlib/src/vespa/searchlib/features/nativefieldmatchfeature.cpp @@ -7,6 +7,7 @@ #include <vespa/searchlib/fef/indexproperties.h> #include <vespa/searchlib/fef/itablemanager.h> #include <vespa/searchlib/fef/properties.h> +#include <vespa/vespalib/stllike/asciistream.h> #include <vespa/vespalib/util/stash.h> using namespace search::fef; @@ -15,12 +16,44 @@ namespace search::features { const uint32_t NativeFieldMatchParam::NOT_DEF_FIELD_LENGTH(std::numeric_limits<uint32_t>::max()); +NativeFieldMatchExecutorSharedState::NativeFieldMatchExecutorSharedState(const IQueryEnvironment& env, + const NativeFieldMatchParams& params) + : fef::Anything(), + _params(params), + _query_terms(), + _divisor(0) +{ + QueryTermHelper queryTerms(env); + for (const QueryTerm & qtTmp : queryTerms.terms()) { + if (qtTmp.termData()->getWeight().percent() != 0) // only consider query terms with contribution + { + MyQueryTerm qt(qtTmp); + typedef search::fef::ITermFieldRangeAdapter FRA; + uint32_t totalFieldWeight = 0; + for (FRA iter(*qt.termData()); iter.valid(); iter.next()) { + const ITermFieldData& tfd = iter.get(); + uint32_t fieldId = tfd.getFieldId(); + if (_params.considerField(fieldId)) { // only consider fields with contribution + totalFieldWeight += _params.vector[fieldId].fieldWeight; + qt.handles().emplace_back(tfd.getHandle(), &tfd); + } + } + if (!qt.handles().empty()) { + _query_terms.push_back(qt); + _divisor += (qt.significance() * qt.termData()->getWeight().percent() * totalFieldWeight); + } + } + } +} + +NativeFieldMatchExecutorSharedState::~NativeFieldMatchExecutorSharedState() = default; + feature_t NativeFieldMatchExecutor::calculateScore(const MyQueryTerm &qt, uint32_t docId) { feature_t termScore = 0; for (size_t i = 0; i < qt.handles().size(); ++i) { - TermFieldHandle tfh = qt.handles()[i]; + TermFieldHandle tfh = qt.handles()[i].first; const TermFieldMatchData *tfmd = _md->resolveTermField(tfh); const NativeFieldMatchParam & param = _params.vector[tfmd->getFieldId()]; if (tfmd->getDocId() == docId) { // do we have a hit @@ -38,33 +71,17 @@ NativeFieldMatchExecutor::calculateScore(const MyQueryTerm &qt, uint32_t docId) return termScore; } -NativeFieldMatchExecutor::NativeFieldMatchExecutor(const IQueryEnvironment & env, - const NativeFieldMatchParams & params) : - FeatureExecutor(), - _params(params), - _queryTerms(), - _divisor(0), - _md(nullptr) +NativeFieldMatchExecutor::NativeFieldMatchExecutor(const NativeFieldMatchExecutorSharedState& shared_state) + : FeatureExecutor(), + _params(shared_state.get_params()), + _queryTerms(shared_state.get_query_terms()), + _divisor(shared_state.get_divisor()), + _md(nullptr) { - QueryTermHelper queryTerms(env); - for (const QueryTerm & qtTmp : queryTerms.terms()) { - if (qtTmp.termData()->getWeight().percent() != 0) // only consider query terms with contribution - { - MyQueryTerm qt(qtTmp); - typedef search::fef::ITermFieldRangeAdapter FRA; - uint32_t totalFieldWeight = 0; - for (FRA iter(*qt.termData()); iter.valid(); iter.next()) { - const ITermFieldData& tfd = iter.get(); - uint32_t fieldId = tfd.getFieldId(); - if (_params.considerField(fieldId)) { // only consider fields with contribution - totalFieldWeight += _params.vector[fieldId].fieldWeight; - qt.handles().push_back(tfd.getHandle()); - } - } - if (!qt.handles().empty()) { - _queryTerms.push_back(qt); - _divisor += (qt.significance() * qt.termData()->getWeight().percent() * totalFieldWeight); - } + for (const auto& qt : _queryTerms) { + for (const auto& handle : qt.handles()) { + // Record that we need normal term field match data + (void) handle.second->getHandle(MatchDataDetails::Normal); } } } @@ -92,7 +109,8 @@ NativeFieldMatchBlueprint::NativeFieldMatchBlueprint() : Blueprint("nativeFieldMatch"), _params(), _defaultFirstOcc("expdecay(8000,12.50)"), - _defaultNumOcc("loggrowth(1500,4000,19)") + _defaultNumOcc("loggrowth(1500,4000,19)"), + _shared_state_key() { } @@ -116,9 +134,12 @@ bool NativeFieldMatchBlueprint::setup(const IIndexEnvironment & env, const ParameterList & params) { + vespalib::asciistream shared_state_key_builder; _params.resize(env.getNumFields()); FieldWrapper fields(env, params, FieldType::INDEX); vespalib::string defaultFirstOccImportance = env.getProperties().lookup(getBaseName(), "firstOccurrenceImportance").get("0.5"); + shared_state_key_builder << "fef.nativeFieldMatch["; + bool first_field = true; for (uint32_t i = 0; i < fields.getNumFields(); ++i) { const FieldInfo * info = fields.getField(i); uint32_t fieldId = info->id(); @@ -160,8 +181,16 @@ NativeFieldMatchBlueprint::setup(const IIndexEnvironment & env, } if (param.field) { env.hintFieldAccess(fieldId); + if (first_field) { + first_field = false; + } else { + shared_state_key_builder << ","; + } + shared_state_key_builder << info->name(); } } + shared_state_key_builder << "]"; + _shared_state_key = shared_state_key_builder.str(); _params.minFieldLength = util::strToNum<uint32_t>(env.getProperties().lookup (getBaseName(), "minFieldLength").get("6")); @@ -172,17 +201,23 @@ NativeFieldMatchBlueprint::setup(const IIndexEnvironment & env, FeatureExecutor & NativeFieldMatchBlueprint::createExecutor(const IQueryEnvironment &env, vespalib::Stash &stash) const { - NativeFieldMatchExecutor &native = stash.create<NativeFieldMatchExecutor>(env, _params); - if (native.empty()) { + auto *shared_state = dynamic_cast<const NativeFieldMatchExecutorSharedState *>(env.getObjectStore().get(_shared_state_key)); + if (shared_state == nullptr) { + shared_state = &stash.create<NativeFieldMatchExecutorSharedState>(env, _params); + } + if (shared_state->empty()) { return stash.create<SingleZeroValueExecutor>(); } else { - return native; + return stash.create<NativeFieldMatchExecutor>(*shared_state); } } void NativeFieldMatchBlueprint::prepareSharedState(const IQueryEnvironment &queryEnv, IObjectStore &objectStore) const { QueryTermHelper::lookupAndStoreQueryTerms(queryEnv, objectStore); + if (objectStore.get(_shared_state_key) == nullptr) { + objectStore.add(_shared_state_key, std::make_unique<NativeFieldMatchExecutorSharedState>(queryEnv, _params)); + } } } diff --git a/searchlib/src/vespa/searchlib/features/nativefieldmatchfeature.h b/searchlib/src/vespa/searchlib/features/nativefieldmatchfeature.h index 9b132561cd3..5e8d865e159 100644 --- a/searchlib/src/vespa/searchlib/features/nativefieldmatchfeature.h +++ b/searchlib/src/vespa/searchlib/features/nativefieldmatchfeature.h @@ -29,13 +29,12 @@ public: }; /** - * Implements the executor for calculating the native field match score. - **/ -class NativeFieldMatchExecutor : public fef::FeatureExecutor -{ -private: - typedef std::vector<fef::TermFieldHandle> HandleVector; - + * Class containing shared state for native field match executor. + */ +class NativeFieldMatchExecutorSharedState : public fef::Anything { +public: + using WrappedHandle = std::pair<fef::TermFieldHandle, const fef::ITermFieldData*>; + using HandleVector = std::vector<WrappedHandle>; class MyQueryTerm : public QueryTerm { private: @@ -45,8 +44,28 @@ private: HandleVector &handles() { return _handles; } const HandleVector &handles() const { return _handles; } }; +private: + const NativeFieldMatchParams& _params; + std::vector<MyQueryTerm> _query_terms; + feature_t _divisor; +public: + NativeFieldMatchExecutorSharedState(const fef::IQueryEnvironment& env, const NativeFieldMatchParams& params); + ~NativeFieldMatchExecutorSharedState(); + const NativeFieldMatchParams& get_params() const { return _params; } + const std::vector<MyQueryTerm>& get_query_terms() const { return _query_terms; } + feature_t get_divisor() const { return _divisor; } + bool empty() const { return _query_terms.empty(); } +}; + +/** + * Implements the executor for calculating the native field match score. + **/ +class NativeFieldMatchExecutor : public fef::FeatureExecutor +{ +private: + using MyQueryTerm = NativeFieldMatchExecutorSharedState::MyQueryTerm; const NativeFieldMatchParams & _params; - std::vector<MyQueryTerm> _queryTerms; + vespalib::ConstArrayRef<MyQueryTerm> _queryTerms; feature_t _divisor; const fef::MatchData *_md; @@ -74,8 +93,7 @@ private: virtual void handle_bind_match_data(const fef::MatchData &md) override; public: - NativeFieldMatchExecutor(const fef::IQueryEnvironment & env, - const NativeFieldMatchParams & params); + NativeFieldMatchExecutor(const NativeFieldMatchExecutorSharedState& shared_state); void execute(uint32_t docId) override; feature_t getFirstOccBoost(uint32_t field, uint32_t position, uint32_t fieldLength) const { @@ -85,7 +103,6 @@ public: feature_t getNumOccBoost(uint32_t field, uint32_t occs, uint32_t fieldLength) const { return getNumOccBoost(_params.vector[field], occs, fieldLength); } - bool empty() const { return _queryTerms.empty(); } }; @@ -97,6 +114,7 @@ private: NativeFieldMatchParams _params; vespalib::string _defaultFirstOcc; vespalib::string _defaultNumOcc; + vespalib::string _shared_state_key; public: NativeFieldMatchBlueprint(); diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/OperationHandlerImpl.java b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/OperationHandlerImpl.java index 3284530392f..8f0e70f554c 100644 --- a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/OperationHandlerImpl.java +++ b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/OperationHandlerImpl.java @@ -72,7 +72,7 @@ public class OperationHandlerImpl implements OperationHandler { public static final int VISIT_TIMEOUT_MS = 120000; public static final int WANTED_DOCUMENT_COUNT_UPPER_BOUND = 1000; // Approximates the max default size of a bucket - public static final int CONCURRENCY_UPPER_BOUND = 200; + public static final int CONCURRENCY_UPPER_BOUND = 100; private final DocumentAccess documentAccess; private final DocumentApiMetrics metricsHelper; private final ClusterEnumerator clusterEnumerator; |