Fixed various small bugs found by Findbugs plugin.
[bgpcep.git] / pcep / 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 com.google.common.base.Preconditions;
11 import com.google.common.collect.Lists;
12 import com.google.common.util.concurrent.FutureCallback;
13 import com.google.common.util.concurrent.Futures;
14 import com.google.common.util.concurrent.JdkFutureAdapters;
15 import java.util.HashSet;
16 import java.util.Map;
17 import java.util.Set;
18 import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
19 import org.opendaylight.controller.md.sal.common.api.data.DataChangeEvent;
20 import org.opendaylight.controller.sal.binding.api.data.DataChangeListener;
21 import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction;
22 import org.opendaylight.controller.sal.binding.api.data.DataProviderService;
23 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpAddress;
24 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.ietf.stateful.rev131222.AdministrativeStatus;
25 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.ietf.stateful.rev131222.Path1;
26 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.ietf.stateful.rev131222.lsp.identifiers.tlv.lsp.identifiers.AddressFamily;
27 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.endpoints.address.family.Ipv4Case;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.endpoints.address.family.Ipv6Case;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.endpoints.address.family.ipv4._case.Ipv4;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.endpoints.address.family.ipv6._case.Ipv6;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.pcep.rev131024.Node1;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.pcep.rev131024.pcep.client.attributes.PathComputationClient;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.pcep.rev131024.pcep.client.attributes.path.computation.client.ReportedLsp;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.tunnel.pcep.rev130820.Link1;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.tunnel.pcep.rev130820.Link1Builder;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.tunnel.pcep.rev130820.SupportingNode1;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.tunnel.pcep.rev130820.SupportingNode1Builder;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.tunnel.pcep.rev130820.tunnel.pcep.supporting.node.attributes.PathComputationClientBuilder;
39 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.LinkId;
40 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
41 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TpId;
42 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.link.attributes.DestinationBuilder;
43 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.link.attributes.SourceBuilder;
44 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
45 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Link;
46 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.LinkBuilder;
47 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.LinkKey;
48 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
49 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeBuilder;
50 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey;
51 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.node.TerminationPoint;
52 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.node.TerminationPointBuilder;
53 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.node.TerminationPointKey;
54 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.node.attributes.SupportingNode;
55 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.node.attributes.SupportingNodeBuilder;
56 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.node.attributes.SupportingNodeKey;
57 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.nt.l3.unicast.igp.topology.rev131021.TerminationPoint1;
58 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.nt.l3.unicast.igp.topology.rev131021.TerminationPoint1Builder;
59 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.nt.l3.unicast.igp.topology.rev131021.igp.termination.point.attributes.IgpTerminationPointAttributesBuilder;
60 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;
61 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;
62 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;
63 import org.opendaylight.yangtools.yang.binding.DataObject;
64 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
65 import org.opendaylight.yangtools.yang.common.RpcResult;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
68
69 public final class NodeChangedListener implements DataChangeListener {
70     private static final Logger LOG = LoggerFactory.getLogger(NodeChangedListener.class);
71     private final InstanceIdentifier<Topology> target;
72     private final DataProviderService dataProvider;
73
74     NodeChangedListener(final DataProviderService dataProvider, final InstanceIdentifier<Topology> target) {
75         this.dataProvider = Preconditions.checkNotNull(dataProvider);
76         this.target = Preconditions.checkNotNull(target);
77     }
78
79     private static void categorizeIdentifier(final InstanceIdentifier<?> i, final Set<InstanceIdentifier<ReportedLsp>> changedLsps,
80             final Set<InstanceIdentifier<Node>> changedNodes) {
81         final InstanceIdentifier<ReportedLsp> li = i.firstIdentifierOf(ReportedLsp.class);
82         if (li == null) {
83             final InstanceIdentifier<Node> ni = i.firstIdentifierOf(Node.class);
84             if (ni == null) {
85                 LOG.warn("Ignoring uncategorized identifier {}", i);
86             } else {
87                 changedNodes.add(ni);
88             }
89         } else {
90             changedLsps.add(li);
91         }
92     }
93
94     private static void enumerateLsps(final InstanceIdentifier<Node> id, final Node node, final Set<InstanceIdentifier<ReportedLsp>> lsps) {
95         if (node == null) {
96             LOG.trace("Skipping null node", id);
97             return;
98         }
99         final Node1 pccnode = node.getAugmentation(Node1.class);
100         if (pccnode == null) {
101             LOG.trace("Skipping non-PCEP-enabled node {}", id);
102             return;
103         }
104
105         for (final ReportedLsp l : pccnode.getPathComputationClient().getReportedLsp()) {
106             lsps.add(id.builder().augmentation(Node1.class).child(PathComputationClient.class).child(ReportedLsp.class, l.getKey()).toInstance());
107         }
108     }
109
110     private static LinkId linkIdForLsp(final InstanceIdentifier<ReportedLsp> i, final ReportedLsp lsp) {
111         return new LinkId(i.firstKeyOf(Node.class, NodeKey.class).getNodeId().getValue() + "/lsps/" + lsp.getName());
112     }
113
114     private InstanceIdentifier<Link> linkForLsp(final LinkId linkId) {
115         return this.target.child(Link.class, new LinkKey(linkId));
116     }
117
118     private SupportingNode createSupportingNode(final NodeId sni, final Boolean inControl) {
119         final SupportingNodeKey sk = new SupportingNodeKey(sni);
120         final SupportingNodeBuilder snb = new SupportingNodeBuilder();
121         snb.setNodeRef(sni);
122         snb.setKey(sk);
123         snb.addAugmentation(SupportingNode1.class, new SupportingNode1Builder().setPathComputationClient(
124                 new PathComputationClientBuilder().setControlling(inControl).build()).build());
125
126         return snb.build();
127     }
128
129     private InstanceIdentifier<TerminationPoint> getIpTerminationPoint(final DataModificationTransaction trans, final IpAddress addr,
130             final InstanceIdentifier<Node> sni, final Boolean inControl) {
131         for (final Node n : ((Topology) trans.readOperationalData(this.target)).getNode()) {
132             for (final TerminationPoint tp : n.getTerminationPoint()) {
133                 final TerminationPoint1 tpa = tp.getAugmentation(TerminationPoint1.class);
134
135                 if (tpa != null) {
136                     final TerminationPointType tpt = tpa.getIgpTerminationPointAttributes().getTerminationPointType();
137
138                     if (tpt instanceof Ip) {
139                         for (final IpAddress a : ((Ip) tpt).getIpAddress()) {
140                             if (addr.equals(a)) {
141                                 if (sni != null) {
142                                     final NodeKey k = InstanceIdentifier.keyOf(sni);
143                                     boolean have = false;
144
145                                     /*
146                                      * We may have found a termination point which has been created as a destination,
147                                      * so it does not have a supporting node pointer. Since we now know what it is,
148                                      * fill it in.
149                                      */
150                                     for (final SupportingNode sn : n.getSupportingNode()) {
151                                         if (sn.getNodeRef().equals(k.getNodeId())) {
152                                             have = true;
153                                             break;
154                                         }
155                                     }
156
157                                     if (!have) {
158                                         final SupportingNode sn = createSupportingNode(k.getNodeId(), inControl);
159
160                                         trans.putOperationalData(this.target.builder().child(Node.class, n.getKey()).child(
161                                                 SupportingNode.class, sn.getKey()).toInstance(), sn);
162                                     }
163                                 }
164                                 return this.target.builder().child(Node.class, n.getKey()).child(TerminationPoint.class, tp.getKey()).toInstance();
165                             }
166                         }
167                     } else {
168                         LOG.debug("Ignoring termination point type {}", tpt);
169                     }
170                 }
171             }
172         }
173
174         LOG.debug("Termination point for {} not found, creating a new one", addr);
175
176         final String url = "ip://" + addr.toString();
177         final TerminationPointKey tpk = new TerminationPointKey(new TpId(url));
178         final TerminationPointBuilder tpb = new TerminationPointBuilder();
179         tpb.setKey(tpk).setTpId(tpk.getTpId());
180         tpb.addAugmentation(TerminationPoint1.class, new TerminationPoint1Builder().setIgpTerminationPointAttributes(
181                 new IgpTerminationPointAttributesBuilder().setTerminationPointType(
182                         new IpBuilder().setIpAddress(Lists.newArrayList(addr)).build()).build()).build());
183
184         final NodeKey nk = new NodeKey(new NodeId(url));
185         final NodeBuilder nb = new NodeBuilder();
186         nb.setKey(nk).setNodeId(nk.getNodeId());
187         nb.setTerminationPoint(Lists.newArrayList(tpb.build()));
188         if (sni != null) {
189             nb.setSupportingNode(Lists.newArrayList(createSupportingNode(InstanceIdentifier.keyOf(sni).getNodeId(), inControl)));
190         }
191
192         final InstanceIdentifier<Node> nid = this.target.child(Node.class, nb.getKey());
193         trans.putOperationalData(nid, nb.build());
194         return nid.child(TerminationPoint.class, tpb.getKey());
195     }
196
197     private void create(final DataModificationTransaction trans, final InstanceIdentifier<ReportedLsp> i, final ReportedLsp value) {
198         final InstanceIdentifier<Node> ni = i.firstIdentifierOf(Node.class);
199
200         final Path1 rl = value.getPath().get(0).getAugmentation(Path1.class);
201
202         final AddressFamily af = rl.getLsp().getTlvs().getLspIdentifiers().getAddressFamily();
203
204         /*
205          * We are trying to ensure we have source and destination nodes.
206          */
207         final IpAddress srcIp, dstIp;
208         if (af instanceof Ipv4Case) {
209             final Ipv4 ipv4 = ((Ipv4Case) af).getIpv4();
210             srcIp = new IpAddress(ipv4.getSourceIpv4Address());
211             dstIp = new IpAddress(ipv4.getDestinationIpv4Address());
212         } else if (af instanceof Ipv6Case) {
213             final Ipv6 ipv6 = ((Ipv6Case) af).getIpv6();
214             srcIp = new IpAddress(ipv6.getSourceIpv6Address());
215             dstIp = new IpAddress(ipv6.getDestinationIpv6Address());
216         } else {
217             throw new IllegalArgumentException("Unsupported address family: " + af.getImplementedInterface());
218         }
219
220         final Link1Builder lab = new Link1Builder(value.getPath().get(0).getLspa());
221         lab.setBandwidth(value.getPath().get(0).getBandwidth().getBandwidth());
222         lab.setClassType(value.getPath().get(0).getClassType().getClassType());
223         lab.setSymbolicPathName(value.getName());
224
225         final InstanceIdentifier<TerminationPoint> dst = getIpTerminationPoint(trans, dstIp, null, Boolean.FALSE);
226         final InstanceIdentifier<TerminationPoint> src = getIpTerminationPoint(trans, srcIp, ni, rl.getLsp().isDelegate());
227
228         final org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.ietf.stateful.rev131222.Link1Builder slab = new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.ietf.stateful.rev131222.Link1Builder();
229         slab.setOperationalStatus(rl.getLsp().getOperational());
230         slab.setAdministrativeStatus(rl.getLsp().isAdministrative() ? AdministrativeStatus.Active : AdministrativeStatus.Inactive);
231
232         final LinkId id = linkIdForLsp(i, value);
233         final LinkBuilder lb = new LinkBuilder();
234         lb.setLinkId(id);
235
236         lb.setSource(new SourceBuilder().setSourceNode(src.firstKeyOf(Node.class, NodeKey.class).getNodeId()).setSourceTp(
237                 src.firstKeyOf(TerminationPoint.class, TerminationPointKey.class).getTpId()).build());
238         lb.setDestination(new DestinationBuilder().setDestNode(dst.firstKeyOf(Node.class, NodeKey.class).getNodeId()).setDestTp(
239                 dst.firstKeyOf(TerminationPoint.class, TerminationPointKey.class).getTpId()).build());
240         lb.addAugmentation(Link1.class, lab.build());
241         lb.addAugmentation(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.ietf.stateful.rev131222.Link1.class,
242                 slab.build());
243
244         trans.putOperationalData(linkForLsp(id), lb.build());
245     }
246
247     private InstanceIdentifier<TerminationPoint> tpIdentifier(final NodeId node, final TpId tp) {
248         return this.target.builder().child(Node.class, new NodeKey(node)).child(TerminationPoint.class, new TerminationPointKey(tp)).toInstance();
249     }
250
251     private InstanceIdentifier<Node> nodeIdentifier(final NodeId node) {
252         return this.target.child(Node.class, new NodeKey(node));
253     }
254
255     private void remove(final DataModificationTransaction trans, final InstanceIdentifier<ReportedLsp> i, final ReportedLsp value) {
256         final InstanceIdentifier<Link> li = linkForLsp(linkIdForLsp(i, value));
257
258         final Link l = (Link) trans.readOperationalData(li);
259         if (l != null) {
260             LOG.debug("Removing link {} (was {})", li, l);
261             trans.removeOperationalData(li);
262
263             LOG.debug("Searching for orphan links/nodes");
264             final Topology t = (Topology) trans.readOperationalData(this.target);
265
266             final NodeId srcNode = l.getSource().getSourceNode();
267             final NodeId dstNode = l.getDestination().getDestNode();
268             final TpId srcTp = l.getSource().getSourceTp();
269             final TpId dstTp = l.getDestination().getDestTp();
270
271             boolean orphSrcNode = true, orphDstNode = true, orphDstTp = true, orphSrcTp = true;
272             for (final Link lw : t.getLink()) {
273                 LOG.trace("Checking link {}", lw);
274
275                 final NodeId sn = lw.getSource().getSourceNode();
276                 final NodeId dn = lw.getDestination().getDestNode();
277                 final TpId st = lw.getSource().getSourceTp();
278                 final TpId dt = lw.getDestination().getDestTp();
279
280                 // Source node checks
281                 if (srcNode.equals(sn)) {
282                     if (orphSrcNode) {
283                         LOG.debug("Node {} held by source of link {}", srcNode, lw);
284                         orphSrcNode = false;
285                     }
286                     if (orphSrcTp && srcTp.equals(st)) {
287                         LOG.debug("TP {} held by source of link {}", srcTp, lw);
288                         orphSrcTp = false;
289                     }
290                 }
291                 if (srcNode.equals(dn)) {
292                     if (orphSrcNode) {
293                         LOG.debug("Node {} held by destination of link {}", srcNode, lw);
294                         orphSrcNode = false;
295                     }
296                     if (orphSrcTp && srcTp.equals(dt)) {
297                         LOG.debug("TP {} held by destination of link {}", srcTp, lw);
298                         orphSrcTp = false;
299                     }
300                 }
301
302                 // Destination node checks
303                 if (dstNode.equals(sn)) {
304                     if (orphDstNode) {
305                         LOG.debug("Node {} held by source of link {}", dstNode, lw);
306                         orphDstNode = false;
307                     }
308                     if (orphDstTp && dstTp.equals(st)) {
309                         LOG.debug("TP {} held by source of link {}", dstTp, lw);
310                         orphDstTp = false;
311                     }
312                 }
313                 if (dstNode.equals(dn)) {
314                     if (orphDstNode) {
315                         LOG.debug("Node {} held by destination of link {}", dstNode, lw);
316                         orphDstNode = false;
317                     }
318                     if (orphDstTp && dstTp.equals(dt)) {
319                         LOG.debug("TP {} held by destination of link {}", dstTp, lw);
320                         orphDstTp = false;
321                     }
322                 }
323             }
324
325             if (orphSrcNode && !orphSrcTp) {
326                 LOG.warn("Orphan source node {} but not TP {}, retaining the node", srcNode, srcTp);
327                 orphSrcNode = false;
328             }
329             if (orphDstNode && !orphDstTp) {
330                 LOG.warn("Orphan destination node {} but not TP {}, retaining the node", dstNode, dstTp);
331                 orphDstNode = false;
332             }
333
334             if (orphSrcNode) {
335                 LOG.debug("Removing orphan node {}", srcNode);
336                 trans.removeOperationalData(nodeIdentifier(srcNode));
337             } else if (orphSrcTp) {
338                 LOG.debug("Removing orphan TP {} on node {}", srcTp, srcNode);
339                 trans.removeOperationalData(tpIdentifier(srcNode, srcTp));
340             }
341             if (orphDstNode) {
342                 LOG.debug("Removing orphan node {}", dstNode);
343                 trans.removeOperationalData(nodeIdentifier(dstNode));
344             } else if (orphDstTp) {
345                 LOG.debug("Removing orphan TP {} on node {}", dstTp, dstNode);
346                 trans.removeOperationalData(tpIdentifier(dstNode, dstTp));
347             }
348         }
349     }
350
351     @Override
352     public void onDataChanged(final DataChangeEvent<InstanceIdentifier<?>, DataObject> change) {
353         final DataModificationTransaction trans = this.dataProvider.beginTransaction();
354
355         final Set<InstanceIdentifier<ReportedLsp>> lsps = new HashSet<>();
356         final Set<InstanceIdentifier<Node>> nodes = new HashSet<>();
357
358         // Categorize reported identifiers
359         for (final InstanceIdentifier<?> i : change.getRemovedOperationalData()) {
360             categorizeIdentifier(i, lsps, nodes);
361         }
362         for (final InstanceIdentifier<?> i : change.getUpdatedOperationalData().keySet()) {
363             categorizeIdentifier(i, lsps, nodes);
364         }
365         for (final InstanceIdentifier<?> i : change.getCreatedOperationalData().keySet()) {
366             categorizeIdentifier(i, lsps, nodes);
367         }
368
369         // Get the subtrees
370         final Map<InstanceIdentifier<?>, DataObject> o = change.getOriginalOperationalData();
371         final Map<InstanceIdentifier<?>, DataObject> n = change.getUpdatedOperationalData();
372
373         // Now walk all nodes, check for removals/additions and cascade them to LSPs
374         for (final InstanceIdentifier<Node> i : nodes) {
375             enumerateLsps(i, (Node) o.get(i), lsps);
376             enumerateLsps(i, (Node) n.get(i), lsps);
377         }
378
379         // We now have list of all affected LSPs. Walk them create/remove them
380         for (final InstanceIdentifier<ReportedLsp> i : lsps) {
381             final ReportedLsp oldValue = (ReportedLsp) o.get(i);
382             final ReportedLsp newValue = (ReportedLsp) n.get(i);
383
384             LOG.debug("Updating lsp {} value {} -> {}", i, oldValue, newValue);
385             if (oldValue != null) {
386                 remove(trans, i, oldValue);
387             }
388             if (newValue != null) {
389                 create(trans, i, newValue);
390             }
391         }
392
393         Futures.addCallback(JdkFutureAdapters.listenInPoolThread(trans.commit()), new FutureCallback<RpcResult<TransactionStatus>>() {
394             @Override
395             public void onSuccess(final RpcResult<TransactionStatus> result) {
396                 LOG.trace("Topology change committed successfully");
397             }
398
399             @Override
400             public void onFailure(final Throwable t) {
401                 LOG.error("Failed to propagate a topology change, target topology became inconsistent", t);
402             }
403         });
404     }
405
406     public static InstanceIdentifier<Link> linkIdentifier(final InstanceIdentifier<Topology> topology, final NodeId node, final String name) {
407         return topology.child(Link.class, new LinkKey(new LinkId(node.getValue() + "/lsp/" + name)));
408     }
409 }