Allow YANG PATCH to communicate ETag/Last-Modified 65/109165/1
authorRobert Varga <robert.varga@pantheon.tech>
Wed, 6 Dec 2023 11:39:51 +0000 (12:39 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Wed, 6 Dec 2023 11:40:15 +0000 (12:40 +0100)
PATCH methods should communicate ETag/Last-Modified where applicable.
Introduce DataYangPatchResult to communicate these for YANG PATCH.

JIRA: NETCONF-1207
Change-Id: Ie98423c6615dc0ef2d19349d19e937f72ed03165
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsRestconf.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/RestconfStrategy.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DataYangPatchResult.java [new file with mode: 0644]
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/RestconfServer.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/mdsal/MdsalRestconfServer.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/AbstractRestconfStrategyTest.java

index f4f052f263dc8bcb90654d267f0dfaf541719485..1c8fe573d30263c8d6079f96322011629f684946 100644 (file)
@@ -72,6 +72,7 @@ import org.opendaylight.restconf.server.api.DataPostResult;
 import org.opendaylight.restconf.server.api.DataPostResult.CreateResource;
 import org.opendaylight.restconf.server.api.DataPostResult.InvokeOperation;
 import org.opendaylight.restconf.server.api.DataPutResult;
+import org.opendaylight.restconf.server.api.DataYangPatchResult;
 import org.opendaylight.restconf.server.api.ModulesGetResult;
 import org.opendaylight.restconf.server.api.OperationsGetResult;
 import org.opendaylight.restconf.server.api.OperationsPostResult;
@@ -388,11 +389,15 @@ public final class JaxRsRestconf implements ParamConverterProvider {
         }
     }
 
-    private static void completeDataYangPATCH(final RestconfFuture<PatchStatusContext> future, final AsyncResponse ar) {
+    private static void completeDataYangPATCH(final RestconfFuture<DataYangPatchResult> future,
+            final AsyncResponse ar) {
         future.addCallback(new JaxRsRestconfCallback<>(ar) {
             @Override
-            Response transform(final PatchStatusContext result) {
-                return Response.status(statusOf(result)).entity(result).build();
+            Response transform(final DataYangPatchResult result) {
+                final var status = result.status();
+                final var builder = Response.status(statusOf(status)).entity(status);
+                fillConfigurationMetadata(builder, result);
+                return builder.build();
             }
 
             private static Status statusOf(final PatchStatusContext result) {
index 196679cf6be67fe488e001c190ec6798a20d6cca..98b767bd7bcc51638c9bcd7c75d99106abbbf482 100644 (file)
@@ -84,6 +84,7 @@ import org.opendaylight.restconf.server.api.DataPostResult.CreateResource;
 import org.opendaylight.restconf.server.api.DataPostResult.InvokeOperation;
 import org.opendaylight.restconf.server.api.DataPutPath;
 import org.opendaylight.restconf.server.api.DataPutResult;
+import org.opendaylight.restconf.server.api.DataYangPatchResult;
 import org.opendaylight.restconf.server.api.DatabindContext;
 import org.opendaylight.restconf.server.api.OperationsGetResult;
 import org.opendaylight.restconf.server.api.OperationsPostPath;
@@ -590,7 +591,7 @@ public abstract class RestconfStrategy {
         return merge(path.instance(), data);
     }
 
-    public final @NonNull RestconfFuture<PatchStatusContext> dataPATCH(final ApiPath apiPath, final PatchBody body) {
+    public final @NonNull RestconfFuture<DataYangPatchResult> dataPATCH(final ApiPath apiPath, final PatchBody body) {
         final DataPath path;
         try {
             path = pathNormalizer.normalizeDataPath(apiPath);
@@ -615,7 +616,7 @@ public abstract class RestconfStrategy {
      * @param patch Patch context to be processed
      * @return {@link PatchStatusContext}
      */
-    public final @NonNull RestconfFuture<PatchStatusContext> patchData(final PatchContext patch) {
+    public final @NonNull RestconfFuture<DataYangPatchResult> patchData(final PatchContext patch) {
         final var editCollection = new ArrayList<PatchStatusEntity>();
         final var tx = prepareWriteExecution();
 
@@ -684,26 +685,28 @@ public abstract class RestconfStrategy {
             }
         }
 
-        final var ret = new SettableRestconfFuture<PatchStatusContext>();
+        final var ret = new SettableRestconfFuture<DataYangPatchResult>();
         // We have errors
         if (!noError) {
             tx.cancel();
-            ret.set(new PatchStatusContext(modelContext(), patch.patchId(), List.copyOf(editCollection), false, null));
+            ret.set(new DataYangPatchResult(
+                new PatchStatusContext(modelContext(), patch.patchId(), List.copyOf(editCollection), false, null)));
             return ret;
         }
 
         Futures.addCallback(tx.commit(), new FutureCallback<CommitInfo>() {
             @Override
             public void onSuccess(final CommitInfo result) {
-                ret.set(new PatchStatusContext(modelContext(), patch.patchId(), List.copyOf(editCollection), true,
-                    null));
+                ret.set(new DataYangPatchResult(
+                    new PatchStatusContext(modelContext(), patch.patchId(), List.copyOf(editCollection), true, null)));
             }
 
             @Override
             public void onFailure(final Throwable cause) {
                 // if errors occurred during transaction commit then patch failed and global errors are reported
-                ret.set(new PatchStatusContext(modelContext(), patch.patchId(), List.copyOf(editCollection), false,
-                    TransactionUtil.decodeException(cause, "PATCH", null, modelContext()).getErrors()));
+                ret.set(new DataYangPatchResult(
+                    new PatchStatusContext(modelContext(), patch.patchId(), List.copyOf(editCollection), false,
+                        TransactionUtil.decodeException(cause, "PATCH", null, modelContext()).getErrors())));
             }
         }, MoreExecutors.directExecutor());
 
diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DataYangPatchResult.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DataYangPatchResult.java
new file mode 100644 (file)
index 0000000..1b4ccf8
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2023 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.api;
+
+import static java.util.Objects.requireNonNull;
+
+import java.time.Instant;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+
+/**
+ * Result of a {@code PATCH} request as defined in
+ * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
+ *
+ * @param status A {@link PatchStatusContext}
+ * @param entityTag response {@code ETag} header, or {@code null} if not applicable
+ * @param lastModified response {@code Last-Modified} header, or {@code null} if not applicable
+ */
+public record DataYangPatchResult(
+        @NonNull PatchStatusContext status,
+        @Nullable EntityTag entityTag,
+        @Nullable Instant lastModified) implements ConfigurationMetadata {
+    public DataYangPatchResult {
+        requireNonNull(status);
+    }
+
+    public DataYangPatchResult(final @NonNull PatchStatusContext status) {
+        this(status, null, null);
+    }
+}
index 590567c8f3f86c62736848c354093643716444b6..756cdaba17b5794dd81aa490acfb17574c2a2860 100644 (file)
@@ -13,7 +13,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.restconf.common.errors.RestconfFuture;
-import org.opendaylight.restconf.common.patch.PatchStatusContext;
 import org.opendaylight.restconf.nb.rfc8040.databind.ChildBody;
 import org.opendaylight.restconf.nb.rfc8040.databind.DataPostBody;
 import org.opendaylight.restconf.nb.rfc8040.databind.OperationInputBody;
@@ -78,10 +77,9 @@ public interface RestconfServer {
      * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
      *
      * @param body YANG Patch body
-     * @return A {@link RestconfFuture} of the {@link PatchStatusContext} content
+     * @return A {@link RestconfFuture} of the {@link DataYangPatchResult} content
      */
-    // FIXME: NETCONF-1207: result should carry ConfigurationMetadata
-    RestconfFuture<PatchStatusContext> dataPATCH(PatchBody body);
+    RestconfFuture<DataYangPatchResult> dataPATCH(PatchBody body);
 
     /**
      * Ordered list of edits that are applied to the datastore by the server, as defined in
@@ -89,10 +87,9 @@ public interface RestconfServer {
      *
      * @param identifier path to target
      * @param body YANG Patch body
-     * @return A {@link RestconfFuture} of the {@link PatchStatusContext} content
+     * @return A {@link RestconfFuture} of the {@link DataYangPatchResult} content
      */
-    // FIXME: NETCONF-1207: result should carry ConfigurationMetadata
-    RestconfFuture<PatchStatusContext> dataPATCH(ApiPath identifier, PatchBody body);
+    RestconfFuture<DataYangPatchResult> dataPATCH(ApiPath identifier, PatchBody body);
 
     RestconfFuture<DataPostResult.CreateResource> dataPOST(ChildBody body, Map<String, String> queryParameters);
 
index 3e2a8346e280ac615b6a65f8f44c5e0d304472fc..4a14e6b1362d87f09b510d04b1db522c4b86f2a5 100644 (file)
@@ -33,7 +33,6 @@ import org.opendaylight.mdsal.dom.api.DOMYangTextSourceProvider;
 import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.common.errors.RestconfFuture;
-import org.opendaylight.restconf.common.patch.PatchStatusContext;
 import org.opendaylight.restconf.nb.rfc8040.databind.ChildBody;
 import org.opendaylight.restconf.nb.rfc8040.databind.DataPostBody;
 import org.opendaylight.restconf.nb.rfc8040.databind.OperationInputBody;
@@ -49,6 +48,7 @@ import org.opendaylight.restconf.server.api.DataPatchResult;
 import org.opendaylight.restconf.server.api.DataPostResult;
 import org.opendaylight.restconf.server.api.DataPostResult.CreateResource;
 import org.opendaylight.restconf.server.api.DataPutResult;
+import org.opendaylight.restconf.server.api.DataYangPatchResult;
 import org.opendaylight.restconf.server.api.DatabindContext;
 import org.opendaylight.restconf.server.api.ModulesGetResult;
 import org.opendaylight.restconf.server.api.OperationsGetResult;
@@ -210,12 +210,12 @@ public final class MdsalRestconfServer
     }
 
     @Override
-    public RestconfFuture<PatchStatusContext> dataPATCH(final PatchBody body) {
+    public RestconfFuture<DataYangPatchResult> dataPATCH(final PatchBody body) {
         return localStrategy().dataPATCH(ApiPath.empty(), body);
     }
 
     @Override
-    public RestconfFuture<PatchStatusContext> dataPATCH(final ApiPath identifier, final PatchBody body) {
+    public RestconfFuture<DataYangPatchResult> dataPATCH(final ApiPath identifier, final PatchBody body) {
         final StrategyAndTail strategyAndTail;
         try {
             strategyAndTail = localStrategy().resolveStrategy(identifier);
index 13d061cef1df148917d82da11beaec3380a7a330..c26f8c0f1aa8d41e52f5ccbca5557451f61b9f82 100644 (file)
@@ -352,7 +352,7 @@ abstract class AbstractRestconfStrategyTest extends AbstractJukeboxTest {
     public final void testDeleteNonexistentData() {
         final var status = deleteNonexistentDataTestStrategy().patchData(new PatchContext("patchD",
             List.of(new PatchEntity("edit", Operation.Delete, CREATE_AND_DELETE_TARGET))))
-            .getOrThrow();
+            .getOrThrow().status();
         assertEquals("patchD", status.patchId());
         assertFalse(status.ok());
         final var edits = status.editCollection();
@@ -496,7 +496,7 @@ abstract class AbstractRestconfStrategyTest extends AbstractJukeboxTest {
     }
 
     private static void patch(final PatchContext patchContext, final RestconfStrategy strategy, final boolean failed) {
-        final var patchStatusContext = strategy.patchData(patchContext).getOrThrow();
+        final var patchStatusContext = strategy.patchData(patchContext).getOrThrow().status();
         for (var entity : patchStatusContext.editCollection()) {
             if (failed) {
                 assertTrue("Edit " + entity.getEditId() + " failed", entity.isOk());