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