2 * Copyright (c) 2018 Inocybe Technologies and others. All rights reserved.
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
8 package org.opendaylight.netconf.topology.singleton.impl;
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.mockito.MockitoAnnotations.initMocks;
22 import static org.opendaylight.mdsal.binding.api.DataObjectModification.ModificationType.DELETE;
23 import static org.opendaylight.mdsal.binding.api.DataObjectModification.ModificationType.SUBTREE_MODIFIED;
24 import static org.opendaylight.mdsal.binding.api.DataObjectModification.ModificationType.WRITE;
26 import akka.actor.ActorSystem;
27 import akka.actor.Props;
28 import akka.cluster.Cluster;
29 import akka.dispatch.Dispatchers;
30 import akka.testkit.TestActorRef;
31 import akka.testkit.javadsl.TestKit;
32 import akka.util.Timeout;
33 import com.google.common.collect.ImmutableList;
34 import com.google.common.collect.Iterables;
35 import com.google.common.io.ByteSource;
36 import com.google.common.util.concurrent.Futures;
37 import com.typesafe.config.ConfigFactory;
38 import java.net.InetSocketAddress;
39 import java.util.Collections;
40 import java.util.List;
42 import java.util.concurrent.CompletableFuture;
43 import java.util.concurrent.ConcurrentHashMap;
44 import java.util.concurrent.ExecutionException;
45 import java.util.concurrent.TimeUnit;
46 import java.util.concurrent.TimeoutException;
47 import java.util.stream.Collectors;
48 import org.junit.After;
49 import org.junit.Before;
50 import org.junit.Test;
51 import org.mockito.Mock;
52 import org.opendaylight.mdsal.binding.api.DataBroker;
53 import org.opendaylight.mdsal.binding.api.DataObjectModification;
54 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
55 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
56 import org.opendaylight.mdsal.dom.api.DOMActionService;
57 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
58 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
59 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
60 import org.opendaylight.mdsal.dom.api.DOMNotificationService;
61 import org.opendaylight.mdsal.dom.api.DOMRpcService;
62 import org.opendaylight.netconf.sal.connect.api.NetconfDeviceSchemasResolver;
63 import org.opendaylight.netconf.sal.connect.netconf.NetconfDevice;
64 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
65 import org.opendaylight.netconf.topology.singleton.impl.actors.NetconfNodeActor;
66 import org.opendaylight.netconf.topology.singleton.impl.utils.NetconfTopologySetup;
67 import org.opendaylight.netconf.topology.singleton.impl.utils.NetconfTopologyUtils;
68 import org.opendaylight.netconf.topology.singleton.messages.AskForMasterMountPoint;
69 import org.opendaylight.netconf.topology.singleton.messages.CreateInitialMasterActorData;
70 import org.opendaylight.netconf.topology.singleton.messages.MasterActorDataInitialized;
71 import org.opendaylight.netconf.topology.singleton.messages.YangTextSchemaSourceRequest;
72 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Host;
73 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
74 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Address;
75 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber;
76 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
77 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeBuilder;
78 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus;
79 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.ClusteredConnectionStatusBuilder;
80 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
81 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
82 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeBuilder;
83 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey;
84 import org.opendaylight.yangtools.concepts.ListenerRegistration;
85 import org.opendaylight.yangtools.concepts.ObjectRegistration;
86 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
87 import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
88 import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactory;
89 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
90 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
91 import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource;
92 import org.opendaylight.yangtools.yang.parser.repo.SharedSchemaRepository;
93 import org.opendaylight.yangtools.yang.parser.rfc7950.repo.TextToASTTransformer;
96 * Unit tests for NetconfNodeManager.
98 * @author Thomas Pantelis
100 public class NetconfNodeManagerTest {
101 private static final String ACTOR_SYSTEM_NAME = "test";
102 private static final RemoteDeviceId DEVICE_ID = new RemoteDeviceId("device", new InetSocketAddress(65535));
103 private static final List<SourceIdentifier> SOURCE_IDENTIFIERS =
104 ImmutableList.of(RevisionSourceIdentifier.create("testID"));
107 private DOMMountPointService mockMountPointService;
110 private DOMMountPointService.DOMMountPointBuilder mockMountPointBuilder;
113 private ObjectRegistration<DOMMountPoint> mockMountPointReg;
116 private DataBroker mockDataBroker;
119 private DOMDataBroker mockDeviceDataBroker;
122 private DOMRpcService mockRpcService;
125 private DOMActionService mockActionService;
128 private NetconfDeviceSchemasResolver mockSchemasResolver;
131 private SchemaContextFactory mockSchemaContextFactory;
133 private ActorSystem slaveSystem;
134 private ActorSystem masterSystem;
135 private TestActorRef<TestMasterActor> testMasterActorRef;
136 private NetconfNodeManager netconfNodeManager;
137 private String masterAddress;
140 public void setup() {
143 final Timeout responseTimeout = Timeout.apply(1, TimeUnit.SECONDS);
145 slaveSystem = ActorSystem.create(ACTOR_SYSTEM_NAME, ConfigFactory.load().getConfig("Slave"));
146 masterSystem = ActorSystem.create(ACTOR_SYSTEM_NAME, ConfigFactory.load().getConfig("Master"));
148 masterAddress = Cluster.get(masterSystem).selfAddress().toString();
150 SharedSchemaRepository masterSchemaRepository = new SharedSchemaRepository("master");
151 masterSchemaRepository.registerSchemaSourceListener(
152 TextToASTTransformer.create(masterSchemaRepository, masterSchemaRepository));
154 String yangTemplate =
156 + " namespace \"ID\";"
160 SOURCE_IDENTIFIERS.stream().map(
161 sourceId -> masterSchemaRepository.registerSchemaSource(
162 id -> Futures.immediateFuture(YangTextSchemaSource.delegateForByteSource(id,
163 ByteSource.wrap(yangTemplate.replaceAll("ID", id.getName()).getBytes(UTF_8)))),
164 PotentialSchemaSource.create(sourceId, YangTextSchemaSource.class, 1)))
165 .collect(Collectors.toList());
167 NetconfTopologySetup masterSetup = new NetconfTopologySetup.NetconfTopologySetupBuilder()
168 .setActorSystem(masterSystem).setDataBroker(mockDataBroker).setSchemaResourceDTO(
169 new NetconfDevice.SchemaResourcesDTO(masterSchemaRepository, masterSchemaRepository,
170 mockSchemaContextFactory, mockSchemasResolver)).build();
172 testMasterActorRef = TestActorRef.create(masterSystem, Props.create(TestMasterActor.class, masterSetup,
173 DEVICE_ID, responseTimeout, mockMountPointService).withDispatcher(Dispatchers.DefaultDispatcherId()),
174 NetconfTopologyUtils.createMasterActorName(DEVICE_ID.getName(), masterAddress));
176 SharedSchemaRepository slaveSchemaRepository = new SharedSchemaRepository("slave");
177 slaveSchemaRepository.registerSchemaSourceListener(
178 TextToASTTransformer.create(slaveSchemaRepository, slaveSchemaRepository));
180 NetconfTopologySetup slaveSetup = new NetconfTopologySetup.NetconfTopologySetupBuilder()
181 .setActorSystem(slaveSystem).setDataBroker(mockDataBroker).setSchemaResourceDTO(
182 new NetconfDevice.SchemaResourcesDTO(slaveSchemaRepository, slaveSchemaRepository,
183 mockSchemaContextFactory, mockSchemasResolver)).build();
185 netconfNodeManager = new NetconfNodeManager(slaveSetup, DEVICE_ID, responseTimeout,
186 mockMountPointService);
188 setupMountPointMocks();
192 public void teardown() {
193 TestKit.shutdownActorSystem(slaveSystem, true);
194 TestKit.shutdownActorSystem(masterSystem, true);
197 @SuppressWarnings("unchecked")
199 public void testSlaveMountPointRegistration() throws InterruptedException, ExecutionException, TimeoutException {
202 ListenerRegistration<?> mockListenerReg = mock(ListenerRegistration.class);
203 doReturn(mockListenerReg).when(mockDataBroker).registerDataTreeChangeListener(any(), any());
205 final NodeId nodeId = new NodeId("device");
206 final NodeKey nodeKey = new NodeKey(nodeId);
207 final String topologyId = "topology-netconf";
208 final InstanceIdentifier<Node> nodeListPath = NetconfTopologyUtils.createTopologyNodeListPath(
209 nodeKey, topologyId);
211 netconfNodeManager.registerDataTreeChangeListener(topologyId, nodeKey);
212 verify(mockDataBroker).registerDataTreeChangeListener(any(), eq(netconfNodeManager));
214 // Invoke onDataTreeChanged with a NetconfNode WRITE to simulate the master writing the operational state to
215 // Connected. Expect the slave mount point created and registered.
217 final NetconfNode netconfNode = newNetconfNode();
218 final Node node = new NodeBuilder().setNodeId(nodeId).addAugmentation(NetconfNode.class, netconfNode).build();
220 DataObjectModification<Node> mockDataObjModification = mock(DataObjectModification.class);
221 doReturn(Iterables.getLast(nodeListPath.getPathArguments())).when(mockDataObjModification).getIdentifier();
222 doReturn(WRITE).when(mockDataObjModification).getModificationType();
223 doReturn(node).when(mockDataObjModification).getDataAfter();
225 netconfNodeManager.onDataTreeChanged(Collections.singletonList(
226 new NetconfTopologyManagerTest.CustomTreeModification(DataTreeIdentifier.create(
227 LogicalDatastoreType.OPERATIONAL, nodeListPath), mockDataObjModification)));
229 verify(mockMountPointBuilder, timeout(5000)).register();
230 verify(mockMountPointBuilder).addService(eq(DOMDataBroker.class), any());
231 verify(mockMountPointBuilder).addService(eq(DOMRpcService.class), any());
232 verify(mockMountPointBuilder).addService(eq(DOMNotificationService.class), any());
233 verify(mockMountPointService).createMountPoint(DEVICE_ID.getTopologyPath());
235 // Notify that the NetconfNode operational state was deleted. Expect the slave mount point closed.
237 doReturn(DELETE).when(mockDataObjModification).getModificationType();
238 doReturn(node).when(mockDataObjModification).getDataBefore();
239 doReturn(null).when(mockDataObjModification).getDataAfter();
241 netconfNodeManager.onDataTreeChanged(Collections.singletonList(
242 new NetconfTopologyManagerTest.CustomTreeModification(DataTreeIdentifier.create(
243 LogicalDatastoreType.OPERATIONAL, nodeListPath), mockDataObjModification)));
245 verify(mockMountPointReg, timeout(5000)).close();
247 // Notify with a NetconfNode operational state WRITE. Expect the slave mount point re-created.
249 setupMountPointMocks();
251 doReturn(WRITE).when(mockDataObjModification).getModificationType();
252 doReturn(null).when(mockDataObjModification).getDataBefore();
253 doReturn(node).when(mockDataObjModification).getDataAfter();
255 netconfNodeManager.onDataTreeChanged(Collections.singletonList(
256 new NetconfTopologyManagerTest.CustomTreeModification(DataTreeIdentifier.create(
257 LogicalDatastoreType.OPERATIONAL, nodeListPath), mockDataObjModification)));
259 verify(mockMountPointBuilder, timeout(5000)).register();
261 // Notify again with a NetconfNode operational state WRITE. Expect the prior slave mount point closed and
262 // and a new one registered.
264 setupMountPointMocks();
266 doReturn(node).when(mockDataObjModification).getDataBefore();
268 netconfNodeManager.onDataTreeChanged(Collections.singletonList(
269 new NetconfTopologyManagerTest.CustomTreeModification(DataTreeIdentifier.create(
270 LogicalDatastoreType.OPERATIONAL, nodeListPath), mockDataObjModification)));
272 verify(mockMountPointReg, timeout(5000)).close();
273 verify(mockMountPointBuilder, timeout(5000)).register();
275 // Notify that the NetconfNode operational state was changed to UnableToConnect. Expect the slave mount point
278 setupMountPointMocks();
280 final Node updatedNode = new NodeBuilder().setNodeId(nodeId)
281 .addAugmentation(NetconfNode.class, new NetconfNodeBuilder(netconfNode)
282 .setConnectionStatus(NetconfNodeConnectionStatus.ConnectionStatus.UnableToConnect)
285 doReturn(SUBTREE_MODIFIED).when(mockDataObjModification).getModificationType();
286 doReturn(node).when(mockDataObjModification).getDataBefore();
287 doReturn(updatedNode).when(mockDataObjModification).getDataAfter();
289 netconfNodeManager.onDataTreeChanged(Collections.singletonList(
290 new NetconfTopologyManagerTest.CustomTreeModification(DataTreeIdentifier.create(
291 LogicalDatastoreType.OPERATIONAL, nodeListPath), mockDataObjModification)));
293 verify(mockMountPointReg, timeout(5000)).close();
295 netconfNodeManager.close();
296 verifyNoMoreInteractions(mockMountPointReg);
299 @SuppressWarnings("unchecked")
301 public void testSlaveMountPointRegistrationFailuresAndRetries()
302 throws InterruptedException, ExecutionException, TimeoutException {
303 final NodeId nodeId = new NodeId("device");
304 final NodeKey nodeKey = new NodeKey(nodeId);
305 final String topologyId = "topology-netconf";
306 final InstanceIdentifier<Node> nodeListPath = NetconfTopologyUtils.createTopologyNodeListPath(
307 nodeKey, topologyId);
309 final NetconfNode netconfNode = newNetconfNode();
310 final Node node = new NodeBuilder().setNodeId(nodeId).addAugmentation(NetconfNode.class, netconfNode).build();
312 DataObjectModification<Node> mockDataObjModification = mock(DataObjectModification.class);
313 doReturn(Iterables.getLast(nodeListPath.getPathArguments())).when(mockDataObjModification).getIdentifier();
314 doReturn(WRITE).when(mockDataObjModification).getModificationType();
315 doReturn(node).when(mockDataObjModification).getDataAfter();
317 // First try the registration where the perceived master hasn't been initialized as the master.
319 netconfNodeManager.onDataTreeChanged(Collections.singletonList(
320 new NetconfTopologyManagerTest.CustomTreeModification(DataTreeIdentifier.create(
321 LogicalDatastoreType.OPERATIONAL, nodeListPath), mockDataObjModification)));
323 verify(mockMountPointBuilder, after(1000).never()).register();
325 // Initialize the master but drop the initial YangTextSchemaSourceRequest message sent to the master so
330 CompletableFuture<AskForMasterMountPoint> yangTextSchemaSourceRequestFuture = new CompletableFuture<>();
331 testMasterActorRef.underlyingActor().messagesToDrop.put(YangTextSchemaSourceRequest.class,
332 yangTextSchemaSourceRequestFuture);
334 netconfNodeManager.onDataTreeChanged(Collections.singletonList(
335 new NetconfTopologyManagerTest.CustomTreeModification(DataTreeIdentifier.create(
336 LogicalDatastoreType.OPERATIONAL, nodeListPath), mockDataObjModification)));
338 yangTextSchemaSourceRequestFuture.get(5, TimeUnit.SECONDS);
339 verify(mockMountPointBuilder, timeout(5000)).register();
341 // Initiate another registration but drop the initial AskForMasterMountPoint message sent to the master so
344 setupMountPointMocks();
346 CompletableFuture<AskForMasterMountPoint> askForMasterMountPointFuture = new CompletableFuture<>();
347 testMasterActorRef.underlyingActor().messagesToDrop.put(AskForMasterMountPoint.class,
348 askForMasterMountPointFuture);
350 netconfNodeManager.onDataTreeChanged(Collections.singletonList(
351 new NetconfTopologyManagerTest.CustomTreeModification(DataTreeIdentifier.create(
352 LogicalDatastoreType.OPERATIONAL, nodeListPath), mockDataObjModification)));
354 askForMasterMountPointFuture.get(5, TimeUnit.SECONDS);
355 verify(mockMountPointReg, timeout(5000)).close();
356 verify(mockMountPointBuilder, timeout(5000)).register();
358 setupMountPointMocks();
359 netconfNodeManager.close();
360 verify(mockMountPointReg, timeout(5000)).close();
363 private NetconfNode newNetconfNode() {
364 return new NetconfNodeBuilder()
365 .setHost(new Host(new IpAddress(new Ipv4Address("127.0.0.1"))))
366 .setPort(new PortNumber(9999))
367 .setConnectionStatus(NetconfNodeConnectionStatus.ConnectionStatus.Connected)
368 .setClusteredConnectionStatus(new ClusteredConnectionStatusBuilder()
369 .setNetconfMasterNode(masterAddress).build())
373 private void setupMountPointMocks() {
374 reset(mockMountPointService, mockMountPointBuilder, mockMountPointReg);
376 doNothing().when(mockMountPointReg).close();
378 doReturn(mockMountPointBuilder).when(mockMountPointBuilder).addInitialSchemaContext(any());
379 doReturn(mockMountPointBuilder).when(mockMountPointBuilder).addService(any(), any());
380 doReturn(mockMountPointReg).when(mockMountPointBuilder).register();
382 doReturn(mockMountPointBuilder).when(mockMountPointService).createMountPoint(any());
385 private void initializeMaster() {
386 TestKit kit = new TestKit(masterSystem);
388 testMasterActorRef.tell(new CreateInitialMasterActorData(mockDeviceDataBroker, SOURCE_IDENTIFIERS,
389 mockRpcService, mockActionService), kit.getRef());
391 kit.expectMsgClass(MasterActorDataInitialized.class);
394 private static class TestMasterActor extends NetconfNodeActor {
395 final Map<Class<?>, CompletableFuture<? extends Object>> messagesToDrop = new ConcurrentHashMap<>();
397 TestMasterActor(final NetconfTopologySetup setup, final RemoteDeviceId deviceId,
398 final Timeout actorResponseWaitTime, final DOMMountPointService mountPointService) {
399 super(setup, deviceId, setup.getSchemaResourcesDTO().getSchemaRegistry(),
400 setup.getSchemaResourcesDTO().getSchemaRepository(), actorResponseWaitTime, mountPointService);
403 @SuppressWarnings({ "rawtypes", "unchecked" })
405 public void handleReceive(final Object message) {
406 CompletableFuture dropFuture = messagesToDrop.remove(message.getClass());
407 if (dropFuture != null) {
408 dropFuture.complete(message);
410 super.handleReceive(message);