Bug 4827: Extend ByteToMessage decoder to support peer constraints
[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.IdentifierUtils;
25 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
26 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.PeerRole;
27 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.rib.peer.AdjRibIn;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.rib.peer.EffectiveRibIn;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.Tables;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.tables.Routes;
31 import org.opendaylight.yangtools.concepts.ListenerRegistration;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
33 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
34 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
35 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
36 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
38 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
39 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
40 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
41 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 /**
46  * Implementation of the BGP import policy. Listens on peer's Adj-RIB-In, inspects all inbound
47  * routes in the context of the advertising peer's role and applies the inbound policy.
48  *
49  * Inbound policy is applied as follows:
50  *
51  * 1) if the peer is an eBGP peer, perform attribute replacement and filtering
52  * 2) check if a route is admissible based on attributes attached to it, as well as the
53  *    advertising peer's role
54  * 3) output admitting routes with edited attributes into /bgp-rib/rib/peer/effective-rib-in/tables/routes
55  *
56  */
57 @NotThreadSafe
58 final class EffectiveRibInWriter implements AutoCloseable {
59     private static final Logger LOG = LoggerFactory.getLogger(EffectiveRibInWriter.class);
60     protected static final NodeIdentifier TABLE_ROUTES = new NodeIdentifier(Routes.QNAME);
61
62     private final class AdjInTracker implements AutoCloseable, DOMDataTreeChangeListener {
63         private final RIBSupportContextRegistry registry;
64         private final YangInstanceIdentifier peerIId;
65         private final YangInstanceIdentifier effRibTables;
66         private final ListenerRegistration<?> reg;
67         private final DOMTransactionChain chain;
68
69         AdjInTracker(final DOMDataTreeChangeService service, final RIBSupportContextRegistry registry, final DOMTransactionChain chain, final YangInstanceIdentifier peerIId) {
70             this.registry = Preconditions.checkNotNull(registry);
71             this.chain = Preconditions.checkNotNull(chain);
72             this.peerIId = Preconditions.checkNotNull(peerIId);
73             this.effRibTables = this.peerIId.node(EffectiveRibIn.QNAME).node(Tables.QNAME);
74
75             final DOMDataTreeIdentifier treeId = new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, this.peerIId.node(AdjRibIn.QNAME).node(Tables.QNAME));
76             LOG.debug("Registered Effective RIB on {}", this.peerIId);
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.getIdentifier());
82             final YangInstanceIdentifier routeId = ribSupport.routePath(routesPath, route.getIdentifier());
83             switch (route.getModificationType()) {
84             case DELETE:
85             case DISAPPEARED:
86                 tx.delete(LogicalDatastoreType.OPERATIONAL, routeId);
87                 break;
88             case UNMODIFIED:
89                 // No-op
90                 break;
91             case APPEARED:
92             case SUBTREE_MODIFIED:
93             case WRITE:
94                 tx.put(LogicalDatastoreType.OPERATIONAL, routeId, route.getDataAfter().get());
95                 // Lookup per-table attributes from RIBSupport
96                 final ContainerNode advertisedAttrs = (ContainerNode) NormalizedNodes.findNode(route.getDataAfter(), ribSupport.routeAttributesIdentifier()).orNull();
97                 final ContainerNode effectiveAttrs;
98
99                 if (advertisedAttrs != null) {
100                     effectiveAttrs = policy.effectiveAttributes(advertisedAttrs);
101                 } else {
102                     effectiveAttrs = null;
103                 }
104
105                 LOG.debug("Route {} effective attributes {} towards {}", route.getIdentifier(), effectiveAttrs, routeId);
106
107                 if (effectiveAttrs != null) {
108                     tx.put(LogicalDatastoreType.OPERATIONAL, routeId.node(ribSupport.routeAttributesIdentifier()), effectiveAttrs);
109                 } else {
110                     LOG.warn("Route {} advertised empty attributes", routeId);
111                     tx.delete(LogicalDatastoreType.OPERATIONAL,  routeId);
112                 }
113                 break;
114             default:
115                 LOG.warn("Ignoring unhandled route {}", route);
116                 break;
117             }
118         }
119
120         private void processTableChildren(final DOMDataWriteTransaction tx, final RIBSupport ribSupport, final YangInstanceIdentifier tablePath, final Collection<DataTreeCandidateNode> children) {
121
122             for (final DataTreeCandidateNode child : children) {
123                 final PathArgument childIdentifier = child.getIdentifier();
124                 final Optional<NormalizedNode<?, ?>> childDataAfter = child.getDataAfter();
125                 LOG.debug("Process table {} type {}, dataAfter {}, dataBefore {}", childIdentifier, child
126                     .getModificationType(), childDataAfter, child.getDataBefore());
127                 final YangInstanceIdentifier childPath = tablePath.node(childIdentifier);
128                 switch (child.getModificationType()) {
129                 case DELETE:
130                 case DISAPPEARED:
131                     tx.delete(LogicalDatastoreType.OPERATIONAL, tablePath.node(childIdentifier));
132                     break;
133                 case UNMODIFIED:
134                     // No-op
135                     break;
136                 case SUBTREE_MODIFIED:
137                     processModifiedRouteTables(child, childIdentifier,tx, ribSupport, importPolicy, childPath, childDataAfter);
138                     break;
139                 case APPEARED:
140                 case WRITE:
141                     writeRouteTables(child, childIdentifier,tx, ribSupport, importPolicy, childPath, childDataAfter);
142
143                     break;
144                 default:
145                     LOG.warn("Ignoring unhandled child {}", child);
146                     break;
147                 }
148             }
149         }
150
151         private void processModifiedRouteTables(final DataTreeCandidateNode child, final PathArgument childIdentifier, final DOMDataWriteTransaction tx,
152             final RIBSupport ribSupport, final AbstractImportPolicy policy, final YangInstanceIdentifier childPath, final Optional<NormalizedNode<?, ?>> childDataAfter) {
153             if (TABLE_ROUTES.equals(childIdentifier)) {
154                 for (final DataTreeCandidateNode route : ribSupport.changedRoutes(child)) {
155                     processRoute(tx, ribSupport, policy, childPath, route);
156                 }
157             } else {
158                 tx.put(LogicalDatastoreType.OPERATIONAL, childPath, childDataAfter.get());
159             }
160         }
161
162         private void writeRouteTables(final DataTreeCandidateNode child, final PathArgument childIdentifier, final DOMDataWriteTransaction tx, final RIBSupport ribSupport, final AbstractImportPolicy policy, final YangInstanceIdentifier childPath, final Optional<NormalizedNode<?, ?>> childDataAfter) {
163             if (TABLE_ROUTES.equals(childIdentifier)) {
164                 final Collection<DataTreeCandidateNode> changedRoutes = ribSupport.changedRoutes(child);
165                 if (!changedRoutes.isEmpty()) {
166                     tx.put(LogicalDatastoreType.OPERATIONAL, childPath, childDataAfter.get());
167                     // Routes are special, as they may end up being filtered. The previous put conveniently
168                     // ensured that we have them in at target, so a subsequent delete will not fail :)
169                     for (final DataTreeCandidateNode route : changedRoutes) {
170                         processRoute(tx, ribSupport, policy, childPath, route);
171                     }
172                 }
173             }
174         }
175
176         private RIBSupportContext getRibSupport(final NodeIdentifierWithPredicates tableKey) {
177             return this.registry.getRIBSupportContext(tableKey);
178         }
179
180         private YangInstanceIdentifier effectiveTablePath(final NodeIdentifierWithPredicates tableKey) {
181             return this.effRibTables.node(tableKey);
182         }
183
184         private void modifyTable(final DOMDataWriteTransaction tx, final NodeIdentifierWithPredicates tableKey, final DataTreeCandidateNode table) {
185             final RIBSupportContext ribSupport = getRibSupport(tableKey);
186             final YangInstanceIdentifier tablePath = effectiveTablePath(tableKey);
187
188             processTableChildren(tx, ribSupport.getRibSupport(), tablePath, table.getChildNodes());
189         }
190
191         private void writeTable(final DOMDataWriteTransaction tx, final NodeIdentifierWithPredicates tableKey, final DataTreeCandidateNode table) {
192             final RIBSupportContext ribSupport = getRibSupport(tableKey);
193             final YangInstanceIdentifier tablePath = effectiveTablePath(tableKey);
194
195             // Create an empty table
196             LOG.trace("Create Empty table", tablePath);
197             ribSupport.createEmptyTableStructure(tx, tablePath);
198
199             processTableChildren(tx, ribSupport.getRibSupport(), tablePath, table.getChildNodes());
200         }
201
202         @Override
203         public void onDataTreeChanged(@Nonnull final Collection<DataTreeCandidate> changes) {
204             LOG.trace("Data changed called to effective RIB. Change : {}", changes);
205
206             // we have a lot of transactions created for 'nothing' because a lot of changes
207             // are skipped, so ensure we only create one transaction when we really need it
208             DOMDataWriteTransaction tx = null;
209             for (final DataTreeCandidate tc : changes) {
210                 final YangInstanceIdentifier rootPath = tc.getRootPath();
211
212                 final DataTreeCandidateNode root = tc.getRootNode();
213                 for (final DataTreeCandidateNode table : root.getChildNodes()) {
214                     if (tx == null) {
215                         tx = this.chain.newWriteOnlyTransaction();
216                     }
217                     changeDataTree(tx, rootPath, root, table);
218                 }
219             }
220             if (tx != null) {
221                 tx.submit();
222             }
223         }
224
225         private void changeDataTree(final DOMDataWriteTransaction tx, final YangInstanceIdentifier rootPath,
226             final DataTreeCandidateNode root, final DataTreeCandidateNode table) {
227             final PathArgument lastArg = table.getIdentifier();
228             Verify.verify(lastArg instanceof NodeIdentifierWithPredicates, "Unexpected type %s in path %s", lastArg.getClass(), rootPath);
229             final NodeIdentifierWithPredicates tableKey = (NodeIdentifierWithPredicates) lastArg;
230             final ModificationType modificationType = root.getModificationType();
231             switch (modificationType) {
232             case DELETE:
233             case DISAPPEARED:
234                 final YangInstanceIdentifier effectiveTablePath = effectiveTablePath(tableKey);
235                 LOG.debug("Delete Effective Table {} modification type {}, ", effectiveTablePath, modificationType);
236                 // delete the corresponding effective table
237                 tx.delete(LogicalDatastoreType.OPERATIONAL, effectiveTablePath);
238                 break;
239             case SUBTREE_MODIFIED:
240                 modifyTable(tx, tableKey, table);
241                 break;
242             case UNMODIFIED:
243                 LOG.info("Ignoring spurious notification on {} data {}", rootPath, table);
244                 break;
245             case APPEARED:
246             case WRITE:
247                 writeTable(tx, tableKey, table);
248                 break;
249             default:
250                 LOG.warn("Ignoring unhandled root {}", root);
251                 break;
252             }
253         }
254
255         @Override
256         public void close() {
257             this.reg.close();
258         }
259     }
260
261     private final AdjInTracker adjInTracker;
262     private final AbstractImportPolicy importPolicy;
263
264     static EffectiveRibInWriter create(@Nonnull final DOMDataTreeChangeService service, @Nonnull final DOMTransactionChain chain,
265         @Nonnull final YangInstanceIdentifier peerIId, @Nonnull final ImportPolicyPeerTracker importPolicyPeerTracker, @Nonnull final RIBSupportContextRegistry registry, final PeerRole peerRole) {
266         return new EffectiveRibInWriter(service, chain, peerIId, importPolicyPeerTracker, registry, peerRole);
267     }
268
269     private EffectiveRibInWriter(final DOMDataTreeChangeService service, final DOMTransactionChain chain, final YangInstanceIdentifier peerIId,
270         final ImportPolicyPeerTracker importPolicyPeerTracker, final RIBSupportContextRegistry registry, final PeerRole peerRole) {
271         importPolicyPeerTracker.peerRoleChanged(peerIId, peerRole);
272         this.importPolicy = importPolicyPeerTracker.policyFor(IdentifierUtils.peerId((NodeIdentifierWithPredicates) peerIId.getLastPathArgument()));
273         this.adjInTracker = new AdjInTracker(service, registry, chain, peerIId);
274     }
275
276     @Override
277     public void close() {
278         this.adjInTracker.close();
279     }
280 }