diff --git a/runtime/service/src/main/java/org/apache/polaris/service/config/Bootstrapper.java b/runtime/service/src/main/java/org/apache/polaris/service/config/Bootstrapper.java new file mode 100644 index 0000000000..e5135c38dd --- /dev/null +++ b/runtime/service/src/main/java/org/apache/polaris/service/config/Bootstrapper.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.polaris.service.config; + +import io.smallrye.common.annotation.Identifier; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.control.ActivateRequestContext; +import jakarta.inject.Inject; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import org.apache.polaris.core.persistence.MetaStoreManagerFactory; +import org.apache.polaris.core.persistence.bootstrap.RootCredentialsSet; +import org.apache.polaris.core.persistence.dao.entity.PrincipalSecretsResult; +import org.apache.polaris.service.context.catalog.RealmContextHolder; + +/** Utility class for running per-realm bootstrap tasks each in a fresh Request Context. */ +@ApplicationScoped +class Bootstrapper { + private final ExecutorService executor; + private final RealmContextHolder realmContextHolder; + private final MetaStoreManagerFactory factory; + + @Inject + Bootstrapper( + // Note: this executor is expected to NOT propagate CDI contexts to tasks. + @Identifier("task-executor") ExecutorService executor, + RealmContextHolder realmContextHolder, + MetaStoreManagerFactory factory) { + this.executor = executor; + this.realmContextHolder = realmContextHolder; + this.factory = factory; + } + + Map bootstrapRealms( + Iterable realmIds, RootCredentialsSet rootCredentialsSet) { + HashMap result = new HashMap<>(); + for (String realmId : realmIds) { + Task t = new Task(realmContextHolder, realmId, rootCredentialsSet, factory); + try { + // Submit an async task per realm to ensure it runs in a fresh RequestContext. + // Note: simultaneous bootstrap of multiple realms is an edge case - no need + // to optimize for fast concurrent completion. + result.putAll(executor.submit(t).get(2, TimeUnit.MINUTES)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + return result; + } + + private record Task( + RealmContextHolder realmContextHolder, + String realmId, + RootCredentialsSet rootCredentialsSet, + MetaStoreManagerFactory factory) + implements Callable> { + + @Override + @ActivateRequestContext + public Map call() { + // Note: each call to this method runs in a new CDI request context. + // Make the realm ID effective in the current request context. + realmContextHolder.set(() -> realmId); + return factory.bootstrapRealms(Collections.singleton(realmId), rootCredentialsSet); + } + } +} diff --git a/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java b/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java index 0042ac84e2..840b3fb80a 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java @@ -271,7 +271,7 @@ public StsClientsPool stsClientsPool( */ public void maybeBootstrap( @Observes Startup event, - MetaStoreManagerFactory factory, + Bootstrapper bootstrapper, PersistenceConfiguration config, RealmContextConfiguration realmContextConfiguration) { var rootCredentialsSet = RootCredentialsSet.fromEnvironment(); @@ -285,7 +285,7 @@ public void maybeBootstrap( RootCredentialsSet.ENVIRONMENT_VARIABLE, RootCredentialsSet.SYSTEM_PROPERTY); - var result = factory.bootstrapRealms(realmIds, rootCredentialsSet); + var result = bootstrapper.bootstrapRealms(realmIds, rootCredentialsSet); result.forEach( (realm, secrets) -> {