Repository: vespa-engine/vespa
File: configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java (Java)

Code before:
    }

    void prepareRemoteSession(long sessionId) {
        // Might need to create local session first
        createLocalSessionFromDistributedApplicationPackage(sessionId);
        RemoteSession session = remoteSessionCache.get(sessionId);
        if (session == null) return;

        SessionZooKeeperClient sessionZooKeeperClient = createSessionZooKeeperClient(sessionId);
        CompletionWaiter waiter = sessionZooKeeperClient.getPrepareWaiter();
        ensureApplicationLoaded(session);
        notifyCompletion(waiter);
    }

    public ApplicationVersions ensureApplicationLoaded(RemoteSession session) {
        if (session.applicationVersions().isPresent()) {
            return session.applicationVersions().get();
        }
        Optional<Long> activeSessionId = getActiveSessionId(session.getApplicationId());
        Optional<ApplicationVersions> previousActiveApplicationVersions = activeSessionId.filter(session::isNewerThan)
                                                                         .flatMap(this::activeApplicationVersions);
        ApplicationVersions applicationVersions = loadApplication(session, previousActiveApplicationVersions);
        RemoteSession activated = session.activated(applicationVersions);
        long sessionId = activated.getSessionId();
        remoteSessionCache.put(sessionId, activated);
        updateSessionStateWatcher(sessionId);

        return applicationVersions;
    }

    void confirmUpload(Session session) {
        CompletionWaiter waiter = createSessionZooKeeperClient(session.getSessionId()).getUploadWaiter();
        long sessionId = session.getSessionId();
        log.log(Level.FINE, () -> "Notifying upload waiter for session " + sessionId);
        notifyCompletion(waiter);
        log.log(Level.FINE, () -> "Done notifying upload for session " + sessionId);
    }

    void notifyCompletion(CompletionWaiter completionWaiter) {
        try {
            completionWaiter.notifyCompletion();
        } catch (RuntimeException e) {
            // Throw only if we get something else than NoNodeException or NodeExistsException.
            // NoNodeException might happen when the session is no longer in use (e.g. the app using this session
            // has been deleted) and this method has not been called yet for the previous session operation on a
            // minority of the config servers.
            // NodeExistsException might happen if an event for this node is delivered more than once, in that case
            // this is a no-op
            Set<Class<? extends KeeperException>> acceptedExceptions = Set.of(KeeperException.NoNodeException.class,
                                                                              KeeperException.NodeExistsException.class);
            Class<? extends Throwable> exceptionClass = e.getCause().getClass();

Review comment (refactor):
This duplicates the existing logic in `Deployment`. @hmusum could that logic now be removed?

We did though discuss yesterday of moving this steps to `prepare` instead, as we prefer having the model  in more of a "final" state prior to `activate`.

Code after:
     * Marks clusters for deferred reconfiguration in the model, i.e. wait until restart to apply new config.
     * This is similar to {@link com.yahoo.vespa.config.server.deploy.Deployment::applyDeferredReconfigurationOfClusters}
     * but for {@link ActivationTriggers} from {@link RemoteSession} in ZooKeeper 
     * rather than from {@link Session} created locally on the config server.
     */
    private void applyDeferredReconfigurationOfClusters(RemoteSession session, ApplicationVersions applicationVersions) {
        Set<String> clustersWithDeferredReconfiguration = session.getActivationTriggers().deferredReconfigurations().stream()
                .map(DeferredReconfiguration::clusterId)
                .collect(Collectors.toSet());
        
        if (clustersWithDeferredReconfiguration.isEmpty()) {
            return;
        }

        // Get the model and mark clusters for deferred reconfiguration
        Model model = applicationVersions.get(session.getVespaVersion())
                .map(Application::getModel)
                .orElseThrow(() -> new IllegalStateException(
                        "Cannot apply deferred reconfiguration: no model available for session " + session.getSessionId()));
        model.markClustersForDeferredReconfiguration(clustersWithDeferredReconfiguration);

        clustersWithDeferredReconfiguration.forEach(clusterName ->
                log.log(Level.INFO, session.logPre() +
                        Text.format("Deferring reconfiguration of cluster '%s' until restart is completed", clusterName)));
    }
    
    private void loadSessionIfActive(RemoteSession session) {
        for (ApplicationId applicationId : tenantApplications.activeApplications()) {
            Optional<Long> activeSession = tenantApplications.activeSessionOf(applicationId);
            if (activeSession.isPresent() && activeSession.get() == session.getSessionId()) {
                log.log(Level.FINE, () -> "Found active application for session " + session.getSessionId() + " , loading it");
                ApplicationVersions applicationVersions = ensureApplicationLoaded(session);
                applyDeferredReconfigurationOfClusters(session, applicationVersions);
                tenantApplications.activateApplication(applicationVersions, session.getSessionId());
                log.log(Level.INFO, session.logPre() + "Application activated successfully: " + applicationId + " (generation " + session.getSessionId() + ")");
                return;
            }
        }
    }

    void prepareRemoteSession(long sessionId) {
        // Might need to create local session first
        createLocalSessionFromDistributedApplicationPackage(sessionId);
        RemoteSession session = remoteSessionCache.get(sessionId);
        if (session == null) return;

        SessionZooKeeperClient sessionZooKeeperClient = createSessionZooKeeperClient(sessionId);
        CompletionWaiter waiter = sessionZooKeeperClient.getPrepareWaiter();
        ensureApplicationLoaded(session);
        notifyCompletion(waiter);
    }