BUG-5844: Fix ComplexRouteEntry creation
[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 java.util.Optional;
13 import javax.annotation.concurrent.NotThreadSafe;
14 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
15 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
16 import org.opendaylight.protocol.bgp.mode.api.BestPath;
17 import org.opendaylight.protocol.bgp.mode.impl.OffsetMap;
18 import org.opendaylight.protocol.bgp.mode.spi.AbstractRouteEntry;
19 import org.opendaylight.protocol.bgp.rib.spi.CacheDisconnectedPeers;
20 import org.opendaylight.protocol.bgp.rib.spi.ExportPolicyPeerTracker;
21 import org.opendaylight.protocol.bgp.rib.spi.PeerExportGroup;
22 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
23 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.PeerId;
24 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.PeerRole;
25 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.TablesKey;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
29 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
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 {
37
38     private static final Logger LOG = LoggerFactory.getLogger(BaseAbstractRouteEntry.class);
39     private static final ContainerNode[] EMPTY_ATTRIBUTES = new ContainerNode[0];
40     private OffsetMap<UnsignedInteger> offsets = new OffsetMap<>(UnsignedInteger.class);
41     private ContainerNode[] values = EMPTY_ATTRIBUTES;
42     private Optional<BaseBestPath> bestPath = Optional.empty();
43     private Optional<BaseBestPath> removedBestPath = Optional.empty();
44
45     private int addRoute(final UnsignedInteger routerId, final ContainerNode attributes) {
46         int offset = this.offsets.offsetOf(routerId);
47         if (offset < 0) {
48             final OffsetMap<UnsignedInteger> newOffsets = this.offsets.with(routerId);
49             offset = newOffsets.offsetOf(routerId);
50
51             this.values = newOffsets.expand(this.offsets, this.values, offset);
52             this.offsets = newOffsets;
53         }
54
55         this.offsets.setValue(this.values, offset, attributes);
56         LOG.trace("Added route from {} attributes {}", routerId, attributes);
57         return offset;
58     }
59
60     /**
61      * Remove route
62      *
63      * @param routerId router ID in unsigned integer
64      * @param offset of removed route
65      * @return true if its the last route
66      */
67     protected final boolean removeRoute(final UnsignedInteger routerId, final int offset) {
68         this.values = this.offsets.removeValue(this.values, offset);
69         this.offsets = this.offsets.without(routerId);
70         return this.offsets.isEmty();
71     }
72
73     @Override
74     public final boolean selectBest(final long localAs) {
75         /*
76          * FIXME: optimize flaps by making sure we consider stability of currently-selected route.
77          */
78         final BasePathSelector selector = new BasePathSelector(localAs);
79
80         // Select the best route.
81         for (int i = 0; i < this.offsets.size(); ++i) {
82             final UnsignedInteger routerId = this.offsets.getRouterKey(i);
83             final ContainerNode attributes = this.offsets.getValue(this.values, i);
84             LOG.trace("Processing router id {} attributes {}", routerId, attributes);
85             selector.processPath(routerId, attributes);
86         }
87
88         // Get the newly-selected best path.
89         final Optional<BaseBestPath> newBestPath = Optional.ofNullable(selector.result());
90         final boolean modified = !newBestPath.equals(this.bestPath);
91         if (modified) {
92             this.removedBestPath = this.bestPath;
93             LOG.trace("Previous best {}, current best {}", this.bestPath, newBestPath);
94             this.bestPath = newBestPath;
95         }
96         return modified;
97     }
98
99     @Override
100     public int addRoute(final UnsignedInteger routerId, final Long remotePathId, final NodeIdentifier attributesIdentifier, final NormalizedNode<?, ?> data) {
101         LOG.trace("Find {} in {}", attributesIdentifier, data);
102         final ContainerNode advertisedAttrs = (ContainerNode) NormalizedNodes.findNode(data, attributesIdentifier).orNull();
103         return addRoute(routerId, advertisedAttrs);
104     }
105
106     @Override
107     public void updateRoute(final TablesKey localTK, final ExportPolicyPeerTracker peerPT, final YangInstanceIdentifier locRibTarget, final RIBSupport ribSup,
108         final CacheDisconnectedPeers discPeers, final DOMDataWriteTransaction tx, final PathArgument routeIdPA) {
109         if (this.removedBestPath.isPresent()) {
110             removePathFromDataStore(this.removedBestPath.get(), routeIdPA, locRibTarget, peerPT, localTK, ribSup, discPeers, tx);
111             this.removedBestPath = Optional.empty();
112         }
113         if (this.bestPath.isPresent()) {
114             addPathToDataStore(this.bestPath.get(), routeIdPA, locRibTarget, ribSup, peerPT, localTK, discPeers, tx);
115         }
116     }
117
118     @Override
119     public void writeRoute(final PeerId destPeer, final PathArgument routeId, final YangInstanceIdentifier rootPath, final PeerExportGroup peerGroup,
120         final TablesKey localTK, final ExportPolicyPeerTracker peerPT, final RIBSupport ribSup, final CacheDisconnectedPeers discPeers,
121         final DOMDataWriteTransaction tx) {
122         if (this.bestPath.isPresent()) {
123             final BaseBestPath path = this.bestPath.get();
124             if (filterRoutes(path.getPeerId(), destPeer, peerPT, localTK, discPeers)) {
125                 final ContainerNode effAttrib = peerGroup.effectiveAttributes(path.getPeerId(), path.getAttributes());
126                 writeRoute(destPeer, getAdjRibOutYII(ribSup, rootPath, routeId, localTK), effAttrib, createValue(routeId, path), ribSup, tx);
127             }
128         }
129     }
130
131     private void removePathFromDataStore(final BestPath path, final PathArgument routeIdPA, final YangInstanceIdentifier locRibTarget,
132         final ExportPolicyPeerTracker peerPT, final TablesKey localTK, final RIBSupport ribSup, final CacheDisconnectedPeers discPeers,
133         final DOMDataWriteTransaction tx) {
134         LOG.trace("Best Path removed {}", path);
135         final PathArgument routeIdAddPath = ribSup.getRouteIdAddPath(path.getPathId(), routeIdPA);
136         final YangInstanceIdentifier pathTarget = ribSup.routePath(locRibTarget.node(ROUTES_IDENTIFIER), routeIdPA);
137         YangInstanceIdentifier pathAddPathTarget = null;
138         if (routeIdAddPath != null) {
139             pathAddPathTarget = ribSup.routePath(locRibTarget.node(ROUTES_IDENTIFIER), routeIdAddPath);
140         }
141         fillLocRib(pathAddPathTarget == null ? pathTarget : pathAddPathTarget, null, tx);
142         fillAdjRibsOut(null, null, routeIdPA, path.getPeerId(), peerPT, localTK, ribSup, discPeers, tx);
143     }
144
145     private void addPathToDataStore(final BestPath path, final PathArgument routeIdPA, final YangInstanceIdentifier locRibTarget,
146         final RIBSupport ribSup, final ExportPolicyPeerTracker peerPT, final TablesKey localTK, final CacheDisconnectedPeers discPeers,
147         final DOMDataWriteTransaction tx) {
148         final PathArgument routeIdAddPath = ribSup.getRouteIdAddPath(path.getPathId(), routeIdPA);
149         final YangInstanceIdentifier pathTarget = ribSup.routePath(locRibTarget.node(ROUTES_IDENTIFIER), routeIdPA);
150         final NormalizedNode<?, ?> value = createValue(routeIdPA, path);
151         NormalizedNode<?, ?> addPathValue = null;
152         YangInstanceIdentifier pathAddPathTarget = null;
153         if (routeIdAddPath == null) {
154             LOG.trace("Selected best value {}", value);
155         } else {
156             pathAddPathTarget = ribSup.routePath(locRibTarget.node(ROUTES_IDENTIFIER), routeIdAddPath);
157             addPathValue = createValue(routeIdAddPath, path);
158             LOG.trace("Selected best value {}", addPathValue);
159         }
160         fillLocRib(pathAddPathTarget == null ? pathTarget : pathAddPathTarget, addPathValue == null ? value : addPathValue, tx);
161         fillAdjRibsOut(path.getAttributes(), value, routeIdPA, path.getPeerId(), peerPT, localTK, ribSup, discPeers, tx);
162     }
163
164     protected final OffsetMap<UnsignedInteger> getOffsets() {
165         return this.offsets;
166     }
167
168     @VisibleForTesting
169     private void fillAdjRibsOut(final ContainerNode attributes, final NormalizedNode<?, ?> value,
170         final PathArgument routeId, final PeerId routePeerId, final ExportPolicyPeerTracker peerPT, final TablesKey localTK, final RIBSupport ribSup,
171         final CacheDisconnectedPeers discPeers, final DOMDataWriteTransaction tx) {
172         /*
173          * We need to keep track of routers and populate adj-ribs-out, too. If we do not, we need to
174          * expose from which client a particular route was learned from in the local RIB, and have
175          * the listener perform filtering.
176          *
177          * We walk the policy set in order to minimize the amount of work we do for multiple peers:
178          * if we have two eBGP peers, for example, there is no reason why we should perform the translation
179          * multiple times.
180          */
181         for (final PeerRole role : PeerRole.values()) {
182             if (PeerRole.Internal.equals(role)) {
183                 continue;
184             }
185             final PeerExportGroup peerGroup = peerPT.getPeerGroup(role);
186             if (peerGroup != null) {
187                 final ContainerNode effAttrib = peerGroup.effectiveAttributes(routePeerId, attributes);
188                 peerGroup.getPeers().stream()
189                     .filter(pid -> filterRoutes(routePeerId, pid.getKey(), peerPT, localTK, discPeers))
190                     .forEach(pid -> update(pid.getKey(), getAdjRibOutYII(ribSup, pid.getValue(), routeId, localTK), effAttrib, value, ribSup, tx));
191             }
192         }
193     }
194
195     private void update(final PeerId destPeer, final YangInstanceIdentifier routeTarget, final ContainerNode effAttrib,
196         final NormalizedNode<?, ?> value, final RIBSupport ribSup, final DOMDataWriteTransaction tx) {
197         if (!writeRoute(destPeer, routeTarget, effAttrib, value, ribSup, tx)) {
198             LOG.trace("Removing {} from transaction for peer {}", routeTarget, destPeer);
199             tx.delete(LogicalDatastoreType.OPERATIONAL, routeTarget);
200         }
201     }
202 }