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

czy006 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/amoro.git


The following commit(s) were added to refs/heads/master by this push:
     new ec2a448b3 [AMORO-4190][ams] Graceful handling of catalog 
initialization failures in DefaultCatalogManager (#4189)
ec2a448b3 is described below

commit ec2a448b32d22dbc25b8f8586cef65897e02b263
Author: Jiwon Park <[email protected]>
AuthorDate: Wed Apr 22 18:04:52 2026 +0900

    [AMORO-4190][ams] Graceful handling of catalog initialization failures in 
DefaultCatalogManager (#4189)
    
    [Improvement] Graceful handling of catalog initialization failures in 
DefaultCatalogManager
    
    - Wrap individual catalog loading in try-catch during initialization so
      that a single catalog failure is logged and skipped rather than crashing 
AMS
    - When a catalog is not present in serverCatalogMap (failed to initialize),
      fall back to updating DB directly and attempt to rebuild with the new 
metadata
    - Add tests verifying both initialization resilience and update recovery
    
    Signed-off-by: Jiwon Park <[email protected]>
---
 .../server/catalog/DefaultCatalogManager.java      |  57 +++++++--
 .../catalog/TestDefaultCatalogManagerRecovery.java | 127 +++++++++++++++++++++
 2 files changed, 174 insertions(+), 10 deletions(-)

diff --git 
a/amoro-ams/src/main/java/org/apache/amoro/server/catalog/DefaultCatalogManager.java
 
b/amoro-ams/src/main/java/org/apache/amoro/server/catalog/DefaultCatalogManager.java
index ce56959be..6535283da 100644
--- 
a/amoro-ams/src/main/java/org/apache/amoro/server/catalog/DefaultCatalogManager.java
+++ 
b/amoro-ams/src/main/java/org/apache/amoro/server/catalog/DefaultCatalogManager.java
@@ -80,11 +80,19 @@ public class DefaultCatalogManager extends PersistentBase 
implements CatalogMana
     listCatalogMetas()
         .forEach(
             c -> {
-              ServerCatalog serverCatalog =
-                  CatalogBuilder.buildServerCatalog(c, serverConfiguration);
-              serverCatalogMap.put(c.getCatalogName(), serverCatalog);
-              metaCache.put(c.getCatalogName(), Optional.of(c));
-              LOG.info("Load catalog {}, type:{}", c.getCatalogName(), 
c.getCatalogType());
+              try {
+                ServerCatalog serverCatalog =
+                    CatalogBuilder.buildServerCatalog(c, serverConfiguration);
+                serverCatalogMap.put(c.getCatalogName(), serverCatalog);
+                metaCache.put(c.getCatalogName(), Optional.of(c));
+                LOG.info("Load catalog {}, type:{}", c.getCatalogName(), 
c.getCatalogType());
+              } catch (Exception e) {
+                LOG.error(
+                    "Failed to load catalog {}, type:{}, skipping",
+                    c.getCatalogName(),
+                    c.getCatalogType(),
+                    e);
+              }
             });
     LOG.info("DefaultCatalogManager initialized, total catalogs: {}", 
serverCatalogMap.size());
   }
@@ -137,6 +145,11 @@ public class DefaultCatalogManager extends PersistentBase 
implements CatalogMana
     serverCatalogMap.put(catalog.name(), catalog);
   }
 
+  @VisibleForTesting
+  public void removeServerCatalog(String catalogName) {
+    serverCatalogMap.remove(catalogName);
+  }
+
   @Override
   public InternalCatalog getInternalCatalog(String catalogName) {
     ServerCatalog serverCatalog = getServerCatalog(catalogName);
@@ -216,12 +229,36 @@ public class DefaultCatalogManager extends PersistentBase 
implements CatalogMana
 
   @Override
   public void updateCatalog(CatalogMeta catalogMeta) {
-    ServerCatalog catalog = getServerCatalog(catalogMeta.getCatalogName());
-    validateCatalogUpdate(catalog.getMetadata(), catalogMeta);
+    String catalogName = catalogMeta.getCatalogName();
+    ServerCatalog catalog = serverCatalogMap.get(catalogName);
+
+    if (catalog != null) {
+      validateCatalogUpdate(catalog.getMetadata(), catalogMeta);
+      catalog.updateMetadata(catalogMeta);
+    } else {
+      // Catalog failed to load during initialization — update DB directly
+      // and attempt to rebuild with the new metadata
+      CatalogMeta oldMeta =
+          getAs(CatalogMetaMapper.class, mapper -> 
mapper.getCatalog(catalogName));
+      if (oldMeta == null) {
+        throw new ObjectNotExistsException("Catalog " + catalogName);
+      }
+      validateCatalogUpdate(oldMeta, catalogMeta);
+      doAs(CatalogMetaMapper.class, mapper -> 
mapper.updateCatalog(catalogMeta));
+      try {
+        ServerCatalog rebuilt = CatalogBuilder.buildServerCatalog(catalogMeta, 
serverConfiguration);
+        serverCatalogMap.put(catalogName, rebuilt);
+        LOG.info("Catalog {} rebuilt successfully after metadata update", 
catalogName);
+      } catch (Exception e) {
+        LOG.warn(
+            "Catalog {} metadata updated in DB but failed to initialize: {}",
+            catalogName,
+            e.getMessage());
+      }
+    }
 
-    catalog.updateMetadata(catalogMeta);
-    metaCache.invalidate(catalogMeta.getCatalogName());
-    LOG.info("Update catalog metadata: {}", catalogMeta.getCatalogName());
+    metaCache.invalidate(catalogName);
+    LOG.info("Updated catalog metadata: {}", catalogName);
   }
 
   @Override
diff --git 
a/amoro-ams/src/test/java/org/apache/amoro/server/catalog/TestDefaultCatalogManagerRecovery.java
 
b/amoro-ams/src/test/java/org/apache/amoro/server/catalog/TestDefaultCatalogManagerRecovery.java
new file mode 100644
index 000000000..453430309
--- /dev/null
+++ 
b/amoro-ams/src/test/java/org/apache/amoro/server/catalog/TestDefaultCatalogManagerRecovery.java
@@ -0,0 +1,127 @@
+/*
+ * 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.amoro.server.catalog;
+
+import org.apache.amoro.TableFormat;
+import org.apache.amoro.api.CatalogMeta;
+import org.apache.amoro.catalog.CatalogTestHelpers;
+import org.apache.amoro.config.Configurations;
+import org.apache.amoro.properties.CatalogMetaProperties;
+import org.apache.amoro.server.AMSManagerTestBase;
+import org.apache.amoro.server.persistence.PersistentBase;
+import org.apache.amoro.server.persistence.mapper.CatalogMetaMapper;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Tests for {@link DefaultCatalogManager} graceful handling of catalog 
initialization failures.
+ * Verifies that:
+ *
+ * <ol>
+ *   <li>A single catalog failure does not crash the entire AMS during startup
+ *   <li>Failed catalogs can still be updated via the API to allow operational 
recovery
+ * </ol>
+ */
+public class TestDefaultCatalogManagerRecovery extends AMSManagerTestBase {
+
+  private static final String TEST_CATALOG_NAME = "test_failed_rest_catalog";
+
+  /** Helper to directly access the database, bypassing CatalogManager and 
buildServerCatalog. */
+  static class DirectDbAccess extends PersistentBase {
+    void insertCatalog(CatalogMeta meta) {
+      doAs(CatalogMetaMapper.class, mapper -> mapper.insertCatalog(meta));
+    }
+
+    void deleteCatalog(String name) {
+      doAs(CatalogMetaMapper.class, mapper -> mapper.deleteCatalog(name));
+    }
+  }
+
+  private static final DirectDbAccess DB = new DirectDbAccess();
+
+  private CatalogMeta buildUnreachableRestCatalog() {
+    Map<String, String> properties = new HashMap<>();
+    properties.put("uri", "http://localhost:1/nonexistent";);
+    return CatalogTestHelpers.buildCatalogMeta(
+        TEST_CATALOG_NAME,
+        CatalogMetaProperties.CATALOG_TYPE_REST,
+        properties,
+        TableFormat.ICEBERG);
+  }
+
+  @After
+  public void cleanup() {
+    CATALOG_MANAGER.removeServerCatalog(TEST_CATALOG_NAME);
+    try {
+      DB.deleteCatalog(TEST_CATALOG_NAME);
+    } catch (Exception e) {
+      // ignore
+    }
+  }
+
+  /**
+   * Verifies that DefaultCatalogManager initialization does not crash when a 
catalog in the
+   * database fails to load (e.g., an unreachable REST endpoint). The failed 
catalog should be
+   * skipped and other catalogs should still be available.
+   */
+  @Test
+  public void testInitializationSkipsFailedCatalog() {
+    // Insert a REST catalog with an unreachable endpoint directly into DB
+    DB.insertCatalog(buildUnreachableRestCatalog());
+
+    // Creating a new DefaultCatalogManager should NOT throw — the failed 
catalog is skipped
+    DefaultCatalogManager manager = new DefaultCatalogManager(new 
Configurations());
+
+    // The manager should be functional — the catalog exists in DB but was 
skipped during init
+    Assert.assertTrue(manager.catalogExist(TEST_CATALOG_NAME));
+    // Verify that the catalog metadata is still readable from DB
+    Assert.assertNotNull(manager.getCatalogMeta(TEST_CATALOG_NAME));
+  }
+
+  /**
+   * Simulates a REST catalog that failed to initialize (exists in DB but not 
in serverCatalogMap),
+   * then verifies that updateCatalog() can update its metadata without 
throwing. This is the core
+   * recovery scenario — operators must be able to fix catalog configuration 
(e.g., correcting a
+   * URI) from the dashboard without restarting AMS.
+   */
+  @Test
+  public void testUpdateCatalogThatFailedToInitialize() {
+    // Insert directly into DB — simulates the state after init try-catch 
skips a failed catalog
+    CatalogMeta meta = buildUnreachableRestCatalog();
+    DB.insertCatalog(meta);
+
+    Assert.assertTrue(CATALOG_MANAGER.catalogExist(TEST_CATALOG_NAME));
+
+    // Update the catalog metadata — this must succeed
+    CatalogMeta updatedMeta = new CatalogMeta(meta);
+    updatedMeta.getCatalogProperties().put("uri", 
"http://localhost:2/also-unreachable";);
+    updatedMeta.getCatalogProperties().put("new-property", "new-value");
+    CATALOG_MANAGER.updateCatalog(updatedMeta);
+
+    // Verify the update was persisted
+    CatalogMeta readMeta = CATALOG_MANAGER.getCatalogMeta(TEST_CATALOG_NAME);
+    Assert.assertEquals(
+        "http://localhost:2/also-unreachable";, 
readMeta.getCatalogProperties().get("uri"));
+    Assert.assertEquals("new-value", 
readMeta.getCatalogProperties().get("new-property"));
+  }
+}

Reply via email to