731f2ddf76d4d65b08910ea1c7717765c4a44918
[bgpcep.git] / graph / graph-impl / src / main / java / org / opendaylight / graph / impl / ConnectedGraphServer.java
1 /*
2  * Copyright (c) 2019 Orange. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.graph.impl;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Preconditions.checkState;
12 import static java.util.Objects.requireNonNull;
13
14 import com.google.common.util.concurrent.FluentFuture;
15 import com.google.common.util.concurrent.FutureCallback;
16 import com.google.common.util.concurrent.MoreExecutors;
17 import java.util.ArrayList;
18 import java.util.HashMap;
19 import java.util.List;
20 import java.util.Map;
21 import javax.annotation.PreDestroy;
22 import javax.inject.Inject;
23 import javax.inject.Singleton;
24 import org.eclipse.jdt.annotation.NonNull;
25 import org.opendaylight.graph.ConnectedGraph;
26 import org.opendaylight.graph.ConnectedGraphProvider;
27 import org.opendaylight.mdsal.binding.api.DataBroker;
28 import org.opendaylight.mdsal.binding.api.ReadWriteTransaction;
29 import org.opendaylight.mdsal.binding.api.Transaction;
30 import org.opendaylight.mdsal.binding.api.TransactionChain;
31 import org.opendaylight.mdsal.binding.api.TransactionChainListener;
32 import org.opendaylight.mdsal.binding.api.WriteTransaction;
33 import org.opendaylight.mdsal.common.api.CommitInfo;
34 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.graph.rev220720.GraphTopology;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.graph.rev220720.GraphTopologyBuilder;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.graph.rev220720.graph.topology.Graph;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.graph.rev220720.graph.topology.Graph.DomainScope;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.graph.rev220720.graph.topology.GraphBuilder;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.graph.rev220720.graph.topology.GraphKey;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.graph.rev220720.graph.topology.graph.Edge;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.graph.rev220720.graph.topology.graph.Prefix;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.graph.rev220720.graph.topology.graph.Vertex;
44 import org.opendaylight.yangtools.yang.binding.DataObject;
45 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
46 import org.osgi.service.component.annotations.Activate;
47 import org.osgi.service.component.annotations.Component;
48 import org.osgi.service.component.annotations.Deactivate;
49 import org.osgi.service.component.annotations.Reference;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 /**
54  * This Class Implements the DataStoreService interface providing the methods
55  * required to manage the network representation elements in the datastore.
56  *
57  * @author Olivier Dugeon
58  * @author Philippe Niger
59  */
60 @Singleton
61 @Component(immediate = true, service = ConnectedGraphProvider.class)
62 public final class ConnectedGraphServer implements ConnectedGraphProvider, TransactionChainListener, AutoCloseable {
63     private static final Logger LOG = LoggerFactory.getLogger(ConnectedGraphServer.class);
64     private static final @NonNull InstanceIdentifier<GraphTopology> GRAPH_TOPOLOGY_IDENTIFIER =
65         InstanceIdentifier.create(GraphTopology.class);
66
67     private final Map<GraphKey, ConnectedGraphImpl> graphs = new HashMap<>();
68     private final DataBroker dataBroker;
69
70     private TransactionChain chain = null;
71
72     @Inject
73     @Activate
74     public ConnectedGraphServer(@Reference final DataBroker dataBroker) {
75         this.dataBroker = requireNonNull(dataBroker);
76         initTransactionChain();
77         initOperationalGraphModel();
78         LOG.info("Graph Model Server started");
79     }
80
81     /**
82      * Remove the Operation Graph Model and destroy the transaction chain.
83      */
84     @Override
85     @Deactivate
86     @PreDestroy
87     public void close() {
88         destroyOperationalGraphModel();
89         destroyTransactionChain();
90     }
91
92     /**
93      * Reset a transaction chain by closing the current chain and starting a new one.
94      */
95     private synchronized void initTransactionChain() {
96         LOG.debug("Initializing transaction chain for Graph Model Server {}", this);
97         checkState(chain == null, "Transaction chain has to be closed before being initialized");
98         chain = dataBroker.createMergingTransactionChain(this);
99     }
100
101     /**
102      * Initialize GraphModel tree at Data Store top-level.
103      */
104     private synchronized void initOperationalGraphModel() {
105         requireNonNull(chain, "A valid transaction chain must be provided.");
106         final WriteTransaction trans = chain.newWriteOnlyTransaction();
107         LOG.info("Create Graph Model at top level in Operational DataStore: {}", GRAPH_TOPOLOGY_IDENTIFIER);
108         trans.put(LogicalDatastoreType.OPERATIONAL, GRAPH_TOPOLOGY_IDENTIFIER, new GraphTopologyBuilder().build());
109         trans.commit().addCallback(new FutureCallback<CommitInfo>() {
110             @Override
111             public void onSuccess(final CommitInfo result) {
112                 LOG.trace("Transaction {} committed successfully", trans.getIdentifier());
113             }
114
115             @Override
116             public void onFailure(final Throwable throwable) {
117                 LOG.error("Failed to initialize GraphModel {} (transaction {}) by listener {}",
118                     GRAPH_TOPOLOGY_IDENTIFIER, trans.getIdentifier(), ConnectedGraphServer.this, throwable);
119             }
120         }, MoreExecutors.directExecutor());
121     }
122
123     /**
124      * Destroy the current operational topology data. Note a valid transaction must be provided.
125      */
126     private synchronized FluentFuture<? extends CommitInfo> destroyOperationalGraphModel() {
127         requireNonNull(chain, "A valid transaction chain must be provided.");
128         final WriteTransaction trans = chain.newWriteOnlyTransaction();
129         trans.delete(LogicalDatastoreType.OPERATIONAL, GRAPH_TOPOLOGY_IDENTIFIER);
130         final FluentFuture<? extends CommitInfo> future = trans.commit();
131         future.addCallback(new FutureCallback<CommitInfo>() {
132             @Override
133             public void onSuccess(final CommitInfo result) {
134                 LOG.trace("Operational GraphModel removed {}", GRAPH_TOPOLOGY_IDENTIFIER);
135             }
136
137             @Override
138             public void onFailure(final Throwable throwable) {
139                 LOG.error("Unable to reset operational GraphModel {} (transaction {})", GRAPH_TOPOLOGY_IDENTIFIER,
140                     trans.getIdentifier(), throwable);
141             }
142         }, MoreExecutors.directExecutor());
143
144         /* Clear Connected Graph */
145         for (ConnectedGraph graph : graphs.values()) {
146             ((ConnectedGraphImpl) graph).clear();
147         }
148         return future;
149     }
150
151     /**
152      * Destroy the current transaction chain.
153      */
154     private synchronized void destroyTransactionChain() {
155         if (chain != null) {
156             LOG.debug("Destroy transaction chain for GraphModel {}", this);
157             chain = null;
158         }
159     }
160
161     /**
162      * Reset the transaction chain only so that the PingPong transaction chain
163      * will become usable again. However, there will be data loss if we do not
164      * apply the previous failed transaction again
165      */
166     protected synchronized void resetTransactionChain() {
167         LOG.debug("Resetting transaction chain for Graph builder");
168         destroyTransactionChain();
169         initTransactionChain();
170     }
171
172     /**
173      *  DataStore Instance Identifier creation for the various Graph components.
174      */
175     private static InstanceIdentifier<Graph> getGraphInstanceIdentifier(final String name) {
176         return GRAPH_TOPOLOGY_IDENTIFIER.child(Graph.class, new GraphKey(name));
177     }
178
179     private static InstanceIdentifier<Vertex> getVertexInstanceIdentifier(final Graph graph, final Vertex vertex) {
180         return GRAPH_TOPOLOGY_IDENTIFIER.child(Graph.class, graph.key()).child(Vertex.class, vertex.key());
181     }
182
183     private static InstanceIdentifier<Edge> getEdgeInstanceIdentifier(final Graph graph, final Edge edge) {
184         return GRAPH_TOPOLOGY_IDENTIFIER.child(Graph.class, graph.key()).child(Edge.class, edge.key());
185     }
186
187     private static InstanceIdentifier<Prefix> getPrefixInstanceIdentifier(final Graph graph, final Prefix prefix) {
188         return GRAPH_TOPOLOGY_IDENTIFIER.child(Graph.class, graph.key()).child(Prefix.class, prefix.key());
189     }
190
191     /**
192      * Add Graph or Graph components to the Data Store.
193      *
194      * @param <T>   As a generic method, T must be a Graph, Vertex, Edge or Prefix.
195      * @param id    Instance Identifier of the Data Object
196      * @param data  Data Object (Graph, Vertex, Edge or Prefix)
197      * @param info  Information to be logged
198      */
199     private synchronized <T extends DataObject> void addToDataStore(final InstanceIdentifier<T> id, final T data,
200             final String info) {
201         final ReadWriteTransaction trans = chain.newReadWriteTransaction();
202         trans.put(LogicalDatastoreType.OPERATIONAL, id, data);
203         trans.commit().addCallback(new FutureCallback<CommitInfo>() {
204             @Override
205             public void onSuccess(final CommitInfo result) {
206                 LOG.info("GraphModel: {} has been published in operational datastore ", info);
207             }
208
209             @Override
210             public void onFailure(final Throwable throwable) {
211                 LOG.error("GrahModel: Cannot write {} to the operational datastore (transaction: {})", info,
212                         trans.getIdentifier());
213             }
214         }, MoreExecutors.directExecutor());
215     }
216
217     /**
218      * Update Graph components (Vertex, Edge or Prefix ) to the Data Store. Old value identified by its Instance ID
219      * will be remove first before adding the new value.
220      *
221      * @param <T>   As a generic method, T must be a Vertex, Edge or Prefix.
222      * @param id    Instance Identifier of the Data Object
223      * @param data  Data Object (Vertex, Edge or Prefix)
224      * @param old   Instance Identifier of the previous version of the Data Object
225      * @param info  Information to be logged
226      */
227     private synchronized <T extends DataObject> void updateToDataStore(final InstanceIdentifier<T> id, final T data,
228             final InstanceIdentifier<T> old, final String info) {
229         final ReadWriteTransaction trans = chain.newReadWriteTransaction();
230         if (old != null) {
231             trans.delete(LogicalDatastoreType.OPERATIONAL, old);
232         }
233         trans.put(LogicalDatastoreType.OPERATIONAL, id, data);
234         trans.commit().addCallback(new FutureCallback<CommitInfo>() {
235             @Override
236             public void onSuccess(final CommitInfo result) {
237                 LOG.info("GraphModel: {} has been published in operational datastore ", info);
238             }
239
240             @Override
241             public void onFailure(final Throwable throwable) {
242                 LOG.error("GrahModel: Cannot write {} to the operational datastore (transaction: {})", info,
243                         trans.getIdentifier());
244             }
245         }, MoreExecutors.directExecutor());
246     }
247
248     /**
249      * Remove Graph or Graph components to the Data Store.
250      *
251      * @param <T>  As a generic method, T must be a Graph, Vertex, Edge or Prefix.
252      * @param id   Instance Identifier of the Data Object
253      * @param info Information to be logged
254      */
255     private synchronized <T extends DataObject> void removeFromDataStore(final InstanceIdentifier<T> id,
256             final String info) {
257         final ReadWriteTransaction trans = chain.newReadWriteTransaction();
258         trans.delete(LogicalDatastoreType.OPERATIONAL, id);
259         trans.commit().addCallback(new FutureCallback<CommitInfo>() {
260             @Override
261             public void onSuccess(final CommitInfo result) {
262                 LOG.info("GraphModel: {} has been deleted in operational datastore ", info);
263             }
264
265             @Override
266             public void onFailure(final Throwable throwable) {
267                 LOG.error("GraphModel: Cannot delete {} to the operational datastore (transaction: {})", info,
268                         trans.getIdentifier());
269             }
270         }, MoreExecutors.directExecutor());
271     }
272
273     /**
274      * Clear Graph. This method is used by the Connected Graph to clear associated Graph.
275      *
276      * @param graph Graph associated to the Connected Graph
277      */
278     public void clearGraph(final Graph graph) {
279         checkArgument(graph != null, "Provided Graph is a null object");
280         removeFromDataStore(getGraphInstanceIdentifier(graph.getName()), "Graph(" + graph.getName() + ")");
281         graphs.remove(graph.key());
282     }
283
284     /**
285      * Add Vertex to existing Graph. Old vertex is remove first. This method is called when a Connected Vertex is
286      * created (See addVertex() method from ConnectedGraph Interface).
287      *
288      * @param graph   Graph where the vertex will be stored
289      * @param vertex  Vertex to be inserted in the graph
290      * @param old     Old vertex when performing an update. Must be null for a simple addition
291      */
292     public void addVertex(final Graph graph, final Vertex vertex, final Vertex old) {
293         checkArgument(graph != null, "Provided Graph is a null object");
294         checkArgument(vertex != null, "Provided Vertex is a null object");
295         InstanceIdentifier<Vertex> oldId = null;
296         /* Remove old Vertex if it exists before storing the new Vertex */
297         if (old != null) {
298             oldId = getVertexInstanceIdentifier(graph, old);
299         }
300         updateToDataStore(getVertexInstanceIdentifier(graph, vertex), vertex, oldId,
301                 "Vertex(" + vertex.getName() + ")");
302     }
303
304     /**
305      * Remove Vertex to existing Graph. This method is called when a Connected Vertex is removed (See deleteVertex()
306      * method from ConnectedGraph Interface).
307      *
308      * @param graph   Graph where the vertex is stored
309      * @param vertex  Vertex to be removed
310      */
311     public void deleteVertex(final Graph graph, final Vertex vertex) {
312         checkArgument(graph != null, "Provided Graph is a null object");
313         checkArgument(vertex != null, "Provided Vertex is a null object");
314         removeFromDataStore(getVertexInstanceIdentifier(graph, vertex), "Vertex(" + vertex.getName() + ")");
315     }
316
317     /**
318      * Add Edge to existing Graph. Old edge is remove first. This method is called when a Connected Edge is
319      * created (See addEdge() method from ConnectedGraph Interface).
320      *
321      * @param graph  Graph where the edge will be stored
322      * @param edge   Edge to be inserted in the graph
323      * @param old    Old edge when performing an update. Must be null for a simple addition
324      */
325     public void addEdge(final Graph graph, final Edge edge, final Edge old) {
326         checkArgument(graph != null, "Provided Graph is a null object");
327         checkArgument(edge != null, "Provided Edge is a null object");
328         InstanceIdentifier<Edge> oldId = null;
329         /* Remove old Edge if it exists before storing the new Edge */
330         if (old != null) {
331             oldId = getEdgeInstanceIdentifier(graph, old);
332         }
333         updateToDataStore(getEdgeInstanceIdentifier(graph, edge), edge, oldId, "Edge(" + edge.getName() + ")");
334     }
335
336     /**
337      * Remove Edge to existing Graph. This method is called when a Connected Edge is removed (See deleteEdge()
338      * method from ConnectedGraph Interface).
339      *
340      * @param graph  Graph where the edge is stored
341      * @param edge   Edge to be removed
342      */
343     public void deleteEdge(final Graph graph, final Edge edge) {
344         checkArgument(graph != null, "Provided Graph is a null object");
345         checkArgument(edge != null, "Provided Edge is a null object");
346         removeFromDataStore(getEdgeInstanceIdentifier(graph, edge), "Edge(" + edge.getName() + ")");
347     }
348
349     /**
350      * Add Prefix to existing Graph. This method is called when a Prefix is added to a Connected Vertex
351      * (See addPrefix() method from ConnectedGraph Interface).
352      *
353      * @param graph  Graph where the prefix will be stored
354      * @param prefix Prefix to be interted in the graph
355      */
356     public void addPrefix(final Graph graph, final Prefix prefix) {
357         checkArgument(graph != null, "Provided Graph is a null object");
358         checkArgument(prefix != null, "Provided Prefix is a null object");
359         addToDataStore(getPrefixInstanceIdentifier(graph, prefix), prefix, "Prefix(" + prefix.getPrefix() + ")");
360     }
361
362     /**
363      * Remove Prefix to existing Graph. This method is called when a Prefix is removed from a Connected Vertex
364      * (See deletePrefix() method from ConnectedGraph Interface).
365      *
366      * @param graph  Graph where the prefix is stored
367      * @param prefix Prefix to be removed
368      */
369     public void deletePrefix(final Graph graph, final Prefix prefix) {
370         checkArgument(graph != null, "Provided Graph is a null object");
371         checkArgument(prefix != null, "Provided Prefix is a null object");
372         removeFromDataStore(getPrefixInstanceIdentifier(graph, prefix), "Prefix(" + prefix.getPrefix() + ")");
373     }
374
375     @Override
376     public synchronized void onTransactionChainFailed(final TransactionChain transactionChain,
377             final Transaction transaction, final Throwable cause) {
378         LOG.error("GraphModel builder for {} failed in transaction: {} ", GRAPH_TOPOLOGY_IDENTIFIER,
379                 transaction != null ? transaction.getIdentifier() : null, cause);
380     }
381
382     @Override
383     public void onTransactionChainSuccessful(final TransactionChain transactionChain) {
384         LOG.info("GraphModel builder for {} shut down", GRAPH_TOPOLOGY_IDENTIFIER);
385     }
386
387     @Override
388     public List<ConnectedGraph> getConnectedGraphs() {
389         return new ArrayList<>(graphs.values());
390     }
391
392     @Override
393     public ConnectedGraph getConnectedGraph(final GraphKey key) {
394         return graphs.get(key);
395     }
396
397     @Override
398     public ConnectedGraph getConnectedGraph(final String name) {
399         return graphs.get(new GraphKey(name));
400     }
401
402     @Override
403     public Graph getGraph(final GraphKey key) {
404         if (graphs.containsKey(key)) {
405             return graphs.get(key).getGraph();
406         } else {
407             return null;
408         }
409     }
410
411     @Override
412     public Graph getGraph(final String name) {
413         return getGraph(new GraphKey(name));
414     }
415
416     @Override
417     public ConnectedGraph createConnectedGraph(final String name, final DomainScope scope) {
418         Graph graph = new GraphBuilder()
419                 .setName(name)
420                 .setDomainScope(scope)
421                 .build();
422         addToDataStore(getGraphInstanceIdentifier(name), graph, "Graph(" + name + ")");
423         ConnectedGraphImpl cgraph = new ConnectedGraphImpl(graph, this);
424         graphs.put(graph.key(), cgraph);
425         return cgraph;
426     }
427
428     @Override
429     public ConnectedGraph addGraph(final Graph graph) {
430         checkArgument(graph != null, "Provided Graph is a null object");
431         addToDataStore(getGraphInstanceIdentifier(graph.getName()), graph, "Graph(" + graph.getName() + ")");
432         ConnectedGraphImpl cgraph = new ConnectedGraphImpl(graph, this);
433         graphs.put(graph.key(), cgraph);
434         return cgraph;
435     }
436
437     @Override
438     public void deleteGraph(final GraphKey key) {
439         checkArgument(key != null, "Provided Graph Key is a null object");
440         ConnectedGraphImpl cgraph = graphs.get(key);
441         /*
442          * Remove the corresponding Connected Graph which will delete the graph
443          * by calling clearGraph() method (see above)
444          */
445         if (cgraph != null) {
446             cgraph.clear();
447         }
448     }
449 }