Use @RunWith(MockitoJUnitRunner.StrictStubs.class)
[netconf.git] / netconf / netconf-topology-singleton / src / test / java / org / opendaylight / netconf / topology / singleton / impl / NetconfNodeManagerTest.java
1 /*
2  * Copyright (c) 2018 Inocybe Technologies and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.netconf.topology.singleton.impl;
9
10 import static java.nio.charset.StandardCharsets.UTF_8;
11 import static org.mockito.ArgumentMatchers.any;
12 import static org.mockito.ArgumentMatchers.eq;
13 import static org.mockito.Mockito.after;
14 import static org.mockito.Mockito.doNothing;
15 import static org.mockito.Mockito.doReturn;
16 import static org.mockito.Mockito.mock;
17 import static org.mockito.Mockito.reset;
18 import static org.mockito.Mockito.timeout;
19 import static org.mockito.Mockito.verify;
20 import static org.mockito.Mockito.verifyNoMoreInteractions;
21 import static org.opendaylight.mdsal.binding.api.DataObjectModification.ModificationType.DELETE;
22 import static org.opendaylight.mdsal.binding.api.DataObjectModification.ModificationType.SUBTREE_MODIFIED;
23 import static org.opendaylight.mdsal.binding.api.DataObjectModification.ModificationType.WRITE;
24
25 import akka.actor.ActorSystem;
26 import akka.actor.Props;
27 import akka.cluster.Cluster;
28 import akka.dispatch.Dispatchers;
29 import akka.testkit.TestActorRef;
30 import akka.testkit.javadsl.TestKit;
31 import akka.util.Timeout;
32 import com.google.common.collect.ImmutableList;
33 import com.google.common.collect.Iterables;
34 import com.google.common.io.ByteSource;
35 import com.google.common.util.concurrent.Futures;
36 import com.typesafe.config.ConfigFactory;
37 import java.net.InetSocketAddress;
38 import java.util.Collections;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.concurrent.CompletableFuture;
42 import java.util.concurrent.ConcurrentHashMap;
43 import java.util.concurrent.ExecutionException;
44 import java.util.concurrent.TimeUnit;
45 import java.util.concurrent.TimeoutException;
46 import java.util.stream.Collectors;
47 import org.junit.After;
48 import org.junit.Before;
49 import org.junit.Test;
50 import org.junit.runner.RunWith;
51 import org.mockito.Mock;
52 import org.mockito.junit.MockitoJUnitRunner;
53 import org.opendaylight.mdsal.binding.api.DataBroker;
54 import org.opendaylight.mdsal.binding.api.DataObjectModification;
55 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
56 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
57 import org.opendaylight.mdsal.dom.api.DOMActionService;
58 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
59 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
60 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
61 import org.opendaylight.mdsal.dom.api.DOMNotificationService;
62 import org.opendaylight.mdsal.dom.api.DOMRpcService;
63 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
64 import org.opendaylight.netconf.sal.connect.api.NetconfDeviceSchemasResolver;
65 import org.opendaylight.netconf.sal.connect.netconf.NetconfDevice;
66 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
67 import org.opendaylight.netconf.topology.singleton.impl.actors.NetconfNodeActor;
68 import org.opendaylight.netconf.topology.singleton.impl.utils.NetconfTopologySetup;
69 import org.opendaylight.netconf.topology.singleton.impl.utils.NetconfTopologyUtils;
70 import org.opendaylight.netconf.topology.singleton.messages.AskForMasterMountPoint;
71 import org.opendaylight.netconf.topology.singleton.messages.CreateInitialMasterActorData;
72 import org.opendaylight.netconf.topology.singleton.messages.MasterActorDataInitialized;
73 import org.opendaylight.netconf.topology.singleton.messages.YangTextSchemaSourceRequest;
74 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Host;
75 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
76 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Address;
77 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber;
78 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
79 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeBuilder;
80 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus;
81 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.ClusteredConnectionStatusBuilder;
82 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
83 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
84 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeBuilder;
85 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey;
86 import org.opendaylight.yangtools.concepts.ListenerRegistration;
87 import org.opendaylight.yangtools.concepts.ObjectRegistration;
88 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
89 import org.opendaylight.yangtools.yang.common.Uint16;
90 import org.opendaylight.yangtools.yang.model.repo.api.EffectiveModelContextFactory;
91 import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
92 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
93 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
94 import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource;
95 import org.opendaylight.yangtools.yang.parser.repo.SharedSchemaRepository;
96 import org.opendaylight.yangtools.yang.parser.rfc7950.repo.TextToIRTransformer;
97
98 /**
99  * Unit tests for NetconfNodeManager.
100  *
101  * @author Thomas Pantelis
102  */
103 @RunWith(MockitoJUnitRunner.StrictStubs.class)
104 public class NetconfNodeManagerTest extends AbstractBaseSchemasTest {
105     private static final String ACTOR_SYSTEM_NAME = "test";
106     private static final RemoteDeviceId DEVICE_ID = new RemoteDeviceId("device", new InetSocketAddress(65535));
107     private static final List<SourceIdentifier> SOURCE_IDENTIFIERS =
108             ImmutableList.of(RevisionSourceIdentifier.create("testID"));
109
110     @Mock
111     private DOMMountPointService mockMountPointService;
112
113     @Mock
114     private DOMMountPointService.DOMMountPointBuilder mockMountPointBuilder;
115
116     @Mock
117     private ObjectRegistration<DOMMountPoint> mockMountPointReg;
118
119     @Mock
120     private DataBroker mockDataBroker;
121
122     @Mock
123     private NetconfDataTreeService netconfService;
124
125     @Mock
126     private DOMDataBroker mockDeviceDataBroker;
127
128     @Mock
129     private DOMRpcService mockRpcService;
130
131     @Mock
132     private DOMActionService mockActionService;
133
134     @Mock
135     private NetconfDeviceSchemasResolver mockSchemasResolver;
136
137     @Mock
138     private EffectiveModelContextFactory mockSchemaContextFactory;
139
140     private ActorSystem slaveSystem;
141     private ActorSystem masterSystem;
142     private TestActorRef<TestMasterActor> testMasterActorRef;
143     private NetconfNodeManager netconfNodeManager;
144     private String masterAddress;
145
146     @Before
147     public void setup() {
148         final Timeout responseTimeout = Timeout.apply(1, TimeUnit.SECONDS);
149
150         slaveSystem = ActorSystem.create(ACTOR_SYSTEM_NAME, ConfigFactory.load().getConfig("Slave"));
151         masterSystem = ActorSystem.create(ACTOR_SYSTEM_NAME, ConfigFactory.load().getConfig("Master"));
152
153         masterAddress = Cluster.get(masterSystem).selfAddress().toString();
154
155         SharedSchemaRepository masterSchemaRepository = new SharedSchemaRepository("master");
156         masterSchemaRepository.registerSchemaSourceListener(
157                 TextToIRTransformer.create(masterSchemaRepository, masterSchemaRepository));
158
159         String yangTemplate =
160                   "module ID {"
161                 + "  namespace \"ID\";"
162                 + "  prefix ID;"
163                 + "}";
164
165         SOURCE_IDENTIFIERS.stream().map(
166             sourceId -> masterSchemaRepository.registerSchemaSource(
167                 id -> Futures.immediateFuture(YangTextSchemaSource.delegateForByteSource(id,
168                         ByteSource.wrap(yangTemplate.replaceAll("ID", id.getName()).getBytes(UTF_8)))),
169                 PotentialSchemaSource.create(sourceId, YangTextSchemaSource.class, 1)))
170         .collect(Collectors.toList());
171
172         NetconfTopologySetup masterSetup = new NetconfTopologySetup.NetconfTopologySetupBuilder()
173                 .setActorSystem(masterSystem).setDataBroker(mockDataBroker).setSchemaResourceDTO(
174                         new NetconfDevice.SchemaResourcesDTO(masterSchemaRepository, masterSchemaRepository,
175                                 mockSchemaContextFactory, mockSchemasResolver)).setBaseSchemas(BASE_SCHEMAS).build();
176
177         testMasterActorRef = TestActorRef.create(masterSystem, Props.create(TestMasterActor.class, masterSetup,
178                 DEVICE_ID, responseTimeout, mockMountPointService).withDispatcher(Dispatchers.DefaultDispatcherId()),
179                 NetconfTopologyUtils.createMasterActorName(DEVICE_ID.getName(), masterAddress));
180
181         SharedSchemaRepository slaveSchemaRepository = new SharedSchemaRepository("slave");
182         slaveSchemaRepository.registerSchemaSourceListener(
183                 TextToIRTransformer.create(slaveSchemaRepository, slaveSchemaRepository));
184
185         NetconfTopologySetup slaveSetup = new NetconfTopologySetup.NetconfTopologySetupBuilder()
186                 .setActorSystem(slaveSystem).setDataBroker(mockDataBroker).setSchemaResourceDTO(
187                         new NetconfDevice.SchemaResourcesDTO(slaveSchemaRepository, slaveSchemaRepository,
188                                 mockSchemaContextFactory, mockSchemasResolver)).setBaseSchemas(BASE_SCHEMAS).build();
189
190         netconfNodeManager = new NetconfNodeManager(slaveSetup, DEVICE_ID, responseTimeout,
191                 mockMountPointService);
192
193         setupMountPointMocks();
194     }
195
196     @After
197     public void teardown() {
198         TestKit.shutdownActorSystem(slaveSystem, true);
199         TestKit.shutdownActorSystem(masterSystem, true);
200     }
201
202     @SuppressWarnings("unchecked")
203     @Test
204     public void testSlaveMountPointRegistration() throws InterruptedException, ExecutionException, TimeoutException {
205         initializeMaster();
206
207         ListenerRegistration<?> mockListenerReg = mock(ListenerRegistration.class);
208         doReturn(mockListenerReg).when(mockDataBroker).registerDataTreeChangeListener(any(), any());
209
210         final NodeId nodeId = new NodeId("device");
211         final NodeKey nodeKey = new NodeKey(nodeId);
212         final String topologyId = "topology-netconf";
213         final InstanceIdentifier<Node> nodeListPath = NetconfTopologyUtils.createTopologyNodeListPath(
214                 nodeKey, topologyId);
215
216         netconfNodeManager.registerDataTreeChangeListener(topologyId, nodeKey);
217         verify(mockDataBroker).registerDataTreeChangeListener(any(), eq(netconfNodeManager));
218
219         // Invoke onDataTreeChanged with a NetconfNode WRITE to simulate the master writing the operational state to
220         // Connected. Expect the slave mount point created and registered.
221
222         final NetconfNode netconfNode = newNetconfNode();
223         final Node node = new NodeBuilder().setNodeId(nodeId).addAugmentation(netconfNode).build();
224
225         DataObjectModification<Node> mockDataObjModification = mock(DataObjectModification.class);
226         doReturn(Iterables.getLast(nodeListPath.getPathArguments())).when(mockDataObjModification).getIdentifier();
227         doReturn(WRITE).when(mockDataObjModification).getModificationType();
228         doReturn(node).when(mockDataObjModification).getDataAfter();
229
230         netconfNodeManager.onDataTreeChanged(Collections.singletonList(
231                 new NetconfTopologyManagerTest.CustomTreeModification(DataTreeIdentifier.create(
232                         LogicalDatastoreType.OPERATIONAL, nodeListPath), mockDataObjModification)));
233
234         verify(mockMountPointBuilder, timeout(5000)).register();
235         verify(mockMountPointBuilder).addService(eq(DOMDataBroker.class), any());
236         verify(mockMountPointBuilder).addService(eq(DOMRpcService.class), any());
237         verify(mockMountPointBuilder).addService(eq(DOMNotificationService.class), any());
238         verify(mockMountPointService).createMountPoint(DEVICE_ID.getTopologyPath());
239
240         // Notify that the NetconfNode operational state was deleted. Expect the slave mount point closed.
241
242         doReturn(DELETE).when(mockDataObjModification).getModificationType();
243
244         netconfNodeManager.onDataTreeChanged(Collections.singletonList(
245                 new NetconfTopologyManagerTest.CustomTreeModification(DataTreeIdentifier.create(
246                         LogicalDatastoreType.OPERATIONAL, nodeListPath), mockDataObjModification)));
247
248         verify(mockMountPointReg, timeout(5000)).close();
249
250         // Notify with a NetconfNode operational state WRITE. Expect the slave mount point re-created.
251
252         setupMountPointMocks();
253
254         doReturn(WRITE).when(mockDataObjModification).getModificationType();
255         doReturn(null).when(mockDataObjModification).getDataBefore();
256         doReturn(node).when(mockDataObjModification).getDataAfter();
257
258         netconfNodeManager.onDataTreeChanged(Collections.singletonList(
259                 new NetconfTopologyManagerTest.CustomTreeModification(DataTreeIdentifier.create(
260                         LogicalDatastoreType.OPERATIONAL, nodeListPath), mockDataObjModification)));
261
262         verify(mockMountPointBuilder, timeout(5000)).register();
263
264         // Notify again with a NetconfNode operational state WRITE. Expect the prior slave mount point closed and
265         // and a new one registered.
266
267         setupMountPointMocks();
268
269         doReturn(node).when(mockDataObjModification).getDataBefore();
270
271         netconfNodeManager.onDataTreeChanged(Collections.singletonList(
272                 new NetconfTopologyManagerTest.CustomTreeModification(DataTreeIdentifier.create(
273                         LogicalDatastoreType.OPERATIONAL, nodeListPath), mockDataObjModification)));
274
275         verify(mockMountPointReg, timeout(5000)).close();
276         verify(mockMountPointBuilder, timeout(5000)).register();
277
278         // Notify that the NetconfNode operational state was changed to UnableToConnect. Expect the slave mount point
279         // closed.
280
281         reset(mockMountPointService, mockMountPointBuilder, mockMountPointReg);
282         doNothing().when(mockMountPointReg).close();
283
284         final Node updatedNode = new NodeBuilder().setNodeId(nodeId)
285                 .addAugmentation(new NetconfNodeBuilder(netconfNode)
286                     .setConnectionStatus(NetconfNodeConnectionStatus.ConnectionStatus.UnableToConnect)
287                     .build())
288                 .build();
289
290         doReturn(SUBTREE_MODIFIED).when(mockDataObjModification).getModificationType();
291         doReturn(node).when(mockDataObjModification).getDataBefore();
292         doReturn(updatedNode).when(mockDataObjModification).getDataAfter();
293
294         netconfNodeManager.onDataTreeChanged(Collections.singletonList(
295                 new NetconfTopologyManagerTest.CustomTreeModification(DataTreeIdentifier.create(
296                         LogicalDatastoreType.OPERATIONAL, nodeListPath), mockDataObjModification)));
297
298         verify(mockMountPointReg, timeout(5000)).close();
299
300         netconfNodeManager.close();
301         verifyNoMoreInteractions(mockMountPointReg);
302     }
303
304     @SuppressWarnings("unchecked")
305     @Test
306     public void testSlaveMountPointRegistrationFailuresAndRetries()
307             throws InterruptedException, ExecutionException, TimeoutException {
308         final NodeId nodeId = new NodeId("device");
309         final NodeKey nodeKey = new NodeKey(nodeId);
310         final String topologyId = "topology-netconf";
311         final InstanceIdentifier<Node> nodeListPath = NetconfTopologyUtils.createTopologyNodeListPath(
312                 nodeKey, topologyId);
313
314         final NetconfNode netconfNode = newNetconfNode();
315         final Node node = new NodeBuilder().setNodeId(nodeId).addAugmentation(netconfNode).build();
316
317         DataObjectModification<Node> mockDataObjModification = mock(DataObjectModification.class);
318         doReturn(Iterables.getLast(nodeListPath.getPathArguments())).when(mockDataObjModification).getIdentifier();
319         doReturn(WRITE).when(mockDataObjModification).getModificationType();
320         doReturn(node).when(mockDataObjModification).getDataAfter();
321
322         // First try the registration where the perceived master hasn't been initialized as the master.
323
324         netconfNodeManager.onDataTreeChanged(Collections.singletonList(
325                 new NetconfTopologyManagerTest.CustomTreeModification(DataTreeIdentifier.create(
326                         LogicalDatastoreType.OPERATIONAL, nodeListPath), mockDataObjModification)));
327
328         verify(mockMountPointBuilder, after(1000).never()).register();
329
330         // Initialize the master but drop the initial YangTextSchemaSourceRequest message sent to the master so
331         // it retries.
332
333         initializeMaster();
334
335         CompletableFuture<AskForMasterMountPoint> yangTextSchemaSourceRequestFuture = new CompletableFuture<>();
336         testMasterActorRef.underlyingActor().messagesToDrop.put(YangTextSchemaSourceRequest.class,
337                 yangTextSchemaSourceRequestFuture);
338
339         netconfNodeManager.onDataTreeChanged(Collections.singletonList(
340                 new NetconfTopologyManagerTest.CustomTreeModification(DataTreeIdentifier.create(
341                         LogicalDatastoreType.OPERATIONAL, nodeListPath), mockDataObjModification)));
342
343         yangTextSchemaSourceRequestFuture.get(5, TimeUnit.SECONDS);
344         verify(mockMountPointBuilder, timeout(5000)).register();
345
346         // Initiate another registration but drop the initial AskForMasterMountPoint message sent to the master so
347         // it retries.
348
349         setupMountPointMocks();
350
351         CompletableFuture<AskForMasterMountPoint> askForMasterMountPointFuture = new CompletableFuture<>();
352         testMasterActorRef.underlyingActor().messagesToDrop.put(AskForMasterMountPoint.class,
353                 askForMasterMountPointFuture);
354
355         netconfNodeManager.onDataTreeChanged(Collections.singletonList(
356                 new NetconfTopologyManagerTest.CustomTreeModification(DataTreeIdentifier.create(
357                         LogicalDatastoreType.OPERATIONAL, nodeListPath), mockDataObjModification)));
358
359         askForMasterMountPointFuture.get(5, TimeUnit.SECONDS);
360         verify(mockMountPointReg, timeout(5000)).close();
361         verify(mockMountPointBuilder, timeout(5000)).register();
362
363         reset(mockMountPointService, mockMountPointBuilder, mockMountPointReg);
364         doNothing().when(mockMountPointReg).close();
365         netconfNodeManager.close();
366         verify(mockMountPointReg, timeout(5000)).close();
367     }
368
369     private NetconfNode newNetconfNode() {
370         return new NetconfNodeBuilder()
371                 .setHost(new Host(new IpAddress(new Ipv4Address("127.0.0.1"))))
372                 .setPort(new PortNumber(Uint16.valueOf(9999)))
373                 .setConnectionStatus(NetconfNodeConnectionStatus.ConnectionStatus.Connected)
374                 .setClusteredConnectionStatus(new ClusteredConnectionStatusBuilder()
375                         .setNetconfMasterNode(masterAddress).build())
376                 .build();
377     }
378
379     private void setupMountPointMocks() {
380         reset(mockMountPointService, mockMountPointBuilder, mockMountPointReg);
381         doNothing().when(mockMountPointReg).close();
382         doReturn(mockMountPointReg).when(mockMountPointBuilder).register();
383         doReturn(mockMountPointBuilder).when(mockMountPointService).createMountPoint(any());
384     }
385
386     private void initializeMaster() {
387         TestKit kit = new TestKit(masterSystem);
388         testMasterActorRef.tell(new CreateInitialMasterActorData(mockDeviceDataBroker, netconfService,
389             SOURCE_IDENTIFIERS,
390                 mockRpcService, mockActionService), kit.getRef());
391
392         kit.expectMsgClass(MasterActorDataInitialized.class);
393     }
394
395     private static class TestMasterActor extends NetconfNodeActor {
396         final Map<Class<?>, CompletableFuture<? extends Object>> messagesToDrop = new ConcurrentHashMap<>();
397
398         TestMasterActor(final NetconfTopologySetup setup, final RemoteDeviceId deviceId,
399                 final Timeout actorResponseWaitTime, final DOMMountPointService mountPointService) {
400             super(setup, deviceId, actorResponseWaitTime, mountPointService);
401         }
402
403         @SuppressWarnings({ "rawtypes", "unchecked" })
404         @Override
405         public void handleReceive(final Object message) {
406             CompletableFuture dropFuture = messagesToDrop.remove(message.getClass());
407             if (dropFuture != null) {
408                 dropFuture.complete(message);
409             } else {
410                 super.handleReceive(message);
411             }
412         }
413     }
414 }