Rename restconf-nb-rfc8040 to restconf-nb
[netconf.git] / restconf / restconf-nb / src / test / java / org / opendaylight / restconf / nb / rfc8040 / rests / utils / PatchDataTransactionUtilTest.java
1 /*
2  * Copyright (c) 2016 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.restconf.nb.rfc8040.rests.utils;
9
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.assertFalse;
12 import static org.junit.Assert.assertTrue;
13 import static org.mockito.ArgumentMatchers.any;
14 import static org.mockito.Mockito.doReturn;
15 import static org.opendaylight.restconf.common.patch.PatchEditOperation.CREATE;
16 import static org.opendaylight.restconf.common.patch.PatchEditOperation.DELETE;
17 import static org.opendaylight.restconf.common.patch.PatchEditOperation.MERGE;
18 import static org.opendaylight.restconf.common.patch.PatchEditOperation.REMOVE;
19 import static org.opendaylight.restconf.common.patch.PatchEditOperation.REPLACE;
20 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFalseFluentFuture;
21 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateTrueFluentFuture;
22
23 import com.google.common.util.concurrent.Futures;
24 import com.google.common.util.concurrent.SettableFuture;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.Optional;
28 import org.junit.Before;
29 import org.junit.Test;
30 import org.junit.runner.RunWith;
31 import org.mockito.Mock;
32 import org.mockito.junit.MockitoJUnitRunner;
33 import org.opendaylight.mdsal.common.api.CommitInfo;
34 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
35 import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
36 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
37 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
38 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
39 import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult;
40 import org.opendaylight.netconf.api.NetconfDocumentedException;
41 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
42 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
43 import org.opendaylight.restconf.common.patch.PatchContext;
44 import org.opendaylight.restconf.common.patch.PatchEntity;
45 import org.opendaylight.restconf.common.patch.PatchStatusContext;
46 import org.opendaylight.restconf.common.patch.PatchStatusEntity;
47 import org.opendaylight.restconf.nb.rfc8040.TestRestconfUtils;
48 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy;
49 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.NetconfRestconfStrategy;
50 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
51 import org.opendaylight.yangtools.yang.common.ErrorSeverity;
52 import org.opendaylight.yangtools.yang.common.ErrorTag;
53 import org.opendaylight.yangtools.yang.common.ErrorType;
54 import org.opendaylight.yangtools.yang.common.QName;
55 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
56 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
57 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
58 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
59 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
60 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
61 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
62 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
63 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
64 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
65
66 @RunWith(MockitoJUnitRunner.StrictStubs.class)
67 public class PatchDataTransactionUtilTest {
68     private static final String PATH_FOR_NEW_SCHEMA_CONTEXT = "/jukebox";
69     @Mock
70     private DOMDataTreeReadWriteTransaction rwTransaction;
71     @Mock
72     private DOMDataBroker mockDataBroker;
73     @Mock
74     private NetconfDataTreeService netconfService;
75
76     private EffectiveModelContext refSchemaCtx;
77     private YangInstanceIdentifier instanceIdContainer;
78     private YangInstanceIdentifier instanceIdCreateAndDelete;
79     private YangInstanceIdentifier instanceIdMerge;
80     private ContainerNode buildBaseContainerForTests;
81     private YangInstanceIdentifier targetNodeForCreateAndDelete;
82     private YangInstanceIdentifier targetNodeMerge;
83     private MapNode buildArtistList;
84
85     @Before
86     public void setUp() throws Exception {
87         refSchemaCtx = YangParserTestUtils.parseYangFiles(
88             TestRestconfUtils.loadFiles(PATH_FOR_NEW_SCHEMA_CONTEXT));
89         final QName baseQName = QName.create("http://example.com/ns/example-jukebox", "2015-04-04", "jukebox");
90         final QName containerPlayerQName = QName.create(baseQName, "player");
91         final QName leafGapQName = QName.create(baseQName, "gap");
92         final QName containerLibraryQName = QName.create(baseQName, "library");
93         final QName listArtistQName = QName.create(baseQName, "artist");
94         final QName leafNameQName = QName.create(baseQName, "name");
95         final NodeIdentifierWithPredicates nodeWithKey = NodeIdentifierWithPredicates.of(listArtistQName, leafNameQName,
96             "name of artist");
97
98         /* instance identifier for accessing container node "player" */
99         instanceIdContainer = YangInstanceIdentifier.builder()
100                 .node(baseQName)
101                 .node(containerPlayerQName)
102                 .build();
103
104         /* instance identifier for accessing leaf node "gap" */
105         instanceIdCreateAndDelete = instanceIdContainer.node(leafGapQName);
106
107         /* values that are used for creating leaf for testPatchDataCreateAndDelete test */
108         final LeafNode<?> buildGapLeaf = Builders.leafBuilder()
109                 .withNodeIdentifier(new NodeIdentifier(leafGapQName))
110                 .withValue(0.2)
111                 .build();
112
113         final ContainerNode buildPlayerContainer = Builders.containerBuilder()
114                 .withNodeIdentifier(new NodeIdentifier(containerPlayerQName))
115                 .withChild(buildGapLeaf)
116                 .build();
117
118         buildBaseContainerForTests = Builders.containerBuilder()
119                 .withNodeIdentifier(new NodeIdentifier(baseQName))
120                 .withChild(buildPlayerContainer)
121                 .build();
122
123         targetNodeForCreateAndDelete = YangInstanceIdentifier.builder(instanceIdCreateAndDelete)
124                 .node(containerPlayerQName)
125                 .node(leafGapQName)
126                 .build();
127
128         /* instance identifier for accessing leaf node "name" in list "artist" */
129         instanceIdMerge = YangInstanceIdentifier.builder()
130                 .node(baseQName)
131                 .node(containerLibraryQName)
132                 .node(listArtistQName)
133                 .nodeWithKey(listArtistQName, QName.create(listArtistQName, "name"), "name of artist")
134                 .node(leafNameQName)
135                 .build();
136
137         /* values that are used for creating leaf for testPatchDataReplaceMergeAndRemove test */
138         final LeafNode<Object> contentName = Builders.leafBuilder()
139                 .withNodeIdentifier(new NodeIdentifier(QName.create(baseQName, "name")))
140                 .withValue("name of artist")
141                 .build();
142
143         final LeafNode<Object> contentDescription = Builders.leafBuilder()
144                 .withNodeIdentifier(new NodeIdentifier(QName.create(baseQName, "description")))
145                 .withValue("description of artist")
146                 .build();
147
148         final MapEntryNode mapEntryNode = Builders.mapEntryBuilder()
149                 .withNodeIdentifier(nodeWithKey)
150                 .withChild(contentName)
151                 .withChild(contentDescription)
152                 .build();
153
154         buildArtistList = Builders.mapBuilder()
155                 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(listArtistQName))
156                 .withChild(mapEntryNode)
157                 .build();
158
159         targetNodeMerge = YangInstanceIdentifier.builder()
160                 .node(baseQName)
161                 .node(containerLibraryQName)
162                 .node(listArtistQName)
163                 .nodeWithKey(listArtistQName, leafNameQName, "name of artist")
164                 .build();
165
166         /* Mocks */
167         doReturn(rwTransaction).when(mockDataBroker).newReadWriteTransaction();
168         doReturn(CommitInfo.emptyFluentFuture()).when(rwTransaction).commit();
169         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).commit();
170         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).discardChanges();
171         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).unlock();
172         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).lock();
173         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).merge(any(), any(),
174             any(), any());
175         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).replace(any(), any(),
176             any(), any());
177     }
178
179     @Test
180     public void testPatchDataReplaceMergeAndRemove() {
181         final PatchEntity entityReplace =
182                 new PatchEntity("edit1", REPLACE, targetNodeMerge, buildArtistList);
183         final PatchEntity entityMerge = new PatchEntity("edit2", MERGE, targetNodeMerge, buildArtistList);
184         final PatchEntity entityRemove = new PatchEntity("edit3", REMOVE, targetNodeMerge);
185         final List<PatchEntity> entities = new ArrayList<>();
186
187         entities.add(entityReplace);
188         entities.add(entityMerge);
189         entities.add(entityRemove);
190
191         final InstanceIdentifierContext iidContext =
192             InstanceIdentifierContext.ofLocalPath(refSchemaCtx, instanceIdMerge);
193         final PatchContext patchContext = new PatchContext(iidContext, entities, "patchRMRm");
194
195         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
196             .remove(LogicalDatastoreType.CONFIGURATION, targetNodeMerge);
197
198         patch(patchContext, new MdsalRestconfStrategy(mockDataBroker), false);
199         patch(patchContext, new NetconfRestconfStrategy(netconfService), false);
200     }
201
202     @Test
203     public void testPatchDataCreateAndDelete() {
204         doReturn(immediateFalseFluentFuture()).when(rwTransaction).exists(LogicalDatastoreType.CONFIGURATION,
205             instanceIdContainer);
206         doReturn(immediateTrueFluentFuture()).when(rwTransaction).exists(LogicalDatastoreType.CONFIGURATION,
207             targetNodeForCreateAndDelete);
208         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
209             .create(LogicalDatastoreType.CONFIGURATION, instanceIdContainer, buildBaseContainerForTests,
210                 Optional.empty());
211         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
212             .delete(LogicalDatastoreType.CONFIGURATION, targetNodeForCreateAndDelete);
213
214         final PatchEntity entityCreate =
215                 new PatchEntity("edit1", CREATE, instanceIdContainer, buildBaseContainerForTests);
216         final PatchEntity entityDelete =
217                 new PatchEntity("edit2", DELETE, targetNodeForCreateAndDelete);
218         final List<PatchEntity> entities = new ArrayList<>();
219
220         entities.add(entityCreate);
221         entities.add(entityDelete);
222
223         final InstanceIdentifierContext iidContext =
224             InstanceIdentifierContext.ofLocalPath(refSchemaCtx, instanceIdCreateAndDelete);
225         final PatchContext patchContext = new PatchContext(iidContext, entities, "patchCD");
226         patch(patchContext, new MdsalRestconfStrategy(mockDataBroker), true);
227         patch(patchContext, new NetconfRestconfStrategy(netconfService), true);
228     }
229
230     @Test
231     public void deleteNonexistentDataTest() {
232         doReturn(immediateFalseFluentFuture()).when(rwTransaction).exists(LogicalDatastoreType.CONFIGURATION,
233             targetNodeForCreateAndDelete);
234         final NetconfDocumentedException exception = new NetconfDocumentedException("id",
235             ErrorType.RPC, ErrorTag.DATA_MISSING, ErrorSeverity.ERROR);
236         final SettableFuture<? extends DOMRpcResult> ret = SettableFuture.create();
237         ret.setException(new TransactionCommitFailedException(
238             String.format("Commit of transaction %s failed", this), exception));
239
240         doReturn(ret).when(netconfService).commit();
241         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
242             .delete(LogicalDatastoreType.CONFIGURATION, targetNodeForCreateAndDelete);
243
244         final PatchEntity entityDelete = new PatchEntity("edit", DELETE, targetNodeForCreateAndDelete);
245         final List<PatchEntity> entities = new ArrayList<>();
246
247         entities.add(entityDelete);
248
249         final PatchContext patchContext = new PatchContext(
250             InstanceIdentifierContext.ofLocalPath(refSchemaCtx, instanceIdCreateAndDelete), entities, "patchD");
251         deleteMdsal(patchContext, new MdsalRestconfStrategy(mockDataBroker));
252         deleteNetconf(patchContext, new NetconfRestconfStrategy(netconfService));
253     }
254
255     @Test
256     public void testPatchMergePutContainer() {
257         final PatchEntity entityMerge =
258                 new PatchEntity("edit1", MERGE, instanceIdContainer, buildBaseContainerForTests);
259         final List<PatchEntity> entities = new ArrayList<>();
260
261         entities.add(entityMerge);
262
263         final InstanceIdentifierContext iidContext =
264             InstanceIdentifierContext.ofLocalPath(refSchemaCtx, instanceIdCreateAndDelete);
265         final PatchContext patchContext = new PatchContext(iidContext, entities, "patchM");
266         patch(patchContext, new MdsalRestconfStrategy(mockDataBroker), false);
267         patch(patchContext, new NetconfRestconfStrategy(netconfService), false);
268     }
269
270     private void patch(final PatchContext patchContext, final RestconfStrategy strategy,
271                        final boolean failed) {
272         final PatchStatusContext patchStatusContext =
273                 PatchDataTransactionUtil.patchData(patchContext, strategy, refSchemaCtx);
274         for (final PatchStatusEntity entity : patchStatusContext.getEditCollection()) {
275             if (failed) {
276                 assertTrue("Edit " + entity.getEditId() + " failed", entity.isOk());
277             } else {
278                 assertTrue(entity.isOk());
279             }
280         }
281         assertTrue(patchStatusContext.isOk());
282     }
283
284     private void deleteMdsal(final PatchContext patchContext, final RestconfStrategy strategy) {
285         final PatchStatusContext patchStatusContext =
286                 PatchDataTransactionUtil.patchData(patchContext, strategy, refSchemaCtx);
287
288         assertFalse(patchStatusContext.isOk());
289         assertEquals(ErrorType.PROTOCOL,
290                 patchStatusContext.getEditCollection().get(0).getEditErrors().get(0).getErrorType());
291         assertEquals(ErrorTag.DATA_MISSING,
292                 patchStatusContext.getEditCollection().get(0).getEditErrors().get(0).getErrorTag());
293     }
294
295     private void deleteNetconf(final PatchContext patchContext, final RestconfStrategy strategy) {
296         final PatchStatusContext patchStatusContext =
297             PatchDataTransactionUtil.patchData(patchContext, strategy, refSchemaCtx);
298
299         assertFalse(patchStatusContext.isOk());
300         assertEquals(ErrorType.PROTOCOL,
301             patchStatusContext.getGlobalErrors().get(0).getErrorType());
302         assertEquals(ErrorTag.DATA_MISSING,
303             patchStatusContext.getGlobalErrors().get(0).getErrorTag());
304     }
305 }