HONEYCOMB-34: Configurable serializer dependency
[honeycomb.git] / v3po / impl / src / main / java / io / fd / honeycomb / v3po / impl / VppDataBrokerInitializationProvider.java
1 /*
2  * Copyright (c) 2016 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.v3po.impl;
18
19 import static com.google.common.base.Preconditions.checkNotNull;
20
21 import com.google.common.base.Optional;
22 import com.google.common.base.Preconditions;
23 import com.google.common.util.concurrent.AsyncFunction;
24 import com.google.common.util.concurrent.Futures;
25 import com.google.common.util.concurrent.ListenableFuture;
26 import io.fd.honeycomb.v3po.data.ModifiableDataTree;
27 import io.fd.honeycomb.v3po.data.ReadableDataTree;
28 import io.fd.honeycomb.v3po.data.impl.ConfigDataTree;
29 import io.fd.honeycomb.v3po.data.impl.DataBroker;
30 import io.fd.honeycomb.v3po.data.impl.OperationalDataTree;
31 import io.fd.honeycomb.v3po.translate.Context;
32 import io.fd.honeycomb.v3po.translate.TranslationException;
33 import io.fd.honeycomb.v3po.translate.read.ReadContext;
34 import io.fd.honeycomb.v3po.translate.read.ReaderRegistry;
35 import io.fd.honeycomb.v3po.translate.write.WriterRegistry;
36 import java.util.ArrayList;
37 import java.util.Collection;
38 import java.util.Collections;
39 import java.util.List;
40 import java.util.Map;
41 import javax.annotation.Nonnull;
42 import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
43 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
44 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
45 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
46 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
47 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
48 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
49 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
50 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
51 import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
52 import org.opendaylight.controller.sal.core.api.Broker;
53 import org.opendaylight.controller.sal.core.api.Provider;
54 import org.opendaylight.controller.sal.core.api.model.SchemaService;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.Vpp;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppBuilder;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppState;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomainKey;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.state.BridgeDomains;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.state.bridge.domains.BridgeDomain;
61 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
62 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
63 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
64 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
65 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
66 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
67 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeBuilder;
68 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey;
69 import org.opendaylight.yangtools.binding.data.codec.api.BindingNormalizedNodeSerializer;
70 import org.opendaylight.yangtools.concepts.ObjectRegistration;
71 import org.opendaylight.yangtools.yang.binding.DataObject;
72 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
73 import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
74 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
75 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
76 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
77 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
78 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
79 import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
80 import org.opendaylight.yangtools.yang.data.impl.schema.tree.InMemoryDataTreeFactory;
81 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
82 import org.slf4j.Logger;
83 import org.slf4j.LoggerFactory;
84
85 /**
86  * Creates VppDataBroker which uses DataTree instead of DataStore internally in order to obtain better control over the
87  * data processing in Honeycomb agent
88  */
89 public final class VppDataBrokerInitializationProvider implements Provider, AutoCloseable {
90
91     private static final Logger LOG = LoggerFactory.getLogger(VppDataBrokerInitializationProvider.class);
92
93     private final TopologyId VPP_TOPOLOGY_ID = TopologyId.getDefaultInstance("vpp-topology");
94     private final NodeId VPP_TOPOLOGY_NODE_ID = NodeId.getDefaultInstance("vpp");
95     private final org.opendaylight.controller.md.sal.binding.api.DataBroker bindingBroker;
96     private final ReaderRegistry readerRegistry;
97     private final InstanceIdentifier<Node> mountPointPath;
98     private final WriterRegistry writerRegistry;
99     private final BindingNormalizedNodeSerializer serializer;
100     private ObjectRegistration<DOMMountPoint> mountPointRegistration;
101     private DOMDataBroker broker;
102
103     public VppDataBrokerInitializationProvider(
104             @Nonnull final org.opendaylight.controller.md.sal.binding.api.DataBroker bindingBroker,
105             final ReaderRegistry readerRegistry,
106             final WriterRegistry writerRegistry,
107             final BindingNormalizedNodeSerializer serializer) {
108         this.bindingBroker = checkNotNull(bindingBroker, "bindingBroker should not be null");
109         this.readerRegistry = checkNotNull(readerRegistry, "readerRegistry should not be null");
110         this.writerRegistry = checkNotNull(writerRegistry, "writerRegistry should not be null");
111         this.serializer = checkNotNull(serializer, "serializer should not be null");
112         this.mountPointPath = getMountPointPath();
113     }
114
115     // TODO make configurable
116     private InstanceIdentifier<Node> getMountPointPath() {
117         final InstanceIdentifier<NetworkTopology> networkTopology =
118                 InstanceIdentifier.builder(NetworkTopology.class).build();
119         final KeyedInstanceIdentifier<Topology, TopologyKey> topology =
120                 networkTopology.child(Topology.class, new TopologyKey(VPP_TOPOLOGY_ID));
121         return topology.child(Node.class, new NodeKey(VPP_TOPOLOGY_NODE_ID));
122     }
123
124     @Override
125     public void onSessionInitiated(final Broker.ProviderSession providerSession) {
126         LOG.info("Session initialized, providerSession={}", providerSession);
127         Preconditions.checkState(!isMountPointRegistered(), "Mount point is already registered");
128
129         final DOMMountPointService mountPointService = providerSession.getService(DOMMountPointService.class);
130         final SchemaService schemaService = providerSession.getService(SchemaService.class);
131
132         final SchemaContext globalContext = schemaService.getGlobalContext();
133         // final BindingNormalizedNodeSerializer serializer = initSerializer(globalContext);
134         final YangInstanceIdentifier path = serializer.toYangInstanceIdentifier(mountPointPath);
135
136         final DOMMountPointService.DOMMountPointBuilder mountPointBuilder = mountPointService.createMountPoint(path);
137         mountPointBuilder.addInitialSchemaContext(globalContext);
138
139         broker = initVppDataBroker(globalContext, serializer);
140         mountPointBuilder.addService(DOMDataBroker.class, broker);
141
142         mountPointRegistration = mountPointBuilder.register();
143         final DOMMountPoint mountPoint = mountPointRegistration.getInstance();
144         LOG.debug("Created mountPoint: identifier={}, schemaContext={}", mountPoint.getIdentifier(),
145                 mountPoint.getSchemaContext());
146
147         createMountPointPlaceholder();
148
149         // TODO initial sync has to go out of here
150 //        initialVppConfigSynchronization(broker);
151     }
152
153     @Override
154     public Collection<ProviderFunctionality> getProviderFunctionality() {
155         return Collections.EMPTY_LIST;
156     }
157
158     private boolean isMountPointRegistered() {
159         final ReadOnlyTransaction readTx = bindingBroker.newReadOnlyTransaction();
160         try {
161             final Optional<Node> cfgPlaceholder =
162                     readTx.read(LogicalDatastoreType.CONFIGURATION, mountPointPath).checkedGet();
163             final Optional<Node> operPlaceholder =
164                     readTx.read(LogicalDatastoreType.OPERATIONAL, mountPointPath).checkedGet();
165             return cfgPlaceholder.isPresent() || operPlaceholder.isPresent();
166         } catch (ReadFailedException e) {
167             throw new IllegalStateException("Failed to read mountpoint placeholder data", e);
168         }
169     }
170
171     private DOMDataBroker initVppDataBroker(final SchemaContext globalContext,
172                                             final BindingNormalizedNodeSerializer serializer) {
173         final ReadableDataTree operationalDataTree =
174                 new OperationalDataTree(serializer, globalContext, readerRegistry); // TODO make configurable
175
176         final DataTree dataTree =
177                 InMemoryDataTreeFactory.getInstance().create(TreeType.CONFIGURATION); // TODO make configurable
178         dataTree.setSchemaContext(globalContext);
179
180         final ModifiableDataTree configDataTree =
181                 new ConfigDataTree(serializer, dataTree, writerRegistry); // TODO make configurable
182
183         // init operational data tree before data broker is initialized
184
185         try {
186             initConfig(serializer, configDataTree);
187         } catch (Exception e) {
188             LOG.warn("Failed to initialize config", e);
189         }
190
191         return new DataBroker(operationalDataTree, configDataTree);
192     }
193
194     private void initConfig(final BindingNormalizedNodeSerializer serializer, final ModifiableDataTree configDataTree)
195             throws TranslationException, DataValidationFailedException {
196         LOG.info("Config initialization");
197
198         final Optional<? extends DataObject> data = readerRegistry.read(InstanceIdentifier.create(VppState.class), new ReadContextImpl());
199         LOG.info("Config initialization data={}", data);
200
201         if (data.isPresent()) {
202             // conversion
203             VppState vppOperationalData = (VppState) data.get();
204             final Vpp vppConfigData = convert(vppOperationalData);
205
206             final Map.Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> normalizedData =
207                     serializer.toNormalizedNode(InstanceIdentifier.create(Vpp.class), vppConfigData);
208
209             final DataTreeModification modification = configDataTree.takeSnapshot().newModification();
210             final YangInstanceIdentifier biPath = normalizedData.getKey();
211             final NormalizedNode<?, ?> biData = normalizedData.getValue();
212             LOG.info("Config initialization biPath={}, biData={}", biPath, biData);
213             modification.write(biPath, biData);
214             modification.ready();
215
216             LOG.info("Config writing modification ...");
217             configDataTree.modify(modification); // TODO do not write to VPP
218             LOG.info("Config writing modification written successfully.");
219         } else {
220             LOG.info("Data is not present");
221         }
222     }
223
224     private Vpp convert(final VppState vppState) {
225         final BridgeDomains bridgeDomains = vppState.getBridgeDomains();
226         final List<BridgeDomain> bridgeDomainList = bridgeDomains.getBridgeDomain();
227
228         VppBuilder vppBuilder = new VppBuilder();
229         org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.BridgeDomainsBuilder bdsBuilder = new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.BridgeDomainsBuilder();
230         final List<org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomain>
231                 listOfBDs = new ArrayList<>();
232
233         // TODO use reflexions
234         for (BridgeDomain bd : bridgeDomainList) {
235             org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomainBuilder bdBuilder = new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomainBuilder();
236             bdBuilder.setLearn(bd.isLearn());
237             bdBuilder.setUnknownUnicastFlood(bd.isUnknownUnicastFlood());
238             bdBuilder.setArpTermination(bd.isArpTermination());
239             bdBuilder.setFlood(bd.isFlood());
240             bdBuilder.setForward(bd.isForward());
241             bdBuilder.setKey(new BridgeDomainKey(bd.getKey().getName()));
242             // TODO bdBuilder.setL2Fib(bd.getL2Fib());
243             bdBuilder.setName(bd.getName());
244             listOfBDs.add(bdBuilder.build());
245         }
246
247         bdsBuilder.setBridgeDomain(listOfBDs);
248         vppBuilder.setBridgeDomains(bdsBuilder.build());
249         return vppBuilder.build();
250     }
251
252
253     // TODO move to utility module
254     private static final class ReadContextImpl implements ReadContext {
255         public final Context ctx = new Context();
256
257         @Nonnull
258         @Override
259         public Context getContext() {
260             return ctx;
261         }
262
263         @Override
264         public void close() {
265             // Make sure to clear the storage in case some customizer stored it  to prevent memory leaks
266             ctx.close();
267         }
268     }
269
270
271     /**
272      * Writes placeholder data into MD-SAL's global datastore to indicate the presence of VPP mountpoint.
273      */
274     private void createMountPointPlaceholder() {
275         final NodeBuilder nodeBuilder = new NodeBuilder();
276         nodeBuilder.setKey(new NodeKey(VPP_TOPOLOGY_NODE_ID));
277         final Node node = nodeBuilder.build();
278
279         final WriteTransaction writeTx = bindingBroker.newWriteOnlyTransaction();
280         writeTx.merge(LogicalDatastoreType.CONFIGURATION, mountPointPath, node, true);
281         writeTx.merge(LogicalDatastoreType.OPERATIONAL, mountPointPath, node, true);
282
283         try {
284             writeTx.submit().checkedGet();
285         } catch (TransactionCommitFailedException e) {
286             throw new IllegalStateException("Failed to create mountpoint placeholder", e);
287         }
288     }
289
290     // TODO operational and config models are not 1-1
291     // decide what part of operational data should be written to config during initialization
292     private void initialVppConfigSynchronization(final DOMDataBroker broker) {
293         // read from operational
294         final DOMDataReadOnlyTransaction readTx = broker.newReadOnlyTransaction();
295
296         final YangInstanceIdentifier
297                 id = YangInstanceIdentifier.builder().node(VppState.QNAME).node(BridgeDomains.QNAME).build();
298
299         LOG.trace("initialVppStateSynchronization id: {}", id);
300
301         final ListenableFuture<Void> writeFuture = Futures.transform(
302                 readTx.read(LogicalDatastoreType.OPERATIONAL, id),
303                 new AsyncFunction<Optional<NormalizedNode<?, ?>>, Void>() {
304                     @Override
305                     public ListenableFuture<Void> apply(final Optional<NormalizedNode<?, ?>> readResult)
306                             throws Exception {
307                         if (readResult.isPresent()) {
308                             final DOMDataWriteTransaction writeTx = broker.newWriteOnlyTransaction();
309                             final NormalizedNode<?, ?> node = readResult.get();
310                             LOG.trace("Read result: {}", node);
311
312                             // FIXME
313                             // this will fail because we are reading OPERATIONAL data and writing to CONFIGURATION
314                             // we need to provide extensible way to register initializer that would
315                             // translate between models
316
317                             // writeTx.put(LogicalDatastoreType.CONFIGURATION, id, node);
318                             return writeTx.submit();
319                         } else {
320                             return Futures
321                                     .immediateFailedFuture(
322                                             new IllegalStateException("Failed to read data from VPP."));
323                         }
324                     }
325                 });
326
327         Futures.addCallback(writeFuture,
328                 new LoggingFuturesCallBack<Void>("Initializing VPP config DataTree failed", LOG));
329     }
330
331     public Optional<DOMDataBroker> getBroker() {
332         return Optional.fromNullable(broker);
333     }
334
335     @Override
336     public void close() throws Exception {
337         if (mountPointRegistration != null) {
338             mountPointRegistration.close();
339         }
340
341         if (broker != null) {
342             broker = null;
343         }
344
345         // remove MD-SAL placeholder data for VPP mount point:
346         final WriteTransaction rwTx = bindingBroker.newWriteOnlyTransaction();
347         // does not fail if data is not present:
348         rwTx.delete(LogicalDatastoreType.CONFIGURATION, mountPointPath);
349         rwTx.delete(LogicalDatastoreType.OPERATIONAL, mountPointPath);
350         try {
351             rwTx.submit().checkedGet();
352         } catch (TransactionCommitFailedException e) {
353             throw new IllegalStateException("Failed to remove mountpoint's placeholder from MD-SAL's global datastore",
354                     e);
355         }
356     }
357 }