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