Support DOMAction-level routing 92/97192/2
authorRobert Varga <robert.varga@pantheon.tech>
Mon, 9 Aug 2021 20:36:05 +0000 (22:36 +0200)
committerRobert Varga <nite@hq.sk>
Thu, 12 Aug 2021 16:53:06 +0000 (16:53 +0000)
We have a distinct use case where we want to route actions across
a system. In this scenario, a next hop provider registers itself as the
local provider with the appropriate cost.

This capability is also exposed as the simplest way of registering
binding-level implementations -- where we want to register to all
actions on the operational datastore.

Add explicit documentation around how how the arguments are interpreted
and teach DOMRpcRouter about wildcards -- similar to how it deals with
RPCs.

JIRA: MDSAL-681
Change-Id: I6daf70b28347fab88c8dc467fce267d8ba71e57d
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
(cherry picked from commit 205d444f07393587da4103bd92d0b2abbefea95b)

binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/ActionProviderServiceAdapter.java
binding/mdsal-binding-dom-adapter/src/test/java/org/opendaylight/mdsal/binding/dom/adapter/ActionProviderServiceAdapterTest.java
dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/DOMActionInstance.java
dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/DOMActionProviderService.java
dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/DOMActionService.java
dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/DOMRpcRouter.java
dom/mdsal-dom-broker/src/test/java/org/opendaylight/mdsal/dom/broker/DOMRpcRouterTest.java

index cef0c0bca211c9633df8354ef4ca3ad090c5e96c..dbf8a0369a84d7085610343181cc3aa8143a44af 100644 (file)
@@ -33,6 +33,7 @@ import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.opendaylight.yangtools.yang.common.RpcResult;
 import org.opendaylight.yangtools.yang.common.YangConstants;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
@@ -70,11 +71,16 @@ public final class ActionProviderServiceAdapter extends AbstractBindingAdapter<D
         final CurrentAdapterSerializer serializer = currentSerializer();
         final Absolute actionPath = serializer.getActionPath(actionInterface);
         final Impl impl = new Impl(adapterContext(), actionPath, actionInterface, implementation);
+        final DOMActionInstance instance = validNodes.isEmpty()
+            // Register on the entire datastore
+            ? DOMActionInstance.of(actionPath, new DOMDataTreeIdentifier(datastore, YangInstanceIdentifier.empty()))
+                // Register on specific instances
+                : DOMActionInstance.of(actionPath, validNodes.stream()
+                    .map(node -> serializer.toDOMDataTreeIdentifier(DataTreeIdentifier.create(datastore, node)))
+                    .collect(Collectors.toUnmodifiableSet()));
 
-        final ObjectRegistration<DOMActionImplementation> reg = getDelegate().registerActionImplementation(impl,
-            DOMActionInstance.of(actionPath, validNodes.stream()
-                .map(instance -> serializer.toDOMDataTreeIdentifier(DataTreeIdentifier.create(datastore, instance)))
-                .collect(Collectors.toUnmodifiableSet())));
+
+        final ObjectRegistration<?> reg = getDelegate().registerActionImplementation(impl, instance);
 
         return new AbstractObjectRegistration<>(implementation) {
             @Override
index 9fa7fd2231df4de803a7b5605c8d5aff4881a19d..48c4881898a48f54c510c682fc5ef56aa3257418 100644 (file)
@@ -55,4 +55,11 @@ public class ActionProviderServiceAdapterTest extends AbstractActionAdapterTest
             new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL,
                 YangInstanceIdentifier.create(new NodeIdentifier(Cont.QNAME))))));
     }
+
+    @Test
+    public void testWildcardRegistration() {
+        adapter.registerImplementation(Foo.class, FOO);
+        verify(actionProvider).registerActionImplementation(any(), eq(DOMActionInstance.of(FOO_PATH,
+            LogicalDatastoreType.OPERATIONAL, YangInstanceIdentifier.empty())));
+    }
 }
index 25c6098a42986f0c52e53b0452e8e13536f867e1..d16c6db73bbfd9be9f91ac435255c944d13d6f0e 100644 (file)
@@ -27,28 +27,30 @@ public final class DOMActionInstance implements Immutable {
     private final Set<DOMDataTreeIdentifier> dataTrees;
     private final Absolute type;
 
-    DOMActionInstance(final Absolute type, final Set<DOMDataTreeIdentifier> dataTrees) {
+    private DOMActionInstance(final Absolute type, final ImmutableSet<DOMDataTreeIdentifier> dataTrees) {
         this.type = requireNonNull(type);
-        this.dataTrees = ImmutableSet.copyOf(dataTrees);
+        this.dataTrees = requireNonNull(dataTrees);
         checkArgument(!dataTrees.isEmpty());
     }
 
     public static DOMActionInstance of(final Absolute type, final Set<DOMDataTreeIdentifier> dataTrees) {
-        return new DOMActionInstance(type, dataTrees);
+        return new DOMActionInstance(type, ImmutableSet.copyOf(dataTrees));
     }
 
     public static DOMActionInstance of(final Absolute type, final DOMDataTreeIdentifier... dataTrees) {
-        return of(type, ImmutableSet.copyOf(dataTrees));
+        return new DOMActionInstance(type, ImmutableSet.copyOf(dataTrees));
     }
 
     public static DOMActionInstance of(final Absolute type, final LogicalDatastoreType datastore,
             final YangInstanceIdentifier path) {
-        return of(type, ImmutableSet.of(new DOMDataTreeIdentifier(datastore, path)));
+        return new DOMActionInstance(type, ImmutableSet.of(new DOMDataTreeIdentifier(datastore, path)));
     }
 
     /**
      * Return the set of data trees on which this action is available. These identifiers are required to point
-     * to concrete items, i.e. they may not be wildcards.
+     * to concrete items, i.e. they may not be wildcards. Identifiers which return an empty
+     * {@link DOMDataTreeIdentifier#getRootIdentifier()} are considered to match all items in that particular datastore
+     * and are expected to be treated as lower-priority alternatives to exact matches.
      *
      * @return Set of trees on which this action is available.
      */
@@ -57,9 +59,9 @@ public final class DOMActionInstance implements Immutable {
     }
 
     /**
-     * Return the operation type.
+     * Return the action type, i.e. the absolute schema node identifier of the action.
      *
-     * @return operation type.
+     * @return action type.
      */
     public Absolute getType() {
         return type;
index c8ef23c1114590997a2ad48ba61f3625844c0f14..ff6c08e8b5a956b98f758bfcac0e44050b883764 100644 (file)
@@ -22,22 +22,41 @@ import org.opendaylight.yangtools.concepts.ObjectRegistration;
 public interface DOMActionProviderService
         extends DOMExtensibleService<DOMActionProviderService, DOMActionProviderServiceExtension> {
     /**
-     * Register an {@link DOMActionImplementation} object with this service.
+     * Register an {@link DOMActionImplementation} object with this service, servicing specified action instances.
      *
      * @param implementation action implementation, must not be null
      * @param instances Set of supported operation identifiers. Must not be null, empty, or contain a null element.
      * @return A {@link ObjectRegistration} object, guaranteed to be non-null.
-     * @throws NullPointerException if implementation or types is null
+     * @throws NullPointerException if {@code implementation} or {@code instances} is null, or if {@code instances}
+     *                              contains a null element.
      * @throws IllegalArgumentException if {@code instances} is empty
      */
     <T extends DOMActionImplementation> ObjectRegistration<T> registerActionImplementation(T implementation,
-            Set<DOMActionInstance> instances);
+        Set<DOMActionInstance> instances);
 
+    /**
+     * Register an {@link DOMActionImplementation} object with this service, servicing specified action instance.
+     *
+     * @param implementation action implementation, must not be null
+     * @param instance supported operation identifier. Must not be null.
+     * @return A {@link ObjectRegistration} object, guaranteed to be non-null.
+     * @throws NullPointerException if any argument is null
+     */
     default <T extends DOMActionImplementation> ObjectRegistration<T> registerActionImplementation(
             final T implementation, final DOMActionInstance instance) {
         return registerActionImplementation(implementation, ImmutableSet.of(instance));
     }
 
+    /**
+     * Register an {@link DOMActionImplementation} object with this service, servicing specified action instances.
+     *
+     * @param implementation action implementation, must not be null
+     * @param instances Set of supported operation identifiers. Must not be null, empty, or contain a null element.
+     * @return A {@link ObjectRegistration} object, guaranteed to be non-null.
+     * @throws NullPointerException if {@code implementation} or {@code instances} is null, or if {@code instances}
+     *                              contains a null element.
+     * @throws IllegalArgumentException if {@code instances} is empty
+     */
     default <T extends DOMActionImplementation> ObjectRegistration<T> registerActionImplementation(
             final T implementation, final DOMActionInstance... instances) {
         return registerActionImplementation(implementation, ImmutableSet.copyOf(instances));
index c8d684586c5a2f9a9420a4f81695109e809b9e47..8627d914a6624db2ae7d667bc3983ae79198bfdc 100644 (file)
@@ -32,6 +32,7 @@ public interface DOMActionService extends DOMExtensibleService<DOMActionService,
      * @param input Input argument
      * @return A FluentFuture which completes with the result of invocation
      * @throws NullPointerException if any of the arguments is null
+     * @throws IllegalArgumentException if {@code path} is empty
      */
     ListenableFuture<? extends DOMActionResult> invokeAction(Absolute type, DOMDataTreeIdentifier path,
             ContainerNode input);
index 9f93a946c25d161105af6184ac51ffdb9e4dcb05..ab9df4725cc317608bc9c186a03e108bf1ee73cc 100644 (file)
@@ -426,13 +426,13 @@ public final class DOMRpcRouter extends AbstractRegistration
         @Override
         public ListenableFuture<? extends DOMActionResult> invokeAction(final Absolute type,
                 final DOMDataTreeIdentifier path, final ContainerNode input) {
+            final YangInstanceIdentifier pathRoot = path.getRootIdentifier();
+            checkArgument(!pathRoot.isEmpty(), "Action path must not be empty");
+
             final DOMActionRoutingTableEntry entry = (DOMActionRoutingTableEntry) actionRoutingTable.getEntry(type);
-            if (entry == null) {
-                return Futures.immediateFailedFuture(
+            return entry != null ? OperationInvocation.invoke(entry, type, path, requireNonNull(input))
+                : Futures.immediateFailedFuture(
                     new DOMActionNotAvailableException("No implementation of Action %s available", type));
-            }
-
-            return OperationInvocation.invoke(entry, type, path, requireNonNull(input));
         }
     }
 
@@ -440,7 +440,7 @@ public final class DOMRpcRouter extends AbstractRegistration
     private final class ActionProviderServiceFacade implements DOMActionProviderService {
         @Override
         public <T extends DOMActionImplementation> ObjectRegistration<T> registerActionImplementation(
-            final T implementation, final Set<DOMActionInstance> instances) {
+                final T implementation, final Set<DOMActionInstance> instances) {
 
             synchronized (DOMRpcRouter.this) {
                 final DOMActionRoutingTable oldTable = actionRoutingTable;
@@ -539,10 +539,14 @@ public final class DOMRpcRouter extends AbstractRegistration
 
         static ListenableFuture<? extends DOMActionResult> invoke(final DOMActionRoutingTableEntry entry,
                 final Absolute type, final DOMDataTreeIdentifier path, final ContainerNode input) {
-            final List<DOMActionImplementation> impls = entry.getImplementations(path);
+            List<DOMActionImplementation> impls = entry.getImplementations(path);
             if (impls == null) {
-                return Futures.immediateFailedFuture(
-                    new DOMActionNotAvailableException("No implementation of Action %s available for %s", type, path));
+                impls = entry.getImplementations(
+                    new DOMDataTreeIdentifier(path.getDatastoreType(), YangInstanceIdentifier.empty()));
+                if (impls == null) {
+                    return Futures.immediateFailedFuture(new DOMActionNotAvailableException(
+                        "No implementation of Action %s available for %s", type, path));
+                }
             }
 
             return impls.get(0).invokeAction(type, path, input);
index b9649b0571ed28f3edd9dca3f67c51affa8acc55..c53056fc258e79db1648b26f86f049f4040f5b23 100644 (file)
@@ -158,6 +158,28 @@ public class DOMRpcRouterTest extends TestUtils {
         }
     }
 
+    @Test
+    public void testActionDatastoreRouting() throws ExecutionException {
+        try (DOMRpcRouter rpcRouter = new DOMRpcRouter()) {
+            rpcRouter.onModelContextUpdated(ACTIONS_CONTEXT);
+
+            final DOMActionProviderService actionProvider = rpcRouter.getActionProviderService();
+            assertNotNull(actionProvider);
+            final DOMActionService actionConsumer = rpcRouter.getActionService();
+            assertNotNull(actionConsumer);
+
+            try (ObjectRegistration<?> reg = actionProvider.registerActionImplementation(IMPL,
+                DOMActionInstance.of(BAZ_TYPE, LogicalDatastoreType.OPERATIONAL, YangInstanceIdentifier.empty()))) {
+
+                assertAvailable(actionConsumer, BAZ_PATH_GOOD);
+                assertAvailable(actionConsumer, BAZ_PATH_BAD);
+            }
+
+            assertUnavailable(actionConsumer, BAZ_PATH_BAD);
+            assertUnavailable(actionConsumer, BAZ_PATH_GOOD);
+        }
+    }
+
     private static void assertAvailable(final DOMActionService actionService, final YangInstanceIdentifier path) {
         final DOMActionResult result;
         try {