Merge "Add linkstate information to RIB"
[bgpcep.git] / bgp / rib-impl / src / main / java / org / opendaylight / protocol / bgp / rib / impl / BGPSessionImpl.java
1 /*
2  * Copyright (c) 2013 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.bgp.rib.impl;
9
10 import io.netty.channel.Channel;
11 import io.netty.util.Timeout;
12 import io.netty.util.Timer;
13 import io.netty.util.TimerTask;
14
15 import java.io.IOException;
16 import java.util.Date;
17 import java.util.Set;
18 import java.util.concurrent.TimeUnit;
19
20 import javax.annotation.concurrent.GuardedBy;
21
22 import org.opendaylight.protocol.bgp.parser.BGPError;
23 import org.opendaylight.protocol.bgp.parser.BGPSession;
24 import org.opendaylight.protocol.bgp.parser.BGPSessionListener;
25 import org.opendaylight.protocol.bgp.parser.BGPTerminationReason;
26 import org.opendaylight.protocol.bgp.parser.BgpTableTypeImpl;
27 import org.opendaylight.protocol.framework.AbstractProtocolSession;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130918.Keepalive;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130918.KeepaliveBuilder;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130918.Notify;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130918.NotifyBuilder;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130918.Open;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130918.open.BgpParameters;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130918.open.bgp.parameters.CParameters;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130918.BgpTableType;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130918.open.bgp.parameters.c.parameters.CMultiprotocol;
37 import org.opendaylight.yangtools.yang.binding.Notification;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 import com.google.common.annotations.VisibleForTesting;
42 import com.google.common.base.Objects;
43 import com.google.common.base.Objects.ToStringHelper;
44 import com.google.common.base.Preconditions;
45 import com.google.common.collect.Sets;
46
47 @VisibleForTesting
48 public class BGPSessionImpl extends AbstractProtocolSession<Notification> implements BGPSession {
49
50         private static final Logger logger = LoggerFactory.getLogger(BGPSessionImpl.class);
51
52         private static final int DEFAULT_HOLD_TIMER_VALUE = 15;
53
54         private static final Notification keepalive = new KeepaliveBuilder().build();
55
56         private static int holdTimerValue = DEFAULT_HOLD_TIMER_VALUE; // 240
57
58         /**
59          * Internal session state.
60          */
61         public enum State {
62                 /**
63                  * The session object is created by the negotiator in OpenConfirm state. While in this state, the session object
64                  * is half-alive, e.g. the timers are running, but the session is not completely up, e.g. it has not been
65                  * announced to the listener. If the session is torn down in this state, we do not inform the listener.
66                  */
67                 OpenConfirm,
68                 /**
69                  * The session has been completely established.
70                  */
71                 Up,
72                 /**
73                  * The session has been closed. It will not be resurrected.
74                  */
75                 Idle,
76         }
77
78         /**
79          * System.nanoTime value about when was sent the last message Protected to be updated also in tests.
80          */
81         @VisibleForTesting
82         protected long lastMessageSentAt;
83
84         /**
85          * System.nanoTime value about when was received the last message
86          */
87         private long lastMessageReceivedAt;
88
89         private final BGPSessionListener listener;
90
91         /**
92          * Timer object grouping FSM Timers
93          */
94         private final Timer stateTimer;
95
96         private final BGPSynchronization sync;
97
98         private int kaCounter = 0;
99
100         private final Channel channel;
101
102         @GuardedBy("this")
103         private State state = State.OpenConfirm;
104
105         private final int keepAlive;
106
107         private final Set<BgpTableType> tableTypes;
108
109         BGPSessionImpl(final Timer timer, final BGPSessionListener listener, final Channel channel, final Open remoteOpen) {
110                 this.listener = Preconditions.checkNotNull(listener);
111                 this.stateTimer = Preconditions.checkNotNull(timer);
112                 this.channel = Preconditions.checkNotNull(channel);
113                 this.keepAlive = remoteOpen.getHoldTimer() / 3;
114                 holdTimerValue = remoteOpen.getHoldTimer();
115
116                 final Set<BgpTableType> tts = Sets.newHashSet();
117                 if (remoteOpen.getBgpParameters() != null) {
118                         for (final BgpParameters param : remoteOpen.getBgpParameters()) {
119                                 if (param instanceof CParameters) {
120                                         final CParameters cp = (CParameters) param;
121                                         final BgpTableType tt = new BgpTableTypeImpl(((CMultiprotocol) cp).getMultiprotocolCapability().getAfi(), ((CMultiprotocol) cp).getMultiprotocolCapability().getSafi());
122                                         tts.add(tt);
123                                 }
124                         }
125                 }
126
127                 this.sync = new BGPSynchronization(this, this.listener, tts);
128                 this.tableTypes = tts;
129
130                 if (remoteOpen.getHoldTimer() != 0) {
131                         this.stateTimer.newTimeout(new TimerTask() {
132
133                                 @Override
134                                 public void run(final Timeout timeout) throws Exception {
135                                         handleHoldTimer();
136                                 }
137                         }, remoteOpen.getHoldTimer(), TimeUnit.SECONDS);
138
139                         this.stateTimer.newTimeout(new TimerTask() {
140                                 @Override
141                                 public void run(final Timeout timeout) throws Exception {
142                                         handleKeepaliveTimer();
143                                 }
144                         }, this.keepAlive, TimeUnit.SECONDS);
145                 }
146         }
147
148         @Override
149         public synchronized void close() {
150                 logger.debug("Closing session: {}", this);
151                 if (this.state != State.Idle) {
152                         this.sendMessage(new NotifyBuilder().setErrorCode(BGPError.CEASE.getCode()).build());
153                         this.channel.close();
154                         this.state = State.Idle;
155                 }
156         }
157
158         /**
159          * Handles incoming message based on their type.
160          * 
161          * @param msg incoming message
162          */
163         @Override
164         public void handleMessage(final Notification msg) {
165                 // Update last reception time
166                 this.lastMessageReceivedAt = System.nanoTime();
167
168                 if (msg instanceof Open) {
169                         // Open messages should not be present here
170                         this.terminate(BGPError.FSM_ERROR);
171                 } else if (msg instanceof Notify) {
172                         // Notifications are handled internally
173                         logger.info("Session closed because Notification message received: {} / {}", ((Notify) msg).getErrorCode(),
174                                         ((Notify) msg).getErrorSubcode());
175                         this.closeWithoutMessage();
176                         this.listener.onSessionTerminated(this,
177                                         new BGPTerminationReason(BGPError.forValue(((Notify) msg).getErrorCode(), ((Notify) msg).getErrorSubcode())));
178                 } else if (msg instanceof Keepalive) {
179                         // Keepalives are handled internally
180                         logger.debug("Received KeepAlive messsage.");
181                         this.kaCounter++;
182                         if (this.kaCounter >= 2) {
183                                 this.sync.kaReceived();
184                         }
185                 } else {
186                         // All others are passed up
187                         this.listener.onMessage(this, msg);
188                 }
189         }
190
191         @Override
192         public synchronized void endOfInput() {
193                 if (this.state == State.Up) {
194                         this.listener.onSessionDown(this, new IOException("End of input detected. Close the session."));
195                 }
196         }
197
198         void sendMessage(final Notification msg) {
199                 try {
200                         this.channel.writeAndFlush(msg);
201                         this.lastMessageSentAt = System.nanoTime();
202                         logger.debug("Sent message: {}", msg);
203                 } catch (final Exception e) {
204                         logger.warn("Message {} was not sent.", msg, e);
205                 }
206         }
207
208         private synchronized void closeWithoutMessage() {
209                 logger.debug("Closing session: {}", this);
210                 this.channel.close();
211                 this.state = State.Idle;
212         }
213
214         /**
215          * Closes PCEP session from the parent with given reason. A message needs to be sent, but parent doesn't have to be
216          * modified, because he initiated the closing. (To prevent concurrent modification exception).
217          * 
218          * @param closeObject
219          */
220         private void terminate(final BGPError error) {
221                 this.sendMessage(new NotifyBuilder().setErrorCode(error.getCode()).setErrorSubcode(error.getSubcode()).build());
222                 this.closeWithoutMessage();
223
224                 this.listener.onSessionTerminated(this, new BGPTerminationReason(error));
225         }
226
227         /**
228          * If HoldTimer expires, the session ends. If a message (whichever) was received during this period, the HoldTimer
229          * will be rescheduled by HOLD_TIMER_VALUE + the time that has passed from the start of the HoldTimer to the time at
230          * which the message was received. If the session was closed by the time this method starts to execute (the session
231          * state will become IDLE), then rescheduling won't occur.
232          */
233         private synchronized void handleHoldTimer() {
234                 if (this.state == State.Idle) {
235                         return;
236                 }
237
238                 final long ct = System.nanoTime();
239                 final long nextHold = this.lastMessageReceivedAt + TimeUnit.SECONDS.toNanos(holdTimerValue);
240
241                 if (ct >= nextHold) {
242                         logger.debug("HoldTimer expired. " + new Date());
243                         this.terminate(BGPError.HOLD_TIMER_EXPIRED);
244                 } else {
245                         this.stateTimer.newTimeout(new TimerTask() {
246                                 @Override
247                                 public void run(final Timeout timeout) throws Exception {
248                                         handleHoldTimer();
249                                 }
250                         }, nextHold - ct, TimeUnit.NANOSECONDS);
251                 }
252         }
253
254         /**
255          * If KeepAlive Timer expires, sends KeepAlive message. If a message (whichever) was send during this period, the
256          * KeepAlive Timer will be rescheduled by KEEP_ALIVE_TIMER_VALUE + the time that has passed from the start of the
257          * KeepAlive timer to the time at which the message was sent. If the session was closed by the time this method
258          * starts to execute (the session state will become IDLE), that rescheduling won't occur.
259          */
260         private synchronized void handleKeepaliveTimer() {
261                 if (this.state == State.Idle) {
262                         return;
263                 }
264
265                 final long ct = System.nanoTime();
266                 long nextKeepalive = this.lastMessageSentAt + TimeUnit.SECONDS.toNanos(this.keepAlive);
267
268                 if (ct >= nextKeepalive) {
269                         this.sendMessage(keepalive);
270                         nextKeepalive = this.lastMessageSentAt + TimeUnit.SECONDS.toNanos(this.keepAlive);
271                 }
272                 this.stateTimer.newTimeout(new TimerTask() {
273                         @Override
274                         public void run(final Timeout timeout) throws Exception {
275                                 handleKeepaliveTimer();
276                         }
277                 }, nextKeepalive - ct, TimeUnit.NANOSECONDS);
278         }
279
280         @Override
281         final public String toString() {
282                 return addToStringAttributes(Objects.toStringHelper(this)).toString();
283         }
284
285         protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
286                 toStringHelper.add("channel", this.channel);
287                 toStringHelper.add("state", this.state);
288                 return toStringHelper;
289         }
290
291         @Override
292         public Set<BgpTableType> getAdvertisedTableTypes() {
293                 return this.tableTypes;
294         }
295
296         @Override
297         protected synchronized void sessionUp() {
298                 this.state = State.Up;
299                 this.listener.onSessionUp(this);
300         }
301
302         public synchronized State getState() {
303                 return this.state;
304         }
305 }