215d8097481c4868bc13d1f8d94071a01807031d
[netconf.git] /
1 /*
2  * Copyright (c) 2023 PANTHEON.tech, s.r.o. 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.restconf.nb.rfc8040.rests.services.impl;
9
10 import static org.junit.jupiter.api.Assertions.assertEquals;
11 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
12 import static org.junit.jupiter.api.Assertions.assertNotNull;
13 import static org.junit.jupiter.api.Assertions.assertNull;
14 import static org.mockito.ArgumentMatchers.any;
15 import static org.mockito.Mockito.doReturn;
16 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFluentFuture;
17
18 import java.util.List;
19 import java.util.Optional;
20 import javax.ws.rs.container.AsyncResponse;
21 import javax.ws.rs.core.MultivaluedHashMap;
22 import javax.ws.rs.core.Response;
23 import javax.ws.rs.core.UriInfo;
24 import org.junit.jupiter.api.BeforeEach;
25 import org.junit.jupiter.api.Test;
26 import org.junit.jupiter.api.extension.ExtendWith;
27 import org.mockito.ArgumentCaptor;
28 import org.mockito.Captor;
29 import org.mockito.Mock;
30 import org.mockito.junit.jupiter.MockitoExtension;
31 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
32 import org.opendaylight.mdsal.dom.api.DOMActionService;
33 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
34 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
35 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
36 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
37 import org.opendaylight.mdsal.dom.api.DOMRpcService;
38 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
39 import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
40 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
41 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
42 import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest;
43 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindContext;
44 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
45 import org.opendaylight.yangtools.yang.common.ErrorTag;
46 import org.opendaylight.yangtools.yang.common.ErrorType;
47 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
48 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
49 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
50 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
51 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
52 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
53
54 @ExtendWith(MockitoExtension.class)
55 class RestconfDataGetTest extends AbstractJukeboxTest {
56     private static final NodeIdentifier PLAYLIST_NID = new NodeIdentifier(PLAYLIST_QNAME);
57     private static final NodeIdentifier LIBRARY_NID = new NodeIdentifier(LIBRARY_QNAME);
58
59     // config contains one child the same as in operational and one additional
60     private static final ContainerNode CONFIG_JUKEBOX = Builders.containerBuilder()
61             .withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME))
62             .withChild(CONT_PLAYER)
63             .withChild(Builders.containerBuilder().withNodeIdentifier(LIBRARY_NID).build())
64             .build();
65     // operational contains one child the same as in config and one additional
66     private static final ContainerNode OPER_JUKEBOX = Builders.containerBuilder()
67             .withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME))
68             .withChild(CONT_PLAYER)
69             .withChild(Builders.mapBuilder().withNodeIdentifier(PLAYLIST_NID).build())
70             .build();
71
72     @Mock
73     private UriInfo uriInfo;
74     @Mock
75     private AsyncResponse asyncResponse;
76     @Mock
77     private DOMDataBroker dataBroker;
78     @Mock
79     private DOMActionService actionService;
80     @Mock
81     private DOMRpcService rpcService;
82     @Mock
83     private DOMMountPointService mountPointService;
84     @Mock
85     private DOMDataTreeReadTransaction tx;
86     @Mock
87     private DOMMountPoint mountPoint;
88     @Captor
89     private ArgumentCaptor<Response> responseCaptor;
90     @Captor
91     private ArgumentCaptor<RestconfDocumentedException> exceptionCaptor;
92
93     private RestconfImpl restconf;
94
95     @BeforeEach
96     void beforeEach() {
97         restconf = new RestconfImpl(new MdsalRestconfServer(() -> DatabindContext.ofModel(JUKEBOX_SCHEMA), dataBroker,
98             rpcService, actionService, mountPointService));
99         doReturn(tx).when(dataBroker).newReadOnlyTransaction();
100     }
101
102     @Test
103     void testReadData() {
104         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
105         doReturn(immediateFluentFuture(Optional.of(EMPTY_JUKEBOX))).when(tx)
106                 .read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
107         doReturn(immediateFluentFuture(Optional.empty()))
108                 .when(tx).read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
109
110         doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
111         restconf.dataGET("example-jukebox:jukebox", uriInfo, asyncResponse);
112         final var response = responseCaptor.getValue();
113         assertEquals(200, response.getStatus());
114         assertEquals(EMPTY_JUKEBOX, ((NormalizedNodePayload) response.getEntity()).data());
115     }
116
117     @Test
118     void testReadRootData() {
119         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
120         doReturn(immediateFluentFuture(Optional.of(wrapNodeByDataRootContainer(CONFIG_JUKEBOX))))
121                 .when(tx)
122                 .read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of());
123         doReturn(immediateFluentFuture(Optional.of(wrapNodeByDataRootContainer(OPER_JUKEBOX))))
124                 .when(tx)
125                 .read(LogicalDatastoreType.OPERATIONAL, YangInstanceIdentifier.of());
126
127         doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
128         restconf.dataGET(uriInfo, asyncResponse);
129         final var response = responseCaptor.getValue();
130         assertEquals(200, response.getStatus());
131
132         final var data = assertInstanceOf(ContainerNode.class, ((NormalizedNodePayload) response.getEntity()).data());
133         final var rootNodes = data.body();
134         assertEquals(1, rootNodes.size());
135         final var allDataChildren = assertInstanceOf(ContainerNode.class, rootNodes.iterator().next()).body();
136         assertEquals(3, allDataChildren.size());
137     }
138
139     private static ContainerNode wrapNodeByDataRootContainer(final DataContainerChild data) {
140         return Builders.containerBuilder()
141             .withNodeIdentifier(NodeIdentifier.create(SchemaContext.NAME))
142             .withChild(data)
143             .build();
144     }
145
146     /**
147      * Test read data from mount point when both {@link LogicalDatastoreType#CONFIGURATION} and
148      * {@link LogicalDatastoreType#OPERATIONAL} contains the same data and some additional data to be merged.
149      */
150     @Test
151     void testReadDataMountPoint() {
152         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
153         doReturn(immediateFluentFuture(Optional.of(CONFIG_JUKEBOX))).when(tx)
154                 .read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
155         doReturn(immediateFluentFuture(Optional.of(OPER_JUKEBOX))).when(tx)
156                 .read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
157
158         doReturn(Optional.of(mountPoint)).when(mountPointService)
159             .getMountPoint(any(YangInstanceIdentifier.class));
160         doReturn(Optional.of(FixedDOMSchemaService.of(JUKEBOX_SCHEMA))).when(mountPoint)
161             .getService(DOMSchemaService.class);
162         doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
163         doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
164         doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
165
166         doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
167         restconf.dataGET("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox", uriInfo, asyncResponse);
168         final var response = responseCaptor.getValue();
169         assertEquals(200, response.getStatus());
170
171         // response must contain all child nodes from config and operational containers merged in one container
172         final var data = assertInstanceOf(ContainerNode.class, ((NormalizedNodePayload) response.getEntity()).data());
173         assertEquals(3, data.size());
174         assertNotNull(data.childByArg(CONT_PLAYER.name()));
175         assertNotNull(data.childByArg(LIBRARY_NID));
176         assertNotNull(data.childByArg(PLAYLIST_NID));
177     }
178
179     @Test
180     void testReadDataNoData() {
181         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
182         doReturn(immediateFluentFuture(Optional.empty()))
183                 .when(tx).read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
184         doReturn(immediateFluentFuture(Optional.empty()))
185                 .when(tx).read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
186
187         doReturn(true).when(asyncResponse).resume(exceptionCaptor.capture());
188         restconf.dataGET("example-jukebox:jukebox", uriInfo, asyncResponse);
189
190         final var errors = exceptionCaptor.getValue().getErrors();
191         assertEquals(1, errors.size());
192         final var error = errors.get(0);
193         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
194         assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
195         assertEquals("Request could not be completed because the relevant data model content does not exist",
196             error.getErrorMessage());
197     }
198
199     /**
200      * Read data from config datastore according to content parameter.
201      */
202     @Test
203     void testReadDataConfigTest() {
204         final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
205         parameters.put("content", List.of("config"));
206
207         doReturn(parameters).when(uriInfo).getQueryParameters();
208         doReturn(immediateFluentFuture(Optional.of(CONFIG_JUKEBOX))).when(tx)
209                 .read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
210
211         doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
212         restconf.dataGET("example-jukebox:jukebox", uriInfo, asyncResponse);
213         final var response = responseCaptor.getValue();
214         assertEquals(200, response.getStatus());
215
216         // response must contain only config data
217         final var data = assertInstanceOf(ContainerNode.class, ((NormalizedNodePayload) response.getEntity()).data());
218
219         // config data present
220         assertNotNull(data.childByArg(CONT_PLAYER.name()));
221         assertNotNull(data.childByArg(LIBRARY_NID));
222
223         // state data absent
224         assertNull(data.childByArg(PLAYLIST_NID));
225     }
226
227     /**
228      * Read data from operational datastore according to content parameter.
229      */
230     @Test
231     void testReadDataOperationalTest() {
232         final var parameters = new MultivaluedHashMap<String, String>();
233         parameters.putSingle("content", "nonconfig");
234
235         doReturn(parameters).when(uriInfo).getQueryParameters();
236         doReturn(immediateFluentFuture(Optional.of(OPER_JUKEBOX))).when(tx)
237                 .read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
238
239         doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
240         restconf.dataGET("example-jukebox:jukebox", uriInfo, asyncResponse);
241         final var response = responseCaptor.getValue();
242
243         assertEquals(200, response.getStatus());
244
245         // response must contain only operational data
246         final var data = assertInstanceOf(ContainerNode.class, ((NormalizedNodePayload) response.getEntity()).data());
247
248         // state data present
249         assertNotNull(data.childByArg(CONT_PLAYER.name()));
250         assertNotNull(data.childByArg(PLAYLIST_NID));
251
252         // config data absent
253         assertNull(data.childByArg(LIBRARY_NID));
254     }
255 }