Refactor AbstractPeer initialization
[bgpcep.git] / bgp / rib-impl / src / main / java / org / opendaylight / protocol / bgp / rib / impl / BGPPeer.java
index a14f54c6012c79438ae2250b2a5be38646474283..d62e6498cc8dfb3a9b09d2034a13f4c74c6b4544 100644 (file)
@@ -8,16 +8,21 @@
 package org.opendaylight.protocol.bgp.rib.impl;
 
 import static java.util.Objects.requireNonNull;
 package org.opendaylight.protocol.bgp.rib.impl;
 
 import static java.util.Objects.requireNonNull;
+import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.ADJRIBOUT_NID;
+import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.TABLES_NID;
 
 import com.google.common.base.MoreObjects;
 
 import com.google.common.base.MoreObjects;
-import com.google.common.base.Objects;
+import com.google.common.base.Stopwatch;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.net.InetAddresses;
+import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.FluentFuture;
 import com.google.common.util.concurrent.FluentFuture;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import java.util.ArrayList;
 import java.util.Collections;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -25,20 +30,25 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
-import javax.annotation.concurrent.GuardedBy;
-import org.opendaylight.controller.md.sal.common.api.data.AsyncTransaction;
-import org.opendaylight.controller.md.sal.common.api.data.TransactionChain;
-import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.RoutedRpcRegistration;
-import org.opendaylight.controller.sal.binding.api.RpcProviderRegistry;
+import org.checkerframework.checker.lock.qual.GuardedBy;
+import org.checkerframework.checker.lock.qual.Holding;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.binding.api.RpcProviderService;
 import org.opendaylight.mdsal.common.api.CommitInfo;
 import org.opendaylight.protocol.bgp.openconfig.spi.BGPTableTypeRegistryConsumer;
 import org.opendaylight.protocol.bgp.parser.BGPDocumentedException;
 import org.opendaylight.protocol.bgp.parser.BGPError;
 import org.opendaylight.protocol.bgp.parser.impl.message.update.LocalPreferenceAttributeParser;
 import org.opendaylight.protocol.bgp.parser.spi.MessageUtil;
 import org.opendaylight.mdsal.common.api.CommitInfo;
 import org.opendaylight.protocol.bgp.openconfig.spi.BGPTableTypeRegistryConsumer;
 import org.opendaylight.protocol.bgp.parser.BGPDocumentedException;
 import org.opendaylight.protocol.bgp.parser.BGPError;
 import org.opendaylight.protocol.bgp.parser.impl.message.update.LocalPreferenceAttributeParser;
 import org.opendaylight.protocol.bgp.parser.spi.MessageUtil;
+import org.opendaylight.protocol.bgp.parser.spi.RevisedErrorHandlingSupport;
+import org.opendaylight.protocol.bgp.rib.impl.config.BgpPeer;
+import org.opendaylight.protocol.bgp.rib.impl.config.GracefulRestartUtil;
+import org.opendaylight.protocol.bgp.rib.impl.spi.BGPSessionPreferences;
 import org.opendaylight.protocol.bgp.rib.impl.spi.RIB;
 import org.opendaylight.protocol.bgp.rib.impl.state.BGPSessionStateProvider;
 import org.opendaylight.protocol.bgp.rib.spi.BGPSession;
 import org.opendaylight.protocol.bgp.rib.impl.spi.RIB;
 import org.opendaylight.protocol.bgp.rib.impl.state.BGPSessionStateProvider;
 import org.opendaylight.protocol.bgp.rib.spi.BGPSession;
@@ -49,48 +59,46 @@ import org.opendaylight.protocol.bgp.rib.spi.RouterIds;
 import org.opendaylight.protocol.bgp.rib.spi.state.BGPSessionState;
 import org.opendaylight.protocol.bgp.rib.spi.state.BGPTimersState;
 import org.opendaylight.protocol.bgp.rib.spi.state.BGPTransportState;
 import org.opendaylight.protocol.bgp.rib.spi.state.BGPSessionState;
 import org.opendaylight.protocol.bgp.rib.spi.state.BGPTimersState;
 import org.opendaylight.protocol.bgp.rib.spi.state.BGPTransportState;
-import org.opendaylight.protocol.bgp.route.targetcontrain.spi.ClientRouteTargetContrainCache;
-import org.opendaylight.protocol.concepts.AbstractRegistration;
 import org.opendaylight.protocol.util.Ipv4Util;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.AsNumber;
 import org.opendaylight.protocol.util.Ipv4Util;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.AsNumber;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddressNoZone;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev180329.ipv4.prefixes.DestinationIpv4Builder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev180329.ipv4.prefixes.destination.ipv4.Ipv4Prefixes;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev180329.ipv4.prefixes.destination.ipv4.Ipv4PrefixesBuilder;
 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;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev180329.ipv4.prefixes.DestinationIpv4Builder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev180329.ipv4.prefixes.destination.ipv4.Ipv4Prefixes;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev180329.ipv4.prefixes.destination.ipv4.Ipv4PrefixesBuilder;
 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;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.Update;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.path.attributes.Attributes;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.path.attributes.AttributesBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.update.message.Nlri;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.Update;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.open.message.BgpParameters;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.path.attributes.Attributes;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.path.attributes.AttributesBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.update.message.Nlri;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.BgpAddPathTableType;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.BgpTableType;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.RouteRefresh;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.SendReceive;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.BgpAddPathTableType;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.BgpTableType;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.RouteRefresh;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.SendReceive;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.attributes.reach.MpReachNlri;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.attributes.reach.MpReachNlriBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.attributes.reach.mp.reach.nlri.AdvertizedRoutesBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.attributes.unreach.MpUnreachNlri;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.attributes.unreach.MpUnreachNlriBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.attributes.unreach.mp.unreach.nlri.WithdrawnRoutesBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.mp.capabilities.GracefulRestartCapability;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.mp.capabilities.add.path.capability.AddressFamilies;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.mp.capabilities.add.path.capability.AddressFamilies;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.update.attributes.MpReachNlri;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.update.attributes.MpReachNlriBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.update.attributes.MpUnreachNlri;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.update.attributes.MpUnreachNlriBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.update.attributes.mp.reach.nlri.AdvertizedRoutesBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.update.attributes.mp.unreach.nlri.WithdrawnRoutesBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.peer.rpc.rev180329.BgpPeerRpcService;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.peer.rpc.rev180329.PeerContext;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.peer.rpc.rev180329.ResetSession;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.peer.rpc.rev180329.RestartGracefully;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.peer.rpc.rev180329.RouteRefreshRequest;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.PeerRole;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.PeerKey;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.PeerRole;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.PeerKey;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.peer.AdjRibOut;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.Tables;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.TablesKey;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.TablesKey;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.route.target.constrain.rev180618.route.target.constrain.routes.route.target.constrain.routes.RouteTargetConstrainRoute;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.AddressFamily;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.ClusterIdentifier;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.Ipv4AddressFamily;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.RouteTarget;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.SubsequentAddressFamily;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.UnicastSubsequentAddressFamily;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev200120.ClusterIdentifier;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev200120.Ipv4AddressFamily;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev200120.RouteTarget;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev200120.UnicastSubsequentAddressFamily;
+import org.opendaylight.yangtools.concepts.Registration;
 import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
 import org.opendaylight.yangtools.yang.binding.Notification;
 import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
 import org.opendaylight.yangtools.yang.binding.Notification;
+import org.opendaylight.yangtools.yang.common.Empty;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -100,67 +108,75 @@ import org.slf4j.LoggerFactory;
  */
 public class BGPPeer extends AbstractPeer implements BGPSessionListener {
     private static final Logger LOG = LoggerFactory.getLogger(BGPPeer.class);
  */
 public class BGPPeer extends AbstractPeer implements BGPSessionListener {
     private static final Logger LOG = LoggerFactory.getLogger(BGPPeer.class);
+    private static final TablesKey IPV4_UCAST_TABLE_KEY =
+        new TablesKey(Ipv4AddressFamily.VALUE, UnicastSubsequentAddressFamily.VALUE);
 
 
-    private Set<TablesKey> tables = Collections.emptySet();
     private final RIB rib;
     private final RIB rib;
+
+    // FIXME: Alright, this right here is a ton of state which has intertwined initialization and dependencies Split
+    //        these out into separate behavior objects. This also has relationship with state in AbstractPeer -- which
+    //        hints at an obvious layer of indirection. Yeah, yeah, we can always add one of those, but the point
+    //        is that this class is a mutable meeting point, whereas the behaviour has captured invariants.
+    private final LoadingCache<NodeIdentifierWithPredicates, YangInstanceIdentifier> tablesIId =
+        CacheBuilder.newBuilder().build(new CacheLoader<NodeIdentifierWithPredicates, YangInstanceIdentifier>() {
+            @Override
+            public YangInstanceIdentifier load(final NodeIdentifierWithPredicates key) {
+                return peerRibOutIId.node(TABLES_NID).node(key).toOptimized();
+            }
+        });
+
+    private ImmutableSet<TablesKey> tables = ImmutableSet.of();
     private final Map<TablesKey, AdjRibOutListener> adjRibOutListenerSet = new HashMap<>();
     private final List<RouteTarget> rtMemberships = new ArrayList<>();
     private final Map<TablesKey, AdjRibOutListener> adjRibOutListenerSet = new HashMap<>();
     private final List<RouteTarget> rtMemberships = new ArrayList<>();
-    private final RpcProviderRegistry rpcRegistry;
+    private final RpcProviderService rpcRegistry;
     private final BGPTableTypeRegistryConsumer tableTypeRegistry;
     private final BGPTableTypeRegistryConsumer tableTypeRegistry;
-    private InstanceIdentifier<AdjRibOut> peerRibOutIId;
+    private final BgpPeer bgpPeer;
+
+    // FIXME: This should be a constant co-located with ApplicationPeer.peerId
+    private YangInstanceIdentifier peerRibOutIId;
     @GuardedBy("this")
     @GuardedBy("this")
-    private AbstractRegistration trackerRegistration;
-    private final LoadingCache<TablesKey, KeyedInstanceIdentifier<Tables, TablesKey>> tablesIId
-            = CacheBuilder.newBuilder()
-            .build(new CacheLoader<TablesKey, KeyedInstanceIdentifier<Tables, TablesKey>>() {
-                @Override
-                public KeyedInstanceIdentifier<Tables, TablesKey> load(final TablesKey tablesKey) {
-                    return BGPPeer.this.peerRibOutIId.child(Tables.class, tablesKey);
-                }
-            });
+    private Registration trackerRegistration;
 
     @GuardedBy("this")
 
     @GuardedBy("this")
-    private BGPSession session;
+    private BGPSession currentSession;
     @GuardedBy("this")
     private AdjRibInWriter ribWriter;
     @GuardedBy("this")
     private EffectiveRibInWriter effRibInWriter;
     @GuardedBy("this")
     private AdjRibInWriter ribWriter;
     @GuardedBy("this")
     private EffectiveRibInWriter effRibInWriter;
-    private RoutedRpcRegistration<BgpPeerRpcService> rpcRegistration;
-    private Map<TablesKey, SendReceive> addPathTableMaps = Collections.emptyMap();
+    private Registration rpcRegistration;
+    private ImmutableMap<TablesKey, SendReceive> addPathTableMaps = ImmutableMap.of();
+    // FIXME: This should be a constant co-located with ApplicationPeer.peerId
     private YangInstanceIdentifier peerPath;
     private YangInstanceIdentifier peerPath;
+    // FIXME: This is for supportsTable() -- a trivial behavior thing, where 'peer-down' type states always return false
     private boolean sessionUp;
     private boolean sessionUp;
+    private boolean llgrSupport;
+    private Stopwatch peerRestartStopwatch;
+    private long currentSelectionDeferralTimerSeconds;
+    private final List<TablesKey> missingEOT = new ArrayList<>();
 
     public BGPPeer(
             final BGPTableTypeRegistryConsumer tableTypeRegistry,
 
     public BGPPeer(
             final BGPTableTypeRegistryConsumer tableTypeRegistry,
-            final IpAddress neighborAddress,
+            final IpAddressNoZone neighborAddress,
             final String peerGroupName,
             final RIB rib,
             final PeerRole role,
             final ClusterIdentifier clusterId,
             final AsNumber localAs,
             final String peerGroupName,
             final RIB rib,
             final PeerRole role,
             final ClusterIdentifier clusterId,
             final AsNumber localAs,
-            final RpcProviderRegistry rpcRegistry,
+            final RpcProviderService rpcRegistry,
             final Set<TablesKey> afiSafisAdvertized,
             final Set<TablesKey> afiSafisAdvertized,
-            final Set<TablesKey> afiSafisGracefulAdvertized) {
-        super(rib, Ipv4Util.toStringIP(neighborAddress), peerGroupName, role, clusterId,
-                localAs, neighborAddress, afiSafisAdvertized, afiSafisGracefulAdvertized);
+            final Set<TablesKey> afiSafisGracefulAdvertized,
+            final Map<TablesKey, Integer> llGracefulTablesAdvertised,
+            final BgpPeer bgpPeer) {
+        super(rib, Ipv4Util.toStringIP(neighborAddress), peerGroupName, role, clusterId, localAs, neighborAddress,
+            afiSafisAdvertized, afiSafisGracefulAdvertized, llGracefulTablesAdvertised);
         this.tableTypeRegistry = requireNonNull(tableTypeRegistry);
         this.rib = requireNonNull(rib);
         this.rpcRegistry = rpcRegistry;
         this.tableTypeRegistry = requireNonNull(tableTypeRegistry);
         this.rib = requireNonNull(rib);
         this.rpcRegistry = rpcRegistry;
-    }
+        this.bgpPeer = bgpPeer;
 
 
-    BGPPeer(
-            final BGPTableTypeRegistryConsumer tableTypeRegistry,
-            final IpAddress neighborAddress,
-            final RIB rib,
-            final PeerRole role,
-            final RpcProviderRegistry rpcRegistry,
-            final Set<TablesKey> afiSafisAdvertized,
-            final Set<TablesKey> afiSafisGracefulAdvertized) {
-        this(tableTypeRegistry, neighborAddress, null, rib, role, null, null, rpcRegistry,
-                afiSafisAdvertized, afiSafisGracefulAdvertized);
+        createDomChain();
     }
 
     }
 
-
     private static Attributes nextHopToAttribute(final Attributes attrs, final MpReachNlri mpReach) {
         if (attrs.getCNextHop() == null && mpReach.getCNextHop() != null) {
             final AttributesBuilder attributesBuilder = new AttributesBuilder(attrs);
     private static Attributes nextHopToAttribute(final Attributes attrs, final MpReachNlri mpReach) {
         if (attrs.getCNextHop() == null && mpReach.getCNextHop() != null) {
             final AttributesBuilder attributesBuilder = new AttributesBuilder(attrs);
@@ -180,11 +196,14 @@ public class BGPPeer extends AbstractPeer implements BGPSessionListener {
         final List<Ipv4Prefixes> prefixes = message.getNlri().stream()
                 .map(n -> new Ipv4PrefixesBuilder().setPrefix(n.getPrefix()).setPathId(n.getPathId()).build())
                 .collect(Collectors.toList());
         final List<Ipv4Prefixes> prefixes = message.getNlri().stream()
                 .map(n -> new Ipv4PrefixesBuilder().setPrefix(n.getPrefix()).setPathId(n.getPathId()).build())
                 .collect(Collectors.toList());
-        final MpReachNlriBuilder b = new MpReachNlriBuilder().setAfi(Ipv4AddressFamily.class).setSafi(
-                UnicastSubsequentAddressFamily.class).setAdvertizedRoutes(
-                new AdvertizedRoutesBuilder().setDestinationType(
-                        new DestinationIpv4CaseBuilder().setDestinationIpv4(
-                                new DestinationIpv4Builder().setIpv4Prefixes(prefixes).build()).build()).build());
+        final MpReachNlriBuilder b = new MpReachNlriBuilder()
+            .setAfi(Ipv4AddressFamily.VALUE)
+            .setSafi(UnicastSubsequentAddressFamily.VALUE)
+            .setAdvertizedRoutes(new AdvertizedRoutesBuilder()
+                .setDestinationType(new DestinationIpv4CaseBuilder()
+                    .setDestinationIpv4(new DestinationIpv4Builder().setIpv4Prefixes(prefixes).build())
+                    .build())
+                .build());
         if (message.getAttributes() != null) {
             b.setCNextHop(message.getAttributes().getCNextHop());
         }
         if (message.getAttributes() != null) {
             b.setCNextHop(message.getAttributes().getCNextHop());
         }
@@ -204,64 +223,65 @@ public class BGPPeer extends AbstractPeer implements BGPSessionListener {
 
             Optional<Nlri> nlriAnounced = Optional.empty();
             if (isAnyNlriAnnounced) {
 
             Optional<Nlri> nlriAnounced = Optional.empty();
             if (isAnyNlriAnnounced) {
-                nlriAnounced = message.getNlri().stream().filter(n -> Objects.equal(n.getPrefix(), w.getPrefix())
-                        && Objects.equal(n.getPathId(), w.getPathId()))
+                nlriAnounced = message.getNlri().stream().filter(n -> Objects.equals(n.getPrefix(), w.getPrefix())
+                        && Objects.equals(n.getPathId(), w.getPathId()))
                         .findAny();
             }
             if (!nlriAnounced.isPresent()) {
                 prefixes.add(new Ipv4PrefixesBuilder().setPrefix(w.getPrefix()).setPathId(w.getPathId()).build());
             }
         });
                         .findAny();
             }
             if (!nlriAnounced.isPresent()) {
                 prefixes.add(new Ipv4PrefixesBuilder().setPrefix(w.getPrefix()).setPathId(w.getPathId()).build());
             }
         });
-        return new MpUnreachNlriBuilder().setAfi(Ipv4AddressFamily.class).setSafi(UnicastSubsequentAddressFamily.class)
+        return new MpUnreachNlriBuilder().setAfi(Ipv4AddressFamily.VALUE).setSafi(UnicastSubsequentAddressFamily.VALUE)
                 .setWithdrawnRoutes(new WithdrawnRoutesBuilder().setDestinationType(new org.opendaylight.yang.gen.v1
                         .urn.opendaylight.params.xml.ns.yang.bgp.inet.rev180329.update.attributes.mp.unreach.nlri
                         .withdrawn.routes.destination.type.DestinationIpv4CaseBuilder().setDestinationIpv4(
                 .setWithdrawnRoutes(new WithdrawnRoutesBuilder().setDestinationType(new org.opendaylight.yang.gen.v1
                         .urn.opendaylight.params.xml.ns.yang.bgp.inet.rev180329.update.attributes.mp.unreach.nlri
                         .withdrawn.routes.destination.type.DestinationIpv4CaseBuilder().setDestinationIpv4(
-                        new DestinationIpv4Builder().setIpv4Prefixes(prefixes).build()).build()).build()).build();
+                            new DestinationIpv4Builder().setIpv4Prefixes(prefixes).build()).build()).build()).build();
     }
 
     }
 
-    private static Map<TablesKey, SendReceive> mapTableTypesFamilies(final List<AddressFamilies> addPathTablesType) {
-        return ImmutableMap.copyOf(addPathTablesType.stream().collect(Collectors.toMap(af -> new TablesKey(af.getAfi(),
-                        af.getSafi()), BgpAddPathTableType::getSendReceive)));
+    private static ImmutableMap<TablesKey, SendReceive> mapTableTypesFamilies(
+            final List<AddressFamilies> addPathTablesType) {
+        return addPathTablesType.stream().collect(ImmutableMap.toImmutableMap(
+            af -> new TablesKey(af.getAfi(), af.getSafi()), BgpAddPathTableType::getSendReceive));
     }
 
     public synchronized void instantiateServiceInstance() {
     }
 
     public synchronized void instantiateServiceInstance() {
-        this.ribWriter = AdjRibInWriter.create(this.rib.getYangRibId(), this.peerRole, this);
+        createDomChain();
+        ribWriter = AdjRibInWriter.create(rib.getYangRibId(), getRole(), this);
         setActive(true);
     }
 
     @Override
     public synchronized FluentFuture<? extends CommitInfo> close() {
         setActive(true);
     }
 
     @Override
     public synchronized FluentFuture<? extends CommitInfo> close() {
-        final FluentFuture<? extends CommitInfo> future = releaseConnection();
+        final FluentFuture<? extends CommitInfo> future = releaseConnection(true);
         closeDomChain();
         setActive(false);
         return future;
     }
 
     @Override
         closeDomChain();
         setActive(false);
         return future;
     }
 
     @Override
-    public void onMessage(final BGPSession session, final Notification msg) throws BGPDocumentedException {
-        if (!(msg instanceof Update) && !(msg instanceof RouteRefresh)) {
-            LOG.info("Ignoring unhandled message class {}", msg.getClass());
-            return;
-        }
-        if (msg instanceof Update) {
-            onUpdateMessage((Update) msg);
+    public void onMessage(final BGPSession session, final Notification<?> msg) throws BGPDocumentedException {
+        if (msg instanceof Update update) {
+            onUpdateMessage(update);
+        } else if (msg instanceof RouteRefresh routeRefresh) {
+            onRouteRefreshMessage(routeRefresh);
         } else {
         } else {
-            onRouteRefreshMessage((RouteRefresh) msg);
+            LOG.info("Ignoring unhandled message class {}", msg.getClass());
         }
     }
 
     private void onRouteRefreshMessage(final RouteRefresh message) {
         }
     }
 
     private void onRouteRefreshMessage(final RouteRefresh message) {
-        final Class<? extends AddressFamily> rrAfi = message.getAfi();
-        final Class<? extends SubsequentAddressFamily> rrSafi = message.getSafi();
+        final var rrAfi = message.getAfi();
+        final var rrSafi = message.getSafi();
 
         final TablesKey key = new TablesKey(rrAfi, rrSafi);
 
         final TablesKey key = new TablesKey(rrAfi, rrSafi);
-        final AdjRibOutListener listener = this.adjRibOutListenerSet.get(key);
-        if (listener != null) {
-            listener.close();
-            this.adjRibOutListenerSet.remove(key);
-            createAdjRibOutListener(key, listener.isMpSupported());
-        } else {
-            LOG.info("Ignoring RouteRefresh message. Afi/Safi is not supported: {}, {}.", rrAfi, rrSafi);
+        synchronized (this) {
+            final AdjRibOutListener listener = adjRibOutListenerSet.remove(key);
+            if (listener != null) {
+                listener.close();
+                createAdjRibOutListener(key, listener.isMpSupported());
+            } else {
+                LOG.info("Ignoring RouteRefresh message. Afi/Safi is not supported: {}, {}.", rrAfi, rrSafi);
+            }
         }
     }
 
         }
     }
 
@@ -273,7 +293,7 @@ public class BGPPeer extends AbstractPeer implements BGPSessionListener {
     private void checkMandatoryAttributesPresence(final Update message) throws BGPDocumentedException {
         if (MessageUtil.isAnyNlriPresent(message)) {
             final Attributes attrs = message.getAttributes();
     private void checkMandatoryAttributesPresence(final Update message) throws BGPDocumentedException {
         if (MessageUtil.isAnyNlriPresent(message)) {
             final Attributes attrs = message.getAttributes();
-            if (this.peerRole == PeerRole.Ibgp && (attrs == null || attrs.getLocalPref() == null)) {
+            if (getRole() == PeerRole.Ibgp && (attrs == null || attrs.getLocalPref() == null)) {
                 throw new BGPDocumentedException(BGPError.MANDATORY_ATTR_MISSING_MSG + "LOCAL_PREF",
                         BGPError.WELL_KNOWN_ATTR_MISSING,
                         new byte[]{LocalPreferenceAttributeParser.TYPE});
                 throw new BGPDocumentedException(BGPError.MANDATORY_ATTR_MISSING_MSG + "LOCAL_PREF",
                         BGPError.WELL_KNOWN_ATTR_MISSING,
                         new byte[]{LocalPreferenceAttributeParser.TYPE});
@@ -300,215 +320,455 @@ public class BGPPeer extends AbstractPeer implements BGPSessionListener {
             mpReach = MessageUtil.getMpReachNlri(attrs);
         }
         if (mpReach != null) {
             mpReach = MessageUtil.getMpReachNlri(attrs);
         }
         if (mpReach != null) {
-            this.ribWriter.updateRoutes(mpReach, nextHopToAttribute(attrs, mpReach));
+            ribWriter.updateRoutes(mpReach, nextHopToAttribute(attrs, mpReach));
         }
         }
-        MpUnreachNlri mpUnreach;
+        final MpUnreachNlri mpUnreach;
         if (message.getWithdrawnRoutes() != null) {
             mpUnreach = prefixesToMpUnreach(message, isAnyNlriAnnounced);
         } else {
             mpUnreach = MessageUtil.getMpUnreachNlri(attrs);
         }
         if (message.getWithdrawnRoutes() != null) {
             mpUnreach = prefixesToMpUnreach(message, isAnyNlriAnnounced);
         } else {
             mpUnreach = MessageUtil.getMpUnreachNlri(attrs);
         }
+        final boolean endOfRib = BgpPeerUtil.isEndOfRib(message);
         if (mpUnreach != null) {
         if (mpUnreach != null) {
-            this.ribWriter.removeRoutes(mpUnreach);
+            if (endOfRib) {
+                final TablesKey tablesKey = new TablesKey(mpUnreach.getAfi(), mpUnreach.getSafi());
+                ribWriter.removeStaleRoutes(tablesKey);
+                missingEOT.remove(tablesKey);
+                handleGracefulEndOfRib();
+            } else {
+                ribWriter.removeRoutes(mpUnreach);
+            }
+        } else if (endOfRib) {
+            ribWriter.removeStaleRoutes(IPV4_UCAST_TABLE_KEY);
+            missingEOT.remove(IPV4_UCAST_TABLE_KEY);
+            handleGracefulEndOfRib();
+        }
+    }
+
+    @Holding("this")
+    private void handleGracefulEndOfRib() {
+        if (isLocalRestarting()) {
+            if (missingEOT.isEmpty()) {
+                createEffRibInWriter();
+                effRibInWriter.init();
+                registerPrefixesCounters(effRibInWriter, effRibInWriter);
+                for (final TablesKey key : getAfiSafisAdvertized()) {
+                    createAdjRibOutListener(key, true);
+                }
+                setLocalRestartingState(false);
+                setGracefulPreferences(false, Collections.emptySet());
+            }
         }
     }
 
     @Override
     public synchronized void onSessionUp(final BGPSession session) {
         }
     }
 
     @Override
     public synchronized void onSessionUp(final BGPSession session) {
-        this.session = session;
-        this.sessionUp = true;
-        this.bindingChain = this.rib.createPeerChain(this);
-        if (this.session instanceof BGPSessionStateProvider) {
-            ((BGPSessionStateProvider) this.session).registerMessagesCounter(this);
+        currentSession = session;
+        sessionUp = true;
+
+        final var chain = rib.createPeerDOMChain();
+        ribOutChain = chain;
+        chain.addCallback(new FutureCallback<Empty>() {
+            @Override
+            public void onSuccess(final Empty result) {
+                LOG.debug("RibOut transaction chain {} successful.", chain);
+            }
+
+            @Override
+            public void onFailure(final Throwable cause) {
+                onRibOutChainFailed(cause);
+            }
+        });
+
+        if (currentSession instanceof BGPSessionStateProvider stateProvider) {
+            stateProvider.registerMessagesCounter(this);
         }
         }
+        final GracefulRestartCapability advertisedGracefulRestartCapability =
+                session.getAdvertisedGracefulRestartCapability();
+        final var advertisedTables = advertisedGracefulRestartCapability.getTables();
+        final var advertisedLLTables = session.getAdvertisedLlGracefulRestartCapability().getTables();
 
         final List<AddressFamilies> addPathTablesType = session.getAdvertisedAddPathTableTypes();
         final Set<BgpTableType> advertizedTableTypes = session.getAdvertisedTableTypes();
 
         final List<AddressFamilies> addPathTablesType = session.getAdvertisedAddPathTableTypes();
         final Set<BgpTableType> advertizedTableTypes = session.getAdvertisedTableTypes();
-        final List<BgpTableType> advertizedGracefulRestartTableTypes = session.getAdvertisedGracefulRestartTableTypes();
-        LOG.info("Session with peer {} went up with tables {} and Add Path tables {}", this.name,
+        LOG.info("Session with peer {} went up with tables {} and Add Path tables {}", getName(),
                 advertizedTableTypes, addPathTablesType);
                 advertizedTableTypes, addPathTablesType);
-        this.rawIdentifier = InetAddresses.forString(session.getBgpId().getValue()).getAddress();
-        this.peerId = RouterIds.createPeerId(session.getBgpId());
-        final KeyedInstanceIdentifier<org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib
-                .rev180329.bgp.rib.rib.Peer, PeerKey> peerIId =
-                getInstanceIdentifier().child(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns
-                .yang.bgp.rib.rev180329.bgp.rib.rib.Peer.class, new PeerKey(this.peerId));
         final Set<TablesKey> setTables = advertizedTableTypes.stream().map(t -> new TablesKey(t.getAfi(), t.getSafi()))
                 .collect(Collectors.toSet());
         final Set<TablesKey> setTables = advertizedTableTypes.stream().map(t -> new TablesKey(t.getAfi(), t.getSafi()))
                 .collect(Collectors.toSet());
-        this.tables = ImmutableSet.copyOf(setTables);
-        this.effRibInWriter = new EffectiveRibInWriter(this, this.rib,
-                this.rib.createPeerChain(this),
-                peerIId, this.tables, this.tableTypeRegistry,
-                this.rtMemberships,
-                this.rtCache);
-        registerPrefixesCounters(this.effRibInWriter, this.effRibInWriter);
-        this.peerRibOutIId = peerIId.child(AdjRibOut.class);
-        this.effRibInWriter.init();
-        setAdvertizedGracefulRestartTableTypes(advertizedGracefulRestartTableTypes.stream()
-                .map(t -> new TablesKey(t.getAfi(), t.getSafi())).collect(Collectors.toList()));
-        this.addPathTableMaps = ImmutableMap.copyOf(mapTableTypesFamilies(addPathTablesType));
-        this.trackerRegistration = this.rib.getPeerTracker().registerPeer(this);
-
-        for (final TablesKey key : this.tables) {
-            createAdjRibOutListener(key, true);
-        }
-
-        addBgp4Support();
-
-        this.peerPath = createPeerPath();
-        this.ribWriter = this.ribWriter.transform(this.peerId, this.peerPath, this.rib.getRibSupportContext(),
-                this.tables, this.addPathTableMaps);
-
-        if (this.rpcRegistry != null) {
-            this.rpcRegistration = this.rpcRegistry.addRoutedRpcImplementation(BgpPeerRpcService.class,
-                    new BgpPeerRpc(this, session, this.tables));
+        tables = ImmutableSet.copyOf(setTables);
+
+        addPathTableMaps = mapTableTypesFamilies(addPathTablesType);
+        final boolean restartingLocally = isLocalRestarting();
+        if (!restartingLocally) {
+            addBgp4Support();
+        }
+        if (!isRestartingGracefully()) {
+            peerId = RouterIds.createPeerId(session.getBgpId());
+
             final KeyedInstanceIdentifier<org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib
             final KeyedInstanceIdentifier<org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib
-                    .rev180329.bgp.rib.rib.Peer, PeerKey> path = this.rib.getInstanceIdentifier()
-                    .child(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib
-                             .rib.Peer.class, new PeerKey(this.peerId));
-            this.rpcRegistration.registerPath(PeerContext.class, path);
+                .rev180329.bgp.rib.rib.Peer, PeerKey> peerIId = getInstanceIdentifier().child(org.opendaylight.yang.gen
+                    .v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.Peer.class,
+                    new PeerKey(peerId));
+            peerPath = createPeerPath(peerId);
+            peerRibOutIId = peerPath.node(ADJRIBOUT_NID);
+            trackerRegistration = rib.getPeerTracker().registerPeer(this);
+            createEffRibInWriter();
+            registerPrefixesCounters(effRibInWriter, effRibInWriter);
+
+            effRibInWriter.init();
+            ribWriter = ribWriter.transform(peerId, peerPath, rib.getRibSupportContext(),
+                    tables, addPathTableMaps);
+
+            if (rpcRegistry != null) {
+                final var bgpPeerHandler = new BgpPeerRpc(this, session, tables);
+                rpcRegistration = rpcRegistry.registerRpcImplementations(List.of(
+                    (ResetSession) bgpPeerHandler::resetSession,
+                    (RestartGracefully) bgpPeerHandler::restartGracefully,
+                    (RouteRefreshRequest) bgpPeerHandler::routeRefreshRequest), ImmutableSet.of(
+                        rib.getInstanceIdentifier().child(
+                            org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib
+                            .rib.Peer.class, new PeerKey(peerId))));
+            }
+        } else {
+            final Set<TablesKey> forwardingTables;
+            if (advertisedTables == null) {
+                forwardingTables = Collections.emptySet();
+            } else {
+                forwardingTables = advertisedTables.values().stream()
+                        .filter(table -> table.getAfiFlags() != null)
+                        .filter(table -> table.getAfiFlags().getForwardingState())
+                        .map(table -> new TablesKey(table.getAfi(), table.getSafi()))
+                        .collect(Collectors.toSet());
+            }
+            ribWriter.clearTables(Sets.difference(tables, forwardingTables));
+            if (restartingLocally) {
+                effRibInWriter.close();
+                peerRestartStopwatch = Stopwatch.createStarted();
+                handleSelectionReferralTimer();
+                missingEOT.addAll(tables);
+            }
+        }
+        if (advertisedTables == null || advertisedTables.isEmpty()) {
+            setAdvertizedGracefulRestartTableTypes(Collections.emptyList());
+        } else {
+            setAdvertizedGracefulRestartTableTypes(advertisedTables.values().stream()
+                    .map(t -> new TablesKey(t.getAfi(), t.getSafi())).collect(Collectors.toList()));
         }
         }
+        setAfiSafiGracefulRestartState(advertisedGracefulRestartCapability.getRestartTime().toJava(), false,
+            restartingLocally);
+
+        final Map<TablesKey, Integer> llTablesReceived;
+        if (advertisedLLTables != null) {
+            llTablesReceived = new HashMap<>();
+            for (var table : advertisedLLTables.values()) {
+                llTablesReceived.put(new TablesKey(table.getAfi(), table.getSafi()),
+                    table.getLongLivedStaleTime().getValue().intValue());
+            }
+        } else {
+            llTablesReceived = Collections.emptyMap();
+        }
+        setAdvertizedLlGracefulRestartTableTypes(llTablesReceived);
+
+        if (!llTablesReceived.isEmpty()) {
+            llgrSupport = true;
+            // FIXME: propagate preserved tables
+        } else {
+            // FIXME: clear preserved tables
+            llgrSupport = false;
+        }
+
+        if (!restartingLocally) {
+            if (!setTables.contains(IPV4_UCAST_TABLE_KEY)) {
+                createAdjRibOutListener(IPV4_UCAST_TABLE_KEY, false);
+            }
+            for (final TablesKey key : getAfiSafisAdvertized()) {
+                createAdjRibOutListener(key, true);
+            }
+        }
+
+        // SpotBugs does not grok Optional.ifPresent() and thinks we are using unsynchronized access
+        final Optional<RevisedErrorHandlingSupport> errorHandling = bgpPeer.getErrorHandling();
+        if (errorHandling.isPresent()) {
+            currentSession.addDecoderConstraint(RevisedErrorHandlingSupport.class, errorHandling.orElseThrow());
+        }
+    }
+
+    private boolean isRestartingGracefully() {
+        return isLocalRestarting() || isPeerRestarting();
+    }
+
+    private synchronized void createEffRibInWriter() {
+        final var chain = rib.createPeerDOMChain();
+        chain.addCallback(this);
+
+        effRibInWriter = new EffectiveRibInWriter(this, rib, chain, peerPath, tables, tableTypeRegistry, rtMemberships,
+            rtCache);
     }
 
     //try to add a support for old-school BGP-4, if peer did not advertise IPv4-Unicast MP capability
     }
 
     //try to add a support for old-school BGP-4, if peer did not advertise IPv4-Unicast MP capability
-    private synchronized void addBgp4Support() {
-        final TablesKey key = new TablesKey(Ipv4AddressFamily.class, UnicastSubsequentAddressFamily.class);
-        if (!this.tables.contains(key)) {
-            final HashSet<TablesKey> newSet = new HashSet<>(this.tables);
-            newSet.add(key);
-            this.tables = ImmutableSet.copyOf(newSet);
-            createAdjRibOutListener(key, false);
+    @Holding("this")
+    private void addBgp4Support() {
+        if (!tables.contains(IPV4_UCAST_TABLE_KEY)) {
+            final HashSet<TablesKey> newSet = new HashSet<>(tables);
+            newSet.add(IPV4_UCAST_TABLE_KEY);
+            tables = ImmutableSet.copyOf(newSet);
         }
     }
 
         }
     }
 
-    private synchronized void createAdjRibOutListener(final TablesKey key,
-            final boolean mpSupport) {
-        final RIBSupport<?, ?, ?, ?> ribSupport = this.rib.getRibSupportContext().getRIBSupport(key);
+    @Holding("this")
+    private void createAdjRibOutListener(final TablesKey key, final boolean mpSupport) {
+        final RIBSupport<?, ?> ribSupport = rib.getRibSupportContext().getRIBSupport(key);
 
         // not particularly nice
 
         // not particularly nice
-        if (ribSupport != null && this.session instanceof BGPSessionImpl) {
-            final ChannelOutputLimiter limiter = ((BGPSessionImpl) this.session).getLimiter();
-            final AdjRibOutListener adjRibOut = AdjRibOutListener.create(this.peerId, key,
-                    this.rib.getYangRibId(), this.rib.getCodecsRegistry(), ribSupport,
-                    this.rib.getService(), limiter, mpSupport);
-            this.adjRibOutListenerSet.put(key, adjRibOut);
+        if (ribSupport != null && currentSession instanceof BGPSessionImpl bgpSession) {
+            final AdjRibOutListener adjRibOut = AdjRibOutListener.create(peerId, rib.getYangRibId(),
+                rib.getCodecsRegistry(), ribSupport, rib.getService(), bgpSession.getLimiter(), mpSupport);
+            adjRibOutListenerSet.put(key, adjRibOut);
             registerPrefixesSentCounter(key, adjRibOut);
         }
     }
 
     @Override
             registerPrefixesSentCounter(key, adjRibOut);
         }
     }
 
     @Override
-    public synchronized void onSessionDown(final BGPSession session, final Exception e) {
-        if (e.getMessage().equals(BGPSessionImpl.END_OF_INPUT)) {
-            LOG.info("Session with peer {} went down", this.name);
+    public synchronized void onSessionDown(final BGPSession session, final Exception exc) {
+        if (exc.getMessage().equals(BGPSessionImpl.END_OF_INPUT)) {
+            LOG.info("Session with peer {} went down", getName());
         } else {
         } else {
-            LOG.info("Session with peer {} went down", this.name, e);
+            LOG.info("Session with peer {} went down", getName(), exc);
         }
         }
-        releaseConnection();
+        releaseConnectionGracefully();
     }
 
     @Override
     public synchronized void onSessionTerminated(final BGPSession session, final BGPTerminationReason cause) {
     }
 
     @Override
     public synchronized void onSessionTerminated(final BGPSession session, final BGPTerminationReason cause) {
-        LOG.info("Session with peer {} terminated: {}", this.name, cause);
-        releaseConnection();
+        LOG.info("Session with peer {} terminated: {}", getName(), cause);
+        releaseConnectionGracefully();
     }
 
     @Override
     public String toString() {
     }
 
     @Override
     public String toString() {
-        return MoreObjects.toStringHelper(this)
-                .add("name", this.name)
-                .add("tables", this.tables).toString();
-        }
+        return MoreObjects.toStringHelper(this).add("name", getName()).add("tables", tables).toString();
+    }
 
     @Override
     public synchronized FluentFuture<? extends CommitInfo> releaseConnection() {
 
     @Override
     public synchronized FluentFuture<? extends CommitInfo> releaseConnection() {
+        return releaseConnection(true);
+    }
+
+    /**
+     * On transaction chain failure, we don't want to wait for future.
+     *
+     * @param isWaitForSubmitted if true, wait for submitted future before closing binding chain. if false, don't wait.
+     */
+    @Holding("this")
+    private @NonNull FluentFuture<? extends CommitInfo> releaseConnection(final boolean isWaitForSubmitted) {
         LOG.info("Closing session with peer");
         LOG.info("Closing session with peer");
-        this.sessionUp = false;
-        this.adjRibOutListenerSet.values().forEach(AdjRibOutListener::close);
-        this.adjRibOutListenerSet.clear();
-        if (this.trackerRegistration != null) {
-            this.trackerRegistration.close();
-            this.trackerRegistration = null;
-        }
-        if (this.rpcRegistration != null) {
-            this.rpcRegistration.close();
+        sessionUp = false;
+        adjRibOutListenerSet.values().forEach(AdjRibOutListener::close);
+        adjRibOutListenerSet.clear();
+        final FluentFuture<? extends CommitInfo> future;
+        // FIXME: this is a typical example of something which should be handled by a behavior into which we have
+        //        transitioned way before this method is called. This really begs to be an abstract base class with
+        //        a 'clearTables' or similar callout
+        if (isRestartingGracefully()) {
+            final Set<TablesKey> gracefulTables = getGracefulTables();
+            ribWriter.storeStaleRoutes(gracefulTables);
+            future = ribWriter.clearTables(Sets.difference(tables, gracefulTables));
+            if (isPeerRestarting()) {
+                peerRestartStopwatch = Stopwatch.createStarted();
+                handleRestartTimer();
+            }
+        } else {
+            future = terminateConnection();
         }
         }
-        releaseBindingChain();
+        releaseRibOutChain(isWaitForSubmitted);
 
 
-        this.ribWriter.releaseChain();
-        // FIXME: BUG-196: support graceful
+        closeSession();
+        return future;
+    }
 
 
-        if (this.effRibInWriter != null) {
-            this.effRibInWriter.close();
-        }
-        this.tables = Collections.emptySet();
-        this.addPathTableMaps = Collections.emptyMap();
-        final FluentFuture<? extends CommitInfo> future = removePeer(this.peerPath);
-        if (this.session != null) {
+    @Holding("this")
+    @SuppressWarnings("checkstyle:illegalCatch")
+    private void closeSession() {
+        if (currentSession != null) {
             try {
             try {
-                this.session.close();
+                if (isRestartingGracefully()) {
+                    currentSession.closeWithoutMessage();
+                } else {
+                    currentSession.close();
+                }
             } catch (final Exception e) {
                 LOG.warn("Error closing session with peer", e);
             }
             } catch (final Exception e) {
                 LOG.warn("Error closing session with peer", e);
             }
-            this.session = null;
+            currentSession = null;
         }
         }
+    }
+
+    private Set<TablesKey> getGracefulTables() {
+        return tables.stream()
+                .filter(this::isGracefulRestartReceived)
+                .filter(this::isGracefulRestartAdvertized)
+                .collect(Collectors.toSet());
+    }
+
+    private synchronized FluentFuture<? extends CommitInfo> terminateConnection() {
+        if (trackerRegistration != null) {
+            trackerRegistration.close();
+            trackerRegistration = null;
+        }
+        if (rpcRegistration != null) {
+            rpcRegistration.close();
+        }
+        ribWriter.releaseChain();
+
+        if (effRibInWriter != null) {
+            effRibInWriter.close();
+        }
+        tables = ImmutableSet.of();
+        addPathTableMaps = ImmutableMap.of();
+        final var future = removePeer(peerPath);
         resetState();
         resetState();
+
         return future;
     }
 
         return future;
     }
 
+    /**
+     * If Graceful Restart Timer expires, remove all routes advertised by peer.
+     */
+    private synchronized void handleRestartTimer() {
+        if (!isPeerRestarting()) {
+            return;
+        }
+
+        final long peerRestartTimeNanos = TimeUnit.SECONDS.toNanos(getPeerRestartTime());
+        final long elapsedNanos = peerRestartStopwatch.elapsed(TimeUnit.NANOSECONDS);
+        if (elapsedNanos >= peerRestartTimeNanos) {
+            setAfiSafiGracefulRestartState(0, false, false);
+            onSessionTerminated(currentSession, new BGPTerminationReason(BGPError.HOLD_TIMER_EXPIRED));
+        }
+
+        currentSession.schedule(this::handleRestartTimer, peerRestartTimeNanos - elapsedNanos, TimeUnit.NANOSECONDS);
+    }
+
+    private synchronized void handleSelectionReferralTimer() {
+        if (!isLocalRestarting()) {
+            return;
+        }
+
+        final long referalTimerNanos = TimeUnit.SECONDS.toNanos(currentSelectionDeferralTimerSeconds);
+        final long elapsedNanos = peerRestartStopwatch.elapsed(TimeUnit.NANOSECONDS);
+        if (elapsedNanos >= referalTimerNanos) {
+            missingEOT.clear();
+            handleGracefulEndOfRib();
+        }
+        currentSession.schedule(this::handleSelectionReferralTimer, referalTimerNanos - elapsedNanos,
+            TimeUnit.NANOSECONDS);
+    }
+
+    @Holding("this")
+    private void releaseConnectionGracefully() {
+        if (getPeerRestartTime() > 0) {
+            setRestartingState();
+        }
+        releaseConnection(true);
+    }
+
     @SuppressFBWarnings("IS2_INCONSISTENT_SYNC")
     @Override
     public SendReceive getSupportedAddPathTables(final TablesKey tableKey) {
     @SuppressFBWarnings("IS2_INCONSISTENT_SYNC")
     @Override
     public SendReceive getSupportedAddPathTables(final TablesKey tableKey) {
-        return this.addPathTableMaps.get(tableKey);
+        return addPathTableMaps.get(tableKey);
     }
 
     @Override
     public boolean supportsTable(final TablesKey tableKey) {
     }
 
     @Override
     public boolean supportsTable(final TablesKey tableKey) {
-        return this.tables.contains(tableKey) && this.sessionUp;
+        return sessionUp && getAfiSafisAdvertized().contains(tableKey) && tables.contains(tableKey);
     }
 
     @Override
     }
 
     @Override
-    public KeyedInstanceIdentifier<Tables, TablesKey> getRibOutIId(final TablesKey tablesKey) {
-        return this.tablesIId.getUnchecked(tablesKey);
+    public YangInstanceIdentifier getRibOutIId(final NodeIdentifierWithPredicates tablekey) {
+        return tablesIId.getUnchecked(tablekey);
     }
 
     @Override
     }
 
     @Override
-    public synchronized void onTransactionChainFailed(final TransactionChain<?, ?> chain,
-            final AsyncTransaction<?, ?> transaction, final Throwable cause) {
+    public synchronized void onFailure(final Throwable cause) {
         LOG.error("Transaction domChain failed.", cause);
         LOG.error("Transaction domChain failed.", cause);
-        releaseConnection();
+        releaseConnection(true);
+    }
+
+    private synchronized void onRibOutChainFailed(final Throwable cause) {
+        LOG.error("RibOut transaction chain failed.", cause);
+        releaseConnection(false);
     }
 
     @Override
     public synchronized void markUptodate(final TablesKey tablesKey) {
     }
 
     @Override
     public synchronized void markUptodate(final TablesKey tablesKey) {
-        this.ribWriter.markTableUptodate(tablesKey);
+        ribWriter.markTableUptodate(tablesKey);
     }
 
     @Override
     public synchronized BGPSessionState getBGPSessionState() {
     }
 
     @Override
     public synchronized BGPSessionState getBGPSessionState() {
-        if (this.session instanceof BGPSessionStateProvider) {
-            return ((BGPSessionStateProvider) this.session).getBGPSessionState();
+        if (currentSession instanceof BGPSessionStateProvider stateProvider) {
+            return stateProvider.getBGPSessionState();
         }
         return null;
     }
 
     @Override
     public synchronized BGPTimersState getBGPTimersState() {
         }
         return null;
     }
 
     @Override
     public synchronized BGPTimersState getBGPTimersState() {
-        if (this.session instanceof BGPSessionStateProvider) {
-            return ((BGPSessionStateProvider) this.session).getBGPTimersState();
+        if (currentSession instanceof BGPSessionStateProvider stateProvider) {
+            return stateProvider.getBGPTimersState();
         }
         return null;
     }
 
     @Override
     public synchronized BGPTransportState getBGPTransportState() {
         }
         return null;
     }
 
     @Override
     public synchronized BGPTransportState getBGPTransportState() {
-        if (this.session instanceof BGPSessionStateProvider) {
-            return ((BGPSessionStateProvider) this.session).getBGPTransportState();
+        if (currentSession instanceof BGPSessionStateProvider stateProvider) {
+            return stateProvider.getBGPTransportState();
         }
         return null;
     }
 
     @Override
     public List<RouteTarget> getMemberships() {
         }
         return null;
     }
 
     @Override
     public List<RouteTarget> getMemberships() {
-        return this.rtMemberships;
+        return rtMemberships;
+    }
+
+    @Override
+    public synchronized ListenableFuture<?> restartGracefully(final long selectionDeferralTimerSeconds) {
+        final Set<TablesKey> tablesToPreserve = getGracefulTables();
+        if (tablesToPreserve == null || tablesToPreserve.isEmpty()) {
+            LOG.info("Peer {} is not capable of graceful restart or have no matching graceful tables.", peerId);
+            return Futures.immediateFailedFuture(new UnsupportedOperationException(
+                    "Peer is not capable of graceful restart"));
+        }
+        setGracefulPreferences(true, tablesToPreserve);
+        currentSelectionDeferralTimerSeconds = selectionDeferralTimerSeconds;
+        setLocalRestartingState(true);
+        return releaseConnection(true);
+    }
+
+    @Override
+    boolean supportsLLGR() {
+        return llgrSupport;
+    }
+
+    private synchronized void setGracefulPreferences(final boolean localRestarting,
+                                                     final Set<TablesKey> preservedTables) {
+        final Set<TablesKey> gracefulTables = tables.stream()
+                .filter(this::isGracefulRestartAdvertized)
+                .collect(Collectors.toSet());
+        final BgpParameters bgpParameters = GracefulRestartUtil.getGracefulBgpParameters(
+                bgpPeer.getBgpFixedCapabilities(), gracefulTables, preservedTables,
+                bgpPeer.getGracefulRestartTimer(), localRestarting, Collections.emptySet());
+        final BGPSessionPreferences oldPrefs = rib.getDispatcher().getBGPPeerRegistry()
+                .getPeerPreferences(getNeighborAddress());
+        final BGPSessionPreferences newPrefs = new BGPSessionPreferences(
+                oldPrefs.getMyAs(),
+                oldPrefs.getHoldTime(),
+                oldPrefs.getBgpId(),
+                oldPrefs.getExpectedRemoteAs(),
+                Collections.singletonList(bgpParameters),
+                oldPrefs.getMd5Password());
+        rib.getDispatcher().getBGPPeerRegistry()
+                .updatePeerPreferences(getNeighborAddress(), newPrefs);
     }
 }
     }
 }