2 * Copyright (c) 2016, 2017 Cisco and/or its affiliates.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at:
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package io.fd.honeycomb.data.impl;
19 import static com.google.common.base.Preconditions.checkNotNull;
20 import static com.google.common.util.concurrent.Futures.immediateCheckedFuture;
22 import com.google.common.annotations.VisibleForTesting;
23 import com.google.common.base.Optional;
24 import com.google.common.collect.HashMultimap;
25 import com.google.common.collect.Multimap;
26 import com.google.common.util.concurrent.CheckedFuture;
27 import io.fd.honeycomb.data.DataModification;
28 import io.fd.honeycomb.data.ReadableDataManager;
29 import io.fd.honeycomb.translate.MappingContext;
30 import io.fd.honeycomb.translate.TranslationException;
31 import io.fd.honeycomb.translate.util.RWUtils;
32 import io.fd.honeycomb.translate.util.TransactionMappingContext;
33 import io.fd.honeycomb.translate.util.write.TransactionWriteContext;
34 import io.fd.honeycomb.translate.write.DataObjectUpdate;
35 import io.fd.honeycomb.translate.write.WriteContext;
36 import io.fd.honeycomb.translate.write.registry.UpdateFailedException;
37 import io.fd.honeycomb.translate.write.registry.WriterRegistry;
38 import java.util.List;
41 import java.util.stream.Collectors;
42 import javax.annotation.Nonnull;
43 import javax.annotation.Nullable;
44 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
45 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
46 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
47 import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
48 import org.opendaylight.yangtools.yang.binding.DataObject;
49 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
50 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
51 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
52 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
53 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
54 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
55 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
60 * Extension of {@link ModifiableDataTreeManager} that propagates data changes to underlying writer layer before they
61 * are fully committed in the backing data tree. Data changes are propagated in BA format.
63 public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager {
65 private static final Logger LOG = LoggerFactory.getLogger(ModifiableDataTreeDelegator.class);
66 private static final ReadableDataManager EMPTY_OPERATIONAL = p -> immediateCheckedFuture(Optional.absent());
68 private final WriterRegistry writerRegistry;
69 private final DataBroker contextBroker;
70 private final BindingNormalizedNodeSerializer serializer;
71 private final SchemaContext schema;
74 * Creates configuration data tree instance.
75 * @param serializer service for serialization between Java Binding Data representation and NormalizedNode
77 * @param dataTree data tree for configuration data representation
78 * @param writerRegistry service for translation between Java Binding Data and data provider, capable of performing
79 * @param contextBroker BA broker providing full access to mapping context data
81 public ModifiableDataTreeDelegator(@Nonnull final BindingNormalizedNodeSerializer serializer,
82 @Nonnull final DataTree dataTree,
83 @Nonnull final SchemaContext schema,
84 @Nonnull final WriterRegistry writerRegistry,
85 @Nonnull final DataBroker contextBroker) {
87 this.contextBroker = checkNotNull(contextBroker, "contextBroker should not be null");
88 this.serializer = checkNotNull(serializer, "serializer should not be null");
89 this.writerRegistry = checkNotNull(writerRegistry, "writerRegistry should not be null");
90 this.schema = checkNotNull(schema, "schema should not be null");
94 public DataModification newModification() {
95 return new DelegatingConfigSnapshot(super.newModification());
98 private final class DelegatingConfigSnapshot extends ModifiableDataTreeManager.ConfigSnapshot {
100 private final DataModification untouchedModification;
103 * @param untouchedModification DataModification captured while this modification/snapshot was created.
104 * To be used later while invoking writers to provide them with before state
105 * (state without current modifications).
106 * It must be captured as close as possible to when current modification started.
108 DelegatingConfigSnapshot(final DataModification untouchedModification) {
109 this.untouchedModification = untouchedModification;
113 * Pass the changes to underlying writer layer.
114 * Transform from BI to BA.
115 * Revert(Write data before to subtrees that have been successfully modified before failure) in case of failure.
118 protected void processCandidate(final DataTreeCandidate candidate)
119 throws TranslationException {
121 final DataTreeCandidateNode rootNode = candidate.getRootNode();
122 final YangInstanceIdentifier rootPath = candidate.getRootPath();
123 LOG.trace("ConfigDataTree.modify() rootPath={}, rootNode={}, dataBefore={}, dataAfter={}",
124 rootPath, rootNode, rootNode.getDataBefore(), rootNode.getDataAfter());
126 final ModificationDiff modificationDiff = new ModificationDiff.ModificationDiffBuilder()
127 .setCtx(schema).build(rootNode);
128 LOG.debug("ConfigDataTree.modify() diff: {}", modificationDiff);
130 // Distinguish between updates (create + update) and deletes
131 final WriterRegistry.DataObjectUpdates baUpdates =
132 toBindingAware(writerRegistry, modificationDiff.getUpdates());
133 LOG.debug("ConfigDataTree.modify() extracted updates={}", baUpdates);
135 final WriteContext ctx = getTransactionWriteContext();
136 final MappingContext mappingContext = ctx.getMappingContext();
138 writerRegistry.processModifications(baUpdates, ctx);
140 final CheckedFuture<Void, TransactionCommitFailedException> contextUpdateResult =
141 ((TransactionMappingContext) mappingContext).submit();
142 // Blocking on context data update
143 contextUpdateResult.checkedGet();
144 } catch (UpdateFailedException e) {
145 // TODO - HONEYCOMB-411
146 LOG.warn("Failed to apply all changes", e);
147 final List<DataObjectUpdate> processed = e.getProcessed();
148 if (processed.isEmpty()) {
149 // nothing was processed, which means either very first operation failed, or it was single operation
150 // update. In both cases, no revert is needed
151 LOG.info("Nothing to revert");
154 LOG.info("Trying to revert successful changes for current transaction");
155 // attempt revert with fresh context, to allow write logic used context-binded data
156 try (final TransactionWriteContext txContext = getRevertTransactionContext(mappingContext)) {
157 new Reverter(processed, writerRegistry).revert(txContext);
158 LOG.info("Changes successfully reverted");
159 } catch (Reverter.RevertFailedException revertFailedException) {
160 // fail with failed revert
161 LOG.error("Failed to revert successful(comitted) changes", revertFailedException);
162 throw revertFailedException;
165 // fail with success revert
166 // not passing the cause,its logged above and it would be logged after transaction
167 // ended again(prevent double logging of same error
168 throw new Reverter.RevertSuccessException(getNonProcessedNodes(baUpdates, processed));
169 } catch (TransactionCommitFailedException e) {
170 // TODO HONEYCOMB-162 revert should probably occur when context is not written successfully
171 final String msg = "Error while updating mapping context data";
173 throw new TranslationException(msg, e);
175 // Using finally instead of try-with-resources in order to leave ctx open for BulkUpdateException catch
176 // block. The context is needed there, but try-with-resources closes the resource before handling ex.
177 LOG.debug("Closing write context {}", ctx);
183 * Creates inverted transaction context for reverting of proceeded changes.
184 * Invert before/after transaction and reuse affected mapping context created by previous updates
185 * to access all data created by previous updates
187 // Sonar reports unclosed resources, but everything is closed by TransactionWriteContext.close()
188 // This is known SonarJava issue
189 // https://jira.sonarsource.com/browse/SONARJAVA-1670
190 // Fixed in SonarJava 4.0 (requires SonarCube 5.6).
191 // TODO(HONEYCOMB-419): remove after LF upgrades SonarCube
192 @SuppressWarnings("squid:S2095")
193 private TransactionWriteContext getRevertTransactionContext(final MappingContext affectedMappingContext) {
194 // Before Tx == after partial update
195 final DOMDataReadOnlyTransaction beforeTx = ReadOnlyTransaction.create(this, EMPTY_OPERATIONAL);
196 // After Tx == before partial update
197 final DOMDataReadOnlyTransaction afterTx = ReadOnlyTransaction.create(untouchedModification, EMPTY_OPERATIONAL);
198 return new TransactionWriteContext(serializer, beforeTx, afterTx, affectedMappingContext);
201 // Sonar reports unclosed resources, but everything is closed by TransactionWriteContext.close()
202 // TODO(HONEYCOMB-419): remove after LF upgrades SonarCube
203 @SuppressWarnings("squid:S2095")
204 private TransactionWriteContext getTransactionWriteContext() {
205 // Before Tx must use modification
206 final DOMDataReadOnlyTransaction beforeTx = ReadOnlyTransaction.create(untouchedModification, EMPTY_OPERATIONAL);
207 // After Tx must use current modification
208 final DOMDataReadOnlyTransaction afterTx = ReadOnlyTransaction.create(this, EMPTY_OPERATIONAL);
209 final TransactionMappingContext mappingContext = new TransactionMappingContext(
210 contextBroker.newReadWriteTransaction());
211 return new TransactionWriteContext(serializer, beforeTx, afterTx, mappingContext);
214 private WriterRegistry.DataObjectUpdates toBindingAware(final WriterRegistry registry,
215 final Map<YangInstanceIdentifier, NormalizedNodeUpdate> biNodes) {
216 return ModifiableDataTreeDelegator.toBindingAware(registry, biNodes, serializer);
220 private static Set<InstanceIdentifier<?>> getNonProcessedNodes(final WriterRegistry.DataObjectUpdates allUpdates,
221 final List<DataObjectUpdate> alreadyProcessed) {
222 return allUpdates.getAllModifications().stream()
223 .filter(update -> !alreadyProcessed.contains(update))
224 .map(DataObjectUpdate::getId)
225 .collect(Collectors.toSet());
229 static WriterRegistry.DataObjectUpdates toBindingAware(
230 final WriterRegistry registry,
231 final Map<YangInstanceIdentifier, NormalizedNodeUpdate> biNodes,
232 final BindingNormalizedNodeSerializer serializer) {
234 final Multimap<InstanceIdentifier<?>, DataObjectUpdate> dataObjectUpdates = HashMultimap.create();
235 final Multimap<InstanceIdentifier<?>, DataObjectUpdate.DataObjectDelete> dataObjectDeletes =
236 HashMultimap.create();
238 for (Map.Entry<YangInstanceIdentifier, NormalizedNodeUpdate> biEntry : biNodes.entrySet()) {
239 final InstanceIdentifier<?> keyedId = serializer.fromYangInstanceIdentifier(biEntry.getKey());
240 final InstanceIdentifier<?> unkeyedIid =
241 RWUtils.makeIidWildcarded(keyedId);
243 NormalizedNodeUpdate normalizedNodeUpdate = biEntry.getValue();
244 final DataObjectUpdate dataObjectUpdate = toDataObjectUpdate(normalizedNodeUpdate, serializer);
245 if (dataObjectUpdate != null) {
246 if (dataObjectUpdate instanceof DataObjectUpdate.DataObjectDelete) {
248 dataObjectDeletes.put(unkeyedIid, (DataObjectUpdate.DataObjectDelete) dataObjectUpdate);
249 } else if (dataObjectUpdate.getDataBefore() != null && !registry.writerSupportsUpdate(unkeyedIid)) {
250 // is update and direct update operation is not supported
251 // breaks update to delete + create pair
253 dataObjectDeletes.put(unkeyedIid,
254 (DataObjectUpdate.DataObjectDelete) DataObjectUpdate.DataObjectDelete
255 .create(keyedId, dataObjectUpdate.getDataBefore(), null));
257 .put(unkeyedIid, DataObjectUpdate.create(keyedId, null, dataObjectUpdate.getDataAfter()));
260 dataObjectUpdates.put(unkeyedIid, dataObjectUpdate);
264 return new WriterRegistry.DataObjectUpdates(dataObjectUpdates, dataObjectDeletes);
268 private static DataObjectUpdate toDataObjectUpdate(
269 final NormalizedNodeUpdate normalizedNodeUpdate,
270 final BindingNormalizedNodeSerializer serializer) {
272 InstanceIdentifier<?> baId = serializer.fromYangInstanceIdentifier(normalizedNodeUpdate.getId());
273 checkNotNull(baId, "Unable to transform instance identifier: %s into BA", normalizedNodeUpdate.getId());
275 DataObject dataObjectBefore = getDataObject(serializer,
276 normalizedNodeUpdate.getDataBefore(), normalizedNodeUpdate.getId());
277 DataObject dataObjectAfter =
278 getDataObject(serializer, normalizedNodeUpdate.getDataAfter(), normalizedNodeUpdate.getId());
280 return dataObjectBefore == null && dataObjectAfter == null
282 : DataObjectUpdate.create(baId, dataObjectBefore, dataObjectAfter);
286 private static DataObject getDataObject(@Nonnull final BindingNormalizedNodeSerializer serializer,
287 @Nullable final NormalizedNode<?, ?> data,
288 @Nonnull final YangInstanceIdentifier id) {
289 DataObject dataObject = null;
291 final Map.Entry<InstanceIdentifier<?>, DataObject> dataObjectEntry =
292 serializer.fromNormalizedNode(id, data);
293 if (dataObjectEntry != null) {
294 dataObject = dataObjectEntry.getValue();