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.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;
68 @RunWith(MockitoJUnitRunner.StrictStubs.class)
69 public class PatchDataTransactionUtilTest {
70 private static final String PATH_FOR_NEW_SCHEMA_CONTEXT = "/jukebox";
72 private DOMDataTreeReadWriteTransaction rwTransaction;
74 private DOMDataBroker mockDataBroker;
76 private NetconfDataTreeService netconfService;
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;
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,
100 /* instance identifier for accessing container node "player" */
101 this.instanceIdContainer = YangInstanceIdentifier.builder()
103 .node(containerPlayerQName)
106 /* instance identifier for accessing leaf node "gap" */
107 this.instanceIdCreateAndDelete = instanceIdContainer.node(leafGapQName);
109 /* values that are used for creating leaf for testPatchDataCreateAndDelete test */
110 final LeafNode<?> buildGapLeaf = Builders.leafBuilder()
111 .withNodeIdentifier(new NodeIdentifier(leafGapQName))
115 final ContainerNode buildPlayerContainer = Builders.containerBuilder()
116 .withNodeIdentifier(new NodeIdentifier(containerPlayerQName))
117 .withChild(buildGapLeaf)
120 this.buildBaseContainerForTests = Builders.containerBuilder()
121 .withNodeIdentifier(new NodeIdentifier(baseQName))
122 .withChild(buildPlayerContainer)
125 this.targetNodeForCreateAndDelete = YangInstanceIdentifier.builder(this.instanceIdCreateAndDelete)
126 .node(containerPlayerQName)
130 /* instance identifier for accessing leaf node "name" in list "artist" */
131 this.instanceIdMerge = YangInstanceIdentifier.builder()
133 .node(containerLibraryQName)
134 .node(listArtistQName)
135 .nodeWithKey(listArtistQName, QName.create(listArtistQName, "name"), "name of artist")
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")
145 final LeafNode<Object> contentDescription = Builders.leafBuilder()
146 .withNodeIdentifier(new NodeIdentifier(QName.create(baseQName, "description")))
147 .withValue("description of artist")
150 final MapEntryNode mapEntryNode = Builders.mapEntryBuilder()
151 .withNodeIdentifier(nodeWithKey)
152 .withChild(contentName)
153 .withChild(contentDescription)
156 this.buildArtistList = Builders.mapBuilder()
157 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(listArtistQName))
158 .withChild(mapEntryNode)
161 this.targetNodeMerge = YangInstanceIdentifier.builder()
163 .node(containerLibraryQName)
164 .node(listArtistQName)
165 .nodeWithKey(listArtistQName, leafNameQName, "name of artist")
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(),
177 doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(this.netconfService).replace(any(), any(),
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<>();
189 entities.add(entityReplace);
190 entities.add(entityMerge);
191 entities.add(entityRemove);
193 final InstanceIdentifierContext<? extends SchemaNode> iidContext =
194 new InstanceIdentifierContext<>(this.instanceIdMerge, null, null, this.refSchemaCtx);
195 final PatchContext patchContext = new PatchContext(iidContext, entities, "patchRMRm");
197 doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(this.netconfService)
198 .remove(LogicalDatastoreType.CONFIGURATION, targetNodeMerge);
200 patch(patchContext, new MdsalRestconfStrategy(mockDataBroker), false);
201 patch(patchContext, new NetconfRestconfStrategy(netconfService), false);
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,
213 doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(this.netconfService)
214 .delete(LogicalDatastoreType.CONFIGURATION, this.targetNodeForCreateAndDelete);
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<>();
222 entities.add(entityCreate);
223 entities.add(entityDelete);
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);
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));
242 doReturn(ret).when(this.netconfService).commit();
243 doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(this.netconfService)
244 .delete(LogicalDatastoreType.CONFIGURATION, this.targetNodeForCreateAndDelete);
246 final PatchEntity entityDelete = new PatchEntity("edit", DELETE, this.targetNodeForCreateAndDelete);
247 final List<PatchEntity> entities = new ArrayList<>();
249 entities.add(entityDelete);
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));
259 public void testPatchMergePutContainer() {
260 final PatchEntity entityMerge =
261 new PatchEntity("edit1", MERGE, this.instanceIdContainer, this.buildBaseContainerForTests);
262 final List<PatchEntity> entities = new ArrayList<>();
264 entities.add(entityMerge);
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);
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()) {
279 assertTrue("Edit " + entity.getEditId() + " failed", entity.isOk());
281 assertTrue(entity.isOk());
284 assertTrue(patchStatusContext.isOk());
287 private void deleteMdsal(final PatchContext patchContext, final RestconfStrategy strategy) {
288 final PatchStatusContext patchStatusContext =
289 PatchDataTransactionUtil.patchData(patchContext, strategy, this.refSchemaCtx);
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());
298 private void deleteNetconf(final PatchContext patchContext, final RestconfStrategy strategy) {
299 final PatchStatusContext patchStatusContext =
300 PatchDataTransactionUtil.patchData(patchContext, strategy, this.refSchemaCtx);
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());