344805f3843dca3dfeb76291ced4788474aa3247
[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.Stopwatch;
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 com.google.common.util.concurrent.Futures;
23 import com.google.common.util.concurrent.ListenableFuture;
24 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Objects;
32 import java.util.Optional;
33 import java.util.Set;
34 import java.util.concurrent.TimeUnit;
35 import java.util.stream.Collectors;
36 import org.checkerframework.checker.lock.qual.GuardedBy;
37 import org.checkerframework.checker.lock.qual.Holding;
38 import org.opendaylight.mdsal.binding.api.RpcProviderService;
39 import org.opendaylight.mdsal.binding.api.Transaction;
40 import org.opendaylight.mdsal.binding.api.TransactionChain;
41 import org.opendaylight.mdsal.common.api.CommitInfo;
42 import org.opendaylight.mdsal.dom.api.DOMDataTreeTransaction;
43 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
44 import org.opendaylight.protocol.bgp.openconfig.spi.BGPTableTypeRegistryConsumer;
45 import org.opendaylight.protocol.bgp.parser.BGPDocumentedException;
46 import org.opendaylight.protocol.bgp.parser.BGPError;
47 import org.opendaylight.protocol.bgp.parser.impl.message.update.LocalPreferenceAttributeParser;
48 import org.opendaylight.protocol.bgp.parser.spi.MessageUtil;
49 import org.opendaylight.protocol.bgp.parser.spi.RevisedErrorHandlingSupport;
50 import org.opendaylight.protocol.bgp.rib.impl.config.BgpPeer;
51 import org.opendaylight.protocol.bgp.rib.impl.config.GracefulRestartUtil;
52 import org.opendaylight.protocol.bgp.rib.impl.spi.BGPSessionPreferences;
53 import org.opendaylight.protocol.bgp.rib.impl.spi.RIB;
54 import org.opendaylight.protocol.bgp.rib.impl.state.BGPSessionStateProvider;
55 import org.opendaylight.protocol.bgp.rib.spi.BGPSession;
56 import org.opendaylight.protocol.bgp.rib.spi.BGPSessionListener;
57 import org.opendaylight.protocol.bgp.rib.spi.BGPTerminationReason;
58 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
59 import org.opendaylight.protocol.bgp.rib.spi.RouterIds;
60 import org.opendaylight.protocol.bgp.rib.spi.state.BGPSessionState;
61 import org.opendaylight.protocol.bgp.rib.spi.state.BGPTimersState;
62 import org.opendaylight.protocol.bgp.rib.spi.state.BGPTransportState;
63 import org.opendaylight.protocol.util.Ipv4Util;
64 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.AsNumber;
65 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddressNoZone;
66 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev180329.ipv4.prefixes.DestinationIpv4Builder;
67 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev180329.ipv4.prefixes.destination.ipv4.Ipv4Prefixes;
68 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev180329.ipv4.prefixes.destination.ipv4.Ipv4PrefixesBuilder;
69 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;
70 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.Update;
71 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.open.message.BgpParameters;
72 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.path.attributes.Attributes;
73 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.path.attributes.AttributesBuilder;
74 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.update.message.Nlri;
75 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.BgpAddPathTableType;
76 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.BgpTableType;
77 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.RouteRefresh;
78 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.SendReceive;
79 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.mp.capabilities.GracefulRestartCapability;
80 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.mp.capabilities.add.path.capability.AddressFamilies;
81 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.update.attributes.MpReachNlri;
82 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.update.attributes.MpReachNlriBuilder;
83 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.update.attributes.MpUnreachNlri;
84 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.update.attributes.MpUnreachNlriBuilder;
85 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.update.attributes.mp.reach.nlri.AdvertizedRoutesBuilder;
86 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.update.attributes.mp.unreach.nlri.WithdrawnRoutesBuilder;
87 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.peer.rpc.rev180329.BgpPeerRpcService;
88 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.PeerRole;
89 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.PeerKey;
90 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.peer.AdjRibOut;
91 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.Tables;
92 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.TablesKey;
93 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev200120.AddressFamily;
94 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev200120.ClusterIdentifier;
95 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev200120.Ipv4AddressFamily;
96 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev200120.RouteTarget;
97 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev200120.SubsequentAddressFamily;
98 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev200120.UnicastSubsequentAddressFamily;
99 import org.opendaylight.yangtools.concepts.ObjectRegistration;
100 import org.opendaylight.yangtools.concepts.Registration;
101 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
102 import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
103 import org.opendaylight.yangtools.yang.binding.Notification;
104 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
105 import org.slf4j.Logger;
106 import org.slf4j.LoggerFactory;
107
108 /**
109  * Class representing a peer. We have a single instance for each peer, which provides translation from BGP events into
110  * RIB actions.
111  */
112 public class BGPPeer extends AbstractPeer implements BGPSessionListener {
113     private static final Logger LOG = LoggerFactory.getLogger(BGPPeer.class);
114     private static final TablesKey IPV4_UCAST_TABLE_KEY = new TablesKey(Ipv4AddressFamily.class,
115         UnicastSubsequentAddressFamily.class);
116
117     private ImmutableSet<TablesKey> tables = ImmutableSet.of();
118     private final RIB rib;
119     private final Map<TablesKey, AdjRibOutListener> adjRibOutListenerSet = new HashMap<>();
120     private final List<RouteTarget> rtMemberships = new ArrayList<>();
121     private final RpcProviderService rpcRegistry;
122     private final BGPTableTypeRegistryConsumer tableTypeRegistry;
123     private final BgpPeer bgpPeer;
124     private InstanceIdentifier<AdjRibOut> peerRibOutIId;
125     private KeyedInstanceIdentifier<org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib
126             .rev180329.bgp.rib.rib.Peer, PeerKey> peerIId;
127     @GuardedBy("this")
128     private Registration trackerRegistration;
129     private final LoadingCache<TablesKey, KeyedInstanceIdentifier<Tables, TablesKey>> tablesIId
130             = CacheBuilder.newBuilder()
131             .build(new CacheLoader<TablesKey, KeyedInstanceIdentifier<Tables, TablesKey>>() {
132                 @Override
133                 public KeyedInstanceIdentifier<Tables, TablesKey> load(final TablesKey tablesKey) {
134                     return BGPPeer.this.peerRibOutIId.child(Tables.class, tablesKey);
135                 }
136             });
137
138     @GuardedBy("this")
139     private BGPSession currentSession;
140     @GuardedBy("this")
141     private AdjRibInWriter ribWriter;
142     @GuardedBy("this")
143     private EffectiveRibInWriter effRibInWriter;
144     private ObjectRegistration<BgpPeerRpcService> rpcRegistration;
145     private Map<TablesKey, SendReceive> addPathTableMaps = Collections.emptyMap();
146     private YangInstanceIdentifier peerPath;
147     private boolean sessionUp;
148     private boolean llgrSupport;
149     private Stopwatch peerRestartStopwatch;
150     private long currentSelectionDeferralTimerSeconds;
151     private final List<TablesKey> missingEOT = new ArrayList<>();
152
153     public BGPPeer(
154             final BGPTableTypeRegistryConsumer tableTypeRegistry,
155             final IpAddressNoZone neighborAddress,
156             final String peerGroupName,
157             final RIB rib,
158             final PeerRole role,
159             final ClusterIdentifier clusterId,
160             final AsNumber localAs,
161             final RpcProviderService rpcRegistry,
162             final Set<TablesKey> afiSafisAdvertized,
163             final Set<TablesKey> afiSafisGracefulAdvertized,
164             final Map<TablesKey, Integer> llGracefulTablesAdvertised,
165             final BgpPeer bgpPeer) {
166         super(rib, Ipv4Util.toStringIP(neighborAddress), peerGroupName, role, clusterId,
167                 localAs, neighborAddress, afiSafisAdvertized, afiSafisGracefulAdvertized, llGracefulTablesAdvertised);
168         this.tableTypeRegistry = requireNonNull(tableTypeRegistry);
169         this.rib = requireNonNull(rib);
170         this.rpcRegistry = rpcRegistry;
171         this.bgpPeer = bgpPeer;
172     }
173
174     private static Attributes nextHopToAttribute(final Attributes attrs, final MpReachNlri mpReach) {
175         if (attrs.getCNextHop() == null && mpReach.getCNextHop() != null) {
176             final AttributesBuilder attributesBuilder = new AttributesBuilder(attrs);
177             attributesBuilder.setCNextHop(mpReach.getCNextHop());
178             return attributesBuilder.build();
179         }
180         return attrs;
181     }
182
183     /**
184      * Creates MPReach for the prefixes to be handled in the same way as linkstate routes.
185      *
186      * @param message Update message containing prefixes in NLRI
187      * @return MpReachNlri with prefixes from the nlri field
188      */
189     private static MpReachNlri prefixesToMpReach(final Update message) {
190         final List<Ipv4Prefixes> prefixes = message.getNlri().stream()
191                 .map(n -> new Ipv4PrefixesBuilder().setPrefix(n.getPrefix()).setPathId(n.getPathId()).build())
192                 .collect(Collectors.toList());
193         final MpReachNlriBuilder b = new MpReachNlriBuilder().setAfi(Ipv4AddressFamily.class).setSafi(
194                 UnicastSubsequentAddressFamily.class).setAdvertizedRoutes(
195                 new AdvertizedRoutesBuilder().setDestinationType(
196                         new DestinationIpv4CaseBuilder().setDestinationIpv4(
197                                 new DestinationIpv4Builder().setIpv4Prefixes(prefixes).build()).build()).build());
198         if (message.getAttributes() != null) {
199             b.setCNextHop(message.getAttributes().getCNextHop());
200         }
201         return b.build();
202     }
203
204     /**
205      * Create MPUnreach for the prefixes to be handled in the same way as linkstate routes.
206      *
207      * @param message            Update message containing withdrawn routes
208      * @param isAnyNlriAnnounced isAnyNlriAnnounced
209      * @return MpUnreachNlri with prefixes from the withdrawn routes field
210      */
211     private static MpUnreachNlri prefixesToMpUnreach(final Update message, final boolean isAnyNlriAnnounced) {
212         final List<Ipv4Prefixes> prefixes = new ArrayList<>();
213         message.getWithdrawnRoutes().forEach(w -> {
214
215             Optional<Nlri> nlriAnounced = Optional.empty();
216             if (isAnyNlriAnnounced) {
217                 nlriAnounced = message.getNlri().stream().filter(n -> Objects.equals(n.getPrefix(), w.getPrefix())
218                         && Objects.equals(n.getPathId(), w.getPathId()))
219                         .findAny();
220             }
221             if (!nlriAnounced.isPresent()) {
222                 prefixes.add(new Ipv4PrefixesBuilder().setPrefix(w.getPrefix()).setPathId(w.getPathId()).build());
223             }
224         });
225         return new MpUnreachNlriBuilder().setAfi(Ipv4AddressFamily.class).setSafi(UnicastSubsequentAddressFamily.class)
226                 .setWithdrawnRoutes(new WithdrawnRoutesBuilder().setDestinationType(new org.opendaylight.yang.gen.v1
227                         .urn.opendaylight.params.xml.ns.yang.bgp.inet.rev180329.update.attributes.mp.unreach.nlri
228                         .withdrawn.routes.destination.type.DestinationIpv4CaseBuilder().setDestinationIpv4(
229                         new DestinationIpv4Builder().setIpv4Prefixes(prefixes).build()).build()).build()).build();
230     }
231
232     private static Map<TablesKey, SendReceive> mapTableTypesFamilies(final List<AddressFamilies> addPathTablesType) {
233         return ImmutableMap.copyOf(addPathTablesType.stream().collect(Collectors.toMap(af -> new TablesKey(af.getAfi(),
234                         af.getSafi()), BgpAddPathTableType::getSendReceive)));
235     }
236
237     public synchronized void instantiateServiceInstance() {
238         createDomChain();
239         this.ribWriter = AdjRibInWriter.create(this.rib.getYangRibId(), this.peerRole, this);
240         setActive(true);
241     }
242
243     @Override
244     public synchronized FluentFuture<? extends CommitInfo> close() {
245         final FluentFuture<? extends CommitInfo> future = releaseConnection();
246         closeDomChain();
247         setActive(false);
248         return future;
249     }
250
251     @Override
252     public void onMessage(final BGPSession session, final Notification msg) throws BGPDocumentedException {
253         if (msg instanceof Update) {
254             onUpdateMessage((Update) msg);
255         } else if (msg instanceof RouteRefresh) {
256             onRouteRefreshMessage((RouteRefresh) msg);
257         } else {
258             LOG.info("Ignoring unhandled message class {}", msg.getClass());
259         }
260     }
261
262     private void onRouteRefreshMessage(final RouteRefresh message) {
263         final Class<? extends AddressFamily> rrAfi = message.getAfi();
264         final Class<? extends SubsequentAddressFamily> rrSafi = message.getSafi();
265
266         final TablesKey key = new TablesKey(rrAfi, rrSafi);
267         synchronized (this) {
268             final AdjRibOutListener listener = this.adjRibOutListenerSet.remove(key);
269             if (listener != null) {
270                 listener.close();
271                 createAdjRibOutListener(key, listener.isMpSupported());
272             } else {
273                 LOG.info("Ignoring RouteRefresh message. Afi/Safi is not supported: {}, {}.", rrAfi, rrSafi);
274             }
275         }
276     }
277
278     /**
279      * Check for presence of well known mandatory attribute LOCAL_PREF in Update message.
280      *
281      * @param message Update message
282      */
283     private void checkMandatoryAttributesPresence(final Update message) throws BGPDocumentedException {
284         if (MessageUtil.isAnyNlriPresent(message)) {
285             final Attributes attrs = message.getAttributes();
286             if (this.peerRole == PeerRole.Ibgp && (attrs == null || attrs.getLocalPref() == null)) {
287                 throw new BGPDocumentedException(BGPError.MANDATORY_ATTR_MISSING_MSG + "LOCAL_PREF",
288                         BGPError.WELL_KNOWN_ATTR_MISSING,
289                         new byte[]{LocalPreferenceAttributeParser.TYPE});
290             }
291         }
292     }
293
294     /**
295      * Process Update message received.
296      * Calls {@link #checkMandatoryAttributesPresence(Update)} to check for presence of mandatory attributes.
297      *
298      * @param message Update message
299      */
300     private synchronized void onUpdateMessage(final Update message) throws BGPDocumentedException {
301         checkMandatoryAttributesPresence(message);
302
303         // update AdjRibs
304         final Attributes attrs = message.getAttributes();
305         MpReachNlri mpReach;
306         final boolean isAnyNlriAnnounced = message.getNlri() != null;
307         if (isAnyNlriAnnounced) {
308             mpReach = prefixesToMpReach(message);
309         } else {
310             mpReach = MessageUtil.getMpReachNlri(attrs);
311         }
312         if (mpReach != null) {
313             this.ribWriter.updateRoutes(mpReach, nextHopToAttribute(attrs, mpReach));
314         }
315         final MpUnreachNlri mpUnreach;
316         if (message.getWithdrawnRoutes() != null) {
317             mpUnreach = prefixesToMpUnreach(message, isAnyNlriAnnounced);
318         } else {
319             mpUnreach = MessageUtil.getMpUnreachNlri(attrs);
320         }
321         final boolean endOfRib = BgpPeerUtil.isEndOfRib(message);
322         if (mpUnreach != null) {
323             if (endOfRib) {
324                 final TablesKey tablesKey = new TablesKey(mpUnreach.getAfi(), mpUnreach.getSafi());
325                 this.ribWriter.removeStaleRoutes(tablesKey);
326                 this.missingEOT.remove(tablesKey);
327                 handleGracefulEndOfRib();
328             } else {
329                 this.ribWriter.removeRoutes(mpUnreach);
330             }
331         } else if (endOfRib) {
332             this.ribWriter.removeStaleRoutes(IPV4_UCAST_TABLE_KEY);
333             this.missingEOT.remove(IPV4_UCAST_TABLE_KEY);
334             handleGracefulEndOfRib();
335         }
336     }
337
338     @Holding("this")
339     private void handleGracefulEndOfRib() {
340         if (isLocalRestarting()) {
341             if (this.missingEOT.isEmpty()) {
342                 createEffRibInWriter();
343                 this.effRibInWriter.init();
344                 registerPrefixesCounters(this.effRibInWriter, this.effRibInWriter);
345                 for (final TablesKey key : getAfiSafisAdvertized()) {
346                     createAdjRibOutListener(key, true);
347                 }
348                 setLocalRestartingState(false);
349                 setGracefulPreferences(false, Collections.emptySet());
350             }
351         }
352     }
353
354     @Override
355     public synchronized void onSessionUp(final BGPSession session) {
356         this.currentSession = session;
357         this.sessionUp = true;
358         this.bindingChain = this.rib.createPeerChain(this);
359         if (this.currentSession instanceof BGPSessionStateProvider) {
360             ((BGPSessionStateProvider) this.currentSession).registerMessagesCounter(this);
361         }
362         final GracefulRestartCapability advertisedGracefulRestartCapability =
363                 session.getAdvertisedGracefulRestartCapability();
364         final var advertisedTables = advertisedGracefulRestartCapability.getTables();
365         final var advertisedLLTables = session.getAdvertisedLlGracefulRestartCapability().getTables();
366
367         final List<AddressFamilies> addPathTablesType = session.getAdvertisedAddPathTableTypes();
368         final Set<BgpTableType> advertizedTableTypes = session.getAdvertisedTableTypes();
369         LOG.info("Session with peer {} went up with tables {} and Add Path tables {}", this.name,
370                 advertizedTableTypes, addPathTablesType);
371         final Set<TablesKey> setTables = advertizedTableTypes.stream().map(t -> new TablesKey(t.getAfi(), t.getSafi()))
372                 .collect(Collectors.toSet());
373         this.tables = ImmutableSet.copyOf(setTables);
374
375         this.addPathTableMaps = mapTableTypesFamilies(addPathTablesType);
376         final boolean restartingLocally = isLocalRestarting();
377         if (!restartingLocally) {
378             addBgp4Support();
379         }
380         if (!isRestartingGracefully()) {
381             this.rawIdentifier = InetAddresses.forString(session.getBgpId().getValue()).getAddress();
382             this.peerId = RouterIds.createPeerId(session.getBgpId());
383             this.peerIId = getInstanceIdentifier().child(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns
384                             .yang.bgp.rib.rev180329.bgp.rib.rib.Peer.class, new PeerKey(this.peerId));
385             this.peerPath = createPeerPath();
386             this.peerRibOutIId = peerIId.child(AdjRibOut.class);
387             this.trackerRegistration = this.rib.getPeerTracker().registerPeer(this);
388             createEffRibInWriter();
389             registerPrefixesCounters(this.effRibInWriter, this.effRibInWriter);
390
391             this.effRibInWriter.init();
392             this.ribWriter = this.ribWriter.transform(this.peerId, this.peerPath, this.rib.getRibSupportContext(),
393                     this.tables, this.addPathTableMaps);
394
395             if (this.rpcRegistry != null) {
396                 this.rpcRegistration = this.rpcRegistry.registerRpcImplementation(BgpPeerRpcService.class,
397                     new BgpPeerRpc(this, session, this.tables), ImmutableSet.of(this.rib.getInstanceIdentifier().child(
398                         org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib
399                         .Peer.class, new PeerKey(this.peerId))));
400             }
401         } else {
402             final Set<TablesKey> forwardingTables;
403             if (advertisedTables == null) {
404                 forwardingTables = Collections.emptySet();
405             } else {
406                 forwardingTables = advertisedTables.values().stream()
407                         .filter(table -> table.getAfiFlags() != null)
408                         .filter(table -> table.getAfiFlags().isForwardingState())
409                         .map(table -> new TablesKey(table.getAfi(), table.getSafi()))
410                         .collect(Collectors.toSet());
411             }
412             this.ribWriter.clearTables(Sets.difference(this.tables, forwardingTables));
413             if (restartingLocally) {
414                 this.effRibInWriter.close();
415                 this.peerRestartStopwatch = Stopwatch.createStarted();
416                 handleSelectionReferralTimer();
417                 this.missingEOT.addAll(this.tables);
418             }
419         }
420         if (advertisedTables == null || advertisedTables.isEmpty()) {
421             setAdvertizedGracefulRestartTableTypes(Collections.emptyList());
422         } else {
423             setAdvertizedGracefulRestartTableTypes(advertisedTables.values().stream()
424                     .map(t -> new TablesKey(t.getAfi(), t.getSafi())).collect(Collectors.toList()));
425         }
426         setAfiSafiGracefulRestartState(advertisedGracefulRestartCapability.getRestartTime().toJava(), false,
427             restartingLocally);
428
429         final Map<TablesKey, Integer> llTablesReceived;
430         if (advertisedLLTables != null) {
431             llTablesReceived = new HashMap<>();
432             for (var table : advertisedLLTables.values()) {
433                 llTablesReceived.put(new TablesKey(table.getAfi(), table.getSafi()),
434                     table.getLongLivedStaleTime().getValue().intValue());
435             }
436         } else {
437             llTablesReceived = Collections.emptyMap();
438         }
439         setAdvertizedLlGracefulRestartTableTypes(llTablesReceived);
440
441         if (!llTablesReceived.isEmpty()) {
442             llgrSupport = true;
443             // FIXME: propagate preserved tables
444         } else {
445             // FIXME: clear preserved tables
446             llgrSupport = false;
447         }
448
449         if (!restartingLocally) {
450             if (!setTables.contains(IPV4_UCAST_TABLE_KEY)) {
451                 createAdjRibOutListener(IPV4_UCAST_TABLE_KEY, false);
452             }
453             for (final TablesKey key : getAfiSafisAdvertized()) {
454                 createAdjRibOutListener(key, true);
455             }
456         }
457
458         // SpotBugs does not grok Optional.ifPresent() and thinks we are using unsynchronized access
459         final Optional<RevisedErrorHandlingSupport> errorHandling = this.bgpPeer.getErrorHandling();
460         if (errorHandling.isPresent()) {
461             this.currentSession.addDecoderConstraint(RevisedErrorHandlingSupport.class, errorHandling.get());
462         }
463     }
464
465     private boolean isRestartingGracefully() {
466         return isLocalRestarting() || isPeerRestarting();
467     }
468
469     private synchronized void createEffRibInWriter() {
470         this.effRibInWriter = new EffectiveRibInWriter(this, this.rib,
471             this.rib.createPeerDOMChain(this),
472             this.peerPath, this.tables, this.tableTypeRegistry,
473             this.rtMemberships,
474             this.rtCache);
475     }
476
477     //try to add a support for old-school BGP-4, if peer did not advertise IPv4-Unicast MP capability
478     @Holding("this")
479     private void addBgp4Support() {
480         if (!this.tables.contains(IPV4_UCAST_TABLE_KEY)) {
481             final HashSet<TablesKey> newSet = new HashSet<>(this.tables);
482             newSet.add(IPV4_UCAST_TABLE_KEY);
483             this.tables = ImmutableSet.copyOf(newSet);
484         }
485     }
486
487     @Holding("this")
488     private void createAdjRibOutListener(final TablesKey key, final boolean mpSupport) {
489         final RIBSupport<?, ?, ?, ?> ribSupport = this.rib.getRibSupportContext().getRIBSupport(key);
490
491         // not particularly nice
492         if (ribSupport != null && this.currentSession instanceof BGPSessionImpl) {
493             final ChannelOutputLimiter limiter = ((BGPSessionImpl) this.currentSession).getLimiter();
494             final AdjRibOutListener adjRibOut = AdjRibOutListener.create(this.peerId, key,
495                     this.rib.getYangRibId(), this.rib.getCodecsRegistry(), ribSupport,
496                     this.rib.getService(), limiter, mpSupport);
497             this.adjRibOutListenerSet.put(key, adjRibOut);
498             registerPrefixesSentCounter(key, adjRibOut);
499         }
500     }
501
502     @Override
503     public synchronized void onSessionDown(final BGPSession session, final Exception exc) {
504         if (exc.getMessage().equals(BGPSessionImpl.END_OF_INPUT)) {
505             LOG.info("Session with peer {} went down", this.name);
506         } else {
507             LOG.info("Session with peer {} went down", this.name, exc);
508         }
509         releaseConnectionGracefully();
510     }
511
512     @Override
513     public synchronized void onSessionTerminated(final BGPSession session, final BGPTerminationReason cause) {
514         LOG.info("Session with peer {} terminated: {}", this.name, cause);
515         releaseConnectionGracefully();
516     }
517
518     @Override
519     public String toString() {
520         return MoreObjects.toStringHelper(this).add("name", this.name).add("tables", this.tables).toString();
521     }
522
523     @Override
524     public synchronized FluentFuture<? extends CommitInfo> releaseConnection() {
525         LOG.info("Closing session with peer");
526         this.sessionUp = false;
527         this.adjRibOutListenerSet.values().forEach(AdjRibOutListener::close);
528         this.adjRibOutListenerSet.clear();
529         final FluentFuture<? extends CommitInfo> future;
530         if (!isRestartingGracefully()) {
531             future = terminateConnection();
532         } else {
533             final Set<TablesKey> gracefulTables = getGracefulTables();
534             this.ribWriter.storeStaleRoutes(gracefulTables);
535             future = this.ribWriter.clearTables(Sets.difference(this.tables, gracefulTables));
536             if (isPeerRestarting()) {
537                 this.peerRestartStopwatch = Stopwatch.createStarted();
538                 handleRestartTimer();
539             }
540         }
541         releaseBindingChain();
542
543         closeSession();
544         return future;
545     }
546
547     @Holding("this")
548     @SuppressWarnings("checkstyle:illegalCatch")
549     private void closeSession() {
550         if (this.currentSession != null) {
551             try {
552                 if (isRestartingGracefully()) {
553                     this.currentSession.closeWithoutMessage();
554                 } else {
555                     this.currentSession.close();
556                 }
557             } catch (final Exception e) {
558                 LOG.warn("Error closing session with peer", e);
559             }
560             this.currentSession = null;
561         }
562     }
563
564     private Set<TablesKey> getGracefulTables() {
565         return this.tables.stream()
566                 .filter(this::isGracefulRestartReceived)
567                 .filter(this::isGracefulRestartAdvertized)
568                 .collect(Collectors.toSet());
569     }
570
571     private synchronized FluentFuture<? extends CommitInfo> terminateConnection() {
572         final FluentFuture<? extends CommitInfo> future;
573         if (this.trackerRegistration != null) {
574             this.trackerRegistration.close();
575             this.trackerRegistration = null;
576         }
577         if (this.rpcRegistration != null) {
578             this.rpcRegistration.close();
579         }
580         this.ribWriter.releaseChain();
581
582         if (this.effRibInWriter != null) {
583             this.effRibInWriter.close();
584         }
585         this.tables = ImmutableSet.of();
586         this.addPathTableMaps = Collections.emptyMap();
587         future = removePeer(this.peerPath);
588         resetState();
589
590         return future;
591     }
592
593     /**
594      * If Graceful Restart Timer expires, remove all routes advertised by peer.
595      */
596     private synchronized void handleRestartTimer() {
597         if (!isPeerRestarting()) {
598             return;
599         }
600
601         final long peerRestartTimeNanos = TimeUnit.SECONDS.toNanos(getPeerRestartTime());
602         final long elapsedNanos = this.peerRestartStopwatch.elapsed(TimeUnit.NANOSECONDS);
603         if (elapsedNanos >= peerRestartTimeNanos) {
604             setAfiSafiGracefulRestartState(0, false, false);
605             onSessionTerminated(this.currentSession, new BGPTerminationReason(BGPError.HOLD_TIMER_EXPIRED));
606         }
607
608         currentSession.schedule(this::handleRestartTimer, peerRestartTimeNanos - elapsedNanos, TimeUnit.NANOSECONDS);
609     }
610
611     private synchronized void handleSelectionReferralTimer() {
612         if (!isLocalRestarting()) {
613             return;
614         }
615
616         final long referalTimerNanos = TimeUnit.SECONDS.toNanos(this.currentSelectionDeferralTimerSeconds);
617         final long elapsedNanos = this.peerRestartStopwatch.elapsed(TimeUnit.NANOSECONDS);
618         if (elapsedNanos >= referalTimerNanos) {
619             this.missingEOT.clear();
620             handleGracefulEndOfRib();
621         }
622         currentSession.schedule(this::handleSelectionReferralTimer, referalTimerNanos - elapsedNanos,
623             TimeUnit.NANOSECONDS);
624     }
625
626     private void releaseConnectionGracefully() {
627         if (getPeerRestartTime() > 0) {
628             setRestartingState();
629         }
630         releaseConnection();
631     }
632
633     @SuppressFBWarnings("IS2_INCONSISTENT_SYNC")
634     @Override
635     public SendReceive getSupportedAddPathTables(final TablesKey tableKey) {
636         return this.addPathTableMaps.get(tableKey);
637     }
638
639     @Override
640     public boolean supportsTable(final TablesKey tableKey) {
641         return this.sessionUp && getAfiSafisAdvertized().contains(tableKey);
642     }
643
644     @Override
645     public KeyedInstanceIdentifier<Tables, TablesKey> getRibOutIId(final TablesKey tablesKey) {
646         return this.tablesIId.getUnchecked(tablesKey);
647     }
648
649     @Override
650     public synchronized void onTransactionChainFailed(final DOMTransactionChain chain,
651             final DOMDataTreeTransaction transaction, final Throwable cause) {
652         LOG.error("Transaction domChain failed.", cause);
653         releaseConnection();
654     }
655
656     @Override
657     public synchronized void onTransactionChainFailed(final TransactionChain chain, final Transaction transaction,
658             final Throwable cause) {
659         LOG.error("Transaction domChain failed.", cause);
660         releaseConnection();
661     }
662
663     @Override
664     public synchronized void markUptodate(final TablesKey tablesKey) {
665         this.ribWriter.markTableUptodate(tablesKey);
666     }
667
668     @Override
669     public synchronized BGPSessionState getBGPSessionState() {
670         if (this.currentSession instanceof BGPSessionStateProvider) {
671             return ((BGPSessionStateProvider) this.currentSession).getBGPSessionState();
672         }
673         return null;
674     }
675
676     @Override
677     public synchronized BGPTimersState getBGPTimersState() {
678         if (this.currentSession instanceof BGPSessionStateProvider) {
679             return ((BGPSessionStateProvider) this.currentSession).getBGPTimersState();
680         }
681         return null;
682     }
683
684     @Override
685     public synchronized BGPTransportState getBGPTransportState() {
686         if (this.currentSession instanceof BGPSessionStateProvider) {
687             return ((BGPSessionStateProvider) this.currentSession).getBGPTransportState();
688         }
689         return null;
690     }
691
692     @Override
693     public List<RouteTarget> getMemberships() {
694         return this.rtMemberships;
695     }
696
697     @Override
698     public synchronized ListenableFuture<?> restartGracefully(final long selectionDeferralTimerSeconds) {
699         final Set<TablesKey> tablesToPreserve = getGracefulTables();
700         if (tablesToPreserve == null || tablesToPreserve.isEmpty()) {
701             LOG.info("Peer {} is not capable of graceful restart or have no matching graceful tables.", this.peerId);
702             return Futures.immediateFailedFuture(new UnsupportedOperationException(
703                     "Peer is not capable of graceful restart"));
704         }
705         setGracefulPreferences(true, tablesToPreserve);
706         this.currentSelectionDeferralTimerSeconds = selectionDeferralTimerSeconds;
707         setLocalRestartingState(true);
708         return releaseConnection();
709     }
710
711     @Override
712     boolean supportsLLGR() {
713         return this.llgrSupport;
714     }
715
716     private synchronized void setGracefulPreferences(final boolean localRestarting,
717                                                      final Set<TablesKey> preservedTables) {
718         final Set<TablesKey> gracefulTables = this.tables.stream()
719                 .filter(this::isGracefulRestartAdvertized)
720                 .collect(Collectors.toSet());
721         final BgpParameters bgpParameters = GracefulRestartUtil.getGracefulBgpParameters(
722                 this.bgpPeer.getBgpFixedCapabilities(), gracefulTables, preservedTables,
723                 this.bgpPeer.getGracefulRestartTimer(), localRestarting, Collections.emptySet());
724         final BGPSessionPreferences oldPrefs = this.rib.getDispatcher().getBGPPeerRegistry()
725                 .getPeerPreferences(getNeighborAddress());
726         final BGPSessionPreferences newPrefs = new BGPSessionPreferences(
727                 oldPrefs.getMyAs(),
728                 oldPrefs.getHoldTime(),
729                 oldPrefs.getBgpId(),
730                 oldPrefs.getExpectedRemoteAs(),
731                 Collections.singletonList(bgpParameters),
732                 oldPrefs.getMd5Password());
733         this.rib.getDispatcher().getBGPPeerRegistry()
734                 .updatePeerPreferences(getNeighborAddress(), newPrefs);
735     }
736 }