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