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