Bug 7803: Implement agent RPCs for data tree change listener testing 78/53278/13
authorTomas Cere <tcere@cisco.com>
Thu, 9 Mar 2017 13:12:45 +0000 (14:12 +0100)
committerTom Pantelis <tompantelis@gmail.com>
Sat, 1 Apr 2017 08:39:35 +0000 (08:39 +0000)
Change-Id: Id2d53d3765fb9d518d4b052792d716d2b2b4c976
Signed-off-by: Tomas Cere <tcere@cisco.com>
opendaylight/md-sal/samples/clustering-test-app/model/src/main/yang/odl-mdsal-lowlevel-control.yang
opendaylight/md-sal/samples/clustering-test-app/provider/src/main/java/org/opendaylight/controller/clustering/it/provider/MdsalLowLevelTestProvider.java
opendaylight/md-sal/samples/clustering-test-app/provider/src/main/java/org/opendaylight/controller/clustering/it/provider/impl/IdIntsListener.java [new file with mode: 0644]

index a10b3ce628154943f0a98d9f8adc396e3ff29ebe..dbd65d19aaf1adf47ecac8f1a50434bdf1c61700 100644 (file)
@@ -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.
     }
index 15c9d8ea60f6a4bde6d07cca820ee40824c5329d..f9f82a237d2ceee8177e08aa8f94c2b6bd0f8d55 100644 (file)
@@ -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<OdlMdsalLowlevelControlService> 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<InstanceIdentifier<?>, DOMRpcImplementationRegistration<RoutedGetConstantService>> routedRegistrations =
             new HashMap<>();
@@ -113,10 +123,14 @@ public class MdsalLowLevelTestProvider implements OdlMdsalLowlevelControlService
     private DOMRpcImplementationRegistration<GetConstantService> globalGetConstantRegistration = null;
     private ClusterSingletonServiceRegistration getSingletonConstantRegistration;
     private FlappingSingletonService flappingSingletonService;
+    private ListenerRegistration<DOMDataTreeChangeListener> dtclReg;
+    private IdIntsListener idIntsListener;
     private Map<String, PublishNotificationsTask> publishNotificationsTasks = new HashMap<>();
     private ListenerRegistration<IdIntsDOMDataTreeLIstener> 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<RpcResult<Void>> 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.<Void>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.<Void>success().build());
     }
 
     @Override
@@ -394,7 +426,42 @@ public class MdsalLowLevelTestProvider implements OdlMdsalLowlevelControlService
 
     @Override
     public Future<RpcResult<UnsubscribeDtclOutput>> 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.<UnsubscribeDtclOutput>failed().withRpcError(error).build());
+        }
+
+        final DOMDataReadOnlyTransaction rTx = domDataBroker.newReadOnlyTransaction();
+        try {
+            if (dtclReg != null) {
+                dtclReg.close();
+                dtclReg = null;
+            }
+
+            final Optional<NormalizedNode<?, ?>> 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.<UnsubscribeDtclOutput>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.<UnsubscribeDtclOutput>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 (file)
index 0000000..5767008
--- /dev/null
@@ -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<DataTreeCandidate> 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);
+    }
+}