Modernize bmp-impl
[bgpcep.git] / bmp / bmp-impl / src / main / java / org / opendaylight / protocol / bmp / impl / app / BmpRibInWriter.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.bmp.impl.app;
9
10 import static org.opendaylight.protocol.bmp.impl.app.TablesUtil.BMP_ATTRIBUTES_QNAME;
11
12 import com.google.common.collect.ImmutableMap;
13 import com.google.common.util.concurrent.FutureCallback;
14 import com.google.common.util.concurrent.MoreExecutors;
15 import java.util.ArrayList;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Set;
19 import java.util.stream.Collectors;
20 import org.eclipse.jdt.annotation.NonNull;
21 import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTree;
22 import org.opendaylight.mdsal.common.api.CommitInfo;
23 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
24 import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
25 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
26 import org.opendaylight.protocol.bgp.rib.spi.RIBExtensionConsumerContext;
27 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev180329.ipv4.prefixes.DestinationIpv4Builder;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev180329.ipv4.prefixes.destination.ipv4.Ipv4Prefixes;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev180329.ipv4.prefixes.destination.ipv4.Ipv4PrefixesBuilder;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev180329.update.attributes.mp.reach.nlri.advertized.routes.destination.type.DestinationIpv4CaseBuilder;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.UpdateMessage;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.AttributesReach;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.AttributesUnreach;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.attributes.reach.MpReachNlri;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.attributes.reach.MpReachNlriBuilder;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.attributes.reach.mp.reach.nlri.AdvertizedRoutesBuilder;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.attributes.unreach.MpUnreachNlri;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.attributes.unreach.MpUnreachNlriBuilder;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.attributes.unreach.mp.unreach.nlri.WithdrawnRoutesBuilder;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.TablesKey;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev200120.Ipv4AddressFamily;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev200120.UnicastSubsequentAddressFamily;
43 import org.opendaylight.yangtools.yang.common.QName;
44 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
45 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
46 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
47 import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 final class BmpRibInWriter {
52     private static final Logger LOG = LoggerFactory.getLogger(BmpRibInWriter.class);
53
54     private static final LeafNode<Boolean> ATTRIBUTES_UPTODATE_FALSE =
55             ImmutableNodes.leafNode(QName.create(BMP_ATTRIBUTES_QNAME, "uptodate").intern(), Boolean.FALSE);
56     private static final LeafNode<Boolean> ATTRIBUTES_UPTODATE_TRUE =
57             ImmutableNodes.leafNode(ATTRIBUTES_UPTODATE_FALSE.name(), Boolean.TRUE);
58
59     private final DOMTransactionChain chain;
60     private final Map<TablesKey, TableContext> tables;
61
62
63     private BmpRibInWriter(final YangInstanceIdentifier tablesRoot, final DOMTransactionChain chain,
64             final RIBExtensionConsumerContext ribExtensions,
65             final Set<TablesKey> tableTypes,  final BindingCodecTree tree) {
66         this.chain = chain;
67         final DOMDataTreeWriteTransaction tx = this.chain.newWriteOnlyTransaction();
68         tables = createTableInstance(tableTypes, tablesRoot, tx, ribExtensions, tree).build();
69
70         LOG.debug("New RIB table {} structure installed.", tablesRoot.toString());
71         tx.commit().addCallback(new FutureCallback<CommitInfo>() {
72             @Override
73             public void onSuccess(final CommitInfo result) {
74                 LOG.trace("Successful commit");
75             }
76
77             @Override
78             public void onFailure(final Throwable trw) {
79                 LOG.error("Failed commit", trw);
80             }
81         }, MoreExecutors.directExecutor());
82     }
83
84     public static BmpRibInWriter create(final @NonNull YangInstanceIdentifier tablesRootPath,
85             final @NonNull DOMTransactionChain chain,
86             final @NonNull RIBExtensionConsumerContext extensions, final @NonNull Set<TablesKey> tableTypes,
87             final @NonNull BindingCodecTree tree) {
88         return new BmpRibInWriter(tablesRootPath, chain, extensions, tableTypes, tree);
89     }
90
91     /**
92      * Write on DS Adj-RIBs-In.
93      */
94     public void onMessage(final UpdateMessage message) {
95
96         if (!checkEndOfRib(message)) {
97             final org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.path
98                     .attributes.Attributes attrs = message.getAttributes();
99             MpReachNlri mpReach = null;
100             if (message.getNlri() != null) {
101                 mpReach = prefixesToMpReach(message);
102             } else if (attrs != null && attrs.augmentation(AttributesReach.class) != null) {
103                 mpReach = attrs.augmentation(AttributesReach.class).getMpReachNlri();
104             }
105             if (mpReach != null) {
106                 addRoutes(mpReach, attrs);
107                 return;
108             }
109
110             MpUnreachNlri mpUnreach = null;
111             if (message.getWithdrawnRoutes() != null) {
112                 mpUnreach = prefixesToMpUnreach(message);
113             } else if (attrs != null && attrs.augmentation(AttributesUnreach.class) != null) {
114                 mpUnreach = attrs.augmentation(AttributesUnreach.class).getMpUnreachNlri();
115             }
116             if (mpUnreach != null) {
117                 removeRoutes(mpUnreach);
118             }
119         }
120     }
121
122     /**
123      * Create new table instance.
124      */
125     private static ImmutableMap.Builder<TablesKey, TableContext> createTableInstance(final Set<TablesKey> tableTypes,
126             final YangInstanceIdentifier yangTableRootIId, final DOMDataTreeWriteTransaction tx,
127             final RIBExtensionConsumerContext ribExtensions, final BindingCodecTree tree) {
128         final var identityCodec = tree.getIdentityCodec();
129
130         final ImmutableMap.Builder<TablesKey, TableContext> tb = ImmutableMap.builder();
131         for (final TablesKey tableType : tableTypes) {
132             final var rs = ribExtensions.getRIBSupport(tableType);
133             if (rs == null) {
134                 LOG.warn("No support for table type {}, skipping it", tableType);
135                 continue;
136             }
137
138             final var domTableKey = NodeIdentifierWithPredicates.of(TablesUtil.BMP_TABLES_QNAME, ImmutableMap.of(
139                 TablesUtil.BMP_AFI_QNAME, identityCodec.fromBinding(tableType.getAfi()),
140                 TablesUtil.BMP_SAFI_QNAME, identityCodec.fromBinding(tableType.getSafi())));
141             final TableContext ctx = new TableContext(rs, yangTableRootIId.node(domTableKey).toOptimized(), tree);
142             ctx.createTable(tx);
143
144             tx.put(LogicalDatastoreType.OPERATIONAL, ctx.getTableId().node(BMP_ATTRIBUTES_QNAME)
145                     .node(ATTRIBUTES_UPTODATE_FALSE.name()), ATTRIBUTES_UPTODATE_FALSE);
146             LOG.debug("Created table instance {}", ctx.getTableId());
147             tb.put(tableType, ctx);
148         }
149         return tb;
150     }
151
152     private synchronized void addRoutes(final MpReachNlri nlri, final org.opendaylight.yang.gen.v1.urn.opendaylight
153             .params.xml.ns.yang.bgp.message.rev200120.path.attributes.Attributes attributes) {
154         final TablesKey key = new TablesKey(nlri.getAfi(), nlri.getSafi());
155         final TableContext ctx = tables.get(key);
156
157         if (ctx == null) {
158             LOG.debug("No table for {}, not accepting NLRI {}", key, nlri);
159             return;
160         }
161
162         final DOMDataTreeWriteTransaction tx = chain.newWriteOnlyTransaction();
163         ctx.writeRoutes(tx, nlri, attributes);
164         LOG.trace("Write routes {}", nlri);
165         tx.commit().addCallback(new FutureCallback<CommitInfo>() {
166             @Override
167             public void onSuccess(final CommitInfo result) {
168                 LOG.trace("Successful commit");
169             }
170
171             @Override
172             public void onFailure(final Throwable trw) {
173                 LOG.error("Failed commit", trw);
174             }
175         }, MoreExecutors.directExecutor());
176     }
177
178     /**
179      * Creates MPReach for the prefixes to be handled in the same way as linkstate routes.
180      *
181      * @param message Update message containing prefixes in NLRI
182      * @return MpReachNlri with prefixes from the nlri field
183      */
184     private static MpReachNlri prefixesToMpReach(final UpdateMessage message) {
185         final List<Ipv4Prefixes> prefixes = message.getNlri().stream()
186                 .map(n -> new Ipv4PrefixesBuilder().setPrefix(n.getPrefix()).setPathId(n.getPathId()).build())
187                 .collect(Collectors.toList());
188         final MpReachNlriBuilder b = new MpReachNlriBuilder().setAfi(Ipv4AddressFamily.VALUE).setSafi(
189             UnicastSubsequentAddressFamily.VALUE).setAdvertizedRoutes(
190                 new AdvertizedRoutesBuilder().setDestinationType(
191                     new DestinationIpv4CaseBuilder().setDestinationIpv4(
192                         new DestinationIpv4Builder().setIpv4Prefixes(prefixes).build()).build()).build());
193         if (message.getAttributes() != null) {
194             b.setCNextHop(message.getAttributes().getCNextHop());
195         }
196         return b.build();
197     }
198
199     private synchronized void removeRoutes(final MpUnreachNlri nlri) {
200         final TablesKey key = new TablesKey(nlri.getAfi(), nlri.getSafi());
201         final TableContext ctx = tables.get(key);
202
203         if (ctx == null) {
204             LOG.debug("No table for {}, not accepting NLRI {}", key, nlri);
205             return;
206         }
207         LOG.trace("Removing routes {}", nlri);
208         final DOMDataTreeWriteTransaction tx = chain.newWriteOnlyTransaction();
209         ctx.removeRoutes(tx, nlri);
210         tx.commit().addCallback(new FutureCallback<CommitInfo>() {
211             @Override
212             public void onSuccess(final CommitInfo result) {
213                 LOG.trace("Successful commit");
214             }
215
216             @Override
217             public void onFailure(final Throwable trw) {
218                 LOG.error("Failed commit", trw);
219             }
220         }, MoreExecutors.directExecutor());
221     }
222
223     /**
224      * Create MPUnreach for the prefixes to be handled in the same way as linkstate routes.
225      *
226      * @param message Update message containing withdrawn routes
227      * @return MpUnreachNlri with prefixes from the withdrawn routes field
228      */
229     private static MpUnreachNlri prefixesToMpUnreach(final UpdateMessage message) {
230         final List<Ipv4Prefixes> prefixes = new ArrayList<>();
231         message.getWithdrawnRoutes().forEach(
232             w -> prefixes.add(new Ipv4PrefixesBuilder().setPrefix(w.getPrefix()).setPathId(w.getPathId()).build()));
233         return new MpUnreachNlriBuilder().setAfi(Ipv4AddressFamily.VALUE).setSafi(UnicastSubsequentAddressFamily.VALUE)
234                 .setWithdrawnRoutes(new WithdrawnRoutesBuilder().setDestinationType(
235                         new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev180329.update
236                                 .attributes.mp.unreach.nlri.withdrawn.routes.destination.type
237                                 .DestinationIpv4CaseBuilder().setDestinationIpv4(new DestinationIpv4Builder()
238                                 .setIpv4Prefixes(prefixes).build()).build()).build()).build();
239     }
240
241     /**
242      * For each received Update message, the upd sync variable needs to be updated to true, for particular AFI/SAFI
243      * combination. Currently we only assume Unicast SAFI. From the Update message we have to extract the AFI. Each
244      * Update message can contain BGP Object with one type of AFI. If the object is BGP Link, BGP Node or a BGPPrefix
245      * the AFI is Linkstate. In case of BGPRoute, the AFI depends on the IP Address of the prefix.
246      *
247      * @param msg received Update message
248      */
249     private boolean checkEndOfRib(final UpdateMessage msg) {
250         TablesKey type = new TablesKey(Ipv4AddressFamily.VALUE, UnicastSubsequentAddressFamily.VALUE);
251         boolean isEOR = false;
252         if (msg.getNlri() == null && msg.getWithdrawnRoutes() == null) {
253             if (msg.getAttributes() != null) {
254                 if (msg.getAttributes().augmentation(AttributesReach.class) != null) {
255                     final AttributesReach pa = msg.getAttributes().augmentation(AttributesReach.class);
256                     if (pa.getMpReachNlri() != null) {
257                         type = new TablesKey(pa.getMpReachNlri().getAfi(), pa.getMpReachNlri().getSafi());
258                     }
259                 } else if (msg.getAttributes().augmentation(AttributesUnreach.class) != null) {
260                     final AttributesUnreach pa = msg.getAttributes().augmentation(AttributesUnreach.class);
261                     if (pa.getMpUnreachNlri() != null) {
262                         type = new TablesKey(pa.getMpUnreachNlri().getAfi(), pa.getMpUnreachNlri().getSafi());
263                     }
264                     if (pa.getMpUnreachNlri().getWithdrawnRoutes() == null) {
265                         // EOR message contains only MPUnreach attribute and no NLRI
266                         isEOR = true;
267                     }
268                 }
269             } else {
270                 // true for empty Update Message
271                 isEOR = true;
272             }
273         }
274
275         if (isEOR) {
276             markTableUptodated(type);
277             LOG.debug("BMP Synchronization finished for table {} ", type);
278         }
279
280         return isEOR;
281     }
282
283     private synchronized void markTableUptodated(final TablesKey tableTypes) {
284         final DOMDataTreeWriteTransaction tx = chain.newWriteOnlyTransaction();
285         final TableContext ctxPre = tables.get(tableTypes);
286         tx.merge(LogicalDatastoreType.OPERATIONAL, ctxPre.getTableId().node(BMP_ATTRIBUTES_QNAME)
287                 .node(ATTRIBUTES_UPTODATE_TRUE.name()), ATTRIBUTES_UPTODATE_TRUE);
288         tx.commit().addCallback(new FutureCallback<CommitInfo>() {
289             @Override
290             public void onSuccess(final CommitInfo result) {
291                 LOG.trace("Successful commit");
292             }
293
294             @Override
295             public void onFailure(final Throwable trw) {
296                 LOG.error("Failed commit", trw);
297             }
298         }, MoreExecutors.directExecutor());
299     }
300 }