BGP Logs improvement
[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.Optional;
11 import com.google.common.base.Preconditions;
12 import com.google.common.base.Verify;
13 import java.util.Collection;
14 import javax.annotation.Nonnull;
15 import javax.annotation.concurrent.NotThreadSafe;
16 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
17 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeListener;
18 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeService;
19 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeIdentifier;
20 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
21 import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
22 import org.opendaylight.protocol.bgp.rib.impl.spi.RIBSupportContext;
23 import org.opendaylight.protocol.bgp.rib.impl.spi.RIBSupportContextRegistry;
24 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
25 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.rib.Peer;
26 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.rib.peer.AdjRibIn;
27 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.rib.peer.EffectiveRibIn;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.Tables;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.tables.Routes;
30 import org.opendaylight.yangtools.concepts.ListenerRegistration;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
33 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
34 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
35 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
38 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
39 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
40 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 /**
45  * Implementation of the BGP import policy. Listens on all Adj-RIB-In, inspects all inbound
46  * routes in the context of the advertising peer's role and applies the inbound policy.
47  *
48  * Inbound policy is applied as follows:
49  *
50  * 1) if the peer is an eBGP peer, perform attribute replacement and filtering
51  * 2) check if a route is admissible based on attributes attached to it, as well as the
52  *    advertising peer's role
53  * 3) output admitting routes with edited attributes into /bgp-rib/rib/peer/effective-rib-in/tables/routes
54  *
55  * Note that we maintain the peer roles using a DCL, even if we could look up our internal
56  * structures. This is done so we maintain causality and loose coupling.
57  */
58 @NotThreadSafe
59 final class EffectiveRibInWriter implements AutoCloseable {
60     private static final Logger LOG = LoggerFactory.getLogger(EffectiveRibInWriter.class);
61     protected static final NodeIdentifier TABLE_ROUTES = new NodeIdentifier(Routes.QNAME);
62     private static final NodeIdentifier ADJRIBIN_NID = new NodeIdentifier(AdjRibIn.QNAME);
63     private static final NodeIdentifier TABLES_NID = new NodeIdentifier(Tables.QNAME);
64
65     /**
66      * Maintains {@link TableRouteListener} instances.
67      */
68     private final class AdjInTracker implements AutoCloseable, DOMDataTreeChangeListener {
69         private final RIBSupportContextRegistry registry;
70         private final YangInstanceIdentifier ribId;
71         private final ListenerRegistration<?> reg;
72         private final DOMTransactionChain chain;
73
74         AdjInTracker(final DOMDataTreeChangeService service, final RIBSupportContextRegistry registry, final DOMTransactionChain chain, final YangInstanceIdentifier ribId) {
75             this.registry = Preconditions.checkNotNull(registry);
76             this.chain = Preconditions.checkNotNull(chain);
77             this.ribId = Preconditions.checkNotNull(ribId);
78
79             final YangInstanceIdentifier tableId = ribId.node(Peer.QNAME).node(Peer.QNAME);
80             final DOMDataTreeIdentifier treeId = new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, tableId);
81             LOG.debug("Registered Effective RIB on {}", tableId);
82             this.reg = service.registerDataTreeChangeListener(treeId, this);
83         }
84
85         private void processRoute(final DOMDataWriteTransaction tx, final RIBSupport ribSupport, final AbstractImportPolicy policy, final YangInstanceIdentifier routesPath, final DataTreeCandidateNode route) {
86             LOG.debug("Process route {}", route.getIdentifier());
87             final YangInstanceIdentifier routeId = ribSupport.routePath(routesPath, route.getIdentifier());
88             switch (route.getModificationType()) {
89             case DELETE:
90             case DISAPPEARED:
91                 tx.delete(LogicalDatastoreType.OPERATIONAL, routeId);
92                 break;
93             case UNMODIFIED:
94                 // No-op
95                 break;
96             case APPEARED:
97             case SUBTREE_MODIFIED:
98             case WRITE:
99                 tx.put(LogicalDatastoreType.OPERATIONAL, routeId, route.getDataAfter().get());
100                 // Lookup per-table attributes from RIBSupport
101                 final ContainerNode advertisedAttrs = (ContainerNode) NormalizedNodes.findNode(route.getDataAfter(), ribSupport.routeAttributesIdentifier()).orNull();
102                 final ContainerNode effectiveAttrs;
103
104                 if (advertisedAttrs != null) {
105                     effectiveAttrs = policy.effectiveAttributes(advertisedAttrs);
106                 } else {
107                     effectiveAttrs = null;
108                 }
109
110                 LOG.debug("Route {} effective attributes {} towards {}", route.getIdentifier(), effectiveAttrs, routeId);
111
112                 if (effectiveAttrs != null) {
113                     tx.put(LogicalDatastoreType.OPERATIONAL, routeId.node(ribSupport.routeAttributesIdentifier()), effectiveAttrs);
114                 } else {
115                     LOG.warn("Route {} advertised empty attributes", routeId);
116                     tx.delete(LogicalDatastoreType.OPERATIONAL,  routeId);
117                 }
118                 break;
119             default:
120                 LOG.warn("Ignoring unhandled route {}", route);
121                 break;
122             }
123         }
124
125         private void processTableChildren(final DOMDataWriteTransaction tx, final RIBSupport ribSupport, final NodeIdentifierWithPredicates peerKey, final YangInstanceIdentifier tablePath, final Collection<DataTreeCandidateNode> children) {
126             final AbstractImportPolicy policy = EffectiveRibInWriter.this.peerPolicyTracker.policyFor(IdentifierUtils.peerId(peerKey));
127
128             for (final DataTreeCandidateNode child : children) {
129                 final PathArgument childIdentifier = child.getIdentifier();
130                 final Optional<NormalizedNode<?, ?>> childDataAfter = child.getDataAfter();
131                 LOG.debug("Process table {} type {}, dataAfter {}, dataBefore {}", childIdentifier, child
132                     .getModificationType(), childDataAfter, child.getDataBefore());
133                 final YangInstanceIdentifier childPath = tablePath.node(childIdentifier);
134                 switch (child.getModificationType()) {
135                 case DELETE:
136                 case DISAPPEARED:
137                     tx.delete(LogicalDatastoreType.OPERATIONAL, tablePath.node(childIdentifier));
138                     break;
139                 case UNMODIFIED:
140                     // No-op
141                     break;
142                 case SUBTREE_MODIFIED:
143                     if (TABLE_ROUTES.equals(childIdentifier)) {
144                         for (final DataTreeCandidateNode route : ribSupport.changedRoutes(child)) {
145                             processRoute(tx, ribSupport, policy, childPath, route);
146                         }
147                     } else {
148                         tx.put(LogicalDatastoreType.OPERATIONAL, childPath, childDataAfter.get());
149                     }
150                     break;
151                 case APPEARED:
152                 case WRITE:
153                     tx.put(LogicalDatastoreType.OPERATIONAL, childPath, childDataAfter.get());
154                     // Routes are special, as they may end up being filtered. The previous put conveniently
155                     // ensured that we have them in at target, so a subsequent delete will not fail :)
156                     if (TABLE_ROUTES.equals(childIdentifier)) {
157                         for (final DataTreeCandidateNode route : ribSupport.changedRoutes(child)) {
158                             processRoute(tx, ribSupport, policy, childPath, 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             LOG.trace("Create Empty table", tablePath);
190             ribSupport.clearTable(tx, tablePath);
191
192             processTableChildren(tx, ribSupport.getRibSupport(), peerKey, tablePath, table.getChildNodes());
193         }
194
195         @Override
196         public void onDataTreeChanged(final Collection<DataTreeCandidate> changes) {
197             LOG.trace("Data changed called to effective RIB. Change : {}", changes);
198
199             // we have a lot of transactions created for 'nothing' because a lot of changes
200             // are skipped, so ensure we only create one transaction when we really need it
201             DOMDataWriteTransaction tx = null;
202             for (final DataTreeCandidate tc : changes) {
203                 final YangInstanceIdentifier rootPath = tc.getRootPath();
204
205                 // Obtain the peer's key
206                 final NodeIdentifierWithPredicates peerKey = IdentifierUtils.peerKey(rootPath);
207                 final DataTreeCandidateNode root = tc.getRootNode();
208
209                 // call out peer-role has changed
210                 final DataTreeCandidateNode roleChange =  root.getModifiedChild(AbstractPeerRoleTracker.PEER_ROLE_NID);
211                 if (roleChange != null) {
212                     EffectiveRibInWriter.this.peerPolicyTracker.onDataTreeChanged(roleChange, IdentifierUtils.peerPath(rootPath));
213                 }
214
215                 // filter out any change outside AdjRibsIn
216                 final DataTreeCandidateNode ribIn =  root.getModifiedChild(ADJRIBIN_NID);
217                 if (ribIn == null) {
218                     LOG.debug("Skipping change {}", root.getIdentifier());
219                     continue;
220                 }
221                 final DataTreeCandidateNode tables = ribIn.getModifiedChild(TABLES_NID);
222                 if (tables == null) {
223                     LOG.debug("Skipping change {}", root.getIdentifier());
224                     continue;
225                 }
226                 for (final DataTreeCandidateNode table : tables.getChildNodes()) {
227                     if (tx == null) {
228                         tx = this.chain.newWriteOnlyTransaction();
229                     }
230                     changeDataTree(tx, rootPath, root, peerKey, table);
231                 }
232             }
233             if (tx != null) {
234                 tx.submit();
235             }
236         }
237
238         private void changeDataTree(final DOMDataWriteTransaction tx, final YangInstanceIdentifier rootPath,
239             final DataTreeCandidateNode root, final NodeIdentifierWithPredicates peerKey, final DataTreeCandidateNode table) {
240             final PathArgument lastArg = table.getIdentifier();
241             Verify.verify(lastArg instanceof NodeIdentifierWithPredicates, "Unexpected type %s in path %s", lastArg.getClass(), rootPath);
242             final NodeIdentifierWithPredicates tableKey = (NodeIdentifierWithPredicates) lastArg;
243             final ModificationType modificationType = root.getModificationType();
244             switch (modificationType) {
245             case DELETE:
246             case DISAPPEARED:
247                 final YangInstanceIdentifier effectiveTablePath = effectiveTablePath(peerKey, tableKey);
248                 LOG.debug("Delete Effective Table {} modification type {}, ", effectiveTablePath, modificationType);
249                 // delete the corresponding effective table
250                 tx.delete(LogicalDatastoreType.OPERATIONAL, effectiveTablePath);
251                 break;
252             case SUBTREE_MODIFIED:
253                 modifyTable(tx, peerKey, tableKey, table);
254                 break;
255             case UNMODIFIED:
256                 LOG.info("Ignoring spurious notification on {} data {}", rootPath, table);
257                 break;
258             case APPEARED:
259             case WRITE:
260                 writeTable(tx, peerKey, tableKey, table);
261                 break;
262             default:
263                 LOG.warn("Ignoring unhandled root {}", root);
264                 break;
265             }
266         }
267
268         @Override
269         public void close() {
270             // FIXME: wipe all effective routes?
271             this.reg.close();
272         }
273     }
274
275     private final ImportPolicyPeerTracker peerPolicyTracker;
276     private final AdjInTracker adjInTracker;
277
278     static EffectiveRibInWriter create(@Nonnull final DOMDataTreeChangeService service, @Nonnull final DOMTransactionChain chain,
279         @Nonnull final YangInstanceIdentifier ribId, @Nonnull final PolicyDatabase pd, @Nonnull final RIBSupportContextRegistry registry) {
280         return new EffectiveRibInWriter(service, chain, ribId, pd, registry);
281     }
282
283     private EffectiveRibInWriter(final DOMDataTreeChangeService service, final DOMTransactionChain chain, final YangInstanceIdentifier ribId,
284         final PolicyDatabase pd, final RIBSupportContextRegistry registry) {
285         this.peerPolicyTracker = new ImportPolicyPeerTracker(pd);
286         this.adjInTracker = new AdjInTracker(service, registry, chain, ribId);
287     }
288
289     @Override
290     public void close() {
291         this.adjInTracker.close();
292     }
293 }