diff --git a/bom/build.gradle.kts b/bom/build.gradle.kts index 213f31ed76..d5e5afc75f 100644 --- a/bom/build.gradle.kts +++ b/bom/build.gradle.kts @@ -63,6 +63,7 @@ dependencies { api(project(":polaris-persistence-nosql-impl")) api(project(":polaris-persistence-nosql-benchmark")) api(project(":polaris-persistence-nosql-metastore")) + api(project(":polaris-persistence-nosql-metastore-maintenance")) api(project(":polaris-persistence-nosql-metastore-types")) api(project(":polaris-persistence-nosql-correctness")) api(project(":polaris-persistence-nosql-cdi-common")) diff --git a/gradle/projects.main.properties b/gradle/projects.main.properties index 85ff05b26d..fadbbadea5 100644 --- a/gradle/projects.main.properties +++ b/gradle/projects.main.properties @@ -78,6 +78,7 @@ polaris-persistence-nosql-api=persistence/nosql/persistence/api polaris-persistence-nosql-impl=persistence/nosql/persistence/impl polaris-persistence-nosql-benchmark=persistence/nosql/persistence/benchmark polaris-persistence-nosql-metastore=persistence/nosql/persistence/metastore +polaris-persistence-nosql-metastore-maintenance=persistence/nosql/persistence/metastore-maintenance polaris-persistence-nosql-metastore-types=persistence/nosql/persistence/metastore-types polaris-persistence-nosql-correctness=persistence/nosql/persistence/correctness polaris-persistence-nosql-cdi-common=persistence/nosql/persistence/cdi/common diff --git a/persistence/nosql/persistence/metastore-maintenance/build.gradle.kts b/persistence/nosql/persistence/metastore-maintenance/build.gradle.kts new file mode 100644 index 0000000000..5ba625328c --- /dev/null +++ b/persistence/nosql/persistence/metastore-maintenance/build.gradle.kts @@ -0,0 +1,80 @@ +/* + * 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. + */ + +plugins { + id("org.kordamp.gradle.jandex") + id("polaris-server") +} + +description = "Polaris NoSQL persistence core types" + +dependencies { + implementation(project(":polaris-core")) + implementation(project(":polaris-persistence-nosql-api")) + implementation(project(":polaris-idgen-api")) + implementation(project(":polaris-persistence-nosql-maintenance-api")) + implementation(project(":polaris-persistence-nosql-maintenance-spi")) + implementation(project(":polaris-persistence-nosql-maintenance-cel")) + implementation(project(":polaris-persistence-nosql-metastore-types")) + + implementation(libs.guava) + implementation(libs.slf4j.api) + + implementation(platform(libs.jackson.bom)) + implementation("com.fasterxml.jackson.core:jackson-annotations") + implementation("com.fasterxml.jackson.core:jackson-core") + implementation("com.fasterxml.jackson.core:jackson-databind") + implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-smile") + + compileOnly(libs.smallrye.config.core) + compileOnly(platform(libs.quarkus.bom)) + compileOnly("io.quarkus:quarkus-core") + + compileOnly(project(":polaris-immutables")) + annotationProcessor(project(":polaris-immutables", configuration = "processor")) + + compileOnly(libs.jakarta.annotation.api) + compileOnly(libs.jakarta.validation.api) + compileOnly(libs.jakarta.inject.api) + compileOnly(libs.jakarta.enterprise.cdi.api) + + testFixturesImplementation(project(":polaris-core")) + testFixturesImplementation(project(":polaris-persistence-nosql-metastore")) + testFixturesImplementation(testFixtures(project(":polaris-persistence-nosql-metastore"))) + testRuntimeOnly(project(":polaris-persistence-nosql-authz-impl")) + testRuntimeOnly(project(":polaris-persistence-nosql-authz-store-nosql")) + + testFixturesImplementation(libs.jakarta.annotation.api) + testFixturesImplementation(libs.jakarta.validation.api) + testFixturesImplementation(libs.jakarta.enterprise.cdi.api) + testImplementation(libs.smallrye.common.annotation) + + testFixturesImplementation(platform(libs.jackson.bom)) + testFixturesImplementation("com.fasterxml.jackson.core:jackson-annotations") + testFixturesImplementation("com.fasterxml.jackson.core:jackson-core") + + testImplementation(project(":polaris-idgen-mocks")) + testImplementation(testFixtures(project(":polaris-persistence-nosql-maintenance-impl"))) + testImplementation(project(":polaris-persistence-nosql-impl")) + + testRuntimeOnly(testFixtures(project(":polaris-persistence-nosql-cdi-weld"))) + testImplementation(libs.weld.se.core) + testImplementation(libs.weld.junit5) + testRuntimeOnly(libs.smallrye.jandex) +} diff --git a/persistence/nosql/persistence/metastore-maintenance/src/main/java/org/apache/polaris/persistence/nosql/metastore/maintenance/CatalogRetainedIdentifier.java b/persistence/nosql/persistence/metastore-maintenance/src/main/java/org/apache/polaris/persistence/nosql/metastore/maintenance/CatalogRetainedIdentifier.java new file mode 100644 index 0000000000..cb5524101b --- /dev/null +++ b/persistence/nosql/persistence/metastore-maintenance/src/main/java/org/apache/polaris/persistence/nosql/metastore/maintenance/CatalogRetainedIdentifier.java @@ -0,0 +1,334 @@ +/* + * 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.persistence.nosql.metastore.maintenance; + +import static java.lang.String.format; +import static org.apache.polaris.persistence.nosql.api.obj.ObjRef.OBJ_REF_SERIALIZER; +import static org.apache.polaris.persistence.nosql.coretypes.catalog.CatalogRolesObj.CATALOG_ROLES_REF_NAME_PATTERN; +import static org.apache.polaris.persistence.nosql.coretypes.catalog.CatalogStateObj.CATALOG_STATE_REF_NAME_PATTERN; +import static org.apache.polaris.persistence.nosql.coretypes.catalog.CatalogsObj.CATALOGS_REF_NAME; +import static org.apache.polaris.persistence.nosql.coretypes.principals.PrincipalRolesObj.PRINCIPAL_ROLES_REF_NAME; +import static org.apache.polaris.persistence.nosql.coretypes.principals.PrincipalsObj.PRINCIPALS_REF_NAME; +import static org.apache.polaris.persistence.nosql.coretypes.realm.ImmediateTasksObj.IMMEDIATE_TASKS_REF_NAME; +import static org.apache.polaris.persistence.nosql.coretypes.realm.PolicyMapping.POLICY_MAPPING_SERIALIZER; +import static org.apache.polaris.persistence.nosql.coretypes.realm.PolicyMappingsObj.POLICY_MAPPINGS_REF_NAME; +import static org.apache.polaris.persistence.nosql.coretypes.realm.RealmGrantsObj.REALM_GRANTS_REF_NAME; +import static org.apache.polaris.persistence.nosql.coretypes.realm.RootObj.ROOT_REF_NAME; +import static org.apache.polaris.persistence.nosql.metastore.maintenance.CatalogsMaintenanceConfig.DEFAULT_CATALOGS_HISTORY_RETAIN; +import static org.apache.polaris.persistence.nosql.metastore.maintenance.CatalogsMaintenanceConfig.DEFAULT_CATALOG_POLICIES_RETAIN; +import static org.apache.polaris.persistence.nosql.metastore.maintenance.CatalogsMaintenanceConfig.DEFAULT_CATALOG_ROLES_RETAIN; +import static org.apache.polaris.persistence.nosql.metastore.maintenance.CatalogsMaintenanceConfig.DEFAULT_CATALOG_STATE_RETAIN; +import static org.apache.polaris.persistence.nosql.metastore.maintenance.CatalogsMaintenanceConfig.DEFAULT_GRANTS_RETAIN; +import static org.apache.polaris.persistence.nosql.metastore.maintenance.CatalogsMaintenanceConfig.DEFAULT_IMMEDIATE_TASKS_RETAIN; +import static org.apache.polaris.persistence.nosql.metastore.maintenance.CatalogsMaintenanceConfig.DEFAULT_PRINCIPALS_RETAIN; +import static org.apache.polaris.persistence.nosql.metastore.maintenance.CatalogsMaintenanceConfig.DEFAULT_PRINCIPAL_ROLES_RETAIN; + +import jakarta.annotation.Nonnull; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; +import java.util.function.Function; +import org.apache.polaris.ids.api.MonotonicClock; +import org.apache.polaris.maintenance.cel.CelReferenceContinuePredicate; +import org.apache.polaris.persistence.nosql.api.exceptions.ReferenceNotFoundException; +import org.apache.polaris.persistence.nosql.api.index.IndexContainer; +import org.apache.polaris.persistence.nosql.api.index.IndexKey; +import org.apache.polaris.persistence.nosql.api.obj.BaseCommitObj; +import org.apache.polaris.persistence.nosql.api.obj.ObjRef; +import org.apache.polaris.persistence.nosql.coretypes.ContainerObj; +import org.apache.polaris.persistence.nosql.coretypes.catalog.CatalogObj; +import org.apache.polaris.persistence.nosql.coretypes.catalog.CatalogRolesObj; +import org.apache.polaris.persistence.nosql.coretypes.catalog.CatalogStateObj; +import org.apache.polaris.persistence.nosql.coretypes.catalog.CatalogsObj; +import org.apache.polaris.persistence.nosql.coretypes.principals.PrincipalRolesObj; +import org.apache.polaris.persistence.nosql.coretypes.principals.PrincipalsObj; +import org.apache.polaris.persistence.nosql.coretypes.realm.ImmediateTasksObj; +import org.apache.polaris.persistence.nosql.coretypes.realm.PolicyMappingsObj; +import org.apache.polaris.persistence.nosql.coretypes.realm.RealmGrantsObj; +import org.apache.polaris.persistence.nosql.coretypes.realm.RootObj; +import org.apache.polaris.persistence.nosql.maintenance.spi.PerRealmRetainedIdentifier; +import org.apache.polaris.persistence.nosql.maintenance.spi.RetainedCollector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ApplicationScoped +class CatalogRetainedIdentifier implements PerRealmRetainedIdentifier { + + private static final Logger LOGGER = LoggerFactory.getLogger(CatalogRetainedIdentifier.class); + + private final CatalogsMaintenanceConfig catalogsMaintenanceConfig; + private final MonotonicClock monotonicClock; + + @SuppressWarnings("CdiInjectionPointsInspection") + @Inject + CatalogRetainedIdentifier( + CatalogsMaintenanceConfig catalogsMaintenanceConfig, MonotonicClock monotonicClock) { + this.catalogsMaintenanceConfig = catalogsMaintenanceConfig; + this.monotonicClock = monotonicClock; + } + + @Override + public String name() { + return "Catalog data"; + } + + @Override + public boolean identifyRetained(@Nonnull RetainedCollector collector) { + + // Note: References & objects retrieved via the `Persistence` instance returned by the + // `RetainedCollector` are automatically retained (no need to call collector.retain*() + // explicitly). + var persistence = collector.realmPersistence(); + + // per realm + + // The root object is "special" (there's only one) + LOGGER.info("Identifying root object..."); + ignoreReferenceNotFound(() -> persistence.fetchReferenceHead(ROOT_REF_NAME, RootObj.class)); + + perRealmContainer( + "principals", + PRINCIPALS_REF_NAME, + catalogsMaintenanceConfig.principalsRetain().orElse(DEFAULT_PRINCIPALS_RETAIN), + PrincipalsObj.class, + collector); + + perRealmContainer( + "principal roles", + PRINCIPAL_ROLES_REF_NAME, + catalogsMaintenanceConfig.principalRolesRetain().orElse(DEFAULT_PRINCIPAL_ROLES_RETAIN), + PrincipalRolesObj.class, + collector); + + perRealm( + "grants", + REALM_GRANTS_REF_NAME, + catalogsMaintenanceConfig.grantsRetain().orElse(DEFAULT_GRANTS_RETAIN), + RealmGrantsObj.class, + RealmGrantsObj::acls, + collector); + + perRealmContainer( + "immediate tasks", + IMMEDIATE_TASKS_REF_NAME, + catalogsMaintenanceConfig.immediateTasksRetain().orElse(DEFAULT_IMMEDIATE_TASKS_RETAIN), + ImmediateTasksObj.class, + collector); + + LOGGER.info("Identifying policy mappings..."); + ignoreReferenceNotFound( + () -> { + var policyMappingsContinue = + new CelReferenceContinuePredicate( + POLICY_MAPPINGS_REF_NAME, + persistence, + catalogsMaintenanceConfig + .catalogPoliciesRetain() + .orElse(DEFAULT_CATALOG_POLICIES_RETAIN)); + // PolicyMappings are stored _INLINE_ + collector.refRetain( + POLICY_MAPPINGS_REF_NAME, + PolicyMappingsObj.class, + policyMappingsContinue, + policyMappingsObj -> + policyMappingsObj + .policyMappings() + .indexForRead(collector.realmPersistence(), POLICY_MAPPING_SERIALIZER) + .forEach( + e -> { + var policyMapping = e.getValue(); + policyMapping.externalMapping().ifPresent(collector::retainObject); + })); + }); + + // per catalog + + LOGGER.info("Identifying catalogs..."); + ignoreReferenceNotFound( + () -> { + var catalogsHistoryContinue = + new CelReferenceContinuePredicate( + CATALOGS_REF_NAME, + persistence, + catalogsMaintenanceConfig + .catalogsHistoryRetain() + .orElse(DEFAULT_CATALOGS_HISTORY_RETAIN)); + var currentCatalogs = new ConcurrentHashMap(); + collector.refRetain( + CATALOGS_REF_NAME, + CatalogsObj.class, + catalogsHistoryContinue, + catalogs -> { + var allCatalogsIndex = + catalogs.nameToObjRef().indexForRead(persistence, OBJ_REF_SERIALIZER); + for (var entry : allCatalogsIndex) { + var catalogKey = entry.getKey(); + var catalogObjRef = entry.getValue(); + currentCatalogs.putIfAbsent(catalogKey, catalogObjRef); + } + collector.indexRetain(catalogs.stableIdToName()); + }); + + var catalogObjs = + persistence.fetchMany( + CatalogObj.class, currentCatalogs.values().toArray(ObjRef[]::new)); + for (var catalogObj : catalogObjs) { + if (catalogObj == null) { + // just in case... + continue; + } + + perCatalog( + "catalog roles", + CATALOG_ROLES_REF_NAME_PATTERN, + catalogObj, + catalogsMaintenanceConfig.catalogRolesRetain().orElse(DEFAULT_CATALOG_ROLES_RETAIN), + CatalogRolesObj.class, + CatalogRolesObj::nameToObjRef, + collector, + catalogRolesObj -> collector.indexRetain(catalogRolesObj.stableIdToName())); + + LOGGER.info( + "Identifying catalog state for catalog '{}' ({})...", + catalogObj.name(), + catalogObj.stableId()); + ignoreReferenceNotFound( + () -> { + var catalogStateRefName = + format(CATALOG_STATE_REF_NAME_PATTERN, catalogObj.stableId()); + var catalogStateContinue = + new CelReferenceContinuePredicate( + catalogStateRefName, + persistence, + catalogsMaintenanceConfig + .catalogStateRetain() + .orElse(DEFAULT_CATALOG_STATE_RETAIN)); + collector.refRetainIndexToSingleObj( + catalogStateRefName, + CatalogStateObj.class, + catalogStateContinue, + CatalogStateObj::nameToObjRef, + new RetainedCollector.ProgressListener<>() { + public static final long PROGRESS_LOG_INTERVAL_MS = 2_000L; + private long commit; + private long nextLog = + monotonicClock.currentTimeMillis() + PROGRESS_LOG_INTERVAL_MS; + + @Override + public void onCommit(CatalogStateObj catalogStateObj, long commit) { + collector.indexRetain(catalogStateObj.stableIdToName()); + catalogStateObj.locations().ifPresent(collector::indexRetain); + catalogStateObj.changes().ifPresent(collector::indexRetain); + this.commit = commit; + } + + @Override + public void onIndexEntry(long inCommit, long total) { + var now = monotonicClock.currentTimeMillis(); + if (now >= nextLog) { + LOGGER.info( + "... {} total index entries processed so far, at commit {}", + total, + commit); + nextLog = now + PROGRESS_LOG_INTERVAL_MS; + } + } + }, + x -> {}); + }); + } + }); + + return true; + } + + @SuppressWarnings({"LoggingSimilarMessage", "SameParameterValue"}) + private void perRealm( + String what, + String refName, + String celRetainExpr, + Class objClazz, + Function> indexContainerFunction, + RetainedCollector collector) { + + LOGGER.info("Identifying {}...", what); + ignoreReferenceNotFound( + () -> { + var persistence = collector.realmPersistence(); + var historyContinue = + new CelReferenceContinuePredicate(refName, persistence, celRetainExpr); + collector.refRetainIndexToSingleObj( + refName, objClazz, historyContinue, indexContainerFunction); + }); + } + + @SuppressWarnings("LoggingSimilarMessage") + private void perRealmContainer( + String what, + String refName, + String celRetainExpr, + Class objClazz, + RetainedCollector collector) { + + LOGGER.info("Identifying {}...", what); + ignoreReferenceNotFound( + () -> { + var persistence = collector.realmPersistence(); + var historyContinue = + new CelReferenceContinuePredicate(refName, persistence, celRetainExpr); + collector.refRetainIndexToSingleObj( + refName, + objClazz, + historyContinue, + ContainerObj::nameToObjRef, + containerObj -> collector.indexRetain(containerObj.stableIdToName())); + }); + } + + private void perCatalog( + String what, + String refNamePattern, + CatalogObj catalogObj, + String celRetainExpr, + Class objClazz, + Function> indexContainerFunction, + RetainedCollector collector, + Consumer objConsumer) { + LOGGER.info( + "Identifying {} for catalog '{}' ({})...", what, catalogObj.name(), catalogObj.stableId()); + ignoreReferenceNotFound( + () -> { + var persistence = collector.realmPersistence(); + var refName = format(refNamePattern, catalogObj.stableId()); + var historyContinue = + new CelReferenceContinuePredicate(refName, persistence, celRetainExpr); + collector.refRetainIndexToSingleObj( + refName, objClazz, historyContinue, indexContainerFunction, objConsumer); + }); + } + + void ignoreReferenceNotFound(Runnable runnable) { + try { + runnable.run(); + } catch (ReferenceNotFoundException e) { + LOGGER.debug("Reference not found: {}", e.getMessage()); + } + } +} diff --git a/persistence/nosql/persistence/metastore-maintenance/src/main/java/org/apache/polaris/persistence/nosql/metastore/maintenance/CatalogsMaintenanceConfig.java b/persistence/nosql/persistence/metastore-maintenance/src/main/java/org/apache/polaris/persistence/nosql/metastore/maintenance/CatalogsMaintenanceConfig.java new file mode 100644 index 0000000000..a03862e664 --- /dev/null +++ b/persistence/nosql/persistence/metastore-maintenance/src/main/java/org/apache/polaris/persistence/nosql/metastore/maintenance/CatalogsMaintenanceConfig.java @@ -0,0 +1,103 @@ +/* + * 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.persistence.nosql.metastore.maintenance; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithDefault; +import java.util.Optional; +import org.apache.polaris.immutables.PolarisImmutable; + +/** + * Polaris stores a history of changes per kind of object (principals, principal roles, grants, + * immediate tasks, catalog roles and catalog state). + * + *

The rules are defined using a CEL + * script. The default rules for all kinds of objects are to retain the history for 3 days, for + * the catalog state for 30 days. + * + *

The scripts have access to the following declared values: + * + *

    + *
  • {@code ref} (string) name of the reference + *
  • {@code commits} (64-bit int) number of the currently processed commit, starting at {@code + * 1} + *
  • {@code ageDays} (64-bit int) age of currently processed commit in days + *
  • {@code ageHours} (64-bit int) age of currently processed commit in hours + *
  • {@code ageMinutes} (64-bit int) age of currently processed commit in minutes + *
+ * + *

Scripts must return a {@code boolean} yielding whether the commit shall be retained. + * Note that maintenance-service implementations can keep the first not-to-be-retained commit. + * + *

Example scripts + * + *

    + *
  • {@code ageDays < 30 || commits <= 10} retains the reference history with at least 10 + * commits and commits that are younger than 30 days + *
  • {@code true} retains the whole reference history + *
  • {@code false} retains the most recent commit + *
+ */ +@ConfigMapping(prefix = "polaris.persistence.maintenance.catalog") +@JsonSerialize(as = ImmutableBuildableCatalogsMaintenanceConfig.class) +@JsonDeserialize(as = ImmutableBuildableCatalogsMaintenanceConfig.class) +public interface CatalogsMaintenanceConfig { + + String DEFAULT_PRINCIPALS_RETAIN = "false"; + String DEFAULT_PRINCIPAL_ROLES_RETAIN = "false"; + String DEFAULT_GRANTS_RETAIN = "false"; + String DEFAULT_IMMEDIATE_TASKS_RETAIN = "false"; + String DEFAULT_CATALOGS_HISTORY_RETAIN = "false"; + String DEFAULT_CATALOG_ROLES_RETAIN = "false"; + String DEFAULT_CATALOG_POLICIES_RETAIN = "ageDays < 30 || commits <= 1"; + String DEFAULT_CATALOG_STATE_RETAIN = "ageDays < 30 || commits <= 1"; + + @WithDefault(DEFAULT_PRINCIPALS_RETAIN) + Optional principalsRetain(); + + @WithDefault(DEFAULT_PRINCIPAL_ROLES_RETAIN) + Optional principalRolesRetain(); + + @WithDefault(DEFAULT_GRANTS_RETAIN) + Optional grantsRetain(); + + @WithDefault(DEFAULT_IMMEDIATE_TASKS_RETAIN) + Optional immediateTasksRetain(); + + @WithDefault(DEFAULT_CATALOGS_HISTORY_RETAIN) + Optional catalogsHistoryRetain(); + + @WithDefault(DEFAULT_CATALOG_ROLES_RETAIN) + Optional catalogRolesRetain(); + + @WithDefault(DEFAULT_CATALOG_POLICIES_RETAIN) + Optional catalogPoliciesRetain(); + + @WithDefault(DEFAULT_CATALOG_STATE_RETAIN) + Optional catalogStateRetain(); + + @PolarisImmutable + interface BuildableCatalogsMaintenanceConfig extends CatalogsMaintenanceConfig { + static ImmutableBuildableCatalogsMaintenanceConfig.Builder builder() { + return ImmutableBuildableCatalogsMaintenanceConfig.builder(); + } + } +} diff --git a/persistence/nosql/persistence/metastore-maintenance/src/main/resources/META-INF/beans.xml b/persistence/nosql/persistence/metastore-maintenance/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000..a297f1aa53 --- /dev/null +++ b/persistence/nosql/persistence/metastore-maintenance/src/main/resources/META-INF/beans.xml @@ -0,0 +1,24 @@ + + + + + \ No newline at end of file diff --git a/persistence/nosql/persistence/metastore-maintenance/src/test/java/org/apache/polaris/persistence/nosql/metastore/maintenance/TestCatalogMaintenance.java b/persistence/nosql/persistence/metastore-maintenance/src/test/java/org/apache/polaris/persistence/nosql/metastore/maintenance/TestCatalogMaintenance.java new file mode 100644 index 0000000000..d5f311054a --- /dev/null +++ b/persistence/nosql/persistence/metastore-maintenance/src/test/java/org/apache/polaris/persistence/nosql/metastore/maintenance/TestCatalogMaintenance.java @@ -0,0 +1,479 @@ +/* + * 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.persistence.nosql.metastore.maintenance; + +import static java.lang.String.format; +import static java.util.function.Function.identity; +import static org.apache.polaris.core.entity.PolarisEntitySubType.ICEBERG_TABLE; +import static org.apache.polaris.core.entity.PolarisEntityType.CATALOG_ROLE; +import static org.apache.polaris.core.entity.PolarisEntityType.NAMESPACE; +import static org.apache.polaris.core.entity.PolarisEntityType.PRINCIPAL; +import static org.apache.polaris.core.entity.PolarisEntityType.PRINCIPAL_ROLE; +import static org.apache.polaris.core.entity.PolarisEntityType.TABLE_LIKE; +import static org.apache.polaris.persistence.nosql.api.index.IndexContainer.newUpdatableIndex; +import static org.apache.polaris.persistence.nosql.api.index.IndexKey.INDEX_KEY_SERIALIZER; +import static org.apache.polaris.persistence.nosql.api.obj.ObjRef.OBJ_REF_SERIALIZER; +import static org.apache.polaris.persistence.nosql.api.obj.ObjRef.objRef; +import static org.apache.polaris.persistence.nosql.coretypes.catalog.CatalogStateObj.CATALOG_STATE_REF_NAME_PATTERN; +import static org.apache.polaris.persistence.nosql.coretypes.catalog.CatalogsObj.CATALOGS_REF_NAME; +import static org.apache.polaris.persistence.nosql.coretypes.catalog.EntityIdSet.ENTITY_ID_SET_SERIALIZER; +import static org.apache.polaris.persistence.nosql.coretypes.changes.Change.CHANGE_SERIALIZER; +import static org.apache.polaris.persistence.nosql.coretypes.realm.ImmediateTasksObj.IMMEDIATE_TASKS_REF_NAME; +import static org.apache.polaris.persistence.nosql.coretypes.realm.PolicyMapping.POLICY_MAPPING_SERIALIZER; +import static org.apache.polaris.persistence.nosql.coretypes.realm.PolicyMappingsObj.POLICY_MAPPINGS_REF_NAME; +import static org.apache.polaris.persistence.nosql.coretypes.refs.References.realmReferenceNames; +import static org.apache.polaris.persistence.nosql.maintenance.impl.MutableMaintenanceConfig.GRACE_TIME; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.fail; + +import io.smallrye.common.annotation.Identifier; +import jakarta.inject.Inject; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.polaris.core.PolarisCallContext; +import org.apache.polaris.core.config.PolarisConfigurationStore; +import org.apache.polaris.core.context.RealmContext; +import org.apache.polaris.core.entity.CatalogEntity; +import org.apache.polaris.core.entity.PolarisBaseEntity; +import org.apache.polaris.core.entity.PolarisEntity; +import org.apache.polaris.core.entity.PrincipalEntity; +import org.apache.polaris.core.persistence.MetaStoreManagerFactory; +import org.apache.polaris.core.persistence.PolarisMetaStoreManager; +import org.apache.polaris.core.persistence.bootstrap.RootCredentialsSet; +import org.apache.polaris.ids.mocks.MutableMonotonicClock; +import org.apache.polaris.persistence.nosql.api.Persistence; +import org.apache.polaris.persistence.nosql.api.RealmPersistenceFactory; +import org.apache.polaris.persistence.nosql.api.cache.CacheBackend; +import org.apache.polaris.persistence.nosql.api.ref.Reference; +import org.apache.polaris.persistence.nosql.coretypes.catalog.CatalogStateObj; +import org.apache.polaris.persistence.nosql.coretypes.catalog.CatalogsObj; +import org.apache.polaris.persistence.nosql.coretypes.realm.ImmediateTasksObj; +import org.apache.polaris.persistence.nosql.coretypes.realm.PolicyMappingsObj; +import org.apache.polaris.persistence.nosql.coretypes.refs.References; +import org.apache.polaris.persistence.nosql.maintenance.api.MaintenanceConfig; +import org.apache.polaris.persistence.nosql.maintenance.api.MaintenanceRunInformation; +import org.apache.polaris.persistence.nosql.maintenance.api.MaintenanceRunSpec; +import org.apache.polaris.persistence.nosql.maintenance.api.MaintenanceService; +import org.apache.polaris.persistence.nosql.maintenance.impl.MutableMaintenanceConfig; +import org.assertj.core.api.SoftAssertions; +import org.assertj.core.api.junit.jupiter.InjectSoftAssertions; +import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension; +import org.jboss.weld.junit5.EnableWeld; +import org.jboss.weld.junit5.WeldInitiator; +import org.jboss.weld.junit5.WeldSetup; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(SoftAssertionsExtension.class) +@EnableWeld +@SuppressWarnings("CdiInjectionPointsInspection") +public class TestCatalogMaintenance { + @InjectSoftAssertions protected SoftAssertions soft; + + @WeldSetup WeldInitiator weld = WeldInitiator.performDefaultDiscovery(); + + String realmId; + RealmContext realmContext; + + @Inject MaintenanceService maintenance; + @Inject MutableMonotonicClock mutableMonotonicClock; + + @Inject PolarisConfigurationStore configurationStore; + @Inject CacheBackend cacheBackend; + @Inject RealmPersistenceFactory realmPersistenceFactory; + + @Inject + @Identifier("nosql") + MetaStoreManagerFactory metaStoreManagerFactory; + + @BeforeEach + protected void setup() { + // Set the "grace time" to 0 so tests can write refs+objs and get those purged + MutableMaintenanceConfig.setCurrent( + MaintenanceConfig.builder().createdAtGraceTime(GRACE_TIME).build()); + + realmId = UUID.randomUUID().toString(); + realmContext = () -> realmId; + + // tell maintenance to only retain the latest commit + MutableCatalogsMaintenanceConfig.setCurrent( + CatalogsMaintenanceConfig.BuildableCatalogsMaintenanceConfig.builder() + .catalogRolesRetain("false") + .catalogsHistoryRetain("false") + .catalogPoliciesRetain("false") + .catalogStateRetain("false") + .grantsRetain("false") + .principalRolesRetain("false") + .principalsRetain("false") + .immediateTasksRetain("false") + .build()); + } + + @Test + public void catalogMaintenance() { + + metaStoreManagerFactory.bootstrapRealms(List.of(realmId), RootCredentialsSet.fromEnvironment()); + + var manager = metaStoreManagerFactory.getOrCreateMetaStoreManager(realmContext); + var session = metaStoreManagerFactory.getOrCreateSession(realmContext); + var callCtx = new PolarisCallContext(realmContext, session, configurationStore); + + var persistence = + realmPersistenceFactory.newBuilder().realmId(realmId).skipDecorators().build(); + + // Some references are "empty", need to populate those to be able to bump the references "back" + // to the "real" state below. + mandatoryRealmObjsForTestImpl(persistence); + + var initialReferenceHeads = new HashMap(); + realmReferenceNames().forEach(n -> initialReferenceHeads.put(n, persistence.fetchReference(n))); + + var principalRole = createPrincipalRole(manager, callCtx, persistence); + var principal = createPrincipal(manager, callCtx, persistence); + + var catalog = createCatalog(manager, callCtx, persistence); + var catalogBase = new PolarisBaseEntity.Builder(catalog).build(); + var catalogId = catalog.getId(); + + // Some references are "empty", need to populate those to be able to bump the references "back" + // to the "real" state below. + mandatoryCatalogObjsForTestImpl(persistence, catalogId); + + References.catalogReferenceNames(catalogId) + .forEach(n -> initialReferenceHeads.put(n, persistence.fetchReference(n))); + + var catalogRole = createCatalogRole(manager, callCtx, catalog, persistence); + var namespace = createNamespace(manager, callCtx, catalog, persistence); + var table = createTable(manager, callCtx, catalog, namespace, persistence); + + var entities = List.of(principalRole, principal, catalogBase, catalogRole, namespace, table); + + checkEntities("sanity", entities); + + // Ensure that "maintenance does not purge objects created before references are bumped". + // In other words: maintenance runs during a commit operation - those objects are protected by + // the "grace period". + + // Update the references to the initial state, so the created objects become "unreachable" - the + // state before the commits' reference bumps. + var currentReferenceHeads = + Stream.concat( + realmReferenceNames().stream(), + References.catalogReferenceNames(catalogId).stream()) + .toList() + .stream() + .collect(Collectors.toMap(identity(), persistence::fetchReference)); + currentReferenceHeads.forEach( + (n, r) -> { + var initial = initialReferenceHeads.get(r.name()).pointer().orElseThrow(); + if (!initial.equals(r.pointer().orElseThrow())) { + assertThat(persistence.updateReferencePointer(r, initial)).describedAs(n).isPresent(); + } + }); + + var runInformation = + maintenance.performMaintenance( + MaintenanceRunSpec.builder() + .includeSystemRealm(false) + .realmsToProcess(Set.of(realmId)) + .build()); + soft.assertThat(runInformation) + .describedAs("%s", runInformation) + .extracting( + MaintenanceRunInformation::success, + MaintenanceRunInformation::purgedRealms, + ri -> ri.referenceStats().map(s -> s.purged().orElse(-1L)), + ri -> ri.objStats().map(s -> s.purged().orElse(-1L))) + .containsExactly( + true, + OptionalInt.of(0), + Optional.of(0L), + // Within grace-time -> nothing must be purged + Optional.of(0L)); + + // Revert the references to the "real" state + initialReferenceHeads.forEach( + (n, r) -> { + var real = currentReferenceHeads.get(r.name()).pointer().orElseThrow(); + if (!real.equals(r.pointer().orElseThrow())) { + assertThat(persistence.updateReferencePointer(r, real)).describedAs(n).isPresent(); + } + }); + + checkEntities("real state within grace", entities); + + // Perform a maintenance run _after_ the references have been bumped (successful commits). + + mutableMonotonicClock.advanceBoth(GRACE_TIME); + runInformation = + maintenance.performMaintenance( + MaintenanceRunSpec.builder() + .includeSystemRealm(false) + .realmsToProcess(Set.of(realmId)) + .build()); + soft.assertThat(runInformation) + .describedAs("%s", runInformation) + .extracting( + MaintenanceRunInformation::success, + MaintenanceRunInformation::purgedRealms, + ri -> ri.referenceStats().map(s -> s.purged().orElse(-1L)), + ri -> ri.objStats().map(s -> s.purged().orElse(-1L))) + .containsExactly( + true, + OptionalInt.of(0), + Optional.of(0L), + // 8 stale objects: + // - 1 namespace (catalog state) + // - 1 table (catalog state) + // - 1 grants (realm setup) - including 1 ACLs + // - 1 principal + // - 1 principal role + // - 1 catalog role + // - 1 catalog + Optional.of(8L)); + + checkEntities("real state after grace", entities); + } + + private static PolarisBaseEntity createTable( + PolarisMetaStoreManager manager, + PolarisCallContext callCtx, + PolarisBaseEntity catalog, + PolarisBaseEntity namespace, + Persistence persistence) { + var tableResult = + manager.createEntityIfNotExists( + callCtx, + List.of(catalog, namespace), + new PolarisEntity.Builder() + .setType(TABLE_LIKE) + .setSubType(ICEBERG_TABLE) + .setName("table1") + .setId(persistence.generateId()) + .setCatalogId(catalog.getId()) + .setCreateTimestamp(System.currentTimeMillis()) + .build()); + return tableResult.getEntity(); + } + + private static PolarisBaseEntity createNamespace( + PolarisMetaStoreManager manager, + PolarisCallContext callCtx, + PolarisBaseEntity catalog, + Persistence persistence) { + var namespaceResult = + manager.createEntityIfNotExists( + callCtx, + List.of(catalog), + new PolarisEntity.Builder() + .setType(NAMESPACE) + .setName("ns") + .setId(persistence.generateId()) + .setCatalogId(catalog.getId()) + .setCreateTimestamp(System.currentTimeMillis()) + .build()); + return namespaceResult.getEntity(); + } + + private static PolarisBaseEntity createCatalogRole( + PolarisMetaStoreManager manager, + PolarisCallContext callCtx, + PolarisBaseEntity catalog, + Persistence persistence) { + var catalogRoleResult = + manager.createEntityIfNotExists( + callCtx, + List.of(catalog), + new PolarisEntity.Builder() + .setType(CATALOG_ROLE) + .setName("catalog-role") + .setId(persistence.generateId()) + .setCatalogId(catalog.getId()) + .setCreateTimestamp(System.currentTimeMillis()) + .build()); + return catalogRoleResult.getEntity(); + } + + private static PolarisBaseEntity createCatalog( + PolarisMetaStoreManager manager, PolarisCallContext callCtx, Persistence persistence) { + var catalogResult = + manager.createCatalog( + callCtx, + new PolarisEntity.Builder( + new CatalogEntity.Builder() + .setName("catalog") + .setDefaultBaseLocation("file:///tmp/foo/bar/baz") + .setCatalogType("INTERNAL") + .build()) + .setId(persistence.generateId()) + .setCatalogId(0L) + .setCreateTimestamp(System.currentTimeMillis()) + .build(), + List.of()); + return catalogResult.getCatalog(); + } + + private static PolarisBaseEntity createPrincipal( + PolarisMetaStoreManager manager, PolarisCallContext callCtx, Persistence persistence) { + var principalResult = + manager.createPrincipal( + callCtx, + new PrincipalEntity.Builder() + .setType(PRINCIPAL) + .setName("principal") + .setId(persistence.generateId()) + .setCatalogId(0L) + .setCreateTimestamp(System.currentTimeMillis()) + .build()); + return principalResult.getPrincipal(); + } + + private static PolarisBaseEntity createPrincipalRole( + PolarisMetaStoreManager manager, PolarisCallContext callCtx, Persistence persistence) { + var principalRoleResult = + manager.createEntityIfNotExists( + callCtx, + List.of(), + new PolarisEntity.Builder() + .setType(PRINCIPAL_ROLE) + .setName("principal-role") + .setId(persistence.generateId()) + .setCatalogId(0L) + .setCreateTimestamp(System.currentTimeMillis()) + .build()); + return principalRoleResult.getEntity(); + } + + private static void mandatoryCatalogObjsForTestImpl(Persistence persistence, long catalogId) { + var catalogStateObj = + persistence.write( + CatalogStateObj.builder() + .id(persistence.generateId()) + .stableIdToName( + newUpdatableIndex(persistence, INDEX_KEY_SERIALIZER) + .toIndexed("", (x, o) -> fail())) + .nameToObjRef( + newUpdatableIndex(persistence, OBJ_REF_SERIALIZER) + .toIndexed("", (x, o) -> fail())) + .changes( + newUpdatableIndex(persistence, CHANGE_SERIALIZER) + .toIndexed("", (x, o) -> fail())) + .locations( + newUpdatableIndex(persistence, ENTITY_ID_SET_SERIALIZER) + .toIndexed("", (x, o) -> fail())) + .createdAtMicros(persistence.currentTimeMicros()) + .seq(1) + .tail() + .build(), + CatalogStateObj.class); + persistence.updateReferencePointer( + persistence.fetchReference(format(CATALOG_STATE_REF_NAME_PATTERN, catalogId)), + objRef(catalogStateObj)); + } + + private static void mandatoryRealmObjsForTestImpl(Persistence persistence) { + var catalogsObj = + persistence.write( + CatalogsObj.builder() + .id(persistence.generateId()) + .stableIdToName( + newUpdatableIndex(persistence, INDEX_KEY_SERIALIZER) + .toIndexed("", (x, o) -> fail())) + .nameToObjRef( + newUpdatableIndex(persistence, OBJ_REF_SERIALIZER) + .toIndexed("", (x, o) -> fail())) + .createdAtMicros(persistence.currentTimeMicros()) + .seq(1) + .tail() + .build(), + CatalogsObj.class); + persistence.updateReferencePointer( + persistence.fetchReference(CATALOGS_REF_NAME), objRef(catalogsObj)); + + var immediateTasksObj = + persistence.write( + ImmediateTasksObj.builder() + .id(persistence.generateId()) + .stableIdToName( + newUpdatableIndex(persistence, INDEX_KEY_SERIALIZER) + .toIndexed("", (x, o) -> fail())) + .nameToObjRef( + newUpdatableIndex(persistence, OBJ_REF_SERIALIZER) + .toIndexed("", (x, o) -> fail())) + .createdAtMicros(persistence.currentTimeMicros()) + .seq(1) + .tail() + .build(), + ImmediateTasksObj.class); + persistence.updateReferencePointer( + persistence.fetchReference(IMMEDIATE_TASKS_REF_NAME), objRef(immediateTasksObj)); + + var policyMappingsObj = + persistence.write( + PolicyMappingsObj.builder() + .id(persistence.generateId()) + .policyMappings( + newUpdatableIndex(persistence, POLICY_MAPPING_SERIALIZER) + .toIndexed("", (x, o) -> fail())) + .createdAtMicros(persistence.currentTimeMicros()) + .seq(1) + .tail() + .build(), + PolicyMappingsObj.class); + persistence.updateReferencePointer( + persistence.fetchReference(POLICY_MAPPINGS_REF_NAME), objRef(policyMappingsObj)); + } + + private void checkEntities(String step, List entities) { + // Purge the whole cache in case maintenance purged objects/references that should not have + // been purged to make the assertions catch those cases. + cacheBackend.purge(); + soft.assertThat(cacheBackend.estimatedSize()).describedAs(step).isEqualTo(0L); + + var manager = metaStoreManagerFactory.getOrCreateMetaStoreManager(realmContext); + var session = metaStoreManagerFactory.getOrCreateSession(realmContext); + var callCtx = new PolarisCallContext(realmContext, session, configurationStore); + + for (var e : entities) { + var result = + manager.loadResolvedEntityById(callCtx, e.getCatalogId(), e.getId(), e.getType()); + var loadedEntity = result.getEntity(); + soft.assertThat(loadedEntity) + .describedAs("%s: %s", step, result.getReturnStatus()) + .isEqualTo(e); + } + + for (var e : entities) { + var result = + manager.loadResolvedEntityByName( + callCtx, e.getCatalogId(), e.getParentId(), e.getType(), e.getName()); + var loadedEntity = result.getEntity(); + soft.assertThat(loadedEntity) + .describedAs("%s: %s", step, result.getReturnStatus()) + .isEqualTo(e); + } + } +} diff --git a/persistence/nosql/persistence/metastore-maintenance/src/test/resources/logback-test.xml b/persistence/nosql/persistence/metastore-maintenance/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..1de5ba06ea --- /dev/null +++ b/persistence/nosql/persistence/metastore-maintenance/src/test/resources/logback-test.xml @@ -0,0 +1,34 @@ + + + + + + + %date{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + diff --git a/persistence/nosql/persistence/metastore-maintenance/src/test/resources/weld.properties b/persistence/nosql/persistence/metastore-maintenance/src/test/resources/weld.properties new file mode 100644 index 0000000000..c26169e0e1 --- /dev/null +++ b/persistence/nosql/persistence/metastore-maintenance/src/test/resources/weld.properties @@ -0,0 +1,21 @@ +# +# 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. +# + +# See https://bugs.openjdk.org/browse/JDK-8349545 +org.jboss.weld.bootstrap.concurrentDeployment=false diff --git a/persistence/nosql/persistence/metastore-maintenance/src/testFixtures/java/org/apache/polaris/persistence/nosql/metastore/maintenance/CdiProducers.java b/persistence/nosql/persistence/metastore-maintenance/src/testFixtures/java/org/apache/polaris/persistence/nosql/metastore/maintenance/CdiProducers.java new file mode 100644 index 0000000000..88f95915ad --- /dev/null +++ b/persistence/nosql/persistence/metastore-maintenance/src/testFixtures/java/org/apache/polaris/persistence/nosql/metastore/maintenance/CdiProducers.java @@ -0,0 +1,32 @@ +/* + * 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.persistence.nosql.metastore.maintenance; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +@ApplicationScoped +public class CdiProducers { + public static MutableCatalogsMaintenanceConfig config = new MutableCatalogsMaintenanceConfig(); + + @Produces + MutableCatalogsMaintenanceConfig produceMutableCatalogsMaintenanceConfig() { + return config; + } +} diff --git a/persistence/nosql/persistence/metastore-maintenance/src/testFixtures/java/org/apache/polaris/persistence/nosql/metastore/maintenance/MutableCatalogsMaintenanceConfig.java b/persistence/nosql/persistence/metastore-maintenance/src/testFixtures/java/org/apache/polaris/persistence/nosql/metastore/maintenance/MutableCatalogsMaintenanceConfig.java new file mode 100644 index 0000000000..50e5171521 --- /dev/null +++ b/persistence/nosql/persistence/metastore-maintenance/src/testFixtures/java/org/apache/polaris/persistence/nosql/metastore/maintenance/MutableCatalogsMaintenanceConfig.java @@ -0,0 +1,71 @@ +/* + * 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.persistence.nosql.metastore.maintenance; + +import java.util.Optional; + +public class MutableCatalogsMaintenanceConfig implements CatalogsMaintenanceConfig { + + private static CatalogsMaintenanceConfig current = + CatalogsMaintenanceConfig.BuildableCatalogsMaintenanceConfig.builder().build(); + + public static void setCurrent(CatalogsMaintenanceConfig config) { + current = config; + } + + @Override + public Optional principalsRetain() { + return current.principalsRetain(); + } + + @Override + public Optional principalRolesRetain() { + return current.principalRolesRetain(); + } + + @Override + public Optional grantsRetain() { + return current.grantsRetain(); + } + + @Override + public Optional immediateTasksRetain() { + return current.immediateTasksRetain(); + } + + @Override + public Optional catalogsHistoryRetain() { + return current.catalogsHistoryRetain(); + } + + @Override + public Optional catalogRolesRetain() { + return current.catalogRolesRetain(); + } + + @Override + public Optional catalogStateRetain() { + return current.catalogStateRetain(); + } + + @Override + public Optional catalogPoliciesRetain() { + return current.catalogPoliciesRetain(); + } +} diff --git a/persistence/nosql/persistence/metastore-maintenance/src/testFixtures/resources/META-INF/beans.xml b/persistence/nosql/persistence/metastore-maintenance/src/testFixtures/resources/META-INF/beans.xml new file mode 100644 index 0000000000..a297f1aa53 --- /dev/null +++ b/persistence/nosql/persistence/metastore-maintenance/src/testFixtures/resources/META-INF/beans.xml @@ -0,0 +1,24 @@ + + + + + \ No newline at end of file