Move transaction chain handling 07/103607/17
authorRobert Varga <robert.varga@pantheon.tech>
Mon, 5 Dec 2022 23:36:25 +0000 (00:36 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Thu, 8 Dec 2022 01:21:24 +0000 (02:21 +0100)
NetconfDeviceTopologyAdapter is the only thing using DataBroker, push
the transaction chain lifecycle management into it.

JIRA: NETCONF-918
Change-Id: I137bd759204be283e09e98ae88dfc4d8484ce8be
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalProvider.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceTopologyAdapter.java
netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeficeTopologyAdapterIntegrationTest.java [new file with mode: 0644]
netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacadeTest.java
netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalProviderTest.java
netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceTopologyAdapterTest.java

index 05e75708a9dd9bb097b727134aa91c14345f5de9..55ede4ccc121e0338ab03d04058e0611bb4917e7 100644 (file)
@@ -12,9 +12,6 @@ import static com.google.common.base.Preconditions.checkState;
 import static java.util.Objects.requireNonNull;
 
 import org.opendaylight.mdsal.binding.api.DataBroker;
-import org.opendaylight.mdsal.binding.api.Transaction;
-import org.opendaylight.mdsal.binding.api.TransactionChain;
-import org.opendaylight.mdsal.binding.api.TransactionChainListener;
 import org.opendaylight.mdsal.dom.api.DOMActionService;
 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
@@ -36,40 +33,20 @@ public class NetconfDeviceSalProvider implements AutoCloseable {
 
     private final RemoteDeviceId id;
     private final MountInstance mountInstance;
-    private final DataBroker dataBroker;
 
     private volatile NetconfDeviceTopologyAdapter topologyDatastoreAdapter;
 
-    private TransactionChain txChain;
-
-    private final TransactionChainListener transactionChainListener =  new TransactionChainListener() {
-        @Override
-        public void onTransactionChainFailed(final TransactionChain chain, final Transaction transaction,
-                final Throwable cause) {
-            LOG.error("{}: TransactionChain({}) {} FAILED!", id, chain, transaction.getIdentifier(), cause);
-            chain.close();
-            resetTransactionChainForAdapaters();
-            throw new IllegalStateException(id + "  TransactionChain(" + chain + ") not committed correctly", cause);
-        }
-
-        @Override
-        public void onTransactionChainSuccessful(final TransactionChain chain) {
-            LOG.trace("{}: TransactionChain({}) SUCCESSFUL", id, chain);
-        }
-    };
-
     public NetconfDeviceSalProvider(final RemoteDeviceId deviceId, final DOMMountPointService mountService) {
         this(deviceId, mountService, null);
     }
 
+    // FIXME: NETCONF-918: remove this method
     public NetconfDeviceSalProvider(final RemoteDeviceId deviceId, final DOMMountPointService mountService,
             final DataBroker dataBroker) {
-        this.id = deviceId;
+        id = deviceId;
         mountInstance = new MountInstance(mountService, id);
-        this.dataBroker = dataBroker;
         if (dataBroker != null) {
-            txChain = requireNonNull(dataBroker).createTransactionChain(transactionChainListener);
-            topologyDatastoreAdapter = new NetconfDeviceTopologyAdapter(id, txChain);
+            topologyDatastoreAdapter = new NetconfDeviceTopologyAdapter(dataBroker, id);
         }
     }
 
@@ -86,21 +63,12 @@ public class NetconfDeviceSalProvider implements AutoCloseable {
         return local;
     }
 
-    private void resetTransactionChainForAdapaters() {
-        txChain = requireNonNull(dataBroker).createTransactionChain(transactionChainListener);
-        topologyDatastoreAdapter.setTxChain(txChain);
-        LOG.trace("{}: Resetting TransactionChain {}", id, txChain);
-    }
-
     @Override
     public void close() {
         mountInstance.close();
         if (topologyDatastoreAdapter != null) {
             topologyDatastoreAdapter.close();
-        }
-        topologyDatastoreAdapter = null;
-        if (txChain != null) {
-            txChain.close();
+            topologyDatastoreAdapter = null;
         }
     }
 
@@ -138,7 +106,7 @@ public class NetconfDeviceSalProvider implements AutoCloseable {
             if (netconfService != null) {
                 mountBuilder.addService(NetconfDataTreeService.class, netconfService);
             }
-            this.notificationService = newNotificationService;
+            notificationService = newNotificationService;
 
             topologyRegistration = mountBuilder.register();
             LOG.debug("{}: TOPOLOGY Mountpoint exposed into MD-SAL {}", id, topologyRegistration);
index 178d7331dab490e75c3f2b0b54db1d76481b4ea8..46eb46c0648077bbb06fdbf4e4ae99d23853c2dd 100644 (file)
@@ -11,12 +11,16 @@ import static java.util.Objects.requireNonNull;
 
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
+import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.binding.api.Transaction;
 import org.opendaylight.mdsal.binding.api.TransactionChain;
+import org.opendaylight.mdsal.binding.api.TransactionChainListener;
 import org.opendaylight.mdsal.binding.api.WriteTransaction;
 import org.opendaylight.mdsal.common.api.CommitInfo;
 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
@@ -36,22 +40,25 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev15
 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeBuilder;
 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey;
+import org.opendaylight.yangtools.yang.common.Empty;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.Uint16;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-// Non-final for testing
-public class NetconfDeviceTopologyAdapter implements AutoCloseable {
+public final class NetconfDeviceTopologyAdapter implements TransactionChainListener, AutoCloseable {
     private static final Logger LOG = LoggerFactory.getLogger(NetconfDeviceTopologyAdapter.class);
 
+    private final SettableFuture<Empty> closeFuture = SettableFuture.create();
+    private final DataBroker dataBroker;
     private final RemoteDeviceId id;
 
     private TransactionChain txChain;
 
-    NetconfDeviceTopologyAdapter(final RemoteDeviceId id, final TransactionChain txChain) {
+    NetconfDeviceTopologyAdapter(final DataBroker dataBroker, final RemoteDeviceId id) {
+        this.dataBroker = requireNonNull(dataBroker);
         this.id = requireNonNull(id);
-        this.txChain = requireNonNull(txChain);
+        txChain = dataBroker.createMergingTransactionChain(this);
 
         final WriteTransaction writeTx = txChain.newWriteOnlyTransaction();
         LOG.trace("{}: Init device state transaction {} putting if absent operational data started.", id,
@@ -106,6 +113,23 @@ public class NetconfDeviceTopologyAdapter implements AutoCloseable {
         commitTransaction(writeTx, "update");
     }
 
+    @Override
+    public void onTransactionChainFailed(final TransactionChain chain, final Transaction transaction,
+            final Throwable cause) {
+        LOG.warn("{}: TransactionChain({}) {} FAILED!", id, chain, transaction.getIdentifier(), cause);
+        chain.close();
+
+        txChain = dataBroker.createMergingTransactionChain(this);
+        LOG.info("{}: TransactionChain reset to {}", id, txChain);
+        // FIXME: restart last update
+    }
+
+    @Override
+    public void onTransactionChainSuccessful(final TransactionChain chain) {
+        LOG.trace("{}: TransactionChain({}) SUCCESSFUL", id, chain);
+        closeFuture.set(Empty.value());
+    }
+
     public void setDeviceAsFailed(final Throwable throwable) {
         String reason = throwable != null && throwable.getMessage() != null ? throwable.getMessage() : "Unknown reason";
 
@@ -171,22 +195,6 @@ public class NetconfDeviceTopologyAdapter implements AutoCloseable {
                 e.getValue()).build()).collect(Collectors.toList())).build();
     }
 
-    public void removeDeviceConfiguration() {
-        final WriteTransaction writeTx = txChain.newWriteOnlyTransaction();
-
-        LOG.trace("{}: Close device state transaction {} removing all data started.", id, writeTx.getIdentifier());
-        writeTx.delete(LogicalDatastoreType.OPERATIONAL, id.getTopologyBindingPath());
-        LOG.trace("{}: Close device state transaction {} removing all data ended.", id, writeTx.getIdentifier());
-
-        final var future = writeTx.commit();
-        try {
-            future.get();
-        } catch (InterruptedException | ExecutionException e) {
-            LOG.error("{}: Transaction(close) {} FAILED!", id, writeTx.getIdentifier(), e);
-            throw new IllegalStateException(id + "  Transaction(close) not committed correctly", e);
-        }
-    }
-
     private void commitTransaction(final WriteTransaction transaction, final String txType) {
         LOG.trace("{}: Committing Transaction {}:{}", id, txType, transaction.getIdentifier());
 
@@ -204,17 +212,24 @@ public class NetconfDeviceTopologyAdapter implements AutoCloseable {
     }
 
     private static NodeBuilder getNodeIdBuilder(final RemoteDeviceId id) {
-        final NodeBuilder nodeBuilder = new NodeBuilder();
-        nodeBuilder.withKey(new NodeKey(new NodeId(id.getName())));
-        return nodeBuilder;
+        return new NodeBuilder().withKey(new NodeKey(new NodeId(id.getName())));
     }
 
     @Override
     public void close() {
-        removeDeviceConfiguration();
-    }
+        final WriteTransaction writeTx = txChain.newWriteOnlyTransaction();
+        LOG.trace("{}: Close device state transaction {} removing all data started.", id, writeTx.getIdentifier());
+        writeTx.delete(LogicalDatastoreType.OPERATIONAL, id.getTopologyBindingPath());
+        LOG.trace("{}: Close device state transaction {} removing all data ended.", id, writeTx.getIdentifier());
+        commitTransaction(writeTx, "close");
+
+        txChain.close();
 
-    public void setTxChain(final TransactionChain txChain) {
-        this.txChain = requireNonNull(txChain);
+        try {
+            closeFuture.get();
+        } catch (InterruptedException | ExecutionException e) {
+            LOG.error("{}: Transaction(close) {} FAILED!", id, writeTx.getIdentifier(), e);
+            throw new IllegalStateException(id + "  Transaction(close) not committed correctly", e);
+        }
     }
 }
diff --git a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeficeTopologyAdapterIntegrationTest.java b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeficeTopologyAdapterIntegrationTest.java
new file mode 100644 (file)
index 0000000..8b31a31
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2022 PANTHEON.tech, s.r.o. 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.netconf.sal.connect.netconf.sal;
+
+import static org.junit.Assert.assertEquals;
+
+import java.net.InetSocketAddress;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+import org.awaitility.Awaitility;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.binding.dom.adapter.test.ConcurrentDataBrokerTestCustomizer;
+import org.opendaylight.mdsal.binding.runtime.api.BindingRuntimeContext;
+import org.opendaylight.mdsal.binding.runtime.spi.BindingRuntimeHelpers;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.dom.api.DOMDataBroker;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
+import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCapabilities;
+import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.augment.test.rev160808.Node1;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyBuilder;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+
+// FIXME: base on AbstractDataBrokerTest test?
+public class NetconfDeficeTopologyAdapterIntegrationTest {
+    private static final RemoteDeviceId ID = new RemoteDeviceId("test", new InetSocketAddress("localhost", 22));
+
+    private static BindingRuntimeContext RUNTIME_CONTEXT;
+
+    private DataBroker dataBroker;
+    private DOMDataBroker domDataBroker;
+
+    private NetconfDeviceTopologyAdapter adapter;
+
+    @BeforeClass
+    public static void beforeClass() {
+        RUNTIME_CONTEXT = BindingRuntimeHelpers.createRuntimeContext(NetconfNode.class, Node1.class);
+    }
+
+    @AfterClass
+    public static void afterClass() {
+        RUNTIME_CONTEXT = null;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        final var customizer = new ConcurrentDataBrokerTestCustomizer(true);
+        domDataBroker = customizer.getDOMDataBroker();
+        dataBroker = customizer.createDataBroker();
+        customizer.updateSchema(RUNTIME_CONTEXT);
+
+        final var tx = dataBroker.newWriteOnlyTransaction();
+        tx.put(LogicalDatastoreType.OPERATIONAL, RemoteDeviceId.DEFAULT_TOPOLOGY_IID, new TopologyBuilder()
+            .withKey(RemoteDeviceId.DEFAULT_TOPOLOGY_IID.getKey())
+            .build());
+        tx.commit().get(2, TimeUnit.SECONDS);
+
+        adapter = new NetconfDeviceTopologyAdapter(dataBroker, ID);
+    }
+
+    @Test
+    public void testFailedDeviceIntegration() {
+        adapter.setDeviceAsFailed(null);
+
+        Awaitility.await().atMost(5, TimeUnit.SECONDS).until(() -> dataBroker.newReadWriteTransaction()
+            .read(LogicalDatastoreType.OPERATIONAL, ID.getTopologyBindingPath().augmentation(NetconfNode.class))
+            .get(5, TimeUnit.SECONDS)
+            .map(NetconfNodeConnectionStatus::getConnectionStatus)
+            .filter(status -> status == NetconfNodeConnectionStatus.ConnectionStatus.UnableToConnect)
+            .isPresent());
+    }
+
+    @Test
+    public void testDeviceAugmentedNodePresence() throws Exception {
+        QName netconfTestLeafQname = QName.create(
+                "urn:TBD:params:xml:ns:yang:network-topology-augment-test", "2016-08-08", "test-id").intern();
+
+        YangInstanceIdentifier pathToAugmentedLeaf = YangInstanceIdentifier.builder().node(NetworkTopology.QNAME)
+                .node(Topology.QNAME)
+                .nodeWithKey(Topology.QNAME, QName.create(Topology.QNAME, "topology-id"), "topology-netconf")
+                .node(Node.QNAME)
+                .nodeWithKey(Node.QNAME, QName.create(Node.QNAME, "node-id"), "test")
+                .node(netconfTestLeafQname).build();
+
+        final Integer dataTestId = 474747;
+        final var augmentNode = ImmutableNodes.leafNode(netconfTestLeafQname, dataTestId);
+
+        DOMDataTreeWriteTransaction wtx =  domDataBroker.newWriteOnlyTransaction();
+        wtx.put(LogicalDatastoreType.OPERATIONAL, pathToAugmentedLeaf, augmentNode);
+        wtx.commit().get(5, TimeUnit.SECONDS);
+
+        adapter.updateDeviceData(true, new NetconfDeviceCapabilities());
+
+        assertEquals(Optional.of(dataTestId), domDataBroker.newReadOnlyTransaction()
+            .read(LogicalDatastoreType.OPERATIONAL, pathToAugmentedLeaf)
+            .get(2, TimeUnit.SECONDS)
+            .map(NormalizedNode::body));
+
+        adapter.setDeviceAsFailed(null);
+
+        assertEquals(Optional.of(dataTestId), domDataBroker.newReadOnlyTransaction()
+            .read(LogicalDatastoreType.OPERATIONAL, pathToAugmentedLeaf)
+            .get(2, TimeUnit.SECONDS)
+            .map(NormalizedNode::body));
+    }
+}
index 0d3cfbedb84d85a0cde60519c3c6bef7adf16a46..d8cda9fab0f53e2c00f2db82d674ff48a42400a5 100644 (file)
@@ -8,6 +8,7 @@
 
 package org.opendaylight.netconf.sal.connect.netconf.sal;
 
+import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
@@ -18,49 +19,63 @@ import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import java.net.InetSocketAddress;
-import java.util.Arrays;
 import java.util.List;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.binding.api.TransactionChain;
+import org.opendaylight.mdsal.binding.api.WriteTransaction;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
 import org.opendaylight.mdsal.dom.api.DOMNotification;
 import org.opendaylight.mdsal.dom.api.DOMRpcService;
 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
-import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCapabilities;
 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
 import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus.ConnectionStatus;
 import org.opendaylight.yangtools.rfc8528.data.util.EmptyMountPointContext;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 
 @RunWith(MockitoJUnitRunner.StrictStubs.class)
 public class NetconfDeviceSalFacadeTest {
+    private final RemoteDeviceId remoteDeviceId = new RemoteDeviceId("test", new InetSocketAddress("127.0.0.1", 8000));
 
-    private NetconfDeviceSalFacade deviceFacade;
-
-    @Mock
-    private NetconfDeviceTopologyAdapter netconfDeviceTopologyAdapter;
     @Mock
     private NetconfDeviceSalProvider.MountInstance mountInstance;
     @Mock
     private NetconfDeviceSalProvider salProvider;
     @Mock
     private DataBroker dataBroker;
+    @Mock
+    private TransactionChain txChain;
+    @Mock
+    private WriteTransaction tx;
+    @Captor
+    private ArgumentCaptor<NetconfNode> nodeCaptor;
+
+    private NetconfDeviceSalFacade deviceFacade;
 
     @Before
     public void setUp() throws Exception {
-        final InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8000);
-        final RemoteDeviceId remoteDeviceId = new RemoteDeviceId("test", address);
+        doReturn(txChain).when(dataBroker).createMergingTransactionChain(any());
+        doReturn(tx).when(txChain).newWriteOnlyTransaction();
+        doNothing().when(tx).mergeParentStructurePut(eq(LogicalDatastoreType.OPERATIONAL),
+            eq(remoteDeviceId.getTopologyBindingPath().augmentation(NetconfNode.class)), nodeCaptor.capture());
+        doReturn(CommitInfo.emptyFluentFuture()).when(tx).commit();
+
+        final NetconfDeviceTopologyAdapter adapter = new NetconfDeviceTopologyAdapter(dataBroker, remoteDeviceId);
 
         deviceFacade = new NetconfDeviceSalFacade(remoteDeviceId, salProvider, dataBroker, "mockTopo");
 
-        doReturn(netconfDeviceTopologyAdapter).when(salProvider).getTopologyDatastoreAdapter();
-        doNothing().when(netconfDeviceTopologyAdapter)
-                .updateDeviceData(any(Boolean.class), any(NetconfDeviceCapabilities.class));
+        doReturn(adapter).when(salProvider).getTopologyDatastoreAdapter();
 
         doReturn(mountInstance).when(salProvider).getMountInstance();
         doNothing().when(mountInstance).onTopologyDeviceDisconnected();
@@ -70,9 +85,8 @@ public class NetconfDeviceSalFacadeTest {
     public void testOnDeviceDisconnected() {
         deviceFacade.onDeviceDisconnected();
 
-        verify(netconfDeviceTopologyAdapter).updateDeviceData(eq(false), any(NetconfDeviceCapabilities.class));
+        verifyConnectionStatusUpdate(ConnectionStatus.Connecting);
         verify(mountInstance, times(1)).onTopologyDeviceDisconnected();
-
     }
 
     @Test
@@ -80,7 +94,7 @@ public class NetconfDeviceSalFacadeTest {
         final Throwable throwable = new Throwable();
         deviceFacade.onDeviceFailed(throwable);
 
-        verify(netconfDeviceTopologyAdapter).setDeviceAsFailed(throwable);
+        verifyConnectionStatusUpdate(ConnectionStatus.UnableToConnect);
         verify(mountInstance, times(1)).onTopologyDeviceDisconnected();
     }
 
@@ -94,18 +108,16 @@ public class NetconfDeviceSalFacadeTest {
     public void testOnDeviceConnected() {
         final EffectiveModelContext schemaContext = mock(EffectiveModelContext.class);
 
-        final NetconfSessionPreferences netconfSessionPreferences =
-                NetconfSessionPreferences.fromStrings(getCapabilities());
+        final var netconfSessionPreferences = NetconfSessionPreferences.fromStrings(
+            List.of(NetconfMessageTransformUtil.NETCONF_CANDIDATE_URI.toString()));
 
         final DOMRpcService deviceRpc = mock(DOMRpcService.class);
         deviceFacade.onDeviceConnected(new EmptyMountPointContext(schemaContext), netconfSessionPreferences, deviceRpc,
             null);
-
+        verifyConnectionStatusUpdate(ConnectionStatus.Connected);
         verify(mountInstance, times(1)).onTopologyDeviceConnected(eq(schemaContext),
                 any(DOMDataBroker.class), any(NetconfDataTreeService.class), eq(deviceRpc),
                 any(NetconfDeviceNotificationService.class), isNull());
-        verify(netconfDeviceTopologyAdapter,
-                times(1)).updateDeviceData(true, netconfSessionPreferences.getNetconfDeviceCapabilities());
     }
 
     @Test
@@ -115,7 +127,9 @@ public class NetconfDeviceSalFacadeTest {
         verify(mountInstance).publish(domNotification);
     }
 
-    private static List<String> getCapabilities() {
-        return Arrays.asList(NetconfMessageTransformUtil.NETCONF_CANDIDATE_URI.toString());
+    private void verifyConnectionStatusUpdate(final ConnectionStatus expectedStatus) {
+        verify(tx).mergeParentStructurePut(eq(LogicalDatastoreType.OPERATIONAL),
+            eq(remoteDeviceId.getTopologyBindingPath().augmentation(NetconfNode.class)), any());
+        assertEquals(expectedStatus, nodeCaptor.getValue().getConnectionStatus());
     }
 }
index 0a3206ab4d6ada3acd55a4cd6f9e73add4cb1d44..8b1e7cc104c34a8aaf099ac0c905e3c41edd2088 100644 (file)
@@ -7,30 +7,27 @@
  */
 package org.opendaylight.netconf.sal.connect.netconf.sal;
 
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.opendaylight.mdsal.common.api.CommitInfo.emptyFluentFuture;
 
 import java.net.InetSocketAddress;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 import org.opendaylight.mdsal.binding.api.DataBroker;
 import org.opendaylight.mdsal.binding.api.TransactionChain;
 import org.opendaylight.mdsal.binding.api.TransactionChainListener;
 import org.opendaylight.mdsal.binding.api.WriteTransaction;
+import org.opendaylight.mdsal.common.api.CommitInfo;
 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
 
 @RunWith(MockitoJUnitRunner.StrictStubs.class)
 public class NetconfDeviceSalProviderTest {
-
     @Mock
     private WriteTransaction tx;
     @Mock
@@ -41,44 +38,36 @@ public class NetconfDeviceSalProviderTest {
     private DOMMountPointService mountPointService;
     @Mock
     private WriteTransaction writeTx;
+    @Captor
+    private ArgumentCaptor<TransactionChainListener> listeners;
 
     private NetconfDeviceSalProvider provider;
 
     @Before
-    public void setUp() throws Exception {
-        doReturn(chain).when(dataBroker).createTransactionChain(any(TransactionChainListener.class));
+    public void setUp() {
+        doReturn(chain).when(dataBroker).createMergingTransactionChain(listeners.capture());
         doReturn(writeTx).when(chain).newWriteOnlyTransaction();
         doReturn("Some object").when(writeTx).getIdentifier();
-        doReturn(emptyFluentFuture()).when(writeTx).commit();
+        doReturn(CommitInfo.emptyFluentFuture()).when(writeTx).commit();
         provider = new NetconfDeviceSalProvider(new RemoteDeviceId("device1",
                 InetSocketAddress.createUnresolved("localhost", 17830)), mountPointService, dataBroker);
-        when(chain.newWriteOnlyTransaction()).thenReturn(tx);
-        doReturn(emptyFluentFuture()).when(tx).commit();
-        when(tx.getIdentifier()).thenReturn(tx);
-    }
-
-    @Test
-    public void replaceChainIfFailed() throws Exception {
-        final ArgumentCaptor<TransactionChainListener> captor = ArgumentCaptor.forClass(TransactionChainListener.class);
-        verify(dataBroker).createTransactionChain(captor.capture());
-        try {
-            captor.getValue().onTransactionChainFailed(chain, tx, new Exception("chain failed"));
-        } catch (final IllegalStateException e) {
-            //expected
-        }
-        verify(dataBroker, times(2)).createTransactionChain(any());
+        doReturn(tx).when(chain).newWriteOnlyTransaction();
+        doReturn(CommitInfo.emptyFluentFuture()).when(tx).commit();
+        doReturn(tx).when(tx).getIdentifier();
     }
 
     @Test
-    public void close() throws Exception {
+    public void close() {
+        listeners.getValue().onTransactionChainSuccessful(chain);
         provider.close();
         verify(chain).close();
     }
 
     @Test
-    public void closeWithoutNPE() throws Exception {
-        provider.close();
+    public void closeWithoutNPE()  {
+        close();
+
+        // No further interations
         provider.close();
-        verify(chain, times(2)).close();
     }
 }
index f22cd14c2652fd516450ee17e7ad219cdfd60fea..5fed5f6703ff8dc532c073c9671e1bcfc63a32cd 100644 (file)
  */
 package org.opendaylight.netconf.sal.connect.netconf.sal;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.opendaylight.mdsal.common.api.CommitInfo.emptyFluentFuture;
 
 import java.net.InetSocketAddress;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
-import org.awaitility.Awaitility;
-import org.junit.AfterClass;
 import org.junit.Before;
-import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 import org.opendaylight.mdsal.binding.api.DataBroker;
-import org.opendaylight.mdsal.binding.api.Transaction;
 import org.opendaylight.mdsal.binding.api.TransactionChain;
 import org.opendaylight.mdsal.binding.api.TransactionChainListener;
 import org.opendaylight.mdsal.binding.api.WriteTransaction;
-import org.opendaylight.mdsal.binding.dom.adapter.test.ConcurrentDataBrokerTestCustomizer;
-import org.opendaylight.mdsal.binding.runtime.api.BindingRuntimeContext;
-import org.opendaylight.mdsal.binding.runtime.spi.BindingRuntimeHelpers;
+import org.opendaylight.mdsal.common.api.CommitInfo;
 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
-import org.opendaylight.mdsal.dom.api.DOMDataBroker;
-import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCapabilities;
 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus.ConnectionStatus;
-import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.augment.test.rev160808.Node1;
-import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
-import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
-import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyBuilder;
 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
 
 @RunWith(MockitoJUnitRunner.StrictStubs.class)
 public class NetconfDeviceTopologyAdapterTest {
-    private static BindingRuntimeContext RUNTIME_CONTEXT;
-
     private final RemoteDeviceId id = new RemoteDeviceId("test", new InetSocketAddress("localhost", 22));
 
     @Mock
-    private WriteTransaction writeTx;
+    private DataBroker mockBroker;
     @Mock
-    private TransactionChain txChain;
-
-    private final String txIdent = "test transaction";
-
-    private TransactionChain transactionChain;
-
-    private DataBroker dataBroker;
-
-    private DOMDataBroker domDataBroker;
+    private TransactionChain mockChain;
+    @Mock
+    private WriteTransaction mockTx;
+    @Captor
+    private ArgumentCaptor<TransactionChainListener> listeners;
 
-    @BeforeClass
-    public static void beforeClass() {
-        RUNTIME_CONTEXT = BindingRuntimeHelpers.createRuntimeContext(NetconfNode.class, Node1.class);
-    }
-
-    @AfterClass
-    public static void afterClass() {
-        RUNTIME_CONTEXT = null;
-    }
+    private NetconfDeviceTopologyAdapter adapter;
 
     @Before
-    public void setUp() throws Exception {
-        doReturn(writeTx).when(txChain).newWriteOnlyTransaction();
-        doNothing().when(writeTx).put(any(LogicalDatastoreType.class), any(InstanceIdentifier.class), any(Node.class));
-        doReturn(txIdent).when(writeTx).getIdentifier();
-
-        ConcurrentDataBrokerTestCustomizer customizer = new ConcurrentDataBrokerTestCustomizer(true);
-        domDataBroker = customizer.getDOMDataBroker();
-        dataBroker = customizer.createDataBroker();
-        customizer.updateSchema(RUNTIME_CONTEXT);
-
-        transactionChain = dataBroker.createTransactionChain(new TransactionChainListener() {
-            @Override
-            public void onTransactionChainFailed(final TransactionChain chain, final Transaction transaction,
-                    final Throwable cause) {
-
-            }
-
-            @Override
-            public void onTransactionChainSuccessful(final TransactionChain chain) {
-
-            }
-        });
-
-        final var wtx = transactionChain.newWriteOnlyTransaction();
-        wtx.put(LogicalDatastoreType.OPERATIONAL, RemoteDeviceId.DEFAULT_TOPOLOGY_IID, new TopologyBuilder()
-            .withKey(RemoteDeviceId.DEFAULT_TOPOLOGY_IID.getKey())
-            .build());
-        wtx.commit();
+    public void before() {
+        doReturn(mockTx).when(mockChain).newWriteOnlyTransaction();
+        // FIXME: exact match
+        doNothing().when(mockTx).put(any(LogicalDatastoreType.class), any(InstanceIdentifier.class), any(Node.class));
+        doReturn("test transaction").when(mockTx).getIdentifier();
+        doReturn(CommitInfo.emptyFluentFuture()).when(mockTx).commit();
+
+        doReturn(mockChain).when(mockBroker).createMergingTransactionChain(listeners.capture());
+        adapter = new NetconfDeviceTopologyAdapter(mockBroker, id);
     }
 
     @Test
-    public void testFailedDevice() throws Exception {
-
-        doReturn(emptyFluentFuture()).when(writeTx).commit();
-        NetconfDeviceTopologyAdapter adapter = new NetconfDeviceTopologyAdapter(id, txChain);
-        adapter.setDeviceAsFailed(null);
-
-        verify(txChain, times(2)).newWriteOnlyTransaction();
-        verify(writeTx, times(1))
-                .put(any(LogicalDatastoreType.class), any(InstanceIdentifier.class), any(Node.class));
-        adapter.close();
-
-        adapter = new NetconfDeviceTopologyAdapter(id, transactionChain); //not a mock
-        adapter.setDeviceAsFailed(null);
-
-        Awaitility.await().atMost(5, TimeUnit.SECONDS).until(() -> {
-            Optional<NetconfNode> netconfNode = dataBroker.newReadWriteTransaction()
-                    .read(LogicalDatastoreType.OPERATIONAL, id.getTopologyBindingPath().augmentation(NetconfNode.class))
-                    .get(5, TimeUnit.SECONDS);
-            return netconfNode.isPresent()
-                && netconfNode.orElseThrow().getConnectionStatus() == ConnectionStatus.UnableToConnect;
-        });
+    public void replaceChainIfFailed() {
+        adapter.onTransactionChainFailed(mockChain, mockTx, new Exception("chain failed"));
+        verify(mockBroker, times(2)).createMergingTransactionChain(any());
     }
 
     @Test
-    public void testDeviceUpdate() throws Exception {
-        doReturn(emptyFluentFuture()).when(writeTx).commit();
-
-        NetconfDeviceTopologyAdapter adapter = new NetconfDeviceTopologyAdapter(id, txChain);
-        adapter.updateDeviceData(true, new NetconfDeviceCapabilities());
-
-        verify(txChain, times(2)).newWriteOnlyTransaction();
-        verify(writeTx, times(1))
-                .put(any(LogicalDatastoreType.class), any(InstanceIdentifier.class), any(Node.class));
-        verify(writeTx, times(1)).put(any(LogicalDatastoreType.class), any(InstanceIdentifier.class), any(Node.class));
+    public void testFailedDevice() {
+        adapter.setDeviceAsFailed(null);
 
+        verify(mockChain, times(2)).newWriteOnlyTransaction();
+        // FIXME: LogicationDataStoreStype, concrete identifier, concreate (or captured/asserted) data
+        verify(mockTx, times(1)).put(any(LogicalDatastoreType.class), any(InstanceIdentifier.class),
+            any(Node.class));
     }
 
     @Test
-    public void testDeviceAugmentedNodePresence() throws Exception {
-
-        Integer dataTestId = 474747;
-
-        NetconfDeviceTopologyAdapter adapter = new NetconfDeviceTopologyAdapter(id, transactionChain);
-
-        QName netconfTestLeafQname = QName.create(
-                "urn:TBD:params:xml:ns:yang:network-topology-augment-test", "2016-08-08", "test-id").intern();
-
-        YangInstanceIdentifier pathToAugmentedLeaf = YangInstanceIdentifier.builder().node(NetworkTopology.QNAME)
-                .node(Topology.QNAME)
-                .nodeWithKey(Topology.QNAME, QName.create(Topology.QNAME, "topology-id"), "topology-netconf")
-                .node(Node.QNAME)
-                .nodeWithKey(Node.QNAME, QName.create(Node.QNAME, "node-id"), "test")
-                .node(netconfTestLeafQname).build();
-
-        LeafNode<Integer> augmentNode = ImmutableNodes.leafNode(netconfTestLeafQname, dataTestId);
-
-        DOMDataTreeWriteTransaction wtx =  domDataBroker.newWriteOnlyTransaction();
-        wtx.put(LogicalDatastoreType.OPERATIONAL, pathToAugmentedLeaf, augmentNode);
-        wtx.commit().get(5, TimeUnit.SECONDS);
-
+    public void testDeviceUpdate() throws Exception {
         adapter.updateDeviceData(true, new NetconfDeviceCapabilities());
-        Optional<NormalizedNode> testNode = domDataBroker.newReadOnlyTransaction()
-                .read(LogicalDatastoreType.OPERATIONAL, pathToAugmentedLeaf).get(2, TimeUnit.SECONDS);
 
-        assertTrue("Augmented node data should be still present after device update.", testNode.isPresent());
-        assertEquals("Augmented data should be the same as before update node.", dataTestId, testNode.get().body());
-
-        adapter.setDeviceAsFailed(null);
-        testNode = domDataBroker.newReadOnlyTransaction()
-                .read(LogicalDatastoreType.OPERATIONAL, pathToAugmentedLeaf).get(2, TimeUnit.SECONDS);
-
-        assertEquals("Augmented node data should be still present after device failed.", true, testNode.isPresent());
-        assertEquals("Augmented data should be the same as before failed device.",
-                dataTestId, testNode.get().body());
+        verify(mockChain, times(2)).newWriteOnlyTransaction();
+        verify(mockTx, times(1)).put(any(LogicalDatastoreType.class), any(InstanceIdentifier.class), any(Node.class));
+        verify(mockTx, times(1)).put(any(LogicalDatastoreType.class), any(InstanceIdentifier.class), any(Node.class));
     }
 
     @Test
     public void testRemoveDeviceConfiguration() throws Exception {
-        doReturn(emptyFluentFuture()).when(writeTx).commit();
-
-        NetconfDeviceTopologyAdapter adapter = new NetconfDeviceTopologyAdapter(id, txChain);
+        listeners.getValue().onTransactionChainSuccessful(mockChain);
         adapter.close();
 
-        verify(txChain, times(2)).newWriteOnlyTransaction();
-        verify(writeTx).delete(LogicalDatastoreType.OPERATIONAL, id.getTopologyBindingPath());
-        verify(writeTx, times(2)).commit();
+        verify(mockChain, times(2)).newWriteOnlyTransaction();
+        verify(mockTx).delete(LogicalDatastoreType.OPERATIONAL, id.getTopologyBindingPath());
+        verify(mockTx, times(2)).commit();
     }
 }