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