diff options
Diffstat (limited to 'configserver')
30 files changed, 504 insertions, 206 deletions
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java index eb268546580..bf2fdbf03ef 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java @@ -25,6 +25,7 @@ import com.yahoo.io.IOUtils; import com.yahoo.jdisc.Metric; import com.yahoo.path.Path; import com.yahoo.slime.Slime; +import com.yahoo.text.Utf8; import com.yahoo.transaction.NestedTransaction; import com.yahoo.transaction.Transaction; import com.yahoo.vespa.config.server.application.Application; @@ -60,9 +61,17 @@ import com.yahoo.vespa.config.server.tenant.ApplicationRolesStore; import com.yahoo.vespa.config.server.tenant.ContainerEndpointsCache; import com.yahoo.vespa.config.server.tenant.EndpointCertificateMetadataStore; import com.yahoo.vespa.config.server.tenant.Tenant; +import com.yahoo.vespa.config.server.tenant.TenantMetaData; import com.yahoo.vespa.config.server.tenant.TenantRepository; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.curator.Lock; +import com.yahoo.vespa.curator.transaction.CuratorOperations; +import com.yahoo.vespa.curator.transaction.CuratorTransaction; +import com.yahoo.vespa.defaults.Defaults; +import com.yahoo.vespa.flags.BooleanFlag; +import com.yahoo.vespa.flags.FlagSource; +import com.yahoo.vespa.flags.Flags; +import com.yahoo.vespa.flags.InMemoryFlagSource; import com.yahoo.vespa.orchestrator.Orchestrator; import java.io.File; @@ -90,9 +99,10 @@ import java.util.stream.Collectors; import static com.yahoo.config.model.api.container.ContainerServiceType.CLUSTERCONTROLLER_CONTAINER; import static com.yahoo.config.model.api.container.ContainerServiceType.CONTAINER; import static com.yahoo.config.model.api.container.ContainerServiceType.LOGSERVER_CONTAINER; -import static com.yahoo.vespa.curator.Curator.CompletionWaiter; +import static com.yahoo.vespa.config.server.filedistribution.FileDistributionUtil.fileReferenceExistsOnDisk; import static com.yahoo.vespa.config.server.filedistribution.FileDistributionUtil.getFileReferencesOnDisk; import static com.yahoo.vespa.config.server.tenant.TenantRepository.HOSTED_VESPA_TENANT; +import static com.yahoo.vespa.curator.Curator.CompletionWaiter; import static com.yahoo.yolean.Exceptions.uncheck; import static java.nio.file.Files.readAttributes; @@ -120,6 +130,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye private final LogRetriever logRetriever; private final TesterClient testerClient; private final Metric metric; + private final BooleanFlag useTenantMetaData; @Inject public ApplicationRepository(TenantRepository tenantRepository, @@ -130,7 +141,8 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye ConfigserverConfig configserverConfig, Orchestrator orchestrator, TesterClient testerClient, - Metric metric) { + Metric metric, + FlagSource flagSource) { this(tenantRepository, hostProvisionerProvider.getHostProvisioner(), infraDeployerProvider.getInfraDeployer(), @@ -142,7 +154,8 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye new FileDistributionStatus(), Clock.systemUTC(), testerClient, - metric); + metric, + flagSource); } // For testing @@ -157,7 +170,8 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye new LogRetriever(), clock, new TesterClient(), - new NullMetric()); + new NullMetric(), + new InMemoryFlagSource()); } // For testing @@ -168,7 +182,8 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye LogRetriever logRetriever, Clock clock, TesterClient testerClient, - Metric metric) { + Metric metric, + FlagSource flagSource) { this(tenantRepository, Optional.of(hostProvisioner), Optional.empty(), @@ -180,7 +195,8 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye new FileDistributionStatus(), clock, testerClient, - metric); + metric, + flagSource); } private ApplicationRepository(TenantRepository tenantRepository, @@ -194,7 +210,8 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye FileDistributionStatus fileDistributionStatus, Clock clock, TesterClient testerClient, - Metric metric) { + Metric metric, + FlagSource flagSource) { this.tenantRepository = tenantRepository; this.hostProvisioner = hostProvisioner; this.infraDeployer = infraDeployer; @@ -207,6 +224,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye this.clock = clock; this.testerClient = testerClient; this.metric = metric; + this.useTenantMetaData = Flags.USE_TENANT_META_DATA.bindTo(flagSource); } public Metric metric() { @@ -252,7 +270,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye boolean ignoreSessionStaleFailure, Instant now) { ApplicationId applicationId = prepareParams.getApplicationId(); long sessionId = createSession(applicationId, prepareParams.getTimeoutBudget(), applicationPackage); - Tenant tenant = tenantRepository.getTenant(applicationId.tenant()); + Tenant tenant = getTenant(applicationId); PrepareResult result = prepare(tenant, sessionId, prepareParams, now); activate(tenant, sessionId, prepareParams.getTimeoutBudget(), ignoreSessionStaleFailure); return result; @@ -346,16 +364,36 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye } public Transaction deactivateCurrentActivateNew(Session active, LocalSession prepared, boolean ignoreStaleSessionFailure) { - SessionRepository sessionRepository = tenantRepository.getTenant(prepared.getTenantName()).getSessionRepository(); - Transaction transaction = sessionRepository.createActivateTransaction(prepared); + Tenant tenant = tenantRepository.getTenant(prepared.getTenantName()); + Transaction transaction = tenant.getSessionRepository().createActivateTransaction(prepared); if (active != null) { checkIfActiveHasChanged(prepared, active, ignoreStaleSessionFailure); checkIfActiveIsNewerThanSessionToBeActivated(prepared.getSessionId(), active.getSessionId()); transaction.add(active.createDeactivateTransaction().operations()); } + + if (useTenantMetaData.value()) + transaction.add(writeTenantMetaData(tenant).operations()); + return transaction; } + private String createMetaData(Tenant tenant) { + return new TenantMetaData(tenant.getSessionRepository().clock().instant()).asJsonString(); + } + + TenantMetaData getTenantMetaData(Tenant tenant) { + Optional<byte[]> data = tenantRepository.getCurator().getData(TenantRepository.getTenantPath(tenant.getName())); + return data.map(bytes -> TenantMetaData.fromJsonString(Utf8.toString(bytes))).orElse(new TenantMetaData(tenant.getCreatedTime())); + } + + private Transaction writeTenantMetaData(Tenant tenant) { + String jsonString = createMetaData(tenant); + return new CuratorTransaction(tenantRepository.getCurator()) + .add(CuratorOperations.setData(TenantRepository.getTenantPath(tenant.getName()).getAbsolute(), + Utf8.toBytes(jsonString))); + } + static void checkIfActiveHasChanged(LocalSession session, Session currentActiveSession, boolean ignoreStaleSessionFailure) { long activeSessionAtCreate = session.getActiveSessionAtCreate(); log.log(Level.FINE, currentActiveSession.logPre() + "active session id at create time=" + activeSessionAtCreate); @@ -379,10 +417,6 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye } } - private static boolean isValidSession(Session session) { - return session != null; - } - // As of now, config generation is based on session id, and config generation must be a monotonically // increasing number static void checkIfActiveIsNewerThanSessionToBeActivated(long sessionId, long currentActiveSessionId) { @@ -412,7 +446,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye * @throws RuntimeException if the delete transaction fails. This method is exception safe. */ public boolean delete(ApplicationId applicationId, Duration waitTime) { - Tenant tenant = tenantRepository.getTenant(applicationId.tenant()); + Tenant tenant = getTenant(applicationId); if (tenant == null) return false; TenantApplications tenantApplications = tenant.getApplicationRepo(); @@ -554,6 +588,9 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye long sessionId = getSessionIdForApplication(tenant, applicationId); RemoteSession session = getRemoteSession(tenant, sessionId); return session.ensureApplicationLoaded().getForVersionOrLatest(version, clock.instant()); + } catch (NotFoundException e) { + log.log(Level.WARNING, "Failed getting application for '" + applicationId + "': " + e.getMessage()); + throw e; } catch (Exception e) { log.log(Level.WARNING, "Failed getting application for '" + applicationId + "'", e); throw e; @@ -586,7 +623,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye } private boolean localSessionHasBeenDeleted(ApplicationId applicationId, long sessionId, Duration waitTime) { - SessionRepository sessionRepository = tenantRepository.getTenant(applicationId.tenant()).getSessionRepository(); + SessionRepository sessionRepository = getTenant(applicationId).getSessionRepository(); Instant end = Instant.now().plus(waitTime); do { if (sessionRepository.getRemoteSession(sessionId) == null) return true; @@ -596,6 +633,26 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye return false; } + public Optional<String> getApplicationPackageReference(ApplicationId applicationId) { + Optional<String> applicationPackage = Optional.empty(); + RemoteSession session = getActiveSession(applicationId); + if (session != null) { + FileReference applicationPackageReference = session.getApplicationPackageReference(); + File downloadDirectory = new File(Defaults.getDefaults().underVespaHome(configserverConfig().fileReferencesDir())); + if (applicationPackageReference != null && ! fileReferenceExistsOnDisk(downloadDirectory, applicationPackageReference)) + applicationPackage = Optional.of(applicationPackageReference.value()); + } + return applicationPackage; + } + + public List<Version> getAllVersions(ApplicationId applicationId) { + Optional<ApplicationSet> applicationSet = getCurrentActiveApplicationSet(getTenant(applicationId), applicationId); + if (applicationSet.isEmpty()) + return List.of(); + else + return applicationSet.get().getAllVersions(applicationId); + } + // ---------------- Convergence ---------------------------------------------------------------- public HttpResponse checkServiceForConfigConvergence(ApplicationId applicationId, String hostAndPort, URI uri, @@ -671,11 +728,11 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye * @return the active session, or null if there is no active session for the given application id. */ public RemoteSession getActiveSession(ApplicationId applicationId) { - return getActiveSession(tenantRepository.getTenant(applicationId.tenant()), applicationId); + return getActiveSession(getTenant(applicationId), applicationId); } public long getSessionIdForApplication(ApplicationId applicationId) { - Tenant tenant = tenantRepository.getTenant(applicationId.tenant()); + Tenant tenant = getTenant(applicationId); if (tenant == null) throw new NotFoundException("Tenant '" + applicationId.tenant() + "' not found"); return getSessionIdForApplication(tenant, applicationId); } @@ -704,7 +761,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye DeployLogger logger, boolean internalRedeploy, TimeoutBudget timeoutBudget) { - Tenant tenant = tenantRepository.getTenant(applicationId.tenant()); + Tenant tenant = getTenant(applicationId); SessionRepository sessionRepository = tenant.getSessionRepository(); RemoteSession fromSession = getExistingSession(tenant, applicationId); LocalSession session = sessionRepository.createSessionFromExisting(fromSession, logger, internalRedeploy, timeoutBudget); @@ -724,7 +781,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye } public long createSession(ApplicationId applicationId, TimeoutBudget timeoutBudget, File applicationDirectory) { - Tenant tenant = tenantRepository.getTenant(applicationId.tenant()); + Tenant tenant = getTenant(applicationId); tenant.getApplicationRepo().createApplication(applicationId); Optional<Long> activeSessionId = tenant.getApplicationRepo().activeSessionOf(applicationId); LocalSession session = tenant.getSessionRepository().createSession(applicationDirectory, @@ -742,9 +799,9 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye Set<ApplicationId> applicationIds = new HashSet<>(); sessionsPerTenant.values() .forEach(sessionList -> sessionList.stream() - .map(Session::getApplicationId) - .filter(Objects::nonNull) - .forEach(applicationIds::add)); + .map(Session::getOptionalApplicationId) + .filter(Optional::isPresent) + .forEach(appId -> applicationIds.add(appId.get()))); Map<ApplicationId, Long> activeSessions = new HashMap<>(); applicationIds.forEach(applicationId -> { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/ConfigServerBootstrap.java b/configserver/src/main/java/com/yahoo/vespa/config/server/ConfigServerBootstrap.java index 0aab83d5a6a..cbea6d99dd2 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/ConfigServerBootstrap.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/ConfigServerBootstrap.java @@ -105,7 +105,11 @@ public class ConfigServerBootstrap extends AbstractComponent implements Runnable initializing(vipStatusMode); // Run maintainers that cleans up zookeeper and disk usage before bootstrapping - configServerMaintenance.ifPresent(ConfigServerMaintenance::runBeforeBootstrap); + try { + configServerMaintenance.ifPresent(ConfigServerMaintenance::runBeforeBootstrap); + } catch (Exception e) { + log.log(Level.INFO, "Running maintainers before bootstrap failed, continuing with bootstrap", e); + } switch (mode) { case BOOTSTRAP_IN_SEPARATE_THREAD: diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ApplicationMapper.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ApplicationMapper.java index 5ce9ebca69d..c0158b55422 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ApplicationMapper.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ApplicationMapper.java @@ -57,7 +57,7 @@ public final class ApplicationMapper { /** Returns whether this registry has an application for the given application id */ public boolean hasApplication(ApplicationId applicationId, Instant now) { - return hasApplicationForVersion(applicationId, Optional.<Version>empty(), now); + return hasApplicationForVersion(applicationId, Optional.empty(), now); } /** Returns whether this registry has an application for the given application id and vespa version */ diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java index d87a37829de..ee9b8a60305 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java @@ -119,6 +119,16 @@ public class TenantApplications implements RequestHandler, HostValidator<Applica return curator.getChildren(applicationsPath).stream() .sorted() .map(ApplicationId::fromSerializedForm) + .filter(applicationId -> { + if ( ! applicationId.tenant().equals(tenant)) { + log.log(Level.WARNING, "There is an application ('" + applicationId + "') with wrong tenant (should be '" + + tenant + "') in " + applicationsPath + ", deleting it"); + curator.delete(applicationsPath.append(applicationId.serializedForm())); + return false; + } else { + return true; + } + }) .filter(id -> activeSessionOf(id).isPresent()) .collect(Collectors.toUnmodifiableList()); } @@ -153,6 +163,8 @@ public class TenantApplications implements RequestHandler, HostValidator<Applica * Creates a node for the given application, marking its existence. */ public void createApplication(ApplicationId id) { + if (! id.tenant().equals(tenant)) + throw new IllegalArgumentException("Cannot write application id '" + id + "' for tenant '" + tenant + "'"); try (Lock lock = lock(id)) { curator.create(applicationPath(id)); } @@ -420,4 +432,5 @@ public class TenantApplications implements RequestHandler, HostValidator<Applica } public TenantFileSystemDirs getTenantFileSystemDirs() { return tenantFileSystemDirs; } + } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java index 8c2e6027691..11ce659625d 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java @@ -108,17 +108,18 @@ public class Deployment implements com.yahoo.config.provision.Deployment { @Override public void prepare() { if (prepared) return; - try (ActionTimer timer = applicationRepository.timerFor(session.getApplicationId(), "deployment.prepareMillis")) { + ApplicationId applicationId = session.getApplicationId(); + try (ActionTimer timer = applicationRepository.timerFor(applicationId, "deployment.prepareMillis")) { TimeoutBudget timeoutBudget = new TimeoutBudget(clock, timeout); - PrepareParams.Builder params = new PrepareParams.Builder().applicationId(session.getApplicationId()) + PrepareParams.Builder params = new PrepareParams.Builder().applicationId(applicationId) .timeoutBudget(timeoutBudget) .ignoreValidationErrors(!validate) .vespaVersion(version.toString()) .isBootstrap(isBootstrap); dockerImageRepository.ifPresent(params::dockerImageRepository); athenzDomain.ifPresent(params::athenzDomain); - Optional<ApplicationSet> activeApplicationSet = applicationRepository.getCurrentActiveApplicationSet(tenant, session.getApplicationId()); + Optional<ApplicationSet> activeApplicationSet = applicationRepository.getCurrentActiveApplicationSet(tenant, applicationId); tenant.getSessionRepository().prepareLocalSession(session, logger, params.build(), activeApplicationSet, tenant.getPath(), clock.instant()); this.prepared = true; @@ -131,11 +132,10 @@ public class Deployment implements com.yahoo.config.provision.Deployment { if ( ! prepared) prepare(); - try (ActionTimer timer = applicationRepository.timerFor(session.getApplicationId(), "deployment.activateMillis")) { + validateSessionStatus(session); + ApplicationId applicationId = session.getApplicationId(); + try (ActionTimer timer = applicationRepository.timerFor(applicationId, "deployment.activateMillis")) { TimeoutBudget timeoutBudget = new TimeoutBudget(clock, timeout); - validateSessionStatus(session); - ApplicationId applicationId = session.getApplicationId(); - if ( ! timeoutBudget.hasTimeLeft()) throw new RuntimeException("Timeout exceeded when trying to activate '" + applicationId + "'"); RemoteSession previousActiveSession; @@ -148,7 +148,7 @@ public class Deployment implements com.yahoo.config.provision.Deployment { throw e; } catch (Exception e) { - throw new InternalServerException("Error activating application", e); + throw new InternalServerException("Error when activating '" + applicationId + "'", e); } waiter.awaitCompletion(timeoutBudget.timeLeft()); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java index 8426d6b56cf..0a805cc6b21 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java @@ -3,7 +3,6 @@ package com.yahoo.vespa.config.server.http.v2; import com.google.inject.Inject; import com.yahoo.component.Version; -import com.yahoo.config.FileReference; import com.yahoo.config.application.api.ApplicationFile; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ApplicationName; @@ -18,18 +17,13 @@ import com.yahoo.jdisc.application.BindingMatch; import com.yahoo.jdisc.application.UriPattern; import com.yahoo.slime.Cursor; import com.yahoo.vespa.config.server.ApplicationRepository; -import com.yahoo.vespa.config.server.application.ApplicationSet; import com.yahoo.vespa.config.server.http.ContentHandler; import com.yahoo.vespa.config.server.http.ContentRequest; import com.yahoo.vespa.config.server.http.HttpErrorResponse; import com.yahoo.vespa.config.server.http.HttpHandler; import com.yahoo.vespa.config.server.http.JSONResponse; import com.yahoo.vespa.config.server.http.NotFoundException; -import com.yahoo.vespa.config.server.session.RemoteSession; -import com.yahoo.vespa.config.server.tenant.Tenant; -import com.yahoo.vespa.defaults.Defaults; -import java.io.File; import java.io.IOException; import java.time.Duration; import java.util.List; @@ -38,8 +32,6 @@ import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; -import static com.yahoo.vespa.config.server.filedistribution.FileDistributionUtil.fileReferenceExistsOnDisk; - /** * Operations on applications (delete, wait for config convergence, restart, application content etc.) * @@ -169,21 +161,10 @@ public class ApplicationHandler extends HttpHandler { } GetApplicationResponse getApplicationResponse(ApplicationId applicationId) { - Tenant tenant = applicationRepository.getTenant(applicationId); - Optional<ApplicationSet> applicationSet = applicationRepository.getCurrentActiveApplicationSet(tenant, applicationId); - String applicationPackage = ""; - RemoteSession session = applicationRepository.getActiveSession(applicationId); - if (session != null) { - FileReference applicationPackageReference = session.getApplicationPackageReference(); - File downloadDirectory = new File(Defaults.getDefaults().underVespaHome(applicationRepository.configserverConfig().fileReferencesDir())); - if (applicationPackageReference != null && ! fileReferenceExistsOnDisk(downloadDirectory, applicationPackageReference)) - applicationPackage = applicationPackageReference.value(); - } - return new GetApplicationResponse(Response.Status.OK, applicationRepository.getApplicationGeneration(applicationId), - applicationSet.get().getAllVersions(applicationId), - applicationPackage); + applicationRepository.getAllVersions(applicationId), + applicationRepository.getApplicationPackageReference(applicationId)); } @Override @@ -346,10 +327,10 @@ public class ApplicationHandler extends HttpHandler { } private static class GetApplicationResponse extends JSONResponse { - GetApplicationResponse(int status, long generation, List<Version> modelVersions, String applicationPackageReference) { + GetApplicationResponse(int status, long generation, List<Version> modelVersions, Optional<String> applicationPackageReference) { super(status); object.setLong("generation", generation); - object.setString("applicationPackageFileReference", applicationPackageReference); + object.setString("applicationPackageFileReference", applicationPackageReference.orElse("")); Cursor modelVersionArray = object.setArray("modelVersions"); modelVersions.forEach(version -> modelVersionArray.addString(version.toFullString())); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandler.java index 124902c988a..4e75234620f 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandler.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandler.java @@ -45,7 +45,7 @@ public class SessionActiveHandler extends SessionHandler { Utils.checkThatTenantExists(tenantRepository, tenantName); Tenant tenant = tenantRepository.getTenant(tenantName); TimeoutBudget timeoutBudget = getTimeoutBudget(request, DEFAULT_ACTIVATE_TIMEOUT); - final Long sessionId = getSessionIdV2(request); + long sessionId = getSessionIdV2(request); ApplicationId applicationId = applicationRepository.activate(tenant, sessionId, timeoutBudget, shouldIgnoreSessionStaleFailure(request)); ApplicationMetaData metaData = applicationRepository.getMetadataFromLocalSession(tenant, sessionId); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java index e9687000b3c..e41d5264c08 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java @@ -89,7 +89,7 @@ public class ApplicationPackageMaintainer extends ConfigServerMaintainer { } private void createLocalSessionIfMissing(ApplicationId applicationId, long sessionId) { - Tenant tenant = applicationRepository.tenantRepository().getTenant(applicationId.tenant()); + Tenant tenant = applicationRepository.getTenant(applicationId); SessionRepository sessionRepository = tenant.getSessionRepository(); if (sessionRepository.getLocalSession(sessionId) == null) sessionRepository.createLocalSessionUsingDistributedApplicationPackage(sessionId); 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 013daa86767..36cac87a326 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 @@ -60,7 +60,7 @@ public class RemoteSession extends Session { // Read hosts allocated on the config server instance which created this Optional<AllocatedHosts> allocatedHosts = applicationPackage.getAllocatedHosts(); - return ApplicationSet.fromList(applicationLoader.buildModels(sessionZooKeeperClient.readApplicationId(), + return ApplicationSet.fromList(applicationLoader.buildModels(getApplicationId(), sessionZooKeeperClient.readDockerImageRepository(), sessionZooKeeperClient.readVespaVersion(), applicationPackage, @@ -101,7 +101,7 @@ public class RemoteSession extends Session { KeeperException.NodeExistsException.class); Class<? extends Throwable> exceptionClass = e.getCause().getClass(); if (acceptedExceptions.contains(exceptionClass)) - log.log(Level.INFO, "Not able to notify completion for session " + getSessionId() + + log.log(Level.FINE, "Not able to notify completion for session " + getSessionId() + " (" + completionWaiter + ")," + " node " + (exceptionClass.equals(KeeperException.NoNodeException.class) ? "has been deleted" 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 0fc85b5e51a..b3e35e955de 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 @@ -34,21 +34,22 @@ public abstract class Session implements Comparable<Session> { protected final Optional<ApplicationPackage> applicationPackage; protected Session(TenantName tenant, long sessionId, SessionZooKeeperClient sessionZooKeeperClient) { - this.tenant = tenant; - this.sessionId = sessionId; - this.sessionZooKeeperClient = sessionZooKeeperClient; - this.applicationPackage = Optional.empty(); + this(tenant, sessionId, sessionZooKeeperClient, Optional.empty()); } protected Session(TenantName tenant, long sessionId, SessionZooKeeperClient sessionZooKeeperClient, ApplicationPackage applicationPackage) { + this(tenant, sessionId, sessionZooKeeperClient, Optional.of(applicationPackage)); + } + + private Session(TenantName tenant, long sessionId, SessionZooKeeperClient sessionZooKeeperClient, + Optional<ApplicationPackage> applicationPackage) { this.tenant = tenant; this.sessionId = sessionId; this.sessionZooKeeperClient = sessionZooKeeperClient; - this.applicationPackage = Optional.of(applicationPackage); + this.applicationPackage = applicationPackage; } - public final long getSessionId() { return sessionId; } @@ -133,7 +134,20 @@ public abstract class Session implements Comparable<Session> { sessionZooKeeperClient.writeAthenzDomain(athenzDomain); } - public ApplicationId getApplicationId() { return sessionZooKeeperClient.readApplicationId(); } + /** Returns application id read from ZooKeeper. Will throw RuntimeException if not found */ + public ApplicationId getApplicationId() { + return sessionZooKeeperClient.readApplicationId() + .orElseThrow(() -> new RuntimeException("Unable to read application id for session " + sessionId)); + } + + /** Returns application id read from ZooKeeper. Will return Optional.empty() if not found */ + public Optional<ApplicationId> getOptionalApplicationId() { + try { + return Optional.of(getApplicationId()); + } catch (RuntimeException e) { + return Optional.empty(); + } + } public FileReference getApplicationPackageReference() {return sessionZooKeeperClient.readApplicationPackageReference(); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java index 03769d0a537..2e6180aeb81 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java @@ -9,7 +9,6 @@ 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; @@ -183,8 +182,9 @@ public class SessionRepository { deleteLocalSession(candidate); } else if (createTime.plus(Duration.ofDays(1)).isBefore(clock.instant())) { // Sessions with state ACTIVATE, but which are not actually active - ApplicationId applicationId = candidate.getApplicationId(); - Long activeSession = activeSessions.get(applicationId); + Optional<ApplicationId> applicationId = candidate.getOptionalApplicationId(); + if (applicationId.isEmpty()) continue; + Long activeSession = activeSessions.get(applicationId.get()); if (activeSession == null || activeSession != candidate.getSessionId()) { deleteLocalSession(candidate); log.log(Level.INFO, "Deleted inactive session " + candidate.getSessionId() + " created " + @@ -624,7 +624,8 @@ public class SessionRepository { log.log(Level.INFO, "File reference for session id " + sessionId + ": " + fileReference + " not found in " + fileDirectory); return Optional.empty(); } - ApplicationId applicationId = sessionZKClient.readApplicationId(); + ApplicationId applicationId = sessionZKClient.readApplicationId() + .orElseThrow(() -> new RuntimeException("Could not find application id for session " + sessionId)); log.log(Level.INFO, "Creating local session for tenant '" + tenantName + "' with session id " + sessionId); LocalSession localSession = createLocalSession(sessionDir, applicationId, sessionId); addLocalSession(localSession); @@ -654,9 +655,7 @@ public class SessionRepository { private SessionZooKeeperClient createSessionZooKeeperClient(long sessionId) { String serverId = componentRegistry.getConfigserverConfig().serverId(); - Optional<NodeFlavors> nodeFlavors = componentRegistry.getZone().nodeFlavors(); - Path sessionPath = getSessionPath(sessionId); - return new SessionZooKeeperClient(curator, componentRegistry.getConfigCurator(), sessionPath, serverId, nodeFlavors); + return new SessionZooKeeperClient(curator, componentRegistry.getConfigCurator(), tenantName, sessionId, serverId); } private File getAndValidateExistingSessionAppDir(long sessionId) { @@ -693,13 +692,15 @@ public class SessionRepository { return curator.lock(lockPath(sessionId), Duration.ofMinutes(1)); // These locks shouldn't be held for very long. } + public Clock clock() { return clock; } + private Path lockPath(long sessionId) { return locksPath.append(String.valueOf(sessionId)); } public Transaction createActivateTransaction(Session session) { Transaction transaction = createSetStatusTransaction(session, Session.Status.ACTIVATE); - transaction.add(applicationRepo.createPutTransaction(session.sessionZooKeeperClient.readApplicationId(), session.getSessionId()).operations()); + transaction.add(applicationRepo.createPutTransaction(session.getApplicationId(), session.getSessionId()).operations()); return transaction; } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java index 27440b3765b..d28a322a05a 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java @@ -11,13 +11,14 @@ import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.AthenzDomain; import com.yahoo.config.provision.DockerImage; -import com.yahoo.config.provision.NodeFlavors; +import com.yahoo.config.provision.TenantName; import com.yahoo.path.Path; import com.yahoo.text.Utf8; import com.yahoo.transaction.Transaction; import com.yahoo.vespa.config.server.UserConfigDefinitionRepo; import com.yahoo.vespa.config.server.deploy.ZooKeeperClient; import com.yahoo.vespa.config.server.deploy.ZooKeeperDeployer; +import com.yahoo.vespa.config.server.tenant.TenantRepository; import com.yahoo.vespa.config.server.zookeeper.ConfigCurator; import com.yahoo.vespa.config.server.zookeeper.ZKApplicationPackage; import com.yahoo.vespa.curator.Curator; @@ -48,27 +49,21 @@ public class SessionZooKeeperClient { private static final String ATHENZ_DOMAIN = "athenzDomain"; private final Curator curator; private final ConfigCurator configCurator; + private final TenantName tenantName; private final Path sessionPath; private final Path sessionStatusPath; private final String serverId; // hostname - private final Optional<NodeFlavors> nodeFlavors; - - // Only for testing - // TODO: Remove, use the constructor below - public SessionZooKeeperClient(Curator curator, Path sessionPath) { - this(curator, ConfigCurator.create(curator), sessionPath, "1", Optional.empty()); - } public SessionZooKeeperClient(Curator curator, ConfigCurator configCurator, - Path sessionPath, - String serverId, - Optional<NodeFlavors> nodeFlavors) { + TenantName tenantName, + long sessionId, + String serverId) { this.curator = curator; this.configCurator = configCurator; - this.sessionPath = sessionPath; + this.tenantName = tenantName; + this.sessionPath = getSessionPath(tenantName, sessionId); this.serverId = serverId; - this.nodeFlavors = nodeFlavors; this.sessionStatusPath = sessionPath.append(ConfigCurator.SESSIONSTATE_ZK_SUBPATH); } @@ -135,7 +130,7 @@ public class SessionZooKeeperClient { } public ApplicationPackage loadApplicationPackage() { - return new ZKApplicationPackage(configCurator, sessionPath, nodeFlavors); + return new ZKApplicationPackage(configCurator, sessionPath); } public ConfigDefinitionRepo getUserConfigDefinitions() { @@ -147,12 +142,16 @@ public class SessionZooKeeperClient { } public void writeApplicationId(ApplicationId id) { + if ( ! id.tenant().equals(tenantName)) + throw new IllegalArgumentException("Cannot write application id '" + id + "' for tenant '" + tenantName + "'"); configCurator.putData(applicationIdPath(), id.serializedForm()); } - public ApplicationId readApplicationId() { + public Optional<ApplicationId> readApplicationId() { String idString = configCurator.getData(applicationIdPath()); - return idString == null ? null : ApplicationId.fromSerializedForm(idString); + return (idString == null) + ? Optional.empty() + : Optional.of(ApplicationId.fromSerializedForm(idString)); } void writeApplicationPackageReference(FileReference applicationPackageReference) { @@ -255,4 +254,8 @@ public class SessionZooKeeperClient { transaction.commit(); } + private static Path getSessionPath(TenantName tenantName, long sessionId) { + return TenantRepository.getSessionsPath(tenantName).append(String.valueOf(sessionId)); + } + } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantMetaData.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantMetaData.java new file mode 100644 index 00000000000..47a3dcc8ee0 --- /dev/null +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantMetaData.java @@ -0,0 +1,65 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.config.server.tenant; + +import com.yahoo.slime.Cursor; +import com.yahoo.slime.Inspector; +import com.yahoo.slime.JsonDecoder; +import com.yahoo.slime.JsonFormat; +import com.yahoo.slime.Slime; +import com.yahoo.text.Utf8; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.Instant; + +/** + * Metadata for a tenant. At the moment only stores last deploy time, to be used by TenantsMaintainer + * to GC unused tenants + * + * @author hmusum + */ +public class TenantMetaData { + + private final Instant lastDeployTimestamp; + + public TenantMetaData(Instant instant) { + this.lastDeployTimestamp = instant; + } + + public Instant lastDeployTimestamp() { + return lastDeployTimestamp; + } + + public String asJsonString() { + Slime slime = getSlime(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + new JsonFormat(false).encode(baos, slime); + return baos.toString(StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException("Unable to encode metadata", e); + } + } + + public static TenantMetaData fromJsonString(String jsonString) { + try { + Slime data = new Slime(); + new JsonDecoder().decode(data, Utf8.toBytes(jsonString)); + Inspector root = data.get(); + Inspector lastDeployTimestamp = root.field("lastDeployTimestamp"); + + return new TenantMetaData(Instant.ofEpochMilli(lastDeployTimestamp.asLong())); + } catch (Exception e) { + throw new IllegalArgumentException("Error parsing json metadata", e); + } + } + + private Slime getSlime() { + Slime slime = new Slime(); + Cursor meta = slime.setObject(); + meta.setLong("lastDeployTimestamp", lastDeployTimestamp.toEpochMilli()); + return slime; + } + +} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackage.java b/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackage.java index 11cec9efd95..665f37759b4 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackage.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackage.java @@ -13,7 +13,6 @@ import com.yahoo.config.codegen.DefParser; import com.yahoo.config.model.application.provider.PreGeneratedFileRegistry; import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.NodeFlavors; import com.yahoo.config.provision.serialization.AllocatedHostsSerializer; import com.yahoo.io.IOUtils; import com.yahoo.io.reader.NamedReader; @@ -52,17 +51,17 @@ public class ZKApplicationPackage implements ApplicationPackage { public static final String allocatedHostsNode = "allocatedHosts"; private final ApplicationMetaData metaData; - public ZKApplicationPackage(ConfigCurator zk, Path sessionPath, Optional<NodeFlavors> nodeFlavors) { + public ZKApplicationPackage(ConfigCurator zk, Path sessionPath) { verifyAppPath(zk, sessionPath); zkApplication = new ZKApplication(zk, sessionPath); metaData = readMetaDataFromLiveApp(zkApplication); importFileRegistries(); - allocatedHosts = importAllocatedHosts(nodeFlavors); + allocatedHosts = importAllocatedHosts(); } - private Optional<AllocatedHosts> importAllocatedHosts(Optional<NodeFlavors> nodeFlavors) { + private Optional<AllocatedHosts> importAllocatedHosts() { if ( ! zkApplication.exists(ZKApplicationPackage.allocatedHostsNode)) return Optional.empty(); - return Optional.of(readAllocatedHosts(nodeFlavors)); + return Optional.of(readAllocatedHosts()); } /** @@ -70,9 +69,9 @@ public class ZKApplicationPackage implements ApplicationPackage { * * @return the allocated hosts at this node or empty if there is no data at this path */ - private AllocatedHosts readAllocatedHosts(Optional<NodeFlavors> nodeFlavors) { + private AllocatedHosts readAllocatedHosts() { try { - return AllocatedHostsSerializer.fromJson(zkApplication.getBytes(ZKApplicationPackage.allocatedHostsNode), nodeFlavors); + return AllocatedHostsSerializer.fromJson(zkApplication.getBytes(ZKApplicationPackage.allocatedHostsNode)); } catch (Exception e) { throw new RuntimeException("Unable to read allocated hosts", e); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java index e42fb78f595..239f103332c 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java @@ -9,6 +9,7 @@ import com.yahoo.config.application.api.ApplicationMetaData; import com.yahoo.config.model.NullConfigModelRegistry; import com.yahoo.config.model.api.ApplicationRoles; import com.yahoo.config.model.application.provider.BaseDeployLogger; +import com.yahoo.config.model.application.provider.FilesApplicationPackage; import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ApplicationName; @@ -41,11 +42,13 @@ import com.yahoo.vespa.config.server.session.PrepareParams; import com.yahoo.vespa.config.server.session.RemoteSession; import com.yahoo.vespa.config.server.session.Session; import com.yahoo.vespa.config.server.session.SessionRepository; +import com.yahoo.vespa.config.server.session.SessionZooKeeperClient; import com.yahoo.vespa.config.server.session.SilentDeployLogger; import com.yahoo.vespa.config.server.tenant.ApplicationRolesStore; import com.yahoo.vespa.config.server.tenant.Tenant; import com.yahoo.vespa.config.server.tenant.TenantRepository; import com.yahoo.vespa.config.server.zookeeper.ConfigCurator; +import com.yahoo.vespa.config.util.ConfigUtils; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.curator.mock.MockCurator; import com.yahoo.vespa.flags.FlagSource; @@ -61,6 +64,7 @@ import org.junit.rules.TemporaryFolder; import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; @@ -105,6 +109,7 @@ public class ApplicationRepositoryTest { private SessionHandlerTest.MockProvisioner provisioner; private OrchestratorMock orchestrator; private TimeoutBudget timeoutBudget; + private Curator curator; private ConfigCurator configCurator; @Rule @@ -119,7 +124,7 @@ public class ApplicationRepositoryTest { } public void setup(FlagSource flagSource) throws IOException { - Curator curator = new MockCurator(); + curator = new MockCurator(); configCurator = ConfigCurator.create(curator); ConfigserverConfig configserverConfig = new ConfigserverConfig.Builder() .payloadCompressionType(ConfigserverConfig.PayloadCompressionType.Enum.UNCOMPRESSED) @@ -147,7 +152,8 @@ public class ApplicationRepositoryTest { new MockLogRetriever(), clock, new MockTesterClient(), - new NullMetric()); + new NullMetric(), + flagSource); timeoutBudget = new TimeoutBudget(clock, Duration.ofSeconds(60)); } @@ -157,11 +163,33 @@ public class ApplicationRepositoryTest { assertTrue(result.configChangeActions().getRefeedActions().isEmpty()); assertTrue(result.configChangeActions().getRestartActions().isEmpty()); - TenantName tenantName = applicationId().tenant(); - Tenant tenant = tenantRepository.getTenant(tenantName); + Tenant tenant = applicationRepository.getTenant(applicationId()); LocalSession session = tenant.getSessionRepository().getLocalSession(tenant.getApplicationRepo() .requireActiveSessionOf(applicationId())); session.getAllocatedHosts(); + + assertEquals(Instant.EPOCH, applicationRepository.getTenantMetaData(tenant).lastDeployTimestamp()); + } + + @Test + public void prepareAndActivateWithTenantMetaData() throws IOException { + FlagSource flagSource = new InMemoryFlagSource().withBooleanFlag(Flags.USE_TENANT_META_DATA.id(), true); + setup(flagSource); + + Duration duration = Duration.ofHours(1); + clock.advance(duration); + Instant deployTime = clock.instant(); + PrepareResult result = prepareAndActivate(testApp); + assertTrue(result.configChangeActions().getRefeedActions().isEmpty()); + assertTrue(result.configChangeActions().getRestartActions().isEmpty()); + + Tenant tenant = applicationRepository.getTenant(applicationId()); + LocalSession session = tenant.getSessionRepository().getLocalSession(tenant.getApplicationRepo() + .requireActiveSessionOf(applicationId())); + session.getAllocatedHosts(); + + assertEquals(deployTime.toEpochMilli(), + applicationRepository.getTenantMetaData(tenant).lastDeployTimestamp().toEpochMilli()); } @Test @@ -189,8 +217,7 @@ public class ApplicationRepositoryTest { long secondSessionId = result2.sessionId(); assertNotEquals(firstSessionId, secondSessionId); - TenantName tenantName = applicationId().tenant(); - Tenant tenant = tenantRepository.getTenant(tenantName); + Tenant tenant = applicationRepository.getTenant(applicationId()); LocalSession session = tenant.getSessionRepository().getLocalSession( tenant.getApplicationRepo().requireActiveSessionOf(applicationId())); assertEquals(firstSessionId, session.getMetaData().getPreviousActiveGeneration()); @@ -273,8 +300,7 @@ public class ApplicationRepositoryTest { @Test public void delete() { - TenantName tenantName = applicationId().tenant(); - Tenant tenant = tenantRepository.getTenant(tenantName); + Tenant tenant = applicationRepository.getTenant(applicationId()); SessionRepository sessionRepository = tenant.getSessionRepository(); { PrepareResult result = deployApp(testApp); @@ -347,9 +373,10 @@ public class ApplicationRepositoryTest { @Test public void testDeletingInactiveSessions() throws IOException { + File serverdb = temporaryFolder.newFolder("serverdb"); ConfigserverConfig configserverConfig = new ConfigserverConfig(new ConfigserverConfig.Builder() - .configServerDBDir(temporaryFolder.newFolder("serverdb").getAbsolutePath()) + .configServerDBDir(serverdb.getAbsolutePath()) .configDefinitionsDir(temporaryFolder.newFolder("configdefinitions").getAbsolutePath()) .sessionLifetime(60)); DeployTester tester = new DeployTester(configserverConfig, clock); @@ -387,6 +414,8 @@ public class ApplicationRepositoryTest { // There should be no expired remote sessions in the common case assertEquals(0, tester.applicationRepository().deleteExpiredRemoteSessions(clock, Duration.ofSeconds(0))); + assertEquals(1, sessionRepository.getLocalSessions().size()); + // Deploy, but do not activate Optional<com.yahoo.config.provision.Deployment> deployment4 = tester.redeployFromLocalActive(); assertTrue(deployment4.isPresent()); @@ -396,6 +425,25 @@ public class ApplicationRepositoryTest { sessionRepository.deleteLocalSession(localSession); assertEquals(1, sessionRepository.getLocalSessions().size()); + // Create a local session without any data in zookeeper (corner case seen in production occasionally) + // and check that expiring local sessions still work + int sessionId = 6; + Files.createDirectory(new TenantFileSystemDirs(serverdb, tenant1).getUserApplicationDir(sessionId).toPath()); + LocalSession localSession2 = new LocalSession(tenant1, + sessionId, + FilesApplicationPackage.fromFile(testApp), + new SessionZooKeeperClient(curator, + configCurator, + tenant1, + sessionId, + ConfigUtils.getCanonicalHostName())); + sessionRepository.addLocalSession(localSession2); + assertEquals(2, sessionRepository.getLocalSessions().size()); + + // Check that trying to expire local session when there exists a local session with no zookeeper data works + tester.applicationRepository().deleteExpiredLocalSessions(); + assertEquals(1, sessionRepository.getLocalSessions().size()); + // Check that trying to expire when there are no active sessions works tester.applicationRepository().deleteExpiredLocalSessions(); } @@ -410,7 +458,8 @@ public class ApplicationRepositoryTest { new MockLogRetriever(), new ManualClock(), new MockTesterClient(), - actual); + actual, + new InMemoryFlagSource()); deployApp(testAppLogServerWithContainer); Map<String, ?> context = Map.of("applicationId", "test1.testapp.default", "tenantName", "test1", @@ -424,7 +473,7 @@ public class ApplicationRepositoryTest { @Test public void deletesApplicationRoles() { - var tenant = tenantRepository.getTenant(tenant1); + var tenant = applicationRepository.getTenant(applicationId()); var applicationId = applicationId(tenant1); var prepareParams = new PrepareParams.Builder().applicationId(applicationId) .applicationRoles(ApplicationRoles.fromString("hostRole","containerRole")).build(); @@ -447,8 +496,7 @@ public class ApplicationRepositoryTest { public void require_that_provision_info_can_be_read() { prepareAndActivate(testAppJdiscOnly); - TenantName tenantName = applicationId().tenant(); - Tenant tenant = tenantRepository.getTenant(tenantName); + Tenant tenant = applicationRepository.getTenant(applicationId()); LocalSession session = tenant.getSessionRepository().getLocalSession(tenant.getApplicationRepo().requireActiveSessionOf(applicationId())); List<NetworkPorts.Allocation> list = new ArrayList<>(); @@ -494,7 +542,7 @@ public class ApplicationRepositoryTest { long sessionId = applicationRepository.createSession(applicationId(), timeoutBudget, testAppJdiscOnly); exceptionRule.expect(IllegalStateException.class); exceptionRule.expectMessage(containsString("tenant:test1 Session 3 is not prepared")); - applicationRepository.activate(tenantRepository.getTenant(tenant1), sessionId, timeoutBudget, false); + applicationRepository.activate(applicationRepository.getTenant(applicationId()), sessionId, timeoutBudget, false); RemoteSession activeSession = applicationRepository.getActiveSession(applicationId()); assertEquals(firstSession, activeSession.getSessionId()); @@ -508,10 +556,10 @@ public class ApplicationRepositoryTest { long firstSession = result.sessionId(); long sessionId = applicationRepository.createSession(applicationId(), timeoutBudget, testAppJdiscOnly); - applicationRepository.prepare(tenantRepository.getTenant(tenant1), sessionId, prepareParams(), clock.instant()); + applicationRepository.prepare(applicationRepository.getTenant(applicationId()), sessionId, prepareParams(), clock.instant()); exceptionRule.expect(RuntimeException.class); exceptionRule.expectMessage(containsString("Timeout exceeded when trying to activate 'test1.testapp'")); - applicationRepository.activate(tenantRepository.getTenant(tenant1), sessionId, new TimeoutBudget(clock, Duration.ofSeconds(0)), false); + applicationRepository.activate(applicationRepository.getTenant(applicationId()), sessionId, new TimeoutBudget(clock, Duration.ofSeconds(0)), false); RemoteSession activeSession = applicationRepository.getActiveSession(applicationId()); assertEquals(firstSession, activeSession.getSessionId()); @@ -533,10 +581,10 @@ public class ApplicationRepositoryTest { PrepareResult result2 = deployApp(testAppJdiscOnly); result2.sessionId(); - applicationRepository.prepare(tenantRepository.getTenant(tenant1), sessionId2, prepareParams(), clock.instant()); + applicationRepository.prepare(applicationRepository.getTenant(applicationId()), sessionId2, prepareParams(), clock.instant()); exceptionRule.expect(ActivationConflictException.class); exceptionRule.expectMessage(containsString("tenant:test1 app:testapp:default Cannot activate session 3 because the currently active session (4) has changed since session 3 was created (was 2 at creation time)")); - applicationRepository.activate(tenantRepository.getTenant(tenant1), sessionId2, timeoutBudget, false); + applicationRepository.activate(applicationRepository.getTenant(applicationId()), sessionId2, timeoutBudget, false); } @Test @@ -546,11 +594,11 @@ public class ApplicationRepositoryTest { exceptionRule.expect(IllegalStateException.class); exceptionRule.expectMessage(containsString("Session is active: 2")); - applicationRepository.prepare(tenantRepository.getTenant(tenant1), sessionId, prepareParams(), clock.instant()); + applicationRepository.prepare(applicationRepository.getTenant(applicationId()), sessionId, prepareParams(), clock.instant()); exceptionRule.expect(IllegalStateException.class); exceptionRule.expectMessage(containsString("tenant:test1 app:testapp:default Session 2 is already active")); - applicationRepository.activate(tenantRepository.getTenant(tenant1), sessionId, timeoutBudget, false); + applicationRepository.activate(applicationRepository.getTenant(applicationId()), sessionId, timeoutBudget, false); } @Test @@ -661,7 +709,8 @@ public class ApplicationRepositoryTest { new MockLogRetriever(), clock, new MockTesterClient(), - new NullMetric()); + new NullMetric(), + new InMemoryFlagSource()); } private PrepareResult prepareAndActivate(File application) { diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/TenantApplicationsTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/TenantApplicationsTest.java index 05f7aade168..cde115eec40 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/application/TenantApplicationsTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/TenantApplicationsTest.java @@ -171,19 +171,21 @@ public class TenantApplicationsTest { @Test public void testListConfigs() throws IOException, SAXException { + applications = TenantApplications.create(componentRegistry, TenantName.defaultName()); assertdefaultAppNotFound(); VespaModel model = new VespaModel(FilesApplicationPackage.fromFile(new File("src/test/apps/app"))); - applications.createApplication(ApplicationId.defaultId()); - applications.createPutTransaction(ApplicationId.defaultId(), 1).commit(); + ApplicationId applicationId = ApplicationId.defaultId(); + applications.createApplication(applicationId); + applications.createPutTransaction(applicationId, 1).commit(); applications.reloadConfig(ApplicationSet.fromSingle(new Application(model, new ServerCache(), 1, false, vespaVersion, MetricUpdater.createTestUpdater(), - ApplicationId.defaultId()))); - Set<ConfigKey<?>> configNames = applications.listConfigs(ApplicationId.defaultId(), Optional.of(vespaVersion), false); + applicationId))); + Set<ConfigKey<?>> configNames = applications.listConfigs(applicationId, Optional.of(vespaVersion), false); assertTrue(configNames.contains(new ConfigKey<>("sentinel", "hosts", "cloud.config"))); configNames = applications.listConfigs(ApplicationId.defaultId(), Optional.of(vespaVersion), true); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java index a574d8f3b60..2b7c1c3a6dc 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java @@ -43,6 +43,7 @@ import com.yahoo.vespa.config.server.tenant.Tenant; import com.yahoo.vespa.config.server.tenant.TenantRepository; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.curator.mock.MockCurator; +import com.yahoo.vespa.flags.InMemoryFlagSource; import com.yahoo.vespa.model.VespaModel; import com.yahoo.vespa.model.VespaModelFactory; @@ -133,7 +134,8 @@ public class DeployTester { new LogRetriever(), clock, new MockTesterClient(), - new NullMetric()); + new NullMetric(), + new InMemoryFlagSource()); } public Tenant tenant() { diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java index dfc5649433a..deff0aba376 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java @@ -165,7 +165,7 @@ public class ZooKeeperClientTest { Path hostsPath = app.append(ZKApplicationPackage.allocatedHostsNode); assertTrue(zk.exists(hostsPath.getAbsolute())); - AllocatedHosts deserialized = fromJson(zk.getBytes(hostsPath.getAbsolute()), Optional.empty()); + AllocatedHosts deserialized = fromJson(zk.getBytes(hostsPath.getAbsolute())); assertEquals(hosts, deserialized.getHosts()); } 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 8bf5215a696..0db6aea312d 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 @@ -45,13 +45,12 @@ public class ApplicationContentHandlerTest extends ContentHandlerTestBase { private final ApplicationId appId1 = new ApplicationId.Builder().tenant(tenantName1).applicationName("foo").instanceName("quux").build(); private final ApplicationId appId2 = new ApplicationId.Builder().tenant(tenantName2).applicationName("foo").instanceName("quux").build(); - private TenantRepository tenantRepository; private ApplicationRepository applicationRepository; private ApplicationHandler handler; @Before public void setupHandler() { - tenantRepository = new TenantRepository(componentRegistry, false); + TenantRepository tenantRepository = new TenantRepository(componentRegistry, false); tenantRepository.addTenant(tenantName1); tenantRepository.addTenant(tenantName2); @@ -106,7 +105,7 @@ public class ApplicationContentHandlerTest extends ContentHandlerTestBase { @Test public void require_that_get_does_not_set_write_flag() throws IOException { - Tenant tenant1 = tenantRepository.getTenant(tenantName1); + Tenant tenant1 = applicationRepository.getTenant(appId1); LocalSession session = applicationRepository.getActiveLocalSession(tenant1, appId1); assertContent("/test.txt", "foo\n"); assertThat(session.getStatus(), is(Session.Status.ACTIVATE)); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java index 1a558f89284..9f8eb95a348 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java @@ -32,6 +32,7 @@ import com.yahoo.vespa.config.server.provision.HostProvisionerProvider; import com.yahoo.vespa.config.server.session.PrepareParams; import com.yahoo.vespa.config.server.tenant.Tenant; import com.yahoo.vespa.config.server.tenant.TenantRepository; +import com.yahoo.vespa.flags.InMemoryFlagSource; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -98,7 +99,8 @@ public class ApplicationHandlerTest { logRetriever, Clock.systemUTC(), testerClient, - metric); + metric, + new InMemoryFlagSource()); } @After @@ -113,7 +115,7 @@ public class ApplicationHandlerTest { { applicationRepository.deploy(testApp, prepareParams(applicationId)); - Tenant mytenant = tenantRepository.getTenant(applicationId.tenant()); + Tenant mytenant = applicationRepository.getTenant(applicationId); deleteAndAssertOKResponse(mytenant, applicationId); } @@ -207,7 +209,8 @@ public class ApplicationHandlerTest { configserverConfig, orchestrator, testerClient, - metric); + metric, + new InMemoryFlagSource()); ApplicationHandler mockHandler = createApplicationHandler(applicationRepository); when(mockHttpProxy.get(any(), eq(host), eq(CLUSTERCONTROLLER_CONTAINER.serviceName),eq("clustercontroller-status/v1/clusterName1"))) .thenReturn(new StaticResponse(200, "text/html", "<html>...</html>")); @@ -301,9 +304,10 @@ public class ApplicationHandlerTest { } private void deleteAndAssertOKResponseMocked(ApplicationId applicationId, boolean fullAppIdInUrl) throws IOException { - long sessionId = tenantRepository.getTenant(applicationId.tenant()).getApplicationRepo().requireActiveSessionOf(applicationId); + Tenant tenant = applicationRepository.getTenant(applicationId); + long sessionId = tenant.getApplicationRepo().requireActiveSessionOf(applicationId); deleteAndAssertResponse(applicationId, Zone.defaultZone(), Response.Status.OK, null, fullAppIdInUrl); - assertNull(tenantRepository.getTenant(applicationId.tenant()).getSessionRepository().getLocalSession(sessionId)); + assertNull(tenant.getSessionRepository().getLocalSession(sessionId)); } private void deleteAndAssertOKResponse(Tenant tenant, ApplicationId applicationId) throws IOException { diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HostHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HostHandlerTest.java index 364e7372e20..5bf5e1f2229 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 @@ -39,7 +39,6 @@ public class HostHandlerTest { private HostHandler handler; private final static TenantName mytenant = TenantName.from("mytenant"); private final static Zone zone = Zone.defaultZone(); - private TenantRepository tenantRepository; private ApplicationRepository applicationRepository; @Before @@ -47,7 +46,7 @@ public class HostHandlerTest { TestComponentRegistry componentRegistry = new TestComponentRegistry.Builder() .zone(zone) .build(); - tenantRepository = new TenantRepository(componentRegistry); + TenantRepository tenantRepository = new TenantRepository(componentRegistry); tenantRepository.addTenant(mytenant); applicationRepository = new ApplicationRepository(tenantRepository, new SessionHandlerTest.MockProvisioner(), @@ -60,7 +59,7 @@ public class HostHandlerTest { public void require_correct_tenant_and_application_for_hostname() throws Exception { ApplicationId applicationId = applicationId(); applicationRepository.deploy(testApp, new PrepareParams.Builder().applicationId(applicationId).build()); - Tenant tenant = tenantRepository.getTenant(mytenant); + Tenant tenant = applicationRepository.getTenant(applicationId); String hostname = applicationRepository.getCurrentActiveApplicationSet(tenant, applicationId).get().getAllHosts().iterator().next(); assertApplicationForHost(hostname, applicationId); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java index 53422df3fad..e48752afaf6 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java @@ -58,7 +58,6 @@ public class SessionActiveHandlerTest { private SessionHandlerTest.MockProvisioner hostProvisioner; private TestComponentRegistry componentRegistry; - private TenantRepository tenantRepository; private ApplicationRepository applicationRepository; private SessionActiveHandler handler; @@ -73,10 +72,10 @@ public class SessionActiveHandlerTest { .curator(new MockCurator()) .modelFactoryRegistry(new ModelFactoryRegistry(List.of((modelFactory)))) .build(); - tenantRepository = new TenantRepository(componentRegistry, false); + TenantRepository tenantRepository = new TenantRepository(componentRegistry, false); + tenantRepository.addTenant(tenantName); applicationRepository = new ApplicationRepository(tenantRepository, hostProvisioner, new OrchestratorMock(), componentRegistry.getClock()); - tenantRepository.addTenant(tenantName); handler = createHandler(); } @@ -124,7 +123,7 @@ public class SessionActiveHandlerTest { ApplicationMetaData getMetaData() { return metaData; } void invoke() { - Tenant tenant = tenantRepository.getTenant(tenantName); + Tenant tenant = applicationRepository.getTenant(applicationId()); long sessionId = applicationRepository.createSession(applicationId(), new TimeoutBudget(componentRegistry.getClock(), Duration.ofSeconds(10)), testApp); 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 3ab56d3869a..cda62361256 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 @@ -54,7 +54,7 @@ public class SessionContentHandlerTest extends ContentHandlerTestBase { new OrchestratorMock(), Clock.systemUTC()); applicationRepository.deploy(testApp, new PrepareParams.Builder().applicationId(applicationId()).build()); - Tenant tenant = tenantRepository.getTenant(tenantName); + Tenant tenant = applicationRepository.getTenant(applicationId()); sessionId = applicationRepository.getActiveLocalSession(tenant, applicationId()).getSessionId(); handler = createHandler(); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java index cf01c9b6713..fb268492dd7 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java @@ -69,17 +69,17 @@ public class LocalSessionTest { @Test public void require_that_session_is_initialized() throws Exception { - LocalSession session = createSession(TenantName.defaultName(), 2); + LocalSession session = createSession(applicationId(), 2); assertThat(session.getSessionId(), is(2L)); - session = createSession(TenantName.defaultName(), Long.MAX_VALUE); + session = createSession(applicationId(), Long.MAX_VALUE); assertThat(session.getSessionId(), is(Long.MAX_VALUE)); assertThat(session.getActiveSessionAtCreate(), is(0L)); } @Test public void require_that_marking_session_modified_changes_status_to_new() throws Exception { - LocalSession session = createSession(TenantName.defaultName(), 3); - doPrepare(session); + LocalSession session = createSession(applicationId(), 3); + doPrepare(session, applicationId()); assertThat(session.getStatus(), is(Session.Status.PREPARE)); session.getApplicationFile(Path.createRoot(), Session.Mode.READ); assertThat(session.getStatus(), is(Session.Status.PREPARE)); @@ -89,7 +89,7 @@ public class LocalSessionTest { @Test public void require_that_application_file_can_be_fetched() throws Exception { - LocalSession session = createSession(TenantName.defaultName(), 3); + LocalSession session = createSession(applicationId(), 3); ApplicationFile f1 = session.getApplicationFile(Path.fromString("services.xml"), Session.Mode.READ); ApplicationFile f2 = session.getApplicationFile(Path.fromString("services2.xml"), Session.Mode.READ); assertTrue(f1.exists()); @@ -98,36 +98,37 @@ public class LocalSessionTest { @Test(expected = IllegalStateException.class) public void require_that_no_provision_info_throws_exception() throws Exception { - createSession(TenantName.defaultName(), 3).getAllocatedHosts(); + createSession(applicationId(), 3).getAllocatedHosts(); } - private LocalSession createSession(TenantName tenant, long sessionId) throws Exception { - return createSession(tenant, sessionId, Optional.empty()); + private LocalSession createSession(ApplicationId applicationId, long sessionId) throws Exception { + return createSession(applicationId, sessionId, Optional.empty()); } - private LocalSession createSession(TenantName tenant, long sessionId, + private LocalSession createSession(ApplicationId applicationId, long sessionId, Optional<AllocatedHosts> allocatedHosts) throws Exception { - SessionZooKeeperClient zkc = new MockSessionZKClient(curator, tenant, sessionId, allocatedHosts); + TenantName tenantName = applicationId.tenant(); + SessionZooKeeperClient zkc = new MockSessionZKClient(curator, tenantName, sessionId, allocatedHosts); zkc.createWriteStatusTransaction(Session.Status.NEW).commit(); ZooKeeperClient zkClient = new ZooKeeperClient(configCurator, new BaseDeployLogger(), - TenantRepository.getSessionsPath(tenant).append(String.valueOf(sessionId))); + TenantRepository.getSessionsPath(tenantName).append(String.valueOf(sessionId))); if (allocatedHosts.isPresent()) { zkClient.write(allocatedHosts.get()); } zkClient.write(Collections.singletonMap(new Version(0, 0, 0), new MockFileRegistry())); TenantApplications applications = tenantRepository.getTenant(tenantName).getApplicationRepo(); - applications.createApplication(applicationId()); - LocalSession session = new LocalSession(tenant, sessionId, FilesApplicationPackage.fromFile(testApp), zkc); - session.setApplicationId(applicationId()); + applications.createApplication(applicationId); + LocalSession session = new LocalSession(tenantName, sessionId, FilesApplicationPackage.fromFile(testApp), zkc); + session.setApplicationId(applicationId); return session; } - private void doPrepare(LocalSession session) { - doPrepare(session, new PrepareParams.Builder().applicationId(applicationId()).build()); + private void doPrepare(LocalSession session, ApplicationId applicationId) { + doPrepare(session, new PrepareParams.Builder().applicationId(applicationId).build()); } private void doPrepare(LocalSession session, PrepareParams params) { - SessionRepository sessionRepository = tenantRepository.getTenant(tenantName).getSessionRepository(); + SessionRepository sessionRepository = tenantRepository.getTenant(params.getApplicationId().tenant()).getSessionRepository(); sessionRepository.prepareLocalSession(session, getLogger(), params, Optional.empty(), tenantPath, Instant.now()); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockSessionZKClient.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockSessionZKClient.java index 692510b8b6d..0451ef84e09 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockSessionZKClient.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockSessionZKClient.java @@ -6,6 +6,8 @@ import com.yahoo.config.model.test.MockApplicationPackage; import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.TenantName; import com.yahoo.vespa.config.server.tenant.TenantRepository; +import com.yahoo.vespa.config.server.zookeeper.ConfigCurator; +import com.yahoo.vespa.config.util.ConfigUtils; import com.yahoo.vespa.curator.Curator; import java.util.Optional; @@ -30,7 +32,11 @@ public class MockSessionZKClient extends SessionZooKeeperClient { } MockSessionZKClient(Curator curator, TenantName tenantName, long sessionId, ApplicationPackage application) { - super(curator, TenantRepository.getSessionsPath(tenantName).append(String.valueOf(sessionId))); + super(curator, + ConfigCurator.create(curator), + tenantName, + sessionId, + ConfigUtils.getCanonicalHostName()); this.app = application; curator.create(TenantRepository.getSessionsPath(tenantName).append(String.valueOf(sessionId))); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java index e53f9270983..b181ad3e8d6 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java @@ -42,13 +42,16 @@ import com.yahoo.vespa.config.server.provision.HostProvisionerProvider; import com.yahoo.vespa.config.server.tenant.ContainerEndpointsCache; import com.yahoo.vespa.config.server.tenant.EndpointCertificateMetadataStore; import com.yahoo.vespa.config.server.tenant.EndpointCertificateRetriever; +import com.yahoo.vespa.config.server.tenant.TenantRepository; import com.yahoo.vespa.config.server.zookeeper.ConfigCurator; +import com.yahoo.vespa.config.util.ConfigUtils; import com.yahoo.vespa.curator.mock.MockCurator; import com.yahoo.vespa.flags.Flags; import com.yahoo.vespa.flags.InMemoryFlagSource; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; import javax.security.auth.x500.X500Principal; @@ -80,7 +83,6 @@ import static org.junit.Assert.assertTrue; public class SessionPreparerTest { private static final Path tenantPath = Path.createRoot(); - private static final Path sessionsPath = tenantPath.append("sessions").append("testapp"); private static final File testApp = new File("src/test/apps/app"); private static final File invalidTestApp = new File("src/test/apps/illegalApp"); private static final Version version123 = new Version(1, 2, 3); @@ -98,6 +100,9 @@ public class SessionPreparerTest { @Rule public TemporaryFolder folder = new TemporaryFolder(); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Before public void setUp() throws IOException { curator = new MockCurator(); @@ -145,25 +150,44 @@ public class SessionPreparerTest { @Test public void require_that_application_validation_exception_is_ignored_if_forced() throws IOException { - prepare(invalidTestApp, new PrepareParams.Builder().applicationId(applicationId()).ignoreValidationErrors(true).timeoutBudget(TimeoutBudgetTest.day()).build()); + prepare(invalidTestApp, + new PrepareParams.Builder() + .applicationId(applicationId()) + .ignoreValidationErrors(true) + .timeoutBudget(TimeoutBudgetTest.day()) + .build(), + 1); } @Test public void require_that_zookeeper_is_not_written_to_if_dryrun() throws IOException { - prepare(testApp, new PrepareParams.Builder().applicationId(applicationId()).dryRun(true).timeoutBudget(TimeoutBudgetTest.day()).build()); - assertFalse(configCurator.exists(sessionsPath.append(ConfigCurator.USERAPP_ZK_SUBPATH).append("services.xml").getAbsolute())); + long sessionId = 1; + prepare(testApp, + new PrepareParams.Builder() + .applicationId(applicationId()) + .dryRun(true) + .timeoutBudget(TimeoutBudgetTest.day()) + .build(), + 1); + Path sessionPath = sessionPath(sessionId); + assertFalse(configCurator.exists(sessionPath.append(ConfigCurator.USERAPP_ZK_SUBPATH).append("services.xml").getAbsolute())); } @Test public void require_that_filedistribution_is_ignored_on_dryrun() throws IOException { - PrepareResult result = prepare(testApp, new PrepareParams.Builder().applicationId(applicationId()).dryRun(true).build()); + PrepareResult result = prepare(testApp, + new PrepareParams.Builder() + .applicationId(applicationId()) + .dryRun(true) + .build(), + 1); assertTrue(result.getFileRegistries().get(version321).export().isEmpty()); } @Test public void require_that_application_is_prepared() throws Exception { prepare(testApp); - assertTrue(configCurator.exists(sessionsPath.append(ConfigCurator.USERAPP_ZK_SUBPATH).append("services.xml").getAbsolute())); + assertTrue(configCurator.exists(sessionPath(1).append(ConfigCurator.USERAPP_ZK_SUBPATH).append("services.xml").getAbsolute())); } @Test(expected = InvalidApplicationException.class) @@ -172,7 +196,7 @@ public class SessionPreparerTest { HostRegistry<ApplicationId> hostValidator = new HostRegistry<>(); hostValidator.update(applicationId("foo"), Collections.singletonList("mytesthost")); preparer.prepare(hostValidator, new BaseDeployLogger(), new PrepareParams.Builder().applicationId(applicationId("default")).build(), - Optional.empty(), tenantPath, Instant.now(), app.getAppDir(), app, new SessionZooKeeperClient(curator, sessionsPath)); + Optional.empty(), tenantPath, Instant.now(), app.getAppDir(), app, createSessionZooKeeperClient()); } @Test @@ -187,28 +211,37 @@ public class SessionPreparerTest { hostValidator.update(applicationId, Collections.singletonList("mytesthost")); preparer.prepare(hostValidator, logger, new PrepareParams.Builder().applicationId(applicationId).build(), Optional.empty(), tenantPath, Instant.now(), app.getAppDir(), app, - new SessionZooKeeperClient(curator, sessionsPath)); + createSessionZooKeeperClient()); assertEquals(logged.toString(), ""); } @Test public void require_that_application_id_is_written_in_prepare() throws IOException { + PrepareParams params = new PrepareParams.Builder().applicationId(applicationId()).build(); + int sessionId = 1; + prepare(testApp, params); + assertThat(createSessionZooKeeperClient(sessionId).readApplicationId().get(), is(applicationId())); + } + + @Test + public void require_that_writing_wrong_application_id_fails() throws IOException { TenantName tenant = TenantName.from("tenant"); ApplicationId origId = new ApplicationId.Builder() - .tenant(tenant) - .applicationName("foo").instanceName("quux").build(); + .tenant(tenant) + .applicationName("foo") + .instanceName("quux") + .build(); PrepareParams params = new PrepareParams.Builder().applicationId(origId).build(); + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Error preparing session"); prepare(testApp, params); - SessionZooKeeperClient zkc = new SessionZooKeeperClient(curator, sessionsPath); - assertTrue(configCurator.exists(sessionsPath.append(SessionZooKeeperClient.APPLICATION_ID_PATH).getAbsolute())); - assertThat(zkc.readApplicationId(), is(origId)); } @Test public void require_that_file_reference_of_application_package_is_written_to_zk() throws Exception { flagSource.withBooleanFlag(Flags.CONFIGSERVER_DISTRIBUTE_APPLICATION_PACKAGE.id(), true); prepare(testApp); - assertTrue(configCurator.exists(sessionsPath.append(APPLICATION_PACKAGE_REFERENCE_PATH).getAbsolute())); + assertTrue(configCurator.exists(sessionPath(1).append(APPLICATION_PACKAGE_REFERENCE_PATH).getAbsolute())); } @Test @@ -333,10 +366,14 @@ public class SessionPreparerTest { } private PrepareResult prepare(File app, PrepareParams params) throws IOException { + return prepare(app, params, 1); + } + + private PrepareResult prepare(File app, PrepareParams params, long sessionId) throws IOException { FilesApplicationPackage applicationPackage = getApplicationPackage(app); return preparer.prepare(new HostRegistry<>(), getLogger(), params, Optional.empty(), tenantPath, Instant.now(), applicationPackage.getAppDir(), - applicationPackage, new SessionZooKeeperClient(curator, sessionsPath)); + applicationPackage, createSessionZooKeeperClient(sessionId)); } private FilesApplicationPackage getApplicationPackage(File testFile) throws IOException { @@ -360,6 +397,18 @@ public class SessionPreparerTest { ApplicationName.from(applicationName), InstanceName.defaultName()); } + private SessionZooKeeperClient createSessionZooKeeperClient() { + return createSessionZooKeeperClient(1); + } + + private SessionZooKeeperClient createSessionZooKeeperClient(long sessionId) { + return new SessionZooKeeperClient(curator, configCurator, applicationId().tenant(), sessionId, ConfigUtils.getCanonicalHostName()); + } + + private Path sessionPath(long sessionId) { + return TenantRepository.getSessionsPath(applicationId().tenant()).append(String.valueOf(sessionId)); + } + private static class FailWithTransientExceptionProvisioner implements Provisioner { @Override diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionRepositoryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionRepositoryTest.java index 13a27e570c2..fb7b5280b1e 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionRepositoryTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionRepositoryTest.java @@ -11,6 +11,8 @@ import com.yahoo.vespa.config.server.TestComponentRegistry; import com.yahoo.vespa.config.server.application.OrchestratorMock; import com.yahoo.vespa.config.server.http.SessionHandlerTest; import com.yahoo.vespa.config.server.tenant.TenantRepository; +import com.yahoo.vespa.config.server.zookeeper.ConfigCurator; +import com.yahoo.vespa.config.util.ConfigUtils; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.curator.mock.MockCurator; import com.yahoo.vespa.flags.FlagSource; @@ -149,11 +151,11 @@ public class SessionRepositoryTest { } private void createSession(long sessionId, boolean wait) { - createSession(sessionId, wait, sessionRepository); - } - - private void createSession(long sessionId, boolean wait, SessionRepository sessionRepository) { - SessionZooKeeperClient zkc = new SessionZooKeeperClient(curator, sessionRepository.getSessionPath(sessionId)); + SessionZooKeeperClient zkc = new SessionZooKeeperClient(curator, + ConfigCurator.create(curator), + tenantName, + sessionId, + ConfigUtils.getCanonicalHostName()); zkc.createNewSession(Instant.now()); if (wait) { Curator.CompletionWaiter waiter = zkc.getUploadWaiter(); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java index 1d7df7acfd0..de55a6677ff 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java @@ -3,13 +3,18 @@ package com.yahoo.vespa.config.server.session; import com.yahoo.config.FileReference; import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.TenantName; import com.yahoo.path.Path; import com.yahoo.text.Utf8; +import com.yahoo.vespa.config.server.tenant.TenantRepository; import com.yahoo.vespa.config.server.zookeeper.ConfigCurator; +import com.yahoo.vespa.config.util.ConfigUtils; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.curator.mock.MockCurator; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import java.time.Instant; @@ -22,18 +27,24 @@ import static org.junit.Assert.assertTrue; */ public class SessionZooKeeperClientTest { + private static final TenantName tenantName = TenantName.defaultName(); + private Curator curator; private ConfigCurator configCurator; + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Before public void setup() { curator = new MockCurator(); configCurator = ConfigCurator.create(curator); + curator.create(sessionsPath()); } @Test public void require_that_status_can_be_updated() { - SessionZooKeeperClient zkc = createSessionZKClient("1"); + SessionZooKeeperClient zkc = createSessionZKClient(1); zkc.writeStatus(Session.Status.NEW); assertThat(zkc.readStatus(), is(Session.Status.NEW)); @@ -49,45 +60,68 @@ public class SessionZooKeeperClientTest { @Test public void require_that_status_is_written_to_zk() { - SessionZooKeeperClient zkc = createSessionZKClient("2"); + int sessionId = 2; + SessionZooKeeperClient zkc = createSessionZKClient(sessionId); zkc.writeStatus(Session.Status.NEW); - String path = "/2" + ConfigCurator.SESSIONSTATE_ZK_SUBPATH; + String path = sessionPath(sessionId).append(ConfigCurator.SESSIONSTATE_ZK_SUBPATH).getAbsolute(); assertTrue(configCurator.exists(path)); assertThat(configCurator.getData(path), is("NEW")); } @Test public void require_that_status_is_read_from_zk() { - SessionZooKeeperClient zkc = createSessionZKClient("3"); - curator.set(Path.fromString("3").append(ConfigCurator.SESSIONSTATE_ZK_SUBPATH), Utf8.toBytes("PREPARE")); + int sessionId = 3; + SessionZooKeeperClient zkc = createSessionZKClient(sessionId); + curator.set(sessionPath(sessionId).append(ConfigCurator.SESSIONSTATE_ZK_SUBPATH), Utf8.toBytes("PREPARE")); assertThat(zkc.readStatus(), is(Session.Status.PREPARE)); } @Test public void require_that_application_id_is_written_to_zk() { ApplicationId id = new ApplicationId.Builder() - .tenant("tenant") - .applicationName("foo").instanceName("bim").build(); - SessionZooKeeperClient zkc = createSessionZKClient("3"); + .tenant(tenantName) + .applicationName("foo") + .instanceName("bim") + .build(); + int sessionId = 3; + SessionZooKeeperClient zkc = createSessionZKClient(sessionId); zkc.writeApplicationId(id); - String path = "/3/" + SessionZooKeeperClient.APPLICATION_ID_PATH; + String path = sessionPath(sessionId).append(SessionZooKeeperClient.APPLICATION_ID_PATH).getAbsolute(); assertTrue(configCurator.exists(path)); - assertThat(configCurator.getData(path), is("tenant:foo:bim")); + assertThat(configCurator.getData(path), is(id.serializedForm())); + } + + @Test + public void require_that_wrong_application_gives_exception() { + ApplicationId id = new ApplicationId.Builder() + .tenant("someOtherTenant") + .applicationName("foo") + .instanceName("bim") + .build(); + int sessionId = 3; + SessionZooKeeperClient zkc = createSessionZKClient(sessionId); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Cannot write application id 'someOtherTenant.foo.bim' for tenant 'default'"); + zkc.writeApplicationId(id); } @Test public void require_that_application_id_is_read_from_zk() { ApplicationId id = new ApplicationId.Builder() - .tenant("tenant") - .applicationName("bar").instanceName("quux").build(); + .tenant("tenant") + .applicationName("bar") + .instanceName("quux") + .build(); String idNoVersion = id.serializedForm(); - assertApplicationIdParse("3", idNoVersion, idNoVersion); + assertApplicationIdParse(3, idNoVersion, idNoVersion); } @Test public void require_that_create_time_can_be_written_and_read() { - SessionZooKeeperClient zkc = createSessionZKClient("3"); - curator.delete(Path.fromString("3")); + int sessionId = 3; + SessionZooKeeperClient zkc = createSessionZKClient(sessionId); + curator.delete(sessionPath(sessionId)); assertThat(zkc.readCreateTime(), is(Instant.EPOCH)); Instant now = Instant.now(); zkc.createNewSession(now); @@ -98,23 +132,35 @@ public class SessionZooKeeperClientTest { @Test public void require_that_application_package_file_reference_can_be_written_and_read() { final FileReference testRef = new FileReference("test-ref"); - SessionZooKeeperClient zkc = createSessionZKClient("3"); + SessionZooKeeperClient zkc = createSessionZKClient(3); zkc.writeApplicationPackageReference(testRef); assertThat(zkc.readApplicationPackageReference(), is(testRef)); } - private void assertApplicationIdParse(String sessionId, String idString, String expectedIdString) { + private void assertApplicationIdParse(long sessionId, String idString, String expectedIdString) { SessionZooKeeperClient zkc = createSessionZKClient(sessionId); - String path = "/" + sessionId + "/" + SessionZooKeeperClient.APPLICATION_ID_PATH; + String path = sessionPath(sessionId).append(SessionZooKeeperClient.APPLICATION_ID_PATH).getAbsolute(); configCurator.putData(path, idString); - ApplicationId zkId = zkc.readApplicationId(); - assertThat(zkId.serializedForm(), is(expectedIdString)); + ApplicationId applicationId = zkc.readApplicationId().get(); + assertThat(applicationId.serializedForm(), is(expectedIdString)); } - private SessionZooKeeperClient createSessionZKClient(String sessionId) { - SessionZooKeeperClient zkc = new SessionZooKeeperClient(curator, Path.fromString(sessionId)); + private SessionZooKeeperClient createSessionZKClient(long sessionId) { + SessionZooKeeperClient zkc = new SessionZooKeeperClient(curator, + ConfigCurator.create(curator), + tenantName, + sessionId, + ConfigUtils.getCanonicalHostName()); zkc.createNewSession(Instant.now()); return zkc; } + private static Path sessionsPath() { + return TenantRepository.getSessionsPath(tenantName); + } + + private static Path sessionPath(long sessionId) { + return sessionsPath().append(String.valueOf(sessionId)); + } + } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRepositoryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRepositoryTest.java index f372b21b065..a31b06bbebb 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRepositoryTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRepositoryTest.java @@ -5,7 +5,9 @@ import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.component.Version; import com.yahoo.config.model.test.MockApplicationPackage; import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.ApplicationName; import com.yahoo.config.provision.Environment; +import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.TenantName; @@ -84,8 +86,9 @@ public class TenantRepositoryTest { @Test public void testListenersAdded() throws IOException, SAXException { TenantApplications applicationRepo = tenantRepository.getTenant(tenant1).getApplicationRepo(); - applicationRepo.createApplication(ApplicationId.defaultId()); - applicationRepo.createPutTransaction(ApplicationId.defaultId(), 4).commit(); + ApplicationId id = ApplicationId.from(tenant1, ApplicationName.defaultName(), InstanceName.defaultName()); + applicationRepo.createApplication(id); + applicationRepo.createPutTransaction(id, 4).commit(); applicationRepo.reloadConfig(ApplicationSet.fromSingle( new Application(new VespaModel(MockApplicationPackage.createEmpty()), new ServerCache(), @@ -93,7 +96,7 @@ public class TenantRepositoryTest { false, new Version(1, 2, 3), MetricUpdater.createTestUpdater(), - ApplicationId.defaultId()))); + id))); assertEquals(1, listener.reloaded.get()); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackageTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackageTest.java index 4b4605cce7d..32d5d84f323 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackageTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackageTest.java @@ -48,7 +48,7 @@ public class ZKApplicationPackageTest { Collections.singleton(new HostSpec("foo.yahoo.com", TEST_FLAVOR.get().resources(), TEST_FLAVOR.get().resources(), - NodeResources.unspecified(), + TEST_FLAVOR.get().resources(), ClusterMembership.from("container/test/0/0", Version.fromString("6.73.1"), Optional.of(DockerImage.fromString("docker.foo.com:4443/vespa/bar"))), Optional.of(Version.fromString("6.0.1")), Optional.empty(), @@ -67,7 +67,7 @@ public class ZKApplicationPackageTest { @Test public void testBasicZKFeed() throws IOException { feed(configCurator, new File(APP)); - ZKApplicationPackage zkApp = new ZKApplicationPackage(configCurator, Path.fromString("/0"), Optional.of(new MockNodeFlavors())); + ZKApplicationPackage zkApp = new ZKApplicationPackage(configCurator, Path.fromString("/0")); assertTrue(Pattern.compile(".*<slobroks>.*",Pattern.MULTILINE+Pattern.DOTALL).matcher(IOUtils.readAll(zkApp.getServices())).matches()); assertTrue(Pattern.compile(".*<alias>.*",Pattern.MULTILINE+Pattern.DOTALL).matcher(IOUtils.readAll(zkApp.getHosts())).matches()); assertTrue(Pattern.compile(".*<slobroks>.*",Pattern.MULTILINE+Pattern.DOTALL).matcher(IOUtils.readAll(zkApp.getFile(Path.fromString("services.xml")).createReader())).matches()); |