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<BrokerService> services = MutableClassToInstanceMap.create();
services.put(DOMRpcService.class, this.domRouter);
*/
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;
/**
final Object... args) {
super(String.format(format, args), Preconditions.checkNotNull(cause));
}
+
+ public DOMRpcImplementationNotAvailableException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
}
*/
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<RpcError> 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 {
}
--- /dev/null
+/*
+ * 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);
+ }
+}
*/
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<org.opendaylight.mdsal.dom.api.DOMRpcException> MDSAL_DOM_RPC_EX_MAPPER =
+ new ExceptionMapper<org.opendaylight.mdsal.dom.api.DOMRpcException>(
+ "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<DOMRpcException> LEGACY_DOM_RPC_EX_MAPPER =
+ new ExceptionMapper<DOMRpcException>("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<org.opendaylight.mdsal.dom.api.DOMRpcImplementation, DOMRpcImplementation> implMapping =
+ Collections.synchronizedMap(new WeakHashMap<>());
- @GuardedBy("this")
- private Collection<Registration<?>> 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
return registerRpcImplementation(implementation, ImmutableSet.copyOf(rpcs));
}
+ @SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public synchronized <T extends DOMRpcImplementation> DOMRpcImplementationRegistration<T> registerRpcImplementation(
final T implementation, final Set<DOMRpcIdentifier> 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<org.opendaylight.mdsal.dom.api.DOMRpcResult,
+ org.opendaylight.mdsal.dom.api.DOMRpcException> 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
+ <org.opendaylight.mdsal.dom.api.DOMRpcImplementation> reg = delegateRpcProviderService
+ .registerRpcImplementation(delegateImpl,
+ rpcs.stream().map(DOMRpcRouter::convert).collect(Collectors.toSet()));
return new AbstractDOMRpcImplementationRegistration<T>(implementation) {
@Override
protected void removeRegistration() {
- removeRpcImplementation(getInstance(), rpcs);
+ reg.close();
+ implMapping.remove(delegateImpl);
}
};
}
- private synchronized void removeRpcImplementation(final DOMRpcImplementation implementation,
- final Set<DOMRpcIdentifier> 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<DOMRpcResult, DOMRpcException> 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<? extends DOMRpcAvailabilityListener> reg) {
- listeners = ImmutableList.copyOf(Collections2.filter(listeners, i -> !reg.equals(i)));
+ private static ListenableFuture<DOMRpcResult> 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<DOMRpcIdentifier> convert(
+ Collection<org.opendaylight.mdsal.dom.api.DOMRpcIdentifier> 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<DOMRpcResult, DOMRpcException> invokeRpc(final SchemaPath type,
+ final NormalizedNode<?, ?> input) {
+ final ListenableFuture<DOMRpcResult> future = Futures.transformAsync(delegateRpcService.invokeRpc(type, input),
+ DOMRpcRouter::convert, MoreExecutors.directExecutor());
+ return MappingCheckedFuture.create(future, LEGACY_DOM_RPC_EX_MAPPER);
}
@Override
public synchronized <T extends DOMRpcAvailabilityListener> ListenerRegistration<T> registerRpcListener(
final T listener) {
- final Registration<T> ret = new Registration<>(this, listener, routingTable.getRpcs(listener));
- final Builder<Registration<?>> 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<T extends DOMRpcAvailabilityListener> extends
- AbstractListenerRegistration<T> {
-
- private final DOMRpcRouter router;
-
- private Map<SchemaPath, Set<YangInstanceIdentifier>> prevRpcs;
-
- Registration(final DOMRpcRouter router, final T listener,
- final Map<SchemaPath, Set<YangInstanceIdentifier>> 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<DOMRpcIdentifier> added = new ArrayList<>();
- for (Entry<SchemaPath, Set<YangInstanceIdentifier>> 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<SchemaPath, Set<YangInstanceIdentifier>> rpcs = Verify.verifyNotNull(newTable.getRpcs(l));
- final MapDifference<SchemaPath, Set<YangInstanceIdentifier>> diff = Maps.difference(prevRpcs, rpcs);
+ final ListenerRegistration<org.opendaylight.mdsal.dom.api.DOMRpcAvailabilityListener> reg =
+ delegateRpcService.registerRpcListener(new org.opendaylight.mdsal.dom.api.DOMRpcAvailabilityListener() {
+ @Override
+ public void onRpcAvailable(Collection<org.opendaylight.mdsal.dom.api.DOMRpcIdentifier> rpcs) {
+ listener.onRpcAvailable(convert(rpcs));
+ }
- final Collection<DOMRpcIdentifier> added = new ArrayList<>();
- for (Entry<SchemaPath, Set<YangInstanceIdentifier>> e : diff.entriesOnlyOnRight().entrySet()) {
- added.addAll(Collections2.transform(e.getValue(), i -> DOMRpcIdentifier.create(e.getKey(), i)));
- }
- for (Entry<SchemaPath, ValueDifference<Set<YangInstanceIdentifier>>> 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<org.opendaylight.mdsal.dom.api.DOMRpcIdentifier> 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<T>(listener) {
+ @Override
+ protected void removeRegistration() {
+ reg.close();
}
+ };
+ }
- final Map<SchemaPath, Set<YangInstanceIdentifier>> rpcs = Verify.verifyNotNull(newTable.getRpcs(l));
- final MapDifference<SchemaPath, Set<YangInstanceIdentifier>> diff = Maps.difference(prevRpcs, rpcs);
-
- final Collection<DOMRpcIdentifier> removed = new ArrayList<>();
- for (Entry<SchemaPath, Set<YangInstanceIdentifier>> e : diff.entriesOnlyOnLeft().entrySet()) {
- removed.addAll(Collections2.transform(e.getValue(), i -> DOMRpcIdentifier.create(e.getKey(), i)));
- }
- for (Entry<SchemaPath, ValueDifference<Set<YangInstanceIdentifier>>> 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);
}
}
}
<!-- DOM RPC Service -->
- <bean id="domRpcRouter" class="org.opendaylight.controller.md.sal.dom.broker.impl.DOMRpcRouter"
- factory-method="newInstance">
- <argument ref="schemaService"/>
+ <reference id="domRpcService" interface="org.opendaylight.mdsal.dom.api.DOMRpcService"
+ odl:type="default"/>
+ <reference id="domRpcProviderService" interface="org.opendaylight.mdsal.dom.api.DOMRpcProviderService"
+ odl:type="default"/>
+
+ <bean id="domRpcRouter" class="org.opendaylight.controller.md.sal.dom.broker.impl.DOMRpcRouter">
+ <argument ref="domRpcService"/>
+ <argument ref="domRpcProviderService"/>
</bean>
<service ref="domRpcRouter" odl:type="default">
--- /dev/null
+/*
+ * 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<TestLegacyDOMRpcImplementation> reg =
+ legacyRpcRouter.registerRpcImplementation(testLegacyRpcImpl, legacyTestRpcIdentifier,
+ legacyTestRpcNoInputIdentifier);
+
+ // Test success
+
+ DefaultDOMRpcResult result = new DefaultDOMRpcResult(RPC_OUTPUT);
+ testLegacyRpcImpl.init(Futures.immediateCheckedFuture(result));
+
+ ListenableFuture<DOMRpcResult> 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<org.opendaylight.mdsal.dom.api.DOMRpcResult> 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<DOMRpcResult> 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<T> {
+ Entry<T, NormalizedNode<?, ?>> rpcInput;
+
+ void verifyInput(T expRpc, NormalizedNode<?, ?> expInput) {
+ assertNotNull(rpcInput);
+ assertEquals(expRpc, rpcInput.getKey());
+ assertEquals(expInput, rpcInput.getValue());
+ }
+ }
+
+ private static class TestLegacyDOMRpcImplementation extends AbstractDOMRpcImplementation<DOMRpcIdentifier>
+ implements DOMRpcImplementation {
+ CheckedFuture<DOMRpcResult, DOMRpcException> returnFuture;
+
+ @Override
+ public CheckedFuture<DOMRpcResult, DOMRpcException> invokeRpc(
+ final DOMRpcIdentifier rpc, final NormalizedNode<?, ?> input) {
+ rpcInput = new SimpleEntry<>(rpc, input);
+ return returnFuture;
+ }
+
+ void init(CheckedFuture<DOMRpcResult, DOMRpcException> retFuture) {
+ this.returnFuture = retFuture;
+ rpcInput = null;
+ }
+ }
+
+ private static class TestMdsalDOMRpcImplementation
+ extends AbstractDOMRpcImplementation<org.opendaylight.mdsal.dom.api.DOMRpcIdentifier>
+ implements org.opendaylight.mdsal.dom.api.DOMRpcImplementation {
+ CheckedFuture<org.opendaylight.mdsal.dom.api.DOMRpcResult,
+ org.opendaylight.mdsal.dom.api.DOMRpcException> returnFuture;
+
+ @Override
+ public CheckedFuture<org.opendaylight.mdsal.dom.api.DOMRpcResult,
+ org.opendaylight.mdsal.dom.api.DOMRpcException> invokeRpc(
+ final org.opendaylight.mdsal.dom.api.DOMRpcIdentifier rpc, final NormalizedNode<?, ?> input) {
+ rpcInput = new SimpleEntry<>(rpc, input);
+ return returnFuture;
+ }
+
+ void init(CheckedFuture<org.opendaylight.mdsal.dom.api.DOMRpcResult,
+ org.opendaylight.mdsal.dom.api.DOMRpcException> retFuture) {
+ this.returnFuture = retFuture;
+ rpcInput = null;
+ }
+ }
+
+ private static class TestLegacyDOMRpcImplementation2 implements DOMRpcImplementation {
+ @Override
+ public CheckedFuture<DOMRpcResult, DOMRpcException> 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");
+ }
+ }
+}
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.";
}
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;
+ }
+ }
}