Merge "BUG-2383 : fixed various small bugs in end-to-end RIB integration"
[bgpcep.git] / bgp / rib-impl / src / main / java / org / opendaylight / protocol / bgp / rib / impl / EffectiveRibInWriter.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.Preconditions;
11 import com.google.common.base.Verify;
12 import java.util.Collection;
13 import javax.annotation.Nonnull;
14 import javax.annotation.concurrent.NotThreadSafe;
15 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
16 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeListener;
17 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeService;
18 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeIdentifier;
19 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
20 import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
21 import org.opendaylight.protocol.bgp.rib.impl.spi.RIBSupportContext;
22 import org.opendaylight.protocol.bgp.rib.impl.spi.RIBSupportContextRegistry;
23 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
24 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.rib.Peer;
25 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.rib.peer.AdjRibIn;
26 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.rib.peer.EffectiveRibIn;
27 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.Tables;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.tables.Routes;
29 import org.opendaylight.yangtools.concepts.ListenerRegistration;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
33 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
34 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
36 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
37 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 /**
42  * Implementation of the BGP import policy. Listens on all Adj-RIB-In, inspects all inbound
43  * routes in the context of the advertising peer's role and applies the inbound policy.
44  *
45  * Inbound policy is applied as follows:
46  *
47  * 1) if the peer is an eBGP peer, perform attribute replacement and filtering
48  * 2) check if a route is admissible based on attributes attached to it, as well as the
49  *    advertising peer's role
50  * 3) output admitting routes with edited attributes into /bgp-rib/rib/peer/effective-rib-in/tables/routes
51  *
52  * Note that we maintain the peer roles using a DCL, even if we could look up our internal
53  * structures. This is done so we maintain causality and loose coupling.
54  */
55 @NotThreadSafe
56 final class EffectiveRibInWriter implements AutoCloseable {
57     private static final Logger LOG = LoggerFactory.getLogger(EffectiveRibInWriter.class);
58     private static final NodeIdentifier TABLE_ROUTES = new NodeIdentifier(Routes.QNAME);
59
60     /**
61      * Maintains {@link TableRouteListener} instances.
62      */
63     private final class AdjInTracker implements AutoCloseable, DOMDataTreeChangeListener {
64         private final RIBSupportContextRegistry registry;
65         private final YangInstanceIdentifier ribId;
66         private final ListenerRegistration<?> reg;
67         private final DOMTransactionChain chain;
68
69         AdjInTracker(final DOMDataTreeChangeService service, final RIBSupportContextRegistry registry, final DOMTransactionChain chain, final YangInstanceIdentifier ribId) {
70             this.registry = Preconditions.checkNotNull(registry);
71             this.chain = Preconditions.checkNotNull(chain);
72             this.ribId = Preconditions.checkNotNull(ribId);
73
74             final YangInstanceIdentifier tableId = ribId.node(Peer.QNAME).node(Peer.QNAME).node(AdjRibIn.QNAME).node(Tables.QNAME).node(Tables.QNAME);
75             final DOMDataTreeIdentifier treeId = new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, tableId);
76             LOG.debug("Registered Effective RIB on {}", tableId);
77             this.reg = service.registerDataTreeChangeListener(treeId, this);
78         }
79
80         private void processRoute(final DOMDataWriteTransaction tx, final RIBSupport ribSupport, final AbstractImportPolicy policy, final YangInstanceIdentifier routesPath, final DataTreeCandidateNode route) {
81             LOG.debug("Process route {}", route);
82             switch (route.getModificationType()) {
83             case DELETE:
84                 // Delete has already been affected by the store in caller, so this is a no-op.
85                 break;
86             case MERGE:
87                 LOG.info("Merge on {} reported, this should never have happened, ignoring", route);
88                 break;
89             case UNMODIFIED:
90                 // No-op
91                 break;
92             case SUBTREE_MODIFIED:
93             case WRITE:
94                 // Lookup per-table attributes from RIBSupport
95                 final ContainerNode advertisedAttrs = (ContainerNode) NormalizedNodes.findNode(route.getDataAfter(), ribSupport.routeAttributesIdentifier()).orNull();
96                 final ContainerNode effectiveAttrs;
97
98                 if (advertisedAttrs != null) {
99                     effectiveAttrs = policy.effectiveAttributes(advertisedAttrs);
100
101                     /*
102                      * Speed hack: if we determine that the policy has passed the attributes
103                      * back unmodified, the corresponding change has already been written in
104                      * our caller. There is no need to perform any further processing.
105                      *
106                      * We also use direct object comparison to make the check very fast, as
107                      * it may not be that common, in which case it does not make sense to pay
108                      * the full equals price.
109                      */
110                     if (effectiveAttrs == advertisedAttrs) {
111                         LOG.trace("Effective and local attributes are equal. Quit processing route {}", route);
112                         return;
113                     }
114                 } else {
115                     effectiveAttrs = null;
116                 }
117
118                 final YangInstanceIdentifier routeId = ribSupport.routePath(routesPath, route.getIdentifier());
119                 LOG.debug("Route {} effective attributes {} towards {}", route.getIdentifier(), effectiveAttrs, routeId);
120
121                 if (effectiveAttrs != null) {
122                     tx.put(LogicalDatastoreType.OPERATIONAL, routeId.node(ribSupport.routeAttributesIdentifier()), effectiveAttrs);
123                 } else {
124                     LOG.warn("Route {} advertised empty attributes", routeId);
125                     tx.delete(LogicalDatastoreType.OPERATIONAL,  routeId);
126                 }
127                 break;
128             default:
129                 LOG.warn("Ignoring unhandled route {}", route);
130                 break;
131             }
132         }
133
134         private void processTableChildren(final DOMDataWriteTransaction tx, final RIBSupport ribSupport, final NodeIdentifierWithPredicates peerKey, final YangInstanceIdentifier tablePath, final Collection<DataTreeCandidateNode> children) {
135             final AbstractImportPolicy policy = EffectiveRibInWriter.this.peerPolicyTracker.policyFor(IdentifierUtils.peerId(peerKey));
136
137             for (final DataTreeCandidateNode child : children) {
138                 LOG.debug("Process table children {}", child);
139                 switch (child.getModificationType()) {
140                 case DELETE:
141                     tx.delete(LogicalDatastoreType.OPERATIONAL, tablePath.node(child.getIdentifier()));
142                     break;
143                 case MERGE:
144                     LOG.info("Merge on {} reported, this should never have happened, ignoring", child);
145                     break;
146                 case UNMODIFIED:
147                     // No-op
148                     break;
149                 case SUBTREE_MODIFIED:
150                 case WRITE:
151                     tx.put(LogicalDatastoreType.OPERATIONAL, tablePath.node(child.getIdentifier()), child.getDataAfter().get());
152
153                     // Routes are special, as they may end up being filtered. The previous put conveniently
154                     // ensured that we have them in at target, so a subsequent delete will not fail :)
155                     if (TABLE_ROUTES.equals(child.getIdentifier())) {
156                         final YangInstanceIdentifier routesPath = tablePath.node(Routes.QNAME);
157                         for (final DataTreeCandidateNode route : ribSupport.changedRoutes(child)) {
158                             processRoute(tx, ribSupport, policy, routesPath, route);
159                         }
160                     }
161                     break;
162                 default:
163                     LOG.warn("Ignoring unhandled child {}", child);
164                     break;
165                 }
166             }
167         }
168
169         private RIBSupportContext getRibSupport(final NodeIdentifierWithPredicates tableKey) {
170             return this.registry.getRIBSupportContext(tableKey);
171         }
172
173         private YangInstanceIdentifier effectiveTablePath(final NodeIdentifierWithPredicates peerKey, final NodeIdentifierWithPredicates tableKey) {
174             return this.ribId.node(Peer.QNAME).node(peerKey).node(EffectiveRibIn.QNAME).node(Tables.QNAME).node(tableKey);
175         }
176
177         private void modifyTable(final DOMDataWriteTransaction tx, final NodeIdentifierWithPredicates peerKey, final NodeIdentifierWithPredicates tableKey, final DataTreeCandidateNode table) {
178             final RIBSupportContext ribSupport = getRibSupport(tableKey);
179             final YangInstanceIdentifier tablePath = effectiveTablePath(peerKey, tableKey);
180
181             processTableChildren(tx, ribSupport.getRibSupport(), peerKey, tablePath, table.getChildNodes());
182         }
183
184         private void writeTable(final DOMDataWriteTransaction tx, final NodeIdentifierWithPredicates peerKey, final NodeIdentifierWithPredicates tableKey, final DataTreeCandidateNode table) {
185             final RIBSupportContext ribSupport = getRibSupport(tableKey);
186             final YangInstanceIdentifier tablePath = effectiveTablePath(peerKey, tableKey);
187
188             // Create an empty table
189             ribSupport.clearTable(tx,tablePath);
190
191             processTableChildren(tx, ribSupport.getRibSupport(), peerKey, tablePath, table.getChildNodes());
192         }
193
194         @Override
195         public void onDataTreeChanged(final Collection<DataTreeCandidate> changes) {
196             LOG.trace("Data changed called to effective RIB. Change : {}", changes);
197             final DOMDataWriteTransaction tx = this.chain.newWriteOnlyTransaction();
198
199             for (final DataTreeCandidate tc : changes) {
200                 final YangInstanceIdentifier rootPath = tc.getRootPath();
201
202                 // Obtain the peer's key
203                 final NodeIdentifierWithPredicates peerKey = IdentifierUtils.peerKey(rootPath);
204
205                 // Extract the table key, this should be safe based on the path where we subscribed,
206                 // but let's verify explicitly.
207                 final PathArgument lastArg = rootPath.getLastPathArgument();
208                 Verify.verify(lastArg instanceof NodeIdentifierWithPredicates, "Unexpected type %s in path %s", lastArg.getClass(), rootPath);
209                 final NodeIdentifierWithPredicates tableKey = (NodeIdentifierWithPredicates) lastArg;
210
211                 final DataTreeCandidateNode root = tc.getRootNode();
212                 switch (root.getModificationType()) {
213                 case DELETE:
214                     // delete the corresponding effective table
215                     tx.delete(LogicalDatastoreType.OPERATIONAL, effectiveTablePath(peerKey, tableKey));
216                     break;
217                 case MERGE:
218                     // TODO: upstream API should never give us this, as it leaks how the delta was created.
219                     LOG.info("Merge on {} reported, this should never have happened, but attempting to cope", rootPath);
220                     modifyTable(tx, peerKey, tableKey, root);
221                     break;
222                 case SUBTREE_MODIFIED:
223                     modifyTable(tx, peerKey, tableKey, root);
224                     break;
225                 case UNMODIFIED:
226                     LOG.info("Ignoring spurious notification on {} data {}", rootPath, root);
227                     break;
228                 case WRITE:
229                     writeTable(tx, peerKey, tableKey, root);
230                     break;
231                 default:
232                     LOG.warn("Ignoring unhandled root {}", root);
233                     break;
234                 }
235             }
236
237             tx.submit();
238         }
239
240         @Override
241         public void close() {
242             // FIXME: wipe all effective routes?
243             this.reg.close();
244         }
245     }
246
247     private final ImportPolicyPeerTracker peerPolicyTracker;
248     private final AdjInTracker adjInTracker;
249
250     static EffectiveRibInWriter create(@Nonnull final DOMDataTreeChangeService service, @Nonnull final DOMTransactionChain chain,
251         @Nonnull final YangInstanceIdentifier ribId, @Nonnull final PolicyDatabase pd, @Nonnull final RIBSupportContextRegistry registry) {
252         return new EffectiveRibInWriter(service, chain, ribId, pd, registry);
253     }
254
255     private EffectiveRibInWriter(final DOMDataTreeChangeService service, final DOMTransactionChain chain, final YangInstanceIdentifier ribId,
256         final PolicyDatabase pd, final RIBSupportContextRegistry registry) {
257         this.peerPolicyTracker = new ImportPolicyPeerTracker(service, ribId, pd);
258         this.adjInTracker = new AdjInTracker(service, registry, chain, ribId);
259     }
260
261     @Override
262     public void close() {
263         this.adjInTracker.close();
264         this.peerPolicyTracker.close();
265     }
266 }