Merge "BUG-2227 : fix exporting transitive attributes"
[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.Sets;
16 import java.util.Collection;
17 import java.util.Iterator;
18 import java.util.Set;
19 import org.opendaylight.protocol.util.Values;
20 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Address;
21 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.path.attributes.attributes.AsPath;
22 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.path.attributes.attributes.ClusterId;
23 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.path.attributes.attributes.Communities;
24 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.path.attributes.attributes.ExtendedCommunities;
25 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.path.attributes.attributes.Origin;
26 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.path.attributes.attributes.OriginatorId;
27 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.path.attributes.attributes.UnrecognizedAttributes;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.path.attributes.attributes.as.path.Segments;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev130919.ClusterIdentifier;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev130919.NextHop;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev130919.as.path.segment.CSegment;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev130919.as.path.segment.c.segment.a.list._case.AList;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev130919.as.path.segment.c.segment.a.list._case.a.list.AsSequence;
34 import org.opendaylight.yangtools.yang.common.QName;
35 import org.opendaylight.yangtools.yang.common.QNameModule;
36 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
37 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
38 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
39 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
40 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
41 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
42 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
43 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
44 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
45 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
46 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
47 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
48 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
49 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
50 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
51 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
52 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder;
53 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
54 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.ListNodeBuilder;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
57
58 /**
59  * Utility class for working with route attributes in binding independent form. An instance
60  * is always bound to a certain namespace, so it maintains cached attribute identifiers.
61  */
62 final class AttributeOperations {
63     private static final Logger LOG = LoggerFactory.getLogger(AttributeOperations.class);
64     private static final LoadingCache<QNameModule, AttributeOperations> ATTRIBUTES_CACHE = CacheBuilder.newBuilder().weakKeys().weakValues().build(
65         new CacheLoader<QNameModule, AttributeOperations>() {
66             @Override
67             public AttributeOperations load(final QNameModule key) throws Exception {
68                 return new AttributeOperations(key);
69             }
70         });
71
72     private final Iterable<PathArgument> originatorIdPath;
73     private final Iterable<PathArgument> clusterListPath;
74     private final NodeIdentifier originatorIdContainer;
75     private final NodeIdentifier originatorIdLeaf;
76     private final NodeIdentifier clusterListContainer;
77     private final NodeIdentifier clusterListLeaf;
78     private final NodeIdentifier asPathContainer;
79     private final NodeIdentifier asPathSegments;
80     private final NodeIdentifier asPathChoice;
81     private final NodeIdentifier asPathList;
82     private final NodeIdentifier asPathSequence;
83     private final NodeIdentifier asPathId;
84     private final NodeIdentifier transitiveLeaf;
85
86     // FIXME: get this list instead of hardcoding
87     private static final Set<QName> TRANSITIVES = Sets.newHashSet(Origin.QNAME, AsPath.QNAME, NextHop.QNAME, Communities.QNAME,
88         ExtendedCommunities.QNAME);
89
90     private AttributeOperations(final QNameModule namespace) {
91         this.asPathContainer = new NodeIdentifier(QName.cachedReference(QName.create(namespace, AsPath.QNAME.getLocalName())));
92         this.asPathSegments = new NodeIdentifier(QName.cachedReference(QName.create(namespace, Segments.QNAME.getLocalName())));
93         this.asPathChoice = new NodeIdentifier(QName.cachedReference(QName.create(namespace, CSegment.QNAME.getLocalName())));
94         this.asPathList = new NodeIdentifier(QName.cachedReference(QName.create(namespace, AList.QNAME.getLocalName())));
95         this.asPathSequence = new NodeIdentifier(QName.cachedReference(QName.create(namespace, AsSequence.QNAME.getLocalName())));
96         this.asPathId = new NodeIdentifier(QName.cachedReference(QName.create(namespace, "as")));
97
98         this.clusterListContainer = new NodeIdentifier(QName.cachedReference(QName.create(namespace, ClusterId.QNAME.getLocalName())));
99         this.clusterListLeaf = new NodeIdentifier(QName.cachedReference(QName.create(namespace, "cluster")));
100         this.clusterListPath = ImmutableList.<PathArgument>of(this.clusterListContainer, this.clusterListLeaf);
101         this.originatorIdContainer = new NodeIdentifier(QName.cachedReference(QName.create(namespace, OriginatorId.QNAME.getLocalName())));
102         this.originatorIdLeaf = new NodeIdentifier(QName.cachedReference(QName.create(namespace, "originator")));
103         this.originatorIdPath = ImmutableList.<PathArgument>of(this.originatorIdContainer, this.originatorIdLeaf);
104
105         this.transitiveLeaf = new NodeIdentifier(QName.cachedReference(QName.create(UnrecognizedAttributes.QNAME, "transitive")));
106     }
107
108     static AttributeOperations getInstance(final ContainerNode attributes) {
109         return ATTRIBUTES_CACHE.getUnchecked(QNameModule.cachedReference(attributes.getNodeType().getModule()));
110     }
111
112     private Collection<UnkeyedListEntryNode> reusableSequence(final UnkeyedListEntryNode segment) {
113         final Optional<NormalizedNode<?, ?>> maybeAsSequence = NormalizedNodes.findNode(segment, this.asPathChoice, this.asPathList, this.asPathSequence);
114         if (maybeAsSequence.isPresent()) {
115             final UnkeyedListNode asList = (UnkeyedListNode) maybeAsSequence.get();
116             if (asList.getSize() < Values.UNSIGNED_BYTE_MAX_VALUE) {
117                 return asList.getValue();
118             }
119         }
120
121         return null;
122     }
123
124     ContainerNode exportedAttributes(final ContainerNode attributes, final Long localAs) {
125         final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> b = Builders.containerBuilder();
126         b.withNodeIdentifier(attributes.getIdentifier());
127
128         // First filter out non-transitive attributes
129         // FIXME: removes MULTI_EXIT_DISC, too.
130         spliceTransitives(b, attributes);
131
132         /*
133          * This is very ugly, as our AS_PATH model is needlessly complex. The top-level container contains a list
134          * of segments, each of which is a choice containing another list. Both are unkeyed lists, so we need to
135          * perform a wholesale replace.
136          */
137         final CollectionNodeBuilder<UnkeyedListEntryNode, UnkeyedListNode> sb = Builders.unkeyedListBuilder();
138         sb.withNodeIdentifier(this.asPathSegments);
139
140         final Optional<NormalizedNode<?, ?>> maybeOldAsSegments = NormalizedNodes.findNode(attributes, this.asPathContainer, this.asPathSegments);
141         if (maybeOldAsSegments.isPresent() && !((UnkeyedListNode) maybeOldAsSegments.get()).getValue().isEmpty()) {
142             // Builder of inner list
143             final CollectionNodeBuilder<UnkeyedListEntryNode, UnkeyedListNode> ilb = Builders.unkeyedListBuilder();
144             ilb.withNodeIdentifier(this.asPathSegments);
145             ilb.withChild(Builders.unkeyedListEntryBuilder().withNodeIdentifier(this.asPathSequence).withChild(ImmutableNodes.leafNode(this.asPathId, localAs)).build());
146
147             /*
148              * We need to check the first entry in the outer list, to check if the choice is a-list. If it is and its
149              * total number of elements is less than 255, we need to modify that one. Otherwise we need to create a
150              * new entry.
151              */
152             final Iterator<UnkeyedListEntryNode> oldAsSegments = ((UnkeyedListNode) maybeOldAsSegments.get()).getValue().iterator();
153             final UnkeyedListEntryNode firstSegment = oldAsSegments.next();
154             final Collection<UnkeyedListEntryNode> reusable = reusableSequence(firstSegment);
155             if (reusable != null) {
156                 for (final UnkeyedListEntryNode child : reusable) {
157                     ilb.withChild(child);
158                 }
159             }
160
161             // Builder of inner container
162             final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> icb = Builders.containerBuilder();
163             icb.withNodeIdentifier(this.asPathList);
164             icb.withChild(ilb.build());
165
166             // Choice inside the outer list
167             final DataContainerNodeBuilder<NodeIdentifier, ChoiceNode> ocb = Builders.choiceBuilder();
168             ocb.withNodeIdentifier(this.asPathChoice);
169             ocb.withChild(icb.build());
170
171             // Add the new first segment
172             sb.withChild(Builders.unkeyedListEntryBuilder().withNodeIdentifier(this.asPathSegments).withChild(ocb.build()).build());
173
174             // If we did not merge into the original segment, add it
175             if (reusable == null) {
176                 sb.withChild(firstSegment);
177             }
178
179             // Add all subsequent segments
180             while (oldAsSegments.hasNext()) {
181                 sb.withChild(oldAsSegments.next());
182             }
183         } else {
184             // Segments are completely empty, create a completely new AS_PATH container with
185             // a single entry
186             sb.withChild(Builders.unkeyedListEntryBuilder().withNodeIdentifier(this.asPathSegments).withChild(
187                 Builders.choiceBuilder().withNodeIdentifier(this.asPathChoice).withChild(
188                     Builders.containerBuilder().withNodeIdentifier(this.asPathList).withChild(
189                         Builders.unkeyedListBuilder().withNodeIdentifier(this.asPathSequence).withChild(
190                             Builders.unkeyedListEntryBuilder().withNodeIdentifier(this.asPathSequence).withChild(
191                                 ImmutableNodes.leafNode(this.asPathId, localAs)).build()).build()).build()).build()).build());
192
193         }
194
195         b.withChild(Builders.containerBuilder().withNodeIdentifier(this.asPathContainer).withChild(sb.build()).build());
196         return b.build();
197     }
198
199
200     // Attributes when reflecting a route
201     ContainerNode reflectedAttributes(final ContainerNode attributes, final Ipv4Address originatorId, final ClusterIdentifier clusterId) {
202         final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> b = Builders.containerBuilder(attributes);
203
204         // Create a new CLUSTER_LIST builder
205         final ListNodeBuilder<Object, LeafSetEntryNode<Object>> clb = Builders.leafSetBuilder();
206         clb.withNodeIdentifier(this.clusterListLeaf);
207
208         // prepend local CLUSTER_ID
209         clb.withChild(Builders.leafSetEntryBuilder().withNodeIdentifier(new NodeWithValue(ClusterId.QNAME, clusterId)).withValue(clusterId).build());
210
211         // if there was a CLUSTER_LIST attribute, add all other entries
212         final Optional<NormalizedNode<?, ?>> maybeClusterList = NormalizedNodes.findNode(attributes, this.clusterListPath);
213         if (maybeClusterList.isPresent()) {
214             final NormalizedNode<?, ?> clusterList = maybeClusterList.get();
215             if (clusterList instanceof LeafSetNode) {
216                 for (final LeafSetEntryNode<?> n : ((LeafSetNode<?>)clusterList).getValue()) {
217                     // There's no way we can safely avoid this cast
218                     @SuppressWarnings("unchecked")
219                     final LeafSetEntryNode<Object> child = (LeafSetEntryNode<Object>)n;
220                     clb.addChild(child);
221                 }
222             } else {
223                 LOG.warn("Ignoring malformed CLUSTER_LIST {}", clusterList);
224             }
225         } else {
226             LOG.debug("Creating fresh CLUSTER_LIST attribute");
227         }
228
229         // Now wrap it in a container and add it to attributes
230         final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> cb = Builders.containerBuilder();
231         cb.withNodeIdentifier(this.clusterListContainer);
232         cb.withChild(clb.build());
233         b.withChild(cb.build());
234
235         // add ORIGINATOR_ID if not present
236         final Optional<NormalizedNode<?, ?>> maybeOriginatorId = NormalizedNodes.findNode(attributes, this.originatorIdPath);
237         if (!maybeOriginatorId.isPresent()) {
238             final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> oib = Builders.containerBuilder();
239             oib.withNodeIdentifier(this.originatorIdContainer);
240             oib.withChild(ImmutableNodes.leafNode(this.originatorIdLeaf, originatorId.getValue()));
241             b.withChild(oib.build());
242         }
243
244         return b.build();
245     }
246
247     private boolean isTransitiveAttribute(final DataContainerChild<? extends PathArgument, ?> child) {
248         if (TRANSITIVES.contains(child.getNodeType())) {
249             return true;
250         }
251         if (UnrecognizedAttributes.QNAME.equals(child.getNodeType())) {
252             final Optional<NormalizedNode<?, ?>> maybeTransitive = NormalizedNodes.findNode(child, this.transitiveLeaf);
253             if (maybeTransitive.isPresent()) {
254                 return (Boolean) maybeTransitive.get().getValue();
255             }
256         }
257         return false;
258     }
259
260     private boolean spliceTransitives(final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> target, final ContainerNode attributes) {
261         // We want to reuse attributes as much as possible, so the result of the loop
262         // indicates whether we performed a modification. If we have not modified the
263         // attributes, we can reuse them.
264         boolean ret = false;
265         for (final DataContainerChild<? extends PathArgument, ?> child : attributes.getValue()) {
266             if (isTransitiveAttribute(child)) {
267                 target.withChild(child);
268             } else {
269                 ret = true;
270             }
271         }
272
273         return ret;
274     }
275
276     /**
277      * Filter out all non-transitive attributes.
278      *
279      * @param attributes Input attributes
280      * @return Output attributes, transitive only.
281      */
282     ContainerNode transitiveAttributes(final ContainerNode attributes) {
283         final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> b = Builders.containerBuilder();
284         b.withNodeIdentifier(attributes.getIdentifier());
285
286         final boolean modified = spliceTransitives(b, attributes);
287         return modified ? b.build() : attributes;
288     }
289
290     LeafSetNode<?> getClusterList(final ContainerNode attributes) {
291         final Optional<NormalizedNode<?, ?>> maybeClusterList = NormalizedNodes.findNode(attributes, this.clusterListPath);
292         if (maybeClusterList.isPresent()) {
293             final NormalizedNode<?, ?> clusterList = maybeClusterList.get();
294             if (clusterList instanceof LeafSetNode) {
295                 return (LeafSetNode<?>) clusterList;
296             }
297
298             LOG.warn("Unexpected CLUSTER_LIST node {}, ignoring it", clusterList);
299         }
300
301         return null;
302     }
303
304     Object getOriginatorId(final ContainerNode attributes) {
305         final Optional<NormalizedNode<?, ?>> maybeOriginatorId = NormalizedNodes.findNode(attributes, this.originatorIdPath);
306         if (!maybeOriginatorId.isPresent()) {
307             LOG.debug("No ORIGINATOR_ID present");
308             return null;
309         }
310
311         final NormalizedNode<?, ?> originatorId = maybeOriginatorId.get();
312         if (originatorId instanceof LeafNode) {
313             return ((LeafNode<?>)originatorId).getValue();
314         }
315
316         LOG.warn("Unexpected ORIGINATOR_ID node {}, ignoring it", originatorId);
317         return null;
318     }
319
320 }