Bump versions to 0.21.6-SNAPSHOT
[bgpcep.git] / pcep / tunnel / tunnel-provider / src / main / java / org / opendaylight / bgpcep / pcep / tunnel / provider / NodeChangedListener.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  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.bgpcep.pcep.tunnel.provider;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.util.concurrent.FutureCallback;
13 import com.google.common.util.concurrent.MoreExecutors;
14 import java.util.ArrayList;
15 import java.util.HashMap;
16 import java.util.HashSet;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.Optional;
20 import java.util.Set;
21 import java.util.concurrent.ExecutionException;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.opendaylight.mdsal.binding.api.DataBroker;
24 import org.opendaylight.mdsal.binding.api.DataObjectModification;
25 import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
26 import org.opendaylight.mdsal.binding.api.DataTreeModification;
27 import org.opendaylight.mdsal.binding.api.ReadWriteTransaction;
28 import org.opendaylight.mdsal.common.api.CommitInfo;
29 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
30 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.ietf.stateful.rev200720.AdministrativeStatus;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.ietf.stateful.rev200720.Path1;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.ietf.stateful.rev200720.lsp.identifiers.tlv.lsp.identifiers.AddressFamily;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.ietf.stateful.rev200720.lsp.identifiers.tlv.lsp.identifiers.address.family.Ipv4Case;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.ietf.stateful.rev200720.lsp.identifiers.tlv.lsp.identifiers.address.family.Ipv6Case;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.ietf.stateful.rev200720.lsp.identifiers.tlv.lsp.identifiers.address.family.ipv4._case.Ipv4;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.ietf.stateful.rev200720.lsp.identifiers.tlv.lsp.identifiers.address.family.ipv6._case.Ipv6;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.pcep.rev220730.Node1;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.pcep.rev220730.pcep.client.attributes.PathComputationClient;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.pcep.rev220730.pcep.client.attributes.path.computation.client.ReportedLsp;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.pcep.rev220730.pcep.client.attributes.path.computation.client.reported.lsp.Path;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.tunnel.pcep.rev181109.Link1Builder;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.tunnel.pcep.rev181109.SupportingNode1Builder;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.tunnel.pcep.rev181109.tunnel.pcep.supporting.node.attributes.PathComputationClientBuilder;
45 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.LinkId;
46 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
47 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
48 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TpId;
49 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.link.attributes.DestinationBuilder;
50 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.link.attributes.SourceBuilder;
51 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
52 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Link;
53 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.LinkBuilder;
54 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.LinkKey;
55 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
56 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeBuilder;
57 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey;
58 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.node.TerminationPoint;
59 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.node.TerminationPointBuilder;
60 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.node.TerminationPointKey;
61 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.node.attributes.SupportingNode;
62 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.node.attributes.SupportingNodeBuilder;
63 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.node.attributes.SupportingNodeKey;
64 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.nt.l3.unicast.igp.topology.rev131021.TerminationPoint1;
65 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.nt.l3.unicast.igp.topology.rev131021.TerminationPoint1Builder;
66 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.nt.l3.unicast.igp.topology.rev131021.igp.termination.point.attributes.IgpTerminationPointAttributesBuilder;
67 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.nt.l3.unicast.igp.topology.rev131021.igp.termination.point.attributes.igp.termination.point.attributes.TerminationPointType;
68 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.nt.l3.unicast.igp.topology.rev131021.igp.termination.point.attributes.igp.termination.point.attributes.termination.point.type.Ip;
69 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.nt.l3.unicast.igp.topology.rev131021.igp.termination.point.attributes.igp.termination.point.attributes.termination.point.type.IpBuilder;
70 import org.opendaylight.yangtools.yang.binding.DataObject;
71 import org.opendaylight.yangtools.yang.binding.DataObjectStep;
72 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
73 import org.opendaylight.yangtools.yang.binding.util.BindingMap;
74 import org.slf4j.Logger;
75 import org.slf4j.LoggerFactory;
76
77 public final class NodeChangedListener implements DataTreeChangeListener<Node> {
78     private static final Logger LOG = LoggerFactory.getLogger(NodeChangedListener.class);
79     private final InstanceIdentifier<Topology> target;
80     private final DataBroker dataProvider;
81     private final TopologyId source;
82
83     NodeChangedListener(final DataBroker dataProvider, final TopologyId source,
84             final InstanceIdentifier<Topology> target) {
85         this.dataProvider = requireNonNull(dataProvider);
86         this.target = requireNonNull(target);
87         this.source = requireNonNull(source);
88     }
89
90     private static void categorizeIdentifier(final InstanceIdentifier<?> identifier,
91             final Set<InstanceIdentifier<ReportedLsp>> changedLsps,
92             final Set<InstanceIdentifier<Node>> changedNodes) {
93         final InstanceIdentifier<ReportedLsp> li = identifier.firstIdentifierOf(ReportedLsp.class);
94         if (li == null) {
95             final InstanceIdentifier<Node> ni = identifier.firstIdentifierOf(Node.class);
96             if (ni == null) {
97                 LOG.warn("Ignoring uncategorized identifier {}", identifier);
98             } else {
99                 changedNodes.add(ni);
100             }
101         } else {
102             changedLsps.add(li);
103         }
104     }
105
106     private static void enumerateLsps(final InstanceIdentifier<Node> id, final Node node,
107             final Set<InstanceIdentifier<ReportedLsp>> lsps) {
108         if (node == null) {
109             LOG.trace("Skipping null node {}", id);
110             return;
111         }
112         final Node1 pccnode = node.augmentation(Node1.class);
113         if (pccnode == null) {
114             LOG.trace("Skipping non-PCEP-enabled node {}", id);
115             return;
116         }
117
118         for (final ReportedLsp l : pccnode.getPathComputationClient().nonnullReportedLsp().values()) {
119             lsps.add(id.builder().augmentation(Node1.class).child(PathComputationClient.class)
120                     .child(ReportedLsp.class, l.key()).build());
121         }
122     }
123
124     private static LinkId linkIdForLsp(final InstanceIdentifier<ReportedLsp> identifier, final ReportedLsp lsp) {
125         return new LinkId(identifier.firstKeyOf(Node.class).getNodeId().getValue() + "/lsps/" + lsp.getName());
126     }
127
128     public static InstanceIdentifier<Link> linkIdentifier(final InstanceIdentifier<Topology> topology,
129             final NodeId node, final String name) {
130         return topology.child(Link.class, new LinkKey(new LinkId(node.getValue() + "/lsp/" + name)));
131     }
132
133     private InstanceIdentifier<Link> linkForLsp(final LinkId linkId) {
134         return target.child(Link.class, new LinkKey(linkId));
135     }
136
137     private SupportingNode createSupportingNode(final NodeId sni, final Boolean inControl) {
138         return new SupportingNodeBuilder()
139                 .setNodeRef(sni)
140                 .withKey(new SupportingNodeKey(sni, source))
141                 .addAugmentation(new SupportingNode1Builder().setPathComputationClient(
142                     new PathComputationClientBuilder().setControlling(inControl).build()).build())
143                 .build();
144     }
145
146     private void handleSni(final InstanceIdentifier<Node> sni, final Node node, final Boolean inControl,
147             final ReadWriteTransaction trans) {
148         if (sni != null) {
149             final NodeKey k = InstanceIdentifier.keyOf(sni);
150             boolean have = false;
151             /*
152              * We may have found a termination point which has been created as a destination,
153              * so it does not have a supporting node pointer. Since we now know what it is,
154              * fill it in.
155              */
156             for (final SupportingNode sn : node.nonnullSupportingNode().values()) {
157                 if (sn.getNodeRef().equals(k.getNodeId())) {
158                     have = true;
159                     break;
160                 }
161             }
162             if (!have) {
163                 final SupportingNode sn = createSupportingNode(k.getNodeId(), inControl);
164                 trans.put(LogicalDatastoreType.OPERATIONAL,
165                     target.child(Node.class, node.key()).child(SupportingNode.class, sn.key()), sn);
166             }
167         }
168     }
169
170     private InstanceIdentifier<TerminationPoint> getIpTerminationPoint(final ReadWriteTransaction trans,
171             final IpAddress addr, final InstanceIdentifier<Node> sni, final Boolean inControl)
172             throws ExecutionException, InterruptedException {
173         final Topology topo = trans.read(LogicalDatastoreType.OPERATIONAL, target).get().orElseThrow();
174         for (final Node n : topo.nonnullNode().values()) {
175             for (final TerminationPoint tp : n.nonnullTerminationPoint().values()) {
176                 final TerminationPoint1 tpa = tp.augmentation(TerminationPoint1.class);
177                 if (tpa != null) {
178                     final TerminationPointType tpt = tpa.getIgpTerminationPointAttributes()
179                             .getTerminationPointType();
180                     if (tpt instanceof Ip) {
181                         for (final IpAddress address : ((Ip) tpt).getIpAddress()) {
182                             if (addr.equals(address)) {
183                                 handleSni(sni, n, inControl, trans);
184                                 return target.builder().child(Node.class, n.key())
185                                         .child(TerminationPoint.class, tp.key()).build();
186                             }
187                         }
188                     } else {
189                         LOG.debug("Ignoring termination point type {}", tpt);
190                     }
191                 }
192             }
193         }
194         LOG.debug("Termination point for {} not found, creating a new one", addr);
195         return createTP(addr, sni, inControl, trans);
196     }
197
198     private InstanceIdentifier<TerminationPoint> createTP(final IpAddress addr, final InstanceIdentifier<Node> sni,
199             final Boolean inControl, final ReadWriteTransaction trans) {
200         final String url = "ip://" + addr.toString();
201         final TerminationPointKey tpk = new TerminationPointKey(new TpId(url));
202         final TerminationPointBuilder tpb = new TerminationPointBuilder();
203         tpb.withKey(tpk).setTpId(tpk.getTpId());
204         tpb.addAugmentation(new TerminationPoint1Builder()
205             .setIgpTerminationPointAttributes(new IgpTerminationPointAttributesBuilder()
206                 .setTerminationPointType(new IpBuilder().setIpAddress(Set.of(addr)).build())
207                 .build())
208             .build());
209
210         final NodeKey nk = new NodeKey(new NodeId(url));
211         final NodeBuilder nb = new NodeBuilder();
212         nb.withKey(nk).setNodeId(nk.getNodeId());
213         nb.setTerminationPoint(BindingMap.of(tpb.build()));
214         if (sni != null) {
215             nb.setSupportingNode(BindingMap.of(createSupportingNode(InstanceIdentifier.keyOf(sni).getNodeId(),
216                     inControl)));
217         }
218         final InstanceIdentifier<Node> nid = target.child(Node.class, nb.key());
219         trans.put(LogicalDatastoreType.OPERATIONAL, nid, nb.build());
220         return nid.child(TerminationPoint.class, tpb.key());
221     }
222
223     private void create(final ReadWriteTransaction trans, final InstanceIdentifier<ReportedLsp> identifier,
224             final ReportedLsp value) throws ExecutionException, InterruptedException {
225         final InstanceIdentifier<Node> ni = identifier.firstIdentifierOf(Node.class);
226
227         final Path1 rl = value.nonnullPath().values().iterator().next().augmentation(Path1.class);
228
229         final AddressFamily af = rl.getLsp().getTlvs().getLspIdentifiers().getAddressFamily();
230
231         /*
232          * We are trying to ensure we have source and destination nodes.
233          */
234         final IpAddress srcIp;
235         final IpAddress dstIp;
236         if (af instanceof Ipv4Case ipv4case) {
237             final Ipv4 ipv4 = ipv4case.getIpv4();
238             srcIp = new IpAddress(ipv4.getIpv4TunnelSenderAddress());
239             dstIp = new IpAddress(ipv4.getIpv4TunnelEndpointAddress());
240         } else if (af instanceof Ipv6Case ipv6case) {
241             final Ipv6 ipv6 = ipv6case.getIpv6();
242             srcIp = new IpAddress(ipv6.getIpv6TunnelSenderAddress());
243             dstIp = new IpAddress(ipv6.getIpv6TunnelSenderAddress());
244         } else {
245             throw new IllegalArgumentException("Unsupported address family: " + af.implementedInterface());
246         }
247
248         final Path path0 = value.nonnullPath().values().iterator().next();
249         final Link1Builder lab = new Link1Builder();
250         if (path0.getBandwidth() != null) {
251             lab.setBandwidth(path0.getBandwidth().getBandwidth());
252         }
253         if (path0.getClassType() != null) {
254             lab.setClassType(path0.getClassType().getClassType());
255         }
256         lab.setSymbolicPathName(value.getName());
257
258         final InstanceIdentifier<TerminationPoint> dst = getIpTerminationPoint(trans, dstIp, null, Boolean.FALSE);
259         final InstanceIdentifier<TerminationPoint> src = getIpTerminationPoint(trans, srcIp, ni,
260                 rl.getLsp().getDelegate());
261
262         final org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.ietf.stateful.rev200720
263                 .Link1Builder slab = new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.ietf
264                 .stateful.rev200720.Link1Builder();
265         slab.setOperationalStatus(rl.getLsp().getOperational());
266         slab.setAdministrativeStatus(rl.getLsp().getAdministrative() ? AdministrativeStatus.Active :
267                 AdministrativeStatus.Inactive);
268
269         final LinkId id = linkIdForLsp(identifier, value);
270         final LinkBuilder lb = new LinkBuilder();
271         lb.setLinkId(id);
272
273         lb.setSource(new SourceBuilder().setSourceNode(src.firstKeyOf(Node.class).getNodeId())
274                 .setSourceTp(src.firstKeyOf(TerminationPoint.class).getTpId()).build());
275         lb.setDestination(new DestinationBuilder().setDestNode(dst.firstKeyOf(Node.class).getNodeId())
276                 .setDestTp(dst.firstKeyOf(TerminationPoint.class).getTpId()).build());
277         lb.addAugmentation(lab.build());
278         lb.addAugmentation(slab.build());
279
280         trans.put(LogicalDatastoreType.OPERATIONAL, linkForLsp(id), lb.build());
281     }
282
283     private InstanceIdentifier<TerminationPoint> tpIdentifier(final NodeId node, final TpId tp) {
284         return target.builder().child(Node.class, new NodeKey(node)).child(TerminationPoint.class,
285                 new TerminationPointKey(tp)).build();
286     }
287
288     private @NonNull InstanceIdentifier<Node> nodeIdentifier(final NodeId node) {
289         return target.child(Node.class, new NodeKey(node));
290     }
291
292     private void remove(final ReadWriteTransaction trans, final InstanceIdentifier<ReportedLsp> identifier,
293             final ReportedLsp value) throws ExecutionException, InterruptedException {
294         final InstanceIdentifier<Link> li = linkForLsp(linkIdForLsp(identifier, value));
295
296         final Optional<Link> ol = trans.read(LogicalDatastoreType.OPERATIONAL, li).get();
297         if (ol.isEmpty()) {
298             return;
299         }
300
301         final Link l = ol.orElseThrow();
302         LOG.debug("Removing link {} (was {})", li, l);
303         trans.delete(LogicalDatastoreType.OPERATIONAL, li);
304
305         LOG.debug("Searching for orphan links/nodes");
306         final Optional<Topology> ot = trans.read(LogicalDatastoreType.OPERATIONAL, target).get();
307
308         final Topology topology = ot.orElseThrow(IllegalStateException::new);
309         final NodeId srcNode = l.getSource().getSourceNode();
310         final NodeId dstNode = l.getDestination().getDestNode();
311         final TpId srcTp = l.getSource().getSourceTp();
312         final TpId dstTp = l.getDestination().getDestTp();
313
314         boolean orphSrcNode = true;
315         boolean orphDstNode = true;
316         boolean orphDstTp = true;
317         boolean orphSrcTp = true;
318         for (final Link lw : topology.nonnullLink().values()) {
319             LOG.trace("Checking link {}", lw);
320
321             final NodeId sn = lw.getSource().getSourceNode();
322             final NodeId dn = lw.getDestination().getDestNode();
323             final TpId st = lw.getSource().getSourceTp();
324             final TpId dt = lw.getDestination().getDestTp();
325
326             // Source node checks
327             if (srcNode.equals(sn)) {
328                 if (orphSrcNode) {
329                     LOG.debug("Node {} held by source of link {}", srcNode, lw);
330                     orphSrcNode = false;
331                 }
332                 if (orphSrcTp && srcTp.equals(st)) {
333                     LOG.debug("TP {} held by source of link {}", srcTp, lw);
334                     orphSrcTp = false;
335                 }
336             }
337             if (srcNode.equals(dn)) {
338                 if (orphSrcNode) {
339                     LOG.debug("Node {} held by destination of link {}", srcNode, lw);
340                     orphSrcNode = false;
341                 }
342                 if (orphSrcTp && srcTp.equals(dt)) {
343                     LOG.debug("TP {} held by destination of link {}", srcTp, lw);
344                     orphSrcTp = false;
345                 }
346             }
347
348             // Destination node checks
349             if (dstNode.equals(sn)) {
350                 if (orphDstNode) {
351                     LOG.debug("Node {} held by source of link {}", dstNode, lw);
352                     orphDstNode = false;
353                 }
354                 if (orphDstTp && dstTp.equals(st)) {
355                     LOG.debug("TP {} held by source of link {}", dstTp, lw);
356                     orphDstTp = false;
357                 }
358             }
359             if (dstNode.equals(dn)) {
360                 if (orphDstNode) {
361                     LOG.debug("Node {} held by destination of link {}", dstNode, lw);
362                     orphDstNode = false;
363                 }
364                 if (orphDstTp && dstTp.equals(dt)) {
365                     LOG.debug("TP {} held by destination of link {}", dstTp, lw);
366                     orphDstTp = false;
367                 }
368             }
369         }
370
371         if (orphSrcNode && !orphSrcTp) {
372             LOG.warn("Orphan source node {} but not TP {}, retaining the node", srcNode, srcTp);
373             orphSrcNode = false;
374         }
375         if (orphDstNode && !orphDstTp) {
376             LOG.warn("Orphan destination node {} but not TP {}, retaining the node", dstNode, dstTp);
377             orphDstNode = false;
378         }
379
380         if (orphSrcNode) {
381             LOG.debug("Removing orphan node {}", srcNode);
382             trans.delete(LogicalDatastoreType.OPERATIONAL, nodeIdentifier(srcNode));
383         } else if (orphSrcTp) {
384             LOG.debug("Removing orphan TP {} on node {}", srcTp, srcNode);
385             trans.delete(LogicalDatastoreType.OPERATIONAL, tpIdentifier(srcNode, srcTp));
386         }
387         if (orphDstNode) {
388             LOG.debug("Removing orphan node {}", dstNode);
389             trans.delete(LogicalDatastoreType.OPERATIONAL, nodeIdentifier(dstNode));
390         } else if (orphDstTp) {
391             LOG.debug("Removing orphan TP {} on node {}", dstTp, dstNode);
392             trans.delete(LogicalDatastoreType.OPERATIONAL, tpIdentifier(dstNode, dstTp));
393         }
394     }
395
396     @Override
397     public void onDataTreeChanged(final List<DataTreeModification<Node>> changes) {
398         final var trans = dataProvider.newReadWriteTransaction();
399
400         final var lsps = new HashSet<InstanceIdentifier<ReportedLsp>>();
401         final var nodes = new HashSet<InstanceIdentifier<Node>>();
402
403         final var original = new HashMap<InstanceIdentifier<?>, DataObject>();
404         final var updated = new HashMap<InstanceIdentifier<?>, DataObject>();
405         final var created = new HashMap<InstanceIdentifier<?>, DataObject>();
406
407         for (final var change : changes) {
408             handleChangedNode(change.getRootNode(), change.getRootPath().path(), lsps, nodes, original, updated,
409                 created);
410         }
411
412         // Now walk all nodes, check for removals/additions and cascade them to LSPs
413         for (var iid : nodes) {
414             enumerateLsps(iid, (Node) original.get(iid), lsps);
415             enumerateLsps(iid, (Node) updated.get(iid), lsps);
416             enumerateLsps(iid, (Node) created.get(iid), lsps);
417         }
418
419         // We now have list of all affected LSPs. Walk them create/remove them
420         updateTransaction(trans, lsps, original, updated, created);
421
422         trans.commit().addCallback(new FutureCallback<CommitInfo>() {
423             @Override
424             public void onSuccess(final CommitInfo result) {
425                 LOG.trace("Topology change committed successfully");
426             }
427
428             @Override
429             public void onFailure(final Throwable throwable) {
430                 LOG.error("Failed to propagate a topology change, target topology became inconsistent", throwable);
431             }
432         }, MoreExecutors.directExecutor());
433     }
434
435     private void handleChangedNode(final DataObjectModification<?> changedNode, final InstanceIdentifier<?> iid,
436             final Set<InstanceIdentifier<ReportedLsp>> lsps, final Set<InstanceIdentifier<Node>> nodes,
437             final Map<InstanceIdentifier<?>, DataObject> original, final Map<InstanceIdentifier<?>, DataObject> updated,
438             final Map<InstanceIdentifier<?>, DataObject> created) {
439
440         // Categorize reported identifiers
441         categorizeIdentifier(iid, lsps, nodes);
442
443         // Get the subtrees
444         switch (changedNode.modificationType()) {
445             case DELETE:
446                 original.put(iid, changedNode.dataBefore());
447                 break;
448             case SUBTREE_MODIFIED:
449                 original.put(iid, changedNode.dataBefore());
450                 updated.put(iid, changedNode.dataAfter());
451                 break;
452             case WRITE:
453                 created.put(iid, changedNode.dataAfter());
454                 break;
455             default:
456                 throw new IllegalArgumentException("Unhandled modification type " + changedNode.modificationType());
457         }
458
459         for (var child : changedNode.modifiedChildren()) {
460             final var pathArguments = new ArrayList<DataObjectStep<?>>();
461             iid.getPathArguments().forEach(pathArguments::add);
462             pathArguments.add(child.step());
463             final var childIID = InstanceIdentifier.unsafeOf(pathArguments);
464             handleChangedNode(child, childIID, lsps, nodes, original, updated, created);
465         }
466     }
467
468     private void updateTransaction(final ReadWriteTransaction trans,
469             final Set<InstanceIdentifier<ReportedLsp>> lsps,
470             final Map<InstanceIdentifier<?>, ? extends DataObject> old,
471             final Map<InstanceIdentifier<?>, DataObject> updated,
472             final Map<InstanceIdentifier<?>, DataObject> created) {
473
474         for (final InstanceIdentifier<ReportedLsp> i : lsps) {
475             final ReportedLsp oldValue = (ReportedLsp) old.get(i);
476             ReportedLsp newValue = (ReportedLsp) updated.get(i);
477             if (newValue == null) {
478                 newValue = (ReportedLsp) created.get(i);
479             }
480
481             LOG.debug("Updating lsp {} value {} -> {}", i, oldValue, newValue);
482             if (oldValue != null) {
483                 try {
484                     remove(trans, i, oldValue);
485                 } catch (final ExecutionException | InterruptedException e) {
486                     LOG.warn("Failed to remove LSP {}", i, e);
487                 }
488             }
489             if (newValue != null) {
490                 try {
491                     create(trans, i, newValue);
492                 } catch (final ExecutionException | InterruptedException e) {
493                     LOG.warn("Failed to add LSP {}", i, e);
494                 }
495             }
496         }
497     }
498
499     DataBroker getDataProvider() {
500         return dataProvider;
501     }
502 }