diff --git a/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/TrinoAwsProxyConfig.java b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/TrinoAwsProxyConfig.java index 7c71ba3f..86a60e6d 100644 --- a/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/TrinoAwsProxyConfig.java +++ b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/TrinoAwsProxyConfig.java @@ -36,6 +36,7 @@ public class TrinoAwsProxyConfig private String logsPath = "/api/v1/s3Proxy/logs"; private int requestLoggerSavedQty = 10000; private Optional maxPayloadSize = Optional.empty(); + private String statusPath = "/api/v1/s3Proxy/status"; @Config("aws.proxy.s3.hostname") @ConfigDescription("Hostname to use for S3 REST operations, virtual-host style addressing is only supported if this is set") @@ -147,4 +148,18 @@ public TrinoAwsProxyConfig setMaxPayloadSize(DataSize maxPayloadSize) this.maxPayloadSize = Optional.of(requireNonNull(maxPayloadSize, "requestByteQuota is null")); return this; } + + @NotNull + public String getStatusPath() + { + return statusPath; + } + + @Config("aws.proxy.status.path") + @ConfigDescription("URL Path for node status, optional") + public TrinoAwsProxyConfig setStatusPath(String statusPath) + { + this.statusPath = statusPath; + return this; + } } diff --git a/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/TrinoAwsProxyServerModule.java b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/TrinoAwsProxyServerModule.java index afe6de28..aa3f824f 100644 --- a/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/TrinoAwsProxyServerModule.java +++ b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/TrinoAwsProxyServerModule.java @@ -25,7 +25,6 @@ import com.google.inject.Provides; import com.google.inject.Scopes; import com.google.inject.TypeLiteral; -import com.google.inject.multibindings.MapBinder; import io.airlift.configuration.AbstractConfigurationAwareModule; import io.airlift.http.server.HttpServerBinder; import io.airlift.jaxrs.JaxrsBinder; @@ -36,14 +35,16 @@ import io.trino.aws.proxy.server.credentials.http.HttpCredentialsModule; import io.trino.aws.proxy.server.remote.RemoteS3Module; import io.trino.aws.proxy.server.rest.LimitStreamController; -import io.trino.aws.proxy.server.rest.RequestFilter; +import io.trino.aws.proxy.server.rest.ParamProvider; import io.trino.aws.proxy.server.rest.RequestLoggerController; +import io.trino.aws.proxy.server.rest.ResourceSecurityDynamicFeature; import io.trino.aws.proxy.server.rest.S3PresignController; import io.trino.aws.proxy.server.rest.ThrowableMapper; import io.trino.aws.proxy.server.rest.TrinoLogsResource; import io.trino.aws.proxy.server.rest.TrinoS3ProxyClient; import io.trino.aws.proxy.server.rest.TrinoS3ProxyClient.ForProxyClient; import io.trino.aws.proxy.server.rest.TrinoS3Resource; +import io.trino.aws.proxy.server.rest.TrinoStatusResource; import io.trino.aws.proxy.server.rest.TrinoStsResource; import io.trino.aws.proxy.server.security.S3SecurityController; import io.trino.aws.proxy.server.security.opa.OpaS3SecurityModule; @@ -60,7 +61,6 @@ import io.trino.aws.proxy.spi.plugin.config.S3SecurityFacadeProviderConfig; import io.trino.aws.proxy.spi.rest.S3RequestRewriter; import io.trino.aws.proxy.spi.security.S3SecurityFacadeProvider; -import io.trino.aws.proxy.spi.signing.SigningServiceType; import org.glassfish.jersey.server.model.Resource; import java.util.List; @@ -68,7 +68,6 @@ import java.util.ServiceLoader; import java.util.Set; -import static com.google.inject.multibindings.MapBinder.newMapBinder; import static com.google.inject.multibindings.Multibinder.newSetBinder; import static com.google.inject.multibindings.OptionalBinder.newOptionalBinder; import static io.airlift.configuration.ConfigBinder.configBinder; @@ -89,13 +88,13 @@ protected void setup(Binder binder) JaxrsBinder jaxrsBinder = jaxrsBinder(binder); - MapBinder, SigningServiceType> signingServiceTypesMapBinder = newMapBinder(binder, new TypeLiteral<>() {}, new TypeLiteral<>() {}); - - jaxrsBinder.bind(RequestFilter.class); jaxrsBinder.bind(ThrowableMapper.class); - bindResourceAtPath(jaxrsBinder, signingServiceTypesMapBinder, SigningServiceType.S3, TrinoS3Resource.class, builtConfig.getS3Path()); - bindResourceAtPath(jaxrsBinder, signingServiceTypesMapBinder, SigningServiceType.STS, TrinoStsResource.class, builtConfig.getStsPath()); - bindResourceAtPath(jaxrsBinder, signingServiceTypesMapBinder, SigningServiceType.LOGS, TrinoLogsResource.class, builtConfig.getLogsPath()); + jaxrsBinder.bind(ParamProvider.class); + jaxrsBinder.bind(ResourceSecurityDynamicFeature.class); + bindResourceAtPath(jaxrsBinder, TrinoS3Resource.class, builtConfig.getS3Path()); + bindResourceAtPath(jaxrsBinder, TrinoStsResource.class, builtConfig.getStsPath()); + bindResourceAtPath(jaxrsBinder, TrinoLogsResource.class, builtConfig.getLogsPath()); + bindResourceAtPath(jaxrsBinder, TrinoStatusResource.class, builtConfig.getStatusPath()); binder.bind(CredentialsController.class).in(Scopes.SINGLETON); binder.bind(RequestLoggerController.class).in(Scopes.SINGLETON); @@ -211,11 +210,9 @@ private void installPlugins() }); } - private static void bindResourceAtPath(JaxrsBinder jaxrsBinder, MapBinder, SigningServiceType> signingServiceTypesMapBinder, SigningServiceType signingServiceType, Class resourceClass, String resourcePathPrefix) + private static void bindResourceAtPath(JaxrsBinder jaxrsBinder, Class resourceClass, String resourcePrefix) { - Resource resource = Resource.builder(resourceClass).path(resourcePathPrefix).build(); jaxrsBinder.bind(resourceClass); - jaxrsBinder.bindInstance(resource); - signingServiceTypesMapBinder.addBinding(resourceClass).toInstance(signingServiceType); + jaxrsBinder.bindInstance(Resource.builder(resourceClass).path(resourcePrefix).build()); } } diff --git a/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/NodeStatus.java b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/NodeStatus.java new file mode 100644 index 00000000..e1089abb --- /dev/null +++ b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/NodeStatus.java @@ -0,0 +1,41 @@ +/* + * Licensed 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 io.trino.aws.proxy.server.rest; + +import io.airlift.units.Duration; + +import static java.util.Objects.requireNonNull; + +public record NodeStatus( + String nodeId, + String environment, + Duration uptime, + String externalAddress, + String internalAddress, + int processors, + double processCpuLoad, + double systemCpuLoad, + long heapUsed, + long heapAvailable, + long nonHeapUsed) +{ + public NodeStatus + { + requireNonNull(nodeId, "nodeId is null"); + requireNonNull(environment, "environment is null"); + requireNonNull(uptime, "uptime is null"); + requireNonNull(externalAddress, "externalAddress is null"); + requireNonNull(internalAddress, "internalAddress is null"); + } +} diff --git a/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/ParamProvider.java b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/ParamProvider.java new file mode 100644 index 00000000..da195481 --- /dev/null +++ b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/ParamProvider.java @@ -0,0 +1,44 @@ +/* + * Licensed 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 io.trino.aws.proxy.server.rest; + +import io.trino.aws.proxy.spi.rest.Request; +import io.trino.aws.proxy.spi.signing.SigningMetadata; +import org.glassfish.jersey.server.ContainerRequest; +import org.glassfish.jersey.server.model.Parameter; +import org.glassfish.jersey.server.spi.internal.ValueParamProvider; + +import java.util.function.Function; + +import static io.trino.aws.proxy.server.rest.SecurityFilter.unwrap; +import static org.glassfish.jersey.server.spi.internal.ValueParamProvider.Priority.HIGH; + +public class ParamProvider + implements ValueParamProvider +{ + @Override + public Function getValueProvider(Parameter parameter) + { + if (Request.class.isAssignableFrom(parameter.getRawType()) || SigningMetadata.class.isAssignableFrom(parameter.getRawType()) || RequestLoggingSession.class.isAssignableFrom(parameter.getRawType())) { + return containerRequest -> unwrap(containerRequest, parameter.getRawType()); + } + return null; + } + + @Override + public PriorityType getPriority() + { + return HIGH; + } +} diff --git a/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/ResourceSecurity.java b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/ResourceSecurity.java new file mode 100644 index 00000000..04eb4bbf --- /dev/null +++ b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/ResourceSecurity.java @@ -0,0 +1,60 @@ +/* + * Licensed 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 io.trino.aws.proxy.server.rest; + +import io.trino.aws.proxy.server.rest.ResourceSecurity.AccessType.Access.PublicAccess; +import io.trino.aws.proxy.server.rest.ResourceSecurity.AccessType.Access.SigV4Access; +import io.trino.aws.proxy.spi.signing.SigningServiceType; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static java.util.Objects.requireNonNull; + +@Retention(RUNTIME) +@Target({TYPE, METHOD}) +public @interface ResourceSecurity +{ + enum AccessType + { + PUBLIC(new PublicAccess()), + S3(new SigV4Access(SigningServiceType.S3)), + STS(new SigV4Access(SigningServiceType.STS)), + LOGS(new SigV4Access(SigningServiceType.LOGS)); + + public sealed interface Access + { + record PublicAccess() implements Access {} + + record SigV4Access(SigningServiceType signingServiceType) implements Access {} + } + + private final Access access; + + AccessType(Access access) + { + this.access = requireNonNull(access, "access is null"); + } + + public Access access() + { + return access; + } + } + + AccessType value(); +} diff --git a/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/ResourceSecurityDynamicFeature.java b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/ResourceSecurityDynamicFeature.java new file mode 100644 index 00000000..564e974f --- /dev/null +++ b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/ResourceSecurityDynamicFeature.java @@ -0,0 +1,69 @@ +/* + * Licensed 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 io.trino.aws.proxy.server.rest; + +import com.google.inject.Inject; +import io.trino.aws.proxy.server.rest.ResourceSecurity.AccessType; +import io.trino.aws.proxy.server.rest.ResourceSecurity.AccessType.Access.PublicAccess; +import io.trino.aws.proxy.server.rest.ResourceSecurity.AccessType.Access.SigV4Access; +import io.trino.aws.proxy.spi.signing.SigningController; +import io.trino.aws.proxy.spi.signing.SigningServiceType; +import jakarta.ws.rs.container.DynamicFeature; +import jakarta.ws.rs.container.ResourceInfo; +import jakarta.ws.rs.core.FeatureContext; + +import java.lang.reflect.AnnotatedElement; +import java.util.Optional; + +import static java.util.Objects.requireNonNull; + +public class ResourceSecurityDynamicFeature + implements DynamicFeature +{ + private final SigningController signingController; + private final RequestLoggerController requestLoggerController; + + @Inject + public ResourceSecurityDynamicFeature(SigningController signingController, RequestLoggerController requestLoggerController) + { + this.signingController = requireNonNull(signingController); + this.requestLoggerController = requireNonNull(requestLoggerController); + } + + @Override + public void configure(ResourceInfo resourceInfo, FeatureContext context) + { + if (resourceInfo.getResourceClass().getPackageName().startsWith("io.trino.aws")) { + AccessType accessType = getAccessType(resourceInfo); + switch (accessType.access()) { + case PublicAccess _ -> {} + case SigV4Access(SigningServiceType signingServiceType) -> + context.register(new SecurityFilter(signingController, signingServiceType, requestLoggerController)); + } + } + } + + private static AccessType getAccessType(ResourceInfo resourceInfo) + { + return getAccessTypeFromAnnotation(resourceInfo.getResourceMethod()) + .or(() -> getAccessTypeFromAnnotation(resourceInfo.getResourceClass())) + .orElseThrow(() -> new IllegalArgumentException("Proxy resource is not annotated with @" + ResourceSecurity.class.getSimpleName() + ": " + resourceInfo.getResourceMethod())); + } + + private static Optional getAccessTypeFromAnnotation(AnnotatedElement annotatedElement) + { + return Optional.ofNullable(annotatedElement.getAnnotation(ResourceSecurity.class)) + .map(ResourceSecurity::value); + } +} diff --git a/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/RequestFilter.java b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/SecurityFilter.java similarity index 72% rename from trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/RequestFilter.java rename to trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/SecurityFilter.java index be13c839..32fb54f6 100644 --- a/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/RequestFilter.java +++ b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/SecurityFilter.java @@ -14,8 +14,6 @@ package io.trino.aws.proxy.server.rest; import com.google.common.base.Throwables; -import com.google.common.collect.ImmutableMap; -import com.google.inject.Inject; import io.airlift.log.Logger; import io.trino.aws.proxy.spi.rest.Request; import io.trino.aws.proxy.spi.signing.SigningController; @@ -28,43 +26,28 @@ import jakarta.ws.rs.container.ContainerResponseFilter; import org.glassfish.jersey.server.ContainerRequest; import org.glassfish.jersey.server.ContainerResponse; -import org.glassfish.jersey.server.model.Parameter; -import org.glassfish.jersey.server.spi.internal.ValueParamProvider; import java.io.Closeable; import java.io.IOException; import java.io.OutputStream; -import java.util.Map; import java.util.Optional; -import java.util.function.Function; import static jakarta.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR; import static java.util.Objects.requireNonNull; -import static org.glassfish.jersey.server.spi.internal.ValueParamProvider.Priority.HIGH; -public class RequestFilter - implements ContainerRequestFilter, ContainerResponseFilter, ValueParamProvider +public class SecurityFilter + implements ContainerRequestFilter, ContainerResponseFilter { - private final Logger log = Logger.get(RequestFilter.class); + private static final Logger log = Logger.get(SecurityFilter.class); + private final SigningController signingController; - private final Map, SigningServiceType> signingServiceTypesMap; + private final SigningServiceType signingServiceType; private final RequestLoggerController requestLoggerController; - private record InternalRequestContext(Request request, SigningMetadata signingMetadata, RequestLoggingSession requestLoggingSession) - { - private InternalRequestContext - { - requireNonNull(request, "request is null"); - requireNonNull(signingMetadata, "signingMetadata is null"); - requireNonNull(requestLoggingSession, "requestLoggingSession is null"); - } - } - - @Inject - RequestFilter(SigningController signingController, Map, SigningServiceType> signingServiceTypesMap, RequestLoggerController requestLoggerController) + public SecurityFilter(SigningController signingController, SigningServiceType signingServiceType, RequestLoggerController requestLoggerController) { this.signingController = requireNonNull(signingController, "signingController is null"); - this.signingServiceTypesMap = ImmutableMap.copyOf(signingServiceTypesMap); + this.signingServiceType = requireNonNull(signingServiceType, "signingServiceType is null"); this.requestLoggerController = requireNonNull(requestLoggerController, "requestLoggerController is null"); } @@ -79,13 +62,6 @@ public void filter(ContainerRequestContext requestContext) throw new WebApplicationException(INTERNAL_SERVER_ERROR); } - Class declaringClass = containerRequest.getUriInfo().getMatchedResourceMethod().getInvocable().getDefinitionMethod().getDeclaringClass(); - SigningServiceType signingServiceType = signingServiceTypesMap.get(declaringClass); - if (signingServiceType == null) { - log.warn("%s does not have a SigningServiceType", declaringClass.getName()); - throw new WebApplicationException(INTERNAL_SERVER_ERROR); - } - Request request = RequestBuilder.fromRequest(containerRequest); containerRequest.setProperty(Request.class.getName(), request); @@ -132,21 +108,6 @@ public void filter(ContainerRequestContext requestContext, ContainerResponseCont } } - @Override - public Function getValueProvider(Parameter parameter) - { - if (Request.class.isAssignableFrom(parameter.getRawType()) || SigningMetadata.class.isAssignableFrom(parameter.getRawType()) || RequestLoggingSession.class.isAssignableFrom(parameter.getRawType())) { - return containerRequest -> unwrap(containerRequest, parameter.getRawType()); - } - return null; - } - - @Override - public PriorityType getPriority() - { - return HIGH; - } - private static OutputStream closingStream(Closeable closeable, OutputStream delegate) { return new OutputStream() @@ -191,7 +152,7 @@ public void close() } @SuppressWarnings("unchecked") - private static T unwrap(ContainerRequest containerRequest, Class type) + static T unwrap(ContainerRequest containerRequest, Class type) { return (T) containerRequest.getProperty(type.getName()); } diff --git a/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/TrinoLogsResource.java b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/TrinoLogsResource.java index 33bf4614..a6cfb763 100644 --- a/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/TrinoLogsResource.java +++ b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/TrinoLogsResource.java @@ -38,11 +38,13 @@ import java.util.function.Predicate; import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.trino.aws.proxy.server.rest.ResourceSecurity.AccessType.LOGS; import static io.trino.aws.proxy.spi.signing.SigningServiceType.S3; import static io.trino.aws.proxy.spi.signing.SigningServiceType.STS; import static jakarta.ws.rs.core.Response.Status.BAD_REQUEST; import static java.util.Objects.requireNonNull; +@ResourceSecurity(LOGS) public class TrinoLogsResource { private static final Set DEFAULT_STREAMS = ImmutableSet.of(S3.serviceName(), STS.serviceName()); diff --git a/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/TrinoS3Resource.java b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/TrinoS3Resource.java index dff0edb8..4a09d47b 100644 --- a/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/TrinoS3Resource.java +++ b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/TrinoS3Resource.java @@ -33,8 +33,10 @@ import java.util.Optional; import static io.trino.aws.proxy.server.rest.RequestBuilder.fromRequest; +import static io.trino.aws.proxy.server.rest.ResourceSecurity.AccessType.S3; import static java.util.Objects.requireNonNull; +@ResourceSecurity(S3) public class TrinoS3Resource { private final TrinoS3ProxyClient proxyClient; diff --git a/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/TrinoStatusResource.java b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/TrinoStatusResource.java new file mode 100644 index 00000000..6aa44046 --- /dev/null +++ b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/TrinoStatusResource.java @@ -0,0 +1,81 @@ +/* + * Licensed 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 io.trino.aws.proxy.server.rest; + +import com.google.inject.Inject; +import com.sun.management.OperatingSystemMXBean; +import io.airlift.node.NodeInfo; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.HEAD; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Response; + +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; + +import static io.airlift.units.Duration.nanosSince; +import static io.trino.aws.proxy.server.rest.ResourceSecurity.AccessType.PUBLIC; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static java.util.Objects.requireNonNull; + +@ResourceSecurity(PUBLIC) +public class TrinoStatusResource +{ + private final NodeInfo nodeInfo; + private final String environment; + private final long startTime = System.nanoTime(); + private final int logicalCores; + private final MemoryMXBean memoryMXBean; + + private OperatingSystemMXBean operatingSystemMXBean; + + @Inject + public TrinoStatusResource(NodeInfo nodeInfo) + { + this.nodeInfo = requireNonNull(nodeInfo, "nodeInfo is null"); + this.environment = nodeInfo.getEnvironment(); + this.memoryMXBean = ManagementFactory.getMemoryMXBean(); + this.logicalCores = Runtime.getRuntime().availableProcessors(); + + if (ManagementFactory.getOperatingSystemMXBean() instanceof OperatingSystemMXBean operatingSystemMXBean) { + // we want the com.sun.management sub-interface of java.lang.management.OperatingSystemMXBean + this.operatingSystemMXBean = operatingSystemMXBean; + } + } + + @HEAD + @Produces(APPLICATION_JSON) + public Response statusPing() + { + return Response.ok().build(); + } + + @GET + @Produces(APPLICATION_JSON) + public NodeStatus getStatus() + { + return new NodeStatus( + nodeInfo.getNodeId(), + environment, + nanosSince(startTime), + nodeInfo.getExternalAddress(), + nodeInfo.getInternalAddress(), + logicalCores, + operatingSystemMXBean == null ? 0 : operatingSystemMXBean.getProcessCpuLoad(), + operatingSystemMXBean == null ? 0 : operatingSystemMXBean.getCpuLoad(), + memoryMXBean.getHeapMemoryUsage().getUsed(), + memoryMXBean.getHeapMemoryUsage().getMax(), + memoryMXBean.getNonHeapMemoryUsage().getUsed()); + } +} diff --git a/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/TrinoStsResource.java b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/TrinoStsResource.java index 5759fb35..7d655fe6 100644 --- a/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/TrinoStsResource.java +++ b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/rest/TrinoStsResource.java @@ -37,8 +37,10 @@ import java.util.Optional; import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static io.trino.aws.proxy.server.rest.ResourceSecurity.AccessType.STS; import static java.util.Objects.requireNonNull; +@ResourceSecurity(STS) public class TrinoStsResource { private static final Logger log = Logger.get(TrinoStsResource.class); diff --git a/trino-aws-proxy/src/test/java/io/trino/aws/proxy/server/rest/TestStatusRequests.java b/trino-aws-proxy/src/test/java/io/trino/aws/proxy/server/rest/TestStatusRequests.java new file mode 100644 index 00000000..24a8068b --- /dev/null +++ b/trino-aws-proxy/src/test/java/io/trino/aws/proxy/server/rest/TestStatusRequests.java @@ -0,0 +1,67 @@ +/* + * Licensed 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 io.trino.aws.proxy.server.rest; + +import com.google.inject.Inject; +import io.airlift.http.client.FullJsonResponseHandler.JsonResponse; +import io.airlift.http.client.HttpClient; +import io.airlift.http.client.Request; +import io.airlift.http.client.StatusResponseHandler.StatusResponse; +import io.airlift.http.server.testing.TestingHttpServer; +import io.airlift.json.JsonCodec; +import io.airlift.node.NodeInfo; +import io.trino.aws.proxy.server.TrinoAwsProxyConfig; +import io.trino.aws.proxy.server.testing.TestingUtil.ForTesting; +import io.trino.aws.proxy.server.testing.harness.TrinoAwsProxyTest; +import io.trino.aws.proxy.server.testing.harness.TrinoAwsProxyTestCommonModules.WithTestingHttpClient; +import jakarta.ws.rs.core.UriBuilder; +import org.junit.jupiter.api.Test; + +import java.net.URI; + +import static io.airlift.http.client.FullJsonResponseHandler.createFullJsonResponseHandler; +import static io.airlift.http.client.StatusResponseHandler.createStatusResponseHandler; +import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; + +@TrinoAwsProxyTest(filters = WithTestingHttpClient.class) +public class TestStatusRequests +{ + private final NodeInfo nodeInfo; + private final URI statusURI; + private final HttpClient httpClient; + private final JsonCodec jsonCodec; + + @Inject + public TestStatusRequests(NodeInfo nodeInfo, TrinoAwsProxyConfig config, TestingHttpServer server, @ForTesting HttpClient httpClient) + { + this.nodeInfo = requireNonNull(nodeInfo, "nodeInfo is null"); + this.statusURI = UriBuilder.fromUri(server.getBaseUrl()).path(config.getStatusPath()).build(); + this.httpClient = requireNonNull(httpClient, "httpClient is null"); + this.jsonCodec = JsonCodec.jsonCodec(NodeStatus.class); + } + + @Test + public void testNodeStatus() + { + JsonResponse response = httpClient.execute(Request.builder().setMethod("GET").setUri(statusURI).build(), createFullJsonResponseHandler(jsonCodec)); + assertThat(response.getStatusCode()).isEqualTo(200); + NodeStatus nodeStatus = response.getValue(); + assertThat(nodeStatus).isNotNull(); + assertThat(nodeStatus.nodeId()).isEqualTo(nodeInfo.getNodeId()); + assertThat(nodeStatus.environment()).isEqualTo(nodeInfo.getEnvironment()); + + assertThat(httpClient.execute(Request.builder().setMethod("HEAD").setUri(statusURI).build(), createStatusResponseHandler())).extracting(StatusResponse::getStatusCode).isEqualTo(200); + } +}