Bug 6622 - ClusterSingletonService registration race condition 18/47018/5
authorMilos Fabian <milfabia@cisco.com>
Mon, 17 Oct 2016 13:34:13 +0000 (15:34 +0200)
committerRobert Varga <nite@hq.sk>
Thu, 20 Oct 2016 23:32:27 +0000 (23:32 +0000)
This is a hot fix for a race condition issue which can happen during a service lifecycle.
An application which manages services in not aware when the unregistration is finished, hence
service close is followed by immendiate service registration which may fail consequently.
This condition is handled by reatempting service regsiration after 10 ms.

Also fixes RIBImpl - create TX chain in #instantiateServiceInstance and asynchronously clean operatoinal DS.

Patch https://git.opendaylight.org/gerrit/#/c/46761/ is depending on this changes.

Change-Id: I110badc8bfb482a5b7cb6545b882cda2d0c661ae
Signed-off-by: Milos Fabian <milfabia@cisco.com>
bgp/rib-impl/src/main/java/org/opendaylight/protocol/bgp/rib/impl/RIBImpl.java
bgp/rib-impl/src/main/java/org/opendaylight/protocol/bgp/rib/impl/config/RibImpl.java
bgp/rib-impl/src/test/java/org/opendaylight/protocol/bgp/rib/impl/config/RibImplTest.java
bgp/rib-spi/pom.xml
bgp/rib-spi/src/main/java/org/opendaylight/protocol/bgp/rib/spi/util/ClusterSingletonServiceRegistrationHelper.java [new file with mode: 0644]
bgp/rib-spi/src/test/java/org/opendaylight/protocol/bgp/rib/spi/util/ClusterSingletonServiceRegistrationHelperTest.java [new file with mode: 0644]
bgp/topology-provider/src/main/java/org/opendaylight/bgpcep/bgp/topology/provider/config/BgpTopologyDeployerImpl.java

index 4ddad0daedaa5ade4aebd7cd1847999917845973..f48740f25016de8513b241d704c6ab66903b64cb 100755 (executable)
@@ -13,10 +13,9 @@ import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.CheckedFuture;
 import com.google.common.util.concurrent.ListenableFuture;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -56,6 +55,7 @@ import org.opendaylight.protocol.bgp.rib.impl.stats.rib.impl.RIBImplRuntimeMXBea
 import org.opendaylight.protocol.bgp.rib.spi.ExportPolicyPeerTracker;
 import org.opendaylight.protocol.bgp.rib.spi.RIBExtensionConsumerContext;
 import org.opendaylight.protocol.bgp.rib.spi.RibSupportUtils;
+import org.opendaylight.protocol.bgp.rib.spi.util.ClusterSingletonServiceRegistrationHelper;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.AsNumber;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130919.BgpTableType;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.BgpRib;
@@ -91,9 +91,10 @@ public final class RIBImpl extends DefaultRibReference implements ClusterSinglet
     private static final Logger LOG = LoggerFactory.getLogger(RIBImpl.class);
     private static final QName RIB_ID_QNAME = QName.create(Rib.QNAME, "id").intern();
     private static final ContainerNode EMPTY_TABLE_ATTRIBUTES = ImmutableNodes.containerNode(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.tables.Attributes.QNAME);
+    private static final int MAX_REGISTRATION_ATTEMPTS = 10;
+    private static final int SLEEP_TIME = MAX_REGISTRATION_ATTEMPTS;
 
     private final BGPDispatcher dispatcher;
-    private final DOMTransactionChain domChain;
     private final AsNumber localAs;
     private final BgpId bgpIdentifier;
     private final Set<BgpTableType> localTables;
@@ -117,6 +118,8 @@ public final class RIBImpl extends DefaultRibReference implements ClusterSinglet
     private final RibId ribId;
     private final Map<TablesKey, ExportPolicyPeerTracker> exportPolicyPeerTrackerMap;
 
+    private DOMTransactionChain domChain;
+
     public RIBImpl(final ClusterSingletonServiceProvider provider, final RibId ribId, final AsNumber localAs, final BgpId localBgpId,
         final ClusterIdentifier clusterId, final RIBExtensionConsumerContext extensions, final BGPDispatcher dispatcher,
         final BindingCodecTreeFactory codecFactory, final DOMDataBroker domDataBroker, final List<BgpTableType> localTables,
@@ -125,7 +128,6 @@ public final class RIBImpl extends DefaultRibReference implements ClusterSinglet
         final BgpDeployer.WriteConfiguration configurationWriter) {
 
         super(InstanceIdentifier.create(BgpRib.class).child(Rib.class, new RibKey(Preconditions.checkNotNull(ribId))));
-        this.domChain = domDataBroker.createTransactionChain(this);
         this.localAs = Preconditions.checkNotNull(localAs);
         this.bgpIdentifier = Preconditions.checkNotNull(localBgpId);
         this.dispatcher = Preconditions.checkNotNull(dispatcher);
@@ -222,9 +224,6 @@ public final class RIBImpl extends DefaultRibReference implements ClusterSinglet
             this.registration.close();
             this.registration = null;
         }
-        if(this.domChain != null) {
-            this.domChain.close();
-        }
     }
 
     @Override
@@ -320,6 +319,7 @@ public final class RIBImpl extends DefaultRibReference implements ClusterSinglet
 
     @Override
     public void instantiateServiceInstance() {
+        this.domChain = this.domDataBroker.createTransactionChain(this);
         if(this.configurationWriter != null) {
             this.configurationWriter.apply();
         }
@@ -361,26 +361,22 @@ public final class RIBImpl extends DefaultRibReference implements ClusterSinglet
     @Override
     public ListenableFuture<Void> closeServiceInstance() {
         LOG.info("Close RIB Singleton Service {}", getIdentifier());
-        for (final LocRibWriter locRib : this.locRibs) {
-            try {
-                locRib.close();
-            } catch (final Exception e) {
-                LOG.warn("Could not close LocalRib reference: {}", locRib, e);
-            }
-        }
-        try {
-            final DOMDataWriteTransaction t = this.domChain.newWriteOnlyTransaction();
-            t.delete(LogicalDatastoreType.OPERATIONAL, getYangRibId());
-            t.submit().checkedGet();
-        } catch (final TransactionCommitFailedException e) {
-            LOG.warn("Failed to remove RIB instance {} from DS.", getYangRibId(), e);
-        }
+        this.locRibs.forEach(LocRibWriter::close);
+        this.locRibs.clear();
+
         this.renderStats.getLocRibRouteCounter().resetAll();
 
         if (this.configModuleTracker != null) {
             this.configModuleTracker.onInstanceClose();
         }
-        return Futures.immediateFuture(null);
+
+        final DOMDataWriteTransaction t = this.domChain.newWriteOnlyTransaction();
+        t.delete(LogicalDatastoreType.OPERATIONAL, getYangRibId());
+        final CheckedFuture<Void, TransactionCommitFailedException> cleanFuture = t.submit();
+
+        this.domChain.close();
+
+        return cleanFuture;
     }
 
     @Override
@@ -390,7 +386,8 @@ public final class RIBImpl extends DefaultRibReference implements ClusterSinglet
 
     @Override
     public ClusterSingletonServiceRegistration registerClusterSingletonService(final ClusterSingletonService clusterSingletonService) {
-        return this.provider.registerClusterSingletonService(clusterSingletonService);
+        return ClusterSingletonServiceRegistrationHelper.registerSingletonService(this.provider, clusterSingletonService, MAX_REGISTRATION_ATTEMPTS,
+                SLEEP_TIME);
     }
 
     @Override
index 6b591f25274b5b4fa171928eaf7b79c15700473b..603094a0be79ee4216c1dfe62e1d7120742b19a4 100644 (file)
@@ -37,7 +37,6 @@ import org.opendaylight.protocol.bgp.rib.impl.spi.ImportPolicyPeerTracker;
 import org.opendaylight.protocol.bgp.rib.impl.spi.RIB;
 import org.opendaylight.protocol.bgp.rib.impl.spi.RIBSupportContextRegistry;
 import org.opendaylight.protocol.bgp.rib.impl.stats.rib.impl.BGPRenderStats;
-import org.opendaylight.protocol.bgp.rib.spi.CacheDisconnectedPeers;
 import org.opendaylight.protocol.bgp.rib.spi.ExportPolicyPeerTracker;
 import org.opendaylight.protocol.bgp.rib.spi.RIBExtensionConsumerContext;
 import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.multiprotocol.rev151009.bgp.common.afi.safi.list.AfiSafi;
@@ -242,6 +241,6 @@ public final class RibImpl implements RIB, AutoCloseable {
 
     @Override
     public ClusterSingletonServiceRegistration registerClusterSingletonService(final ClusterSingletonService clusterSingletonService) {
-        return this.provider.registerClusterSingletonService(clusterSingletonService);
+        return this.ribImpl.registerClusterSingletonService(clusterSingletonService);
     }
 }
index 61384aac9576bfbacef6f29f9c7b0958dd25d6a5..207e6306f156d26251746cbfdd4bfa20e5a18959 100644 (file)
@@ -15,7 +15,6 @@ import static org.mockito.Matchers.anyList;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
-import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.primitives.Shorts;
 import java.util.ArrayList;
@@ -28,7 +27,6 @@ import org.mockito.Mockito;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
-import org.opendaylight.controller.md.sal.dom.api.DOMDataBrokerExtension;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeService;
 import org.opendaylight.controller.sal.core.api.model.SchemaService;
 import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTreeFactory;
@@ -39,8 +37,6 @@ import org.opendaylight.mdsal.singleton.common.api.ServiceGroupIdentifier;
 import org.opendaylight.protocol.bgp.mode.impl.add.all.paths.AllPathSelection;
 import org.opendaylight.protocol.bgp.openconfig.spi.BGPOpenConfigMappingService;
 import org.opendaylight.protocol.bgp.parser.BgpTableTypeImpl;
-import org.opendaylight.protocol.bgp.rib.DefaultRibReference;
-import org.opendaylight.protocol.bgp.rib.RibReference;
 import org.opendaylight.protocol.bgp.rib.impl.RIBImpl;
 import org.opendaylight.protocol.bgp.rib.spi.RIBExtensionConsumerContext;
 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
@@ -54,19 +50,13 @@ import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130919.BgpTableType;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.openconfig.extensions.rev160614.AfiSafi2;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.openconfig.extensions.rev160614.AfiSafi2Builder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.BgpRib;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.RibId;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.Rib;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.RibKey;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.TablesKey;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev130919.BgpId;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev130919.Ipv4AddressFamily;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev130919.UnicastSubsequentAddressFamily;
 import org.opendaylight.yangtools.concepts.ListenerRegistration;
 import org.opendaylight.yangtools.sal.binding.generator.impl.GeneratedClassLoadingStrategy;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
 import org.osgi.framework.ServiceRegistration;
@@ -101,14 +91,15 @@ public class RibImplTest extends AbstractConfig {
     @Mock
     private ServiceRegistration serviceRegistration;
 
+    @Override
     @Before
     public void setUp() throws Exception {
         super.setUp();
         Mockito.doAnswer(new Answer<ClusterSingletonServiceRegistration>() {
             @Override
             public ClusterSingletonServiceRegistration answer(final InvocationOnMock invocationOnMock) throws Throwable {
-                singletonService = (ClusterSingletonService) invocationOnMock.getArguments()[0];
-                return singletonServiceRegistration;
+                RibImplTest.this.singletonService = (ClusterSingletonService) invocationOnMock.getArguments()[0];
+                return RibImplTest.this.singletonServiceRegistration;
             }
         }).when(this.clusterSingletonServiceProvider).registerClusterSingletonService(any(ClusterSingletonService.class));
 
@@ -143,7 +134,6 @@ public class RibImplTest extends AbstractConfig {
         verify(this.mappingService).toPathSelectionMode(anyList());
         verify(this.mappingService).toTableTypes(anyList());
         verify(this.extension).getClassLoadingStrategy();
-        verify(this.domDataBroker).createTransactionChain(any(RIBImpl.class));
         verify(this.domDataBroker).getSupportedExtensions();
         verify(this.clusterSingletonServiceProvider).registerClusterSingletonService(any());
         verify(this.schemaService).registerSchemaContextListener(any(RIBImpl.class));
index cdd0768af0289ba81b971dde6c6a38ef1cd6930e..c10f2ff168e5f31785b4201678e5ce382b378913 100644 (file)
             <artifactId>org.osgi.core</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.opendaylight.mdsal</groupId>
+            <artifactId>mdsal-singleton-common-api</artifactId>
+        </dependency>
 
         <!-- Test dependencies -->
         <dependency>
diff --git a/bgp/rib-spi/src/main/java/org/opendaylight/protocol/bgp/rib/spi/util/ClusterSingletonServiceRegistrationHelper.java b/bgp/rib-spi/src/main/java/org/opendaylight/protocol/bgp/rib/spi/util/ClusterSingletonServiceRegistrationHelper.java
new file mode 100644 (file)
index 0000000..3602d61
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.protocol.bgp.rib.spi.util;
+
+import com.google.common.util.concurrent.Uninterruptibles;
+import java.util.concurrent.TimeUnit;
+import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonService;
+import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceProvider;
+import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceRegistration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility class which provides helper functionality for ClusterSingletonService.
+ *
+ */
+public final class ClusterSingletonServiceRegistrationHelper {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ClusterSingletonServiceRegistrationHelper.class);
+
+    private ClusterSingletonServiceRegistrationHelper() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * This helper function wraps {@link ClusterSingletonServiceProvider#registerClusterSingletonService(ClusterSingletonService)} in order to
+     * execute repeated registration attempts while catching RuntimeException. If registration is not successful, RuntimeException is re-thrown.
+     * @param singletonProvider
+     * @param clusterSingletonService
+     * @param maxAttempts Upper bound for registration retries count.
+     * @param sleepTime Sleep time between registration retries in milliseconds.
+     * @return Registration
+     */
+    public static ClusterSingletonServiceRegistration registerSingletonService(final ClusterSingletonServiceProvider singletonProvider,
+            final ClusterSingletonService clusterSingletonService, final int maxAttempts, final int sleepTime) {
+        int attempts = maxAttempts;
+        while (true) {
+            try {
+              return singletonProvider.registerClusterSingletonService(clusterSingletonService);
+            } catch (final RuntimeException e) {
+                if (attempts == 0) {
+                    LOG.error("Giving up after {} registration attempts for service {}.", maxAttempts, clusterSingletonService, e);
+                    throw e;
+                }
+                attempts--;
+                LOG.warn("Failed to register {} service to ClusterSingletonServiceProvider. Try again in {} ms.", clusterSingletonService, sleepTime);
+                Uninterruptibles.sleepUninterruptibly(sleepTime, TimeUnit.MILLISECONDS);
+            }
+        }
+    }
+
+}
diff --git a/bgp/rib-spi/src/test/java/org/opendaylight/protocol/bgp/rib/spi/util/ClusterSingletonServiceRegistrationHelperTest.java b/bgp/rib-spi/src/test/java/org/opendaylight/protocol/bgp/rib/spi/util/ClusterSingletonServiceRegistrationHelperTest.java
new file mode 100644 (file)
index 0000000..0dcafc8
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.protocol.bgp.rib.spi.util;
+
+import static org.opendaylight.protocol.bgp.rib.spi.util.ClusterSingletonServiceRegistrationHelper.registerSingletonService;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonService;
+import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceProvider;
+import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceRegistration;
+
+public class ClusterSingletonServiceRegistrationHelperTest {
+
+    @Mock
+    private ClusterSingletonService clusterSingletonService;
+    @Mock
+    private ClusterSingletonServiceProvider singletonProvider;
+    @Mock
+    private ClusterSingletonServiceRegistration serviceRegistration;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Mockito.doReturn(null).when(this.singletonProvider).registerClusterSingletonService(Mockito.any());
+        Mockito.when(this.singletonProvider.registerClusterSingletonService(Mockito.any()))
+            .thenThrow(new RuntimeException())
+            .thenReturn(this.serviceRegistration);
+    }
+
+    @Test(expected=UnsupportedOperationException.class)
+    public void testPrivateConstructor() throws Throwable {
+        final Constructor<ClusterSingletonServiceRegistrationHelper> c = ClusterSingletonServiceRegistrationHelper.class.getDeclaredConstructor();
+        c.setAccessible(true);
+        try {
+            c.newInstance();
+        } catch (final InvocationTargetException e) {
+            throw e.getCause();
+        }
+    }
+
+    @Test(expected=RuntimeException.class)
+    public void testRegisterSingletonServiceFailure() {
+        registerSingletonService(this.singletonProvider, this.clusterSingletonService, 0, 1);
+    }
+
+    @Test
+    public void testRegisterSingletonServiceSuccessfulRetry() {
+        final ClusterSingletonServiceRegistration registerSingletonService = registerSingletonService(this.singletonProvider, this.clusterSingletonService, 1, 1);
+        Assert.assertEquals(this.serviceRegistration, registerSingletonService);
+        //first reg. attempt failed, second succeeded
+        Mockito.verify(this.singletonProvider, Mockito.times(2)).registerClusterSingletonService(Mockito.any());
+    }
+
+}
index 38fa5ab168ae5670bd58c0e2450f6b19fd42c075..e0ffead293d36302d6b1307d17ce1674dd42a7b1 100644 (file)
@@ -27,8 +27,10 @@ import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
 import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
 import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonService;
 import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceProvider;
 import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceRegistration;
+import org.opendaylight.protocol.bgp.rib.spi.util.ClusterSingletonServiceRegistrationHelper;
 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
 import org.opendaylight.yangtools.concepts.AbstractRegistration;
@@ -43,6 +45,9 @@ public final class BgpTopologyDeployerImpl implements AutoCloseable, ClusteredDa
 
     private static final Logger LOG = LoggerFactory.getLogger(BgpTopologyDeployerImpl.class);
 
+    private static final int MAX_REGISTRATION_ATTEMPTS = 10;
+    private static final int SLEEP_TIME = MAX_REGISTRATION_ATTEMPTS;
+
     @GuardedBy("this")
     private final Set<BgpTopologyProvider> topologyProviders = new HashSet<>();
     private final DataBroker dataBroker;
@@ -57,7 +62,7 @@ public final class BgpTopologyDeployerImpl implements AutoCloseable, ClusteredDa
         this.dataBroker = Preconditions.checkNotNull(dataBroker);
         this.singletonProvider = Preconditions.checkNotNull(singletonProvider);
         this.registration =
-                this.dataBroker.registerDataTreeChangeListener(new DataTreeIdentifier<Topology>(LogicalDatastoreType.CONFIGURATION, InstanceIdentifier.create(NetworkTopology.class).child(Topology.class)), this);
+                this.dataBroker.registerDataTreeChangeListener(new DataTreeIdentifier<>(LogicalDatastoreType.CONFIGURATION, InstanceIdentifier.create(NetworkTopology.class).child(Topology.class)), this);
         LOG.info("BGP topology deployer started.");
     }
 
@@ -123,7 +128,7 @@ public final class BgpTopologyDeployerImpl implements AutoCloseable, ClusteredDa
         final Dictionary<String, String> properties = new Hashtable<>();
         properties.put("topology-id", topologyProviderService.getInstanceIdentifier().firstKeyOf(Topology.class).getTopologyId().getValue());
         final ServiceRegistration<?> registerService = this.context.registerService(new String[] {TopologyReference.class.getName()}, topologyProviderService, properties);
-        final ClusterSingletonServiceRegistration registerClusterSingletonService = this.singletonProvider.registerClusterSingletonService(topologyProviderService);
+        final ClusterSingletonServiceRegistration registerClusterSingletonService = registerSingletonService(topologyProviderService);
         return new AbstractRegistration() {
             @Override
             protected void removeRegistration() {
@@ -148,4 +153,8 @@ public final class BgpTopologyDeployerImpl implements AutoCloseable, ClusteredDa
         filterTopologyBuilders(topology).forEach(provider -> provider.onTopologyBuilderRemoved(topology));
     }
 
+    private ClusterSingletonServiceRegistration registerSingletonService(final ClusterSingletonService clusterSingletonService) {
+        return ClusterSingletonServiceRegistrationHelper.registerSingletonService(this.singletonProvider, clusterSingletonService, MAX_REGISTRATION_ATTEMPTS, SLEEP_TIME);
+    }
+
 }