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