This is an automated email from the ASF dual-hosted git repository.

dimas pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris.git


The following commit(s) were added to refs/heads/main by this push:
     new ace94a5a7 NoSQL: Add to runtime-service (#3396)
ace94a5a7 is described below

commit ace94a5a7842913c95c29c265cf4ff3d3ed3e8fe
Author: Robert Stupp <[email protected]>
AuthorDate: Tue Jan 27 20:39:03 2026 +0100

    NoSQL: Add to runtime-service (#3396)
    
    * NoSQL: Add to runtime-service
    
    This change adds the NoSQL persistence to polaris-runtime-service.
---
 .../src/main/resources/application-test.properties |   1 +
 .../src/main/resources/application.properties      |  21 +-
 runtime/service/build.gradle.kts                   |  10 +
 .../service/it/nosql/NoSqlApplicationIT.java}      |  31 +--
 .../polaris/service/it/nosql/NoSqlCatalogIT.java}  |  33 ++--
 .../it/nosql/NoSqlManagementServiceIT.java}        |  31 +--
 .../polaris/service/it/nosql/NoSqlTesting.java}    |  25 +--
 .../apache/polaris/service/catalog/Profiles.java   |  17 ++
 .../PolarisGenericTableCatalogNoSqlInMemTest.java  |  44 +++++
 .../IcebergCatalogHandlerNoSqlAuthzTest.java       |  53 +++++
 .../iceberg/IcebergCatalogNoSqlInMemTest.java      |  64 ++++++
 .../iceberg/IcebergViewCatalogNoSqlInMemTest.java  |  56 ++++++
 .../policy/PolicyCatalogNoSqlInMemTest.java        |  43 +++++
 .../polaris/service/distcache/HttpTestServer.java  |  70 +++++++
 ...rsistenceDistCacheInvalidationsIntegration.java | 214 +++++++++++++++++++++
 15 files changed, 630 insertions(+), 83 deletions(-)

diff --git a/runtime/defaults/src/main/resources/application-test.properties 
b/runtime/defaults/src/main/resources/application-test.properties
index 0e6aecedf..abd7d8e7d 100644
--- a/runtime/defaults/src/main/resources/application-test.properties
+++ b/runtime/defaults/src/main/resources/application-test.properties
@@ -22,6 +22,7 @@
 
 quarkus.datasource.devservices.enabled=false
 quarkus.keycloak.devservices.enabled=false
+quarkus.mongodb.devservices.enabled=false
 
 quarkus.log.level=ERROR
 quarkus.log.file.enable=false
diff --git a/runtime/defaults/src/main/resources/application.properties 
b/runtime/defaults/src/main/resources/application.properties
index aa4fe4ad1..f05048a35 100644
--- a/runtime/defaults/src/main/resources/application.properties
+++ b/runtime/defaults/src/main/resources/application.properties
@@ -41,6 +41,8 @@ quarkus.micrometer.enabled=true
 quarkus.micrometer.export.prometheus.enabled=true
 quarkus.oidc.enabled=true
 quarkus.otel.enabled=true
+#quarkus.mongodb.metrics.enabled=true
+#quarkus.mongodb.connection-string=mongodb://localhost:27017
 
 # ---- Runtime Configuration ----
 # Below are default values for properties that can be changed in runtime.
@@ -128,9 +130,19 @@ 
polaris.features."SUPPORTED_CATALOG_CONNECTION_TYPES"=["ICEBERG_REST"]
 # realm overrides
 # 
polaris.features.realm-overrides."my-realm"."SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION"=true
 
-# polaris.persistence.type=in-memory-atomic
+# Available types:
+# - in-memory - InMemoryPolarisMetaStoreManagerFactory
+# - in-memory-atomic - InMemoryAtomicOperationMetaStoreManagerFactory
+# - nosql (beta) - NoSQL persistence backend, define the backend type via 
'polaris.persistence.nosql.backend'
+# - relational-jdbc
 polaris.persistence.type=in-memory
-# polaris.persistence.type=relational-jdbc
+# Database backend for 'nosql' persistence-type
+# Available backends:
+# - InMemory - for testing purposes
+# - MongoDb - configure the via the Quarkus extension
+#     Configure the necessary MongoDB properties starting with 
'quarkus.mongodb.' in this file.
+#     See https://quarkus.io/guides/mongodb#configuration-reference for 
details about these configurations.
+#polaris.persistence.nosql.backend=InMemory
 
 polaris.secrets-manager.type=in-memory
 # if set to true it will try to start localstack at build and run time for the 
local environment
@@ -138,6 +150,9 @@ polaris.secrets-manager.type=in-memory
 quarkus.rds.devservices.enabled=false
 quarkus.rds.sync-client.type=apache
 
+## MongoDB specific configuration
+#quarkus.mongodb.database=polaris
+
 polaris.file-io.type=default
 
 polaris.event-listener.type=no-op
@@ -282,6 +297,8 @@ quarkus.arc.ignored-split-packages=\
 
 ## Quarkus required setting for third party indexing
 # fixed at build-time
+quarkus.index-dependency.agrona.group-id=org.agrona
+quarkus.index-dependency.agrona.artifact-id=agrona
 quarkus.index-dependency.avro.group-id=org.apache.avro
 quarkus.index-dependency.avro.artifact-id=avro
 quarkus.index-dependency.guava.group-id=com.google.guava
diff --git a/runtime/service/build.gradle.kts b/runtime/service/build.gradle.kts
index 9dcd8c588..11d1b7a48 100644
--- a/runtime/service/build.gradle.kts
+++ b/runtime/service/build.gradle.kts
@@ -39,6 +39,12 @@ dependencies {
   compileOnly(project(":polaris-immutables"))
   annotationProcessor(project(":polaris-immutables", configuration = 
"processor"))
 
+  runtimeOnly(project(":polaris-persistence-nosql-metastore"))
+  runtimeOnly(project(":polaris-persistence-nosql-cdi-quarkus"))
+  runtimeOnly(project(":polaris-persistence-nosql-cdi-quarkus-distcache"))
+  runtimeOnly(project(":polaris-persistence-nosql-maintenance-impl"))
+  runtimeOnly(project(":polaris-persistence-nosql-metastore-maintenance"))
+
   implementation(platform(libs.iceberg.bom))
   implementation("org.apache.iceberg:iceberg-api")
   implementation("org.apache.iceberg:iceberg-core")
@@ -153,6 +159,10 @@ dependencies {
   testImplementation("org.testcontainers:testcontainers")
   testImplementation("org.testcontainers:testcontainers-postgresql")
 
+  testImplementation(project(":polaris-persistence-nosql-api"))
+  testImplementation(testFixtures(project(":polaris-persistence-nosql-api")))
+  testImplementation(project(":polaris-persistence-nosql-impl"))
+
   testFixturesImplementation(project(":polaris-core"))
   testFixturesImplementation(project(":polaris-api-management-model"))
   testFixturesImplementation(project(":polaris-api-management-service"))
diff --git 
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/Profiles.java
 
b/runtime/service/src/intTest/java/org/apache/polaris/service/it/nosql/NoSqlApplicationIT.java
similarity index 52%
copy from 
runtime/service/src/test/java/org/apache/polaris/service/catalog/Profiles.java
copy to 
runtime/service/src/intTest/java/org/apache/polaris/service/it/nosql/NoSqlApplicationIT.java
index c4231ac26..0181296f0 100644
--- 
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/Profiles.java
+++ 
b/runtime/service/src/intTest/java/org/apache/polaris/service/it/nosql/NoSqlApplicationIT.java
@@ -16,29 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.polaris.service.it.nosql;
 
-package org.apache.polaris.service.catalog;
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+import io.quarkus.test.junit.TestProfile;
+import org.apache.polaris.service.it.test.PolarisApplicationIntegrationTest;
 
-import io.quarkus.test.junit.QuarkusTestProfile;
-import java.util.Map;
-
-public final class Profiles {
-  private Profiles() {}
-
-  public static class DefaultProfile implements QuarkusTestProfile {
-    @Override
-    public Map<String, String> getConfigOverrides() {
-      return Map.of(
-          "polaris.features.\"ALLOW_SPECIFYING_FILE_IO_IMPL\"",
-          "true",
-          "polaris.features.\"ALLOW_INSECURE_STORAGE_TYPES\"",
-          "true",
-          "polaris.features.\"SUPPORTED_CATALOG_STORAGE_TYPES\"",
-          "[\"FILE\",\"S3\"]",
-          "polaris.event-listener.type",
-          "test",
-          "polaris.readiness.ignore-severe-issues",
-          "true");
-    }
-  }
-}
+@QuarkusIntegrationTest
+@TestProfile(value = NoSqlTesting.PersistenceInMemoryProfile.class)
+public class NoSqlApplicationIT extends PolarisApplicationIntegrationTest {}
diff --git 
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/Profiles.java
 
b/runtime/service/src/intTest/java/org/apache/polaris/service/it/nosql/NoSqlCatalogIT.java
similarity index 52%
copy from 
runtime/service/src/test/java/org/apache/polaris/service/catalog/Profiles.java
copy to 
runtime/service/src/intTest/java/org/apache/polaris/service/it/nosql/NoSqlCatalogIT.java
index c4231ac26..ba99caab5 100644
--- 
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/Profiles.java
+++ 
b/runtime/service/src/intTest/java/org/apache/polaris/service/it/nosql/NoSqlCatalogIT.java
@@ -16,29 +16,26 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.polaris.service.it.nosql;
 
-package org.apache.polaris.service.catalog;
-
-import io.quarkus.test.junit.QuarkusTestProfile;
+import com.google.common.collect.ImmutableMap;
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+import io.quarkus.test.junit.TestProfile;
 import java.util.Map;
+import org.apache.polaris.service.it.PolarisRestCatalogMinIOIT;
 
-public final class Profiles {
-  private Profiles() {}
-
-  public static class DefaultProfile implements QuarkusTestProfile {
+@QuarkusIntegrationTest
+@TestProfile(value = NoSqlCatalogIT.Profile.class)
+public class NoSqlCatalogIT extends PolarisRestCatalogMinIOIT {
+  public static class Profile extends NoSqlTesting.PersistenceInMemoryProfile {
     @Override
     public Map<String, String> getConfigOverrides() {
-      return Map.of(
-          "polaris.features.\"ALLOW_SPECIFYING_FILE_IO_IMPL\"",
-          "true",
-          "polaris.features.\"ALLOW_INSECURE_STORAGE_TYPES\"",
-          "true",
-          "polaris.features.\"SUPPORTED_CATALOG_STORAGE_TYPES\"",
-          "[\"FILE\",\"S3\"]",
-          "polaris.event-listener.type",
-          "test",
-          "polaris.readiness.ignore-severe-issues",
-          "true");
+      return ImmutableMap.<String, String>builder()
+          .putAll(super.getConfigOverrides())
+          .put("polaris.storage.aws.access-key", MINIO_ACCESS_KEY)
+          .put("polaris.storage.aws.secret-key", MINIO_SECRET_KEY)
+          .put("polaris.features.\"SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION\"", 
"false")
+          .build();
     }
   }
 }
diff --git 
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/Profiles.java
 
b/runtime/service/src/intTest/java/org/apache/polaris/service/it/nosql/NoSqlManagementServiceIT.java
similarity index 52%
copy from 
runtime/service/src/test/java/org/apache/polaris/service/catalog/Profiles.java
copy to 
runtime/service/src/intTest/java/org/apache/polaris/service/it/nosql/NoSqlManagementServiceIT.java
index c4231ac26..c76cf4b87 100644
--- 
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/Profiles.java
+++ 
b/runtime/service/src/intTest/java/org/apache/polaris/service/it/nosql/NoSqlManagementServiceIT.java
@@ -16,29 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.polaris.service.it.nosql;
 
-package org.apache.polaris.service.catalog;
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+import io.quarkus.test.junit.TestProfile;
+import 
org.apache.polaris.service.it.test.PolarisManagementServiceIntegrationTest;
 
-import io.quarkus.test.junit.QuarkusTestProfile;
-import java.util.Map;
-
-public final class Profiles {
-  private Profiles() {}
-
-  public static class DefaultProfile implements QuarkusTestProfile {
-    @Override
-    public Map<String, String> getConfigOverrides() {
-      return Map.of(
-          "polaris.features.\"ALLOW_SPECIFYING_FILE_IO_IMPL\"",
-          "true",
-          "polaris.features.\"ALLOW_INSECURE_STORAGE_TYPES\"",
-          "true",
-          "polaris.features.\"SUPPORTED_CATALOG_STORAGE_TYPES\"",
-          "[\"FILE\",\"S3\"]",
-          "polaris.event-listener.type",
-          "test",
-          "polaris.readiness.ignore-severe-issues",
-          "true");
-    }
-  }
-}
+@QuarkusIntegrationTest
+@TestProfile(value = NoSqlTesting.PersistenceInMemoryProfile.class)
+public class NoSqlManagementServiceIT extends 
PolarisManagementServiceIntegrationTest {}
diff --git 
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/Profiles.java
 
b/runtime/service/src/intTest/java/org/apache/polaris/service/it/nosql/NoSqlTesting.java
similarity index 63%
copy from 
runtime/service/src/test/java/org/apache/polaris/service/catalog/Profiles.java
copy to 
runtime/service/src/intTest/java/org/apache/polaris/service/it/nosql/NoSqlTesting.java
index c4231ac26..b8bc044e8 100644
--- 
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/Profiles.java
+++ 
b/runtime/service/src/intTest/java/org/apache/polaris/service/it/nosql/NoSqlTesting.java
@@ -16,29 +16,24 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
-package org.apache.polaris.service.catalog;
+package org.apache.polaris.service.it.nosql;
 
 import io.quarkus.test.junit.QuarkusTestProfile;
 import java.util.Map;
 
-public final class Profiles {
-  private Profiles() {}
+public final class NoSqlTesting {
+  private NoSqlTesting() {}
 
-  public static class DefaultProfile implements QuarkusTestProfile {
+  public static class PersistenceInMemoryProfile implements QuarkusTestProfile 
{
     @Override
     public Map<String, String> getConfigOverrides() {
       return Map.of(
-          "polaris.features.\"ALLOW_SPECIFYING_FILE_IO_IMPL\"",
-          "true",
-          "polaris.features.\"ALLOW_INSECURE_STORAGE_TYPES\"",
-          "true",
-          "polaris.features.\"SUPPORTED_CATALOG_STORAGE_TYPES\"",
-          "[\"FILE\",\"S3\"]",
-          "polaris.event-listener.type",
-          "test",
-          "polaris.readiness.ignore-severe-issues",
-          "true");
+          "polaris.persistence.type",
+          "nosql",
+          "polaris.persistence.nosql.backend",
+          "InMemory",
+          "polaris.persistence.auto-bootstrap-types",
+          "nosql");
     }
   }
 }
diff --git 
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/Profiles.java
 
b/runtime/service/src/test/java/org/apache/polaris/service/catalog/Profiles.java
index c4231ac26..9c5411037 100644
--- 
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/Profiles.java
+++ 
b/runtime/service/src/test/java/org/apache/polaris/service/catalog/Profiles.java
@@ -19,12 +19,19 @@
 
 package org.apache.polaris.service.catalog;
 
+import com.google.common.collect.ImmutableMap;
 import io.quarkus.test.junit.QuarkusTestProfile;
 import java.util.Map;
 
 public final class Profiles {
   private Profiles() {}
 
+  public static final Map<String, String> NOSQL_IN_MEM =
+      ImmutableMap.<String, String>builder()
+          .put("polaris.persistence.type", "nosql")
+          .put("polaris.persistence.nosql.backend", "InMemory")
+          .build();
+
   public static class DefaultProfile implements QuarkusTestProfile {
     @Override
     public Map<String, String> getConfigOverrides() {
@@ -41,4 +48,14 @@ public final class Profiles {
           "true");
     }
   }
+
+  public static class DefaultNoSqlProfile extends DefaultProfile {
+    @Override
+    public Map<String, String> getConfigOverrides() {
+      return ImmutableMap.<String, String>builder()
+          .putAll(super.getConfigOverrides())
+          .putAll(NOSQL_IN_MEM)
+          .build();
+    }
+  }
 }
diff --git 
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/generic/PolarisGenericTableCatalogNoSqlInMemTest.java
 
b/runtime/service/src/test/java/org/apache/polaris/service/catalog/generic/PolarisGenericTableCatalogNoSqlInMemTest.java
new file mode 100644
index 000000000..a7caa216f
--- /dev/null
+++ 
b/runtime/service/src/test/java/org/apache/polaris/service/catalog/generic/PolarisGenericTableCatalogNoSqlInMemTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.catalog.generic;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.TestProfile;
+import jakarta.inject.Inject;
+import java.util.List;
+import org.apache.polaris.core.persistence.MetaStoreManagerFactory;
+import org.apache.polaris.core.persistence.bootstrap.RootCredentialsSet;
+import org.apache.polaris.service.catalog.Profiles;
+
+@QuarkusTest
+@TestProfile(Profiles.DefaultNoSqlProfile.class)
+public class PolarisGenericTableCatalogNoSqlInMemTest
+    extends AbstractPolarisGenericTableCatalogTest {
+
+  @Inject MetaStoreManagerFactory metaStoreManagerFactory;
+
+  @Override
+  protected void bootstrapRealm(String realmName) {
+    metaStoreManagerFactory
+        .bootstrapRealms(
+            List.of(realmName),
+            RootCredentialsSet.fromList(List.of(realmName + 
",aClientId,aSecret")))
+        .get(realmName);
+  }
+}
diff --git 
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandlerNoSqlAuthzTest.java
 
b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandlerNoSqlAuthzTest.java
new file mode 100644
index 000000000..aad67ce27
--- /dev/null
+++ 
b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandlerNoSqlAuthzTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.catalog.iceberg;
+
+import static org.apache.polaris.service.catalog.Profiles.NOSQL_IN_MEM;
+
+import com.google.common.collect.ImmutableMap;
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.TestProfile;
+import jakarta.inject.Inject;
+import java.util.List;
+import java.util.Map;
+import org.apache.polaris.core.persistence.MetaStoreManagerFactory;
+import org.apache.polaris.core.persistence.bootstrap.RootCredentialsSet;
+import org.apache.polaris.service.admin.PolarisAuthzTestBase;
+
+@QuarkusTest
+@TestProfile(IcebergCatalogHandlerNoSqlAuthzTest.Profile.class)
+public class IcebergCatalogHandlerNoSqlAuthzTest extends 
AbstractIcebergCatalogHandlerAuthzTest {
+
+  public static class Profile extends PolarisAuthzTestBase.Profile {
+    @Override
+    public Map<String, String> getConfigOverrides() {
+      return ImmutableMap.<String, String>builder()
+          .putAll(super.getConfigOverrides())
+          .putAll(NOSQL_IN_MEM)
+          .build();
+    }
+  }
+
+  @Inject MetaStoreManagerFactory metaStoreManagerFactory;
+
+  @Override
+  protected void bootstrapRealm(String realmIdentifier) {
+    metaStoreManagerFactory.bootstrapRealms(List.of(realmIdentifier), 
RootCredentialsSet.EMPTY);
+  }
+}
diff --git 
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogNoSqlInMemTest.java
 
b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogNoSqlInMemTest.java
new file mode 100644
index 000000000..536c17c03
--- /dev/null
+++ 
b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogNoSqlInMemTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.catalog.iceberg;
+
+import static org.apache.polaris.service.catalog.Profiles.NOSQL_IN_MEM;
+
+import com.google.common.collect.ImmutableMap;
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.TestProfile;
+import java.util.List;
+import java.util.Map;
+import org.apache.polaris.core.PolarisDiagnostics;
+import org.apache.polaris.core.config.RealmConfig;
+import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
+import org.apache.polaris.core.persistence.bootstrap.RootCredentialsSet;
+import org.apache.polaris.core.persistence.cache.EntityCache;
+
+@QuarkusTest
+@TestProfile(IcebergCatalogNoSqlInMemTest.Profile.class)
+public class IcebergCatalogNoSqlInMemTest extends AbstractIcebergCatalogTest {
+
+  public static class Profile extends AbstractIcebergCatalogTest.Profile {
+    @Override
+    public Map<String, String> getConfigOverrides() {
+      return ImmutableMap.<String, String>builder()
+          .putAll(super.getConfigOverrides())
+          .putAll(NOSQL_IN_MEM)
+          .build();
+    }
+  }
+
+  @Override
+  protected void bootstrapRealm(String realmName) {
+    metaStoreManagerFactory
+        .bootstrapRealms(
+            List.of(realmName),
+            RootCredentialsSet.fromList(List.of(realmName + 
",aClientId,aSecret")))
+        .get(realmName);
+  }
+
+  @Override
+  protected EntityCache createEntityCache(
+      PolarisDiagnostics diagnostics,
+      RealmConfig realmConfig,
+      PolarisMetaStoreManager metaStoreManager) {
+    return null;
+  }
+}
diff --git 
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergViewCatalogNoSqlInMemTest.java
 
b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergViewCatalogNoSqlInMemTest.java
new file mode 100644
index 000000000..4089c173f
--- /dev/null
+++ 
b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergViewCatalogNoSqlInMemTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.catalog.iceberg;
+
+import static org.apache.polaris.service.catalog.Profiles.NOSQL_IN_MEM;
+
+import com.google.common.collect.ImmutableMap;
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.TestProfile;
+import jakarta.inject.Inject;
+import java.util.List;
+import java.util.Map;
+import org.apache.polaris.core.persistence.MetaStoreManagerFactory;
+import org.apache.polaris.core.persistence.bootstrap.RootCredentialsSet;
+
+@QuarkusTest
+@TestProfile(IcebergViewCatalogNoSqlInMemTest.Profile.class)
+public class IcebergViewCatalogNoSqlInMemTest extends 
AbstractIcebergCatalogViewTest {
+
+  @Inject MetaStoreManagerFactory metaStoreManagerFactory;
+
+  public static class Profile extends AbstractIcebergCatalogViewTest.Profile {
+    @Override
+    public Map<String, String> getConfigOverrides() {
+      return ImmutableMap.<String, String>builder()
+          .putAll(super.getConfigOverrides())
+          .putAll(NOSQL_IN_MEM)
+          .build();
+    }
+  }
+
+  @Override
+  protected void bootstrapRealm(String realmName) {
+    metaStoreManagerFactory
+        .bootstrapRealms(
+            List.of(realmName),
+            RootCredentialsSet.fromList(List.of(realmName + 
",aClientId,aSecret")))
+        .get(realmName);
+  }
+}
diff --git 
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/policy/PolicyCatalogNoSqlInMemTest.java
 
b/runtime/service/src/test/java/org/apache/polaris/service/catalog/policy/PolicyCatalogNoSqlInMemTest.java
new file mode 100644
index 000000000..521769e4b
--- /dev/null
+++ 
b/runtime/service/src/test/java/org/apache/polaris/service/catalog/policy/PolicyCatalogNoSqlInMemTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.catalog.policy;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.TestProfile;
+import jakarta.inject.Inject;
+import java.util.List;
+import org.apache.polaris.core.persistence.MetaStoreManagerFactory;
+import org.apache.polaris.core.persistence.bootstrap.RootCredentialsSet;
+import org.apache.polaris.service.catalog.Profiles;
+
+@QuarkusTest
+@TestProfile(Profiles.DefaultNoSqlProfile.class)
+public class PolicyCatalogNoSqlInMemTest extends AbstractPolicyCatalogTest {
+
+  @Inject MetaStoreManagerFactory metaStoreManagerFactory;
+
+  @Override
+  protected void bootstrapRealm(String realmName) {
+    metaStoreManagerFactory
+        .bootstrapRealms(
+            List.of(realmName),
+            RootCredentialsSet.fromList(List.of(realmName + 
",aClientId,aSecret")))
+        .get(realmName);
+  }
+}
diff --git 
a/runtime/service/src/test/java/org/apache/polaris/service/distcache/HttpTestServer.java
 
b/runtime/service/src/test/java/org/apache/polaris/service/distcache/HttpTestServer.java
new file mode 100644
index 000000000..d89ef619a
--- /dev/null
+++ 
b/runtime/service/src/test/java/org/apache/polaris/service/distcache/HttpTestServer.java
@@ -0,0 +1,70 @@
+/*
+ * 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.distcache;
+
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.URI;
+
+/** HTTP test server. */
+public class HttpTestServer implements AutoCloseable {
+  private final HttpServer server;
+
+  public HttpTestServer(String context, HttpHandler handler) throws 
IOException {
+    this(new InetSocketAddress("localhost", 0), context, handler);
+  }
+
+  public HttpTestServer(InetSocketAddress bind, String context, HttpHandler 
handler)
+      throws IOException {
+    HttpHandler safeHandler =
+        exchange -> {
+          try {
+            handler.handle(exchange);
+          } catch (RuntimeException | Error e) {
+            exchange.sendResponseHeaders(503, 0);
+            throw e;
+          }
+        };
+    server = HttpServer.create(bind, 0);
+    server.createContext(context, safeHandler);
+    server.setExecutor(null);
+
+    server.start();
+  }
+
+  public InetSocketAddress getAddress() {
+    return server.getAddress();
+  }
+
+  public URI getUri() {
+    return URI.create(
+        "http://";
+            + getAddress().getAddress().getHostAddress()
+            + ":"
+            + getAddress().getPort()
+            + "/");
+  }
+
+  @Override
+  public void close() {
+    server.stop(0);
+  }
+}
diff --git 
a/runtime/service/src/test/java/org/apache/polaris/service/distcache/TestPersistenceDistCacheInvalidationsIntegration.java
 
b/runtime/service/src/test/java/org/apache/polaris/service/distcache/TestPersistenceDistCacheInvalidationsIntegration.java
new file mode 100644
index 000000000..9b8cc63b4
--- /dev/null
+++ 
b/runtime/service/src/test/java/org/apache/polaris/service/distcache/TestPersistenceDistCacheInvalidationsIntegration.java
@@ -0,0 +1,214 @@
+/*
+ * 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.distcache;
+
+import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
+import static java.lang.String.format;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Map.entry;
+import static java.util.Objects.requireNonNull;
+import static 
org.apache.polaris.persistence.nosql.api.cache.CacheInvalidations.CacheInvalidationEvictObj.cacheInvalidationEvictObj;
+import static 
org.apache.polaris.persistence.nosql.api.cache.CacheInvalidations.CacheInvalidationEvictReference.cacheInvalidationEvictReference;
+import static org.apache.polaris.persistence.nosql.api.obj.ObjRef.objRef;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.InstanceOfAssertFactories.list;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableMap;
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.TestProfile;
+import jakarta.inject.Inject;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import org.apache.polaris.persistence.nosql.api.Persistence;
+import org.apache.polaris.persistence.nosql.api.RealmPersistenceFactory;
+import org.apache.polaris.persistence.nosql.api.SystemPersistence;
+import org.apache.polaris.persistence.nosql.api.cache.CacheBackend;
+import org.apache.polaris.persistence.nosql.api.cache.CacheInvalidations;
+import 
org.apache.polaris.persistence.nosql.api.cache.CacheInvalidations.CacheInvalidation;
+import org.apache.polaris.persistence.nosql.api.obj.SimpleTestObj;
+import org.apache.polaris.service.catalog.iceberg.AbstractIcebergCatalogTest;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+
+@QuarkusTest
+@TestProfile(TestPersistenceDistCacheInvalidationsIntegration.Profile.class)
+// Need two IPs in this test. macOS doesn't allow binding to arbitrary 
127.x.x.x addresses.
+@EnabledOnOs(OS.LINUX)
+@SuppressWarnings("CdiInjectionPointsInspection")
+public class TestPersistenceDistCacheInvalidationsIntegration {
+
+  public static class Profile extends AbstractIcebergCatalogTest.Profile {
+    @Override
+    public Map<String, String> getConfigOverrides() {
+      return ImmutableMap.<String, String>builder()
+          .putAll(super.getConfigOverrides())
+          .put("quarkus.management.port", "" + QUARKUS_MANAGEMENT_PORT)
+          .put("quarkus.management.test-port", "" + QUARKUS_MANAGEMENT_PORT)
+          .put("quarkus.management.host", "127.0.0.1")
+          .put("quarkus.management.enabled", "true")
+          .put("polaris.persistence.type", "nosql")
+          .put("polaris.persistence.auto-bootstrap-types", "nosql")
+          .put("polaris.persistence.nosql.backend", "InMemory")
+          .put(
+              
"polaris.persistence.distributed-cache-invalidations.valid-tokens", "token1," + 
TOKEN)
+          .put(
+              
"polaris.persistence.distributed-cache-invalidations.service-names",
+              "=127.0.0.1,=127.1.2.3")
+          .build();
+    }
+  }
+
+  static final String TOKEN = "otherToken";
+  static final String ENDPOINT = "/polaris-management/cache-coherency";
+
+  // MUST be constant for test AND service
+  static final int QUARKUS_MANAGEMENT_PORT = 64321;
+
+  static URI CACHE_INVALIDATIONS_ENDPOINT =
+      URI.create(
+          format(
+              "http://127.0.0.1:%d%s?sender="; + UUID.randomUUID(),
+              QUARKUS_MANAGEMENT_PORT,
+              ENDPOINT));
+
+  SoftAssertions soft;
+  ObjectMapper mapper;
+
+  @Inject CacheBackend cacheBackend;
+
+  @Inject @SystemPersistence Persistence systemPersistence;
+  @Inject RealmPersistenceFactory realmPersistenceFactory;
+
+  @BeforeEach
+  public void before() {
+    soft = new SoftAssertions();
+    mapper = new ObjectMapper();
+  }
+
+  @AfterEach
+  public void after() {
+    soft.assertAll();
+  }
+
+  @Test
+  public void systemRealm() throws Exception {
+    sendReceive(systemPersistence);
+  }
+
+  @Test
+  public void otherRealm() throws Exception {
+    var persistence = 
realmPersistenceFactory.newBuilder().realmId("foo").build();
+    sendReceive(persistence);
+  }
+
+  private void sendReceive(Persistence persistence) throws Exception {
+    var queue = new LinkedBlockingQueue<Map.Entry<URI, String>>();
+    try (var ignore =
+        new HttpTestServer(
+            new InetSocketAddress("127.1.2.3", QUARKUS_MANAGEMENT_PORT),
+            ENDPOINT,
+            exchange -> {
+              try (InputStream requestBody = exchange.getRequestBody()) {
+                queue.add(
+                    entry(exchange.getRequestURI(), new 
String(requestBody.readAllBytes(), UTF_8)));
+              } catch (Exception e) {
+                throw new RuntimeException(e);
+              }
+              exchange.sendResponseHeaders(204, -1);
+              exchange.getResponseBody().close();
+            })) {
+
+      var obj = 
SimpleTestObj.builder().id(persistence.generateId()).text("test").build();
+      persistence.write(obj, SimpleTestObj.class);
+
+      // verify that "we" received the invalidation for the obj
+      var invalidation = queue.poll(1, TimeUnit.MINUTES);
+      var uri = requireNonNull(invalidation).getKey();
+      soft.assertThat(uri.getRawQuery()).startsWith("sender=");
+      var invalidations = mapper.readValue(invalidation.getValue(), 
CacheInvalidations.class);
+      soft.assertThat(invalidations)
+          .extracting(CacheInvalidations::invalidations, 
list(CacheInvalidation.class))
+          .containsExactly(cacheInvalidationEvictObj(persistence.realmId(), 
objRef(obj)));
+
+      // verify that "the service" processes an invalidation
+      soft.assertThat(cacheBackend.get(persistence.realmId(), 
objRef(obj))).isNotNull();
+      send(invalidations);
+      awaitCondition(
+          () -> assertThat(cacheBackend.get(persistence.realmId(), 
objRef(obj))).isNull());
+
+      // reference
+
+      var refName = "foo-ref";
+      persistence.createReference(refName, Optional.empty());
+
+      // verify that "we" received the invalidation for the reference
+      invalidation = queue.poll(1, TimeUnit.MINUTES);
+      uri = requireNonNull(invalidation).getKey();
+      soft.assertThat(uri.getRawQuery()).startsWith("sender=");
+      invalidations = mapper.readValue(invalidation.getValue(), 
CacheInvalidations.class);
+      soft.assertThat(invalidations)
+          .extracting(CacheInvalidations::invalidations, 
list(CacheInvalidation.class))
+          
.containsExactly(cacheInvalidationEvictReference(persistence.realmId(), 
refName));
+
+      // verify that "the service" processes an invalidation
+      soft.assertThat(cacheBackend.getReference(persistence.realmId(), 
refName)).isNotNull();
+      send(invalidations);
+      awaitCondition(
+          () -> assertThat(cacheBackend.get(persistence.realmId(), 
objRef(obj))).isNull());
+    }
+  }
+
+  @SuppressWarnings("BusyWait")
+  private void awaitCondition(Runnable test) throws Exception {
+    var tEnd = System.nanoTime() + TimeUnit.MINUTES.toNanos(1);
+    while (System.nanoTime() < tEnd) {
+      try {
+        test.run();
+        return;
+      } catch (AssertionError e) {
+        if (System.nanoTime() > tEnd) {
+          throw e;
+        }
+      }
+      Thread.sleep(1);
+    }
+  }
+
+  private void send(CacheInvalidations invalidations) throws Exception {
+    var urlConn = CACHE_INVALIDATIONS_ENDPOINT.toURL().openConnection();
+    urlConn.setDoOutput(true);
+    urlConn.setRequestProperty("Content-Type", APPLICATION_JSON);
+    urlConn.setRequestProperty("Polaris-Cache-Invalidation-Token", TOKEN);
+    try (var out = urlConn.getOutputStream()) {
+      out.write(mapper.writeValueAsBytes(invalidations));
+    }
+    urlConn.getInputStream().readAllBytes();
+  }
+}


Reply via email to