BGPCEP-754: Fix NPE and rework
[bgpcep.git] / bgp / path-selection-mode / src / main / java / org / opendaylight / protocol / bgp / mode / impl / base / BaseAbstractRouteEntry.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.mode.impl.base;
9
10 import com.google.common.annotations.VisibleForTesting;
11 import com.google.common.primitives.UnsignedInteger;
12 import javax.annotation.concurrent.NotThreadSafe;
13 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
14 import org.opendaylight.protocol.bgp.mode.spi.AbstractRouteEntry;
15 import org.opendaylight.protocol.bgp.rib.spi.BGPPeerTracker;
16 import org.opendaylight.protocol.bgp.rib.spi.ExportPolicyPeerTracker;
17 import org.opendaylight.protocol.bgp.rib.spi.Peer;
18 import org.opendaylight.protocol.bgp.rib.spi.PeerExportGroup;
19 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
20 import org.opendaylight.protocol.bgp.rib.spi.entry.RouteEntryDependenciesContainer;
21 import org.opendaylight.protocol.bgp.rib.spi.entry.RouteEntryInfo;
22 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.PeerId;
23 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.PeerRole;
24 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.rib.TablesKey;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
28 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
29 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
30 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 @NotThreadSafe
36 abstract class BaseAbstractRouteEntry extends AbstractRouteEntry<BaseBestPath> {
37     private static final Logger LOG = LoggerFactory.getLogger(BaseAbstractRouteEntry.class);
38     private static final ContainerNode[] EMPTY_ATTRIBUTES = new ContainerNode[0];
39     private OffsetMap offsets = OffsetMap.EMPTY;
40     private ContainerNode[] values = EMPTY_ATTRIBUTES;
41     private BaseBestPath bestPath;
42     private BaseBestPath removedBestPath;
43
44     BaseAbstractRouteEntry(final BGPPeerTracker peerTracker) {
45         super(peerTracker);
46     }
47
48     /**
49      * Remove route.
50      *
51      * @param routerId router ID in unsigned integer
52      * @param offset   of removed route
53      * @return true if its the last route
54      */
55     protected final boolean removeRoute(final UnsignedInteger routerId, final int offset) {
56         this.values = this.offsets.removeValue(this.values, offset);
57         this.offsets = this.offsets.without(routerId);
58         return this.offsets.isEmpty();
59     }
60
61     @Override
62     public final boolean selectBest(final long localAs) {
63         /*
64          * FIXME: optimize flaps by making sure we consider stability of currently-selected route.
65          */
66         final BasePathSelector selector = new BasePathSelector(localAs);
67
68         // Select the best route.
69         for (int i = 0; i < this.offsets.size(); ++i) {
70             final UnsignedInteger routerId = this.offsets.getRouterKey(i);
71             final ContainerNode attributes = this.offsets.getValue(this.values, i);
72             LOG.trace("Processing router id {} attributes {}", routerId, attributes);
73             selector.processPath(routerId, attributes);
74         }
75
76         // Get the newly-selected best path.
77         final BaseBestPath newBestPath = selector.result();
78         final boolean modified = newBestPath == null || !newBestPath.equals(this.bestPath);
79         if (modified) {
80             if (this.offsets.isEmpty()) {
81                 this.removedBestPath = this.bestPath;
82             }
83             LOG.trace("Previous best {}, current best {}", this.bestPath, newBestPath);
84             this.bestPath = newBestPath;
85         }
86         return modified;
87     }
88
89     @Override
90     public int addRoute(final UnsignedInteger routerId, final Long remotePathId,
91             final NodeIdentifier attributesIdentifier, final NormalizedNode<?, ?> data) {
92         LOG.trace("Find {} in {}", attributesIdentifier, data);
93         final ContainerNode advertisedAttrs
94                 = (ContainerNode) NormalizedNodes
95                 .findNode(data, attributesIdentifier).orElse(null);
96         int offset = this.offsets.offsetOf(routerId);
97         if (offset < 0) {
98             final OffsetMap newOffsets = this.offsets.with(routerId);
99             offset = newOffsets.offsetOf(routerId);
100
101             this.values = newOffsets.expand(this.offsets, this.values, offset);
102             this.offsets = newOffsets;
103         }
104
105         this.offsets.setValue(this.values, offset, advertisedAttrs);
106         LOG.trace("Added route from {} attributes {}", routerId, advertisedAttrs);
107         return offset;
108     }
109
110     @Override
111     public void updateBestPaths(final RouteEntryDependenciesContainer entryDependencies,
112             final NodeIdentifierWithPredicates routeIdPA,
113             final DOMDataWriteTransaction tx) {
114         if (this.removedBestPath != null) {
115             removePathFromDataStore(entryDependencies, routeIdPA, tx);
116             this.removedBestPath = null;
117         }
118         if (this.bestPath != null) {
119             addPathToDataStore(entryDependencies, routeIdPA, tx);
120         }
121     }
122
123     @Override
124     public void initializeBestPaths(final RouteEntryDependenciesContainer entryDep,
125             final RouteEntryInfo entryInfo, final PeerExportGroup peerGroup, final DOMDataWriteTransaction tx) {
126         if (this.bestPath != null) {
127             final TablesKey localTK = entryDep.getLocalTablesKey();
128             final BaseBestPath path = this.bestPath;
129             final PeerId toPeerId = entryInfo.getToPeerId();
130             if (filterRoutes(path.getPeerId(), toPeerId, localTK)) {
131                 final NodeIdentifierWithPredicates routeId = entryInfo.getRouteId();
132                 final RIBSupport ribSupport = entryDep.getRibSupport();
133                 NodeIdentifierWithPredicates routeIdDest = ribSupport.getRouteIdAddPath(path.getPathId(), routeId);
134                 if (routeIdDest == null) {
135                     routeIdDest = routeId;
136                 }
137                 final Peer fromPeer = this.peerTracker.getPeer(path.getPeerId());
138                 final ContainerNode effAttrib = peerGroup.effectiveAttributes(
139                         fromPeer.getRole(), path.getAttributes());
140                 final YangInstanceIdentifier rootPath = entryInfo.getRootPath();
141                 writeRoute(toPeerId, getAdjRibOutYII(ribSupport, rootPath, routeIdDest, localTK), effAttrib,
142                         createValue(routeIdDest, path), ribSupport, tx);
143             }
144         }
145     }
146
147     private void removePathFromDataStore(final RouteEntryDependenciesContainer entryDep,
148             final NodeIdentifierWithPredicates routeIdPA, final DOMDataWriteTransaction tx) {
149         LOG.trace("Best Path removed {}", this.removedBestPath);
150         final YangInstanceIdentifier locRibTarget = entryDep.getLocRibTableTarget();
151         final RIBSupport ribSup = entryDep.getRibSupport();
152         NodeIdentifierWithPredicates routeIdTarget
153                 = ribSup.getRouteIdAddPath(this.removedBestPath.getPathId(), routeIdPA);
154         if (routeIdTarget == null) {
155             routeIdTarget = routeIdPA;
156         }
157         fillLocRib(ribSup.routePath(locRibTarget.node(ROUTES_IDENTIFIER), routeIdTarget), null, tx);
158         fillAdjRibsOut(null, null, routeIdTarget, this.removedBestPath.getPeerId(), entryDep, tx);
159     }
160
161     private void addPathToDataStore(final RouteEntryDependenciesContainer entryDep,
162             final NodeIdentifierWithPredicates routeIdPA, final DOMDataWriteTransaction tx) {
163         final RIBSupport ribSup = entryDep.getRibSupport();
164         final YangInstanceIdentifier locRibTarget = entryDep.getLocRibTableTarget();
165         NodeIdentifierWithPredicates routeIdDest = ribSup.getRouteIdAddPath(this.bestPath.getPathId(), routeIdPA);
166         if (routeIdDest == null) {
167             routeIdDest = routeIdPA;
168         }
169
170         final MapEntryNode value = createValue(routeIdDest, this.bestPath);
171         LOG.trace("Selected best value {}", value);
172
173         final YangInstanceIdentifier pathAddPathTarget
174                 = ribSup.routePath(locRibTarget.node(ROUTES_IDENTIFIER), routeIdDest);
175         fillLocRib(pathAddPathTarget, value, tx);
176         fillAdjRibsOut(this.bestPath.getAttributes(), value, routeIdDest, this.bestPath.getPeerId(), entryDep, tx);
177     }
178
179     final OffsetMap getOffsets() {
180         return this.offsets;
181     }
182
183     @VisibleForTesting
184     private void fillAdjRibsOut(final ContainerNode attributes, final MapEntryNode value,
185             final NodeIdentifierWithPredicates routeIdPA, final PeerId fromPeerId,
186             final RouteEntryDependenciesContainer routeEntryDep,
187             final DOMDataWriteTransaction tx) {
188         /*
189          * We need to keep track of routers and populate adj-ribs-out, too. If we do not, we need to
190          * expose from which client a particular route was learned from in the local RIB, and have
191          * the listener perform filtering.
192          *
193          * We walk the policy set in order to minimize the amount of work we do for multiple peers:
194          * if we have two eBGP peers, for example, there is no reason why we should perform the translation
195          * multiple times.
196          */
197         final RIBSupport ribSup = routeEntryDep.getRibSupport();
198         final ExportPolicyPeerTracker peerPT = routeEntryDep.getExportPolicyPeerTracker();
199         final TablesKey localTK = routeEntryDep.getLocalTablesKey();
200         for (final PeerRole role : PeerRole.values()) {
201             final PeerExportGroup peerGroup = peerPT.getPeerGroup(role);
202             if (peerGroup != null) {
203                 final Peer fromPeer = this.peerTracker.getPeer(fromPeerId);
204                 final ContainerNode effAttrib = peerGroup.effectiveAttributes(fromPeer.getRole(), attributes);
205                 peerGroup.forEach((destPeer, rootPath) -> {
206                     if (!filterRoutes(fromPeerId, destPeer, localTK)) {
207                         return;
208                     }
209                     update(destPeer, getAdjRibOutYII(ribSup, rootPath, routeIdPA, localTK), effAttrib, value, ribSup,
210                             tx);
211                 });
212             }
213         }
214     }
215 }