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.mockito.Mockito.mock;
16 import static org.mockito.MockitoAnnotations.initMocks;
17 import static org.opendaylight.restconf.common.patch.PatchEditOperation.CREATE;
18 import static org.opendaylight.restconf.common.patch.PatchEditOperation.DELETE;
19 import static org.opendaylight.restconf.common.patch.PatchEditOperation.MERGE;
20 import static org.opendaylight.restconf.common.patch.PatchEditOperation.REMOVE;
21 import static org.opendaylight.restconf.common.patch.PatchEditOperation.REPLACE;
22 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFalseFluentFuture;
23 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFluentFuture;
24 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateTrueFluentFuture;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.Optional;
29 import org.junit.Before;
30 import org.junit.Test;
31 import org.mockito.Mock;
32 import org.mockito.Mockito;
33 import org.opendaylight.mdsal.common.api.CommitInfo;
34 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
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.dom.api.NetconfDataTreeService;
39 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
40 import org.opendaylight.restconf.common.errors.RestconfError;
41 import org.opendaylight.restconf.common.patch.PatchContext;
42 import org.opendaylight.restconf.common.patch.PatchEntity;
43 import org.opendaylight.restconf.common.patch.PatchStatusContext;
44 import org.opendaylight.restconf.common.patch.PatchStatusEntity;
45 import org.opendaylight.restconf.nb.rfc8040.TestRestconfUtils;
46 import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler;
47 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy;
48 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.NetconfRestconfStrategy;
49 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
50 import org.opendaylight.yangtools.yang.common.QName;
51 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
52 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
53 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
54 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
55 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
56 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
57 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
58 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
59 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
60 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
61 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
62 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
64 public class PatchDataTransactionUtilTest {
65 private static final String PATH_FOR_NEW_SCHEMA_CONTEXT = "/jukebox";
67 private DOMTransactionChain transactionChain;
69 private DOMDataTreeReadWriteTransaction rwTransaction;
71 private DOMDataBroker mockDataBroker;
73 private NetconfDataTreeService netconfService;
75 private TransactionChainHandler transactionChainHandler;
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 {
89 doReturn(transactionChain).when(mockDataBroker).createTransactionChain(any());
90 transactionChainHandler = new TransactionChainHandler(mockDataBroker);
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,
103 /* instance identifier for accessing container node "player" */
104 this.instanceIdContainer = YangInstanceIdentifier.builder()
106 .node(containerPlayerQName)
109 /* instance identifier for accessing leaf node "gap" */
110 this.instanceIdCreateAndDelete = instanceIdContainer.node(leafGapQName);
112 /* values that are used for creating leaf for testPatchDataCreateAndDelete test */
113 final LeafNode<?> buildGapLeaf = Builders.leafBuilder()
114 .withNodeIdentifier(new NodeIdentifier(leafGapQName))
118 final ContainerNode buildPlayerContainer = Builders.containerBuilder()
119 .withNodeIdentifier(new NodeIdentifier(containerPlayerQName))
120 .withChild(buildGapLeaf)
123 this.buildBaseContainerForTests = Builders.containerBuilder()
124 .withNodeIdentifier(new NodeIdentifier(baseQName))
125 .withChild(buildPlayerContainer)
128 this.targetNodeForCreateAndDelete = YangInstanceIdentifier.builder(this.instanceIdCreateAndDelete)
129 .node(containerPlayerQName)
133 /* instance identifier for accessing leaf node "name" in list "artist" */
134 this.instanceIdMerge = YangInstanceIdentifier.builder()
136 .node(containerLibraryQName)
137 .node(listArtistQName)
138 .nodeWithKey(listArtistQName, QName.create(listArtistQName, "name"), "name of artist")
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")
148 final LeafNode<Object> contentDescription = Builders.leafBuilder()
149 .withNodeIdentifier(new NodeIdentifier(QName.create(baseQName, "description")))
150 .withValue("description of artist")
153 final MapEntryNode mapEntryNode = Builders.mapEntryBuilder()
154 .withNodeIdentifier(nodeWithKey)
155 .withChild(contentName)
156 .withChild(contentDescription)
159 this.buildArtistList = Builders.mapBuilder()
160 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(listArtistQName))
161 .withChild(mapEntryNode)
164 this.targetNodeMerge = YangInstanceIdentifier.builder()
166 .node(containerLibraryQName)
167 .node(listArtistQName)
168 .nodeWithKey(listArtistQName, leafNameQName, "name of artist")
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());
178 public void testPatchDataReplaceMergeAndRemove() {
179 doReturn(immediateFalseFluentFuture()).doReturn(immediateTrueFluentFuture())
180 .when(this.rwTransaction).exists(LogicalDatastoreType.CONFIGURATION, this.targetNodeMerge);
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<>();
188 entities.add(entityReplace);
189 entities.add(entityMerge);
190 entities.add(entityRemove);
192 final InstanceIdentifierContext<? extends SchemaNode> iidContext =
193 new InstanceIdentifierContext<>(this.instanceIdMerge, null, null, this.refSchemaCtx);
194 final PatchContext patchContext = new PatchContext(iidContext, entities, "patchRMRm");
196 patch(patchContext, new MdsalRestconfStrategy(iidContext, transactionChainHandler), false);
197 patch(patchContext, new NetconfRestconfStrategy(netconfService, iidContext), false);
201 public void testPatchDataCreateAndDelete() {
202 doReturn(immediateFalseFluentFuture()).when(this.rwTransaction).exists(LogicalDatastoreType.CONFIGURATION,
203 this.instanceIdContainer);
204 Mockito.when(this.netconfService.getConfig(this.instanceIdContainer))
205 .thenReturn(immediateFluentFuture(Optional.empty()));
206 doReturn(immediateTrueFluentFuture()).when(this.rwTransaction).exists(LogicalDatastoreType.CONFIGURATION,
207 this.targetNodeForCreateAndDelete);
208 Mockito.when(this.netconfService.getConfig(this.targetNodeForCreateAndDelete))
209 .thenReturn(immediateFluentFuture(Optional.of(mock(NormalizedNode.class))));
211 final PatchEntity entityCreate =
212 new PatchEntity("edit1", CREATE, this.instanceIdContainer, this.buildBaseContainerForTests);
213 final PatchEntity entityDelete =
214 new PatchEntity("edit2", DELETE, this.targetNodeForCreateAndDelete);
215 final List<PatchEntity> entities = new ArrayList<>();
217 entities.add(entityCreate);
218 entities.add(entityDelete);
220 final InstanceIdentifierContext<? extends SchemaNode> iidContext =
221 new InstanceIdentifierContext<>(this.instanceIdCreateAndDelete, null, null, this.refSchemaCtx);
222 final PatchContext patchContext = new PatchContext(iidContext, entities, "patchCD");
223 patch(patchContext, new MdsalRestconfStrategy(iidContext, transactionChainHandler), true);
224 patch(patchContext, new NetconfRestconfStrategy(netconfService, iidContext), true);
228 public void deleteNonexistentDataTest() {
229 doReturn(immediateFalseFluentFuture()).when(this.rwTransaction).exists(LogicalDatastoreType.CONFIGURATION,
230 this.targetNodeForCreateAndDelete);
231 Mockito.when(this.netconfService.getConfig(this.targetNodeForCreateAndDelete))
232 .thenReturn(immediateFluentFuture(Optional.empty()));
234 final PatchEntity entityDelete = new PatchEntity("edit", DELETE, this.targetNodeForCreateAndDelete);
235 final List<PatchEntity> entities = new ArrayList<>();
237 entities.add(entityDelete);
239 final InstanceIdentifierContext<? extends SchemaNode> iidContext =
240 new InstanceIdentifierContext<>(this.instanceIdCreateAndDelete, null, null, this.refSchemaCtx);
241 final PatchContext patchContext = new PatchContext(iidContext, entities, "patchD");
242 delete(patchContext, new MdsalRestconfStrategy(iidContext, transactionChainHandler));
243 delete(patchContext, new NetconfRestconfStrategy(netconfService, iidContext));
247 public void testPatchMergePutContainer() {
248 doReturn(immediateFalseFluentFuture()).doReturn(immediateTrueFluentFuture())
249 .when(this.rwTransaction).exists(LogicalDatastoreType.CONFIGURATION, this.targetNodeForCreateAndDelete);
251 final PatchEntity entityMerge =
252 new PatchEntity("edit1", MERGE, this.instanceIdContainer, this.buildBaseContainerForTests);
253 final List<PatchEntity> entities = new ArrayList<>();
255 entities.add(entityMerge);
257 final InstanceIdentifierContext<? extends SchemaNode> iidContext =
258 new InstanceIdentifierContext<>(this.instanceIdCreateAndDelete, null, null, this.refSchemaCtx);
259 final PatchContext patchContext = new PatchContext(iidContext, entities, "patchM");
260 patch(patchContext, new MdsalRestconfStrategy(iidContext, transactionChainHandler), false);
261 patch(patchContext, new NetconfRestconfStrategy(netconfService, iidContext), false);
264 private void patch(final PatchContext patchContext, final RestconfStrategy strategy,
265 final boolean failed) {
266 final PatchStatusContext patchStatusContext =
267 PatchDataTransactionUtil.patchData(patchContext, strategy, this.refSchemaCtx);
268 for (final PatchStatusEntity entity : patchStatusContext.getEditCollection()) {
270 assertTrue("Edit " + entity.getEditId() + " failed", entity.isOk());
272 assertTrue(entity.isOk());
275 assertTrue(patchStatusContext.isOk());
278 private void delete(PatchContext patchContext, RestconfStrategy strategy) {
279 final PatchStatusContext patchStatusContext =
280 PatchDataTransactionUtil.patchData(patchContext, strategy, this.refSchemaCtx);
282 assertFalse(patchStatusContext.isOk());
283 assertEquals(RestconfError.ErrorType.PROTOCOL,
284 patchStatusContext.getEditCollection().get(0).getEditErrors().get(0).getErrorType());
285 assertEquals(RestconfError.ErrorTag.DATA_MISSING,
286 patchStatusContext.getEditCollection().get(0).getEditErrors().get(0).getErrorTag());