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.common.api.data.TransactionCommitFailedException;
45 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
46 import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
47 import org.opendaylight.yangtools.yang.binding.DataObject;
48 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
49 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
50 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
51 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
52 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
53 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
54 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
59 * Extension of {@link ModifiableDataTreeManager} that propagates data changes to underlying writer layer before they
60 * are fully committed in the backing data tree. Data changes are propagated in BA format.
62 public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager {
64 private static final Logger LOG = LoggerFactory.getLogger(ModifiableDataTreeDelegator.class);
65 private static final ReadableDataManager EMPTY_OPERATIONAL = p -> immediateCheckedFuture(Optional.absent());
67 private final WriterRegistry writerRegistry;
68 private final org.opendaylight.controller.md.sal.binding.api.DataBroker contextBroker;
69 private final BindingNormalizedNodeSerializer serializer;
70 private final SchemaContext schema;
73 * Creates configuration data tree instance.
74 * @param serializer service for serialization between Java Binding Data representation and NormalizedNode
76 * @param dataTree data tree for configuration data representation
77 * @param writerRegistry service for translation between Java Binding Data and data provider, capable of performing
78 * @param contextBroker BA broker providing full access to mapping context data
80 public ModifiableDataTreeDelegator(@Nonnull final BindingNormalizedNodeSerializer serializer,
81 @Nonnull final DataTree dataTree,
82 @Nonnull final SchemaContext schema,
83 @Nonnull final WriterRegistry writerRegistry,
84 @Nonnull final org.opendaylight.controller.md.sal.binding.api.DataBroker contextBroker) {
86 this.contextBroker = checkNotNull(contextBroker, "contextBroker should not be null");
87 this.serializer = checkNotNull(serializer, "serializer should not be null");
88 this.writerRegistry = checkNotNull(writerRegistry, "writerRegistry should not be null");
89 this.schema = checkNotNull(schema, "schema should not be null");
93 public DataModification newModification() {
94 return new DelegatingConfigSnapshot(super.newModification());
97 private final class DelegatingConfigSnapshot extends ModifiableDataTreeManager.ConfigSnapshot {
99 private final DataModification untouchedModification;
102 * @param untouchedModification DataModification captured while this modification/snapshot was created.
103 * To be used later while invoking writers to provide them with before state
104 * (state without current modifications).
105 * It must be captured as close as possible to when current modification started.
107 DelegatingConfigSnapshot(final DataModification untouchedModification) {
108 this.untouchedModification = untouchedModification;
112 * Pass the changes to underlying writer layer.
113 * Transform from BI to BA.
114 * Revert(Write data before to subtrees that have been successfully modified before failure) in case of failure.
117 protected void processCandidate(final DataTreeCandidate candidate)
118 throws TranslationException {
120 final DataTreeCandidateNode rootNode = candidate.getRootNode();
121 final YangInstanceIdentifier rootPath = candidate.getRootPath();
122 LOG.trace("ConfigDataTree.modify() rootPath={}, rootNode={}, dataBefore={}, dataAfter={}",
123 rootPath, rootNode, rootNode.getDataBefore(), rootNode.getDataAfter());
125 final ModificationDiff modificationDiff = new ModificationDiff.ModificationDiffBuilder()
126 .setCtx(schema).build(rootNode);
127 LOG.debug("ConfigDataTree.modify() diff: {}", modificationDiff);
129 // Distinguish between updates (create + update) and deletes
130 final WriterRegistry.DataObjectUpdates baUpdates =
131 toBindingAware(writerRegistry, modificationDiff.getUpdates());
132 LOG.debug("ConfigDataTree.modify() extracted updates={}", baUpdates);
134 final WriteContext ctx = getTransactionWriteContext();
135 final MappingContext mappingContext = ctx.getMappingContext();
137 writerRegistry.processModifications(baUpdates, ctx);
139 final CheckedFuture<Void, TransactionCommitFailedException> contextUpdateResult =
140 ((TransactionMappingContext) mappingContext).submit();
141 // Blocking on context data update
142 contextUpdateResult.checkedGet();
143 } catch (UpdateFailedException e) {
144 // TODO - HONEYCOMB-411
145 LOG.warn("Failed to apply all changes", e);
146 final List<DataObjectUpdate> processed = e.getProcessed();
147 if (processed.isEmpty()) {
148 // nothing was processed, which means either very first operation failed, or it was single operation
149 // update. In both cases, no revert is needed
150 LOG.info("Nothing to revert");
153 LOG.info("Trying to revert successful changes for current transaction");
154 // attempt revert with fresh context, to allow write logic used context-binded data
155 try (final TransactionWriteContext txContext = getRevertTransactionContext(mappingContext)) {
156 new Reverter(processed, writerRegistry).revert(txContext);
157 LOG.info("Changes successfully reverted");
158 } catch (Reverter.RevertFailedException revertFailedException) {
159 // fail with failed revert
160 LOG.error("Failed to revert successful(comitted) changes", revertFailedException);
161 throw revertFailedException;
164 // fail with success revert
165 // not passing the cause,its logged above and it would be logged after transaction
166 // ended again(prevent double logging of same error
167 throw new Reverter.RevertSuccessException(getNonProcessedNodes(baUpdates, processed));
168 } catch (TransactionCommitFailedException e) {
169 // TODO HONEYCOMB-162 revert should probably occur when context is not written successfully
170 final String msg = "Error while updating mapping context data";
172 throw new TranslationException(msg, e);
174 // Using finally instead of try-with-resources in order to leave ctx open for BulkUpdateException catch
175 // block. The context is needed there, but try-with-resources closes the resource before handling ex.
176 LOG.debug("Closing write context {}", ctx);
182 * Creates inverted transaction context for reverting of proceeded changes.
183 * Invert before/after transaction and reuse affected mapping context created by previous updates
184 * to access all data created by previous updates
186 // Sonar reports unclosed resources, but everything is closed by TransactionWriteContext.close()
187 // This is known SonarJava issue
188 // https://jira.sonarsource.com/browse/SONARJAVA-1670
189 // Fixed in SonarJava 4.0 (requires SonarCube 5.6).
190 // TODO(HONEYCOMB-419): remove after LF upgrades SonarCube
191 @SuppressWarnings("squid:S2095")
192 private TransactionWriteContext getRevertTransactionContext(final MappingContext affectedMappingContext) {
193 // Before Tx == after partial update
194 final DOMDataReadOnlyTransaction beforeTx = ReadOnlyTransaction.create(this, EMPTY_OPERATIONAL);
195 // After Tx == before partial update
196 final DOMDataReadOnlyTransaction afterTx = ReadOnlyTransaction.create(untouchedModification, EMPTY_OPERATIONAL);
197 return new TransactionWriteContext(serializer, beforeTx, afterTx, affectedMappingContext);
200 // Sonar reports unclosed resources, but everything is closed by TransactionWriteContext.close()
201 // TODO(HONEYCOMB-419): remove after LF upgrades SonarCube
202 @SuppressWarnings("squid:S2095")
203 private TransactionWriteContext getTransactionWriteContext() {
204 // Before Tx must use modification
205 final DOMDataReadOnlyTransaction beforeTx = ReadOnlyTransaction.create(untouchedModification, EMPTY_OPERATIONAL);
206 // After Tx must use current modification
207 final DOMDataReadOnlyTransaction afterTx = ReadOnlyTransaction.create(this, EMPTY_OPERATIONAL);
208 final TransactionMappingContext mappingContext = new TransactionMappingContext(
209 contextBroker.newReadWriteTransaction());
210 return new TransactionWriteContext(serializer, beforeTx, afterTx, mappingContext);
213 private WriterRegistry.DataObjectUpdates toBindingAware(final WriterRegistry registry,
214 final Map<YangInstanceIdentifier, NormalizedNodeUpdate> biNodes) {
215 return ModifiableDataTreeDelegator.toBindingAware(registry, biNodes, serializer);
219 private static Set<InstanceIdentifier<?>> getNonProcessedNodes(final WriterRegistry.DataObjectUpdates allUpdates,
220 final List<DataObjectUpdate> alreadyProcessed) {
221 return allUpdates.getAllModifications().stream()
222 .filter(update -> !alreadyProcessed.contains(update))
223 .map(DataObjectUpdate::getId)
224 .collect(Collectors.toSet());
228 static WriterRegistry.DataObjectUpdates toBindingAware(
229 final WriterRegistry registry,
230 final Map<YangInstanceIdentifier, NormalizedNodeUpdate> biNodes,
231 final BindingNormalizedNodeSerializer serializer) {
233 final Multimap<InstanceIdentifier<?>, DataObjectUpdate> dataObjectUpdates = HashMultimap.create();
234 final Multimap<InstanceIdentifier<?>, DataObjectUpdate.DataObjectDelete> dataObjectDeletes =
235 HashMultimap.create();
237 for (Map.Entry<YangInstanceIdentifier, NormalizedNodeUpdate> biEntry : biNodes.entrySet()) {
238 final InstanceIdentifier<?> keyedId = serializer.fromYangInstanceIdentifier(biEntry.getKey());
239 final InstanceIdentifier<?> unkeyedIid =
240 RWUtils.makeIidWildcarded(keyedId);
242 NormalizedNodeUpdate normalizedNodeUpdate = biEntry.getValue();
243 final DataObjectUpdate dataObjectUpdate = toDataObjectUpdate(normalizedNodeUpdate, serializer);
244 if (dataObjectUpdate != null) {
245 if (dataObjectUpdate instanceof DataObjectUpdate.DataObjectDelete) {
247 dataObjectDeletes.put(unkeyedIid, (DataObjectUpdate.DataObjectDelete) dataObjectUpdate);
248 } else if (dataObjectUpdate.getDataBefore() != null && !registry.writerSupportsUpdate(unkeyedIid)) {
249 // is update and direct update operation is not supported
250 // breaks update to delete + create pair
252 dataObjectDeletes.put(unkeyedIid,
253 (DataObjectUpdate.DataObjectDelete) DataObjectUpdate.DataObjectDelete
254 .create(keyedId, dataObjectUpdate.getDataBefore(), null));
256 .put(unkeyedIid, DataObjectUpdate.create(keyedId, null, dataObjectUpdate.getDataAfter()));
259 dataObjectUpdates.put(unkeyedIid, dataObjectUpdate);
263 return new WriterRegistry.DataObjectUpdates(dataObjectUpdates, dataObjectDeletes);
267 private static DataObjectUpdate toDataObjectUpdate(
268 final NormalizedNodeUpdate normalizedNodeUpdate,
269 final BindingNormalizedNodeSerializer serializer) {
271 InstanceIdentifier<?> baId = serializer.fromYangInstanceIdentifier(normalizedNodeUpdate.getId());
272 checkNotNull(baId, "Unable to transform instance identifier: %s into BA", normalizedNodeUpdate.getId());
274 DataObject dataObjectBefore = getDataObject(serializer,
275 normalizedNodeUpdate.getDataBefore(), normalizedNodeUpdate.getId());
276 DataObject dataObjectAfter =
277 getDataObject(serializer, normalizedNodeUpdate.getDataAfter(), normalizedNodeUpdate.getId());
279 return dataObjectBefore == null && dataObjectAfter == null
281 : DataObjectUpdate.create(baId, dataObjectBefore, dataObjectAfter);
285 private static DataObject getDataObject(@Nonnull final BindingNormalizedNodeSerializer serializer,
286 @Nullable final NormalizedNode<?, ?> data,
287 @Nonnull final YangInstanceIdentifier id) {
288 DataObject dataObject = null;
290 final Map.Entry<InstanceIdentifier<?>, DataObject> dataObjectEntry =
291 serializer.fromNormalizedNode(id, data);
292 if (dataObjectEntry != null) {
293 dataObject = dataObjectEntry.getValue();