Hms standalone rest server with Spring Boot#6327
Conversation
2e24788 to
196fd31
Compare
|
.../qtest-iceberg/src/test/java/org/apache/hadoop/hive/cli/TestStandaloneRESTCatalogServer.java
Show resolved
Hide resolved
.../qtest-iceberg/src/test/java/org/apache/hadoop/hive/cli/TestStandaloneRESTCatalogServer.java
Outdated
Show resolved
Hide resolved
.../qtest-iceberg/src/test/java/org/apache/hadoop/hive/cli/TestStandaloneRESTCatalogServer.java
Outdated
Show resolved
Hide resolved
| LOG.info("=== Test: Health Check ==="); | ||
| String healthUrl = "http://localhost:" + restCatalogServer.getPort() + "/health"; | ||
| public void testPrometheusMetrics() throws Exception { |
There was a problem hiding this comment.
do we use spring actuator? nice :)
.../qtest-iceberg/src/test/java/org/apache/hadoop/hive/cli/TestStandaloneRESTCatalogServer.java
Outdated
Show resolved
Hide resolved
|
to support OAuth / JWT Authentication don't we need SecurityConfig? cc @okumin |
...log/src/main/java/org/apache/iceberg/rest/standalone/health/HMSReadinessHealthIndicator.java
Show resolved
Hide resolved
...st-catalog/src/main/java/org/apache/iceberg/rest/standalone/StandaloneRESTCatalogServer.java
Show resolved
Hide resolved
standalone-metastore/metastore-rest-catalog/src/main/resources/application.properties
Outdated
Show resolved
Hide resolved
19640da to
a054c87
Compare
We don’t need Spring Security for JWT/OAuth2 here. Auth is handled by the Hive metastore’s I also added JWT integration tests for the Standalone REST Catalog server in |
a054c87 to
adc0bc2
Compare
|
DISCLAIMER: I might not be understanding Spring with Servlet correctly. |
| String namespacesUrl = restCatalogServer.getRestEndpoint() + "/v1/namespaces"; | ||
|
|
||
| String namespacePath = "/iceberg/v1/namespaces"; |
There was a problem hiding this comment.
optional: would it be better to use ResourcePaths.V1_NAMESPACES?
|
|
||
| @Test(timeout = 60000) | ||
| public void testServerPort() { | ||
| super.testServerPort(server); |
There was a problem hiding this comment.
why no add test annotation in base class instead of copy&paste?
| String namespacePath = "/iceberg/v1/namespaces"; | ||
| String namespaceName = "jwt_test_db"; | ||
|
|
||
| try (CloseableHttpClient httpClient = HttpClients.createDefault()) { |
There was a problem hiding this comment.
can we extract common parts to base class? both tests use similar logic
| * Spring configuration for the Iceberg REST Catalog servlet. | ||
| * Extracted to separate concerns from the main application bootstrap. | ||
| */ | ||
| @org.springframework.context.annotation.Configuration |
There was a problem hiding this comment.
can we import the package instead of fqname?
| // Determine servlet path and port | ||
| String servletPath = MetastoreConf.getVar(conf, ConfVars.ICEBERG_CATALOG_SERVLET_PATH); | ||
| if (servletPath == null || servletPath.isEmpty()) { | ||
| servletPath = "iceberg"; |
|
|
||
| int port = MetastoreConf.getIntVar(conf, ConfVars.CATALOG_SERVLET_PORT); | ||
| if (port == 0) { | ||
| port = 8080; |
| "Failed to start REST Catalog server on port %d. Port may already be in use. ", servletPort)); | ||
|
|
||
| if (LOG.isInfoEnabled()) { | ||
| LOG.info(" Warehouse: {}", MetastoreConf.getVar(conf, ConfVars.WAREHOUSE)); |
There was a problem hiding this comment.
I am not sure about that: should we display both WAREHOUSE and WAREHOUSE_EXTERNAL?
see https://issues.apache.org/jira/browse/HIVE-29461
| if (servletPath == null || servletPath.isEmpty()) { | ||
| servletPath = "iceberg"; | ||
| } | ||
| this.restEndpoint = "http://localhost:" + actualPort + "/" + servletPath; |
There was a problem hiding this comment.
do we need to construct this manually or iceberg RestCatalog has utils for that? if not, maybe
this.restEndpoint = UriComponentsBuilder
.scheme("http").host("localhost").port(actualPort)
.pathSegment(servletPath)
.toUriString();
should we support ssl?
|
|
||
| // Start Spring Boot with pre-configured beans | ||
| SpringApplication app = new SpringApplication(StandaloneRESTCatalogServer.class, IcebergCatalogConfiguration.class); | ||
| app.addInitializers(ctx -> { |
There was a problem hiding this comment.
You don't need manual registerSingleton at all, Spring should handle this
- StandaloneRESTCatalogServer (bootstrap only)
/**
* StandaloneRESTCatalogServer (bootstrap)
*/
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class StandaloneRESTCatalogServer {
public static void main(String[] args) {
SpringApplication.run(StandaloneRESTCatalogServer.class, args);
}
}
- RestCatalogServerRuntime (runtime lifecycle)
@Component
public class RestCatalogServerRuntime {
private static final Logger LOG = LoggerFactory.getLogger(RestCatalogServerRuntime.class);
private final Configuration conf;
private String restEndpoint;
private int port;
public RestCatalogServerRuntime(Configuration conf) {
this.conf = conf;
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");
}
LOG.info("Hadoop Configuration initialized");
LOG.info(" HMS Thrift URIs: {}", thriftUris);
if (LOG.isInfoEnabled()) {
LOG.info(" Warehouse: {}", MetastoreConf.getVar(conf, ConfVars.WAREHOUSE));
}
}
@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 = UriComponentsBuilder
.scheme("http").host("localhost").port(actualPort)
.pathSegment(servletPath)
.toUriString();
LOG.info("REST endpoint set to actual server port: {}", restEndpoint);
}
}
@VisibleForTesting
public int getPort() {
return port;
}
public String getRestEndpoint() {
return restEndpoint;
}
}
- IcebergCatalogConfig
@Configuration
public class IcebergCatalogConfig {
private static final Logger LOG =
LoggerFactory.getLogger(IcebergCatalogConfig.class);
/**
* Hadoop configuration bean.
*/
@Bean
public Configuration hadoopConfiguration() {
return MetastoreConf.newMetastoreConf();
}
/**
* Iceberg REST Catalog servlet registration.
*/
@Bean
public ServletRegistrationBean<HttpServlet> restCatalogServlet(Configuration conf) {
String servletPath =
MetastoreConf.getVar(conf, ConfVars.ICEBERG_CATALOG_SERVLET_PATH);
if (servletPath == null || servletPath.isEmpty()) {
servletPath = "iceberg";
MetastoreConf.setVar(conf, ConfVars.ICEBERG_CATALOG_SERVLET_PATH, servletPath);
}
int port = MetastoreConf.getIntVar(conf, ConfVars.CATALOG_SERVLET_PORT);
if (port == 0) {
port = 8080;
MetastoreConf.setLongVar(conf, ConfVars.CATALOG_SERVLET_PORT, port);
}
LOG.info("Creating REST Catalog servlet at /{}", servletPath);
org.apache.hadoop.hive.metastore.ServletServerBuilder.Descriptor descriptor =
HMSCatalogFactory.createServlet(conf);
if (descriptor == null || descriptor.getServlet() == null) {
throw new IllegalStateException("Failed to create Iceberg REST Catalog servlet");
}
ServletRegistrationBean<HttpServlet> registration =
new ServletRegistrationBean<>(descriptor.getServlet(), "/" + servletPath + "/*");
registration.setName("IcebergRESTCatalog");
registration.setLoadOnStartup(1);
return registration;
}
}
| System.setProperty(ConfVars.CATALOG_SERVLET_PORT.getVarname(), String.valueOf(port)); | ||
| } | ||
|
|
||
| StandaloneRESTCatalogServer server = new StandaloneRESTCatalogServer(conf); |
There was a problem hiding this comment.
Spring Boot should manage the lifecycle of your application class. Typically you don't manually instantiate your @SpringBootApplication class. see above snippet
deniskuzZ
left a comment
There was a problem hiding this comment.
added few comments, rest LGTM



What changes were proposed in this pull request?
The Standalone REST Catalog Server is reimplemented to use Spring Boot instead of plain Java:
Standalone packaging – Adds a spring-boot-maven-plugin “exec” JAR for running the server as a standalone process.
Why are the changes needed?
Spring Boot improves how the Standalone REST Catalog Server is run and operated:
Does this PR introduce any user-facing change?
If the standalone REST Catalog server is deployed in Kubernetes:
-- Liveness: httpGet: /actuator/health/liveness
-- Readiness: httpGet: /actuator/health/readiness
How was this patch tested?
Integration tests in
TestStandaloneRESTCatalogServerandTestStandaloneRESTCatalogServerJwtAuthrun the Spring Boot standalone HMS REST catalog server and verify liveness/readiness probes, Prometheus metrics, REST catalog operations, and JWT auth with Keycloak (Testcontainers).