This server runs independently of HMS and provides a REST API for Iceberg catalog operations. * It connects to an external HMS instance via Thrift. @@ -46,85 +42,47 @@ * *
Multiple instances can run behind a Kubernetes Service for load balancing. */ +@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) public class StandaloneRESTCatalogServer { private static final Logger LOG = LoggerFactory.getLogger(StandaloneRESTCatalogServer.class); private final Configuration conf; - private Server server; + private String restEndpoint; private int port; - public StandaloneRESTCatalogServer(Configuration conf) { - this.conf = conf; - } - /** - * Starts the standalone REST Catalog server. + * Constructor that accepts Configuration. + * Standard Hive approach - caller controls Configuration creation. */ - public void start() { + public StandaloneRESTCatalogServer(Configuration conf) { + this.conf = conf; + // Validate required configuration String thriftUris = MetastoreConf.getVar(conf, ConfVars.THRIFT_URIS); if (thriftUris == null || thriftUris.isEmpty()) { throw new IllegalArgumentException("metastore.thrift.uris must be configured to connect to HMS"); } - int servletPort = MetastoreConf.getIntVar(conf, ConfVars.CATALOG_SERVLET_PORT); - String servletPath = MetastoreConf.getVar(conf, ConfVars.ICEBERG_CATALOG_SERVLET_PATH); - - if (servletPath == null || servletPath.isEmpty()) { - servletPath = "iceberg"; // Default path - MetastoreConf.setVar(conf, ConfVars.ICEBERG_CATALOG_SERVLET_PATH, servletPath); - } - - LOG.info("Starting Standalone REST Catalog Server"); + LOG.info("Hadoop Configuration initialized"); LOG.info(" HMS Thrift URIs: {}", thriftUris); - LOG.info(" Servlet Port: {}", servletPort); - LOG.info(" Servlet Path: /{}", servletPath); - - // Create servlet using factory - ServletServerBuilder.Descriptor catalogDescriptor = HMSCatalogFactory.createServlet(conf); - if (catalogDescriptor == null) { - throw new IllegalStateException("Failed to create REST Catalog servlet. " + - "Check that metastore.catalog.servlet.port and metastore.iceberg.catalog.servlet.path are configured."); - } - - // Create health check servlet - HealthCheckServlet healthServlet = new HealthCheckServlet(); - - // Build and start server - ServletServerBuilder builder = new ServletServerBuilder(conf); - builder.addServlet(catalogDescriptor); - builder.addServlet(servletPort, "health", healthServlet); - - server = builder.start(LOG); - if (server == null || !server.isStarted()) { - // Server failed to start - likely a port conflict - throw new IllegalStateException(String.format( - "Failed to start REST Catalog server on port %d. Port may already be in use. ", servletPort)); - } - - // Get actual port (may be auto-assigned) - port = catalogDescriptor.getPort(); - LOG.info("Standalone REST Catalog Server started successfully on port {}", port); - LOG.info(" REST Catalog endpoint: http://localhost:{}/{}", port, servletPath); - LOG.info(" Health check endpoint: http://localhost:{}/health", port); + LOG.info(" Warehouse: {}", MetastoreConf.getVar(conf, ConfVars.WAREHOUSE)); } /** - * Stops the server. + * Updates port and restEndpoint with the actual server port once the web server has started. + * Handles RANDOM_PORT (tests) and server.port=0 where the real port differs from config. */ - public void stop() { - if (server != null && server.isStarted()) { - try { - LOG.info("Stopping Standalone REST Catalog Server"); - server.stop(); - server.join(); - LOG.info("Standalone REST Catalog Server stopped"); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - LOG.warn("Server stop interrupted", e); - } catch (Exception e) { - LOG.error("Error stopping server", e); + @EventListener + public void onWebServerInitialized(WebServerInitializedEvent event) { + int actualPort = event.getWebServer().getPort(); + if (actualPort > 0) { + this.port = actualPort; + String servletPath = MetastoreConf.getVar(conf, ConfVars.ICEBERG_CATALOG_SERVLET_PATH); + if (servletPath == null || servletPath.isEmpty()) { + servletPath = "iceberg"; } + this.restEndpoint = "http://localhost:" + actualPort + "/" + servletPath; + LOG.info("REST endpoint set to actual server port: {}", restEndpoint); } } @@ -142,27 +100,7 @@ public int getPort() { * @return the endpoint URL */ public String getRestEndpoint() { - String servletPath = MetastoreConf.getVar(conf, ConfVars.ICEBERG_CATALOG_SERVLET_PATH); - if (servletPath == null || servletPath.isEmpty()) { - servletPath = "iceberg"; - } - return "http://localhost:" + port + "/" + servletPath; - } - - /** - * Simple health check servlet for Kubernetes readiness/liveness probes. - */ - private static final class HealthCheckServlet extends HttpServlet { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) { - try { - resp.setContentType("application/json"); - resp.setStatus(HttpServletResponse.SC_OK); - resp.getWriter().println("{\"status\":\"healthy\"}"); - } catch (IOException e) { - LOG.warn("Failed to write health check response", e); - } - } + return restEndpoint; } /** @@ -183,26 +121,26 @@ public static void main(String[] args) { } } + // Sync port from MetastoreConf to Spring's Environment so server.port uses it + int port = MetastoreConf.getIntVar(conf, ConfVars.CATALOG_SERVLET_PORT); + if (port > 0) { + System.setProperty(ConfVars.CATALOG_SERVLET_PORT.getVarname(), String.valueOf(port)); + } + StandaloneRESTCatalogServer server = new StandaloneRESTCatalogServer(conf); + + // Start Spring Boot with pre-configured beans + SpringApplication app = new SpringApplication(StandaloneRESTCatalogServer.class, IcebergCatalogConfiguration.class); + app.addInitializers(ctx -> { + ctx.getBeanFactory().registerSingleton("hadoopConfiguration", conf); + ctx.getBeanFactory().registerSingleton("standaloneRESTCatalogServer", server); + }); - // Add shutdown hook - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - LOG.info("Shutdown hook triggered"); - server.stop(); - })); + app.run(args); - try { - server.start(); - LOG.info("Server running. Press Ctrl+C to stop."); - - // Keep server running - server.server.join(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - LOG.warn("Server stop interrupted", e); - } catch (Exception e) { - LOG.error("Failed to start server", e); - System.exit(1); - } + LOG.info("Standalone REST Catalog Server started successfully"); + LOG.info("Server running. Press Ctrl+C to stop."); + + // Spring Boot's graceful shutdown will handle cleanup automatically } } diff --git a/standalone-metastore/metastore-rest-catalog/src/main/resources/application.yml b/standalone-metastore/metastore-rest-catalog/src/main/resources/application.yml new file mode 100644 index 000000000000..99aec2c763f8 --- /dev/null +++ b/standalone-metastore/metastore-rest-catalog/src/main/resources/application.yml @@ -0,0 +1,61 @@ +# 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. + +# Spring Boot Configuration for Standalone HMS REST Catalog Server + +# Server configuration +# Port is set via MetastoreConf.CATALOG_SERVLET_PORT +server: + port: ${metastore.catalog.servlet.port:8080} + shutdown: graceful +spring: + lifecycle: + timeout-per-shutdown-phase: 30s + +# Actuator endpoints for Kubernetes +management: + endpoints: + web: + exposure: + include: health,prometheus,info + endpoint: + health: + show-details: always + probes: + enabled: true + health: + livenessState: + enabled: true + readinessState: + enabled: true + metrics: + export: + prometheus: + enabled: true + +# Logging +logging: + level: + org.apache.iceberg.rest.standalone: INFO + org.apache.hadoop.hive.metastore: INFO + org.springframework.boot: WARN + +# Application info +info: + app: + name: Standalone HMS REST Catalog Server + description: Standalone REST Catalog Server for Apache Hive Metastore + version: "@project.version@"