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