002435ee71b40a519367bbc237c817b4e941c603
[netconf.git] / plugins / netconf-client-mdsal / src / test / java / org / opendaylight / netconf / client / mdsal / NetconfDeviceTest.java
1 /*
2  * Copyright (c) 2014, 2015 Cisco Systems, Inc. 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.client.mdsal;
9
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;
24
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;
32 import java.util.Map;
33 import java.util.Set;
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.yang.ietf.netconf.monitoring.rev101004.NetconfState;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.available.capabilities.AvailableCapability.CapabilityOrigin;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.available.capabilities.AvailableCapabilityBuilder;
55 import org.opendaylight.yangtools.yang.common.QName;
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;
63
64 @RunWith(MockitoJUnitRunner.StrictStubs.class)
65 public class NetconfDeviceTest extends AbstractTestModelTest {
66     public static final String TEST_NAMESPACE = "test:namespace";
67     public static final String TEST_MODULE = "test-module";
68     public static final String TEST_REVISION = "2013-07-22";
69     public static final SourceIdentifier TEST_SID = new SourceIdentifier(TEST_MODULE, TEST_REVISION);
70     public static final String TEST_CAPABILITY =
71             TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION;
72
73     public static final SourceIdentifier TEST_SID2 = new SourceIdentifier(TEST_MODULE + "2", TEST_REVISION);
74     public static final String TEST_CAPABILITY2 =
75             TEST_NAMESPACE + "?module=" + TEST_MODULE + "2" + "&revision=" + TEST_REVISION;
76
77     private static NetconfMessage NOTIFICATION;
78
79     @Mock
80     private SchemaSourceRegistry schemaRegistry;
81     @Mock
82     private DeviceNetconfSchemaProvider schemaProvider;
83
84     @BeforeClass
85     public static final void setupNotification() throws Exception {
86         NOTIFICATION = new NetconfMessage(XmlUtil.readXmlToDocument(
87             NetconfDeviceTest.class.getResourceAsStream("/notification-payload.xml")));
88     }
89
90     @Test
91     public void testNetconfDeviceFailFirstSchemaFailSecondEmpty() throws Exception {
92         final var facade = getFacade();
93         final var listener = getListener();
94
95         final var schemaFactory = getSchemaFactory();
96
97         // Make fallback attempt to fail due to empty resolved sources
98         final var schemaResolutionException = new SchemaResolutionException("fail first",
99             new SourceIdentifier("test-module", "2013-07-22"), new Throwable());
100         doReturn(Futures.immediateFailedFuture(schemaResolutionException))
101                 .when(schemaFactory).createEffectiveModelContext(anyCollection());
102
103         final var device = new NetconfDeviceBuilder()
104             .setReconnectOnSchemasChange(true)
105             .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider(getSchemaRepository(), schemaFactory))
106             .setProcessingExecutor(MoreExecutors.directExecutor())
107             .setId(getId())
108             .setSalFacade(facade)
109             .setBaseSchemaProvider(BASE_SCHEMAS)
110             .build();
111
112         // Monitoring not supported
113         device.onRemoteSessionUp(getSessionCaps(false, TEST_CAPABILITY), listener);
114
115         final var captor = ArgumentCaptor.forClass(Throwable.class);
116         verify(facade, timeout(5000)).onDeviceFailed(captor.capture());
117         assertThat(captor.getValue(), instanceOf(EmptySchemaContextException.class));
118
119         verify(listener, timeout(5000)).close();
120         verify(schemaFactory, times(1)).createEffectiveModelContext(anyCollection());
121     }
122
123     private static SchemaRepository getSchemaRepository() {
124         final var mock = mock(SchemaRepository.class);
125         final var mockRep = mock(YangTextSource.class);
126         doReturn(Futures.immediateFuture(mockRep))
127                 .when(mock).getSchemaSource(any(SourceIdentifier.class), eq(YangTextSource.class));
128         return mock;
129     }
130
131     @Test
132     public void testNotificationBeforeSchema() throws Exception {
133         final var facade = getFacade();
134         final var deviceSchemaProvider = mock(DeviceNetconfSchemaProvider.class);
135         final var schemaFuture = SettableFuture.<DeviceNetconfSchema>create();
136         doReturn(schemaFuture).when(deviceSchemaProvider).deviceNetconfSchemaFor(any(), any(), any(), any(), any());
137
138         final var device = new NetconfDeviceBuilder()
139             .setReconnectOnSchemasChange(true)
140             .setDeviceSchemaProvider(deviceSchemaProvider)
141             .setProcessingExecutor(MoreExecutors.directExecutor())
142             .setId(getId())
143             .setSalFacade(facade)
144             .setBaseSchemaProvider(BASE_SCHEMAS)
145             .build();
146
147         final var sessionCaps = getSessionCaps(true, TEST_CAPABILITY);
148         device.onRemoteSessionUp(sessionCaps, getListener());
149
150         device.onNotification(NOTIFICATION);
151         device.onNotification(NOTIFICATION);
152         verify(facade, times(0)).onNotification(any(DOMNotification.class));
153
154         // Now enable schema
155         schemaFuture.set(new DeviceNetconfSchema(NetconfDeviceCapabilities.empty(),
156             NetconfToNotificationTest.getNotificationSchemaContext(NetconfDeviceTest.class, false)));
157
158         verify(facade, timeout(10000).times(2)).onNotification(any(DOMNotification.class));
159
160         device.onNotification(NOTIFICATION);
161         verify(facade, times(3)).onNotification(any(DOMNotification.class));
162     }
163
164     @Test
165     public void testNetconfDeviceReconnect() throws Exception {
166         final var facade = getFacade();
167         final var listener = getListener();
168
169         final var deviceSchemaProvider = mockDeviceNetconfSchemaProvider();
170
171         final var device = new NetconfDeviceBuilder()
172             .setReconnectOnSchemasChange(true)
173             .setDeviceSchemaProvider(deviceSchemaProvider)
174             .setProcessingExecutor(MoreExecutors.directExecutor())
175             .setId(getId())
176             .setSalFacade(facade)
177             .setBaseSchemaProvider(BASE_SCHEMAS)
178             .build();
179         final var sessionCaps = getSessionCaps(true,
180                 TEST_NAMESPACE + "?module=" + TEST_MODULE + "&amp;revision=" + TEST_REVISION);
181         device.onRemoteSessionUp(sessionCaps, listener);
182
183         verify(facade, timeout(5000)).onDeviceConnected(
184                 any(NetconfDeviceSchema.class), any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
185
186         device.onRemoteSessionDown();
187         verify(facade, timeout(5000)).onDeviceDisconnected();
188
189         device.onRemoteSessionUp(sessionCaps, listener);
190
191         verify(facade, timeout(5000).times(2)).onDeviceConnected(
192                 any(NetconfDeviceSchema.class), any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
193     }
194
195     @Test
196     public void testNetconfDeviceDisconnectListenerCallCancellation() throws Exception {
197         final var facade = getFacade();
198         final var schemaFuture = SettableFuture.<DeviceNetconfSchema>create();
199         doReturn(schemaFuture).when(schemaProvider).deviceNetconfSchemaFor(any(), any(), any(), any(), any());
200
201         final var device = new NetconfDeviceBuilder()
202             .setReconnectOnSchemasChange(true)
203             .setDeviceSchemaProvider(schemaProvider)
204             .setProcessingExecutor(MoreExecutors.directExecutor())
205             .setId(getId())
206             .setSalFacade(facade)
207             .setBaseSchemaProvider(BASE_SCHEMAS)
208             .build();
209         //session up, start schema resolution
210         device.onRemoteSessionUp(getSessionCaps(true,
211             TEST_NAMESPACE + "?module=" + TEST_MODULE + "&amp;revision=" + TEST_REVISION), getListener());
212         //session closed
213         device.onRemoteSessionDown();
214         verify(facade, timeout(5000)).onDeviceDisconnected();
215         //complete schema setup
216         schemaFuture.set(new DeviceNetconfSchema(NetconfDeviceCapabilities.empty(), SCHEMA_CONTEXT));
217         //facade.onDeviceDisconnected() was called, so facade.onDeviceConnected() shouldn't be called anymore
218         verify(facade, after(1000).never()).onDeviceConnected(any(), any(), any(RemoteDeviceServices.class));
219     }
220
221     @Test
222     public void testNetconfDeviceReconnectBeforeSchemaSetup() throws Exception {
223         final var facade = getFacade();
224
225         final var schemaContextProviderFactory = mock(EffectiveModelContextFactory.class);
226         final var schemaFuture = SettableFuture.<EffectiveModelContext>create();
227         doReturn(schemaFuture).when(schemaContextProviderFactory).createEffectiveModelContext(anyCollection());
228
229         final var device = new NetconfDeviceBuilder()
230             .setReconnectOnSchemasChange(true)
231             .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider(getSchemaRepository(),
232                 schemaContextProviderFactory))
233             .setProcessingExecutor(MoreExecutors.directExecutor())
234             .setId(getId())
235             .setSalFacade(facade)
236             .setBaseSchemaProvider(BASE_SCHEMAS)
237             .build();
238         final var sessionCaps = getSessionCaps(true,
239             TEST_NAMESPACE + "?module=" + TEST_MODULE + "&amp;revision=" + TEST_REVISION);
240
241         final NetconfDeviceCommunicator listener = getListener();
242         // session up, start schema resolution
243         device.onRemoteSessionUp(sessionCaps, listener);
244         // session down
245         device.onRemoteSessionDown();
246         verify(facade, timeout(5000)).onDeviceDisconnected();
247         // session back up, start another schema resolution
248         device.onRemoteSessionUp(sessionCaps, listener);
249         // complete schema setup
250         schemaFuture.set(SCHEMA_CONTEXT);
251         // schema setup performed twice
252         verify(schemaContextProviderFactory, timeout(5000).times(2)).createEffectiveModelContext(anyCollection());
253         // onDeviceConnected called once
254         verify(facade, timeout(5000)).onDeviceConnected(
255             any(NetconfDeviceSchema.class), any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
256     }
257
258     @Test
259     public void testNetconfDeviceAvailableCapabilitiesBuilding() throws Exception {
260         final var facade = getFacade();
261         final var listener = getListener();
262
263         final var netconfSpy = spy(new NetconfDeviceBuilder()
264             .setReconnectOnSchemasChange(true)
265             .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider())
266             .setProcessingExecutor(MoreExecutors.directExecutor())
267             .setId(getId())
268             .setSalFacade(facade)
269             .setBaseSchemaProvider(BASE_SCHEMAS)
270             .build());
271
272         final var sessionCaps = getSessionCaps(true,
273             TEST_NAMESPACE + "?module=" + TEST_MODULE + "&amp;revision=" + TEST_REVISION);
274         final var moduleBasedCaps = new HashMap<QName, CapabilityOrigin>();
275         moduleBasedCaps.putAll(sessionCaps.moduleBasedCaps());
276         moduleBasedCaps.put(QName.create("(test:qname:side:loading)test"), CapabilityOrigin.UserDefined);
277
278         netconfSpy.onRemoteSessionUp(sessionCaps.replaceModuleCaps(moduleBasedCaps), listener);
279
280         final var argument = ArgumentCaptor.forClass(NetconfDeviceSchema.class);
281         verify(facade, timeout(5000)).onDeviceConnected(argument.capture(), any(NetconfSessionPreferences.class),
282             any(RemoteDeviceServices.class));
283
284         assertEquals(Set.of(
285             new AvailableCapabilityBuilder()
286                 .setCapability("(test:namespace?revision=2013-07-22)test-module")
287                 .setCapabilityOrigin(CapabilityOrigin.DeviceAdvertised)
288                 .build(),
289             new AvailableCapabilityBuilder()
290                 .setCapability("(test:qname:side:loading)test")
291                 .setCapabilityOrigin(CapabilityOrigin.UserDefined)
292                 .build()), argument.getValue().capabilities().resolvedCapabilities());
293     }
294
295     @Test
296     public void testNetconfDeviceNotificationsModelNotPresentWithCapability() throws Exception {
297         final var facade = getFacade();
298         final var netconfSpy = spy(new NetconfDeviceBuilder()
299             .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider())
300             .setProcessingExecutor(MoreExecutors.directExecutor())
301             .setId(getId())
302             .setSalFacade(facade)
303             .setBaseSchemaProvider(BASE_SCHEMAS)
304             .build());
305
306         netconfSpy.onRemoteSessionUp(getSessionCaps(false, CapabilityURN.NOTIFICATION), getListener());
307
308         final var argument = ArgumentCaptor.forClass(NetconfDeviceSchema.class);
309         verify(facade, timeout(5000)).onDeviceConnected(argument.capture(), any(NetconfSessionPreferences.class),
310                 any(RemoteDeviceServices.class));
311
312         assertEquals(Set.of(
313             new AvailableCapabilityBuilder()
314                 .setCapability("(urn:ietf:params:xml:ns:yang:ietf-yang-types?revision=2013-07-15)ietf-yang-types")
315                 .build(),
316             new AvailableCapabilityBuilder()
317                 .setCapability("(urn:ietf:params:xml:ns:netconf:notification:1.0?revision=2008-07-14)notifications")
318                 .build()), argument.getValue().capabilities().resolvedCapabilities());
319     }
320
321     @Test
322     public void testNetconfDeviceNotificationsModelIsPresent() throws Exception {
323         final var facade = getFacade();
324         final var listener = getListener();
325
326         final var netconfSpy = spy(new NetconfDeviceBuilder()
327             .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider())
328             .setProcessingExecutor(MoreExecutors.directExecutor())
329             .setId(getId())
330             .setSalFacade(facade)
331             .setBaseSchemaProvider(BASE_SCHEMAS)
332             .build());
333
334         netconfSpy.onRemoteSessionUp(getSessionCaps(false).replaceModuleCaps(Map.of(
335             org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714
336                 .YangModuleInfoImpl.getInstance().getName(), CapabilityOrigin.DeviceAdvertised,
337             org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715
338                 .YangModuleInfoImpl.getInstance().getName(), CapabilityOrigin.DeviceAdvertised
339             )), listener);
340
341         final var argument = ArgumentCaptor.forClass(NetconfDeviceSchema.class);
342         verify(facade, timeout(5000)).onDeviceConnected(argument.capture(), any(NetconfSessionPreferences.class),
343                 any(RemoteDeviceServices.class));
344
345         assertEquals(Set.of(
346             new AvailableCapabilityBuilder()
347                 .setCapability("(urn:ietf:params:xml:ns:yang:ietf-yang-types?revision=2013-07-15)ietf-yang-types")
348                 .setCapabilityOrigin(CapabilityOrigin.DeviceAdvertised)
349                 .build(),
350             new AvailableCapabilityBuilder()
351                 .setCapability("(urn:ietf:params:xml:ns:netconf:notification:1.0?revision=2008-07-14)notifications")
352                 .setCapabilityOrigin(CapabilityOrigin.DeviceAdvertised)
353                 .build()), argument.getValue().capabilities().resolvedCapabilities());
354     }
355
356     private static EffectiveModelContextFactory getSchemaFactory() {
357         final var schemaFactory = mock(EffectiveModelContextFactory.class);
358         doReturn(Futures.immediateFuture(SCHEMA_CONTEXT))
359                 .when(schemaFactory).createEffectiveModelContext(anyCollection());
360         return schemaFactory;
361     }
362
363     private static RemoteDeviceHandler getFacade() throws Exception {
364         final RemoteDeviceHandler remoteDeviceHandler = mockCloseableClass(RemoteDeviceHandler.class);
365         doNothing().when(remoteDeviceHandler).onDeviceConnected(
366                 any(NetconfDeviceSchema.class), any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
367         doNothing().when(remoteDeviceHandler).onDeviceDisconnected();
368         doNothing().when(remoteDeviceHandler).onNotification(any(DOMNotification.class));
369         return remoteDeviceHandler;
370     }
371
372     private static <T extends AutoCloseable> T mockCloseableClass(final Class<T> remoteDeviceHandlerClass)
373             throws Exception {
374         final T mock = mock(remoteDeviceHandlerClass);
375         doNothing().when(mock).close();
376         return mock;
377     }
378
379     private DeviceNetconfSchemaProvider mockDeviceNetconfSchemaProvider() {
380         return mockDeviceNetconfSchemaProvider(getSchemaRepository(), getSchemaFactory());
381     }
382
383     private DeviceNetconfSchemaProvider mockDeviceNetconfSchemaProvider(final SchemaRepository schemaRepository,
384             final EffectiveModelContextFactory schemaFactory) {
385         return new DefaultDeviceNetconfSchemaProvider(schemaRegistry, schemaRepository, schemaFactory,
386             (unused1, unused2, unused3, unused4) -> Futures.immediateFuture(NetconfStateSchemas.EMPTY));
387     }
388
389     public RemoteDeviceId getId() {
390         return new RemoteDeviceId("test-D", InetSocketAddress.createUnresolved("localhost", 22));
391     }
392
393     public NetconfSessionPreferences getSessionCaps(final boolean addMonitor,
394             final String... additionalCapabilities) {
395         final var capabilities = new ArrayList<String>();
396         capabilities.add(CapabilityURN.BASE);
397         capabilities.add(CapabilityURN.BASE_1_1);
398         if (addMonitor) {
399             capabilities.add(NetconfState.QNAME.getNamespace().toString());
400         }
401         capabilities.addAll(List.of(additionalCapabilities));
402         return NetconfSessionPreferences.fromStrings(capabilities);
403     }
404
405     public NetconfDeviceCommunicator getListener() throws Exception {
406         return mockCloseableClass(NetconfDeviceCommunicator.class);
407     }
408 }