Do not create empty route list container
[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 static java.util.Objects.requireNonNull;
11
12 import com.google.common.collect.ImmutableMap;
13 import com.google.common.collect.ImmutableSet;
14 import com.google.common.util.concurrent.FluentFuture;
15 import com.google.common.util.concurrent.FutureCallback;
16 import com.google.common.util.concurrent.MoreExecutors;
17 import java.util.Collection;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Optional;
21 import java.util.Set;
22 import java.util.concurrent.ExecutionException;
23 import java.util.concurrent.atomic.LongAdder;
24 import javax.annotation.Nonnull;
25 import javax.annotation.concurrent.GuardedBy;
26 import javax.annotation.concurrent.NotThreadSafe;
27 import org.opendaylight.controller.md.sal.binding.api.BindingTransactionChain;
28 import org.opendaylight.controller.md.sal.binding.api.ClusteredDataTreeChangeListener;
29 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
30 import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
31 import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
32 import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
33 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
34 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
35 import org.opendaylight.mdsal.common.api.CommitInfo;
36 import org.opendaylight.protocol.bgp.openconfig.spi.BGPTableTypeRegistryConsumer;
37 import org.opendaylight.protocol.bgp.rib.impl.spi.RIB;
38 import org.opendaylight.protocol.bgp.rib.impl.spi.RIBSupportContextRegistry;
39 import org.opendaylight.protocol.bgp.rib.impl.spi.RibOutRefresh;
40 import org.opendaylight.protocol.bgp.rib.impl.state.peer.PrefixesInstalledCounters;
41 import org.opendaylight.protocol.bgp.rib.impl.state.peer.PrefixesReceivedCounters;
42 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
43 import org.opendaylight.protocol.bgp.rib.spi.policy.BGPRibRoutingPolicy;
44 import org.opendaylight.protocol.bgp.rib.spi.policy.BGPRouteEntryImportParameters;
45 import org.opendaylight.protocol.bgp.route.targetcontrain.spi.ClientRouteTargetContrainCache;
46 import org.opendaylight.protocol.bgp.route.targetcontrain.spi.RouteTargetMembeshipUtil;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.path.attributes.Attributes;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.PeerRole;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.Route;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.Peer;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.PeerKey;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.peer.AdjRibIn;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.peer.EffectiveRibIn;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.Tables;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.TablesBuilder;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.TablesKey;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.tables.Routes;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.Ipv4AddressFamily;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.Ipv6AddressFamily;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.MplsLabeledVpnSubsequentAddressFamily;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.RouteTarget;
62 import org.opendaylight.yangtools.concepts.ListenerRegistration;
63 import org.opendaylight.yangtools.yang.binding.ChildOf;
64 import org.opendaylight.yangtools.yang.binding.ChoiceIn;
65 import org.opendaylight.yangtools.yang.binding.DataObject;
66 import org.opendaylight.yangtools.yang.binding.Identifiable;
67 import org.opendaylight.yangtools.yang.binding.Identifier;
68 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
69 import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
70 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
71 import org.slf4j.Logger;
72 import org.slf4j.LoggerFactory;
73
74 /**
75  * Implementation of the BGP import policy. Listens on peer's Adj-RIB-In, inspects all inbound
76  * routes in the context of the advertising peer's role and applies the inbound policy.
77  * <p>
78  * Inbound policy is applied as follows:
79  * <p>
80  * 1) if the peer is an eBGP peer, perform attribute replacement and filtering
81  * 2) check if a route is admissible based on attributes attached to it, as well as the
82  * advertising peer's role
83  * 3) output admitting routes with edited attributes into /bgp-rib/rib/peer/effective-rib-in/tables/routes
84  */
85 @NotThreadSafe
86 final class EffectiveRibInWriter implements PrefixesReceivedCounters, PrefixesInstalledCounters,
87         AutoCloseable, ClusteredDataTreeChangeListener<Tables> {
88
89     private static final Logger LOG = LoggerFactory.getLogger(EffectiveRibInWriter.class);
90     static final NodeIdentifier TABLE_ROUTES = new NodeIdentifier(Routes.QNAME);
91     private static final TablesKey IVP4_VPN_TABLE_KEY = new TablesKey(Ipv4AddressFamily.class,
92             MplsLabeledVpnSubsequentAddressFamily.class);
93     private static final TablesKey IVP6_VPN_TABLE_KEY = new TablesKey(Ipv6AddressFamily.class,
94             MplsLabeledVpnSubsequentAddressFamily.class);
95     private final RIBSupportContextRegistry registry;
96     private final KeyedInstanceIdentifier<Peer, PeerKey> peerIId;
97     private final InstanceIdentifier<EffectiveRibIn> effRibTables;
98     private final DataBroker databroker;
99     private final List<RouteTarget> rtMemberships;
100     private final RibOutRefresh vpnTableRefresher;
101     private final ClientRouteTargetContrainCache rtCache;
102     private ListenerRegistration<?> reg;
103     private BindingTransactionChain chain;
104     private final Map<TablesKey, LongAdder> prefixesReceived;
105     private final Map<TablesKey, LongAdder> prefixesInstalled;
106     private final BGPRibRoutingPolicy ribPolicies;
107     private final BGPRouteEntryImportParameters peerImportParameters;
108     private final BGPTableTypeRegistryConsumer tableTypeRegistry;
109     @GuardedBy("this")
110     private FluentFuture<? extends CommitInfo> submitted;
111     private boolean rtMembershipsUpdated;
112
113     EffectiveRibInWriter(
114             final BGPRouteEntryImportParameters peer,
115             final RIB rib,
116             final BindingTransactionChain chain,
117             final KeyedInstanceIdentifier<Peer, PeerKey> peerIId,
118             final Set<TablesKey> tables,
119             final BGPTableTypeRegistryConsumer tableTypeRegistry,
120             final List<RouteTarget> rtMemberships,
121             final ClientRouteTargetContrainCache rtCache) {
122         this.registry = requireNonNull(rib.getRibSupportContext());
123         this.chain = requireNonNull(chain);
124         this.peerIId = requireNonNull(peerIId);
125         this.effRibTables = this.peerIId.child(EffectiveRibIn.class);
126         this.prefixesInstalled = buildPrefixesTables(tables);
127         this.prefixesReceived = buildPrefixesTables(tables);
128         this.ribPolicies = requireNonNull(rib.getRibPolicies());
129         this.databroker = requireNonNull(rib.getDataBroker());
130         this.tableTypeRegistry = requireNonNull(tableTypeRegistry);
131         this.peerImportParameters = peer;
132         this.rtMemberships = rtMemberships;
133         this.rtCache = rtCache;
134         this.vpnTableRefresher = rib;
135     }
136
137     public void init() {
138         final DataTreeIdentifier<Tables> treeId = new DataTreeIdentifier<>(LogicalDatastoreType.OPERATIONAL,
139                 this.peerIId.child(AdjRibIn.class).child(Tables.class));
140         LOG.debug("Registered Effective RIB on {}", this.peerIId);
141         this.reg = requireNonNull(this.databroker).registerDataTreeChangeListener(treeId, this);
142     }
143
144     private static Map<TablesKey, LongAdder> buildPrefixesTables(final Set<TablesKey> tables) {
145         final ImmutableMap.Builder<TablesKey, LongAdder> b = ImmutableMap.builder();
146         tables.forEach(table -> b.put(table, new LongAdder()));
147         return b.build();
148     }
149
150     @Override
151     @SuppressWarnings("unchecked")
152     public synchronized void onDataTreeChanged(@Nonnull final Collection<DataTreeModification<Tables>> changes) {
153         if (this.chain == null) {
154             LOG.trace("Chain closed. Ignoring Changes : {}", changes);
155             return;
156         }
157         LOG.trace("Data changed called to effective RIB. Change : {}", changes);
158         WriteTransaction tx = null;
159         for (final DataTreeModification<Tables> tc : changes) {
160             final DataObjectModification<Tables> table = tc.getRootNode();
161             if (tx == null) {
162                 tx = this.chain.newWriteOnlyTransaction();
163             }
164             final DataObjectModification.ModificationType modificationType = table.getModificationType();
165             switch (modificationType) {
166                 case DELETE:
167                     final Tables removeTable = table.getDataBefore();
168                     final TablesKey tableKey = removeTable.key();
169                     final KeyedInstanceIdentifier<Tables, TablesKey> effectiveTablePath
170                             = this.effRibTables.child(Tables.class, tableKey);
171                     LOG.debug("Delete Effective Table {} modification type {}, "
172                             , effectiveTablePath, modificationType);
173                     tx.delete(LogicalDatastoreType.OPERATIONAL, effectiveTablePath);
174                     CountersUtil.decrement(this.prefixesInstalled.get(tableKey), tableKey);
175                     break;
176                 case SUBTREE_MODIFIED:
177                     final Tables before = table.getDataBefore();
178                     final Tables after = table.getDataAfter();
179                     final TablesKey tk = after.key();
180                     LOG.debug("Process table {} type {}, dataAfter {}, dataBefore {}",
181                             tk, modificationType, after, before);
182
183                     final KeyedInstanceIdentifier<Tables, TablesKey> tablePath
184                             = this.effRibTables.child(Tables.class, tk);
185                     final RIBSupport ribSupport = this.registry.getRIBSupport(tk);
186                     if (ribSupport == null) {
187                         break;
188                     }
189                     tx.put(LogicalDatastoreType.OPERATIONAL,
190                             tablePath.child(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp
191                                     .rib.rev180329.rib.tables.Attributes.class), after.getAttributes());
192
193                     final DataObjectModification routesChangesContainer = table.getModifiedChildContainer(
194                         ribSupport.routesCaseClass(), ribSupport.routesContainerClass());
195
196                     if (routesChangesContainer == null) {
197                         break;
198                     }
199                     updateRoutes(tx, tk, ribSupport, tablePath, routesChangesContainer.getModifiedChildren());
200                     break;
201                 case WRITE:
202                     writeTable(tx, table);
203                     break;
204                 default:
205                     LOG.warn("Ignoring unhandled root {}", table);
206                     break;
207             }
208         }
209         if (tx != null) {
210             final FluentFuture<? extends CommitInfo> future = tx.commit();
211             this.submitted = future;
212             future.addCallback(new FutureCallback<CommitInfo>() {
213                 @Override
214                 public void onSuccess(final CommitInfo result) {
215                     LOG.trace("Successful commit");
216                 }
217
218                 @Override
219                 public void onFailure(final Throwable trw) {
220                     LOG.error("Failed commit", trw);
221                 }
222             }, MoreExecutors.directExecutor());
223         }
224
225         //Refresh VPN Table if RT Memberships were updated
226         if (this.rtMembershipsUpdated) {
227             this.vpnTableRefresher.refreshTable(IVP4_VPN_TABLE_KEY, this.peerImportParameters.getFromPeerId());
228             this.vpnTableRefresher.refreshTable(IVP6_VPN_TABLE_KEY, this.peerImportParameters.getFromPeerId());
229             this.rtMembershipsUpdated = false;
230         }
231     }
232
233     @SuppressWarnings("unchecked")
234     private <C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>,
235         R extends Route & ChildOf<? super S> & Identifiable<I>, I extends Identifier<R>> void updateRoutes(
236             final WriteTransaction tx,
237             final TablesKey tableKey, final RIBSupport<C, S, R, I> ribSupport,
238             final KeyedInstanceIdentifier<Tables, TablesKey> tablePath,
239             final Collection<DataObjectModification<R>> routeChanges) {
240         for (final DataObjectModification<R> routeChanged : routeChanges) {
241             final I routeKey
242                     = ((InstanceIdentifier.IdentifiableItem<R, I>) routeChanged.getIdentifier()).getKey();
243             switch (routeChanged.getModificationType()) {
244                 case SUBTREE_MODIFIED:
245                 case WRITE:
246                     writeRoutes(tx, tableKey, ribSupport, tablePath, routeKey, routeChanged.getDataAfter());
247                     break;
248                 case DELETE:
249                     final InstanceIdentifier<R> routeIID = ribSupport.createRouteIdentifier(tablePath, routeKey);
250                     deleteRoutes(routeIID, routeChanged.getDataBefore(), tx);
251                     break;
252             }
253         }
254     }
255
256     private <C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>,
257         R extends Route & ChildOf<? super S> & Identifiable<I>, I extends Identifier<R>> void writeRoutes(
258                 final WriteTransaction tx, final TablesKey tk, final RIBSupport<C, S, R, I> ribSupport,
259             final KeyedInstanceIdentifier<Tables, TablesKey> tablePath, final I routeKey,
260             final R route) {
261         final InstanceIdentifier<R> routeIID = ribSupport.createRouteIdentifier(tablePath, routeKey);
262         CountersUtil.increment(this.prefixesReceived.get(tk), tk);
263         final Optional<Attributes> effAtt = this.ribPolicies
264                 .applyImportPolicies(this.peerImportParameters, route.getAttributes(),
265                         tableTypeRegistry.getAfiSafiType(ribSupport.getTablesKey()).get());
266         if (effAtt.isPresent()) {
267             final Optional<RouteTarget> rtMembership = RouteTargetMembeshipUtil.getRT(route);
268             if (rtMembership.isPresent()) {
269                 final RouteTarget rt = rtMembership.get();
270                 if(PeerRole.Ebgp != this.peerImportParameters.getFromPeerRole()) {
271                     this.rtCache.cacheRoute(route);
272                 }
273                 this.rtMemberships.add(rt);
274                 this.rtMembershipsUpdated = true;
275             }
276             CountersUtil.increment(this.prefixesInstalled.get(tk), tk);
277             tx.put(LogicalDatastoreType.OPERATIONAL, routeIID, route);
278             tx.put(LogicalDatastoreType.OPERATIONAL, routeIID.child(Attributes.class), effAtt.get());
279         } else {
280             deleteRoutes(routeIID, route, tx);
281
282         }
283     }
284
285     private <R extends Route> void deleteRoutes(final InstanceIdentifier<R> routeIID,
286             final R route, final WriteTransaction tx) {
287         final Optional<RouteTarget> rtMembership = RouteTargetMembeshipUtil.getRT(route);
288         if (rtMembership.isPresent()) {
289             if(PeerRole.Ebgp != this.peerImportParameters.getFromPeerRole()) {
290                 this.rtCache.uncacheRoute(route);
291             }
292             this.rtMemberships.remove(rtMembership.get());
293             this.rtMembershipsUpdated = true;
294         }
295         tx.delete(LogicalDatastoreType.OPERATIONAL, routeIID);
296     }
297
298     @SuppressWarnings("unchecked")
299     private void writeTable(final WriteTransaction tx, final DataObjectModification<Tables> table) {
300         final Tables newTable = table.getDataAfter();
301         if (newTable == null) {
302             return;
303         }
304         final TablesKey tableKey = newTable.key();
305         final KeyedInstanceIdentifier<Tables, TablesKey> tablePath
306                 = this.effRibTables.child(Tables.class, tableKey);
307
308         // Create an empty table
309         LOG.trace("Create Empty table at {}", tablePath);
310         if (table.getDataBefore() == null) {
311             tx.put(LogicalDatastoreType.OPERATIONAL, tablePath, new TablesBuilder()
312                     .setAfi(tableKey.getAfi()).setSafi(tableKey.getSafi())
313                     .setAttributes(newTable.getAttributes()).build());
314         }
315
316         final RIBSupport ribSupport = this.registry.getRIBSupport(tableKey);
317         final Routes routes = newTable.getRoutes();
318         if (ribSupport == null || routes == null) {
319             return;
320         }
321
322         final DataObjectModification routesChangesContainer =
323                 table.getModifiedChildContainer(ribSupport.routesCaseClass(), ribSupport.routesContainerClass());
324
325         if (routesChangesContainer == null) {
326             return;
327         }
328         updateRoutes(tx, tableKey, ribSupport, tablePath, routesChangesContainer.getModifiedChildren());
329     }
330
331     @Override
332     public synchronized void close() {
333         if (this.reg != null) {
334             this.reg.close();
335             this.reg = null;
336         }
337         if (this.submitted != null) {
338             try {
339                 this.submitted.get();
340             } catch (final InterruptedException | ExecutionException throwable) {
341                 LOG.error("Write routes failed", throwable);
342             }
343         }
344         if (this.chain != null) {
345             this.chain.close();
346             this.chain = null;
347         }
348         this.prefixesReceived.values().forEach(LongAdder::reset);
349         this.prefixesInstalled.values().forEach(LongAdder::reset);
350     }
351
352     @Override
353     public long getPrefixedReceivedCount(final TablesKey tablesKey) {
354         final LongAdder counter = this.prefixesReceived.get(tablesKey);
355         if (counter == null) {
356             return 0;
357         }
358         return counter.longValue();
359     }
360
361     @Override
362     public Set<TablesKey> getTableKeys() {
363         return ImmutableSet.copyOf(this.prefixesReceived.keySet());
364     }
365
366     @Override
367     public boolean isSupported(final TablesKey tablesKey) {
368         return this.prefixesReceived.containsKey(tablesKey);
369     }
370
371     @Override
372     public long getPrefixedInstalledCount(final TablesKey tablesKey) {
373         final LongAdder counter = this.prefixesInstalled.get(tablesKey);
374         if (counter == null) {
375             return 0;
376         }
377         return counter.longValue();
378     }
379
380     @Override
381     public long getTotalPrefixesInstalled() {
382         return this.prefixesInstalled.values().stream().mapToLong(LongAdder::longValue).sum();
383     }
384 }