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