2 * Copyright (c) 2014, 2015 Cisco Systems, Inc. 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.client.mdsal;
10 import static org.junit.jupiter.api.Assertions.assertEquals;
11 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
12 import static org.mockito.ArgumentMatchers.any;
13 import static org.mockito.ArgumentMatchers.anyCollection;
14 import static org.mockito.ArgumentMatchers.eq;
15 import static org.mockito.Mockito.after;
16 import static org.mockito.Mockito.doNothing;
17 import static org.mockito.Mockito.doReturn;
18 import static org.mockito.Mockito.mock;
19 import static org.mockito.Mockito.spy;
20 import static org.mockito.Mockito.timeout;
21 import static org.mockito.Mockito.times;
22 import static org.mockito.Mockito.verify;
24 import com.google.common.util.concurrent.Futures;
25 import com.google.common.util.concurrent.MoreExecutors;
26 import com.google.common.util.concurrent.SettableFuture;
27 import java.net.InetSocketAddress;
28 import java.util.ArrayList;
29 import java.util.HashMap;
30 import java.util.List;
33 import org.junit.jupiter.api.BeforeAll;
34 import org.junit.jupiter.api.Test;
35 import org.junit.jupiter.api.extension.ExtendWith;
36 import org.mockito.ArgumentCaptor;
37 import org.mockito.Mock;
38 import org.mockito.junit.jupiter.MockitoExtension;
39 import org.opendaylight.mdsal.dom.api.DOMNotification;
40 import org.opendaylight.netconf.api.CapabilityURN;
41 import org.opendaylight.netconf.api.messages.NetconfMessage;
42 import org.opendaylight.netconf.api.xml.XmlUtil;
43 import org.opendaylight.netconf.client.mdsal.NetconfDevice.EmptySchemaContextException;
44 import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchema;
45 import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchemaProvider;
46 import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
47 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceHandler;
48 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
49 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices;
50 import org.opendaylight.netconf.client.mdsal.impl.DefaultDeviceNetconfSchemaProvider;
51 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.NetconfState;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240611.connection.oper.available.capabilities.AvailableCapability.CapabilityOrigin;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240611.connection.oper.available.capabilities.AvailableCapabilityBuilder;
54 import org.opendaylight.yangtools.yang.common.QName;
55 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
56 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
57 import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier;
58 import org.opendaylight.yangtools.yang.model.api.source.YangTextSource;
59 import org.opendaylight.yangtools.yang.model.repo.api.EffectiveModelContextFactory;
60 import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository;
61 import org.opendaylight.yangtools.yang.model.repo.api.SchemaResolutionException;
62 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
64 @ExtendWith(MockitoExtension.class)
65 class NetconfDeviceTest extends AbstractTestModelTest {
66 private static final String TEST_NAMESPACE = "test:namespace";
67 private static final String TEST_MODULE = "test-module";
68 private static final String TEST_REVISION = "2013-07-22";
69 private static final SourceIdentifier TEST_SID = new SourceIdentifier(TEST_MODULE, TEST_REVISION);
70 private static final String TEST_CAPABILITY =
71 TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION;
73 private static NetconfMessage NOTIFICATION;
76 private SchemaSourceRegistry schemaRegistry;
78 private DeviceNetconfSchemaProvider schemaProvider;
80 private RemoteDeviceHandler facade;
82 private NetconfDeviceCommunicator listener;
84 private EffectiveModelContextFactory schemaFactory;
86 private DeviceNetconfSchemaProvider deviceSchemaProvider;
89 static void setupNotification() throws Exception {
90 NOTIFICATION = new NetconfMessage(XmlUtil.readXmlToDocument(
91 NetconfDeviceTest.class.getResourceAsStream("/notification-payload.xml")));
95 void testNetconfDeviceFailFirstSchemaFailSecondEmpty() {
96 // Make fallback attempt to fail due to empty resolved sources
97 final var schemaResolutionException = new SchemaResolutionException("fail first",
98 new SourceIdentifier("test-module", "2013-07-22"), new Throwable());
99 doReturn(Futures.immediateFailedFuture(schemaResolutionException))
100 .when(schemaFactory).createEffectiveModelContext(anyCollection());
102 final var device = new NetconfDeviceBuilder()
103 .setReconnectOnSchemasChange(true)
104 .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider(getSchemaRepository(), schemaFactory))
105 .setProcessingExecutor(MoreExecutors.directExecutor())
107 .setSalFacade(facade)
108 .setBaseSchemaProvider(BASE_SCHEMAS)
111 // Monitoring not supported
112 device.onRemoteSessionUp(getSessionCaps(false, TEST_CAPABILITY), listener);
114 final var captor = ArgumentCaptor.forClass(Throwable.class);
115 verify(facade, timeout(5000)).onDeviceFailed(captor.capture());
116 assertInstanceOf(EmptySchemaContextException.class, captor.getValue());
118 verify(listener, timeout(5000)).close();
119 verify(schemaFactory, times(1)).createEffectiveModelContext(anyCollection());
122 private static SchemaRepository getSchemaRepository() {
123 final var mock = mock(SchemaRepository.class);
124 final var mockRep = mock(YangTextSource.class);
125 doReturn(Futures.immediateFuture(mockRep))
126 .when(mock).getSchemaSource(any(SourceIdentifier.class), eq(YangTextSource.class));
131 void testNotificationBeforeSchema() {
132 final var remoteDeviceHandler = mockRemoteDeviceHandler();
133 doNothing().when(remoteDeviceHandler).onNotification(any(DOMNotification.class));
134 final var schemaFuture = SettableFuture.<DeviceNetconfSchema>create();
135 doReturn(schemaFuture).when(deviceSchemaProvider).deviceNetconfSchemaFor(any(), any(), any(), any(), any());
137 final var device = new NetconfDeviceBuilder()
138 .setReconnectOnSchemasChange(true)
139 .setDeviceSchemaProvider(deviceSchemaProvider)
140 .setProcessingExecutor(MoreExecutors.directExecutor())
142 .setSalFacade(remoteDeviceHandler)
143 .setBaseSchemaProvider(BASE_SCHEMAS)
146 final var sessionCaps = getSessionCaps(true, TEST_CAPABILITY);
147 device.onRemoteSessionUp(sessionCaps, listener);
149 device.onNotification(NOTIFICATION);
150 device.onNotification(NOTIFICATION);
151 verify(remoteDeviceHandler, times(0)).onNotification(any(DOMNotification.class));
154 schemaFuture.set(new DeviceNetconfSchema(NetconfDeviceCapabilities.empty(),
155 NetconfToNotificationTest.getNotificationSchemaContext(NetconfDeviceTest.class, false)));
157 verify(remoteDeviceHandler, timeout(10000).times(2))
158 .onNotification(any(DOMNotification.class));
160 device.onNotification(NOTIFICATION);
161 verify(remoteDeviceHandler, times(3)).onNotification(any(DOMNotification.class));
164 private RemoteDeviceHandler mockRemoteDeviceHandler() {
165 doNothing().when(facade).onDeviceConnected(
166 any(NetconfDeviceSchema.class), any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
171 void testNetconfDeviceReconnect() {
172 doReturn(RpcResultBuilder.failed().buildFuture()).when(listener).sendRequest(any());
174 final var device = new NetconfDeviceBuilder()
175 .setReconnectOnSchemasChange(true)
176 .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider())
177 .setProcessingExecutor(MoreExecutors.directExecutor())
179 .setSalFacade(facade)
180 .setBaseSchemaProvider(BASE_SCHEMAS)
182 final var sessionCaps = getSessionCaps(true,
183 TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION);
184 device.onRemoteSessionUp(sessionCaps, listener);
186 verify(facade, timeout(5000)).onDeviceConnected(
187 any(NetconfDeviceSchema.class), any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
189 device.onRemoteSessionDown();
190 verify(facade, timeout(5000)).onDeviceDisconnected();
192 device.onRemoteSessionUp(sessionCaps, listener);
194 verify(facade, timeout(5000).times(2)).onDeviceConnected(
195 any(NetconfDeviceSchema.class), any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
199 void testNetconfDeviceDisconnectListenerCallCancellation() {
200 doNothing().when(facade).onDeviceDisconnected();
201 final var schemaFuture = SettableFuture.<DeviceNetconfSchema>create();
202 doReturn(schemaFuture).when(schemaProvider).deviceNetconfSchemaFor(any(), any(), any(), any(), any());
204 final var device = new NetconfDeviceBuilder()
205 .setReconnectOnSchemasChange(true)
206 .setDeviceSchemaProvider(schemaProvider)
207 .setProcessingExecutor(MoreExecutors.directExecutor())
209 .setSalFacade(facade)
210 .setBaseSchemaProvider(BASE_SCHEMAS)
212 //session up, start schema resolution
213 device.onRemoteSessionUp(getSessionCaps(true,
214 TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION),
217 device.onRemoteSessionDown();
218 verify(facade, timeout(5000)).onDeviceDisconnected();
219 //complete schema setup
220 schemaFuture.set(new DeviceNetconfSchema(NetconfDeviceCapabilities.empty(), SCHEMA_CONTEXT));
221 //facade.onDeviceDisconnected() was called, so facade.onDeviceConnected() shouldn't be called anymore
222 verify(facade, after(1000).never()).onDeviceConnected(any(), any(), any(RemoteDeviceServices.class));
226 void testNetconfDeviceReconnectBeforeSchemaSetup() {
227 final var schemaFuture = SettableFuture.<EffectiveModelContext>create();
228 doReturn(schemaFuture).when(schemaFactory).createEffectiveModelContext(anyCollection());
230 doReturn(RpcResultBuilder.failed().buildFuture()).when(listener).sendRequest(any());
232 final var device = new NetconfDeviceBuilder()
233 .setReconnectOnSchemasChange(true)
234 .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider(getSchemaRepository(),
236 .setProcessingExecutor(MoreExecutors.directExecutor())
238 .setSalFacade(facade)
239 .setBaseSchemaProvider(BASE_SCHEMAS)
241 final var sessionCaps = getSessionCaps(true,
242 TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION);
244 // session up, start schema resolution
245 device.onRemoteSessionUp(sessionCaps, listener);
247 device.onRemoteSessionDown();
248 verify(facade, timeout(5000)).onDeviceDisconnected();
249 // session back up, start another schema resolution
250 device.onRemoteSessionUp(sessionCaps, listener);
251 // complete schema setup
252 schemaFuture.set(SCHEMA_CONTEXT);
253 // schema setup performed twice
254 verify(schemaFactory, timeout(5000).times(2)).createEffectiveModelContext(anyCollection());
255 // onDeviceConnected called once
256 verify(facade, timeout(5000)).onDeviceConnected(
257 any(NetconfDeviceSchema.class), any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
261 void testNetconfDeviceAvailableCapabilitiesBuilding() {
262 doReturn(RpcResultBuilder.failed().buildFuture()).when(listener).sendRequest(any());
264 final var netconfSpy = spy(new NetconfDeviceBuilder()
265 .setReconnectOnSchemasChange(true)
266 .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider())
267 .setProcessingExecutor(MoreExecutors.directExecutor())
269 .setSalFacade(facade)
270 .setBaseSchemaProvider(BASE_SCHEMAS)
273 final var sessionCaps = getSessionCaps(true,
274 TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION);
275 final var moduleBasedCaps = new HashMap<QName, CapabilityOrigin>();
276 moduleBasedCaps.putAll(sessionCaps.moduleBasedCaps());
277 moduleBasedCaps.put(QName.create("(test:qname:side:loading)test"), CapabilityOrigin.UserDefined);
279 netconfSpy.onRemoteSessionUp(sessionCaps.replaceModuleCaps(moduleBasedCaps), listener);
281 final var argument = ArgumentCaptor.forClass(NetconfDeviceSchema.class);
282 verify(facade, timeout(5000)).onDeviceConnected(argument.capture(),
283 any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
286 new AvailableCapabilityBuilder()
287 .setCapability("(test:namespace?revision=2013-07-22)test-module")
288 .setCapabilityOrigin(CapabilityOrigin.DeviceAdvertised)
290 new AvailableCapabilityBuilder()
291 .setCapability("(test:qname:side:loading)test")
292 .setCapabilityOrigin(CapabilityOrigin.UserDefined)
293 .build()), argument.getValue().capabilities().resolvedCapabilities());
297 void testNetconfDeviceNotificationsModelNotPresentWithCapability() {
298 final var netconfSpy = spy(new NetconfDeviceBuilder()
299 .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider())
300 .setProcessingExecutor(MoreExecutors.directExecutor())
302 .setSalFacade(facade)
303 .setBaseSchemaProvider(BASE_SCHEMAS)
306 netconfSpy.onRemoteSessionUp(getSessionCaps(false, CapabilityURN.NOTIFICATION,
307 CapabilityURN.INTERLEAVE), listener);
309 final var argument = ArgumentCaptor.forClass(NetconfDeviceSchema.class);
310 verify(facade, timeout(5000)).onDeviceConnected(argument.capture(), any(NetconfSessionPreferences.class),
311 any(RemoteDeviceServices.class));
314 new AvailableCapabilityBuilder()
315 .setCapability("(urn:ietf:params:xml:ns:yang:ietf-yang-types?revision=2013-07-15)ietf-yang-types")
317 new AvailableCapabilityBuilder()
318 .setCapability("(urn:ietf:params:xml:ns:netconf:notification:1.0?revision=2008-07-14)notifications")
319 .build()), argument.getValue().capabilities().resolvedCapabilities());
323 void testNetconfDeviceNotificationsModelNotPresentWithoutInterleaveCapability() {
324 final var netconfSpy = spy(new NetconfDeviceBuilder()
325 .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider())
326 .setProcessingExecutor(MoreExecutors.directExecutor())
328 .setSalFacade(facade)
329 .setBaseSchemaProvider(BASE_SCHEMAS)
332 final var sessionCaps = getSessionCaps(false,
333 TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION, CapabilityURN.NOTIFICATION);
335 netconfSpy.onRemoteSessionUp(sessionCaps, listener);
337 final var argument = ArgumentCaptor.forClass(NetconfDeviceSchema.class);
338 verify(facade, timeout(5000)).onDeviceConnected(argument.capture(), any(NetconfSessionPreferences.class),
339 any(RemoteDeviceServices.class));
341 // Notification schema was not added when there is no Interleave capability
343 new AvailableCapabilityBuilder()
344 .setCapability("(test:namespace?revision=2013-07-22)test-module")
345 .setCapabilityOrigin(CapabilityOrigin.DeviceAdvertised)
346 .build()), argument.getValue().capabilities().resolvedCapabilities());
350 void testNetconfDeviceNotificationsModelIsPresent() {
351 final var netconfSpy = spy(new NetconfDeviceBuilder()
352 .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider())
353 .setProcessingExecutor(MoreExecutors.directExecutor())
355 .setSalFacade(facade)
356 .setBaseSchemaProvider(BASE_SCHEMAS)
359 netconfSpy.onRemoteSessionUp(getSessionCaps(false).replaceModuleCaps(Map.of(
360 org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714
361 .YangModuleInfoImpl.getInstance().getName(), CapabilityOrigin.DeviceAdvertised,
362 org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715
363 .YangModuleInfoImpl.getInstance().getName(), CapabilityOrigin.DeviceAdvertised
366 final var argument = ArgumentCaptor.forClass(NetconfDeviceSchema.class);
367 verify(facade, timeout(5000)).onDeviceConnected(argument.capture(), any(NetconfSessionPreferences.class),
368 any(RemoteDeviceServices.class));
371 new AvailableCapabilityBuilder()
372 .setCapability("(urn:ietf:params:xml:ns:yang:ietf-yang-types?revision=2013-07-15)ietf-yang-types")
373 .setCapabilityOrigin(CapabilityOrigin.DeviceAdvertised)
375 new AvailableCapabilityBuilder()
376 .setCapability("(urn:ietf:params:xml:ns:netconf:notification:1.0?revision=2008-07-14)notifications")
377 .setCapabilityOrigin(CapabilityOrigin.DeviceAdvertised)
378 .build()), argument.getValue().capabilities().resolvedCapabilities());
381 private EffectiveModelContextFactory getSchemaFactory() {
382 doReturn(Futures.immediateFuture(SCHEMA_CONTEXT))
383 .when(schemaFactory).createEffectiveModelContext(anyCollection());
384 return schemaFactory;
387 private DeviceNetconfSchemaProvider mockDeviceNetconfSchemaProvider() {
388 return mockDeviceNetconfSchemaProvider(getSchemaRepository(), getSchemaFactory());
391 private DeviceNetconfSchemaProvider mockDeviceNetconfSchemaProvider(final SchemaRepository schemaRepository,
392 final EffectiveModelContextFactory modelContextFactory) {
393 return new DefaultDeviceNetconfSchemaProvider(schemaRegistry, schemaRepository, modelContextFactory);
396 private static RemoteDeviceId getId() {
397 return new RemoteDeviceId("test-D", InetSocketAddress.createUnresolved("localhost", 22));
400 private static NetconfSessionPreferences getSessionCaps(final boolean addMonitor,
401 final String... additionalCapabilities) {
402 final var capabilities = new ArrayList<String>();
403 capabilities.add(CapabilityURN.BASE);
404 capabilities.add(CapabilityURN.BASE_1_1);
406 capabilities.add(NetconfState.QNAME.getNamespace().toString());
408 capabilities.addAll(List.of(additionalCapabilities));
409 return NetconfSessionPreferences.fromStrings(capabilities);