BUG-9054: add a ClusterSingletonService integration test 49/62449/5
authorRobert Varga <robert.varga@pantheon.tech>
Wed, 30 Aug 2017 14:02:55 +0000 (16:02 +0200)
committerTom Pantelis <tompantelis@gmail.com>
Tue, 5 Sep 2017 23:03:56 +0000 (23:03 +0000)
This adds the equivalent of the chasing-the-leader in unit test
format, so we can check if the integration works as expected.

Change-Id: I53a89172e8fd750532ee8d13c62ee6dbb94ffb59
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/entityownership/SingletonServiceIntegrationTest.java [new file with mode: 0644]

diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/entityownership/SingletonServiceIntegrationTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/entityownership/SingletonServiceIntegrationTest.java
new file mode 100644 (file)
index 0000000..0155c0e
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies, s.r.o. 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.controller.cluster.datastore.entityownership;
+
+import static java.util.Objects.requireNonNull;
+import static org.junit.Assert.assertTrue;
+import static org.opendaylight.controller.cluster.datastore.entityownership.DistributedEntityOwnershipService.ENTITY_OWNERSHIP_SHARD_NAME;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.cluster.datastore.AbstractDataStore;
+import org.opendaylight.controller.cluster.datastore.DatastoreContext;
+import org.opendaylight.controller.cluster.datastore.MemberNode;
+import org.opendaylight.controller.cluster.datastore.entityownership.selectionstrategy.EntityOwnerSelectionStrategyConfig;
+import org.opendaylight.controller.md.cluster.datastore.model.SchemaContextHelper;
+import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipService;
+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.mdsal.singleton.common.api.ServiceGroupIdentifier;
+import org.opendaylight.mdsal.singleton.dom.impl.DOMClusterSingletonServiceProviderImpl;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Simple integration test to check if the Entity Ownership Service implementation can support the Cluster Singleton
+ * Service.
+ *
+ * @author Robert Varga
+ */
+public class SingletonServiceIntegrationTest {
+    private static final class Service implements ClusterSingletonService {
+        private final ClusterSingletonServiceProvider provider;
+        private final ServiceGroupIdentifier identifier;
+
+        private ClusterSingletonServiceRegistration reg;
+        private long startCount;
+        private long stopCount;
+
+        Service(final ClusterSingletonServiceProvider provider, final String identifier) {
+            this.provider = requireNonNull(provider);
+            this.identifier = ServiceGroupIdentifier.create(identifier);
+        }
+
+        @Override
+        public ServiceGroupIdentifier getIdentifier() {
+            return identifier;
+        }
+
+        @Override
+        public ListenableFuture<Void> closeServiceInstance() {
+            LOG.debug("{} stopping", identifier);
+            stopCount++;
+            start();
+            return Futures.immediateFuture(null);
+        }
+
+        @SuppressWarnings("checkstyle:illegalCatch")
+        @Override
+        public void instantiateServiceInstance() {
+            LOG.debug("{} starting", identifier);
+            startCount++;
+            try {
+                reg.close();
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        void start() {
+            reg = provider.registerClusterSingletonService(this);
+        }
+
+        long getStartCount() {
+            return startCount;
+        }
+
+        long getStopCount() {
+            return stopCount;
+        }
+    }
+
+    private static final Logger LOG = LoggerFactory.getLogger(SingletonServiceIntegrationTest.class);
+    private static final String MODULE_SHARDS_CONFIG = "module-shards-default.conf";
+    private static final SchemaContext SCHEMA_CONTEXT = SchemaContextHelper.entityOwners();
+    private static final long TEST_TIME_SECONDS = 10;
+
+    private final DatastoreContext.Builder leaderDatastoreContextBuilder =
+            DatastoreContext.newBuilder().shardHeartbeatIntervalInMillis(100).shardElectionTimeoutFactor(5)
+                    .shardIsolatedLeaderCheckIntervalInMillis(1000000);
+
+    private final DatastoreContext.Builder followerDatastoreContextBuilder =
+            DatastoreContext.newBuilder().shardHeartbeatIntervalInMillis(100).shardElectionTimeoutFactor(10000);
+
+    private final List<MemberNode> memberNodes = new ArrayList<>();
+
+    private ClusterSingletonServiceProvider leaderSingleton;
+    private ClusterSingletonServiceProvider follower1Singleton;
+    private ClusterSingletonServiceProvider follower2Singleton;
+
+    private static DistributedEntityOwnershipService newOwnershipService(final AbstractDataStore datastore) {
+        return DistributedEntityOwnershipService.start(datastore.getActorContext(),
+                EntityOwnerSelectionStrategyConfig.newBuilder().build());
+    }
+
+    @Before
+    public void setup() throws Exception {
+        final String name = "SingletonServiceIntegrationTest";
+        MemberNode leaderNode = MemberNode.builder(memberNodes).akkaConfig("Member1").testName(name)
+                .moduleShardsConfig(MODULE_SHARDS_CONFIG).schemaContext(SCHEMA_CONTEXT).createOperDatastore(false)
+                .datastoreContextBuilder(leaderDatastoreContextBuilder).build();
+
+        MemberNode follower1Node = MemberNode.builder(memberNodes).akkaConfig("Member2").testName(name)
+                .moduleShardsConfig(MODULE_SHARDS_CONFIG).schemaContext(SCHEMA_CONTEXT).createOperDatastore(false)
+                .datastoreContextBuilder(followerDatastoreContextBuilder).build();
+
+        MemberNode follower2Node = MemberNode.builder(memberNodes).akkaConfig("Member3").testName(name)
+                .moduleShardsConfig(MODULE_SHARDS_CONFIG).schemaContext(SCHEMA_CONTEXT).createOperDatastore(false)
+                .datastoreContextBuilder(followerDatastoreContextBuilder).build();
+
+        AbstractDataStore leaderDistributedDataStore = leaderNode.configDataStore();
+
+        leaderDistributedDataStore.waitTillReady();
+        follower1Node.configDataStore().waitTillReady();
+        follower2Node.configDataStore().waitTillReady();
+
+        final DOMEntityOwnershipService leaderEntityOwnershipService = newOwnershipService(leaderDistributedDataStore);
+        final DOMEntityOwnershipService follower1EntityOwnershipService = newOwnershipService(
+            follower1Node.configDataStore());
+        final DOMEntityOwnershipService follower2EntityOwnershipService = newOwnershipService(
+            follower2Node.configDataStore());
+
+        leaderNode.kit().waitUntilLeader(leaderNode.configDataStore().getActorContext(), ENTITY_OWNERSHIP_SHARD_NAME);
+
+        DOMClusterSingletonServiceProviderImpl impl = new DOMClusterSingletonServiceProviderImpl(
+            leaderEntityOwnershipService);
+        impl.initializeProvider();
+        leaderSingleton = impl;
+
+        impl = new DOMClusterSingletonServiceProviderImpl(follower1EntityOwnershipService);
+        impl.initializeProvider();
+        follower1Singleton = impl;
+
+        impl = new DOMClusterSingletonServiceProviderImpl(follower2EntityOwnershipService);
+        impl.initializeProvider();
+        follower2Singleton = impl;
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        follower2Singleton.close();
+        follower1Singleton.close();
+        leaderSingleton.close();
+
+        for (MemberNode m : Lists.reverse(memberNodes)) {
+            m.cleanup();
+        }
+        memberNodes.clear();
+    }
+
+    @Test
+    public void testSingletonServiceIntegration() throws InterruptedException {
+        List<Service> services = ImmutableList.of(new Service(leaderSingleton, "leader"),
+            new Service(follower1Singleton, "follower1"),
+            new Service(follower2Singleton, "follower2"));
+
+        services.forEach(Service::start);
+
+        LOG.info("Waiting {} seconds...", TEST_TIME_SECONDS);
+        Thread.sleep(TimeUnit.SECONDS.toMillis(TEST_TIME_SECONDS));
+
+        long stops = 0;
+        long starts = 0;
+        for (Service service : services) {
+            starts += service.getStartCount();
+            stops += service.getStopCount();
+        }
+
+        LOG.info("Services saw {} starts and {} stops in {} seconds", starts, stops, TEST_TIME_SECONDS);
+        assertTrue(starts > 0);
+        assertTrue(stops <= starts);
+    }
+}