/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package io.atomix;
import io.atomix.copycat.Command;
import io.atomix.copycat.Query;
import io.atomix.copycat.client.CopycatClient;
import io.atomix.copycat.server.Commit;
import io.atomix.resource.*;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import static org.testng.Assert.assertEquals;
/**
* Replica test.
*
* @author <a href="http://github.com/kuujo>Jordan Halterman</a>
*/
@Test
public class AtomixReplicaTest extends AbstractAtomixTest {
@BeforeMethod
protected void beforeMethod() {
init();
}
@AfterMethod
protected void afterMethod() throws Throwable {
cleanup();
}
/**
* Tests submitting a command.
*/
public void testSubmitCommand() throws Throwable {
Atomix replica = createReplicas(3, new ResourceType(TestResource.class)).iterator().next();
TestResource resource = replica.getResource("test", TestResource.class).get(5, TimeUnit.SECONDS);
resource.command("Hello world!").thenAccept(result -> {
threadAssertEquals(result, "Hello world!");
resume();
});
await(10000);
}
/**
* Tests submitting a query.
*/
public void testSubmitQueryWithSequentialConsistency() throws Throwable {
testSubmitQuery(ReadConsistency.SEQUENTIAL);
}
/**
* Tests submitting a query.
*/
public void testSubmitQueryWithAtomicLeaseConsistency() throws Throwable {
testSubmitQuery(ReadConsistency.ATOMIC_LEASE);
}
/**
* Tests submitting a query.
*/
public void testSubmitQueryWithAtomicConsistency() throws Throwable {
testSubmitQuery(ReadConsistency.ATOMIC);
}
/**
* Tests submitting a query with a configured consistency level.
*/
private void testSubmitQuery(ReadConsistency consistency) throws Throwable {
Atomix replica = createReplicas(3, new ResourceType(TestResource.class)).iterator().next();
TestResource resource = replica.getResource("test", TestResource.class).get(5, TimeUnit.SECONDS);
resource.query("Hello world!").thenAccept(result -> {
threadAssertEquals(result, "Hello world!");
resume();
});
await(10000);
}
/**
* Tests submitting a command through all nodes.
*/
public void testSubmitAll() throws Throwable {
List<Atomix> replicas = createReplicas(3, new ResourceType(ValueResource.class));
for (Atomix replica : replicas) {
ValueResource resource = replica.getResource("test", ValueResource.class).get(5, TimeUnit.SECONDS);
resource.set("Hello world!").thenRun(this::resume);
await(10000);
}
ValueResource resource = replicas.get(0).getResource("test", ValueResource.class).get(5, TimeUnit.SECONDS);
resource.get().thenAccept(result -> {
threadAssertEquals("Hello world!", result);
resume();
});
await(10000);
}
/**
* Tests getting a resource and submitting commands.
*/
public void testGetConcurrency() throws Throwable {
List<Atomix> replicas = createReplicas(3, new ResourceType(ValueResource.class));
Atomix replica1 = replicas.get(0);
Atomix replica2 = replicas.get(1);
ValueResource resource1 = replica1.getResource("test", ValueResource.class).get(5, TimeUnit.SECONDS);
ValueResource resource2 = replica2.getResource("test", ValueResource.class).get(5, TimeUnit.SECONDS);
resource1.set("Hello world!").join();
resource2.get().thenAccept(result -> {
threadAssertEquals("Hello world!", result);
resume();
});
await(10000);
}
/**
* Tests operating many separate resources from the same clients.
*/
public void testOperateMany() throws Throwable {
List<Atomix> replicas = createReplicas(3, new ResourceType(ValueResource.class));
Atomix replica1 = replicas.get(0);
Atomix replica2 = replicas.get(1);
ValueResource resource11 = replica1.getResource("test1", ValueResource.class).get(5, TimeUnit.SECONDS);
ValueResource resource12 = replica2.getResource("test1", ValueResource.class).get(5, TimeUnit.SECONDS);
ValueResource resource21 = replica1.getResource("test2", ValueResource.class).get(5, TimeUnit.SECONDS);
ValueResource resource22 = replica2.getResource("test2", ValueResource.class).get(5, TimeUnit.SECONDS);
resource11.set("foo").join();
assertEquals(resource12.get().get(), "foo");
resource21.set("bar").join();
assertEquals(resource22.get().get(), "bar");
assertEquals(resource11.get().get(), "foo");
assertEquals(resource21.get().get(), "bar");
}
/**
* Test resource.
*/
@ResourceTypeInfo(id=3, factory=TestResource.Factory.class)
public static class TestResource extends AbstractResource<TestResource> {
public TestResource(CopycatClient client, Properties options) {
super(client, options);
}
public CompletableFuture<String> command(String value) {
return client.submit(new TestCommand(value));
}
public CompletableFuture<String> query(String value) {
return client.submit(new TestQuery(value));
}
/**
* Test resource factory.
*/
public static class Factory implements ResourceFactory<TestResource> {
@Override
public ResourceStateMachine createStateMachine(Properties config) {
return new TestStateMachine(config);
}
@Override
public TestResource createInstance(CopycatClient client, Properties options) {
return new TestResource(client, options);
}
}
}
/**
* Test state machine.
*/
public static class TestStateMachine extends ResourceStateMachine {
public TestStateMachine(Properties config) {
super(config);
}
public String command(Commit<TestCommand> commit) {
return commit.operation().value();
}
public String query(Commit<TestQuery> commit) {
return commit.operation().value();
}
}
/**
* Test command.
*/
public static class TestCommand implements Command<String> {
private String value;
public TestCommand(String value) {
this.value = value;
}
public String value() {
return value;
}
}
/**
* Test query.
*/
public static class TestQuery implements Query<String> {
private String value;
public TestQuery(String value) {
this.value = value;
}
public String value() {
return value;
}
}
/**
* Value resource.
*/
@ResourceTypeInfo(id=4, factory=ValueResource.Factory.class)
public static class ValueResource extends AbstractResource<ValueResource> {
public ValueResource(CopycatClient client, Properties options) {
super(client, options);
}
public CompletableFuture<Void> set(String value) {
return client.submit(new SetCommand(value));
}
public CompletableFuture<String> get() {
return client.submit(new GetQuery());
}
/**
* Value resource factory.
*/
public static class Factory implements ResourceFactory<ValueResource> {
@Override
public ResourceStateMachine createStateMachine(Properties config) {
return new ValueStateMachine(config);
}
@Override
public ValueResource createInstance(CopycatClient client, Properties options) {
return new ValueResource(client, options);
}
}
}
/**
* Value state machine.
*/
public static class ValueStateMachine extends ResourceStateMachine {
private Commit<SetCommand> value;
public ValueStateMachine(Properties config) {
super(config);
}
public void set(Commit<SetCommand> commit) {
Commit<SetCommand> oldValue = value;
value = commit;
if (oldValue != null)
oldValue.close();
}
public String get(Commit<GetQuery> commit) {
try {
return value != null ? value.operation().value() : null;
} finally {
commit.close();
}
}
}
/**
* Set command.
*/
public static class SetCommand implements Command<Void> {
private String value;
public SetCommand(String value) {
this.value = value;
}
public String value() {
return value;
}
}
/**
* Get query.
*/
public static class GetQuery implements Query<String> {
}
}