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