BUG-2399: handle new ModificationTypes
[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     private static final NodeIdentifier ADJRIBIN_NID = new NodeIdentifier(AdjRibIn.QNAME);
60     private static final NodeIdentifier TABLES_NID = new NodeIdentifier(Tables.QNAME);
61
62     /**
63      * Maintains {@link TableRouteListener} instances.
64      */
65     private final class AdjInTracker implements AutoCloseable, DOMDataTreeChangeListener {
66         private final RIBSupportContextRegistry registry;
67         private final YangInstanceIdentifier ribId;
68         private final ListenerRegistration<?> reg;
69         private final DOMTransactionChain chain;
70
71         AdjInTracker(final DOMDataTreeChangeService service, final RIBSupportContextRegistry registry, final DOMTransactionChain chain, final YangInstanceIdentifier ribId) {
72             this.registry = Preconditions.checkNotNull(registry);
73             this.chain = Preconditions.checkNotNull(chain);
74             this.ribId = Preconditions.checkNotNull(ribId);
75
76             final YangInstanceIdentifier tableId = ribId.node(Peer.QNAME).node(Peer.QNAME);
77             final DOMDataTreeIdentifier treeId = new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, tableId);
78             LOG.debug("Registered Effective RIB on {}", tableId);
79             this.reg = service.registerDataTreeChangeListener(treeId, this);
80         }
81
82         private void processRoute(final DOMDataWriteTransaction tx, final RIBSupport ribSupport, final AbstractImportPolicy policy, final YangInstanceIdentifier routesPath, final DataTreeCandidateNode route) {
83             LOG.debug("Process route {}", route);
84             final YangInstanceIdentifier routeId = ribSupport.routePath(routesPath, route.getIdentifier());
85             switch (route.getModificationType()) {
86             case DELETE:
87             case DISAPPEARED:
88                 tx.delete(LogicalDatastoreType.OPERATIONAL, routeId);
89                 break;
90             case UNMODIFIED:
91                 // No-op
92                 break;
93             case APPEARED:
94             case SUBTREE_MODIFIED:
95             case WRITE:
96                 tx.put(LogicalDatastoreType.OPERATIONAL, routeId, route.getDataAfter().get());
97                 // Lookup per-table attributes from RIBSupport
98                 final ContainerNode advertisedAttrs = (ContainerNode) NormalizedNodes.findNode(route.getDataAfter(), ribSupport.routeAttributesIdentifier()).orNull();
99                 final ContainerNode effectiveAttrs;
100
101                 if (advertisedAttrs != null) {
102                     effectiveAttrs = policy.effectiveAttributes(advertisedAttrs);
103                 } else {
104                     effectiveAttrs = null;
105                 }
106
107                 LOG.debug("Route {} effective attributes {} towards {}", route.getIdentifier(), effectiveAttrs, routeId);
108
109                 if (effectiveAttrs != null) {
110                     tx.put(LogicalDatastoreType.OPERATIONAL, routeId.node(ribSupport.routeAttributesIdentifier()), effectiveAttrs);
111                 } else {
112                     LOG.warn("Route {} advertised empty attributes", routeId);
113                     tx.delete(LogicalDatastoreType.OPERATIONAL,  routeId);
114                 }
115                 break;
116             default:
117                 LOG.warn("Ignoring unhandled route {}", route);
118                 break;
119             }
120         }
121
122         private void processTableChildren(final DOMDataWriteTransaction tx, final RIBSupport ribSupport, final NodeIdentifierWithPredicates peerKey, final YangInstanceIdentifier tablePath, final Collection<DataTreeCandidateNode> children) {
123             final AbstractImportPolicy policy = EffectiveRibInWriter.this.peerPolicyTracker.policyFor(IdentifierUtils.peerId(peerKey));
124
125             for (final DataTreeCandidateNode child : children) {
126                 LOG.debug("Process table {} type {}", child, child.getModificationType());
127                 final YangInstanceIdentifier childPath = tablePath.node(child.getIdentifier());
128                 switch (child.getModificationType()) {
129                 case DELETE:
130                 case DISAPPEARED:
131                     tx.delete(LogicalDatastoreType.OPERATIONAL, tablePath.node(child.getIdentifier()));
132                     break;
133                 case UNMODIFIED:
134                     // No-op
135                     break;
136                 case SUBTREE_MODIFIED:
137                     if (TABLE_ROUTES.equals(child.getIdentifier())) {
138                         for (final DataTreeCandidateNode route : ribSupport.changedRoutes(child)) {
139                             processRoute(tx, ribSupport, policy, childPath, route);
140                         }
141                     } else {
142                         tx.put(LogicalDatastoreType.OPERATIONAL, childPath, child.getDataAfter().get());
143                     }
144                     break;
145                 case APPEARED:
146                 case WRITE:
147                     tx.put(LogicalDatastoreType.OPERATIONAL, childPath, child.getDataAfter().get());
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                         for (final DataTreeCandidateNode route : ribSupport.changedRoutes(child)) {
152                             processRoute(tx, ribSupport, policy, childPath, route);
153                         }
154                     }
155                     break;
156                 default:
157                     LOG.warn("Ignoring unhandled child {}", child);
158                     break;
159                 }
160             }
161         }
162
163         private RIBSupportContext getRibSupport(final NodeIdentifierWithPredicates tableKey) {
164             return this.registry.getRIBSupportContext(tableKey);
165         }
166
167         private YangInstanceIdentifier effectiveTablePath(final NodeIdentifierWithPredicates peerKey, final NodeIdentifierWithPredicates tableKey) {
168             return this.ribId.node(Peer.QNAME).node(peerKey).node(EffectiveRibIn.QNAME).node(Tables.QNAME).node(tableKey);
169         }
170
171         private void modifyTable(final DOMDataWriteTransaction tx, final NodeIdentifierWithPredicates peerKey, final NodeIdentifierWithPredicates tableKey, final DataTreeCandidateNode table) {
172             final RIBSupportContext ribSupport = getRibSupport(tableKey);
173             final YangInstanceIdentifier tablePath = effectiveTablePath(peerKey, tableKey);
174
175             processTableChildren(tx, ribSupport.getRibSupport(), peerKey, tablePath, table.getChildNodes());
176         }
177
178         private void writeTable(final DOMDataWriteTransaction tx, final NodeIdentifierWithPredicates peerKey, final NodeIdentifierWithPredicates tableKey, final DataTreeCandidateNode table) {
179             final RIBSupportContext ribSupport = getRibSupport(tableKey);
180             final YangInstanceIdentifier tablePath = effectiveTablePath(peerKey, tableKey);
181
182             // Create an empty table
183             ribSupport.clearTable(tx,tablePath);
184
185             processTableChildren(tx, ribSupport.getRibSupport(), peerKey, tablePath, table.getChildNodes());
186         }
187
188         @Override
189         public void onDataTreeChanged(final Collection<DataTreeCandidate> changes) {
190             LOG.trace("Data changed called to effective RIB. Change : {}", changes);
191
192             // we have a lot of transactions created for 'nothing' because a lot of changes
193             // are skipped, so ensure we only create one transaction when we really need it
194             DOMDataWriteTransaction tx = null;
195             for (final DataTreeCandidate tc : changes) {
196                 final YangInstanceIdentifier rootPath = tc.getRootPath();
197
198                 // Obtain the peer's key
199                 final NodeIdentifierWithPredicates peerKey = IdentifierUtils.peerKey(rootPath);
200                 final DataTreeCandidateNode root = tc.getRootNode();
201
202                 // call out peer-role has changed
203                 final DataTreeCandidateNode roleChange =  root.getModifiedChild(AbstractPeerRoleTracker.PEER_ROLE_NID);
204                 if (roleChange != null) {
205                     EffectiveRibInWriter.this.peerPolicyTracker.onDataTreeChanged(roleChange, IdentifierUtils.peerPath(rootPath));
206                 }
207
208                 // filter out any change outside AdjRibsIn
209                 final DataTreeCandidateNode ribIn =  root.getModifiedChild(ADJRIBIN_NID);
210                 if (ribIn == null) {
211                     LOG.debug("Skipping change {}", tc.getRootNode());
212                     continue;
213                 }
214                 final DataTreeCandidateNode tables = ribIn.getModifiedChild(TABLES_NID);
215                 if (tables == null) {
216                     LOG.debug("Skipping change {}", tc.getRootNode());
217                     continue;
218                 }
219                 for (final DataTreeCandidateNode table : tables.getChildNodes()) {
220                     if (tx == null) {
221                         tx = this.chain.newWriteOnlyTransaction();
222                     }
223                     changeDataTree(tx, rootPath, root, peerKey, table);
224                 }
225             }
226             if (tx != null) {
227                 tx.submit();
228             }
229         }
230
231         private void changeDataTree(final DOMDataWriteTransaction tx, final YangInstanceIdentifier rootPath,
232             final DataTreeCandidateNode root, final NodeIdentifierWithPredicates peerKey, final DataTreeCandidateNode table) {
233             final PathArgument lastArg = table.getIdentifier();
234             Verify.verify(lastArg instanceof NodeIdentifierWithPredicates, "Unexpected type %s in path %s", lastArg.getClass(), rootPath);
235             final NodeIdentifierWithPredicates tableKey = (NodeIdentifierWithPredicates) lastArg;
236
237             switch (root.getModificationType()) {
238             case DELETE:
239             case DISAPPEARED:
240                 // delete the corresponding effective table
241                 tx.delete(LogicalDatastoreType.OPERATIONAL, effectiveTablePath(peerKey, tableKey));
242                 break;
243             case SUBTREE_MODIFIED:
244                 modifyTable(tx, peerKey, tableKey, table);
245                 break;
246             case UNMODIFIED:
247                 LOG.info("Ignoring spurious notification on {} data {}", rootPath, table);
248                 break;
249             case APPEARED:
250             case WRITE:
251                 writeTable(tx, peerKey, tableKey, table);
252                 break;
253             default:
254                 LOG.warn("Ignoring unhandled root {}", root);
255                 break;
256             }
257         }
258
259         @Override
260         public void close() {
261             // FIXME: wipe all effective routes?
262             this.reg.close();
263         }
264     }
265
266     private final ImportPolicyPeerTracker peerPolicyTracker;
267     private final AdjInTracker adjInTracker;
268
269     static EffectiveRibInWriter create(@Nonnull final DOMDataTreeChangeService service, @Nonnull final DOMTransactionChain chain,
270         @Nonnull final YangInstanceIdentifier ribId, @Nonnull final PolicyDatabase pd, @Nonnull final RIBSupportContextRegistry registry) {
271         return new EffectiveRibInWriter(service, chain, ribId, pd, registry);
272     }
273
274     private EffectiveRibInWriter(final DOMDataTreeChangeService service, final DOMTransactionChain chain, final YangInstanceIdentifier ribId,
275         final PolicyDatabase pd, final RIBSupportContextRegistry registry) {
276         this.peerPolicyTracker = new ImportPolicyPeerTracker(pd);
277         this.adjInTracker = new AdjInTracker(service, registry, chain, ribId);
278     }
279
280     @Override
281     public void close() {
282         this.adjInTracker.close();
283     }
284 }