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

majin1102 pushed a commit to branch emr/initialize-network-integration
in repository https://gitbox.apache.org/repos/asf/amoro.git

commit f049e009468b03176a1e6c80252cb8725693654f
Author: majin.nathan <[email protected]>
AuthorDate: Mon Aug 3 17:04:20 2026 +0800

    feat: initialize LAS network integration
---
 amoro-ams/pom.xml                                  |  24 +++
 .../amoro/server/las/LasIntegrationConfig.java     | 122 +++++++++++++
 .../amoro/server/las/LasIntegrationContext.java    | 191 +++++++++++++++++++++
 .../apache/amoro/server/las/LasRestExtension.java  | 123 +++++++++++++
 .../org.apache.amoro.server.RestExtensionFactory   |   1 +
 .../server/las/TestLasIntegrationContext.java      |  86 ++++++++++
 dist/src/main/amoro-bin/conf/config.yaml           |  23 +++
 .../amoro-bin/conf/plugins/rest-extensions.yaml    |   4 +
 pom.xml                                            |  11 ++
 9 files changed, 585 insertions(+)

diff --git a/amoro-ams/pom.xml b/amoro-ams/pom.xml
index af6fa6bb6..143d2c06f 100644
--- a/amoro-ams/pom.xml
+++ b/amoro-ams/pom.xml
@@ -64,6 +64,30 @@
             <artifactId>kubernetes-client</artifactId>
         </dependency>
 
+        <!-- LAS/EMR network integrations. Concrete clients are initialized 
lazily by AMS. -->
+        <dependency>
+            <groupId>io.proton</groupId>
+            <artifactId>proton-hadoop3-bundle</artifactId>
+            <version>${proton.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.bytedance.las</groupId>
+            <artifactId>iam</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.fasterxml.jackson.core</groupId>
+                    <artifactId>jackson-databind</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>com.volcengine.emr.serverless</groupId>
+            <artifactId>serverless-sdk-query</artifactId>
+            <version>${serverless-sdk-query.version}</version>
+        </dependency>
+
         <dependency>
             <groupId>org.apache.spark</groupId>
             <artifactId>spark-core_${scala.binary.version}</artifactId>
diff --git 
a/amoro-ams/src/main/java/org/apache/amoro/server/las/LasIntegrationConfig.java 
b/amoro-ams/src/main/java/org/apache/amoro/server/las/LasIntegrationConfig.java
new file mode 100644
index 000000000..cd119ee09
--- /dev/null
+++ 
b/amoro-ams/src/main/java/org/apache/amoro/server/las/LasIntegrationConfig.java
@@ -0,0 +1,122 @@
+/*
+ * 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.las;
+
+import org.apache.amoro.config.ConfigOption;
+import org.apache.amoro.config.ConfigOptions;
+
+import java.time.Duration;
+
+/** Configuration used to connect AMS to services in the LAS/EMR network. */
+public final class LasIntegrationConfig {
+
+  private static final String PREFIX = "las.integration.";
+
+  public static final ConfigOption<Boolean> ENABLED =
+      ConfigOptions.key(PREFIX + "enabled")
+          .booleanType()
+          .defaultValue(false)
+          .withDescription("Whether the LAS/EMR integration is enabled.");
+
+  public static final ConfigOption<String> HMS_URI =
+      ConfigOptions.key(PREFIX + "hms-uri")
+          .stringType()
+          .noDefaultValue()
+          .withDescription("HMS3 Thrift URI used as the metadata source of 
truth.");
+
+  public static final ConfigOption<String> TOS_ENDPOINT =
+      ConfigOptions.key(PREFIX + "tos-endpoint")
+          .stringType()
+          .noDefaultValue()
+          .withDescription("TOS endpoint used by Proton file system clients.");
+
+  public static final ConfigOption<String> IAM_ENDPOINT =
+      ConfigOptions.key(PREFIX + "iam-endpoint")
+          .stringType()
+          .noDefaultValue()
+          .withDescription("IAM endpoint used to obtain short-lived workload 
credentials.");
+
+  public static final ConfigOption<String> EMR_SERVERLESS_ENDPOINT =
+      ConfigOptions.key(PREFIX + "emr-serverless-endpoint")
+          .stringType()
+          .noDefaultValue()
+          .withDescription("EMR Serverless endpoint used to submit maintenance 
executors.");
+
+  public static final ConfigOption<String> EMR_SERVERLESS_SERVICE =
+      ConfigOptions.key(PREFIX + "emr-serverless-service")
+          .stringType()
+          .defaultValue("emr_serverless")
+          .withDescription("Service name used by the EMR Serverless client.");
+
+  public static final ConfigOption<String> REGION =
+      ConfigOptions.key(PREFIX + "region")
+          .stringType()
+          .defaultValue("cn-beijing")
+          .withDescription("Cloud region containing the LAS/EMR services.");
+
+  public static final ConfigOption<Duration> CONNECT_TIMEOUT =
+      ConfigOptions.key(PREFIX + "connect-timeout")
+          .durationType()
+          .defaultValue(Duration.ofSeconds(30))
+          .withDescription("Connection timeout for LAS/EMR service clients.");
+
+  public static final ConfigOption<Duration> READ_TIMEOUT =
+      ConfigOptions.key(PREFIX + "read-timeout")
+          .durationType()
+          .defaultValue(Duration.ofSeconds(30))
+          .withDescription("Socket read timeout for LAS/EMR service clients.");
+
+  public static final ConfigOption<String> OPTIMIZER_JAR_URI =
+      ConfigOptions.key(PREFIX + "optimizer-jar-uri")
+          .stringType()
+          .noDefaultValue()
+          .withDescription("URI of the optimizer jar submitted to EMR 
Serverless.");
+
+  public static final ConfigOption<Boolean> CROSS_VPC_ENABLED =
+      ConfigOptions.key(PREFIX + "cross-vpc.enabled")
+          .booleanType()
+          .defaultValue(false)
+          .withDescription("Whether EMR Serverless cross-VPC access is 
enabled.");
+
+  public static final ConfigOption<String> CROSS_VPC_ACCOUNT_ID =
+      ConfigOptions.key(PREFIX + "cross-vpc.account-id")
+          .stringType()
+          .noDefaultValue()
+          .withDescription("Account ID owning the VPC used by EMR 
Serverless.");
+
+  public static final ConfigOption<String> CROSS_VPC_VPC_ID =
+      ConfigOptions.key(PREFIX + "cross-vpc.vpc-id")
+          .stringType()
+          .noDefaultValue()
+          .withDescription("VPC ID used by EMR Serverless.");
+
+  public static final ConfigOption<String> CROSS_VPC_SUBNET_IDS =
+      ConfigOptions.key(PREFIX + "cross-vpc.subnet-ids")
+          .stringType()
+          .noDefaultValue()
+          .withDescription("Comma-separated subnet IDs used by EMR 
Serverless.");
+
+  public static final ConfigOption<String> CROSS_VPC_SECURITY_GROUP_ID =
+      ConfigOptions.key(PREFIX + "cross-vpc.security-group-id")
+          .stringType()
+          .noDefaultValue()
+          .withDescription("Security group ID used by EMR Serverless.");
+
+  private LasIntegrationConfig() {}
+}
diff --git 
a/amoro-ams/src/main/java/org/apache/amoro/server/las/LasIntegrationContext.java
 
b/amoro-ams/src/main/java/org/apache/amoro/server/las/LasIntegrationContext.java
new file mode 100644
index 000000000..ca4c6f76c
--- /dev/null
+++ 
b/amoro-ams/src/main/java/org/apache/amoro/server/las/LasIntegrationContext.java
@@ -0,0 +1,191 @@
+/*
+ * 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.las;
+
+import org.apache.amoro.config.ConfigOption;
+import org.apache.amoro.config.Configurations;
+import org.apache.amoro.hive.CachedHiveClientPool;
+import org.apache.amoro.shade.guava32.com.google.common.collect.Maps;
+import org.apache.amoro.table.TableMetaStore;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hadoop.conf.Configuration;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/** Validated network configuration and client factories shared by future LAS 
OpenAPI handlers. */
+public final class LasIntegrationContext {
+
+  private static final Set<String> HTTP_SCHEMES = new 
HashSet<>(Arrays.asList("http", "https"));
+  private static final Set<String> OPTIMIZER_JAR_SCHEMES =
+      new HashSet<>(Arrays.asList("tos", "http", "https"));
+
+  private final boolean enabled;
+  private final URI hmsUri;
+  private final URI tosEndpoint;
+  private final URI iamEndpoint;
+  private final URI emrServerlessEndpoint;
+  private final URI optimizerJarUri;
+  private final String region;
+  private final String emrServerlessService;
+  private final Duration connectTimeout;
+  private final Duration readTimeout;
+
+  private LasIntegrationContext(Configurations configurations) {
+    this.enabled = configurations.getBoolean(LasIntegrationConfig.ENABLED);
+    this.region = configurations.getString(LasIntegrationConfig.REGION);
+    this.emrServerlessService =
+        configurations.getString(LasIntegrationConfig.EMR_SERVERLESS_SERVICE);
+    this.connectTimeout = 
configurations.get(LasIntegrationConfig.CONNECT_TIMEOUT);
+    this.readTimeout = configurations.get(LasIntegrationConfig.READ_TIMEOUT);
+
+    if (!enabled) {
+      this.hmsUri = null;
+      this.tosEndpoint = null;
+      this.iamEndpoint = null;
+      this.emrServerlessEndpoint = null;
+      this.optimizerJarUri = null;
+      return;
+    }
+
+    this.hmsUri =
+        requiredUri(configurations, LasIntegrationConfig.HMS_URI, 
Collections.singleton("thrift"));
+    this.tosEndpoint = requiredUri(configurations, 
LasIntegrationConfig.TOS_ENDPOINT, HTTP_SCHEMES);
+    this.iamEndpoint = requiredUri(configurations, 
LasIntegrationConfig.IAM_ENDPOINT, HTTP_SCHEMES);
+    this.emrServerlessEndpoint =
+        requiredUri(configurations, 
LasIntegrationConfig.EMR_SERVERLESS_ENDPOINT, HTTP_SCHEMES);
+    this.optimizerJarUri =
+        requiredUri(configurations, LasIntegrationConfig.OPTIMIZER_JAR_URI, 
OPTIMIZER_JAR_SCHEMES);
+    requiredString(configurations, LasIntegrationConfig.REGION);
+    requiredString(configurations, 
LasIntegrationConfig.EMR_SERVERLESS_SERVICE);
+    positiveDuration(configurations, LasIntegrationConfig.CONNECT_TIMEOUT);
+    positiveDuration(configurations, LasIntegrationConfig.READ_TIMEOUT);
+
+    if (configurations.getBoolean(LasIntegrationConfig.CROSS_VPC_ENABLED)) {
+      requiredString(configurations, 
LasIntegrationConfig.CROSS_VPC_ACCOUNT_ID);
+      requiredString(configurations, LasIntegrationConfig.CROSS_VPC_VPC_ID);
+      requiredString(configurations, 
LasIntegrationConfig.CROSS_VPC_SUBNET_IDS);
+      requiredString(configurations, 
LasIntegrationConfig.CROSS_VPC_SECURITY_GROUP_ID);
+    }
+  }
+
+  public static LasIntegrationContext initialize(Configurations 
configurations) {
+    return new LasIntegrationContext(configurations);
+  }
+
+  public boolean enabled() {
+    return enabled;
+  }
+
+  public CachedHiveClientPool newHmsClientPool() {
+    ensureEnabled();
+    Configuration configuration = new Configuration();
+    configuration.set("hive.metastore.uris", hmsUri.toString());
+    TableMetaStore metaStore = 
TableMetaStore.builder().withConfiguration(configuration).build();
+    return new CachedHiveClientPool(metaStore, Maps.newHashMap());
+  }
+
+  public Configuration newTosConfiguration() {
+    ensureEnabled();
+    Configuration configuration = new Configuration(false);
+    configuration.set("fs.AbstractFileSystem.tos.impl", 
"io.proton.fs.ProtonFS");
+    configuration.set("fs.tos.impl", "io.proton.fs.ProtonFileSystem");
+    configuration.set("fs.tos.endpoint", tosEndpoint.toString());
+    return configuration;
+  }
+
+  public URI hmsUri() {
+    ensureEnabled();
+    return hmsUri;
+  }
+
+  public URI iamEndpoint() {
+    ensureEnabled();
+    return iamEndpoint;
+  }
+
+  public URI emrServerlessEndpoint() {
+    ensureEnabled();
+    return emrServerlessEndpoint;
+  }
+
+  public URI optimizerJarUri() {
+    ensureEnabled();
+    return optimizerJarUri;
+  }
+
+  public String region() {
+    return region;
+  }
+
+  public String emrServerlessService() {
+    return emrServerlessService;
+  }
+
+  public Duration connectTimeout() {
+    return connectTimeout;
+  }
+
+  public Duration readTimeout() {
+    return readTimeout;
+  }
+
+  private void ensureEnabled() {
+    if (!enabled) {
+      throw new IllegalStateException("LAS/EMR integration is disabled");
+    }
+  }
+
+  private static URI requiredUri(
+      Configurations configurations, ConfigOption<String> option, Set<String> 
allowedSchemes) {
+    String value = requiredString(configurations, option);
+    try {
+      URI uri = new URI(value);
+      if (!allowedSchemes.contains(uri.getScheme()) || 
StringUtils.isBlank(uri.getHost())) {
+        throw new IllegalArgumentException(
+            String.format("%s must contain a supported scheme and host: %s", 
option.key(), value));
+      }
+      return uri;
+    } catch (URISyntaxException e) {
+      throw new IllegalArgumentException(option.key() + " is not a valid URI: 
" + value, e);
+    }
+  }
+
+  private static String requiredString(Configurations configurations, 
ConfigOption<String> option) {
+    String value = configurations.getString(option);
+    if (StringUtils.isBlank(value)) {
+      throw new IllegalArgumentException(option.key() + " must be configured");
+    }
+    return value;
+  }
+
+  private static Duration positiveDuration(
+      Configurations configurations, ConfigOption<Duration> option) {
+    Duration value = configurations.get(option);
+    if (value == null || value.isZero() || value.isNegative()) {
+      throw new IllegalArgumentException(option.key() + " must be greater than 
zero");
+    }
+    return value;
+  }
+}
diff --git 
a/amoro-ams/src/main/java/org/apache/amoro/server/las/LasRestExtension.java 
b/amoro-ams/src/main/java/org/apache/amoro/server/las/LasRestExtension.java
new file mode 100644
index 000000000..29676349f
--- /dev/null
+++ b/amoro-ams/src/main/java/org/apache/amoro/server/las/LasRestExtension.java
@@ -0,0 +1,123 @@
+/*
+ * 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.las;
+
+import io.javalin.apibuilder.EndpointGroup;
+import io.javalin.http.Context;
+import org.apache.amoro.config.Configurations;
+import org.apache.amoro.server.RestExtension;
+import org.apache.amoro.server.RestExtensionFactory;
+import org.apache.amoro.server.catalog.CatalogManager;
+import org.apache.amoro.server.table.TableManager;
+import org.apache.amoro.shade.guava32.com.google.common.base.Preconditions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+
+/** Bootstrap extension for LAS/EMR network integration. */
+public class LasRestExtension implements RestExtension {
+
+  private static final Logger LOG = 
LoggerFactory.getLogger(LasRestExtension.class);
+
+  private final LasIntegrationContext integrationContext;
+  private final CatalogManager catalogManager;
+  private final TableManager tableManager;
+
+  LasRestExtension(
+      LasIntegrationContext integrationContext,
+      CatalogManager catalogManager,
+      TableManager tableManager) {
+    this.integrationContext = integrationContext;
+    this.catalogManager = catalogManager;
+    this.tableManager = tableManager;
+    LOG.info("LAS/EMR integration initialized, enabled={}", 
integrationContext.enabled());
+  }
+
+  @Override
+  public EndpointGroup endpoints() {
+    return () -> {
+      // Intentionally empty. Add management-plane routes here under 
/api/ams/v1/las when the
+      // OpenAPI contract is ready. Controllers should receive 
integrationContext, catalogManager,
+      // and tableManager from this extension instead of constructing HMS, 
TOS, IAM, or EMR clients
+      // themselves. Routes registered here automatically pass through the 
existing AMS REST
+      // authentication filter; do not add management-plane routes to the URL 
whitelist.
+    };
+  }
+
+  @Override
+  public boolean needHandleException(Context ctx) {
+    return false;
+  }
+
+  @Override
+  public void handleException(Exception e, Context ctx) {
+    throw new UnsupportedOperationException("LAS REST extension does not 
expose endpoints yet", e);
+  }
+
+  /** Factory loaded by the AMS REST extension plugin manager. */
+  public static class Factory implements RestExtensionFactory {
+
+    private Configurations serviceConfig;
+    private CatalogManager catalogManager;
+    private TableManager tableManager;
+
+    @Override
+    public RestExtensionFactory withServiceConfig(Configurations 
serviceConfig) {
+      this.serviceConfig = serviceConfig;
+      return this;
+    }
+
+    @Override
+    public RestExtensionFactory withCatalogManager(CatalogManager 
catalogManager) {
+      this.catalogManager = catalogManager;
+      return this;
+    }
+
+    @Override
+    public RestExtensionFactory withTableManager(TableManager tableManager) {
+      this.tableManager = tableManager;
+      return this;
+    }
+
+    @Override
+    public RestExtension build() {
+      Preconditions.checkNotNull(serviceConfig, "serviceConfig is required");
+      Preconditions.checkNotNull(catalogManager, "catalogManager is required");
+      Preconditions.checkNotNull(tableManager, "tableManager is required");
+      return new LasRestExtension(
+          LasIntegrationContext.initialize(serviceConfig), catalogManager, 
tableManager);
+    }
+
+    @Override
+    public void open(Map<String, String> properties) {
+      LOG.info("Opening LAS/EMR integration extension");
+    }
+
+    @Override
+    public void close() {
+      LOG.info("Closing LAS/EMR integration extension");
+    }
+
+    @Override
+    public String name() {
+      return "las-integration";
+    }
+  }
+}
diff --git 
a/amoro-ams/src/main/resources/META-INF/services/org.apache.amoro.server.RestExtensionFactory
 
b/amoro-ams/src/main/resources/META-INF/services/org.apache.amoro.server.RestExtensionFactory
index a0b5cf404..a3f310817 100644
--- 
a/amoro-ams/src/main/resources/META-INF/services/org.apache.amoro.server.RestExtensionFactory
+++ 
b/amoro-ams/src/main/resources/META-INF/services/org.apache.amoro.server.RestExtensionFactory
@@ -17,3 +17,4 @@
 #
 
 org.apache.amoro.server.RestCatalogService$RestCatalogServiceFactory
+org.apache.amoro.server.las.LasRestExtension$Factory
diff --git 
a/amoro-ams/src/test/java/org/apache/amoro/server/las/TestLasIntegrationContext.java
 
b/amoro-ams/src/test/java/org/apache/amoro/server/las/TestLasIntegrationContext.java
new file mode 100644
index 000000000..fef97b5e8
--- /dev/null
+++ 
b/amoro-ams/src/test/java/org/apache/amoro/server/las/TestLasIntegrationContext.java
@@ -0,0 +1,86 @@
+/*
+ * 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.las;
+
+import org.apache.amoro.config.Configurations;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class TestLasIntegrationContext {
+
+  @Test
+  public void testDisabledByDefault() {
+    LasIntegrationContext context = LasIntegrationContext.initialize(new 
Configurations());
+
+    Assertions.assertFalse(context.enabled());
+    Assertions.assertThrows(IllegalStateException.class, 
context::newHmsClientPool);
+  }
+
+  @Test
+  public void testInitializeKubernetesServiceEndpoints() {
+    LasIntegrationContext context = 
LasIntegrationContext.initialize(validConfigurations());
+
+    Assertions.assertTrue(context.enabled());
+    Assertions.assertEquals("thrift://hms-service:9083", 
context.hmsUri().toString());
+    Assertions.assertEquals("https://iam.volcengineapi.com";, 
context.iamEndpoint().toString());
+    Assertions.assertEquals("https://emr-serverless";, 
context.emrServerlessEndpoint().toString());
+    Assertions.assertEquals("cn-beijing", context.region());
+    Assertions.assertNotNull(context.newHmsClientPool());
+    Assertions.assertEquals(
+        "https://tos-cn-beijing.volces.com";, 
context.newTosConfiguration().get("fs.tos.endpoint"));
+  }
+
+  @Test
+  public void testRejectMissingRequiredEndpoint() {
+    Configurations configurations = validConfigurations();
+    configurations.setString(LasIntegrationConfig.HMS_URI, "");
+
+    IllegalArgumentException exception =
+        Assertions.assertThrows(
+            IllegalArgumentException.class, () -> 
LasIntegrationContext.initialize(configurations));
+    
Assertions.assertTrue(exception.getMessage().contains(LasIntegrationConfig.HMS_URI.key()));
+  }
+
+  @Test
+  public void testCrossVpcConfigurationIsAtomic() {
+    Configurations configurations = validConfigurations();
+    configurations.setBoolean(LasIntegrationConfig.CROSS_VPC_ENABLED, true);
+
+    IllegalArgumentException exception =
+        Assertions.assertThrows(
+            IllegalArgumentException.class, () -> 
LasIntegrationContext.initialize(configurations));
+    Assertions.assertTrue(
+        
exception.getMessage().contains(LasIntegrationConfig.CROSS_VPC_ACCOUNT_ID.key()));
+  }
+
+  private static Configurations validConfigurations() {
+    Map<String, String> values = new HashMap<>();
+    values.put(LasIntegrationConfig.ENABLED.key(), "true");
+    values.put(LasIntegrationConfig.HMS_URI.key(), 
"thrift://hms-service:9083");
+    values.put(LasIntegrationConfig.TOS_ENDPOINT.key(), 
"https://tos-cn-beijing.volces.com";);
+    values.put(LasIntegrationConfig.IAM_ENDPOINT.key(), 
"https://iam.volcengineapi.com";);
+    values.put(LasIntegrationConfig.EMR_SERVERLESS_ENDPOINT.key(), 
"https://emr-serverless";);
+    values.put(
+        LasIntegrationConfig.OPTIMIZER_JAR_URI.key(), 
"tos://amoro-artifacts/amoro-optimizer.jar");
+    return Configurations.fromMap(values);
+  }
+}
diff --git a/dist/src/main/amoro-bin/conf/config.yaml 
b/dist/src/main/amoro-bin/conf/config.yaml
index f4cdaaac8..d505e3823 100644
--- a/dist/src/main/amoro-bin/conf/config.yaml
+++ b/dist/src/main/amoro-bin/conf/config.yaml
@@ -71,6 +71,29 @@ ams:
     #   set default-role: VIEWER only when you intentionally want authenticated
     #   users without a matched role mapping to receive read-only access.
 
+  # LAS/EMR integration bootstrap. The disabled default keeps existing 
deployments unchanged.
+  # Values may also be supplied through environment variables, for example:
+  # AMS_LAS_INTEGRATION_ENABLED=true
+  # 
AMS_LAS_INTEGRATION_HMS__URI=thrift://hms-service.<namespace>.svc.cluster.local:9083
+  las:
+    integration:
+      enabled: false
+      # hms-uri: thrift://hms-service.<namespace>.svc.cluster.local:9083
+      # tos-endpoint: https://tos-cn-beijing.volces.com
+      # iam-endpoint: https://iam.volcengineapi.com
+      # emr-serverless-endpoint: https://open.volcengineapi.com
+      # optimizer-jar-uri: tos://<bucket>/<path>/amoro-optimizer.jar
+      region: cn-beijing
+      emr-serverless-service: emr_serverless
+      connect-timeout: 30s
+      read-timeout: 30s
+      cross-vpc:
+        enabled: false
+        # account-id: <account-id>
+        # vpc-id: <vpc-id>
+        # subnet-ids: <subnet-id-1>,<subnet-id-2>
+        # security-group-id: <security-group-id>
+
   refresh-external-catalogs:
     interval: 3min # 180000
     thread-count: 10
diff --git a/dist/src/main/amoro-bin/conf/plugins/rest-extensions.yaml 
b/dist/src/main/amoro-bin/conf/plugins/rest-extensions.yaml
index ff2729d81..1ceca25d0 100644
--- a/dist/src/main/amoro-bin/conf/plugins/rest-extensions.yaml
+++ b/dist/src/main/amoro-bin/conf/plugins/rest-extensions.yaml
@@ -20,3 +20,7 @@
 rest-extensions:
   - name: iceberg-rest-catalog
     enabled: true
+  - name: las-integration
+    # This extension initializes the LAS/EMR integration context only. It 
intentionally exposes no
+    # HTTP endpoints until the management-plane API contract is implemented.
+    enabled: true
diff --git a/pom.xml b/pom.xml
index 40cc34ecb..edba971c8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -168,6 +168,9 @@
         <jcasbin.version>1.99.0</jcasbin.version>
         <fasterxml.jackson.version>2.14.2</fasterxml.jackson.version>
         <arrow.version>18.3.0</arrow.version>
+        
<las-dependency.version>catalog-1.0.23-SNAPSHOT</las-dependency.version>
+        <proton.version>2.3.5</proton.version>
+        <serverless-sdk-query.version>1.0.2</serverless-sdk-query.version>
         
<apache-directory-server.version>2.0.0-M15</apache-directory-server.version>
         
<apache-directory-api-all.version>1.0.0-M20</apache-directory-api-all.version>
         <rocksdb-dependency-scope>compile</rocksdb-dependency-scope>
@@ -1327,6 +1330,14 @@
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
+
+            <dependency>
+                <groupId>com.bytedance.las</groupId>
+                <artifactId>las-bom</artifactId>
+                <version>${las-dependency.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 

Reply via email to