jerryshao commented on code in PR #13367:
URL: https://github.com/apache/gravitino/pull/13367#discussion_r4060110182


##########
core/src/main/java/org/apache/gravitino/GravitinoEnv.java:
##########
@@ -229,24 +231,43 @@ public static GravitinoEnv getInstance() {
    */
   public void initializeBaseComponents(Config config) {
     LOG.info("Initializing Gravitino base environment...");
-    this.config = config;
-    FileFetcher.get().initialize(config.get(Configs.BLOCK_UNSAFE_REMOTE_URI));
-    SecretPropertyUtils.configureSensitiveKeyKeywords(config);
+    initializeConfig(config);
     this.manageFullComponents = false;
     initBaseComponents();
     LOG.info("Gravitino base environment is initialized.");
   }
 
+  /**
+   * Initializes components required for normalized metadata operations.
+   *
+   * <p>This initialization profile does not initialize event listeners, audit 
logging, metadata
+   * hooks, auxiliary services, or job management.
+   *
+   * <p>This method must be invoked on the singleton returned by {@link 
#getInstance()}, because
+   * metadata components use environment-scoped dependencies.
+   *
+   * @param config The configuration object to initialize the environment.
+   */
+  public void initializeMetadataComponents(Config config) {
+    Preconditions.checkState(
+        this == getInstance(),
+        "Metadata components must be initialized on 
GravitinoEnv.getInstance().");

Review Comment:
   [Question] This guard establishes that there is exactly one env, but nothing 
checks whether that env is *already* initialized. Called on a singleton that 
has already run `initializeFullComponents` (or this method), it silently 
overwrites `entityStore`, `catalogManager`, `lockManager` and `idGenerator` 
without shutting the previous ones down -- `initEntityStoreAndCatalogManager()` 
(`GravitinoEnv.java:1031-1043`) assigns unconditionally, and no init path nulls 
or closes what was there.
   
   For the server that is harmless because startup happens once, but this 
profile is advertised for embedded hosts that may already hold an env. While 
you are formalizing the contract, is a second `checkState` (e.g. `entityStore 
== null`) or an explicitly documented re-init policy worth adding?
   
   Verified by: read `initializeBaseComponents`, 
`initializeMetadataComponents`, `initializeFullComponents` and 
`initEntityStoreAndCatalogManager` on this branch.



##########
core/src/main/java/org/apache/gravitino/GravitinoEnv.java:
##########
@@ -229,24 +231,43 @@ public static GravitinoEnv getInstance() {
    */
   public void initializeBaseComponents(Config config) {
     LOG.info("Initializing Gravitino base environment...");
-    this.config = config;
-    FileFetcher.get().initialize(config.get(Configs.BLOCK_UNSAFE_REMOTE_URI));
-    SecretPropertyUtils.configureSensitiveKeyKeywords(config);
+    initializeConfig(config);
     this.manageFullComponents = false;
     initBaseComponents();
     LOG.info("Gravitino base environment is initialized.");
   }
 
+  /**
+   * Initializes components required for normalized metadata operations.
+   *
+   * <p>This initialization profile does not initialize event listeners, audit 
logging, metadata
+   * hooks, auxiliary services, or job management.
+   *
+   * <p>This method must be invoked on the singleton returned by {@link 
#getInstance()}, because
+   * metadata components use environment-scoped dependencies.

Review Comment:
   [Nit] The stated reason reads backwards. "environment-scoped dependencies" 
suggests the components are scoped to *this* env, which would be an argument 
for allowing any instance. The actual reason is the opposite: several 
components resolve their dependencies through the global 
`GravitinoEnv.getInstance()` instead of the env they were constructed on -- 
`lock/TreeLockUtils.java:46`, `catalog/TopicOperationDispatcher.java:106,142`, 
`metalake/MetalakeManager.java:573`, 
`storage/relational/service/SchemaMetaService.java:660`.
   
   Something like "... because some metadata components resolve their 
dependencies through `GravitinoEnv.getInstance()` rather than through this 
instance" would tell the next reader why the precondition on the line below 
exists.
   
   Verified by: read each of the four call sites on this branch.



##########
core/src/test/java/org/apache/gravitino/TestGravitinoEnvMetadataComponents.java:
##########
@@ -0,0 +1,299 @@
+/*
+ * 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.gravitino;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.withSettings;
+
+import java.util.Collections;
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.apache.gravitino.Entity.EntityType;
+import org.apache.gravitino.catalog.FilesetNormalizeDispatcher;
+import org.apache.gravitino.catalog.FilesetOperationDispatcher;
+import org.apache.gravitino.catalog.FunctionNormalizeDispatcher;
+import org.apache.gravitino.catalog.FunctionOperationDispatcher;
+import org.apache.gravitino.catalog.ModelNormalizeDispatcher;
+import org.apache.gravitino.catalog.ModelOperationDispatcher;
+import org.apache.gravitino.catalog.PartitionNormalizeDispatcher;
+import org.apache.gravitino.catalog.PartitionOperationDispatcher;
+import org.apache.gravitino.catalog.SchemaNormalizeDispatcher;
+import org.apache.gravitino.catalog.SchemaOperationDispatcher;
+import org.apache.gravitino.catalog.TableNormalizeDispatcher;
+import org.apache.gravitino.catalog.TableOperationDispatcher;
+import org.apache.gravitino.catalog.TopicNormalizeDispatcher;
+import org.apache.gravitino.catalog.TopicOperationDispatcher;
+import org.apache.gravitino.catalog.ViewNormalizeDispatcher;
+import org.apache.gravitino.catalog.ViewOperationDispatcher;
+import org.apache.gravitino.hook.FilesetHookDispatcher;
+import org.apache.gravitino.hook.FunctionHookDispatcher;
+import org.apache.gravitino.hook.ModelHookDispatcher;
+import org.apache.gravitino.hook.SchemaHookDispatcher;
+import org.apache.gravitino.hook.TableHookDispatcher;
+import org.apache.gravitino.hook.TopicHookDispatcher;
+import org.apache.gravitino.hook.ViewHookDispatcher;
+import org.apache.gravitino.listener.FilesetEventDispatcher;
+import org.apache.gravitino.listener.FunctionEventDispatcher;
+import org.apache.gravitino.listener.ModelEventDispatcher;
+import org.apache.gravitino.listener.PartitionEventDispatcher;
+import org.apache.gravitino.listener.SchemaEventDispatcher;
+import org.apache.gravitino.listener.StatisticEventDispatcher;
+import org.apache.gravitino.listener.TableEventDispatcher;
+import org.apache.gravitino.listener.TopicEventDispatcher;
+import org.apache.gravitino.listener.ViewEventDispatcher;
+import org.apache.gravitino.meta.BaseMetalake;
+import org.apache.gravitino.stats.StatisticManager;
+import org.apache.gravitino.stats.storage.MemoryPartitionStatsStorageFactory;
+import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.mockito.MockedStatic;
+
+@TestMethodOrder(OrderAnnotation.class)
+class TestGravitinoEnvMetadataComponents {
+
+  @Test
+  @Order(1)
+  void testMetadataProfileRejectsNonSingletonEnvironment() {
+    IllegalStateException exception =
+        assertThrows(
+            IllegalStateException.class,
+            () -> new 
TestGravitinoEnv().initializeMetadataComponents(metadataConfig(false)));
+
+    assertEquals(
+        "Metadata components must be initialized on 
GravitinoEnv.getInstance().",
+        exception.getMessage());
+  }
+
+  @Test
+  @Order(2)
+  void 
testMetadataProfileProvidesCompleteMetadataAccessWithoutServerServices() throws 
Exception {
+    Config config = metadataConfig(false);
+    EntityStore entityStore = relationStore();
+    GravitinoEnv env = GravitinoEnv.getInstance();

Review Comment:
   [Important] These tests now initialize and shut down the real process-wide 
singleton and never put it back, so whatever this class leaves behind is 
inherited by the rest of the `core` test JVM.
   
   `shutdown()` (`GravitinoEnv.java:800-863`) closes components but nulls only 
`jobOperationDispatcher`. `entityStore`, `catalogManager`, `lockManager`, 
`idGenerator`, `config`, the `internal*` dispatchers and 
`internalAccessControlDispatcher` all keep their values, so after this class 
`GravitinoEnv.getInstance()` permanently holds a mocked `EntityStore`, a closed 
`CatalogManager`/`MetricsSystem`/`SecretManager`, and this test's `Config`.
   
   That matters because production code resolves through the singleton rather 
than through the env it was built on: 
`authorization/AuthorizationUtils.java:332,393,547` branch on 
`GravitinoEnv.getInstance().internalAccessControlDispatcher() != null`, 
`lock/TreeLockUtils.java:46` on `lockManager()`, 
`metalake/MetalakeManager.java:158,573` on `entityStore()`/`catalogManager()`, 
`storage/relational/service/SchemaMetaService.java:660` on `idGenerator()` (it 
throws `IllegalStateException` when that is null), 
`connector/BaseCatalog.java:570` on `config()`. 46 test classes under 
`core/src/test` already reach into the singleton, `build.gradle.kts` sets no 
`forkEvery`, so they all share one JVM, and JUnit fixes no order between 
classes. Running `testMetadataProfileProvidesInternalAuthorizationWhenEnabled` 
alone via `--tests` leaves every later test in that JVM seeing authorization 
enabled on a half-configured env; running the whole class leaves them seeing a 
real `idGenerator` where they previ
 ously saw null.
   
   I could not point at a test that fails today, so this is order-dependence 
rather than a live break -- but the earlier version of this file avoided most 
of it by using a `TestGravitinoEnv` subclass, and the singleton contract added 
in this commit makes that no longer possible. Suggest snapshotting the 
singleton's fields before 
`initializeMetadataComponents`/`initializeFullComponents` and restoring them in 
the `finally`/`@AfterEach`, so the class leaves the env exactly as it found it.
   
   Verified by: read `shutdown()` and `initEntityStoreAndCatalogManager()` 
(`GravitinoEnv.java:1031-1043`) on this branch; grepped 
`GravitinoEnv.getInstance()` across `*/src/main` and `core/src/test`; confirmed 
`build.gradle.kts` sets neither `forkEvery` nor `maxParallelForks` for the test 
tasks.



##########
core/src/main/java/org/apache/gravitino/GravitinoEnv.java:
##########
@@ -739,13 +771,26 @@ public StatisticDispatcher statisticDispatcher() {
     return statisticDispatcher;
   }
 
+  /**
+   * Get the internal StatisticDispatcher associated with the Gravitino 
environment.
+   *
+   * @return The internal StatisticDispatcher instance.
+   */
+  public StatisticDispatcher internalStatisticDispatcher() {
+    Preconditions.checkArgument(
+        internalStatisticDispatcher != null, "GravitinoEnv is not 
initialized.");
+    return internalStatisticDispatcher;

Review Comment:
   [Question] Both accessors this PR adds are still unreferenced outside this 
file and the new test: a repo-wide grep for `internalPartitionDispatcher()` and 
`internalStatisticDispatcher()` returns only `GravitinoEnv.java` and 
`TestGravitinoEnvMetadataComponents.java:116,126,258,263`. That is defensible 
if the metadata profile is meant to be driven from outside this repo, but it 
does mean the public surface added here is never exercised by a real consumer. 
Is the first caller close behind, or would these be better kept package-private 
until one exists?
   
   Minor inconsistency while you are here: this accessor throws 
`IllegalArgumentException` when unset, while its sibling 
`internalPartitionDispatcher()` (`GravitinoEnv.java:458-459`) returns `null` in 
the same situation, so callers of the two internal dispatchers need two 
different not-initialized checks.
   
   Verified by: repo-wide grep for both symbols on this branch; read both 
accessors.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to