From 0ecd0c6a0930bb2aae015ce26fc689d88fb09ff8 Mon Sep 17 00:00:00 2001 From: Tomas Cere Date: Thu, 9 Mar 2017 14:12:45 +0100 Subject: [PATCH] Bug 7803: Implement agent RPCs for data tree change listener testing Change-Id: Id2d53d3765fb9d518d4b052792d716d2b2b4c976 Signed-off-by: Tomas Cere --- .../main/yang/odl-mdsal-lowlevel-control.yang | 10 ++- .../provider/MdsalLowLevelTestProvider.java | 71 ++++++++++++++++++- .../it/provider/impl/IdIntsListener.java | 57 +++++++++++++++ 3 files changed, 130 insertions(+), 8 deletions(-) create mode 100644 opendaylight/md-sal/samples/clustering-test-app/provider/src/main/java/org/opendaylight/controller/clustering/it/provider/impl/IdIntsListener.java diff --git a/opendaylight/md-sal/samples/clustering-test-app/model/src/main/yang/odl-mdsal-lowlevel-control.yang b/opendaylight/md-sal/samples/clustering-test-app/model/src/main/yang/odl-mdsal-lowlevel-control.yang index a10b3ce628..dbd65d19aa 100644 --- a/opendaylight/md-sal/samples/clustering-test-app/model/src/main/yang/odl-mdsal-lowlevel-control.yang +++ b/opendaylight/md-sal/samples/clustering-test-app/model/src/main/yang/odl-mdsal-lowlevel-control.yang @@ -433,12 +433,10 @@ module odl-mdsal-lowlevel-control { rpc subscribe-dtcl { description "Upon receiving this, the member checks whether it has already subscribed and fails if yes. If no, the member subscribes a Data Tree Change Listener - to listen for changes on whole llt:id-ints, and stores the state - from the initial notification to a local variable (called the local copy). - Each Data Tree Change from further Notifications shall be applied - to the local copy if it is compatible - (the old state from notification is equal to the local copy state). - If a notification is not compatible, it shall be ignored."; + to listen for changes on whole llt:id-ints. The first notification received is stored immediately. + Every notification received after the first one has the data(getDataBefore()) compared with the + last stored notification(called local copy), if they match the local copy is overwritten with + this notifications data(getDataAfter()). If they don't match the new notification is ignored."; // No input. // No output. } diff --git a/opendaylight/md-sal/samples/clustering-test-app/provider/src/main/java/org/opendaylight/controller/clustering/it/provider/MdsalLowLevelTestProvider.java b/opendaylight/md-sal/samples/clustering-test-app/provider/src/main/java/org/opendaylight/controller/clustering/it/provider/MdsalLowLevelTestProvider.java index 15c9d8ea60..f9f82a237d 100644 --- a/opendaylight/md-sal/samples/clustering-test-app/provider/src/main/java/org/opendaylight/controller/clustering/it/provider/MdsalLowLevelTestProvider.java +++ b/opendaylight/md-sal/samples/clustering-test-app/provider/src/main/java/org/opendaylight/controller/clustering/it/provider/MdsalLowLevelTestProvider.java @@ -8,6 +8,7 @@ package org.opendaylight.controller.clustering.it.provider; +import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -25,6 +26,7 @@ import org.opendaylight.controller.cluster.sharding.DistributedShardFactory; import org.opendaylight.controller.clustering.it.provider.impl.FlappingSingletonService; import org.opendaylight.controller.clustering.it.provider.impl.GetConstantService; import org.opendaylight.controller.clustering.it.provider.impl.IdIntsDOMDataTreeLIstener; +import org.opendaylight.controller.clustering.it.provider.impl.IdIntsListener; import org.opendaylight.controller.clustering.it.provider.impl.PrefixShardHandler; import org.opendaylight.controller.clustering.it.provider.impl.ProduceTransactionsHandler; import org.opendaylight.controller.clustering.it.provider.impl.PublishNotificationsTask; @@ -34,7 +36,11 @@ import org.opendaylight.controller.clustering.it.provider.impl.WriteTransactions import org.opendaylight.controller.clustering.it.provider.impl.YnlListener; import org.opendaylight.controller.md.sal.binding.api.NotificationPublishService; import org.opendaylight.controller.md.sal.binding.api.NotificationService; +import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker; +import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction; +import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeListener; +import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeService; import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementationRegistration; import org.opendaylight.controller.md.sal.dom.api.DOMRpcProviderService; import org.opendaylight.controller.sal.binding.api.BindingAwareBroker; @@ -73,6 +79,7 @@ import org.opendaylight.yang.gen.v1.tag.opendaylight.org._2017.controller.yang.l import org.opendaylight.yang.gen.v1.tag.opendaylight.org._2017.controller.yang.lowlevel.control.rev170215.UnsubscribeDdtlOutput; import org.opendaylight.yang.gen.v1.tag.opendaylight.org._2017.controller.yang.lowlevel.control.rev170215.UnsubscribeDdtlOutputBuilder; import org.opendaylight.yang.gen.v1.tag.opendaylight.org._2017.controller.yang.lowlevel.control.rev170215.UnsubscribeDtclOutput; +import org.opendaylight.yang.gen.v1.tag.opendaylight.org._2017.controller.yang.lowlevel.control.rev170215.UnsubscribeDtclOutputBuilder; import org.opendaylight.yang.gen.v1.tag.opendaylight.org._2017.controller.yang.lowlevel.control.rev170215.UnsubscribeYnlInput; import org.opendaylight.yang.gen.v1.tag.opendaylight.org._2017.controller.yang.lowlevel.control.rev170215.UnsubscribeYnlOutput; import org.opendaylight.yang.gen.v1.tag.opendaylight.org._2017.controller.yang.lowlevel.control.rev170215.WriteTransactionsInput; @@ -91,6 +98,8 @@ import org.slf4j.LoggerFactory; public class MdsalLowLevelTestProvider implements OdlMdsalLowlevelControlService { private static final Logger LOG = LoggerFactory.getLogger(MdsalLowLevelTestProvider.class); + private static final org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType CONTROLLER_CONFIG = + org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.CONFIGURATION; private final RpcProviderRegistry rpcRegistry; private final BindingAwareBroker.RpcRegistration registration; @@ -104,6 +113,7 @@ public class MdsalLowLevelTestProvider implements OdlMdsalLowlevelControlService private final ClusterSingletonServiceProvider singletonService; private final DOMRpcProviderService domRpcService; private final PrefixShardHandler prefixShardHandler; + private final DOMDataTreeChangeService domDataTreeChangeService; private Map, DOMRpcImplementationRegistration> routedRegistrations = new HashMap<>(); @@ -113,10 +123,14 @@ public class MdsalLowLevelTestProvider implements OdlMdsalLowlevelControlService private DOMRpcImplementationRegistration globalGetConstantRegistration = null; private ClusterSingletonServiceRegistration getSingletonConstantRegistration; private FlappingSingletonService flappingSingletonService; + private ListenerRegistration dtclReg; + private IdIntsListener idIntsListener; private Map publishNotificationsTasks = new HashMap<>(); private ListenerRegistration ddtlReg; private IdIntsDOMDataTreeLIstener idIntsDdtl; + + public MdsalLowLevelTestProvider(final RpcProviderRegistry rpcRegistry, final DOMRpcProviderService domRpcService, final ClusterSingletonServiceProvider singletonService, @@ -138,6 +152,9 @@ public class MdsalLowLevelTestProvider implements OdlMdsalLowlevelControlService this.domDataTreeService = domDataTreeService; this.distributedShardFactory = distributedShardFactory; + domDataTreeChangeService = + (DOMDataTreeChangeService) domDataBroker.getSupportedExtensions().get(DOMDataTreeChangeService.class); + registration = rpcRegistry.addRpcImplementation(OdlMdsalLowlevelControlService.class, this); prefixShardHandler = new PrefixShardHandler(distributedShardFactory, bindingNormalizedNodeSerializer); @@ -185,7 +202,22 @@ public class MdsalLowLevelTestProvider implements OdlMdsalLowlevelControlService @Override public Future> subscribeDtcl() { - return null; + + if (dtclReg != null) { + final RpcError error = RpcResultBuilder.newError(ErrorType.RPC, "Registration present.", + "There is already dataTreeChangeListener registered on id-ints list."); + return Futures.immediateFuture(RpcResultBuilder.failed().withRpcError(error).build()); + } + + idIntsListener = new IdIntsListener(); + + dtclReg = domDataTreeChangeService + .registerDataTreeChangeListener( + new org.opendaylight.controller.md.sal.dom.api.DOMDataTreeIdentifier( + CONTROLLER_CONFIG, WriteTransactionsHandler.ID_INTS_YID), + idIntsListener); + + return Futures.immediateFuture(RpcResultBuilder.success().build()); } @Override @@ -394,7 +426,42 @@ public class MdsalLowLevelTestProvider implements OdlMdsalLowlevelControlService @Override public Future> unsubscribeDtcl() { - return null; + LOG.debug("Received unsubscribe-dtcl"); + + if (idIntsListener == null || dtclReg == null) { + final RpcError error = RpcResultBuilder.newError( + ErrorType.RPC, "Dtcl missing.", "No DataTreeChangeListener registered."); + return Futures.immediateFuture(RpcResultBuilder.failed().withRpcError(error).build()); + } + + final DOMDataReadOnlyTransaction rTx = domDataBroker.newReadOnlyTransaction(); + try { + if (dtclReg != null) { + dtclReg.close(); + dtclReg = null; + } + + final Optional> readResult = + rTx.read(CONTROLLER_CONFIG, WriteTransactionsHandler.ID_INTS_YID).checkedGet(); + + if (!readResult.isPresent()) { + final RpcError error = RpcResultBuilder.newError( + ErrorType.APPLICATION, "Final read empty.", "No data read from id-ints list."); + return Futures.immediateFuture(RpcResultBuilder.failed() + .withRpcError(error).build()); + } + + return Futures.immediateFuture( + RpcResultBuilder.success(new UnsubscribeDtclOutputBuilder() + .setCopyMatches(idIntsListener.checkEqual(readResult.get()))).build()); + + } catch (final ReadFailedException e) { + final RpcError error = RpcResultBuilder.newError( + ErrorType.APPLICATION, "Read failed.", "Final read from id-ints failed."); + return Futures.immediateFuture(RpcResultBuilder.failed() + .withRpcError(error).build()); + + } } @Override diff --git a/opendaylight/md-sal/samples/clustering-test-app/provider/src/main/java/org/opendaylight/controller/clustering/it/provider/impl/IdIntsListener.java b/opendaylight/md-sal/samples/clustering-test-app/provider/src/main/java/org/opendaylight/controller/clustering/it/provider/impl/IdIntsListener.java new file mode 100644 index 0000000000..5767008d05 --- /dev/null +++ b/opendaylight/md-sal/samples/clustering-test-app/provider/src/main/java/org/opendaylight/controller/clustering/it/provider/impl/IdIntsListener.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2017 Cisco Systems, Inc. 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.clustering.it.provider.impl; + +import com.google.common.base.Preconditions; +import java.util.Collection; +import javax.annotation.Nonnull; +import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeListener; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate; +import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType; +import org.opendaylight.yangtools.yang.data.impl.schema.tree.InMemoryDataTreeFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class IdIntsListener implements DOMDataTreeChangeListener { + + private static final Logger LOG = LoggerFactory.getLogger(IdIntsListener.class); + + private NormalizedNode localCopy = null; + + @Override + public void onDataTreeChanged(@Nonnull final Collection changes) { + + // There should only be one candidate reported + Preconditions.checkState(changes.size() == 1); + + // do not log the change into debug, only use trace since it will lead to OOM on default heap settings + LOG.debug("Received data tree changed"); + + changes.forEach(change -> { + if (change.getRootNode().getDataAfter().isPresent()) { + LOG.trace("Received change, data before: {}, data after: ", change.getRootNode().getDataBefore().get(), + change.getRootNode().getDataAfter().get()); + + if (localCopy == null || checkEqual(change.getRootNode().getDataBefore().get())) { + localCopy = change.getRootNode().getDataAfter().get(); + } else { + LOG.debug("Ignoring notification: {}", change); + } + } else { + LOG.warn("getDataAfter() is missing from notification. change: {}", change); + } + }); + } + + public boolean checkEqual(final NormalizedNode expected) { + return localCopy.equals(expected); + } +} -- 2.36.6