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