BUG-4438: When delete a route from the app RIB transaction chain brokes
[bgpcep.git] / bgp / rib-impl / src / main / java / org / opendaylight / protocol / bgp / rib / impl / AttributeOperations.java
1 /*
2  * Copyright (c) 2015 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.protocol.bgp.rib.impl;
9
10 import com.google.common.base.Optional;
11 import com.google.common.cache.CacheBuilder;
12 import com.google.common.cache.CacheLoader;
13 import com.google.common.cache.LoadingCache;
14 import com.google.common.collect.ImmutableList;
15 import com.google.common.collect.ImmutableSet;
16 import java.util.Iterator;
17 import org.opendaylight.protocol.util.Values;
18 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Address;
19 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.path.attributes.attributes.AsPath;
20 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.path.attributes.attributes.ClusterId;
21 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.path.attributes.attributes.Communities;
22 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.path.attributes.attributes.ExtendedCommunities;
23 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.path.attributes.attributes.Origin;
24 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.path.attributes.attributes.OriginatorId;
25 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.path.attributes.attributes.UnrecognizedAttributes;
26 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.path.attributes.attributes.as.path.Segments;
27 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev130919.ClusterIdentifier;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev130919.next.hop.CNextHop;
29 import org.opendaylight.yangtools.yang.common.QName;
30 import org.opendaylight.yangtools.yang.common.QNameModule;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
33 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
34 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
35 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
37 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
38 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
39 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
40 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
41 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
42 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
43 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
44 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
45 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
46 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
47 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder;
48 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.ListNodeBuilder;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 /**
53  * Utility class for working with route attributes in binding independent form. An instance
54  * is always bound to a certain namespace, so it maintains cached attribute identifiers.
55  */
56 final class AttributeOperations {
57     private static final Logger LOG = LoggerFactory.getLogger(AttributeOperations.class);
58     private static final LoadingCache<QNameModule, AttributeOperations> ATTRIBUTES_CACHE = CacheBuilder.newBuilder().weakKeys().weakValues().build(
59         new CacheLoader<QNameModule, AttributeOperations>() {
60             @Override
61             public AttributeOperations load(final QNameModule key) throws Exception {
62                 return new AttributeOperations(key);
63             }
64         });
65     private static final LoadingCache<QNameModule, ImmutableSet<QName>> TRANSITIVE_CACHE = CacheBuilder.newBuilder()
66         .weakKeys()
67         .weakValues().build(
68             new CacheLoader<QNameModule, ImmutableSet<QName>>() {
69                 @Override
70                 public ImmutableSet<QName> load(final QNameModule key) {
71                     return ImmutableSet.of(QName.cachedReference(QName.create(key, Origin.QNAME.getLocalName())),
72                         QName.cachedReference(QName.create(key, AsPath.QNAME.getLocalName())),
73                         QName.cachedReference(QName.create(key, CNextHop.QNAME.getLocalName())),
74                         QName.cachedReference(QName.create(key, Communities.QNAME.getLocalName())),
75                         QName.cachedReference(QName.create(key, ExtendedCommunities.QNAME.getLocalName())));
76                 }
77             });
78     private final ImmutableSet<QName> transitiveCollection;
79     private final Iterable<PathArgument> originatorIdPath;
80     private final Iterable<PathArgument> clusterListPath;
81     private final NodeIdentifier originatorIdContainer;
82     private final NodeIdentifier originatorIdLeaf;
83     private final NodeIdentifier clusterListContainer;
84     private final NodeIdentifier clusterListLeaf;
85     private final QName clusterQname;
86     private final NodeIdentifier asPathContainer;
87     private final NodeIdentifier asPathSegments;
88     private final NodeIdentifier asPathSequence;
89     private final QName asNumberQname;
90     private final NodeIdentifier transitiveLeaf;
91
92     private AttributeOperations(final QNameModule namespace) {
93         this.asPathContainer = new NodeIdentifier(QName.cachedReference(QName.create(namespace, AsPath.QNAME.getLocalName())));
94         this.asPathSegments = new NodeIdentifier(QName.cachedReference(QName.create(namespace, Segments.QNAME.getLocalName())));
95         this.asPathSequence = new NodeIdentifier(QName.cachedReference(QName.create(namespace, "as-sequence")));
96         this.asNumberQname = QName.cachedReference(QName.create(namespace, "as-number"));
97
98         this.clusterListContainer = new NodeIdentifier(QName.cachedReference(QName.create(namespace, ClusterId.QNAME.getLocalName())));
99         this.clusterQname = QName.cachedReference(QName.create(namespace, "cluster"));
100         this.clusterListLeaf = new NodeIdentifier(this.clusterQname);
101         this.clusterListPath = ImmutableList.<PathArgument>of(this.clusterListContainer, this.clusterListLeaf);
102         this.originatorIdContainer = new NodeIdentifier(QName.cachedReference(QName.create(namespace, OriginatorId.QNAME.getLocalName())));
103         this.originatorIdLeaf = new NodeIdentifier(QName.cachedReference(QName.create(namespace, "originator")));
104         this.originatorIdPath = ImmutableList.<PathArgument>of(this.originatorIdContainer, this.originatorIdLeaf);
105
106         this.transitiveLeaf = new NodeIdentifier(QName.cachedReference(QName.create(UnrecognizedAttributes.QNAME, "transitive")));
107         this.transitiveCollection = TRANSITIVE_CACHE.getUnchecked(namespace);
108     }
109
110     static AttributeOperations getInstance(final ContainerNode attributes) {
111         return ATTRIBUTES_CACHE.getUnchecked(attributes.getNodeType().getModule());
112     }
113
114     private LeafSetNode<?> reusableSegment(final UnkeyedListEntryNode segment) {
115         final Optional<NormalizedNode<?, ?>> maybeAsSequence = NormalizedNodes.findNode(segment, this.asPathSequence);
116         if (maybeAsSequence.isPresent()) {
117             final LeafSetNode<?> asList = (LeafSetNode<?>) maybeAsSequence.get();
118             if (asList.getValue().size() < Values.UNSIGNED_BYTE_MAX_VALUE) {
119                 return asList;
120             }
121         }
122         return null;
123     }
124
125     ContainerNode exportedAttributes(final ContainerNode attributes, final Long localAs) {
126         final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> containerBuilder = Builders.containerBuilder();
127         containerBuilder.withNodeIdentifier(attributes.getIdentifier());
128
129         // First filter out non-transitive attributes
130         // FIXME: removes MULTI_EXIT_DISC, too.
131         spliceTransitives(containerBuilder, attributes);
132
133         final CollectionNodeBuilder<UnkeyedListEntryNode, UnkeyedListNode> segmentsBuilder = Builders.unkeyedListBuilder();
134         segmentsBuilder.withNodeIdentifier(this.asPathSegments);
135
136         final Optional<NormalizedNode<?, ?>> maybeOldAsSegments = NormalizedNodes.findNode(attributes, this.asPathContainer, this.asPathSegments);
137         if (maybeOldAsSegments.isPresent() && !((UnkeyedListNode) maybeOldAsSegments.get()).getValue().isEmpty()) {
138
139             /*
140              * We need to check the first segment.
141              * If it has as-set then new as-sequence with local AS is prepended.
142              * If it has as-sequence, we may add local AS when it has less than 255 elements.
143              * Otherwise we need to create new as-sequence for local AS.
144              */
145
146             final ListNodeBuilder<Object,LeafSetEntryNode<Object>> asSequenceBuilder = Builders.orderedLeafSetBuilder();
147             // add local AS
148             asSequenceBuilder.withNodeIdentifier(this.asPathSequence).addChild(Builders.leafSetEntryBuilder().withNodeIdentifier(new NodeWithValue(this.asNumberQname, localAs)).withValue(localAs).build());
149
150             final Iterator<UnkeyedListEntryNode> oldAsSegments = ((UnkeyedListNode) maybeOldAsSegments.get()).getValue().iterator();
151             final UnkeyedListEntryNode firstSegment = oldAsSegments.next();
152             final LeafSetNode<?> reusableAsSeq = reusableSegment(firstSegment);
153             // first segment contains as-sequence with less then 255 elements and it's append to local AS
154             if (reusableAsSeq != null) {
155                 for (final LeafSetEntryNode<?> child : reusableAsSeq.getValue())  {
156                     asSequenceBuilder.withChild(Builders.leafSetEntryBuilder().withNodeIdentifier(new NodeWithValue(this.asNumberQname, child.getValue())).withValue(child.getValue()).build());
157                 }
158             }
159             // Add the new first segment
160             segmentsBuilder.withChild(Builders.unkeyedListEntryBuilder().withNodeIdentifier(this.asPathSegments).withChild(asSequenceBuilder.build()).build());
161
162             // When first segment contains as-set or full as-sequence, append it
163             if (reusableAsSeq == null) {
164                 segmentsBuilder.withChild(firstSegment);
165             }
166
167             // Add all subsequent segments
168             while (oldAsSegments.hasNext()) {
169                 segmentsBuilder.withChild(oldAsSegments.next());
170             }
171         } else {
172             // Segments are completely empty, create a completely new AS_PATH container with
173             // a single entry
174             segmentsBuilder.withChild(Builders.unkeyedListEntryBuilder().withNodeIdentifier(this.asPathSegments).withChild(
175                 Builders.orderedLeafSetBuilder().withNodeIdentifier(this.asPathSequence).addChild(
176                     Builders.leafSetEntryBuilder().withNodeIdentifier(new NodeWithValue(this.asNumberQname, localAs)).withValue(localAs).build()).build()).build());
177         }
178
179         containerBuilder.withChild(Builders.containerBuilder().withNodeIdentifier(this.asPathContainer).withChild(segmentsBuilder.build()).build());
180         return containerBuilder.build();
181     }
182
183     /**
184      * Attributes when reflecting a route from Internal iBGP
185      * @param attributes
186      * @return
187      */
188     ContainerNode reflectedAttributes(final ContainerNode attributes) {
189         final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> b = Builders.containerBuilder(attributes);
190
191         // Create a new CLUSTER_LIST builder
192         final ListNodeBuilder<Object, LeafSetEntryNode<Object>> clb = Builders.orderedLeafSetBuilder();
193         clb.withNodeIdentifier(this.clusterListLeaf);
194
195         // if there was a CLUSTER_LIST attribute, add all other entries
196         final Optional<NormalizedNode<?, ?>> maybeClusterList = NormalizedNodes.findNode(attributes, this.clusterListPath);
197         if (maybeClusterList.isPresent()) {
198             final NormalizedNode<?, ?> clusterList = maybeClusterList.get();
199             if (clusterList instanceof LeafSetNode) {
200                 for (final LeafSetEntryNode<?> n : ((LeafSetNode<?>)clusterList).getValue()) {
201                     // There's no way we can safely avoid this cast
202                     @SuppressWarnings("unchecked")
203                     final LeafSetEntryNode<Object> child = (LeafSetEntryNode<Object>)n;
204                     clb.addChild(child);
205                 }
206             } else {
207                 LOG.warn("Ignoring malformed CLUSTER_LIST {}", clusterList);
208             }
209         } else {
210             LOG.debug("Creating fresh CLUSTER_LIST attribute");
211         }
212
213         // Now wrap it in a container and add it to attributes
214         final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> cb = Builders.containerBuilder();
215         cb.withNodeIdentifier(this.clusterListContainer);
216         cb.withChild(clb.build());
217         b.withChild(cb.build());
218
219         return b.build();
220     }
221
222     // Attributes when reflecting a route
223     ContainerNode reflectedAttributes(final ContainerNode attributes, final Ipv4Address originatorId, final ClusterIdentifier clusterId) {
224         final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> b = Builders.containerBuilder(attributes);
225
226         // Create a new CLUSTER_LIST builder
227         final ListNodeBuilder<Object, LeafSetEntryNode<Object>> clb = Builders.orderedLeafSetBuilder();
228         clb.withNodeIdentifier(this.clusterListLeaf);
229
230         // prepend local CLUSTER_ID
231         clb.withChild(Builders.leafSetEntryBuilder().withNodeIdentifier(new NodeWithValue(this.clusterQname, clusterId.getValue())).
232             withValue(clusterId.getValue()).build());
233
234         // if there was a CLUSTER_LIST attribute, add all other entries
235         final Optional<NormalizedNode<?, ?>> maybeClusterList = NormalizedNodes.findNode(attributes, this.clusterListPath);
236         if (maybeClusterList.isPresent()) {
237             final NormalizedNode<?, ?> clusterList = maybeClusterList.get();
238             if (clusterList instanceof LeafSetNode) {
239                 for (final LeafSetEntryNode<?> n : ((LeafSetNode<?>)clusterList).getValue()) {
240                     // There's no way we can safely avoid this cast
241                     @SuppressWarnings("unchecked")
242                     final LeafSetEntryNode<Object> child = (LeafSetEntryNode<Object>)n;
243                     clb.addChild(child);
244                 }
245             } else {
246                 LOG.warn("Ignoring malformed CLUSTER_LIST {}", clusterList);
247             }
248         } else {
249             LOG.debug("Creating fresh CLUSTER_LIST attribute");
250         }
251
252         // Now wrap it in a container and add it to attributes
253         final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> cb = Builders.containerBuilder();
254         cb.withNodeIdentifier(this.clusterListContainer);
255         cb.withChild(clb.build());
256         b.withChild(cb.build());
257
258         // add ORIGINATOR_ID if not present
259         final Optional<NormalizedNode<?, ?>> maybeOriginatorId = NormalizedNodes.findNode(attributes, this.originatorIdPath);
260         if (!maybeOriginatorId.isPresent()) {
261             final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> oib = Builders.containerBuilder();
262             oib.withNodeIdentifier(this.originatorIdContainer);
263             oib.withChild(ImmutableNodes.leafNode(this.originatorIdLeaf, originatorId.getValue()));
264             b.withChild(oib.build());
265         }
266
267         return b.build();
268     }
269
270     private boolean isTransitiveAttribute(final DataContainerChild<? extends PathArgument, ?> child) {
271         if (child.getIdentifier() instanceof AugmentationIdentifier) {
272             final AugmentationIdentifier ai = (AugmentationIdentifier) child.getIdentifier();
273             for (final QName name : ai.getPossibleChildNames()) {
274                 LOG.trace("Augmented QNAME {}", name);
275                 if (this.transitiveCollection.contains(name)) {
276                     return true;
277                 }
278             }
279             return false;
280         }
281         if (this.transitiveCollection.contains(child.getNodeType())) {
282             return true;
283         }
284         if (UnrecognizedAttributes.QNAME.equals(child.getNodeType())) {
285             final Optional<NormalizedNode<?, ?>> maybeTransitive = NormalizedNodes.findNode(child, this.transitiveLeaf);
286             if (maybeTransitive.isPresent()) {
287                 return (Boolean) maybeTransitive.get().getValue();
288             }
289         }
290         return false;
291     }
292
293     private boolean spliceTransitives(final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> target, final ContainerNode attributes) {
294         // We want to reuse attributes as much as possible, so the result of the loop
295         // indicates whether we performed a modification. If we have not modified the
296         // attributes, we can reuse them.
297         boolean ret = false;
298         for (final DataContainerChild<? extends PathArgument, ?> child : attributes.getValue()) {
299             if (isTransitiveAttribute(child)) {
300                 target.withChild(child);
301             } else {
302                 ret = true;
303             }
304         }
305
306         return ret;
307     }
308
309     /**
310      * Filter out all non-transitive attributes.
311      *
312      * @param attributes Input attributes
313      * @return Output attributes, transitive only.
314      */
315     ContainerNode transitiveAttributes(final ContainerNode attributes) {
316         final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> b = Builders.containerBuilder();
317         b.withNodeIdentifier(attributes.getIdentifier());
318
319         final boolean modified = spliceTransitives(b, attributes);
320         return modified ? b.build() : attributes;
321     }
322
323     LeafSetNode<?> getClusterList(final ContainerNode attributes) {
324         final Optional<NormalizedNode<?, ?>> maybeClusterList = NormalizedNodes.findNode(attributes, this.clusterListPath);
325         if (maybeClusterList.isPresent()) {
326             final NormalizedNode<?, ?> clusterList = maybeClusterList.get();
327             if (clusterList instanceof LeafSetNode) {
328                 return (LeafSetNode<?>) clusterList;
329             }
330
331             LOG.warn("Unexpected CLUSTER_LIST node {}, ignoring it", clusterList);
332         }
333
334         return null;
335     }
336
337     Object getOriginatorId(final ContainerNode attributes) {
338         final Optional<NormalizedNode<?, ?>> maybeOriginatorId = NormalizedNodes.findNode(attributes, this.originatorIdPath);
339         if (!maybeOriginatorId.isPresent()) {
340             LOG.debug("No ORIGINATOR_ID present");
341             return null;
342         }
343
344         final NormalizedNode<?, ?> originatorId = maybeOriginatorId.get();
345         if (originatorId instanceof LeafNode) {
346             return ((LeafNode<?>) originatorId).getValue();
347         }
348
349         LOG.warn("Unexpected ORIGINATOR_ID node {}, ignoring it", originatorId);
350         return null;
351     }
352 }