From 25254ae7fd8ea10847621ed11a1a27438f17b35c Mon Sep 17 00:00:00 2001 From: Tom Pantelis Date: Fri, 22 Jun 2018 23:30:17 -0400 Subject: [PATCH] Proxy DOMRpcRouter to the mdsal implementation Proxying to the dom mdsal RPC service(s) will allow an easier migration path where both can co-exist. Change-Id: Ib31efc2985b24e83106e6a25d2c94c9c29850eb2 Signed-off-by: Tom Pantelis --- .../binding/test/util/BindingTestContext.java | 5 +- .../md/sal/dom/api/DOMRpcException.java | 5 +- ...pcImplementationNotAvailableException.java | 4 + .../md/sal/dom/api/DOMRpcResult.java | 25 +- .../sal/dom/api/DefaultDOMRpcException.java | 21 + .../md/sal/dom/broker/impl/DOMRpcRouter.java | 272 ++++++------- .../org/opendaylight/blueprint/dom-broker.xml | 11 +- .../sal/dom/broker/impl/DOMRpcRouterTest.java | 362 ++++++++++++++++++ .../test/resources/odl-datastore-test.yang | 25 +- 9 files changed, 551 insertions(+), 179 deletions(-) create mode 100644 opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DefaultDOMRpcException.java create mode 100644 opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMRpcRouterTest.java diff --git a/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/sal/binding/test/util/BindingTestContext.java b/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/sal/binding/test/util/BindingTestContext.java index a5b55a3f9d..29f7cd3334 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/sal/binding/test/util/BindingTestContext.java +++ b/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/sal/binding/test/util/BindingTestContext.java @@ -208,8 +208,9 @@ public class BindingTestContext implements AutoCloseable { private void startDomBroker() { checkState(this.executor != null); - this.domRouter = new DOMRpcRouter(); - this.mockSchemaService.registerSchemaContextListener(this.domRouter); + org.opendaylight.mdsal.dom.broker.DOMRpcRouter delegate = + org.opendaylight.mdsal.dom.broker.DOMRpcRouter.newInstance(mockSchemaService); + this.domRouter = new DOMRpcRouter(delegate, delegate); final ClassToInstanceMap services = MutableClassToInstanceMap.create(); services.put(DOMRpcService.class, this.domRouter); diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcException.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcException.java index 7ea4f4cf56..e4c30aa6d5 100644 --- a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcException.java +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcException.java @@ -7,11 +7,14 @@ */ package org.opendaylight.controller.md.sal.dom.api; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + /** * Base class for failures that can occur during RPC invocation. This covers * transport and protocol-level failures. */ -public abstract class DOMRpcException extends Exception { +@SuppressFBWarnings(value = "NM_SAME_SIMPLE_NAME_AS_SUPERCLASS", justification = "Migration") +public abstract class DOMRpcException extends org.opendaylight.mdsal.dom.api.DOMRpcException { private static final long serialVersionUID = 1L; /** diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcImplementationNotAvailableException.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcImplementationNotAvailableException.java index 71685285d2..2858255faa 100644 --- a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcImplementationNotAvailableException.java +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcImplementationNotAvailableException.java @@ -24,4 +24,8 @@ public class DOMRpcImplementationNotAvailableException extends DOMRpcException { final Object... args) { super(String.format(format, args), Preconditions.checkNotNull(cause)); } + + public DOMRpcImplementationNotAvailableException(final String message, final Throwable cause) { + super(message, cause); + } } diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcResult.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcResult.java index 5893688870..ef7c670573 100644 --- a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcResult.java +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcResult.java @@ -7,30 +7,11 @@ */ package org.opendaylight.controller.md.sal.dom.api; -import java.util.Collection; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.opendaylight.yangtools.yang.common.RpcError; -import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * Interface defining a result of an RPC call. */ -public interface DOMRpcResult { - /** - * Returns a set of errors and warnings which occurred during processing - * the call. - * - * @return a Collection of {@link RpcError}, guaranteed to be non-null. In case - * no errors are reported, an empty collection is returned. - */ - @Nonnull Collection getErrors(); - - /** - * Returns the value result of the call or null if no result is available. - * - * @return Invocation result, null if the operation has not produced a result. This might - * be the case if the operation does not produce a result, or if it failed. - */ - @Nullable NormalizedNode getResult(); +@SuppressFBWarnings(value = "NM_SAME_SIMPLE_NAME_AS_INTERFACE", justification = "Migration") +public interface DOMRpcResult extends org.opendaylight.mdsal.dom.api.DOMRpcResult { } diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DefaultDOMRpcException.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DefaultDOMRpcException.java new file mode 100644 index 0000000000..c07b31a27d --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DefaultDOMRpcException.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2018 Inocybe Technologies 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.controller.md.sal.dom.api; + +/** + * Default implementation of DOMRpcException. + * + * @author Thomas Pantelis + */ +public class DefaultDOMRpcException extends DOMRpcException { + private static final long serialVersionUID = 1L; + + public DefaultDOMRpcException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMRpcRouter.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMRpcRouter.java index f8e47bb4d9..3d57aac601 100644 --- a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMRpcRouter.java +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMRpcRouter.java @@ -7,61 +7,81 @@ */ package org.opendaylight.controller.md.sal.dom.broker.impl; -import com.google.common.base.Preconditions; -import com.google.common.base.Verify; -import com.google.common.collect.Collections2; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableList.Builder; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.MapDifference; -import com.google.common.collect.MapDifference.ValueDifference; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; import com.google.common.util.concurrent.CheckedFuture; -import com.google.common.util.concurrent.ThreadFactoryBuilder; -import java.util.ArrayList; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import java.util.Collection; import java.util.Collections; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; -import javax.annotation.concurrent.GuardedBy; +import java.util.WeakHashMap; +import java.util.stream.Collectors; import org.opendaylight.controller.md.sal.dom.api.DOMRpcAvailabilityListener; import org.opendaylight.controller.md.sal.dom.api.DOMRpcException; import org.opendaylight.controller.md.sal.dom.api.DOMRpcIdentifier; import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementation; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementationNotAvailableException; import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementationRegistration; import org.opendaylight.controller.md.sal.dom.api.DOMRpcProviderService; import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult; import org.opendaylight.controller.md.sal.dom.api.DOMRpcService; +import org.opendaylight.controller.md.sal.dom.api.DefaultDOMRpcException; import org.opendaylight.controller.md.sal.dom.spi.AbstractDOMRpcImplementationRegistration; -import org.opendaylight.controller.sal.core.api.model.SchemaService; +import org.opendaylight.controller.md.sal.dom.spi.DefaultDOMRpcResult; +import org.opendaylight.mdsal.common.api.MappingCheckedFuture; import org.opendaylight.yangtools.concepts.AbstractListenerRegistration; import org.opendaylight.yangtools.concepts.ListenerRegistration; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.util.concurrent.ExceptionMapper; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.api.SchemaContextListener; import org.opendaylight.yangtools.yang.model.api.SchemaPath; public final class DOMRpcRouter implements AutoCloseable, DOMRpcService, DOMRpcProviderService, SchemaContextListener { - private static final ThreadFactory THREAD_FACTORY = new ThreadFactoryBuilder() - .setNameFormat("DOMRpcRouter-listener-%s").setDaemon(true).build(); + private static final ExceptionMapper MDSAL_DOM_RPC_EX_MAPPER = + new ExceptionMapper( + "rpc", org.opendaylight.mdsal.dom.api.DOMRpcException.class) { + @Override + protected org.opendaylight.mdsal.dom.api.DOMRpcException newWithCause(String message, Throwable cause) { + return cause instanceof org.opendaylight.mdsal.dom.api.DOMRpcException + ? (org.opendaylight.mdsal.dom.api.DOMRpcException)cause + : new org.opendaylight.mdsal.dom.api.DefaultDOMRpcException("RPC failed", cause); + } + }; + + private static final ExceptionMapper LEGACY_DOM_RPC_EX_MAPPER = + new ExceptionMapper("rpc", DOMRpcException.class) { + @Override + protected DOMRpcException newWithCause(String message, Throwable cause) { + return cause instanceof DOMRpcException ? (DOMRpcException)cause + : cause instanceof org.opendaylight.mdsal.dom.api.DOMRpcImplementationNotAvailableException + ? new DOMRpcImplementationNotAvailableException(cause.getMessage(), cause.getCause()) + : new DefaultDOMRpcException("RPC failed", cause); + } + }; - private final ExecutorService listenerNotifier = Executors.newSingleThreadExecutor(THREAD_FACTORY); + // This mapping is used to translate mdsal DOMRpcImplementations to their corresponding legacy + // DOMRpcImplementations registered thru this interface when invoking a DOMRpcAvailabilityListener. + private final Map implMapping = + Collections.synchronizedMap(new WeakHashMap<>()); - @GuardedBy("this") - private Collection> listeners = Collections.emptyList(); + private final org.opendaylight.mdsal.dom.api.DOMRpcService delegateRpcService; + private final org.opendaylight.mdsal.dom.api.DOMRpcProviderService delegateRpcProviderService; - private volatile DOMRpcRoutingTable routingTable = DOMRpcRoutingTable.EMPTY; + @VisibleForTesting + public DOMRpcRouter() { + org.opendaylight.mdsal.dom.broker.DOMRpcRouter delegate = new org.opendaylight.mdsal.dom.broker.DOMRpcRouter(); + this.delegateRpcService = delegate; + this.delegateRpcProviderService = delegate; + } - public static DOMRpcRouter newInstance(final SchemaService schemaService) { - final DOMRpcRouter rpcRouter = new DOMRpcRouter(); - schemaService.registerSchemaContextListener(rpcRouter); - return rpcRouter; + public DOMRpcRouter(final org.opendaylight.mdsal.dom.api.DOMRpcService delegateRpcService, + final org.opendaylight.mdsal.dom.api.DOMRpcProviderService delegateRpcProviderService) { + this.delegateRpcService = delegateRpcService; + this.delegateRpcProviderService = delegateRpcProviderService; } @Override @@ -70,160 +90,112 @@ public final class DOMRpcRouter implements AutoCloseable, DOMRpcService, DOMRpcP return registerRpcImplementation(implementation, ImmutableSet.copyOf(rpcs)); } + @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public synchronized DOMRpcImplementationRegistration registerRpcImplementation( final T implementation, final Set rpcs) { - final DOMRpcRoutingTable oldTable = routingTable; - final DOMRpcRoutingTable newTable = oldTable.add(implementation, rpcs); - routingTable = newTable; + org.opendaylight.mdsal.dom.api.DOMRpcImplementation delegateImpl = + new org.opendaylight.mdsal.dom.api.DOMRpcImplementation() { + @Override + public CheckedFuture invokeRpc( + org.opendaylight.mdsal.dom.api.DOMRpcIdentifier rpc, NormalizedNode input) { + final ListenableFuture future = implementation.invokeRpc(convert(rpc), input); + return MappingCheckedFuture.create(future, MDSAL_DOM_RPC_EX_MAPPER); + } + + @Override + public long invocationCost() { + return implementation.invocationCost(); + } + }; + + implMapping.put(delegateImpl, implementation); - listenerNotifier.execute(() -> notifyAdded(newTable, implementation)); + final org.opendaylight.mdsal.dom.api.DOMRpcImplementationRegistration + reg = delegateRpcProviderService + .registerRpcImplementation(delegateImpl, + rpcs.stream().map(DOMRpcRouter::convert).collect(Collectors.toSet())); return new AbstractDOMRpcImplementationRegistration(implementation) { @Override protected void removeRegistration() { - removeRpcImplementation(getInstance(), rpcs); + reg.close(); + implMapping.remove(delegateImpl); } }; } - private synchronized void removeRpcImplementation(final DOMRpcImplementation implementation, - final Set rpcs) { - final DOMRpcRoutingTable oldTable = routingTable; - final DOMRpcRoutingTable newTable = oldTable.remove(implementation, rpcs); - routingTable = newTable; - - listenerNotifier.execute(() -> notifyRemoved(newTable, implementation)); + private static org.opendaylight.mdsal.dom.api.DOMRpcIdentifier convert(DOMRpcIdentifier from) { + return org.opendaylight.mdsal.dom.api.DOMRpcIdentifier.create(from.getType(), from.getContextReference()); } - @Override - public CheckedFuture invokeRpc(final SchemaPath type, - final NormalizedNode input) { - return routingTable.invokeRpc(type, input); + private static DOMRpcIdentifier convert(org.opendaylight.mdsal.dom.api.DOMRpcIdentifier from) { + return DOMRpcIdentifier.create(from.getType(), from.getContextReference()); } - private synchronized void removeListener(final ListenerRegistration reg) { - listeners = ImmutableList.copyOf(Collections2.filter(listeners, i -> !reg.equals(i))); + private static ListenableFuture convert(org.opendaylight.mdsal.dom.api.DOMRpcResult from) { + return from == null ? Futures.immediateFuture(null) + : from instanceof DOMRpcResult ? Futures.immediateFuture((DOMRpcResult)from) + : Futures.immediateFuture(new DefaultDOMRpcResult(from.getResult(), from.getErrors())); } - private synchronized void notifyAdded(final DOMRpcRoutingTable newTable, final DOMRpcImplementation impl) { - for (Registration l : listeners) { - l.addRpc(newTable, impl); - } + private static Collection convert( + Collection from) { + return from.stream().map(DOMRpcRouter::convert).collect(Collectors.toList()); } - private synchronized void notifyRemoved(final DOMRpcRoutingTable newTable, final DOMRpcImplementation impl) { - for (Registration l : listeners) { - l.removeRpc(newTable, impl); - } + @Override + public CheckedFuture invokeRpc(final SchemaPath type, + final NormalizedNode input) { + final ListenableFuture future = Futures.transformAsync(delegateRpcService.invokeRpc(type, input), + DOMRpcRouter::convert, MoreExecutors.directExecutor()); + return MappingCheckedFuture.create(future, LEGACY_DOM_RPC_EX_MAPPER); } @Override public synchronized ListenerRegistration registerRpcListener( final T listener) { - final Registration ret = new Registration<>(this, listener, routingTable.getRpcs(listener)); - final Builder> b = ImmutableList.builder(); - b.addAll(listeners); - b.add(ret); - listeners = b.build(); - - listenerNotifier.execute(ret::initialTable); - return ret; - } - - @Override - public synchronized void onGlobalContextUpdated(final SchemaContext context) { - final DOMRpcRoutingTable oldTable = routingTable; - final DOMRpcRoutingTable newTable = oldTable.setSchemaContext(context); - routingTable = newTable; - } - - @Override - public void close() { - listenerNotifier.shutdown(); - } - - private static final class Registration extends - AbstractListenerRegistration { - - private final DOMRpcRouter router; - - private Map> prevRpcs; - - Registration(final DOMRpcRouter router, final T listener, - final Map> rpcs) { - super(Preconditions.checkNotNull(listener)); - this.router = Preconditions.checkNotNull(router); - this.prevRpcs = Preconditions.checkNotNull(rpcs); - } - - @Override - protected void removeRegistration() { - router.removeListener(this); - } - - void initialTable() { - final Collection added = new ArrayList<>(); - for (Entry> e : prevRpcs.entrySet()) { - added.addAll(Collections2.transform(e.getValue(), i -> DOMRpcIdentifier.create(e.getKey(), i))); - } - - if (!added.isEmpty()) { - final T l = getInstance(); - l.onRpcAvailable(added); - } - } - - void addRpc(final DOMRpcRoutingTable newTable, final DOMRpcImplementation impl) { - final T l = getInstance(); - if (!l.acceptsImplementation(impl)) { - return; - } - - final Map> rpcs = Verify.verifyNotNull(newTable.getRpcs(l)); - final MapDifference> diff = Maps.difference(prevRpcs, rpcs); + final ListenerRegistration reg = + delegateRpcService.registerRpcListener(new org.opendaylight.mdsal.dom.api.DOMRpcAvailabilityListener() { + @Override + public void onRpcAvailable(Collection rpcs) { + listener.onRpcAvailable(convert(rpcs)); + } - final Collection added = new ArrayList<>(); - for (Entry> e : diff.entriesOnlyOnRight().entrySet()) { - added.addAll(Collections2.transform(e.getValue(), i -> DOMRpcIdentifier.create(e.getKey(), i))); - } - for (Entry>> e : diff.entriesDiffering() - .entrySet()) { - for (YangInstanceIdentifier i : Sets.difference(e.getValue().rightValue(), e.getValue().leftValue())) { - added.add(DOMRpcIdentifier.create(e.getKey(), i)); + @Override + public void onRpcUnavailable(Collection rpcs) { + listener.onRpcUnavailable(convert(rpcs)); } - } - prevRpcs = rpcs; - if (!added.isEmpty()) { - l.onRpcAvailable(added); - } - } + @Override + public boolean acceptsImplementation(final org.opendaylight.mdsal.dom.api.DOMRpcImplementation impl) { + // If the DOMRpcImplementation wasn't registered thru this interface then the mapping won't be + // present - in this we can't call the listener so just assume acceptance which is the default + // behavior. This should be fine since a legacy listener would not be aware of implementation types + // registered via the new mdsal API. + final DOMRpcImplementation legacyImpl = implMapping.get(impl); + return legacyImpl != null ? listener.acceptsImplementation(legacyImpl) : true; + } + }); - void removeRpc(final DOMRpcRoutingTable newTable, final DOMRpcImplementation impl) { - final T l = getInstance(); - if (!l.acceptsImplementation(impl)) { - return; + return new AbstractListenerRegistration(listener) { + @Override + protected void removeRegistration() { + reg.close(); } + }; + } - final Map> rpcs = Verify.verifyNotNull(newTable.getRpcs(l)); - final MapDifference> diff = Maps.difference(prevRpcs, rpcs); - - final Collection removed = new ArrayList<>(); - for (Entry> e : diff.entriesOnlyOnLeft().entrySet()) { - removed.addAll(Collections2.transform(e.getValue(), i -> DOMRpcIdentifier.create(e.getKey(), i))); - } - for (Entry>> e : diff.entriesDiffering() - .entrySet()) { - for (YangInstanceIdentifier i : Sets.difference(e.getValue().leftValue(), e.getValue().rightValue())) { - removed.add(DOMRpcIdentifier.create(e.getKey(), i)); - } - } + @Override + public void close() { + } - prevRpcs = rpcs; - if (!removed.isEmpty()) { - l.onRpcUnavailable(removed); - } + @Override + @VisibleForTesting + public void onGlobalContextUpdated(final SchemaContext context) { + if (delegateRpcService instanceof SchemaContextListener) { + ((SchemaContextListener)delegateRpcService).onGlobalContextUpdated(context); } } } diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/resources/org/opendaylight/blueprint/dom-broker.xml b/opendaylight/md-sal/sal-dom-broker/src/main/resources/org/opendaylight/blueprint/dom-broker.xml index d86c8232fc..4cc85298eb 100644 --- a/opendaylight/md-sal/sal-dom-broker/src/main/resources/org/opendaylight/blueprint/dom-broker.xml +++ b/opendaylight/md-sal/sal-dom-broker/src/main/resources/org/opendaylight/blueprint/dom-broker.xml @@ -36,9 +36,14 @@ - - + + + + + + diff --git a/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMRpcRouterTest.java b/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMRpcRouterTest.java new file mode 100644 index 0000000000..e2356e3f43 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMRpcRouterTest.java @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2018 Inocybe Technologies 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.controller.md.sal.dom.broker.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.after; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.CheckedFuture; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import java.util.AbstractMap.SimpleEntry; +import java.util.Collections; +import java.util.Map.Entry; +import java.util.concurrent.ExecutionException; +import org.junit.Before; +import org.junit.Test; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcAvailabilityListener; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcException; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcIdentifier; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementation; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementationNotAvailableException; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementationRegistration; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult; +import org.opendaylight.controller.md.sal.dom.spi.DefaultDOMRpcResult; +import org.opendaylight.controller.md.sal.dom.store.impl.TestModel; +import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.RpcError.ErrorType; +import org.opendaylight.yangtools.yang.common.RpcResultBuilder; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.RpcDefinition; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaPath; + +/** + * Unit tests for DOMRpcRouter. + * + * @author Thomas Pantelis + */ +public class DOMRpcRouterTest { + + private static final NormalizedNode RPC_INPUT = ImmutableNodes.leafNode( + QName.create(TestModel.TEST_QNAME.getModule(), "input-leaf"), "foo"); + private static final NormalizedNode RPC_OUTPUT = ImmutableNodes.leafNode( + QName.create(TestModel.TEST_QNAME.getModule(), "output-leaf"), "bar"); + private final TestLegacyDOMRpcImplementation testLegacyRpcImpl = new TestLegacyDOMRpcImplementation(); + private final TestMdsalDOMRpcImplementation testMdsalRpcImpl = new TestMdsalDOMRpcImplementation(); + private org.opendaylight.mdsal.dom.broker.DOMRpcRouter mdsalRpcRouter; + private DOMRpcRouter legacyRpcRouter; + private DOMRpcIdentifier legacyTestRpcIdentifier; + private DOMRpcIdentifier legacyTestRpcNoInputIdentifier; + private org.opendaylight.mdsal.dom.api.DOMRpcIdentifier mdsalTestRpcIdentifier; + private org.opendaylight.mdsal.dom.api.DOMRpcIdentifier mdsalTestRpcNoInputIdentifier; + + @Before + public void setup() { + mdsalRpcRouter = new org.opendaylight.mdsal.dom.broker.DOMRpcRouter(); + final SchemaContext schemaContext = TestModel.createTestContext(); + mdsalRpcRouter.onGlobalContextUpdated(schemaContext); + legacyRpcRouter = new DOMRpcRouter(mdsalRpcRouter, mdsalRpcRouter); + + legacyTestRpcIdentifier = DOMRpcIdentifier.create(findRpc(schemaContext, "test-rpc")); + legacyTestRpcNoInputIdentifier = DOMRpcIdentifier.create(findRpc(schemaContext, "test-rpc-no-input")); + mdsalTestRpcIdentifier = org.opendaylight.mdsal.dom.api.DOMRpcIdentifier.create( + findRpc(schemaContext, "test-rpc")); + mdsalTestRpcNoInputIdentifier = org.opendaylight.mdsal.dom.api.DOMRpcIdentifier.create( + findRpc(schemaContext, "test-rpc-no-input")); + } + + @Test + public void testLegacyRegistrationAndInvocation() throws InterruptedException, ExecutionException { + final DOMRpcImplementationRegistration reg = + legacyRpcRouter.registerRpcImplementation(testLegacyRpcImpl, legacyTestRpcIdentifier, + legacyTestRpcNoInputIdentifier); + + // Test success + + DefaultDOMRpcResult result = new DefaultDOMRpcResult(RPC_OUTPUT); + testLegacyRpcImpl.init(Futures.immediateCheckedFuture(result)); + + ListenableFuture future = legacyRpcRouter.invokeRpc(legacyTestRpcIdentifier.getType(), RPC_INPUT); + + assertSame(result, future.get()); + testLegacyRpcImpl.verifyInput(legacyTestRpcIdentifier, RPC_INPUT); + + // Test exception returned + + TestLegacyDOMRpcException rpcEx = new TestLegacyDOMRpcException(); + testLegacyRpcImpl.init(Futures.immediateFailedCheckedFuture(rpcEx)); + + try { + legacyRpcRouter.invokeRpc(legacyTestRpcIdentifier.getType(), RPC_INPUT).get(); + fail("Expected exception"); + } catch (ExecutionException e) { + assertEquals(rpcEx, e.getCause()); + } + + // Test no input or output + + testLegacyRpcImpl.init(Futures.immediateCheckedFuture(null)); + + future = legacyRpcRouter.invokeRpc(legacyTestRpcNoInputIdentifier.getType(), null); + + assertNull(future.get()); + testLegacyRpcImpl.verifyInput(legacyTestRpcNoInputIdentifier, null); + + // Test close + + reg.close(); + + try { + legacyRpcRouter.invokeRpc(legacyTestRpcIdentifier.getType(), RPC_INPUT).get(); + fail("Expected exception"); + } catch (ExecutionException e) { + assertTrue(e.getCause() instanceof DOMRpcImplementationNotAvailableException); + } + } + + @Test + public void testLegacyRegistrationAndMdsalInvocation() throws InterruptedException, ExecutionException { + legacyRpcRouter.registerRpcImplementation(testLegacyRpcImpl, legacyTestRpcIdentifier, + legacyTestRpcNoInputIdentifier); + + // Test success + + DefaultDOMRpcResult result = new DefaultDOMRpcResult(RPC_OUTPUT, + Collections.singleton(RpcResultBuilder.newError(ErrorType.RPC, "tag", "message"))); + testLegacyRpcImpl.init(Futures.immediateCheckedFuture(result)); + + ListenableFuture future = + mdsalRpcRouter.invokeRpc(mdsalTestRpcIdentifier.getType(), RPC_INPUT); + + assertEquals(RPC_OUTPUT, future.get().getResult()); + assertEquals(1, future.get().getErrors().size()); + assertEquals(ErrorType.RPC, future.get().getErrors().iterator().next().getErrorType()); + assertEquals("tag", future.get().getErrors().iterator().next().getTag()); + assertEquals("message", future.get().getErrors().iterator().next().getMessage()); + testLegacyRpcImpl.verifyInput(legacyTestRpcIdentifier, RPC_INPUT); + + // Test exception returned + + TestLegacyDOMRpcException rpcEx = new TestLegacyDOMRpcException(); + testLegacyRpcImpl.init(Futures.immediateFailedCheckedFuture(rpcEx)); + + try { + mdsalRpcRouter.invokeRpc(mdsalTestRpcIdentifier.getType(), RPC_INPUT).get(); + fail("Expected exception"); + } catch (ExecutionException e) { + assertEquals(rpcEx, e.getCause()); + } + + // Test no input or output + + testLegacyRpcImpl.init(Futures.immediateCheckedFuture(null)); + + future = mdsalRpcRouter.invokeRpc(mdsalTestRpcNoInputIdentifier.getType(), null); + + assertNull(future.get()); + testLegacyRpcImpl.verifyInput(legacyTestRpcNoInputIdentifier, null); + } + + @Test + public void testMdsalRegistrationAndLegacyInvocation() throws InterruptedException, ExecutionException { + mdsalRpcRouter.registerRpcImplementation(testMdsalRpcImpl, mdsalTestRpcIdentifier, + mdsalTestRpcNoInputIdentifier); + + // Test success + + org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult result = + new org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult(RPC_OUTPUT, + Collections.singleton(RpcResultBuilder.newError(ErrorType.RPC, "tag", "message"))); + testMdsalRpcImpl.init(Futures.immediateCheckedFuture(result)); + + ListenableFuture future = legacyRpcRouter.invokeRpc(legacyTestRpcIdentifier.getType(), RPC_INPUT); + + assertEquals(RPC_OUTPUT, future.get().getResult()); + assertEquals(1, future.get().getErrors().size()); + assertEquals(ErrorType.RPC, future.get().getErrors().iterator().next().getErrorType()); + assertEquals("tag", future.get().getErrors().iterator().next().getTag()); + assertEquals("message", future.get().getErrors().iterator().next().getMessage()); + testMdsalRpcImpl.verifyInput(mdsalTestRpcIdentifier, RPC_INPUT); + + // Test exception returned + + TestMdsalDOMRpcException rpcEx = new TestMdsalDOMRpcException(); + testMdsalRpcImpl.init(Futures.immediateFailedCheckedFuture(rpcEx)); + + try { + legacyRpcRouter.invokeRpc(legacyTestRpcIdentifier.getType(), RPC_INPUT).get(); + fail("Expected exception"); + } catch (ExecutionException e) { + assertTrue("Unexpected exception " + e.getCause(), e.getCause() instanceof DOMRpcException); + assertEquals(rpcEx, e.getCause().getCause()); + } + + // Test no input or output + + testMdsalRpcImpl.init(Futures.immediateCheckedFuture(null)); + + future = legacyRpcRouter.invokeRpc(legacyTestRpcNoInputIdentifier.getType(), null); + + assertNull(future.get()); + testMdsalRpcImpl.verifyInput(mdsalTestRpcNoInputIdentifier, null); + } + + @Test + public void testRegisterRpcListener() { + final TestLegacyDOMRpcImplementation2 testRpcImpl2 = new TestLegacyDOMRpcImplementation2(); + + DOMRpcAvailabilityListener listener = mock(DOMRpcAvailabilityListener.class); + doNothing().when(listener).onRpcAvailable(any()); + doNothing().when(listener).onRpcUnavailable(any()); + doReturn(true).when(listener).acceptsImplementation(any()); + final ListenerRegistration listenerReg = legacyRpcRouter.registerRpcListener(listener); + + DOMRpcAvailabilityListener filteredListener = mock(DOMRpcAvailabilityListener.class); + doNothing().when(filteredListener).onRpcAvailable(any()); + doNothing().when(filteredListener).onRpcUnavailable(any()); + doReturn(true).when(filteredListener).acceptsImplementation(testLegacyRpcImpl); + doReturn(false).when(filteredListener).acceptsImplementation(testRpcImpl2); + final ListenerRegistration filteredListenerReg = legacyRpcRouter.registerRpcListener(filteredListener); + + final DOMRpcImplementationRegistration testRpcReg = + legacyRpcRouter.registerRpcImplementation(testLegacyRpcImpl, legacyTestRpcIdentifier); + + verify(listener, timeout(5000)).onRpcAvailable(ImmutableList.of(legacyTestRpcIdentifier)); + verify(filteredListener, timeout(5000)).onRpcAvailable(ImmutableList.of(legacyTestRpcIdentifier)); + + final DOMRpcImplementationRegistration testRpcNoInputReg = + legacyRpcRouter.registerRpcImplementation(testRpcImpl2, legacyTestRpcNoInputIdentifier); + + verify(listener, timeout(5000)).onRpcAvailable(ImmutableList.of(legacyTestRpcNoInputIdentifier)); + verify(filteredListener, after(200).never()).onRpcAvailable(ImmutableList.of(legacyTestRpcNoInputIdentifier)); + + testRpcReg.close(); + + verify(listener, timeout(5000)).onRpcUnavailable(ImmutableList.of(legacyTestRpcIdentifier)); + verify(filteredListener, timeout(5000)).onRpcUnavailable(ImmutableList.of(legacyTestRpcIdentifier)); + + testRpcNoInputReg.close(); + + verify(listener, timeout(5000)).onRpcUnavailable(ImmutableList.of(legacyTestRpcNoInputIdentifier)); + verify(filteredListener, after(200).never()).onRpcUnavailable(ImmutableList.of(legacyTestRpcNoInputIdentifier)); + + reset(listener, filteredListener); + + listenerReg.close(); + filteredListenerReg.close(); + + legacyRpcRouter.registerRpcImplementation(testLegacyRpcImpl, legacyTestRpcIdentifier); + + verify(listener, after(200).never()).onRpcAvailable(ImmutableList.of(legacyTestRpcIdentifier)); + verify(filteredListener, never()).onRpcAvailable(ImmutableList.of(legacyTestRpcIdentifier)); + } + + private static SchemaPath findRpc(SchemaContext schemaContext, String name) { + Module testModule = schemaContext.findModule("odl-datastore-test", TestModel.TEST_QNAME.getRevision()).get(); + RpcDefinition rpcDefinition = null; + for (RpcDefinition def: testModule.getRpcs()) { + if (def.getQName().getLocalName().equals(name)) { + rpcDefinition = def; + break; + } + } + + assertNotNull(name + " rpc not found in " + testModule.getRpcs(), rpcDefinition); + return rpcDefinition.getPath(); + } + + private abstract static class AbstractDOMRpcImplementation { + Entry> rpcInput; + + void verifyInput(T expRpc, NormalizedNode expInput) { + assertNotNull(rpcInput); + assertEquals(expRpc, rpcInput.getKey()); + assertEquals(expInput, rpcInput.getValue()); + } + } + + private static class TestLegacyDOMRpcImplementation extends AbstractDOMRpcImplementation + implements DOMRpcImplementation { + CheckedFuture returnFuture; + + @Override + public CheckedFuture invokeRpc( + final DOMRpcIdentifier rpc, final NormalizedNode input) { + rpcInput = new SimpleEntry<>(rpc, input); + return returnFuture; + } + + void init(CheckedFuture retFuture) { + this.returnFuture = retFuture; + rpcInput = null; + } + } + + private static class TestMdsalDOMRpcImplementation + extends AbstractDOMRpcImplementation + implements org.opendaylight.mdsal.dom.api.DOMRpcImplementation { + CheckedFuture returnFuture; + + @Override + public CheckedFuture invokeRpc( + final org.opendaylight.mdsal.dom.api.DOMRpcIdentifier rpc, final NormalizedNode input) { + rpcInput = new SimpleEntry<>(rpc, input); + return returnFuture; + } + + void init(CheckedFuture retFuture) { + this.returnFuture = retFuture; + rpcInput = null; + } + } + + private static class TestLegacyDOMRpcImplementation2 implements DOMRpcImplementation { + @Override + public CheckedFuture invokeRpc( + final DOMRpcIdentifier rpc, final NormalizedNode input) { + return null; + } + } + + private static class TestLegacyDOMRpcException extends DOMRpcException { + private static final long serialVersionUID = 1L; + + TestLegacyDOMRpcException() { + super("test"); + } + } + + private static class TestMdsalDOMRpcException extends org.opendaylight.mdsal.dom.api.DOMRpcException { + private static final long serialVersionUID = 1L; + + TestMdsalDOMRpcException() { + super("test"); + } + } +} diff --git a/opendaylight/md-sal/sal-dom-broker/src/test/resources/odl-datastore-test.yang b/opendaylight/md-sal/sal-dom-broker/src/test/resources/odl-datastore-test.yang index a0bf157e35..f7b960e9f9 100644 --- a/opendaylight/md-sal/sal-dom-broker/src/test/resources/odl-datastore-test.yang +++ b/opendaylight/md-sal/sal-dom-broker/src/test/resources/odl-datastore-test.yang @@ -2,7 +2,7 @@ module odl-datastore-test { yang-version 1; namespace "urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:store:test"; prefix "store-test"; - + revision "2014-03-13" { description "Initial revision."; } @@ -44,4 +44,27 @@ module odl-datastore-test { container test2 { } + + rpc test-rpc { + input { + leaf input-leaf { + type string; + } + } + + output { + leaf output-leaf { + type string; + } + } + } + + rpc test-rpc-no-input { + } + + notification test-notification { + leaf value-leaf { + type string; + } + } } -- 2.36.6