introduce graceful restart listener capability
[bgpcep.git] / bgp / rib-impl / src / main / java / org / opendaylight / protocol / bgp / rib / impl / BGPPeer.java
1 /*
2  * Copyright (c) 2014 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 static java.util.Objects.requireNonNull;
11
12 import com.google.common.base.MoreObjects;
13 import com.google.common.base.Objects;
14 import com.google.common.cache.CacheBuilder;
15 import com.google.common.cache.CacheLoader;
16 import com.google.common.cache.LoadingCache;
17 import com.google.common.collect.ImmutableMap;
18 import com.google.common.collect.ImmutableSet;
19 import com.google.common.collect.Sets;
20 import com.google.common.net.InetAddresses;
21 import com.google.common.util.concurrent.FluentFuture;
22 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Optional;
30 import java.util.Set;
31 import java.util.concurrent.ScheduledThreadPoolExecutor;
32 import java.util.concurrent.TimeUnit;
33 import java.util.stream.Collectors;
34 import javax.annotation.concurrent.GuardedBy;
35 import org.opendaylight.controller.md.sal.common.api.data.AsyncTransaction;
36 import org.opendaylight.controller.md.sal.common.api.data.TransactionChain;
37 import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.RoutedRpcRegistration;
38 import org.opendaylight.controller.sal.binding.api.RpcProviderRegistry;
39 import org.opendaylight.mdsal.common.api.CommitInfo;
40 import org.opendaylight.protocol.bgp.openconfig.spi.BGPTableTypeRegistryConsumer;
41 import org.opendaylight.protocol.bgp.parser.BGPDocumentedException;
42 import org.opendaylight.protocol.bgp.parser.BGPError;
43 import org.opendaylight.protocol.bgp.parser.impl.message.update.LocalPreferenceAttributeParser;
44 import org.opendaylight.protocol.bgp.parser.spi.MessageUtil;
45 import org.opendaylight.protocol.bgp.rib.impl.spi.RIB;
46 import org.opendaylight.protocol.bgp.rib.impl.state.BGPSessionStateProvider;
47 import org.opendaylight.protocol.bgp.rib.spi.BGPSession;
48 import org.opendaylight.protocol.bgp.rib.spi.BGPSessionListener;
49 import org.opendaylight.protocol.bgp.rib.spi.BGPTerminationReason;
50 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
51 import org.opendaylight.protocol.bgp.rib.spi.RouterIds;
52 import org.opendaylight.protocol.bgp.rib.spi.state.BGPSessionState;
53 import org.opendaylight.protocol.bgp.rib.spi.state.BGPTimersState;
54 import org.opendaylight.protocol.bgp.rib.spi.state.BGPTransportState;
55 import org.opendaylight.protocol.concepts.AbstractRegistration;
56 import org.opendaylight.protocol.util.Ipv4Util;
57 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.AsNumber;
58 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev180329.ipv4.prefixes.DestinationIpv4Builder;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev180329.ipv4.prefixes.destination.ipv4.Ipv4Prefixes;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev180329.ipv4.prefixes.destination.ipv4.Ipv4PrefixesBuilder;
62 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;
63 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.Update;
64 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.UpdateMessage;
65 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.path.attributes.Attributes;
66 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.path.attributes.AttributesBuilder;
67 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.update.message.Nlri;
68 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.Attributes1;
69 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.Attributes2;
70 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.BgpAddPathTableType;
71 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.BgpTableType;
72 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.RouteRefresh;
73 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.SendReceive;
74 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.mp.capabilities.GracefulRestartCapability;
75 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.mp.capabilities.add.path.capability.AddressFamilies;
76 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.update.attributes.MpReachNlri;
77 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.update.attributes.MpReachNlriBuilder;
78 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.update.attributes.MpUnreachNlri;
79 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.update.attributes.MpUnreachNlriBuilder;
80 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.update.attributes.mp.reach.nlri.AdvertizedRoutesBuilder;
81 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.update.attributes.mp.unreach.nlri.WithdrawnRoutesBuilder;
82 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.peer.rpc.rev180329.BgpPeerRpcService;
83 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.peer.rpc.rev180329.PeerContext;
84 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.PeerRole;
85 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.PeerKey;
86 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.peer.AdjRibOut;
87 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.Tables;
88 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.TablesKey;
89 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.AddressFamily;
90 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.ClusterIdentifier;
91 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.Ipv4AddressFamily;
92 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.RouteTarget;
93 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.SubsequentAddressFamily;
94 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.UnicastSubsequentAddressFamily;
95 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
96 import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
97 import org.opendaylight.yangtools.yang.binding.Notification;
98 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
99 import org.slf4j.Logger;
100 import org.slf4j.LoggerFactory;
101
102 /**
103  * Class representing a peer. We have a single instance for each peer, which provides translation from BGP events into
104  * RIB actions.
105  */
106 public class BGPPeer extends AbstractPeer implements BGPSessionListener {
107     private static final Logger LOG = LoggerFactory.getLogger(BGPPeer.class);
108     private static final TablesKey IPV4_UCAST_TABLE_KEY = new TablesKey(Ipv4AddressFamily.class, UnicastSubsequentAddressFamily.class);
109
110     private Set<TablesKey> tables = Collections.emptySet();
111     private final RIB rib;
112     private final Map<TablesKey, AdjRibOutListener> adjRibOutListenerSet = new HashMap<>();
113     private final List<RouteTarget> rtMemberships = new ArrayList<>();
114     private final RpcProviderRegistry rpcRegistry;
115     private final BGPTableTypeRegistryConsumer tableTypeRegistry;
116     private InstanceIdentifier<AdjRibOut> peerRibOutIId;
117     @GuardedBy("this")
118     private AbstractRegistration trackerRegistration;
119     private final LoadingCache<TablesKey, KeyedInstanceIdentifier<Tables, TablesKey>> tablesIId
120             = CacheBuilder.newBuilder()
121             .build(new CacheLoader<TablesKey, KeyedInstanceIdentifier<Tables, TablesKey>>() {
122                 @Override
123                 public KeyedInstanceIdentifier<Tables, TablesKey> load(final TablesKey tablesKey) {
124                     return BGPPeer.this.peerRibOutIId.child(Tables.class, tablesKey);
125                 }
126             });
127
128     @GuardedBy("this")
129     private BGPSession session;
130     @GuardedBy("this")
131     private AdjRibInWriter ribWriter;
132     @GuardedBy("this")
133     private EffectiveRibInWriter effRibInWriter;
134     private RoutedRpcRegistration<BgpPeerRpcService> rpcRegistration;
135     private Map<TablesKey, SendReceive> addPathTableMaps = Collections.emptyMap();
136     private YangInstanceIdentifier peerPath;
137     private boolean sessionUp;
138     private long peerRestartStart;
139
140     public BGPPeer(
141             final BGPTableTypeRegistryConsumer tableTypeRegistry,
142             final IpAddress neighborAddress,
143             final String peerGroupName,
144             final RIB rib,
145             final PeerRole role,
146             final ClusterIdentifier clusterId,
147             final AsNumber localAs,
148             final RpcProviderRegistry rpcRegistry,
149             final Set<TablesKey> afiSafisAdvertized,
150             final Set<TablesKey> afiSafisGracefulAdvertized) {
151         super(rib, Ipv4Util.toStringIP(neighborAddress), peerGroupName, role, clusterId,
152                 localAs, neighborAddress, afiSafisAdvertized, afiSafisGracefulAdvertized);
153         this.tableTypeRegistry = requireNonNull(tableTypeRegistry);
154         this.rib = requireNonNull(rib);
155         this.rpcRegistry = rpcRegistry;
156     }
157
158     BGPPeer(
159             final BGPTableTypeRegistryConsumer tableTypeRegistry,
160             final IpAddress neighborAddress,
161             final RIB rib,
162             final PeerRole role,
163             final RpcProviderRegistry rpcRegistry,
164             final Set<TablesKey> afiSafisAdvertized,
165             final Set<TablesKey> afiSafisGracefulAdvertized) {
166         this(tableTypeRegistry, neighborAddress, null, rib, role, null, null, rpcRegistry,
167                 afiSafisAdvertized, afiSafisGracefulAdvertized);
168     }
169
170
171     private static Attributes nextHopToAttribute(final Attributes attrs, final MpReachNlri mpReach) {
172         if (attrs.getCNextHop() == null && mpReach.getCNextHop() != null) {
173             final AttributesBuilder attributesBuilder = new AttributesBuilder(attrs);
174             attributesBuilder.setCNextHop(mpReach.getCNextHop());
175             return attributesBuilder.build();
176         }
177         return attrs;
178     }
179
180     /**
181      * Creates MPReach for the prefixes to be handled in the same way as linkstate routes.
182      *
183      * @param message Update message containing prefixes in NLRI
184      * @return MpReachNlri with prefixes from the nlri field
185      */
186     private static MpReachNlri prefixesToMpReach(final Update message) {
187         final List<Ipv4Prefixes> prefixes = message.getNlri().stream()
188                 .map(n -> new Ipv4PrefixesBuilder().setPrefix(n.getPrefix()).setPathId(n.getPathId()).build())
189                 .collect(Collectors.toList());
190         final MpReachNlriBuilder b = new MpReachNlriBuilder().setAfi(Ipv4AddressFamily.class).setSafi(
191                 UnicastSubsequentAddressFamily.class).setAdvertizedRoutes(
192                 new AdvertizedRoutesBuilder().setDestinationType(
193                         new DestinationIpv4CaseBuilder().setDestinationIpv4(
194                                 new DestinationIpv4Builder().setIpv4Prefixes(prefixes).build()).build()).build());
195         if (message.getAttributes() != null) {
196             b.setCNextHop(message.getAttributes().getCNextHop());
197         }
198         return b.build();
199     }
200
201     /**
202      * Create MPUnreach for the prefixes to be handled in the same way as linkstate routes.
203      *
204      * @param message            Update message containing withdrawn routes
205      * @param isAnyNlriAnnounced isAnyNlriAnnounced
206      * @return MpUnreachNlri with prefixes from the withdrawn routes field
207      */
208     private static MpUnreachNlri prefixesToMpUnreach(final Update message, final boolean isAnyNlriAnnounced) {
209         final List<Ipv4Prefixes> prefixes = new ArrayList<>();
210         message.getWithdrawnRoutes().forEach(w -> {
211
212             Optional<Nlri> nlriAnounced = Optional.empty();
213             if (isAnyNlriAnnounced) {
214                 nlriAnounced = message.getNlri().stream().filter(n -> Objects.equal(n.getPrefix(), w.getPrefix())
215                         && Objects.equal(n.getPathId(), w.getPathId()))
216                         .findAny();
217             }
218             if (!nlriAnounced.isPresent()) {
219                 prefixes.add(new Ipv4PrefixesBuilder().setPrefix(w.getPrefix()).setPathId(w.getPathId()).build());
220             }
221         });
222         return new MpUnreachNlriBuilder().setAfi(Ipv4AddressFamily.class).setSafi(UnicastSubsequentAddressFamily.class)
223                 .setWithdrawnRoutes(new WithdrawnRoutesBuilder().setDestinationType(new org.opendaylight.yang.gen.v1
224                         .urn.opendaylight.params.xml.ns.yang.bgp.inet.rev180329.update.attributes.mp.unreach.nlri
225                         .withdrawn.routes.destination.type.DestinationIpv4CaseBuilder().setDestinationIpv4(
226                         new DestinationIpv4Builder().setIpv4Prefixes(prefixes).build()).build()).build()).build();
227     }
228
229     private static Map<TablesKey, SendReceive> mapTableTypesFamilies(final List<AddressFamilies> addPathTablesType) {
230         return ImmutableMap.copyOf(addPathTablesType.stream().collect(Collectors.toMap(af -> new TablesKey(af.getAfi(),
231                         af.getSafi()), BgpAddPathTableType::getSendReceive)));
232     }
233
234     public synchronized void instantiateServiceInstance() {
235         this.ribWriter = AdjRibInWriter.create(this.rib.getYangRibId(), this.peerRole, this);
236         setActive(true);
237     }
238
239     @Override
240     public synchronized FluentFuture<? extends CommitInfo> close() {
241         final FluentFuture<? extends CommitInfo> future = releaseConnection();
242         closeDomChain();
243         setActive(false);
244         return future;
245     }
246
247     @Override
248     public void onMessage(final BGPSession session, final Notification msg) throws BGPDocumentedException {
249         if (msg instanceof Update) {
250             onUpdateMessage((Update) msg);
251         } else if (msg instanceof RouteRefresh) {
252             onRouteRefreshMessage((RouteRefresh) msg);
253         } else {
254             LOG.info("Ignoring unhandled message class {}", msg.getClass());
255         }
256     }
257
258     private void onRouteRefreshMessage(final RouteRefresh message) {
259         final Class<? extends AddressFamily> rrAfi = message.getAfi();
260         final Class<? extends SubsequentAddressFamily> rrSafi = message.getSafi();
261
262         final TablesKey key = new TablesKey(rrAfi, rrSafi);
263         final AdjRibOutListener listener = this.adjRibOutListenerSet.get(key);
264         if (listener != null) {
265             listener.close();
266             this.adjRibOutListenerSet.remove(key);
267             createAdjRibOutListener(key, listener.isMpSupported());
268         } else {
269             LOG.info("Ignoring RouteRefresh message. Afi/Safi is not supported: {}, {}.", rrAfi, rrSafi);
270         }
271     }
272
273     /**
274      * Check for presence of well known mandatory attribute LOCAL_PREF in Update message.
275      *
276      * @param message Update message
277      */
278     private void checkMandatoryAttributesPresence(final Update message) throws BGPDocumentedException {
279         if (MessageUtil.isAnyNlriPresent(message)) {
280             final Attributes attrs = message.getAttributes();
281             if (this.peerRole == PeerRole.Ibgp && (attrs == null || attrs.getLocalPref() == null)) {
282                 throw new BGPDocumentedException(BGPError.MANDATORY_ATTR_MISSING_MSG + "LOCAL_PREF",
283                         BGPError.WELL_KNOWN_ATTR_MISSING,
284                         new byte[]{LocalPreferenceAttributeParser.TYPE});
285             }
286         }
287     }
288
289     /**
290      * Process Update message received.
291      * Calls {@link #checkMandatoryAttributesPresence(Update)} to check for presence of mandatory attributes.
292      *
293      * @param message Update message
294      */
295     private synchronized void onUpdateMessage(final Update message) throws BGPDocumentedException {
296         checkMandatoryAttributesPresence(message);
297
298         // update AdjRibs
299         final Attributes attrs = message.getAttributes();
300         MpReachNlri mpReach;
301         final boolean isAnyNlriAnnounced = message.getNlri() != null;
302         if (isAnyNlriAnnounced) {
303             mpReach = prefixesToMpReach(message);
304         } else {
305             mpReach = MessageUtil.getMpReachNlri(attrs);
306         }
307         if (mpReach != null) {
308             this.ribWriter.updateRoutes(mpReach, nextHopToAttribute(attrs, mpReach));
309         }
310         MpUnreachNlri mpUnreach;
311         if (message.getWithdrawnRoutes() != null) {
312             mpUnreach = prefixesToMpUnreach(message, isAnyNlriAnnounced);
313         } else {
314             mpUnreach = MessageUtil.getMpUnreachNlri(attrs);
315         }
316         final boolean endOfRib = isEndOfRib(message);
317         if (mpUnreach != null) {
318             if (endOfRib) {
319                 final TablesKey tablesKey = new TablesKey(mpUnreach.getAfi(), mpUnreach.getSafi());
320                 this.ribWriter.removeStaleRoutes(tablesKey);
321             } else {
322                 this.ribWriter.removeRoutes(mpUnreach);
323             }
324         } else if (endOfRib) {
325
326             this.ribWriter.removeStaleRoutes(IPV4_UCAST_TABLE_KEY);
327         }
328     }
329
330     @Override
331     public synchronized void onSessionUp(final BGPSession session) {
332         this.session = session;
333         this.sessionUp = true;
334         this.bindingChain = this.rib.createPeerChain(this);
335         if (this.session instanceof BGPSessionStateProvider) {
336             ((BGPSessionStateProvider) this.session).registerMessagesCounter(this);
337         }
338         final GracefulRestartCapability advertisedGracefulRestartCapability =
339                 session.getAdvertisedGracefulRestartCapability();
340         final List<org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.mp.capabilities.graceful.restart.capability.Tables> advertisedTables =
341                 advertisedGracefulRestartCapability.getTables();
342         final List<AddressFamilies> addPathTablesType = session.getAdvertisedAddPathTableTypes();
343         final Set<BgpTableType> advertizedTableTypes = session.getAdvertisedTableTypes();
344         LOG.info("Session with peer {} went up with tables {} and Add Path tables {}", this.name,
345                 advertizedTableTypes, addPathTablesType);
346         final Set<TablesKey> setTables = advertizedTableTypes.stream().map(t -> new TablesKey(t.getAfi(), t.getSafi()))
347                 .collect(Collectors.toSet());
348         this.tables = ImmutableSet.copyOf(setTables);
349         this.addPathTableMaps = mapTableTypesFamilies(addPathTablesType);
350
351         if (!isPeerRestarting()) {
352             this.rawIdentifier = InetAddresses.forString(session.getBgpId().getValue()).getAddress();
353             this.peerId = RouterIds.createPeerId(session.getBgpId());
354             final KeyedInstanceIdentifier<org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib
355                     .rev180329.bgp.rib.rib.Peer, PeerKey> peerIId =
356                     getInstanceIdentifier().child(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns
357                             .yang.bgp.rib.rev180329.bgp.rib.rib.Peer.class, new PeerKey(this.peerId));
358             this.peerPath = createPeerPath();
359             this.peerRibOutIId = peerIId.child(AdjRibOut.class);
360             this.trackerRegistration = this.rib.getPeerTracker().registerPeer(this);
361             this.effRibInWriter = new EffectiveRibInWriter(this, this.rib,
362                     this.rib.createPeerChain(this),
363                     peerIId, this.tables, this.tableTypeRegistry,
364                     this.rtMemberships,
365                     this.rtCache);
366             registerPrefixesCounters(this.effRibInWriter, this.effRibInWriter);
367             this.effRibInWriter.init();
368
369             this.ribWriter = this.ribWriter.transform(this.peerId, this.peerPath, this.rib.getRibSupportContext(),
370                     this.tables, this.addPathTableMaps);
371
372             if (this.rpcRegistry != null) {
373                 this.rpcRegistration = this.rpcRegistry.addRoutedRpcImplementation(BgpPeerRpcService.class,
374                         new BgpPeerRpc(this, session, this.tables));
375                 final KeyedInstanceIdentifier<org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib
376                         .rev180329.bgp.rib.rib.Peer, PeerKey> path = this.rib.getInstanceIdentifier()
377                         .child(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib
378                                 .rib.Peer.class, new PeerKey(this.peerId));
379                 this.rpcRegistration.registerPath(PeerContext.class, path);
380             }
381         } else {
382             final Set<TablesKey> forwardingTables;
383             if (advertisedTables == null) {
384                 forwardingTables = Collections.emptySet();
385             } else {
386                 forwardingTables = advertisedTables.stream()
387                         .filter(table -> table.getAfiFlags() != null)
388                         .filter(table -> table.getAfiFlags().isForwardingState())
389                         .map(table -> new TablesKey(table.getAfi(), table.getSafi()))
390                         .collect(Collectors.toSet());
391             }
392             this.ribWriter.clearTables(Sets.difference(this.tables, forwardingTables));
393         }
394         if (advertisedTables == null ||
395                 advertisedTables.isEmpty()) {
396             setAdvertizedGracefulRestartTableTypes(Collections.emptyList());
397         } else {
398             setAdvertizedGracefulRestartTableTypes(advertisedTables.stream()
399                     .map(t -> new TablesKey(t.getAfi(), t.getSafi())).collect(Collectors.toList()));
400         }
401         final int restartTime = advertisedGracefulRestartCapability.getRestartTime();
402         setAfiSafiGracefulRestartState(restartTime, false,false);
403         addBgp4Support();
404         for (final TablesKey key : this.tables) {
405             createAdjRibOutListener(key, true);
406         }
407     }
408
409     //try to add a support for old-school BGP-4, if peer did not advertise IPv4-Unicast MP capability
410     private synchronized void addBgp4Support() {
411         if (!this.tables.contains(IPV4_UCAST_TABLE_KEY)) {
412             final HashSet<TablesKey> newSet = new HashSet<>(this.tables);
413             newSet.add(IPV4_UCAST_TABLE_KEY);
414             this.tables = ImmutableSet.copyOf(newSet);
415             createAdjRibOutListener(IPV4_UCAST_TABLE_KEY, false);
416         }
417     }
418
419     private synchronized void createAdjRibOutListener(final TablesKey key,
420                                                       final boolean mpSupport) {
421         final RIBSupport<?, ?, ?, ?> ribSupport = this.rib.getRibSupportContext().getRIBSupport(key);
422
423         // not particularly nice
424         if (ribSupport != null && this.session instanceof BGPSessionImpl) {
425             final ChannelOutputLimiter limiter = ((BGPSessionImpl) this.session).getLimiter();
426             final AdjRibOutListener adjRibOut = AdjRibOutListener.create(this.peerId, key,
427                     this.rib.getYangRibId(), this.rib.getCodecsRegistry(), ribSupport,
428                     this.rib.getService(), limiter, mpSupport);
429             this.adjRibOutListenerSet.put(key, adjRibOut);
430             registerPrefixesSentCounter(key, adjRibOut);
431         }
432     }
433
434     @Override
435     public synchronized void onSessionDown(final BGPSession session, final Exception e) {
436         if (e.getMessage().equals(BGPSessionImpl.END_OF_INPUT)) {
437             LOG.info("Session with peer {} went down", this.name);
438         } else {
439             LOG.info("Session with peer {} went down", this.name, e);
440         }
441         releaseConnectionGracefully();
442     }
443
444     @Override
445     public synchronized void onSessionTerminated(final BGPSession session, final BGPTerminationReason cause) {
446         LOG.info("Session with peer {} terminated: {}", this.name, cause);
447         releaseConnectionGracefully();
448     }
449
450     @Override
451     public String toString() {
452         return MoreObjects.toStringHelper(this)
453                 .add("name", this.name)
454                 .add("tables", this.tables).toString();
455         }
456
457     @Override
458     public synchronized FluentFuture<? extends CommitInfo> releaseConnection() {
459         LOG.info("Closing session with peer");
460         this.sessionUp = false;
461         this.adjRibOutListenerSet.values().forEach(AdjRibOutListener::close);
462         this.adjRibOutListenerSet.clear();
463         final FluentFuture<? extends CommitInfo> future;
464         if (!isPeerRestarting()) {
465             future = terminateConnection();
466         } else {
467             final Set<TablesKey> gracefulTables = getGracefulTables();
468             this.ribWriter.storeStaleRoutes(gracefulTables);
469             future = this.ribWriter.clearTables(Sets.difference(this.tables, gracefulTables));
470             this.peerRestartStart = System.nanoTime();
471             handleRestartTimer();
472         }
473         releaseBindingChain();
474
475         if (this.session != null) {
476             try {
477                 if (isPeerRestarting()) {
478                     this.session.closeWithoutMessage();
479                 } else {
480                     this.session.close();
481                 }
482             } catch (final Exception e) {
483                 LOG.warn("Error closing session with peer", e);
484             }
485             this.session = null;
486         }
487         return future;
488     }
489
490     private Set<TablesKey> getGracefulTables() {
491         return this.tables.stream()
492                 .filter(this::isGracefulRestartReceived)
493                 .filter(this::isGracefulRestartAdvertized)
494                 .collect(Collectors.toSet());
495     }
496
497     private synchronized FluentFuture<? extends CommitInfo> terminateConnection() {
498         final FluentFuture<? extends CommitInfo> future;
499         if (this.trackerRegistration != null) {
500             this.trackerRegistration.close();
501             this.trackerRegistration = null;
502         }
503         if (this.rpcRegistration != null) {
504             this.rpcRegistration.close();
505         }
506         this.ribWriter.releaseChain();
507
508         if (this.effRibInWriter != null) {
509             this.effRibInWriter.close();
510         }
511         this.tables = Collections.emptySet();
512         this.addPathTableMaps = Collections.emptyMap();
513         future = removePeer(this.peerPath);
514         resetState();
515
516         return future;
517     }
518
519     /**
520      * If Graceful Restart Timer expires, remove all routes advertised by peer.
521      */
522     private synchronized void handleRestartTimer() {
523         if (!isPeerRestarting()) {
524             return;
525         }
526
527         final long ct = System.nanoTime();
528         final long restartExpire = this.peerRestartStart + TimeUnit.SECONDS.toNanos(getPeerRestartTime());
529
530         if (ct >= restartExpire) {
531             setAfiSafiGracefulRestartState(0, false, false);
532             onSessionTerminated(this.session, new BGPTerminationReason(BGPError.HOLD_TIMER_EXPIRED));
533         }
534         new ScheduledThreadPoolExecutor(1)
535                 .schedule(this::handleRestartTimer, restartExpire - ct, TimeUnit.NANOSECONDS);
536     }
537
538     private boolean isEndOfRib(final UpdateMessage msg) {
539         if (msg.getNlri() == null && msg.getWithdrawnRoutes() == null) {
540             final Attributes msgAttributes = msg.getAttributes();
541             if (msgAttributes != null) {
542                 final Attributes2 pa = msgAttributes.augmentation(Attributes2.class);
543                 if (msgAttributes.augmentation(Attributes1.class) == null && pa != null) {
544                     //only MP_UNREACH_NLRI allowed in EOR
545                     if (pa.getMpUnreachNlri() != null && pa.getMpUnreachNlri().getWithdrawnRoutes() == null) {
546                         // EOR message contains only MPUnreach attribute and no NLRI
547                         return true;
548                     }
549                 }
550             } else {
551                 // true for empty IPv4 Unicast
552                 return true;
553             }
554         }
555         return false;
556     }
557
558     private void releaseConnectionGracefully() {
559         if (getPeerRestartTime() > 0) {
560             setRestartingState();
561         }
562         releaseConnection();
563     }
564
565     @SuppressFBWarnings("IS2_INCONSISTENT_SYNC")
566     @Override
567     public SendReceive getSupportedAddPathTables(final TablesKey tableKey) {
568         return this.addPathTableMaps.get(tableKey);
569     }
570
571     @Override
572     public boolean supportsTable(final TablesKey tableKey) {
573         return this.tables.contains(tableKey) && this.sessionUp;
574     }
575
576     @Override
577     public KeyedInstanceIdentifier<Tables, TablesKey> getRibOutIId(final TablesKey tablesKey) {
578         return this.tablesIId.getUnchecked(tablesKey);
579     }
580
581     @Override
582     public synchronized void onTransactionChainFailed(final TransactionChain<?, ?> chain,
583             final AsyncTransaction<?, ?> transaction, final Throwable cause) {
584         LOG.error("Transaction domChain failed.", cause);
585         releaseConnection();
586     }
587
588     @Override
589     public synchronized void markUptodate(final TablesKey tablesKey) {
590         this.ribWriter.markTableUptodate(tablesKey);
591     }
592
593     @Override
594     public synchronized BGPSessionState getBGPSessionState() {
595         if (this.session instanceof BGPSessionStateProvider) {
596             return ((BGPSessionStateProvider) this.session).getBGPSessionState();
597         }
598         return null;
599     }
600
601     @Override
602     public synchronized BGPTimersState getBGPTimersState() {
603         if (this.session instanceof BGPSessionStateProvider) {
604             return ((BGPSessionStateProvider) this.session).getBGPTimersState();
605         }
606         return null;
607     }
608
609     @Override
610     public synchronized BGPTransportState getBGPTransportState() {
611         if (this.session instanceof BGPSessionStateProvider) {
612             return ((BGPSessionStateProvider) this.session).getBGPTransportState();
613         }
614         return null;
615     }
616
617     @Override
618     public List<RouteTarget> getMemberships() {
619         return this.rtMemberships;
620     }
621 }