Do not use FQN of DataBroker in ModifiableDataTreeDelegator
[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.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;
58
59 /**
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.
62  */
63 public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager {
64
65     private static final Logger LOG = LoggerFactory.getLogger(ModifiableDataTreeDelegator.class);
66     private static final ReadableDataManager EMPTY_OPERATIONAL = p -> immediateCheckedFuture(Optional.absent());
67
68     private final WriterRegistry writerRegistry;
69     private final DataBroker contextBroker;
70     private final BindingNormalizedNodeSerializer serializer;
71     private final SchemaContext schema;
72
73     /**
74      * Creates configuration data tree instance.
75      *  @param serializer     service for serialization between Java Binding Data representation and NormalizedNode
76      *                       representation.
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
80      */
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) {
86         super(dataTree);
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");
91     }
92
93     @Override
94     public DataModification newModification() {
95         return new DelegatingConfigSnapshot(super.newModification());
96     }
97
98     private final class DelegatingConfigSnapshot extends ModifiableDataTreeManager.ConfigSnapshot {
99
100         private final DataModification untouchedModification;
101
102         /**
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.
107          */
108         DelegatingConfigSnapshot(final DataModification untouchedModification) {
109             this.untouchedModification = untouchedModification;
110         }
111
112         /**
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.
116          */
117         @Override
118         protected void processCandidate(final DataTreeCandidate candidate)
119             throws TranslationException {
120
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());
125
126             final ModificationDiff modificationDiff = new ModificationDiff.ModificationDiffBuilder()
127                     .setCtx(schema).build(rootNode);
128             LOG.debug("ConfigDataTree.modify() diff: {}", modificationDiff);
129
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);
134
135             final WriteContext ctx = getTransactionWriteContext();
136             final MappingContext mappingContext = ctx.getMappingContext();
137             try {
138                 writerRegistry.processModifications(baUpdates, ctx);
139
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");
152                     throw e;
153                 } else {
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;
163                     }
164                 }
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";
172                 LOG.error(msg, e);
173                 throw new TranslationException(msg, e);
174             } finally {
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);
178                 ctx.close();
179             }
180         }
181
182         /**
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
186          * */
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);
199         }
200
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);
212         }
213
214         private WriterRegistry.DataObjectUpdates toBindingAware(final WriterRegistry registry,
215                 final Map<YangInstanceIdentifier, NormalizedNodeUpdate> biNodes) {
216             return ModifiableDataTreeDelegator.toBindingAware(registry, biNodes, serializer);
217         }
218     }
219
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());
226     }
227
228     @VisibleForTesting
229     static WriterRegistry.DataObjectUpdates toBindingAware(
230             final WriterRegistry registry,
231             final Map<YangInstanceIdentifier, NormalizedNodeUpdate> biNodes,
232             final BindingNormalizedNodeSerializer serializer) {
233
234         final Multimap<InstanceIdentifier<?>, DataObjectUpdate> dataObjectUpdates = HashMultimap.create();
235         final Multimap<InstanceIdentifier<?>, DataObjectUpdate.DataObjectDelete> dataObjectDeletes =
236                 HashMultimap.create();
237
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);
242
243             NormalizedNodeUpdate normalizedNodeUpdate = biEntry.getValue();
244             final DataObjectUpdate dataObjectUpdate = toDataObjectUpdate(normalizedNodeUpdate, serializer);
245             if (dataObjectUpdate != null) {
246                 if (dataObjectUpdate instanceof DataObjectUpdate.DataObjectDelete) {
247                     // is delete
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
252
253                     dataObjectDeletes.put(unkeyedIid,
254                             (DataObjectUpdate.DataObjectDelete) DataObjectUpdate.DataObjectDelete
255                                     .create(keyedId, dataObjectUpdate.getDataBefore(), null));
256                     dataObjectUpdates
257                             .put(unkeyedIid, DataObjectUpdate.create(keyedId, null, dataObjectUpdate.getDataAfter()));
258                 } else {
259                     // is create
260                     dataObjectUpdates.put(unkeyedIid, dataObjectUpdate);
261                 }
262             }
263         }
264         return new WriterRegistry.DataObjectUpdates(dataObjectUpdates, dataObjectDeletes);
265     }
266
267     @Nullable
268     private static DataObjectUpdate toDataObjectUpdate(
269             final NormalizedNodeUpdate normalizedNodeUpdate,
270             final BindingNormalizedNodeSerializer serializer) {
271
272         InstanceIdentifier<?> baId = serializer.fromYangInstanceIdentifier(normalizedNodeUpdate.getId());
273         checkNotNull(baId, "Unable to transform instance identifier: %s into BA", normalizedNodeUpdate.getId());
274
275         DataObject dataObjectBefore = getDataObject(serializer,
276                 normalizedNodeUpdate.getDataBefore(), normalizedNodeUpdate.getId());
277         DataObject dataObjectAfter =
278                 getDataObject(serializer, normalizedNodeUpdate.getDataAfter(), normalizedNodeUpdate.getId());
279
280         return dataObjectBefore == null && dataObjectAfter == null
281                 ? null
282                 : DataObjectUpdate.create(baId, dataObjectBefore, dataObjectAfter);
283     }
284
285     @Nullable
286     private static DataObject getDataObject(@Nonnull final BindingNormalizedNodeSerializer serializer,
287                                             @Nullable final NormalizedNode<?, ?> data,
288                                             @Nonnull final YangInstanceIdentifier id) {
289         DataObject dataObject = null;
290         if (data != null) {
291             final Map.Entry<InstanceIdentifier<?>, DataObject> dataObjectEntry =
292                     serializer.fromNormalizedNode(id, data);
293             if (dataObjectEntry != null) {
294                 dataObject = dataObjectEntry.getValue();
295             }
296         }
297         return dataObject;
298     }
299
300 }
301
302
303