/* * 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.testing; import io.atomix.catalyst.transport.Address; import io.atomix.catalyst.transport.local.LocalServerRegistry; import io.atomix.catalyst.transport.local.LocalTransport; import io.atomix.copycat.client.ConnectionStrategies; import io.atomix.copycat.client.CopycatClient; import io.atomix.copycat.client.RecoveryStrategies; import io.atomix.copycat.client.ServerSelectionStrategies; import io.atomix.copycat.server.CopycatServer; import io.atomix.copycat.server.StateMachine; import io.atomix.copycat.server.storage.Storage; import io.atomix.copycat.server.storage.StorageLevel; import io.atomix.resource.Resource; import io.atomix.resource.ResourceType; import net.jodah.concurrentunit.ConcurrentTestCase; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; /** * Abstract copycat test. * * @author <a href="http://github.com/kuujo>Jordan Halterman</a> */ public abstract class AbstractCopycatTest<T extends Resource> extends ConcurrentTestCase { protected LocalServerRegistry registry; protected int port; protected List<Address> members; protected List<T> resources; protected List<CopycatServer> servers; /** * Returns the resource type. * * @return The resource type. */ protected abstract Class<? super T> type(); @BeforeMethod protected void init() { port = 5000; registry = new LocalServerRegistry(); members = new ArrayList<>(); resources = new ArrayList<>(); servers = new ArrayList<>(); } @AfterMethod protected void cleanup() { resources.stream().forEach(c -> { try { c.close().join(); } catch (Exception ignore) { } }); servers.stream().forEach(s -> { try { s.leave().join(); } catch (Exception ignore) { } }); resources.clear(); servers.clear(); } /** * Returns the next server address. * * @return The next server address. */ private Address nextAddress() { return new Address("localhost", port++); } /** * Creates a new copycat client instance */ protected CopycatClient createCopycatClient() { return CopycatClient.builder(members) .withTransport(new LocalTransport(registry)) .withServerSelectionStrategy(ServerSelectionStrategies.ANY) .withConnectionStrategy(ConnectionStrategies.FIBONACCI_BACKOFF) .withRecoveryStrategy(RecoveryStrategies.RECOVER) .build(); } /** * Creates a new resource instance. */ protected T createResource() throws Throwable { return createResource(new Resource.Options()); } /** * Creates a new resource instance. * @todo config was never used... this should be fixed */ protected T createResource(Resource.Config config) throws Throwable { return createResource(createCopycatClient(), new Resource.Options()); } /** * Creates a new resource instance. */ protected T createResource(Resource.Options options) throws Throwable { return createResource(createCopycatClient(), options); } /** * Creates a new resource instance. */ @SuppressWarnings("unchecked") protected T createResource(CopycatClient client, Resource.Options options) throws Throwable { ResourceType type = new ResourceType((Class<? extends Resource>) type()); T resource = (T) type.factory().newInstance().createInstance(client, options); type.factory().newInstance().createSerializableTypeResolver().resolve(client.serializer().registry()); resource.open().thenRun(this::resume); resources.add(resource); await(10000); return resource; } /** * Creates a Raft server. */ protected CopycatServer createServer(Address address) throws Throwable{ return createServer(address, new Resource.Config()); } /** * Creates a Raft server. */ @SuppressWarnings("unchecked") protected CopycatServer createServer(Address address, Resource.Config config) throws Throwable { ResourceType type = new ResourceType((Class<? extends Resource>) type()); Supplier<StateMachine> stateMachine = () -> { try { return type.factory().newInstance().createStateMachine(config); } catch (InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); } }; CopycatServer server = CopycatServer.builder(address) .withTransport(new LocalTransport(registry)) .withStorage(new Storage(StorageLevel.MEMORY)) .withStateMachine(stateMachine) .build(); type.factory().newInstance().createSerializableTypeResolver().resolve(server.serializer().registry()); servers.add(server); return server; } /** * Creates a set of Raft servers. */ protected List<CopycatServer> createServers(int live, int total) throws Throwable { return createServers(live, total, new Resource.Config()); } /** * Creates a set of Raft servers. */ protected List<CopycatServer> createServers(int live, int total, Resource.Config config) throws Throwable { List<Address> members = new ArrayList<>(); for (int i = 0; i < total; i++) { members.add(nextAddress()); } this.members.addAll(members); List<CopycatServer> servers = new ArrayList<>(); for (int i = 0; i < live; i++) { CopycatServer server = createServer(members.get(i), config); server.bootstrap(members).thenRun(this::resume); servers.add(server); } await(0, live); return servers; } /** * Creates a set of Raft servers. */ protected List<CopycatServer> createServers(int nodes) throws Throwable { return createServers(nodes, nodes, new Resource.Config()); } /** * Creates a set of Raft servers. */ protected List<CopycatServer> createServers(int nodes, Resource.Config config) throws Throwable { return createServers(nodes, nodes, config); } }