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.hamcrest.CoreMatchers.instanceOf;
11 import static org.hamcrest.MatcherAssert.assertThat;
12 import static org.junit.Assert.assertEquals;
13 import static org.mockito.ArgumentMatchers.any;
14 import static org.mockito.ArgumentMatchers.anyCollection;
15 import static org.mockito.ArgumentMatchers.eq;
16 import static org.mockito.Mockito.after;
17 import static org.mockito.Mockito.doNothing;
18 import static org.mockito.Mockito.doReturn;
19 import static org.mockito.Mockito.mock;
20 import static org.mockito.Mockito.spy;
21 import static org.mockito.Mockito.timeout;
22 import static org.mockito.Mockito.times;
23 import static org.mockito.Mockito.verify;
25 import com.google.common.util.concurrent.Futures;
26 import com.google.common.util.concurrent.MoreExecutors;
27 import com.google.common.util.concurrent.SettableFuture;
28 import java.net.InetSocketAddress;
29 import java.util.ArrayList;
30 import java.util.HashMap;
31 import java.util.List;
34 import org.junit.BeforeClass;
35 import org.junit.Test;
36 import org.junit.runner.RunWith;
37 import org.mockito.ArgumentCaptor;
38 import org.mockito.Mock;
39 import org.mockito.junit.MockitoJUnitRunner;
40 import org.opendaylight.mdsal.dom.api.DOMNotification;
41 import org.opendaylight.netconf.api.CapabilityURN;
42 import org.opendaylight.netconf.api.messages.NetconfMessage;
43 import org.opendaylight.netconf.api.xml.XmlUtil;
44 import org.opendaylight.netconf.client.mdsal.NetconfDevice.EmptySchemaContextException;
45 import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchema;
46 import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchemaProvider;
47 import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
48 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceHandler;
49 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
50 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices;
51 import org.opendaylight.netconf.client.mdsal.impl.DefaultDeviceNetconfSchemaProvider;
52 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.Get;
53 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.NetconfState;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.available.capabilities.AvailableCapability.CapabilityOrigin;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.available.capabilities.AvailableCapabilityBuilder;
56 import org.opendaylight.yangtools.yang.common.QName;
57 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
58 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
59 import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier;
60 import org.opendaylight.yangtools.yang.model.api.source.YangTextSource;
61 import org.opendaylight.yangtools.yang.model.repo.api.EffectiveModelContextFactory;
62 import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository;
63 import org.opendaylight.yangtools.yang.model.repo.api.SchemaResolutionException;
64 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
66 @RunWith(MockitoJUnitRunner.StrictStubs.class)
67 public class NetconfDeviceTest extends AbstractTestModelTest {
68 public static final String TEST_NAMESPACE = "test:namespace";
69 public static final String TEST_MODULE = "test-module";
70 public static final String TEST_REVISION = "2013-07-22";
71 public static final SourceIdentifier TEST_SID = new SourceIdentifier(TEST_MODULE, TEST_REVISION);
72 public static final String TEST_CAPABILITY =
73 TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION;
75 public static final SourceIdentifier TEST_SID2 = new SourceIdentifier(TEST_MODULE + "2", TEST_REVISION);
76 public static final String TEST_CAPABILITY2 =
77 TEST_NAMESPACE + "?module=" + TEST_MODULE + "2" + "&revision=" + TEST_REVISION;
79 private static NetconfMessage NOTIFICATION;
82 private SchemaSourceRegistry schemaRegistry;
84 private DeviceNetconfSchemaProvider schemaProvider;
87 public static final void setupNotification() throws Exception {
88 NOTIFICATION = new NetconfMessage(XmlUtil.readXmlToDocument(
89 NetconfDeviceTest.class.getResourceAsStream("/notification-payload.xml")));
93 public void testNetconfDeviceFailFirstSchemaFailSecondEmpty() throws Exception {
94 final var facade = getFacade();
95 final var listener = getListener();
97 final var schemaFactory = getSchemaFactory();
99 // Make fallback attempt to fail due to empty resolved sources
100 final var schemaResolutionException = new SchemaResolutionException("fail first",
101 new SourceIdentifier("test-module", "2013-07-22"), new Throwable());
102 doReturn(Futures.immediateFailedFuture(schemaResolutionException))
103 .when(schemaFactory).createEffectiveModelContext(anyCollection());
105 final var device = new NetconfDeviceBuilder()
106 .setReconnectOnSchemasChange(true)
107 .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider(getSchemaRepository(), schemaFactory))
108 .setProcessingExecutor(MoreExecutors.directExecutor())
110 .setSalFacade(facade)
111 .setBaseSchemaProvider(BASE_SCHEMAS)
114 // Monitoring not supported
115 device.onRemoteSessionUp(getSessionCaps(false, TEST_CAPABILITY), listener);
117 final var captor = ArgumentCaptor.forClass(Throwable.class);
118 verify(facade, timeout(5000)).onDeviceFailed(captor.capture());
119 assertThat(captor.getValue(), instanceOf(EmptySchemaContextException.class));
121 verify(listener, timeout(5000)).close();
122 verify(schemaFactory, times(1)).createEffectiveModelContext(anyCollection());
125 private static SchemaRepository getSchemaRepository() {
126 final var mock = mock(SchemaRepository.class);
127 final var mockRep = mock(YangTextSource.class);
128 doReturn(Futures.immediateFuture(mockRep))
129 .when(mock).getSchemaSource(any(SourceIdentifier.class), eq(YangTextSource.class));
134 public void testNotificationBeforeSchema() throws Exception {
135 final var facade = getFacade();
136 final var deviceSchemaProvider = mock(DeviceNetconfSchemaProvider.class);
137 final var schemaFuture = SettableFuture.<DeviceNetconfSchema>create();
138 doReturn(schemaFuture).when(deviceSchemaProvider).deviceNetconfSchemaFor(any(), any(), any(), any(), any());
140 final var device = new NetconfDeviceBuilder()
141 .setReconnectOnSchemasChange(true)
142 .setDeviceSchemaProvider(deviceSchemaProvider)
143 .setProcessingExecutor(MoreExecutors.directExecutor())
145 .setSalFacade(facade)
146 .setBaseSchemaProvider(BASE_SCHEMAS)
149 final var sessionCaps = getSessionCaps(true, TEST_CAPABILITY);
150 device.onRemoteSessionUp(sessionCaps, getListener());
152 device.onNotification(NOTIFICATION);
153 device.onNotification(NOTIFICATION);
154 verify(facade, times(0)).onNotification(any(DOMNotification.class));
157 schemaFuture.set(new DeviceNetconfSchema(NetconfDeviceCapabilities.empty(),
158 NetconfToNotificationTest.getNotificationSchemaContext(NetconfDeviceTest.class, false)));
160 verify(facade, timeout(10000).times(2)).onNotification(any(DOMNotification.class));
162 device.onNotification(NOTIFICATION);
163 verify(facade, times(3)).onNotification(any(DOMNotification.class));
167 public void testNetconfDeviceReconnect() throws Exception {
168 final var facade = getFacade();
169 final var listener = getListener();
171 doReturn(RpcResultBuilder.failed().buildFuture()).when(listener).sendRequest(any(), eq(Get.QNAME));
173 final var device = new NetconfDeviceBuilder()
174 .setReconnectOnSchemasChange(true)
175 .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider())
176 .setProcessingExecutor(MoreExecutors.directExecutor())
178 .setSalFacade(facade)
179 .setBaseSchemaProvider(BASE_SCHEMAS)
181 final var sessionCaps = getSessionCaps(true,
182 TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION);
183 device.onRemoteSessionUp(sessionCaps, listener);
185 verify(facade, timeout(5000)).onDeviceConnected(
186 any(NetconfDeviceSchema.class), any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
188 device.onRemoteSessionDown();
189 verify(facade, timeout(5000)).onDeviceDisconnected();
191 device.onRemoteSessionUp(sessionCaps, listener);
193 verify(facade, timeout(5000).times(2)).onDeviceConnected(
194 any(NetconfDeviceSchema.class), any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
198 public void testNetconfDeviceDisconnectListenerCallCancellation() throws Exception {
199 final var facade = getFacade();
200 final var schemaFuture = SettableFuture.<DeviceNetconfSchema>create();
201 doReturn(schemaFuture).when(schemaProvider).deviceNetconfSchemaFor(any(), any(), any(), any(), any());
203 final var device = new NetconfDeviceBuilder()
204 .setReconnectOnSchemasChange(true)
205 .setDeviceSchemaProvider(schemaProvider)
206 .setProcessingExecutor(MoreExecutors.directExecutor())
208 .setSalFacade(facade)
209 .setBaseSchemaProvider(BASE_SCHEMAS)
211 //session up, start schema resolution
212 device.onRemoteSessionUp(getSessionCaps(true,
213 TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION), getListener());
215 device.onRemoteSessionDown();
216 verify(facade, timeout(5000)).onDeviceDisconnected();
217 //complete schema setup
218 schemaFuture.set(new DeviceNetconfSchema(NetconfDeviceCapabilities.empty(), SCHEMA_CONTEXT));
219 //facade.onDeviceDisconnected() was called, so facade.onDeviceConnected() shouldn't be called anymore
220 verify(facade, after(1000).never()).onDeviceConnected(any(), any(), any(RemoteDeviceServices.class));
224 public void testNetconfDeviceReconnectBeforeSchemaSetup() throws Exception {
225 final var facade = getFacade();
227 final var schemaContextProviderFactory = mock(EffectiveModelContextFactory.class);
228 final var schemaFuture = SettableFuture.<EffectiveModelContext>create();
229 doReturn(schemaFuture).when(schemaContextProviderFactory).createEffectiveModelContext(anyCollection());
231 final var listener = getListener();
232 doReturn(RpcResultBuilder.failed().buildFuture()).when(listener).sendRequest(any(), eq(Get.QNAME));
234 final var device = new NetconfDeviceBuilder()
235 .setReconnectOnSchemasChange(true)
236 .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider(getSchemaRepository(),
237 schemaContextProviderFactory))
238 .setProcessingExecutor(MoreExecutors.directExecutor())
240 .setSalFacade(facade)
241 .setBaseSchemaProvider(BASE_SCHEMAS)
243 final var sessionCaps = getSessionCaps(true,
244 TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION);
246 // session up, start schema resolution
247 device.onRemoteSessionUp(sessionCaps, listener);
249 device.onRemoteSessionDown();
250 verify(facade, timeout(5000)).onDeviceDisconnected();
251 // session back up, start another schema resolution
252 device.onRemoteSessionUp(sessionCaps, listener);
253 // complete schema setup
254 schemaFuture.set(SCHEMA_CONTEXT);
255 // schema setup performed twice
256 verify(schemaContextProviderFactory, timeout(5000).times(2)).createEffectiveModelContext(anyCollection());
257 // onDeviceConnected called once
258 verify(facade, timeout(5000)).onDeviceConnected(
259 any(NetconfDeviceSchema.class), any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
263 public void testNetconfDeviceAvailableCapabilitiesBuilding() throws Exception {
264 final var facade = getFacade();
265 final var listener = getListener();
266 doReturn(RpcResultBuilder.failed().buildFuture()).when(listener).sendRequest(any(), eq(Get.QNAME));
268 final var netconfSpy = spy(new NetconfDeviceBuilder()
269 .setReconnectOnSchemasChange(true)
270 .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider())
271 .setProcessingExecutor(MoreExecutors.directExecutor())
273 .setSalFacade(facade)
274 .setBaseSchemaProvider(BASE_SCHEMAS)
277 final var sessionCaps = getSessionCaps(true,
278 TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION);
279 final var moduleBasedCaps = new HashMap<QName, CapabilityOrigin>();
280 moduleBasedCaps.putAll(sessionCaps.moduleBasedCaps());
281 moduleBasedCaps.put(QName.create("(test:qname:side:loading)test"), CapabilityOrigin.UserDefined);
283 netconfSpy.onRemoteSessionUp(sessionCaps.replaceModuleCaps(moduleBasedCaps), listener);
285 final var argument = ArgumentCaptor.forClass(NetconfDeviceSchema.class);
286 verify(facade, timeout(5000)).onDeviceConnected(argument.capture(), any(NetconfSessionPreferences.class),
287 any(RemoteDeviceServices.class));
290 new AvailableCapabilityBuilder()
291 .setCapability("(test:namespace?revision=2013-07-22)test-module")
292 .setCapabilityOrigin(CapabilityOrigin.DeviceAdvertised)
294 new AvailableCapabilityBuilder()
295 .setCapability("(test:qname:side:loading)test")
296 .setCapabilityOrigin(CapabilityOrigin.UserDefined)
297 .build()), argument.getValue().capabilities().resolvedCapabilities());
301 public void testNetconfDeviceNotificationsModelNotPresentWithCapability() throws Exception {
302 final var facade = getFacade();
303 final var netconfSpy = spy(new NetconfDeviceBuilder()
304 .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider())
305 .setProcessingExecutor(MoreExecutors.directExecutor())
307 .setSalFacade(facade)
308 .setBaseSchemaProvider(BASE_SCHEMAS)
311 netconfSpy.onRemoteSessionUp(getSessionCaps(false, CapabilityURN.NOTIFICATION), getListener());
313 final var argument = ArgumentCaptor.forClass(NetconfDeviceSchema.class);
314 verify(facade, timeout(5000)).onDeviceConnected(argument.capture(), any(NetconfSessionPreferences.class),
315 any(RemoteDeviceServices.class));
318 new AvailableCapabilityBuilder()
319 .setCapability("(urn:ietf:params:xml:ns:yang:ietf-yang-types?revision=2013-07-15)ietf-yang-types")
321 new AvailableCapabilityBuilder()
322 .setCapability("(urn:ietf:params:xml:ns:netconf:notification:1.0?revision=2008-07-14)notifications")
323 .build()), argument.getValue().capabilities().resolvedCapabilities());
327 public void testNetconfDeviceNotificationsModelIsPresent() throws Exception {
328 final var facade = getFacade();
329 final var listener = getListener();
331 final var netconfSpy = spy(new NetconfDeviceBuilder()
332 .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider())
333 .setProcessingExecutor(MoreExecutors.directExecutor())
335 .setSalFacade(facade)
336 .setBaseSchemaProvider(BASE_SCHEMAS)
339 netconfSpy.onRemoteSessionUp(getSessionCaps(false).replaceModuleCaps(Map.of(
340 org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714
341 .YangModuleInfoImpl.getInstance().getName(), CapabilityOrigin.DeviceAdvertised,
342 org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715
343 .YangModuleInfoImpl.getInstance().getName(), CapabilityOrigin.DeviceAdvertised
346 final var argument = ArgumentCaptor.forClass(NetconfDeviceSchema.class);
347 verify(facade, timeout(5000)).onDeviceConnected(argument.capture(), any(NetconfSessionPreferences.class),
348 any(RemoteDeviceServices.class));
351 new AvailableCapabilityBuilder()
352 .setCapability("(urn:ietf:params:xml:ns:yang:ietf-yang-types?revision=2013-07-15)ietf-yang-types")
353 .setCapabilityOrigin(CapabilityOrigin.DeviceAdvertised)
355 new AvailableCapabilityBuilder()
356 .setCapability("(urn:ietf:params:xml:ns:netconf:notification:1.0?revision=2008-07-14)notifications")
357 .setCapabilityOrigin(CapabilityOrigin.DeviceAdvertised)
358 .build()), argument.getValue().capabilities().resolvedCapabilities());
361 private static EffectiveModelContextFactory getSchemaFactory() {
362 final var schemaFactory = mock(EffectiveModelContextFactory.class);
363 doReturn(Futures.immediateFuture(SCHEMA_CONTEXT))
364 .when(schemaFactory).createEffectiveModelContext(anyCollection());
365 return schemaFactory;
368 private static RemoteDeviceHandler getFacade() throws Exception {
369 final RemoteDeviceHandler remoteDeviceHandler = mockCloseableClass(RemoteDeviceHandler.class);
370 doNothing().when(remoteDeviceHandler).onDeviceConnected(
371 any(NetconfDeviceSchema.class), any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
372 doNothing().when(remoteDeviceHandler).onDeviceDisconnected();
373 doNothing().when(remoteDeviceHandler).onNotification(any(DOMNotification.class));
374 return remoteDeviceHandler;
377 private static <T extends AutoCloseable> T mockCloseableClass(final Class<T> remoteDeviceHandlerClass)
379 final T mock = mock(remoteDeviceHandlerClass);
380 doNothing().when(mock).close();
384 private DeviceNetconfSchemaProvider mockDeviceNetconfSchemaProvider() {
385 return mockDeviceNetconfSchemaProvider(getSchemaRepository(), getSchemaFactory());
388 private DeviceNetconfSchemaProvider mockDeviceNetconfSchemaProvider(final SchemaRepository schemaRepository,
389 final EffectiveModelContextFactory schemaFactory) {
390 return new DefaultDeviceNetconfSchemaProvider(schemaRegistry, schemaRepository, schemaFactory);
393 public RemoteDeviceId getId() {
394 return new RemoteDeviceId("test-D", InetSocketAddress.createUnresolved("localhost", 22));
397 public NetconfSessionPreferences getSessionCaps(final boolean addMonitor,
398 final String... additionalCapabilities) {
399 final var capabilities = new ArrayList<String>();
400 capabilities.add(CapabilityURN.BASE);
401 capabilities.add(CapabilityURN.BASE_1_1);
403 capabilities.add(NetconfState.QNAME.getNamespace().toString());
405 capabilities.addAll(List.of(additionalCapabilities));
406 return NetconfSessionPreferences.fromStrings(capabilities);
409 public NetconfDeviceCommunicator getListener() throws Exception {
410 return mockCloseableClass(NetconfDeviceCommunicator.class);