2 * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.restconf.nb.rfc8040.rests.utils;
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;
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;
66 @RunWith(MockitoJUnitRunner.StrictStubs.class)
67 public class PatchDataTransactionUtilTest {
68 private static final String PATH_FOR_NEW_SCHEMA_CONTEXT = "/jukebox";
70 private DOMDataTreeReadWriteTransaction rwTransaction;
72 private DOMDataBroker mockDataBroker;
74 private NetconfDataTreeService netconfService;
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;
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,
98 /* instance identifier for accessing container node "player" */
99 instanceIdContainer = YangInstanceIdentifier.builder()
101 .node(containerPlayerQName)
104 /* instance identifier for accessing leaf node "gap" */
105 instanceIdCreateAndDelete = instanceIdContainer.node(leafGapQName);
107 /* values that are used for creating leaf for testPatchDataCreateAndDelete test */
108 final LeafNode<?> buildGapLeaf = Builders.leafBuilder()
109 .withNodeIdentifier(new NodeIdentifier(leafGapQName))
113 final ContainerNode buildPlayerContainer = Builders.containerBuilder()
114 .withNodeIdentifier(new NodeIdentifier(containerPlayerQName))
115 .withChild(buildGapLeaf)
118 buildBaseContainerForTests = Builders.containerBuilder()
119 .withNodeIdentifier(new NodeIdentifier(baseQName))
120 .withChild(buildPlayerContainer)
123 targetNodeForCreateAndDelete = YangInstanceIdentifier.builder(instanceIdCreateAndDelete)
124 .node(containerPlayerQName)
128 /* instance identifier for accessing leaf node "name" in list "artist" */
129 instanceIdMerge = YangInstanceIdentifier.builder()
131 .node(containerLibraryQName)
132 .node(listArtistQName)
133 .nodeWithKey(listArtistQName, QName.create(listArtistQName, "name"), "name of artist")
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")
143 final LeafNode<Object> contentDescription = Builders.leafBuilder()
144 .withNodeIdentifier(new NodeIdentifier(QName.create(baseQName, "description")))
145 .withValue("description of artist")
148 final MapEntryNode mapEntryNode = Builders.mapEntryBuilder()
149 .withNodeIdentifier(nodeWithKey)
150 .withChild(contentName)
151 .withChild(contentDescription)
154 buildArtistList = Builders.mapBuilder()
155 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(listArtistQName))
156 .withChild(mapEntryNode)
159 targetNodeMerge = YangInstanceIdentifier.builder()
161 .node(containerLibraryQName)
162 .node(listArtistQName)
163 .nodeWithKey(listArtistQName, leafNameQName, "name of artist")
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(),
175 doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).replace(any(), any(),
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<>();
187 entities.add(entityReplace);
188 entities.add(entityMerge);
189 entities.add(entityRemove);
191 final InstanceIdentifierContext iidContext =
192 InstanceIdentifierContext.ofLocalPath(refSchemaCtx, instanceIdMerge);
193 final PatchContext patchContext = new PatchContext(iidContext, entities, "patchRMRm");
195 doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
196 .remove(LogicalDatastoreType.CONFIGURATION, targetNodeMerge);
198 patch(patchContext, new MdsalRestconfStrategy(mockDataBroker), false);
199 patch(patchContext, new NetconfRestconfStrategy(netconfService), false);
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,
211 doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
212 .delete(LogicalDatastoreType.CONFIGURATION, targetNodeForCreateAndDelete);
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<>();
220 entities.add(entityCreate);
221 entities.add(entityDelete);
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);
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));
240 doReturn(ret).when(netconfService).commit();
241 doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
242 .delete(LogicalDatastoreType.CONFIGURATION, targetNodeForCreateAndDelete);
244 final PatchEntity entityDelete = new PatchEntity("edit", DELETE, targetNodeForCreateAndDelete);
245 final List<PatchEntity> entities = new ArrayList<>();
247 entities.add(entityDelete);
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));
256 public void testPatchMergePutContainer() {
257 final PatchEntity entityMerge =
258 new PatchEntity("edit1", MERGE, instanceIdContainer, buildBaseContainerForTests);
259 final List<PatchEntity> entities = new ArrayList<>();
261 entities.add(entityMerge);
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);
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()) {
276 assertTrue("Edit " + entity.getEditId() + " failed", entity.isOk());
278 assertTrue(entity.isOk());
281 assertTrue(patchStatusContext.isOk());
284 private void deleteMdsal(final PatchContext patchContext, final RestconfStrategy strategy) {
285 final PatchStatusContext patchStatusContext =
286 PatchDataTransactionUtil.patchData(patchContext, strategy, refSchemaCtx);
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());
295 private void deleteNetconf(final PatchContext patchContext, final RestconfStrategy strategy) {
296 final PatchStatusContext patchStatusContext =
297 PatchDataTransactionUtil.patchData(patchContext, strategy, refSchemaCtx);
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());