76dfd38fca39c7213121cf0bcf101a50607db8c8
[bgpcep.git] / bmp / bmp-impl / src / main / java / org / opendaylight / protocol / bmp / impl / app / BmpRouterImpl.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 java.util.Objects.requireNonNull;
11
12 import com.google.common.base.Preconditions;
13 import com.google.common.net.InetAddresses;
14 import com.google.common.util.concurrent.FutureCallback;
15 import com.google.common.util.concurrent.MoreExecutors;
16 import java.util.HashMap;
17 import java.util.Iterator;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Optional;
21 import java.util.concurrent.ExecutionException;
22 import org.checkerframework.checker.lock.qual.GuardedBy;
23 import org.checkerframework.checker.lock.qual.Holding;
24 import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTree;
25 import org.opendaylight.mdsal.common.api.CommitInfo;
26 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
27 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
28 import org.opendaylight.mdsal.dom.api.DOMDataTreeTransaction;
29 import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
30 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
31 import org.opendaylight.mdsal.dom.api.DOMTransactionChainListener;
32 import org.opendaylight.protocol.bgp.rib.spi.RIBExtensionConsumerContext;
33 import org.opendaylight.protocol.bmp.api.BmpSession;
34 import org.opendaylight.protocol.bmp.impl.spi.BmpRouter;
35 import org.opendaylight.protocol.bmp.impl.spi.BmpRouterPeer;
36 import org.opendaylight.protocol.util.Ipv4Util;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.OpenMessage;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.PeerId;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bmp.message.rev200120.InitiationMessage;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bmp.message.rev200120.PeerDownNotification;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bmp.message.rev200120.PeerHeader;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bmp.message.rev200120.PeerUpNotification;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bmp.message.rev200120.string.informations.StringInformation;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bmp.monitor.rev200120.RouterId;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bmp.monitor.rev200120.peers.Peer;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bmp.monitor.rev200120.routers.Router;
47 import org.opendaylight.yangtools.yang.binding.Notification;
48 import org.opendaylight.yangtools.yang.common.QName;
49 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
50 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
51 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
52 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 public final class BmpRouterImpl implements BmpRouter, DOMTransactionChainListener {
57
58     private static final Logger LOG = LoggerFactory.getLogger(BmpRouterImpl.class);
59
60     private static final QName ROUTER_ID_QNAME = QName.create(Router.QNAME, "router-id").intern();
61     private static final QName ROUTER_STATUS_QNAME = QName.create(Router.QNAME, "status").intern();
62     private static final QName ROUTER_NAME_QNAME = QName.create(Router.QNAME, "name").intern();
63     private static final QName ROUTER_DESCRIPTION_QNAME = QName.create(Router.QNAME, "description").intern();
64     private static final QName ROUTER_INFO_QNAME = QName.create(Router.QNAME, "info").intern();
65     private static final String UP = "up";
66     private static final String DOWN = "down";
67
68     private final RouterSessionManager sessionManager;
69     @GuardedBy("this")
70     private final Map<PeerId, BmpRouterPeer> peers = new HashMap<>();
71     private final DOMTransactionChain domTxChain;
72     private final DOMDataBroker domDataBroker;
73     private final RIBExtensionConsumerContext extensions;
74     private final BindingCodecTree tree;
75     private BmpSession session;
76     private RouterId routerId;
77     private String routerIp;
78     @GuardedBy("this")
79     private YangInstanceIdentifier routerYangIId;
80     @GuardedBy("this")
81     private YangInstanceIdentifier peersYangIId;
82
83     public BmpRouterImpl(final RouterSessionManager sessionManager) {
84         this.sessionManager = requireNonNull(sessionManager);
85         domDataBroker = sessionManager.getDomDataBroker();
86         domTxChain = domDataBroker.createMergingTransactionChain(this);
87         extensions = sessionManager.getExtensions();
88         tree = sessionManager.getCodecTree();
89     }
90
91     @Override
92     public synchronized void onSessionUp(final BmpSession psession) {
93         session = psession;
94         routerIp = InetAddresses.toAddrString(session.getRemoteAddress());
95         routerId = new RouterId(Ipv4Util.getIpAddress(session.getRemoteAddress()));
96         // check if this session is redundant
97         if (!sessionManager.addSessionListener(this)) {
98             LOG.warn("Redundant BMP session with remote router {} ({}) detected. This BMP session will be abandoned.",
99                 routerIp, session);
100             close();
101         } else {
102             routerYangIId = YangInstanceIdentifier.builder(sessionManager.getRoutersYangIId())
103                 .nodeWithKey(Router.QNAME, ROUTER_ID_QNAME, routerIp).build();
104             peersYangIId = YangInstanceIdentifier.builder(routerYangIId).node(Peer.QNAME).build();
105             createRouterEntry();
106             LOG.info("BMP session with remote router {} ({}) is up now.", routerIp, session);
107         }
108     }
109
110     @Override
111     public synchronized void onSessionDown(final Exception exception) {
112         // we want to tear down as we want to do clean up like closing the transaction chain, etc.
113         // even when datastore is not writable (routerYangIId == null / redundant session)
114         tearDown();
115     }
116
117     @Override
118     public void onMessage(final Notification<?> message) {
119         if (message instanceof InitiationMessage) {
120             onInitiate((InitiationMessage) message);
121         } else if (message instanceof PeerUpNotification) {
122             onPeerUp((PeerUpNotification) message);
123         } else if (message instanceof PeerHeader) {
124             delegateToPeer(message);
125         }
126     }
127
128     @Override
129     public synchronized RouterId getRouterId() {
130         return routerId;
131     }
132
133     @Override
134     @SuppressWarnings("checkstyle:IllegalCatch")
135     public synchronized void close() {
136         if (session != null) {
137             try {
138                 session.close();
139             } catch (final Exception exc) {
140                 LOG.error("Fail to close session.", exc);
141             }
142         }
143     }
144
145     @Holding("this")
146     @SuppressWarnings("checkstyle:IllegalCatch")
147     private synchronized void tearDown() {
148         // the session has been teared down before
149         if (session == null) {
150             return;
151         }
152         // we want to display remote router's IP here, as sometimes this.session.close() is already
153         // invoked before tearDown(), and session channel is null in this case, which leads to unuseful
154         // log information
155         LOG.info("BMP Session with remote router {} ({}) went down.", routerIp, session);
156         session = null;
157         final Iterator<BmpRouterPeer> it = peers.values().iterator();
158         try {
159             while (it.hasNext()) {
160                 it.next().close();
161                 it.remove();
162             }
163             domTxChain.close();
164         } catch (final Exception e) {
165             LOG.error("Failed to properly close BMP application.", e);
166         } finally {
167             // remove session only when session is valid, otherwise
168             // we would remove the original valid session when a redundant connection happens
169             // as the routerId is the same for both connection
170             if (isDatastoreWritable()) {
171                 try {
172                     // it means the session was closed before it was written to datastore
173                     final DOMDataTreeWriteTransaction wTx = domDataBroker.newWriteOnlyTransaction();
174                     wTx.delete(LogicalDatastoreType.OPERATIONAL, routerYangIId);
175                     wTx.commit().get();
176                 } catch (final InterruptedException | ExecutionException e) {
177                     LOG.error("Failed to remove BMP router data from DS.", e);
178                 }
179                 sessionManager.removeSessionListener(this);
180             }
181         }
182     }
183
184     @Override
185     public synchronized void onTransactionChainFailed(final DOMTransactionChain chain,
186         final DOMDataTreeTransaction transaction, final Throwable cause) {
187         LOG.error("Transaction chain failed.", cause);
188     }
189
190     @Override
191     public void onTransactionChainSuccessful(final DOMTransactionChain chain) {
192         LOG.debug("Transaction chain {} successfully.", chain);
193     }
194
195     private synchronized boolean isDatastoreWritable() {
196         return routerYangIId != null;
197     }
198
199     private synchronized void createRouterEntry() {
200         Preconditions.checkState(isDatastoreWritable());
201         final DOMDataTreeWriteTransaction wTx = domTxChain.newWriteOnlyTransaction();
202         wTx.put(LogicalDatastoreType.OPERATIONAL, routerYangIId,
203                 Builders.mapEntryBuilder()
204                 .withNodeIdentifier(NodeIdentifierWithPredicates.of(Router.QNAME, ROUTER_ID_QNAME, routerIp))
205                 .withChild(ImmutableNodes.leafNode(ROUTER_ID_QNAME, routerIp))
206                 .withChild(ImmutableNodes.leafNode(ROUTER_STATUS_QNAME, DOWN))
207                 .withChild(ImmutableNodes.mapNodeBuilder(Peer.QNAME).build()).build());
208         wTx.commit().addCallback(new FutureCallback<CommitInfo>() {
209             @Override
210             public void onSuccess(final CommitInfo result) {
211                 LOG.trace("Successful commit");
212             }
213
214             @Override
215             public void onFailure(final Throwable trw) {
216                 LOG.error("Failed commit", trw);
217             }
218         }, MoreExecutors.directExecutor());
219     }
220
221     private synchronized void onInitiate(final InitiationMessage initiation) {
222         Preconditions.checkState(isDatastoreWritable());
223         final DOMDataTreeWriteTransaction wTx = domTxChain.newWriteOnlyTransaction();
224         wTx.merge(LogicalDatastoreType.OPERATIONAL, routerYangIId,
225                 Builders.mapEntryBuilder()
226                 .withNodeIdentifier(NodeIdentifierWithPredicates.of(Router.QNAME, ROUTER_ID_QNAME, routerIp))
227                 .withChild(ImmutableNodes.leafNode(ROUTER_NAME_QNAME, initiation.getTlvs().getNameTlv().getName()))
228                 .withChild(ImmutableNodes.leafNode(ROUTER_DESCRIPTION_QNAME, initiation.getTlvs().getDescriptionTlv()
229                         .getDescription()))
230                 .withChild(ImmutableNodes.leafNode(ROUTER_INFO_QNAME, getStringInfo(initiation.getTlvs()
231                         .getStringInformation())))
232                 .withChild(ImmutableNodes.leafNode(ROUTER_STATUS_QNAME, UP)).build());
233         wTx.commit().addCallback(new FutureCallback<CommitInfo>() {
234             @Override
235             public void onSuccess(final CommitInfo result) {
236                 LOG.trace("Successful commit");
237             }
238
239             @Override
240             public void onFailure(final Throwable trw) {
241                 LOG.error("Failed commit", trw);
242             }
243         }, MoreExecutors.directExecutor());
244     }
245
246     private synchronized void onPeerUp(final PeerUpNotification peerUp) {
247         final PeerId peerId = getPeerIdFromOpen(peerUp.getReceivedOpen());
248         if (!getPeer(peerId).isPresent()) {
249             final BmpRouterPeer peer = BmpRouterPeerImpl.createRouterPeer(domTxChain, peersYangIId, peerUp,
250                 extensions, tree, peerId);
251             peers.put(peerId, peer);
252             LOG.debug("Router {}: Peer {} goes up.", routerIp, peerId.getValue());
253         } else {
254             LOG.debug("Peer: {} for Router: {} already exists.", peerId.getValue(), routerIp);
255         }
256     }
257
258     private synchronized void delegateToPeer(final Notification<?> perPeerMessage) {
259         final PeerId peerId = getPeerId((PeerHeader) perPeerMessage);
260         final Optional<BmpRouterPeer> maybePeer = getPeer(peerId);
261         if (maybePeer.isPresent()) {
262             maybePeer.orElseThrow().onPeerMessage(perPeerMessage);
263             if (perPeerMessage instanceof PeerDownNotification) {
264                 peers.remove(peerId);
265                 LOG.debug("Router {}: Peer {} removed.", routerIp, peerId.getValue());
266             }
267         } else {
268             LOG.debug("Peer: {} for Router: {} was not found.", peerId.getValue(), routerIp);
269         }
270     }
271
272     private Optional<BmpRouterPeer> getPeer(final PeerId peerId) {
273         return Optional.ofNullable(peers.get(peerId));
274     }
275
276     private static PeerId getPeerId(final PeerHeader peerHeader) {
277         return new PeerId(peerHeader.getPeerHeader().getBgpId().getValue());
278     }
279
280     private static PeerId getPeerIdFromOpen(final OpenMessage open) {
281         return new PeerId(open.getBgpIdentifier().getValue());
282     }
283
284     private static String getStringInfo(final List<StringInformation> info) {
285         final StringBuilder builder = new StringBuilder();
286         if (info != null) {
287             for (final StringInformation string : info) {
288                 if (string.getStringTlv() != null) {
289                     builder.append(string.getStringTlv().getStringInfo());
290                     builder.append(";");
291                 }
292             }
293         }
294         return builder.toString();
295     }
296
297 }