38145610028c2f9ade261cfebccf8612f8b62da2
[honeycomb.git] / infra / data-impl / src / main / java / io / fd / honeycomb / data / impl / ModifiableDataTreeDelegator.java
1 /*
2  * Copyright (c) 2016, 2017 Cisco and/or its affiliates.
3  *
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:
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package io.fd.honeycomb.data.impl;
18
19 import static com.google.common.base.Preconditions.checkNotNull;
20 import static com.google.common.util.concurrent.Futures.immediateCheckedFuture;
21
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;
39 import java.util.Map;
40 import java.util.Set;
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;
57
58 /**
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.
61  */
62 public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager {
63
64     private static final Logger LOG = LoggerFactory.getLogger(ModifiableDataTreeDelegator.class);
65     private static final ReadableDataManager EMPTY_OPERATIONAL = p -> immediateCheckedFuture(Optional.absent());
66
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;
71
72     /**
73      * Creates configuration data tree instance.
74      *  @param serializer     service for serialization between Java Binding Data representation and NormalizedNode
75      *                       representation.
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
79      */
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) {
85         super(dataTree);
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");
90     }
91
92     @Override
93     public DataModification newModification() {
94         return new DelegatingConfigSnapshot(super.newModification());
95     }
96
97     private final class DelegatingConfigSnapshot extends ModifiableDataTreeManager.ConfigSnapshot {
98
99         private final DataModification untouchedModification;
100
101         /**
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.
106          */
107         DelegatingConfigSnapshot(final DataModification untouchedModification) {
108             this.untouchedModification = untouchedModification;
109         }
110
111         /**
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.
115          */
116         @Override
117         protected void processCandidate(final DataTreeCandidate candidate)
118             throws TranslationException {
119
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());
124
125             final ModificationDiff modificationDiff = new ModificationDiff.ModificationDiffBuilder()
126                     .setCtx(schema).build(rootNode);
127             LOG.debug("ConfigDataTree.modify() diff: {}", modificationDiff);
128
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);
133
134             final WriteContext ctx = getTransactionWriteContext();
135             final MappingContext mappingContext = ctx.getMappingContext();
136             try {
137                 writerRegistry.processModifications(baUpdates, ctx);
138
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");
151                     throw e;
152                 } else {
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;
162                     }
163                 }
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";
171                 LOG.error(msg, e);
172                 throw new TranslationException(msg, e);
173             } finally {
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);
177                 ctx.close();
178             }
179         }
180
181         /**
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
185          * */
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);
198         }
199
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);
211         }
212
213         private WriterRegistry.DataObjectUpdates toBindingAware(final WriterRegistry registry,
214                 final Map<YangInstanceIdentifier, NormalizedNodeUpdate> biNodes) {
215             return ModifiableDataTreeDelegator.toBindingAware(registry, biNodes, serializer);
216         }
217     }
218
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());
225     }
226
227     @VisibleForTesting
228     static WriterRegistry.DataObjectUpdates toBindingAware(
229             final WriterRegistry registry,
230             final Map<YangInstanceIdentifier, NormalizedNodeUpdate> biNodes,
231             final BindingNormalizedNodeSerializer serializer) {
232
233         final Multimap<InstanceIdentifier<?>, DataObjectUpdate> dataObjectUpdates = HashMultimap.create();
234         final Multimap<InstanceIdentifier<?>, DataObjectUpdate.DataObjectDelete> dataObjectDeletes =
235                 HashMultimap.create();
236
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);
241
242             NormalizedNodeUpdate normalizedNodeUpdate = biEntry.getValue();
243             final DataObjectUpdate dataObjectUpdate = toDataObjectUpdate(normalizedNodeUpdate, serializer);
244             if (dataObjectUpdate != null) {
245                 if (dataObjectUpdate instanceof DataObjectUpdate.DataObjectDelete) {
246                     // is delete
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
251
252                     dataObjectDeletes.put(unkeyedIid,
253                             (DataObjectUpdate.DataObjectDelete) DataObjectUpdate.DataObjectDelete
254                                     .create(keyedId, dataObjectUpdate.getDataBefore(), null));
255                     dataObjectUpdates
256                             .put(unkeyedIid, DataObjectUpdate.create(keyedId, null, dataObjectUpdate.getDataAfter()));
257                 } else {
258                     // is create
259                     dataObjectUpdates.put(unkeyedIid, dataObjectUpdate);
260                 }
261             }
262         }
263         return new WriterRegistry.DataObjectUpdates(dataObjectUpdates, dataObjectDeletes);
264     }
265
266     @Nullable
267     private static DataObjectUpdate toDataObjectUpdate(
268             final NormalizedNodeUpdate normalizedNodeUpdate,
269             final BindingNormalizedNodeSerializer serializer) {
270
271         InstanceIdentifier<?> baId = serializer.fromYangInstanceIdentifier(normalizedNodeUpdate.getId());
272         checkNotNull(baId, "Unable to transform instance identifier: %s into BA", normalizedNodeUpdate.getId());
273
274         DataObject dataObjectBefore = getDataObject(serializer,
275                 normalizedNodeUpdate.getDataBefore(), normalizedNodeUpdate.getId());
276         DataObject dataObjectAfter =
277                 getDataObject(serializer, normalizedNodeUpdate.getDataAfter(), normalizedNodeUpdate.getId());
278
279         return dataObjectBefore == null && dataObjectAfter == null
280                 ? null
281                 : DataObjectUpdate.create(baId, dataObjectBefore, dataObjectAfter);
282     }
283
284     @Nullable
285     private static DataObject getDataObject(@Nonnull final BindingNormalizedNodeSerializer serializer,
286                                             @Nullable final NormalizedNode<?, ?> data,
287                                             @Nonnull final YangInstanceIdentifier id) {
288         DataObject dataObject = null;
289         if (data != null) {
290             final Map.Entry<InstanceIdentifier<?>, DataObject> dataObjectEntry =
291                     serializer.fromNormalizedNode(id, data);
292             if (dataObjectEntry != null) {
293                 dataObject = dataObjectEntry.getValue();
294             }
295         }
296         return dataObject;
297     }
298
299 }
300
301
302