Use instanceof patterns in BGPPeer
[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         final FluentFuture<? extends CommitInfo> future;
609         if (trackerRegistration != null) {
610             trackerRegistration.close();
611             trackerRegistration = null;
612         }
613         if (rpcRegistration != null) {
614             rpcRegistration.close();
615         }
616         ribWriter.releaseChain();
617
618         if (effRibInWriter != null) {
619             effRibInWriter.close();
620         }
621         tables = ImmutableSet.of();
622         addPathTableMaps = ImmutableMap.of();
623         future = removePeer(peerPath);
624         resetState();
625
626         return future;
627     }
628
629     /**
630      * If Graceful Restart Timer expires, remove all routes advertised by peer.
631      */
632     private synchronized void handleRestartTimer() {
633         if (!isPeerRestarting()) {
634             return;
635         }
636
637         final long peerRestartTimeNanos = TimeUnit.SECONDS.toNanos(getPeerRestartTime());
638         final long elapsedNanos = peerRestartStopwatch.elapsed(TimeUnit.NANOSECONDS);
639         if (elapsedNanos >= peerRestartTimeNanos) {
640             setAfiSafiGracefulRestartState(0, false, false);
641             onSessionTerminated(currentSession, new BGPTerminationReason(BGPError.HOLD_TIMER_EXPIRED));
642         }
643
644         currentSession.schedule(this::handleRestartTimer, peerRestartTimeNanos - elapsedNanos, TimeUnit.NANOSECONDS);
645     }
646
647     private synchronized void handleSelectionReferralTimer() {
648         if (!isLocalRestarting()) {
649             return;
650         }
651
652         final long referalTimerNanos = TimeUnit.SECONDS.toNanos(currentSelectionDeferralTimerSeconds);
653         final long elapsedNanos = peerRestartStopwatch.elapsed(TimeUnit.NANOSECONDS);
654         if (elapsedNanos >= referalTimerNanos) {
655             missingEOT.clear();
656             handleGracefulEndOfRib();
657         }
658         currentSession.schedule(this::handleSelectionReferralTimer, referalTimerNanos - elapsedNanos,
659             TimeUnit.NANOSECONDS);
660     }
661
662     @Holding("this")
663     private void releaseConnectionGracefully() {
664         if (getPeerRestartTime() > 0) {
665             setRestartingState();
666         }
667         releaseConnection(true);
668     }
669
670     @SuppressFBWarnings("IS2_INCONSISTENT_SYNC")
671     @Override
672     public SendReceive getSupportedAddPathTables(final TablesKey tableKey) {
673         return addPathTableMaps.get(tableKey);
674     }
675
676     @Override
677     public boolean supportsTable(final TablesKey tableKey) {
678         return sessionUp && getAfiSafisAdvertized().contains(tableKey) && tables.contains(tableKey);
679     }
680
681     @Override
682     public YangInstanceIdentifier getRibOutIId(final NodeIdentifierWithPredicates tablekey) {
683         return tablesIId.getUnchecked(tablekey);
684     }
685
686     @Override
687     public synchronized void onTransactionChainFailed(final DOMTransactionChain chain,
688             final DOMDataTreeTransaction transaction, final Throwable cause) {
689         LOG.error("Transaction domChain failed.", cause);
690         releaseConnection(true);
691     }
692
693     private synchronized void onRibOutChainFailed(final Throwable cause) {
694         LOG.error("RibOut transaction chain failed.", cause);
695         releaseConnection(false);
696     }
697
698     @Override
699     public synchronized void markUptodate(final TablesKey tablesKey) {
700         ribWriter.markTableUptodate(tablesKey);
701     }
702
703     @Override
704     public synchronized BGPSessionState getBGPSessionState() {
705         if (currentSession instanceof BGPSessionStateProvider stateProvider) {
706             return stateProvider.getBGPSessionState();
707         }
708         return null;
709     }
710
711     @Override
712     public synchronized BGPTimersState getBGPTimersState() {
713         if (currentSession instanceof BGPSessionStateProvider stateProvider) {
714             return stateProvider.getBGPTimersState();
715         }
716         return null;
717     }
718
719     @Override
720     public synchronized BGPTransportState getBGPTransportState() {
721         if (currentSession instanceof BGPSessionStateProvider stateProvider) {
722             return stateProvider.getBGPTransportState();
723         }
724         return null;
725     }
726
727     @Override
728     public List<RouteTarget> getMemberships() {
729         return rtMemberships;
730     }
731
732     @Override
733     public synchronized ListenableFuture<?> restartGracefully(final long selectionDeferralTimerSeconds) {
734         final Set<TablesKey> tablesToPreserve = getGracefulTables();
735         if (tablesToPreserve == null || tablesToPreserve.isEmpty()) {
736             LOG.info("Peer {} is not capable of graceful restart or have no matching graceful tables.", peerId);
737             return Futures.immediateFailedFuture(new UnsupportedOperationException(
738                     "Peer is not capable of graceful restart"));
739         }
740         setGracefulPreferences(true, tablesToPreserve);
741         currentSelectionDeferralTimerSeconds = selectionDeferralTimerSeconds;
742         setLocalRestartingState(true);
743         return releaseConnection(true);
744     }
745
746     @Override
747     boolean supportsLLGR() {
748         return llgrSupport;
749     }
750
751     private synchronized void setGracefulPreferences(final boolean localRestarting,
752                                                      final Set<TablesKey> preservedTables) {
753         final Set<TablesKey> gracefulTables = tables.stream()
754                 .filter(this::isGracefulRestartAdvertized)
755                 .collect(Collectors.toSet());
756         final BgpParameters bgpParameters = GracefulRestartUtil.getGracefulBgpParameters(
757                 bgpPeer.getBgpFixedCapabilities(), gracefulTables, preservedTables,
758                 bgpPeer.getGracefulRestartTimer(), localRestarting, Collections.emptySet());
759         final BGPSessionPreferences oldPrefs = rib.getDispatcher().getBGPPeerRegistry()
760                 .getPeerPreferences(getNeighborAddress());
761         final BGPSessionPreferences newPrefs = new BGPSessionPreferences(
762                 oldPrefs.getMyAs(),
763                 oldPrefs.getHoldTime(),
764                 oldPrefs.getBgpId(),
765                 oldPrefs.getExpectedRemoteAs(),
766                 Collections.singletonList(bgpParameters),
767                 oldPrefs.getMd5Password());
768         rib.getDispatcher().getBGPPeerRegistry()
769                 .updatePeerPreferences(getNeighborAddress(), newPrefs);
770     }
771 }