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