From f8eefcf253f0c7d6d5ed9f909fb939072f2d989a Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Sat, 7 Sep 2024 13:28:57 +0200 Subject: [PATCH] Split out WellKnownResources Well-known resources are extremely simple to service, as they take only a few inputs and produce an immediate response. Split them out into their own class and execute them immediately from RestconfSession. This allows us to report '501 Not Implemented' for unimplemented methods, along with 'Allow' header hinting at what is implemented. JIRA: NETCONF-1379 Change-Id: I91af1aee99e2ffb2bf05d6833d64dd8471a40765 Signed-off-by: Robert Varga --- .../server/HostMetaRequestProcessor.java | 79 ----------- .../restconf/server/PathParameters.java | 20 +-- .../restconf/server/RequestParameters.java | 8 +- .../server/RestconfRequestDispatcher.java | 9 +- .../restconf/server/RestconfSession.java | 48 +++++-- .../RestconfTransportChannelListener.java | 5 +- .../restconf/server/WellKnownResources.java | 127 ++++++++++++++++++ .../server/AbstractRequestProcessorTest.java | 3 +- .../restconf/server/ErrorHandlerTest.java | 7 +- .../server/HostMetaRequestProcessorTest.java | 53 -------- .../restconf/server/PathParametersTest.java | 6 - .../server/WellKnownResourcesTest.java | 57 ++++++++ 12 files changed, 238 insertions(+), 184 deletions(-) delete mode 100644 protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/HostMetaRequestProcessor.java create mode 100644 protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/WellKnownResources.java delete mode 100644 protocol/restconf-server/src/test/java/org/opendaylight/restconf/server/HostMetaRequestProcessorTest.java create mode 100644 protocol/restconf-server/src/test/java/org/opendaylight/restconf/server/WellKnownResourcesTest.java diff --git a/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/HostMetaRequestProcessor.java b/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/HostMetaRequestProcessor.java deleted file mode 100644 index ce75045155..0000000000 --- a/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/HostMetaRequestProcessor.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2024 PANTHEON.tech s.r.o. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.restconf.server; - -import static org.opendaylight.restconf.server.Method.GET; -import static org.opendaylight.restconf.server.Method.HEAD; -import static org.opendaylight.restconf.server.Method.OPTIONS; -import static org.opendaylight.restconf.server.ResponseUtils.allowHeaderValue; -import static org.opendaylight.restconf.server.ResponseUtils.optionsResponse; -import static org.opendaylight.restconf.server.ResponseUtils.unmappedRequestErrorResponse; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.util.concurrent.FutureCallback; -import io.netty.handler.codec.http.FullHttpResponse; -import io.netty.handler.codec.http.HttpResponseStatus; -import io.netty.util.AsciiString; -import java.nio.charset.StandardCharsets; - -/** - * Static request processor serving root resource discovery requests. - * - * @see RFC 8040 Section 3.1 - * @see RFC 6415 Sections 6.1 - 6.2 - */ -final class HostMetaRequestProcessor { - @VisibleForTesting - static final String ALLOW_METHODS = allowHeaderValue(OPTIONS, HEAD, GET); - - @VisibleForTesting - static final String XRD_TEMPLATE = """ - - - - """; - @VisibleForTesting - - static final String JRD_TEMPLATE = """ - { - "links" : { - "rel" : "restconf", - "href" : "%s" - } - }"""; - - private HostMetaRequestProcessor() { - // hidden on purpose - } - - static void processHostMetaRequest(final RequestParameters params, - final FutureCallback callback) { - switch (params.method()) { - case OPTIONS -> callback.onSuccess(optionsResponse(params, ALLOW_METHODS)); - // Root Resource Discovery as XRD. - case HEAD, GET -> getHostMeta(params, callback, XRD_TEMPLATE, NettyMediaTypes.APPLICATION_XRD_XML); - default -> callback.onSuccess(unmappedRequestErrorResponse(params)); - } - } - - static void processHostMetaJsonRequest(final RequestParameters params, - final FutureCallback callback) { - switch (params.method()) { - case OPTIONS -> callback.onSuccess(optionsResponse(params, ALLOW_METHODS)); - // Root Resource Discovery as a JRD. - case HEAD, GET -> getHostMeta(params, callback, JRD_TEMPLATE, NettyMediaTypes.APPLICATION_JSON); - default -> callback.onSuccess(unmappedRequestErrorResponse(params)); - } - } - - private static void getHostMeta(final RequestParameters params, final FutureCallback callback, - final String template, final AsciiString contentType) { - final var content = template.formatted(params.basePath()).getBytes(StandardCharsets.UTF_8); - callback.onSuccess(ResponseUtils.simpleResponse(params, HttpResponseStatus.OK, contentType, content)); - } -} diff --git a/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/PathParameters.java b/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/PathParameters.java index 3d19fbe1c6..06ef4aa5e6 100644 --- a/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/PathParameters.java +++ b/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/PathParameters.java @@ -53,12 +53,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault; */ @NonNullByDefault record PathParameters(String apiResource, String childIdentifier) { - - /** - * URI path prefix for discovery requests. - */ - static final String DISCOVERY_BASE = "/.well-known"; - /** * API resource for datastore. */ @@ -85,18 +79,7 @@ record PathParameters(String apiResource, String childIdentifier) { static final String STREAMS = "/streams"; - /** - * API resource equivalent for discovery XRD request. - */ - static final String HOST_META = "/host-meta"; - - /** - * API resource equivalent for discovery JRD request. - */ - static final String HOST_META_JSON = "/host-meta.json"; - private static final Set API_RESOURCES = Set.of(DATA, OPERATIONS, YANG_LIBRARY_VERSION, MODULES, STREAMS); - private static final Set DISCOVERY_API_RESOURCES = Set.of(HOST_META, HOST_META_JSON); private static final PathParameters EMPTY = new PathParameters("", ""); PathParameters { @@ -105,7 +88,7 @@ record PathParameters(String apiResource, String childIdentifier) { } static PathParameters from(final String fullPath, final String basePath) { - if (!fullPath.startsWith(basePath) && !fullPath.startsWith(DISCOVERY_BASE)) { + if (!fullPath.startsWith(basePath)) { return EMPTY; } final var maxIndex = fullPath.length() - 1; @@ -117,7 +100,6 @@ record PathParameters(String apiResource, String childIdentifier) { final var child = cut(fullPath, childStartIndex, -1, maxIndex); return basePath.equals(base) && API_RESOURCES.contains(resource) - || DISCOVERY_BASE.equals(base) && DISCOVERY_API_RESOURCES.contains(resource) ? new PathParameters(resource, child) : EMPTY; } diff --git a/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/RequestParameters.java b/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/RequestParameters.java index c6d0ced3c0..e1d2e1fcf4 100644 --- a/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/RequestParameters.java +++ b/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/RequestParameters.java @@ -18,7 +18,6 @@ import io.netty.handler.codec.http.QueryStringDecoder; import io.netty.util.AsciiString; import java.io.InputStream; import java.net.URI; -import java.nio.charset.StandardCharsets; import java.security.Principal; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -41,9 +40,9 @@ final class RequestParameters { private final @Nullable Principal principal; private final PrettyPrintParam defaultPrettyPrint; - RequestParameters(final URI baseUri, final FullHttpRequest request, final @Nullable Principal principal, - final ErrorTagMapping errorTagMapping, final AsciiString defaultAcceptType, - final PrettyPrintParam defaultPrettyPrint) { + RequestParameters(final URI baseUri, final QueryStringDecoder decoder, final FullHttpRequest request, + final @Nullable Principal principal, final ErrorTagMapping errorTagMapping, + final AsciiString defaultAcceptType, final PrettyPrintParam defaultPrettyPrint) { this.baseUri = requireNonNull(baseUri); this.request = requireNonNull(request); this.principal = principal; @@ -52,7 +51,6 @@ final class RequestParameters { this.defaultPrettyPrint = requireNonNull(defaultPrettyPrint); contentType = extractContentType(request, defaultAcceptType); - final var decoder = new QueryStringDecoder(request.uri(), StandardCharsets.UTF_8); pathParameters = PathParameters.from(decoder.path(), baseUri.getPath()); queryParameters = QueryParameters.ofMultiValue(decoder.parameters()); } diff --git a/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/RestconfRequestDispatcher.java b/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/RestconfRequestDispatcher.java index 9f856cb712..8616f42174 100644 --- a/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/RestconfRequestDispatcher.java +++ b/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/RestconfRequestDispatcher.java @@ -15,6 +15,7 @@ import static org.opendaylight.restconf.server.ResponseUtils.unmappedRequestErro import com.google.common.util.concurrent.FutureCallback; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.QueryStringDecoder; import io.netty.util.AsciiString; import java.net.URI; import org.opendaylight.restconf.api.query.PrettyPrintParam; @@ -50,11 +51,12 @@ final class RestconfRequestDispatcher { } @SuppressWarnings("IllegalCatch") - void dispatch(final FullHttpRequest request, final FutureCallback callback) { + void dispatch(final QueryStringDecoder decoder, final FullHttpRequest request, + final FutureCallback callback) { LOG.debug("Dispatching {} {}", request.method(), request.uri()); final var principal = principalService.acquirePrincipal(request); - final var params = new RequestParameters(baseUri, request, principal, + final var params = new RequestParameters(baseUri, decoder, request, principal, errorTagMapping, defaultAcceptType, defaultPrettyPrint); try { switch (params.pathParameters().apiResource()) { @@ -65,9 +67,6 @@ final class RestconfRequestDispatcher { ModulesRequestProcessor.processYangLibraryVersion(params, restconfService, callback); case PathParameters.MODULES -> ModulesRequestProcessor.processModules(params, restconfService, callback); - case PathParameters.HOST_META -> HostMetaRequestProcessor.processHostMetaRequest(params, callback); - case PathParameters.HOST_META_JSON -> - HostMetaRequestProcessor.processHostMetaJsonRequest(params, callback); default -> callback.onSuccess( params.method() == Method.OPTIONS ? optionsResponse(params, Method.OPTIONS.name()) diff --git a/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/RestconfSession.java b/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/RestconfSession.java index d7f56492fb..9a05fd34a2 100644 --- a/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/RestconfSession.java +++ b/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/RestconfSession.java @@ -20,6 +20,8 @@ import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaderValues; import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.http.QueryStringDecoder; import io.netty.handler.codec.http2.HttpConversionUtil.ExtensionHeaderNames; import io.netty.util.AsciiString; import org.opendaylight.restconf.server.api.TransportSession; @@ -33,37 +35,63 @@ final class RestconfSession extends SimpleChannelInboundHandler private static final AsciiString STREAM_ID = ExtensionHeaderNames.STREAM_ID.text(); private final RestconfRequestDispatcher dispatcher; + private final WellKnownResources wellKnown; - RestconfSession(final RestconfRequestDispatcher dispatcher) { + RestconfSession(final WellKnownResources wellKnown, final RestconfRequestDispatcher dispatcher) { super(FullHttpRequest.class, false); + this.wellKnown = requireNonNull(wellKnown); this.dispatcher = requireNonNull(dispatcher); } @Override protected void channelRead0(final ChannelHandlerContext ctx, final FullHttpRequest msg) { - dispatcher.dispatch(msg, new FutureCallback<>() { + // non-null indicates HTTP/2 request, which we need to propagate to any response + final var streamId = msg.headers().getInt(STREAM_ID); + final var version = msg.protocolVersion(); + final var decoder = new QueryStringDecoder(msg.uri()); + final var path = decoder.path(); + + if (path.startsWith("/.well-known/")) { + // Well-known resources are immediately available and are trivial to service + respond(ctx, streamId, wellKnown.request(version, msg.method(), path.substring(13))); + msg.release(); + } else { + // Defer to dispatcher + dispatchRequest(ctx, version, streamId, decoder, msg); + } + } + + private void dispatchRequest(final ChannelHandlerContext ctx, final HttpVersion version, final Integer streamId, + final QueryStringDecoder decoder, final FullHttpRequest msg) { + dispatcher.dispatch(decoder, msg, new FutureCallback<>() { @Override public void onSuccess(final FullHttpResponse response) { - final var streamId = msg.headers().getInt(STREAM_ID); - if (streamId != null) { - response.headers().setInt(STREAM_ID, streamId); - } msg.release(); - ctx.writeAndFlush(response); + respond(ctx, streamId, response); } @Override public void onFailure(final Throwable throwable) { + msg.release(); + final var message = throwable.getMessage(); final var content = message == null ? Unpooled.EMPTY_BUFFER : ByteBufUtil.writeUtf8(ctx.alloc(), message); - final var response = new DefaultFullHttpResponse(msg.protocolVersion(), - HttpResponseStatus.INTERNAL_SERVER_ERROR, content); + final var response = new DefaultFullHttpResponse(version, HttpResponseStatus.INTERNAL_SERVER_ERROR, + content); response.headers() .set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setInt(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes()); - onSuccess(response); + respond(ctx, streamId, response); } }); } + + private static void respond(final ChannelHandlerContext ctx, final Integer streamId, + final FullHttpResponse response) { + if (streamId != null) { + response.headers().setInt(STREAM_ID, streamId); + } + ctx.writeAndFlush(response); + } } diff --git a/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/RestconfTransportChannelListener.java b/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/RestconfTransportChannelListener.java index 8e6cda95c1..569e9cccab 100644 --- a/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/RestconfTransportChannelListener.java +++ b/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/RestconfTransportChannelListener.java @@ -26,11 +26,14 @@ final class RestconfTransportChannelListener implements TransportChannelListener private final RestconfStream.Registry streamRegistry; private final NettyEndpointConfiguration configuration; private final RestconfRequestDispatcher dispatcher; + private final WellKnownResources wellKnown; RestconfTransportChannelListener(final RestconfServer server, final RestconfStream.Registry streamRegistry, final PrincipalService principalService, final NettyEndpointConfiguration configuration) { this.streamRegistry = requireNonNull(streamRegistry); this.configuration = requireNonNull(configuration); + wellKnown = new WellKnownResources(configuration.baseUri().getPath()); + dispatcher = new RestconfRequestDispatcher(server, principalService, configuration.baseUri(), configuration.errorTagMapping(), configuration.defaultAcceptType(), configuration.prettyPrint()); } @@ -42,7 +45,7 @@ final class RestconfTransportChannelListener implements TransportChannelListener new RestconfStreamService(streamRegistry, configuration.baseUri(), configuration.errorTagMapping(), configuration.defaultAcceptType(), configuration.prettyPrint()), configuration.sseMaximumFragmentLength().toJava(), configuration.sseHeartbeatIntervalMillis().toJava()), - new RestconfSession(dispatcher)); + new RestconfSession(wellKnown, dispatcher)); } @Override diff --git a/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/WellKnownResources.java b/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/WellKnownResources.java new file mode 100644 index 0000000000..c6c2456220 --- /dev/null +++ b/protocol/restconf-server/src/main/java/org/opendaylight/restconf/server/WellKnownResources.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.restconf.server; + +import static java.util.Objects.requireNonNull; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.buffer.UnpooledByteBufAllocator; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpHeaderValues; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.util.AsciiString; + +/** + * Well-known resources supported by a particular host. + * + * @see RFC 8615 + * @see RFC 6415, section 3 + * @see RFC 6415, appendix A + * @see RFC 8040, section 3.1 + */ +final class WellKnownResources { + private final ByteBuf jrd; + private final ByteBuf xrd; + + WellKnownResources(final String restconf) { + requireNonNull(restconf); + jrd = newDescriptor(""" + { + "links" : { + "rel" : "restconf", + "href" : "%s" + } + }""", restconf); + xrd = newDescriptor(""" + + + + """, restconf); + } + + // The format/args combination here is overly flexible, but it has a secondary use: it defeats SpotBugs analysis. + // Since 'format' is an argument instead of a literal, simple bytecode analysis cannot point out + // VA_FORMAT_STRING_USES_NEWLINE. + private static ByteBuf newDescriptor(final String format, final Object... args) { + return Unpooled.unreleasableBuffer( + ByteBufUtil.writeUtf8(UnpooledByteBufAllocator.DEFAULT, format.formatted(args)).asReadOnly()); + } + + FullHttpResponse request(final HttpVersion version, final HttpMethod method, final String suffix) { + return switch (suffix) { + case "host-meta" -> requestXRD(version, method); + case "host-meta.json" -> requestJRD(version, method); + default -> new DefaultFullHttpResponse(version, HttpResponseStatus.NOT_FOUND); + }; + } + + // https://www.rfc-editor.org/rfc/rfc6415#section-6.1 + private FullHttpResponse requestXRD(final HttpVersion version, final HttpMethod method) { + // FIXME: https://www.rfc-editor.org/rfc/rfc6415#appendix-A paragraph 2 says: + // + // The client MAY request a JRD representation using the HTTP "Accept" + // request header field with a value of "application/json" + // + // so we should be checking Accept and redirect to requestJRD() + return switch (method.name()) { + case "GET" -> getResponse(version, NettyMediaTypes.APPLICATION_XRD_XML, xrd); + case "HEAD" -> headResponse(version, NettyMediaTypes.APPLICATION_XRD_XML, xrd); + case "OPTIONS" -> optionsResponse(version); + default -> notImplemented(version); + }; + } + + // https://www.rfc-editor.org/rfc/rfc6415#section-6.2 + private FullHttpResponse requestJRD(final HttpVersion version, final HttpMethod method) { + return switch (method.name()) { + case "GET" -> getResponse(version, HttpHeaderValues.APPLICATION_JSON, jrd); + case "HEAD" -> headResponse(version, HttpHeaderValues.APPLICATION_JSON, jrd); + case "OPTIONS" -> optionsResponse(version); + default -> notImplemented(version); + }; + } + + private static FullHttpResponse getResponse(final HttpVersion version, final AsciiString contentType, + final ByteBuf content) { + return setContentHeaders(new DefaultFullHttpResponse(version, HttpResponseStatus.OK, content), + contentType, content); + } + + private static FullHttpResponse headResponse(final HttpVersion version, final AsciiString contentType, + final ByteBuf content) { + return setContentHeaders(new DefaultFullHttpResponse(version, HttpResponseStatus.OK, Unpooled.EMPTY_BUFFER), + contentType, content); + } + + private static FullHttpResponse optionsResponse(final HttpVersion version) { + return setAllowHeader(new DefaultFullHttpResponse(version, HttpResponseStatus.OK, Unpooled.EMPTY_BUFFER)); + } + + private static FullHttpResponse notImplemented(final HttpVersion version) { + return setAllowHeader(new DefaultFullHttpResponse(version, HttpResponseStatus.NOT_IMPLEMENTED)); + } + + private static FullHttpResponse setAllowHeader(final FullHttpResponse response) { + response.headers().set(HttpHeaderNames.ALLOW, "GET, HEAD, OPTIONS"); + return response; + } + + private static FullHttpResponse setContentHeaders(final FullHttpResponse response, final AsciiString contentType, + final ByteBuf content) { + response.headers() + .set(HttpHeaderNames.CONTENT_TYPE, contentType) + .setInt(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes()); + return response; + } +} diff --git a/protocol/restconf-server/src/test/java/org/opendaylight/restconf/server/AbstractRequestProcessorTest.java b/protocol/restconf-server/src/test/java/org/opendaylight/restconf/server/AbstractRequestProcessorTest.java index d59f9ca47e..2a0d278839 100644 --- a/protocol/restconf-server/src/test/java/org/opendaylight/restconf/server/AbstractRequestProcessorTest.java +++ b/protocol/restconf-server/src/test/java/org/opendaylight/restconf/server/AbstractRequestProcessorTest.java @@ -16,6 +16,7 @@ import static org.opendaylight.restconf.server.TestUtils.ERROR_TAG_MAPPING; import com.google.common.util.concurrent.FutureCallback; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.QueryStringDecoder; import java.net.URI; import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; @@ -66,7 +67,7 @@ public class AbstractRequestProcessorTest { } protected FullHttpResponse dispatch(final FullHttpRequest request) { - dispatcher.dispatch(request, callback); + dispatcher.dispatch(new QueryStringDecoder(request.uri()), request, callback); verify(callback).onSuccess(responseCaptor.capture()); return responseCaptor.getValue(); } diff --git a/protocol/restconf-server/src/test/java/org/opendaylight/restconf/server/ErrorHandlerTest.java b/protocol/restconf-server/src/test/java/org/opendaylight/restconf/server/ErrorHandlerTest.java index 3129e5c231..40d8ec0e07 100644 --- a/protocol/restconf-server/src/test/java/org/opendaylight/restconf/server/ErrorHandlerTest.java +++ b/protocol/restconf-server/src/test/java/org/opendaylight/restconf/server/ErrorHandlerTest.java @@ -10,9 +10,6 @@ package org.opendaylight.restconf.server; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; -import static org.opendaylight.restconf.server.PathParameters.DISCOVERY_BASE; -import static org.opendaylight.restconf.server.PathParameters.HOST_META; -import static org.opendaylight.restconf.server.PathParameters.HOST_META_JSON; import static org.opendaylight.restconf.server.PathParameters.MODULES; import static org.opendaylight.restconf.server.PathParameters.OPERATIONS; import static org.opendaylight.restconf.server.PathParameters.YANG_LIBRARY_VERSION; @@ -68,8 +65,8 @@ class ErrorHandlerTest extends AbstractRequestProcessorTest { Arguments.of(TestEncoding.XML, HttpMethod.PUT, OPERATIONS_PATH), Arguments.of(TestEncoding.XML, HttpMethod.POST, BASE_PATH + YANG_LIBRARY_VERSION), Arguments.of(TestEncoding.XML, HttpMethod.POST, BASE_PATH + MODULES), - Arguments.of(TestEncoding.XML, HttpMethod.POST, DISCOVERY_BASE + HOST_META), - Arguments.of(TestEncoding.XML, HttpMethod.POST, DISCOVERY_BASE + HOST_META_JSON) + Arguments.of(TestEncoding.XML, HttpMethod.POST, "/.well-known/host-meta"), + Arguments.of(TestEncoding.XML, HttpMethod.POST, "/.well-known/host-meta.json") ); } diff --git a/protocol/restconf-server/src/test/java/org/opendaylight/restconf/server/HostMetaRequestProcessorTest.java b/protocol/restconf-server/src/test/java/org/opendaylight/restconf/server/HostMetaRequestProcessorTest.java deleted file mode 100644 index 012110028e..0000000000 --- a/protocol/restconf-server/src/test/java/org/opendaylight/restconf/server/HostMetaRequestProcessorTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2024 PANTHEON.tech s.r.o. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.restconf.server; - -import static org.opendaylight.restconf.server.HostMetaRequestProcessor.JRD_TEMPLATE; -import static org.opendaylight.restconf.server.HostMetaRequestProcessor.XRD_TEMPLATE; -import static org.opendaylight.restconf.server.PathParameters.DISCOVERY_BASE; -import static org.opendaylight.restconf.server.PathParameters.HOST_META; -import static org.opendaylight.restconf.server.PathParameters.HOST_META_JSON; -import static org.opendaylight.restconf.server.TestUtils.assertOptionsResponse; -import static org.opendaylight.restconf.server.TestUtils.assertResponse; - -import io.netty.handler.codec.http.DefaultFullHttpRequest; -import io.netty.handler.codec.http.HttpMethod; -import io.netty.handler.codec.http.HttpResponseStatus; -import io.netty.handler.codec.http.HttpVersion; -import io.netty.util.AsciiString; -import java.util.stream.Stream; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.ValueSource; - -class HostMetaRequestProcessorTest extends AbstractRequestProcessorTest { - private static final String XRD_URI = DISCOVERY_BASE + HOST_META; - private static final String JRD_URI = DISCOVERY_BASE + HOST_META_JSON; - - @ParameterizedTest - @ValueSource(strings = {XRD_URI, JRD_URI}) - void options(final String uri) { - final var request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.OPTIONS, uri); - assertOptionsResponse(dispatch(request), HostMetaRequestProcessor.ALLOW_METHODS); - } - - @ParameterizedTest - @MethodSource - void getHostMeta(final String uri, final String contentTemplate, final AsciiString contentType) { - final var request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri); - assertResponse(dispatch(request), HttpResponseStatus.OK, contentType, contentTemplate.formatted(BASE_PATH)); - } - - private static Stream getHostMeta() { - return Stream.of( - Arguments.of(XRD_URI, XRD_TEMPLATE, NettyMediaTypes.APPLICATION_XRD_XML), - Arguments.of(JRD_URI, JRD_TEMPLATE, NettyMediaTypes.APPLICATION_JSON) - ); - } -} diff --git a/protocol/restconf-server/src/test/java/org/opendaylight/restconf/server/PathParametersTest.java b/protocol/restconf-server/src/test/java/org/opendaylight/restconf/server/PathParametersTest.java index 1991c643a4..d901318e3b 100644 --- a/protocol/restconf-server/src/test/java/org/opendaylight/restconf/server/PathParametersTest.java +++ b/protocol/restconf-server/src/test/java/org/opendaylight/restconf/server/PathParametersTest.java @@ -10,9 +10,6 @@ package org.opendaylight.restconf.server; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.opendaylight.restconf.server.PathParameters.DATA; -import static org.opendaylight.restconf.server.PathParameters.DISCOVERY_BASE; -import static org.opendaylight.restconf.server.PathParameters.HOST_META; -import static org.opendaylight.restconf.server.PathParameters.HOST_META_JSON; import static org.opendaylight.restconf.server.PathParameters.MODULES; import static org.opendaylight.restconf.server.PathParameters.OPERATIONS; import static org.opendaylight.restconf.server.PathParameters.YANG_LIBRARY_VERSION; @@ -51,9 +48,6 @@ class PathParametersTest { Arguments.of(BASE + YANG_LIBRARY_VERSION, YANG_LIBRARY_VERSION, ""), Arguments.of(BASE + MODULES, MODULES, ""), Arguments.of(BASE + MODULES + CHILD_PATH, MODULES, CHILD_ID), - Arguments.of(DISCOVERY_BASE, "", ""), - Arguments.of(DISCOVERY_BASE + HOST_META, HOST_META, ""), - Arguments.of(DISCOVERY_BASE + HOST_META_JSON, HOST_META_JSON, ""), // unsupported Arguments.of(UNSUPPORTED_BASE, "", ""), diff --git a/protocol/restconf-server/src/test/java/org/opendaylight/restconf/server/WellKnownResourcesTest.java b/protocol/restconf-server/src/test/java/org/opendaylight/restconf/server/WellKnownResourcesTest.java new file mode 100644 index 0000000000..4310013b03 --- /dev/null +++ b/protocol/restconf-server/src/test/java/org/opendaylight/restconf/server/WellKnownResourcesTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024 PANTHEON.tech s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.restconf.server; + +import static org.opendaylight.restconf.server.TestUtils.assertOptionsResponse; +import static org.opendaylight.restconf.server.TestUtils.assertResponse; + +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.util.AsciiString; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +class WellKnownResourcesTest { + private static final String XRD_SUFFIX = "host-meta"; + private static final String JRD_SUFFIX = "host-meta.json"; + private static final WellKnownResources RESOURCES = new WellKnownResources("testRestconf"); + + @ParameterizedTest + @ValueSource(strings = {XRD_SUFFIX, JRD_SUFFIX}) + void options(final String uri) { + assertOptionsResponse(RESOURCES.request(HttpVersion.HTTP_1_1, HttpMethod.OPTIONS, uri), "GET, HEAD, OPTIONS"); + } + + @ParameterizedTest + @MethodSource + void getHostMeta(final String uri, final AsciiString contentType, final String content) { + assertResponse(RESOURCES.request(HttpVersion.HTTP_1_1, HttpMethod.GET, uri), + HttpResponseStatus.OK, contentType, content); + } + + private static Stream getHostMeta() { + return Stream.of( + Arguments.of(XRD_SUFFIX, NettyMediaTypes.APPLICATION_XRD_XML, """ + + + + """), + Arguments.of(JRD_SUFFIX, NettyMediaTypes.APPLICATION_JSON, """ + { + "links" : { + "rel" : "restconf", + "href" : "testRestconf" + } + }""") + ); + } +} -- 2.36.6