Refactor NetconfDeviceSchemas
[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.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;
65
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;
74
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;
78
79     private static NetconfMessage NOTIFICATION;
80
81     @Mock
82     private SchemaSourceRegistry schemaRegistry;
83     @Mock
84     private DeviceNetconfSchemaProvider schemaProvider;
85
86     @BeforeClass
87     public static final void setupNotification() throws Exception {
88         NOTIFICATION = new NetconfMessage(XmlUtil.readXmlToDocument(
89             NetconfDeviceTest.class.getResourceAsStream("/notification-payload.xml")));
90     }
91
92     @Test
93     public void testNetconfDeviceFailFirstSchemaFailSecondEmpty() throws Exception {
94         final var facade = getFacade();
95         final var listener = getListener();
96
97         final var schemaFactory = getSchemaFactory();
98
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());
104
105         final var device = new NetconfDeviceBuilder()
106             .setReconnectOnSchemasChange(true)
107             .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider(getSchemaRepository(), schemaFactory))
108             .setProcessingExecutor(MoreExecutors.directExecutor())
109             .setId(getId())
110             .setSalFacade(facade)
111             .setBaseSchemaProvider(BASE_SCHEMAS)
112             .build();
113
114         // Monitoring not supported
115         device.onRemoteSessionUp(getSessionCaps(false, TEST_CAPABILITY), listener);
116
117         final var captor = ArgumentCaptor.forClass(Throwable.class);
118         verify(facade, timeout(5000)).onDeviceFailed(captor.capture());
119         assertThat(captor.getValue(), instanceOf(EmptySchemaContextException.class));
120
121         verify(listener, timeout(5000)).close();
122         verify(schemaFactory, times(1)).createEffectiveModelContext(anyCollection());
123     }
124
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));
130         return mock;
131     }
132
133     @Test
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());
139
140         final var device = new NetconfDeviceBuilder()
141             .setReconnectOnSchemasChange(true)
142             .setDeviceSchemaProvider(deviceSchemaProvider)
143             .setProcessingExecutor(MoreExecutors.directExecutor())
144             .setId(getId())
145             .setSalFacade(facade)
146             .setBaseSchemaProvider(BASE_SCHEMAS)
147             .build();
148
149         final var sessionCaps = getSessionCaps(true, TEST_CAPABILITY);
150         device.onRemoteSessionUp(sessionCaps, getListener());
151
152         device.onNotification(NOTIFICATION);
153         device.onNotification(NOTIFICATION);
154         verify(facade, times(0)).onNotification(any(DOMNotification.class));
155
156         // Now enable schema
157         schemaFuture.set(new DeviceNetconfSchema(NetconfDeviceCapabilities.empty(),
158             NetconfToNotificationTest.getNotificationSchemaContext(NetconfDeviceTest.class, false)));
159
160         verify(facade, timeout(10000).times(2)).onNotification(any(DOMNotification.class));
161
162         device.onNotification(NOTIFICATION);
163         verify(facade, times(3)).onNotification(any(DOMNotification.class));
164     }
165
166     @Test
167     public void testNetconfDeviceReconnect() throws Exception {
168         final var facade = getFacade();
169         final var listener = getListener();
170
171         doReturn(RpcResultBuilder.failed().buildFuture()).when(listener).sendRequest(any(), eq(Get.QNAME));
172
173         final var device = new NetconfDeviceBuilder()
174             .setReconnectOnSchemasChange(true)
175             .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider())
176             .setProcessingExecutor(MoreExecutors.directExecutor())
177             .setId(getId())
178             .setSalFacade(facade)
179             .setBaseSchemaProvider(BASE_SCHEMAS)
180             .build();
181         final var sessionCaps = getSessionCaps(true,
182                 TEST_NAMESPACE + "?module=" + TEST_MODULE + "&amp;revision=" + TEST_REVISION);
183         device.onRemoteSessionUp(sessionCaps, listener);
184
185         verify(facade, timeout(5000)).onDeviceConnected(
186                 any(NetconfDeviceSchema.class), any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
187
188         device.onRemoteSessionDown();
189         verify(facade, timeout(5000)).onDeviceDisconnected();
190
191         device.onRemoteSessionUp(sessionCaps, listener);
192
193         verify(facade, timeout(5000).times(2)).onDeviceConnected(
194                 any(NetconfDeviceSchema.class), any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
195     }
196
197     @Test
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());
202
203         final var device = new NetconfDeviceBuilder()
204             .setReconnectOnSchemasChange(true)
205             .setDeviceSchemaProvider(schemaProvider)
206             .setProcessingExecutor(MoreExecutors.directExecutor())
207             .setId(getId())
208             .setSalFacade(facade)
209             .setBaseSchemaProvider(BASE_SCHEMAS)
210             .build();
211         //session up, start schema resolution
212         device.onRemoteSessionUp(getSessionCaps(true,
213             TEST_NAMESPACE + "?module=" + TEST_MODULE + "&amp;revision=" + TEST_REVISION), getListener());
214         //session closed
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));
221     }
222
223     @Test
224     public void testNetconfDeviceReconnectBeforeSchemaSetup() throws Exception {
225         final var facade = getFacade();
226
227         final var schemaContextProviderFactory = mock(EffectiveModelContextFactory.class);
228         final var schemaFuture = SettableFuture.<EffectiveModelContext>create();
229         doReturn(schemaFuture).when(schemaContextProviderFactory).createEffectiveModelContext(anyCollection());
230
231         final var listener = getListener();
232         doReturn(RpcResultBuilder.failed().buildFuture()).when(listener).sendRequest(any(), eq(Get.QNAME));
233
234         final var device = new NetconfDeviceBuilder()
235             .setReconnectOnSchemasChange(true)
236             .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider(getSchemaRepository(),
237                 schemaContextProviderFactory))
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         // session up, start schema resolution
247         device.onRemoteSessionUp(sessionCaps, listener);
248         // session down
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));
260     }
261
262     @Test
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));
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());
386     }
387
388     private DeviceNetconfSchemaProvider mockDeviceNetconfSchemaProvider(final SchemaRepository schemaRepository,
389             final EffectiveModelContextFactory schemaFactory) {
390         return new DefaultDeviceNetconfSchemaProvider(schemaRegistry, schemaRepository, schemaFactory);
391     }
392
393     public RemoteDeviceId getId() {
394         return new RemoteDeviceId("test-D", InetSocketAddress.createUnresolved("localhost", 22));
395     }
396
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);
402         if (addMonitor) {
403             capabilities.add(NetconfState.QNAME.getNamespace().toString());
404         }
405         capabilities.addAll(List.of(additionalCapabilities));
406         return NetconfSessionPreferences.fromStrings(capabilities);
407     }
408
409     public NetconfDeviceCommunicator getListener() throws Exception {
410         return mockCloseableClass(NetconfDeviceCommunicator.class);
411     }
412 }