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