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