<!-- sal-distributed-datastore -->
<module>sal-distributed-datastore</module>
+ <module>sal-dummy-distributed-datastore</module>
<!-- XSQL -->
<module>sal-dom-xsql</module>
--- /dev/null
+To use this run a real instance of the controller on your laptop.
+Modify the module-shards.conf to replicate modules to member-2 or
+member-2 and member-3 as neccessary.
+
+Then run the dummy datastore.
+
+For example,
+
+ java -jar ./target/sal-dummy-distributed-datastore-1.2.0-SNAPSHOT-allinone.jar -member-name member-2 -cause-trouble -drop-replies -max-delay-millis 500
+
+Runs the dummy datastore as member-2. Will cause failures including dropped replies and when it does reply may cause a random delay of upto
+500 millis
+
+This will start of the dummy datastore which will then spawn dummy shard actors which will listen to the RequestVote
+and AppendEntries messages. For RequestVote messages it will always respond with a positive vote and for AppendEntries
+it will put a sleep for a randomized interval upto the max delay.
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-parent</artifactId>
+ <version>1.2.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>sal-dummy-distributed-datastore</artifactId>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.typesafe.akka</groupId>
+ <artifactId>akka-actor_${scala.version}</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.typesafe.akka</groupId>
+ <artifactId>akka-cluster_${scala.version}</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.typesafe.akka</groupId>
+ <artifactId>akka-persistence-experimental_${scala.version}</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.typesafe.akka</groupId>
+ <artifactId>akka-remote_${scala.version}</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.typesafe.akka</groupId>
+ <artifactId>akka-testkit_${scala.version}</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.typesafe.akka</groupId>
+ <artifactId>akka-slf4j_${scala.version}</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.typesafe.akka</groupId>
+ <artifactId>akka-osgi_${scala.version}</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-clustering-commons</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-akka-raft</artifactId>
+ <version>1.2.0-SNAPSHOT</version>
+ </dependency>
+
+
+ <!-- Test Dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>args4j</groupId>
+ <artifactId>args4j</artifactId>
+ <version>2.0.29</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>1.7.7</version>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-Name>${project.groupId}.${project.artifactId}</Bundle-Name>
+ <Export-package></Export-package>
+ <Private-Package></Private-Package>
+ <Import-Package>!*snappy;!org.jboss.*;!com.jcraft.*;!*jetty*;!sun.security.*;*</Import-Package>
+ <!--
+ <Embed-Dependency>
+ sal-clustering-commons;
+ sal-akka-raft;
+ *metrics*;
+ !sal*;
+ !*config-api*;
+ !*testkit*;
+ akka*;
+ *leveldb*;
+ *config*;
+ *hawt*;
+ *protobuf*;
+ *netty*;
+ *uncommons*;
+ *scala*;
+ </Embed-Dependency>
+ <Embed-Transitive>true</Embed-Transitive>
+ -->
+ </instructions>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>test-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.jacoco</groupId>
+ <artifactId>jacoco-maven-plugin</artifactId>
+ <configuration>
+ <includes>
+ <include>org.opendaylight.controller.*</include>
+
+ </includes>
+ <excludes>
+ <exclude>org.opendaylight.controller.config.yang.config.*</exclude>
+ </excludes>
+ <check>false</check>
+ </configuration>
+ <executions>
+ <execution>
+ <id>pre-test</id>
+ <goals>
+ <goal>prepare-agent</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>post-test</id>
+ <goals>
+ <goal>report</goal>
+ </goals>
+ <phase>test</phase>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <version>1.5</version>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <shadedArtifactAttached>true</shadedArtifactAttached>
+ <shadedClassifierName>allinone</shadedClassifierName>
+ <artifactSet>
+ <includes>
+ <include>*:*</include>
+ </includes>
+ </artifactSet>
+ <transformers>
+ <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
+ <resource>reference.conf</resource>
+ </transformer>
+ <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+ <manifestEntries>
+ <Main-Class>org.opendaylight.controller.dummy.datastore.Main</Main-Class>
+ </manifestEntries>
+ </transformer>
+ </transformers>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ <scm>
+ <connection>scm:git:ssh://git.opendaylight.org:29418/controller.git</connection>
+ <developerConnection>scm:git:ssh://git.opendaylight.org:29418/controller.git</developerConnection>
+ <tag>HEAD</tag>
+ <url>https://wiki.opendaylight.org/view/OpenDaylight_Controller:MD-SAL:Architecture:Clustering</url>
+ </scm>
+</project>
--- /dev/null
+/*
+ * Copyright (c) 2014 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.controller.dummy.datastore;
+
+public class Configuration {
+ private final int maxDelayInMillis;
+ private final boolean dropReplies;
+ private final boolean causeTrouble;
+
+ public Configuration(int maxDelayInMillis, boolean dropReplies, boolean causeTrouble) {
+ this.maxDelayInMillis = maxDelayInMillis;
+ this.dropReplies = dropReplies;
+ this.causeTrouble = causeTrouble;
+ }
+
+ public int getMaxDelayInMillis() {
+ return maxDelayInMillis;
+ }
+
+ public boolean shouldDropReplies() {
+ return dropReplies;
+ }
+
+ public boolean shouldCauseTrouble() {
+ return causeTrouble;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 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.controller.dummy.datastore;
+
+import akka.actor.Props;
+import akka.actor.UntypedActor;
+import akka.japi.Creator;
+import org.opendaylight.controller.cluster.raft.messages.AppendEntries;
+import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply;
+import org.opendaylight.controller.cluster.raft.messages.InstallSnapshot;
+import org.opendaylight.controller.cluster.raft.messages.InstallSnapshotReply;
+import org.opendaylight.controller.cluster.raft.messages.RequestVote;
+import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DummyShard extends UntypedActor{
+ private final Configuration configuration;
+ private final String followerId;
+ private final Logger LOG = LoggerFactory.getLogger(DummyShard.class);
+
+ public DummyShard(Configuration configuration, String followerId) {
+ this.configuration = configuration;
+ this.followerId = followerId;
+ LOG.info("Creating : {}", followerId);
+ }
+
+ @Override
+ public void onReceive(Object o) throws Exception {
+ if(o instanceof RequestVote){
+ RequestVote req = (RequestVote) o;
+ sender().tell(new RequestVoteReply(req.getTerm(), true), self());
+ } else if(AppendEntries.LEGACY_SERIALIZABLE_CLASS.equals(o.getClass()) || o instanceof AppendEntries) {
+ AppendEntries req = AppendEntries.fromSerializable(o);
+ handleAppendEntries(req);
+ } else if(InstallSnapshot.SERIALIZABLE_CLASS.equals(o.getClass())) {
+ InstallSnapshot req = InstallSnapshot.fromSerializable(o);
+ handleInstallSnapshot(req);
+ } else if(o instanceof InstallSnapshot){
+ handleInstallSnapshot((InstallSnapshot) o);
+ } else {
+ LOG.error("Unknown message : {}", o.getClass());
+ }
+ }
+
+ private void handleInstallSnapshot(InstallSnapshot req) {
+ sender().tell(new InstallSnapshotReply(req.getTerm(), followerId, req.getChunkIndex(), true), self());
+ }
+
+ protected void handleAppendEntries(AppendEntries req) throws InterruptedException {
+ LOG.info("{} - Received AppendEntries message : leader term, index, size = {}, {}, {}", followerId, req.getTerm(),req.getLeaderCommit(), req.getEntries().size());
+ long lastIndex = req.getLeaderCommit();
+ if (req.getEntries().size() > 0)
+ lastIndex = req.getEntries().get(0).getIndex();
+
+ if (configuration.shouldCauseTrouble()) {
+ boolean ignore = false;
+
+ if (configuration.shouldDropReplies()) {
+ ignore = Math.random() > 0.5;
+ }
+
+ long delay = (long) (Math.random() * configuration.getMaxDelayInMillis());
+
+ if (!ignore) {
+ LOG.info("{} - Randomizing delay : {}", followerId, delay);
+ Thread.sleep(delay);
+ sender().tell(new AppendEntriesReply(followerId, req.getTerm(), true, lastIndex, req.getTerm()), self());
+ }
+ } else {
+ sender().tell(new AppendEntriesReply(followerId, req.getTerm(), true, lastIndex, req.getTerm()), self());
+ }
+ }
+
+ public static Props props(Configuration configuration, final String followerId) {
+
+ return Props.create(new DummyShardCreator(configuration, followerId));
+ }
+
+ private static class DummyShardCreator implements Creator<DummyShard> {
+
+ private static final long serialVersionUID = 1L;
+ private final Configuration configuration;
+ private final String followerId;
+
+ DummyShardCreator(Configuration configuration, String followerId) {
+ this.configuration = configuration;
+ this.followerId = followerId;
+ }
+
+ @Override
+ public DummyShard create() throws Exception {
+ return new DummyShard(configuration, followerId);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 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.controller.dummy.datastore;
+
+import akka.actor.ActorContext;
+import akka.actor.Props;
+import akka.actor.UntypedActor;
+import akka.japi.Creator;
+
+public class DummyShardManager extends UntypedActor {
+ public DummyShardManager(Configuration configuration, String memberName, String[] shardNames, String type) throws Exception {
+ new DummyShardsCreator(configuration, context(), memberName, shardNames, type).create();
+ }
+
+ @Override
+ public void onReceive(Object o) throws Exception {
+
+ }
+
+ public static Props props(Configuration configuration, String memberName, String[] shardNames, String type){
+ return Props.create(new DummyShardManagerCreator(configuration, memberName, shardNames, type));
+ }
+
+ private static class DummyShardManagerCreator implements Creator<DummyShardManager> {
+
+ private final Configuration configuration;
+ private final String memberName;
+ private final String[] shardNames;
+ private final String type;
+
+ public DummyShardManagerCreator(Configuration configuration, String memberName, String[] shardNames, String type) {
+ this.configuration = configuration;
+ this.memberName = memberName;
+ this.shardNames = shardNames;
+ this.type = type;
+ }
+
+ @Override
+ public DummyShardManager create() throws Exception {
+ return new DummyShardManager(configuration, memberName, shardNames, type );
+ }
+ }
+
+ private static class DummyShardsCreator {
+ private final Configuration configuration;
+ private final ActorContext actorSystem;
+ private final String memberName;
+ private final String[] shardNames;
+ private final String type;
+
+ DummyShardsCreator(Configuration configuration, ActorContext actorSystem, String memberName, String[] shardNames, String type){
+ this.configuration = configuration;
+ this.actorSystem = actorSystem;
+ this.memberName = memberName;
+ this.shardNames = shardNames;
+ this.type = type;
+ }
+
+ void create(){
+ for(String shardName : shardNames){
+ String shardId = memberName + "-shard-" + shardName + "-" + type;
+ actorSystem.actorOf(DummyShard.props(configuration, shardId), shardId);
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 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.controller.dummy.datastore;
+
+import akka.actor.ActorSystem;
+import com.typesafe.config.ConfigFactory;
+import org.kohsuke.args4j.CmdLineParser;
+import org.kohsuke.args4j.Option;
+
+public class Main {
+ @Option(name="-member-name", usage="Sets the member name", required = true)
+ public String memberName;
+
+ @Option(name="-max-delay-millis", usage = "Sets the maximum delay that should be applied for any append entry. Only applies when cause-trouble is present.")
+ public int maxDelayInMillis = 500;
+
+ @Option(name="-cause-trouble", usage="If present turns on artificial failures")
+ public boolean causeTrouble = false;
+
+ @Option(name="-drop-replies", usage = "If present drops replies. Only applies when cause-trouble is present.")
+ public boolean dropReplies = false;
+
+ public void run(){
+ ActorSystem actorSystem = ActorSystem.create("opendaylight-cluster-data", ConfigFactory.load(memberName).getConfig("odl-cluster-data"));
+
+ Configuration configuration = new Configuration(maxDelayInMillis, dropReplies, causeTrouble);
+
+ actorSystem.actorOf(DummyShardManager.props(configuration, memberName, new String[] {"inventory", "default", "toaster", "topology"}, "operational"), "shardmanager-operational");
+ actorSystem.actorOf(DummyShardManager.props(configuration, memberName, new String[] {"inventory", "default", "toaster", "topology"}, "config"), "shardmanager-config");
+ }
+
+ @Override
+ public String toString() {
+ return "Main{" +
+ "memberName='" + memberName + '\'' +
+ ", maxDelayInMillis=" + maxDelayInMillis +
+ ", causeTrouble=" + causeTrouble +
+ ", dropReplies=" + dropReplies +
+ '}';
+ }
+
+ public static void main(String[] args){
+ Main bean = new Main();
+ CmdLineParser parser = new CmdLineParser(bean);
+
+ try {
+ parser.parseArgument(args);
+ System.out.println(bean.toString());
+ bean.run();
+ } catch(Exception e){
+ System.err.println(e.getMessage());
+ parser.printUsage(System.err);
+ }
+ }
+
+}
--- /dev/null
+odl-cluster-data {
+ bounded-mailbox {
+ mailbox-type = "org.opendaylight.controller.cluster.common.actor.MeteredBoundedMailbox"
+ mailbox-capacity = 1000
+ mailbox-push-timeout-time = 100ms
+ }
+
+ metric-capture-enabled = true
+
+ akka {
+ loglevel = "INFO"
+ loggers = ["akka.event.slf4j.Slf4jLogger"]
+
+ actor {
+
+ provider = "akka.cluster.ClusterActorRefProvider"
+ serializers {
+ java = "akka.serialization.JavaSerializer"
+ proto = "akka.remote.serialization.ProtobufSerializer"
+ }
+
+ serialization-bindings {
+ "com.google.protobuf.Message" = proto
+
+ }
+ }
+ remote {
+ log-remote-lifecycle-events = off
+ netty.tcp {
+ hostname = "127.0.0.1"
+ port = 2553
+ maximum-frame-size = 419430400
+ send-buffer-size = 52428800
+ receive-buffer-size = 52428800
+ }
+ }
+
+ cluster {
+ seed-nodes = ["akka.tcp://opendaylight-cluster-data@127.0.0.1:2550", "akka.tcp://opendaylight-cluster-data@127.0.0.1:2553"]
+
+ auto-down-unreachable-after = 10s
+
+ roles = [
+ "member-2"
+ ]
+
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+odl-cluster-data {
+ bounded-mailbox {
+ mailbox-type = "org.opendaylight.controller.cluster.common.actor.MeteredBoundedMailbox"
+ mailbox-capacity = 1000
+ mailbox-push-timeout-time = 100ms
+ }
+
+ metric-capture-enabled = true
+
+ akka {
+ loglevel = "INFO"
+ loggers = ["akka.event.slf4j.Slf4jLogger"]
+
+ actor {
+
+ provider = "akka.cluster.ClusterActorRefProvider"
+ serializers {
+ java = "akka.serialization.JavaSerializer"
+ proto = "akka.remote.serialization.ProtobufSerializer"
+ }
+
+ serialization-bindings {
+ "com.google.protobuf.Message" = proto
+
+ }
+ }
+ remote {
+ log-remote-lifecycle-events = off
+ netty.tcp {
+ hostname = "127.0.0.1"
+ port = 2554
+ maximum-frame-size = 419430400
+ send-buffer-size = 52428800
+ receive-buffer-size = 52428800
+ }
+ }
+
+ cluster {
+ seed-nodes = ["akka.tcp://opendaylight-cluster-data@127.0.0.1:2550", "akka.tcp://opendaylight-cluster-data@127.0.0.1:2554"]
+
+ auto-down-unreachable-after = 10s
+
+ roles = [
+ "member-3"
+ ]
+
+ }
+ }
+}
\ No newline at end of file