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