OpenApi add POST request to device root
[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.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;
23
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;
31 import java.util.Map;
32 import java.util.Set;
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;
63
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;
72
73     private static NetconfMessage NOTIFICATION;
74
75     @Mock
76     private SchemaSourceRegistry schemaRegistry;
77     @Mock
78     private DeviceNetconfSchemaProvider schemaProvider;
79     @Mock
80     private RemoteDeviceHandler facade;
81     @Mock
82     private NetconfDeviceCommunicator listener;
83     @Mock
84     private EffectiveModelContextFactory schemaFactory;
85     @Mock
86     private DeviceNetconfSchemaProvider deviceSchemaProvider;
87
88     @BeforeAll
89     static void setupNotification() throws Exception {
90         NOTIFICATION = new NetconfMessage(XmlUtil.readXmlToDocument(
91             NetconfDeviceTest.class.getResourceAsStream("/notification-payload.xml")));
92     }
93
94     @Test
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());
101
102         final var device = new NetconfDeviceBuilder()
103             .setReconnectOnSchemasChange(true)
104             .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider(getSchemaRepository(), schemaFactory))
105             .setProcessingExecutor(MoreExecutors.directExecutor())
106             .setId(getId())
107             .setSalFacade(facade)
108             .setBaseSchemaProvider(BASE_SCHEMAS)
109             .build();
110
111         // Monitoring not supported
112         device.onRemoteSessionUp(getSessionCaps(false, TEST_CAPABILITY), listener);
113
114         final var captor = ArgumentCaptor.forClass(Throwable.class);
115         verify(facade, timeout(5000)).onDeviceFailed(captor.capture());
116         assertInstanceOf(EmptySchemaContextException.class, captor.getValue());
117
118         verify(listener, timeout(5000)).close();
119         verify(schemaFactory, times(1)).createEffectiveModelContext(anyCollection());
120     }
121
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));
127         return mock;
128     }
129
130     @Test
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());
136
137         final var device = new NetconfDeviceBuilder()
138             .setReconnectOnSchemasChange(true)
139             .setDeviceSchemaProvider(deviceSchemaProvider)
140             .setProcessingExecutor(MoreExecutors.directExecutor())
141             .setId(getId())
142             .setSalFacade(remoteDeviceHandler)
143             .setBaseSchemaProvider(BASE_SCHEMAS)
144             .build();
145
146         final var sessionCaps = getSessionCaps(true, TEST_CAPABILITY);
147         device.onRemoteSessionUp(sessionCaps, listener);
148
149         device.onNotification(NOTIFICATION);
150         device.onNotification(NOTIFICATION);
151         verify(remoteDeviceHandler, times(0)).onNotification(any(DOMNotification.class));
152
153         // Now enable schema
154         schemaFuture.set(new DeviceNetconfSchema(NetconfDeviceCapabilities.empty(),
155             NetconfToNotificationTest.getNotificationSchemaContext(NetconfDeviceTest.class, false)));
156
157         verify(remoteDeviceHandler, timeout(10000).times(2))
158             .onNotification(any(DOMNotification.class));
159
160         device.onNotification(NOTIFICATION);
161         verify(remoteDeviceHandler, times(3)).onNotification(any(DOMNotification.class));
162     }
163
164     private RemoteDeviceHandler mockRemoteDeviceHandler() {
165         doNothing().when(facade).onDeviceConnected(
166             any(NetconfDeviceSchema.class), any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
167         return facade;
168     }
169
170     @Test
171     void testNetconfDeviceReconnect() {
172         doReturn(RpcResultBuilder.failed().buildFuture()).when(listener).sendRequest(any());
173
174         final var device = new NetconfDeviceBuilder()
175             .setReconnectOnSchemasChange(true)
176             .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider())
177             .setProcessingExecutor(MoreExecutors.directExecutor())
178             .setId(getId())
179             .setSalFacade(facade)
180             .setBaseSchemaProvider(BASE_SCHEMAS)
181             .build();
182         final var sessionCaps = getSessionCaps(true,
183                 TEST_NAMESPACE + "?module=" + TEST_MODULE + "&amp;revision=" + TEST_REVISION);
184         device.onRemoteSessionUp(sessionCaps, listener);
185
186         verify(facade, timeout(5000)).onDeviceConnected(
187                 any(NetconfDeviceSchema.class), any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
188
189         device.onRemoteSessionDown();
190         verify(facade, timeout(5000)).onDeviceDisconnected();
191
192         device.onRemoteSessionUp(sessionCaps, listener);
193
194         verify(facade, timeout(5000).times(2)).onDeviceConnected(
195                 any(NetconfDeviceSchema.class), any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
196     }
197
198     @Test
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());
203
204         final var device = new NetconfDeviceBuilder()
205             .setReconnectOnSchemasChange(true)
206             .setDeviceSchemaProvider(schemaProvider)
207             .setProcessingExecutor(MoreExecutors.directExecutor())
208             .setId(getId())
209             .setSalFacade(facade)
210             .setBaseSchemaProvider(BASE_SCHEMAS)
211             .build();
212         //session up, start schema resolution
213         device.onRemoteSessionUp(getSessionCaps(true,
214             TEST_NAMESPACE + "?module=" + TEST_MODULE + "&amp;revision=" + TEST_REVISION),
215             listener);
216         //session closed
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));
223     }
224
225     @Test
226     void testNetconfDeviceReconnectBeforeSchemaSetup() {
227         final var schemaFuture = SettableFuture.<EffectiveModelContext>create();
228         doReturn(schemaFuture).when(schemaFactory).createEffectiveModelContext(anyCollection());
229
230         doReturn(RpcResultBuilder.failed().buildFuture()).when(listener).sendRequest(any());
231
232         final var device = new NetconfDeviceBuilder()
233             .setReconnectOnSchemasChange(true)
234             .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider(getSchemaRepository(),
235                 schemaFactory))
236             .setProcessingExecutor(MoreExecutors.directExecutor())
237             .setId(getId())
238             .setSalFacade(facade)
239             .setBaseSchemaProvider(BASE_SCHEMAS)
240             .build();
241         final var sessionCaps = getSessionCaps(true,
242             TEST_NAMESPACE + "?module=" + TEST_MODULE + "&amp;revision=" + TEST_REVISION);
243
244         // session up, start schema resolution
245         device.onRemoteSessionUp(sessionCaps, listener);
246         // session down
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));
258     }
259
260     @Test
261     void testNetconfDeviceAvailableCapabilitiesBuilding() {
262         doReturn(RpcResultBuilder.failed().buildFuture()).when(listener).sendRequest(any());
263
264         final var netconfSpy = spy(new NetconfDeviceBuilder()
265             .setReconnectOnSchemasChange(true)
266             .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider())
267             .setProcessingExecutor(MoreExecutors.directExecutor())
268             .setId(getId())
269             .setSalFacade(facade)
270             .setBaseSchemaProvider(BASE_SCHEMAS)
271             .build());
272
273         final var sessionCaps = getSessionCaps(true,
274             TEST_NAMESPACE + "?module=" + TEST_MODULE + "&amp;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);
278
279         netconfSpy.onRemoteSessionUp(sessionCaps.replaceModuleCaps(moduleBasedCaps), listener);
280
281         final var argument = ArgumentCaptor.forClass(NetconfDeviceSchema.class);
282         verify(facade, timeout(5000)).onDeviceConnected(argument.capture(),
283             any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
284
285         assertEquals(Set.of(
286             new AvailableCapabilityBuilder()
287                 .setCapability("(test:namespace?revision=2013-07-22)test-module")
288                 .setCapabilityOrigin(CapabilityOrigin.DeviceAdvertised)
289                 .build(),
290             new AvailableCapabilityBuilder()
291                 .setCapability("(test:qname:side:loading)test")
292                 .setCapabilityOrigin(CapabilityOrigin.UserDefined)
293                 .build()), argument.getValue().capabilities().resolvedCapabilities());
294     }
295
296     @Test
297     void testNetconfDeviceNotificationsModelNotPresentWithCapability() {
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,
307                 CapabilityURN.INTERLEAVE), listener);
308
309         final var argument = ArgumentCaptor.forClass(NetconfDeviceSchema.class);
310         verify(facade, timeout(5000)).onDeviceConnected(argument.capture(), any(NetconfSessionPreferences.class),
311                 any(RemoteDeviceServices.class));
312
313         assertEquals(Set.of(
314             new AvailableCapabilityBuilder()
315                 .setCapability("(urn:ietf:params:xml:ns:yang:ietf-yang-types?revision=2013-07-15)ietf-yang-types")
316                 .build(),
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());
320     }
321
322     @Test
323     void testNetconfDeviceNotificationsModelNotPresentWithoutInterleaveCapability() {
324         final var netconfSpy = spy(new NetconfDeviceBuilder()
325             .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider())
326             .setProcessingExecutor(MoreExecutors.directExecutor())
327             .setId(getId())
328             .setSalFacade(facade)
329             .setBaseSchemaProvider(BASE_SCHEMAS)
330             .build());
331
332         final var sessionCaps = getSessionCaps(false,
333             TEST_NAMESPACE + "?module=" + TEST_MODULE + "&amp;revision=" + TEST_REVISION, CapabilityURN.NOTIFICATION);
334
335         netconfSpy.onRemoteSessionUp(sessionCaps, listener);
336
337         final var argument = ArgumentCaptor.forClass(NetconfDeviceSchema.class);
338         verify(facade, timeout(5000)).onDeviceConnected(argument.capture(), any(NetconfSessionPreferences.class),
339             any(RemoteDeviceServices.class));
340
341         // Notification schema was not added when there is no Interleave capability
342         assertEquals(Set.of(
343             new AvailableCapabilityBuilder()
344                 .setCapability("(test:namespace?revision=2013-07-22)test-module")
345                 .setCapabilityOrigin(CapabilityOrigin.DeviceAdvertised)
346                 .build()), argument.getValue().capabilities().resolvedCapabilities());
347     }
348
349     @Test
350     void testNetconfDeviceNotificationsModelIsPresent() {
351         final var netconfSpy = spy(new NetconfDeviceBuilder()
352             .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider())
353             .setProcessingExecutor(MoreExecutors.directExecutor())
354             .setId(getId())
355             .setSalFacade(facade)
356             .setBaseSchemaProvider(BASE_SCHEMAS)
357             .build());
358
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
364             )), listener);
365
366         final var argument = ArgumentCaptor.forClass(NetconfDeviceSchema.class);
367         verify(facade, timeout(5000)).onDeviceConnected(argument.capture(), any(NetconfSessionPreferences.class),
368                 any(RemoteDeviceServices.class));
369
370         assertEquals(Set.of(
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)
374                 .build(),
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());
379     }
380
381     private EffectiveModelContextFactory getSchemaFactory() {
382         doReturn(Futures.immediateFuture(SCHEMA_CONTEXT))
383                 .when(schemaFactory).createEffectiveModelContext(anyCollection());
384         return schemaFactory;
385     }
386
387     private DeviceNetconfSchemaProvider mockDeviceNetconfSchemaProvider() {
388         return mockDeviceNetconfSchemaProvider(getSchemaRepository(), getSchemaFactory());
389     }
390
391     private DeviceNetconfSchemaProvider mockDeviceNetconfSchemaProvider(final SchemaRepository schemaRepository,
392             final EffectiveModelContextFactory modelContextFactory) {
393         return new DefaultDeviceNetconfSchemaProvider(schemaRegistry, schemaRepository, modelContextFactory);
394     }
395
396     private static RemoteDeviceId getId() {
397         return new RemoteDeviceId("test-D", InetSocketAddress.createUnresolved("localhost", 22));
398     }
399
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);
405         if (addMonitor) {
406             capabilities.add(NetconfState.QNAME.getNamespace().toString());
407         }
408         capabilities.addAll(List.of(additionalCapabilities));
409         return NetconfSessionPreferences.fromStrings(capabilities);
410     }
411 }