You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@amoro.apache.org by na...@apache.org on 2026/08/03 14:48:59 UTC
(amoro) 01/02: feat: initialize LAS network integration
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 <ma...@bytedance.com>
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>