package io.blobkeeper.cluster.service;
/*
* Copyright (C) 2015 by Denis M. Gabaydulin
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.inject.*;
import io.blobkeeper.cluster.domain.Node;
import io.blobkeeper.common.service.*;
import io.blobkeeper.common.service.SecondServerRootModule;
import io.blobkeeper.file.configuration.FileModule;
import io.blobkeeper.file.service.FileStorage;
import io.blobkeeper.index.dao.IndexDao;
import io.blobkeeper.index.dao.PartitionDao;
import junit.framework.Assert;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.mockito.Mockito;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import javax.inject.*;
import javax.inject.Singleton;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import static com.jayway.awaitility.Awaitility.await;
import static com.jayway.awaitility.Duration.ONE_HUNDRED_MILLISECONDS;
import static io.blobkeeper.cluster.domain.Role.MASTER;
import static io.blobkeeper.cluster.domain.Role.SLAVE;
import static junit.framework.Assert.assertFalse;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
public class ClusterMembershipServiceTest extends BaseMultipleInjectorTest {
private ClusterMembershipService membershipService1;
private ClusterMembershipService membershipService2;
private FileStorage fileStorage1;
private FileStorage fileStorage2;
private CountedMasterChangedListener listener1;
private CountedMasterChangedListener listener2;
@Test
public void startEmpty() {
membershipService1.start("node1");
assertEquals(membershipService1.getSelfNode().getRole(), SLAVE);
membershipService1.stop();
}
@Test
public void keepOriginalMasterIfExists() throws InterruptedException {
membershipService1.start("node1");
assertFalse(membershipService1.isMaster());
membershipService1.setMaster(membershipService1.getSelfNode());
membershipService2.start("node2");
await().forever().pollInterval(ONE_HUNDRED_MILLISECONDS).until(
() -> listener1.getMasterChangedCount() >= 3 && listener2.getMasterChangedCount() >= 1
);
assertEquals(membershipService1.getSelfNode().getRole(), MASTER);
assertEquals(membershipService2.getSelfNode().getRole(), SLAVE);
assertEquals(membershipService1.getMaster(), membershipService2.getMaster());
assertTrue(membershipService1.isMaster());
assertFalse(membershipService2.isMaster());
verifyZeroInteractions(fileStorage1);
// stop slave
membershipService2.stop();
await().forever().pollInterval(ONE_HUNDRED_MILLISECONDS).until(
() -> listener1.getMasterChangedCount() >= 2
);
// keep original master
assertEquals(membershipService1.getSelfNode().getRole(), MASTER);
assertTrue(membershipService1.isMaster());
verifyZeroInteractions(fileStorage1);
membershipService1.stop();
}
@Test
public void removeMasterOnSalveIfMasterIsDisconnected() {
membershipService1.start("node1");
assertFalse(membershipService1.isMaster());
membershipService1.setMaster(membershipService1.getSelfNode());
membershipService2.start("node2");
await().forever().pollInterval(ONE_HUNDRED_MILLISECONDS).until(
() -> listener1.getMasterChangedCount() >= 3 && listener2.getMasterChangedCount() >= 1
);
assertEquals(membershipService1.getSelfNode().getRole(), MASTER);
assertEquals(membershipService2.getSelfNode().getRole(), SLAVE);
assertEquals(membershipService1.getMaster(), membershipService2.getMaster());
assertTrue(membershipService1.isMaster());
assertFalse(membershipService2.isMaster());
verifyZeroInteractions(fileStorage1);
// stop master
membershipService1.stop();
await().forever().pollInterval(ONE_HUNDRED_MILLISECONDS).until(
() -> listener2.getMasterChangedCount() >= 3
);
// the master is not available on the slave node
assertEquals(membershipService2.getSelfNode().getRole(), SLAVE);
assertFalse(membershipService2.getMaster().isPresent());
verifyZeroInteractions(fileStorage1);
membershipService2.stop();
}
@Test
public void getNodes() throws InterruptedException {
membershipService1.start("node1");
assertFalse(membershipService1.isMaster());
membershipService1.setMaster(membershipService1.getSelfNode());
membershipService2.start("node2");
await().forever().pollInterval(ONE_HUNDRED_MILLISECONDS).until(
() -> listener1.getMasterChangedCount() >= 3 && listener2.getMasterChangedCount() >= 1
);
assertEquals(membershipService1.getNodes(), ImmutableList.of(membershipService1.getSelfNode(), membershipService2.getSelfNode()));
assertEquals(membershipService2.getNodes(), ImmutableList.of(membershipService1.getSelfNode(), membershipService2.getSelfNode()));
membershipService2.stop();
membershipService1.stop();
}
@Test
public void notEnoughNodesForRepair() {
membershipService1.start("node1");
membershipService1.setMaster(membershipService1.getSelfNode());
assertFalse(membershipService1.getNodeForRepair(false).isPresent());
assertFalse(membershipService1.getNodeForRepair(true).isPresent());
membershipService1.stop();
}
@Test
public void notEnoughNodesForRepairForRepairActive() {
membershipService1.start("node1");
membershipService2.start("node2");
assertEquals(membershipService1.getNodeForRepair(false).get().getAddress().toString(), "node2");
assertFalse(membershipService1.getNodeForRepair(true).isPresent());
assertEquals(membershipService2.getNodeForRepair(false).get().getAddress().toString(), "node1");
assertFalse(membershipService2.getNodeForRepair(true).isPresent());
membershipService1.stop();
membershipService2.stop();
}
@Test
public void activeRepairOnlyFromMaster() {
membershipService1.start("node1");
membershipService2.start("node2");
membershipService1.trySetMaster(membershipService1.getSelfNode().getAddress());
assertEquals(membershipService1.getNodeForRepair(false).get().getAddress().toString(), "node2");
assertFalse(membershipService1.getNodeForRepair(true).isPresent());
assertEquals(membershipService2.getNodeForRepair(false).get().getAddress().toString(), "node1");
assertEquals(membershipService2.getNodeForRepair(true).get().getAddress().toString(), "node1");
membershipService1.stop();
membershipService2.stop();
}
@BeforeMethod(dependsOnMethods = "createInjectors")
protected void setUp() {
membershipService1 = firstServerInjector.getInstance(ClusterMembershipService.class);
listener1 = (CountedMasterChangedListener) firstServerInjector.getInstance(MasterChangedListener.class);
fileStorage1 = firstServerInjector.getInstance(FileStorage.class);
membershipService2 = secondServerInjector.getInstance(ClusterMembershipService.class);
listener2 = (CountedMasterChangedListener) secondServerInjector.getInstance(MasterChangedListener.class);
fileStorage2 = secondServerInjector.getInstance(FileStorage.class);
}
@Override
protected Set<Module> getFirstInjectorModules() {
return ImmutableSet.of(new FirstInjectorMocks(), new FirstServerRootModule(), new FileModule());
}
@Override
protected Set<Module> getSecondInjectorModules() {
return ImmutableSet.of(new SecondInjectorMocks(), new SecondServerRootModule(), new FileModule());
}
@Override
protected Set<Module> getThirdInjectorModules() {
return ImmutableSet.of(new ThirdInjectorMocks(), new ThirdServerRootModule(), new FileModule());
}
public static class FirstInjectorMocks extends AbstractModule {
@Provides
@Singleton
PartitionDao partitionDao() {
return Mockito.mock(PartitionDao.class);
}
@Provides
@Singleton
IndexDao indexDao() {
return Mockito.mock(IndexDao.class);
}
@Provides
@Singleton
MasterChangedListener changedListener(Injector injector) {
DefaultMasterChangedListener listener = new DefaultMasterChangedListener();
injector.injectMembers(listener);
return new CountedMasterChangedListener(listener);
}
@Provides
@Singleton
FileStorage fileStorage() {
return mock(FileStorage.class);
}
@Override
protected void configure() {
}
}
public static class SecondInjectorMocks extends AbstractModule {
@Provides
@Singleton
PartitionDao partitionDao() {
return Mockito.mock(PartitionDao.class);
}
@Provides
@Singleton
IndexDao indexDao() {
return Mockito.mock(IndexDao.class);
}
@Provides
@Singleton
MasterChangedListener changedListener(Injector injector) {
DefaultMasterChangedListener listener = new DefaultMasterChangedListener();
injector.injectMembers(listener);
return new CountedMasterChangedListener(listener);
}
@Provides
@Singleton
FileStorage fileStorage() {
return mock(FileStorage.class);
}
@Override
protected void configure() {
}
}
public static class ThirdInjectorMocks extends AbstractModule {
@Provides
@Singleton
PartitionDao partitionDao() {
return Mockito.mock(PartitionDao.class);
}
@Provides
@Singleton
IndexDao indexDao() {
return Mockito.mock(IndexDao.class);
}
@Provides
@Singleton
MasterChangedListener changedListener(Injector injector) {
DefaultMasterChangedListener listener = new DefaultMasterChangedListener();
injector.injectMembers(listener);
return new CountedMasterChangedListener(listener);
}
@Provides
@Singleton
FileStorage fileStorage() {
return mock(FileStorage.class);
}
@Override
protected void configure() {
}
}
public static class CountedMasterChangedListener implements MasterChangedListener {
private static final Logger log = LoggerFactory.getLogger(CountedMasterChangedListener.class);
private final AtomicInteger counter;
private final DefaultMasterChangedListener originalListener;
public CountedMasterChangedListener(DefaultMasterChangedListener originalListener) {
counter = new AtomicInteger();
this.originalListener = originalListener;
}
@Override
public void onMasterChanged(@NotNull Node selfNode, @Nullable Node oldMaster, @Nullable Node newMaster) {
originalListener.onMasterChanged(selfNode, oldMaster, newMaster);
counter.incrementAndGet();
}
public int getMasterChangedCount() {
return counter.get();
}
}
}