package io.blobkeeper.cluster.service;
/*
* Copyright (C) 2015-2016 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.ImmutableSortedMap;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import io.blobkeeper.cluster.domain.*;
import io.blobkeeper.common.configuration.MetricModule;
import io.blobkeeper.common.configuration.RootModule;
import io.blobkeeper.common.util.Block;
import io.blobkeeper.common.util.BlockElt;
import io.blobkeeper.common.util.MerkleTree;
import io.blobkeeper.common.util.Utils;
import io.blobkeeper.file.service.DiskService;
import io.blobkeeper.file.service.FileListService;
import io.blobkeeper.file.service.FileStorage;
import io.blobkeeper.file.service.PartitionService;
import io.blobkeeper.index.dao.IndexDao;
import io.blobkeeper.index.dao.PartitionDao;
import io.blobkeeper.index.domain.Partition;
import io.blobkeeper.index.service.IndexService;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.jgroups.Address;
import org.jgroups.JChannel;
import org.jgroups.Message;
import org.jgroups.View;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Arrays;
import java.util.Optional;
import static com.google.common.collect.Range.closedOpen;
import static java.lang.Thread.sleep;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
@Guice(modules = {RootModule.class, RepairServiceTest.Mocks.class, MetricModule.class})
public class RepairServiceTest {
private static final Logger log = LoggerFactory.getLogger(RepairServiceTest.class);
@Inject
private FileStorage fileStorage;
@Inject
private IndexService indexService;
@Inject
private ReplicationClientService replicationClientService;
@Inject
private ClusterMembershipService clusterMembershipService;
@Inject
private JChannel channel;
@Inject
private ReplicationHandlerService replicationHandlerService;
@Inject
private IndexDao indexDao;
@Inject
private PartitionDao partitionDao;
@Inject
private PartitionService partitionService;
@Inject
private RepairService repairService;
@Inject
private FileListService fileListService;
@Inject
private DiskService diskService;
@Test
public void replicateNotEqualsAndNonActivePartitionsFromAnyNode() throws Exception {
Address masterAddress = mock(Address.class);
Address slaveAddress1 = mock(Address.class);
Address slaveAddress2 = mock(Address.class);
Node master = new Node(Role.MASTER, masterAddress, System.currentTimeMillis());
Node slave1 = new Node(Role.SLAVE, slaveAddress1, System.currentTimeMillis());
Node slave2 = new Node(Role.SLAVE, slaveAddress2, System.currentTimeMillis());
when(clusterMembershipService.getMaster()).thenReturn(Optional.of(master));
when(clusterMembershipService.getSelfNode()).thenReturn(slave1);
when(clusterMembershipService.getChannel()).thenReturn(channel);
when(clusterMembershipService.getNodeForRepair(eq(true))).thenReturn(Optional.empty());
when(clusterMembershipService.getNodeForRepair(eq(false))).thenReturn(Optional.of(slave2));
View view = mock(View.class);
when(channel.getView()).thenReturn(view);
when(view.getMembers()).thenReturn(ImmutableList.of(masterAddress, slaveAddress1, slaveAddress2));
Partition partition1 = new Partition(0, 0);
partition1.setTree(Utils.createEmptyTree(closedOpen(0L, 100L), MerkleTree.MAX_LEVEL));
Partition partition2 = new Partition(0, 1);
when(partitionService.getPartitions(eq(0))).thenReturn(ImmutableList.of(partition1, partition2));
when(partitionService.getActivePartition(eq(0))).thenReturn(partition2);
MerkleTree masterTree = Utils.createTree(
closedOpen(0L, 100L),
32,
ImmutableSortedMap.of(42L, new Block(1L, Arrays.asList(new BlockElt(1, 0, 2, 3, 4))))
);
MerkleTreeInfo masterInfo = new MerkleTreeInfo();
masterInfo.setTree(masterTree);
masterInfo.setDisk(0);
masterInfo.setPartition(0);
// index already exists
MerkleTree slaveTree = Utils.createTree(
closedOpen(0L, 100L),
32,
ImmutableSortedMap.of()
);
MerkleTreeInfo slaveInfo = new MerkleTreeInfo();
slaveInfo.setTree(slaveTree);
slaveInfo.setDisk(0);
slaveInfo.setPartition(0);
DifferenceInfo partitionInfo = new DifferenceInfo();
partitionInfo.setDisk(0);
partitionInfo.setPartition(0);
partitionInfo.setDifference(MerkleTree.difference(masterInfo.getTree(), slaveInfo.getTree()));
when(clusterMembershipService.getMerkleTreeInfo(eq(slaveAddress2), eq(0), eq(0))).thenReturn(masterInfo);
when(clusterMembershipService.getMerkleTreeInfo(eq(slaveAddress1), eq(0), eq(0))).thenReturn(slaveInfo);
when(clusterMembershipService.getDifference(eq(slaveAddress1), eq(0), eq(0))).thenReturn(partitionInfo);
when(clusterMembershipService.getDifference(eq(slaveInfo))).thenReturn(partitionInfo);
when(diskService.getDisks()).thenReturn(ImmutableList.of(0));
repairService.repair(true);
sleep(100);
verify(channel).send(argThat(new MessageMatcher(slaveAddress2, slaveAddress1, partitionInfo)));
}
@Test
public void replicateActivePartition() throws Exception {
Address masterAddress = mock(Address.class);
Address slaveAddress = mock(Address.class);
Node master = new Node(Role.MASTER, masterAddress, System.currentTimeMillis());
Node slave = new Node(Role.SLAVE, slaveAddress, System.currentTimeMillis());
when(clusterMembershipService.getMaster()).thenReturn(Optional.of(master));
when(clusterMembershipService.getSelfNode()).thenReturn(slave);
when(clusterMembershipService.getChannel()).thenReturn(channel);
when(clusterMembershipService.getNodeForRepair(eq(true))).thenReturn(Optional.of(master));
View view = mock(View.class);
when(channel.getView()).thenReturn(view);
when(view.getMembers()).thenReturn(ImmutableList.of(masterAddress, slaveAddress));
Partition partition = new Partition(0, 0);
when(partitionService.getPartitions(eq(0))).thenReturn(ImmutableList.of(partition));
when(partitionService.getActivePartition(eq(0))).thenReturn(partition);
when(diskService.getDisks()).thenReturn(ImmutableList.of(0));
repairService.repair(true);
sleep(100);
verify(channel, only()).send(any(Message.class));
}
@Test
public void replicateNotEqualsPartitions() throws Exception {
Address masterAddress = mock(Address.class);
Address slaveAddress = mock(Address.class);
Node master = new Node(Role.MASTER, masterAddress, System.currentTimeMillis());
Node slave = new Node(Role.SLAVE, slaveAddress, System.currentTimeMillis());
when(clusterMembershipService.getMaster()).thenReturn(Optional.of(master));
when(clusterMembershipService.getSelfNode()).thenReturn(slave);
when(clusterMembershipService.getChannel()).thenReturn(channel);
when(clusterMembershipService.getNodeForRepair(eq(true))).thenReturn(Optional.of(master));
when(clusterMembershipService.getNodeForRepair(eq(false))).thenReturn(Optional.of(master));
View view = mock(View.class);
when(channel.getView()).thenReturn(view);
when(view.getMembers()).thenReturn(ImmutableList.of(masterAddress, slaveAddress));
Partition partition1 = new Partition(0, 0);
partition1.setTree(Utils.createEmptyTree(closedOpen(0L, 100L), MerkleTree.MAX_LEVEL));
Partition partition2 = new Partition(0, 1);
when(partitionService.getPartitions(eq(0))).thenReturn(ImmutableList.of(partition1, partition2));
when(partitionService.getActivePartition(eq(0))).thenReturn(partition2);
MerkleTree masterTree = Utils.createTree(
closedOpen(0L, 100L),
32,
ImmutableSortedMap.of(42L, new Block(1L, Arrays.asList(new BlockElt(1, 0, 2, 3, 4))))
);
MerkleTreeInfo masterInfo = new MerkleTreeInfo();
masterInfo.setTree(masterTree);
masterInfo.setDisk(0);
masterInfo.setPartition(0);
// index already exists
MerkleTree slaveTree = Utils.createTree(
closedOpen(0L, 100L),
32,
ImmutableSortedMap.of()
);
MerkleTreeInfo slaveInfo = new MerkleTreeInfo();
slaveInfo.setTree(slaveTree);
slaveInfo.setDisk(0);
slaveInfo.setPartition(0);
DifferenceInfo partitionInfo = new DifferenceInfo();
partitionInfo.setDisk(0);
partitionInfo.setPartition(0);
partitionInfo.setDifference(MerkleTree.difference(masterInfo.getTree(), slaveInfo.getTree()));
DifferenceInfo activePartitionInfo = new DifferenceInfo();
activePartitionInfo.setDisk(0);
activePartitionInfo.setPartition(1);
activePartitionInfo.setCompletelyDifferent(true);
when(clusterMembershipService.getMerkleTreeInfo(eq(masterAddress), eq(0), eq(0))).thenReturn(masterInfo);
when(clusterMembershipService.getMerkleTreeInfo(eq(slaveAddress), eq(0), eq(0))).thenReturn(slaveInfo);
when(clusterMembershipService.getDifference(eq(slaveAddress), eq(0), eq(0))).thenReturn(partitionInfo);
when(clusterMembershipService.getDifference(eq(slaveInfo))).thenReturn(partitionInfo);
when(diskService.getDisks()).thenReturn(ImmutableList.of(0));
repairService.repair(true);
sleep(100);
verify(channel).send(argThat(new MessageMatcher(masterAddress, slaveAddress, activePartitionInfo)));
verify(channel).send(argThat(new MessageMatcher(masterAddress, slaveAddress, partitionInfo)));
}
@BeforeClass
private void init() {
initMocks(this);
}
@BeforeMethod
private void clear() {
reset(fileStorage, indexService, replicationClientService, clusterMembershipService, channel, fileListService);
}
private static class MessageMatcher implements Matcher<Message> {
private final Address dst;
private final Address src;
private final DifferenceInfo info;
public MessageMatcher(Address dst, Address src, DifferenceInfo info) {
this.dst = dst;
this.src = src;
this.info = info;
}
@Override
public boolean matches(Object address) {
Message message = ((Message) address);
return
dst.equals(message.getDest())
&& src.equals(message.getSrc())
&& info.equals(message.getObject());
}
@Override
public void _dont_implement_Matcher___instead_extend_BaseMatcher_() {
}
@Override
public void describeTo(Description description) {
}
}
public static class Mocks extends AbstractModule {
@Provides
@Singleton
PartitionDao partitionDao() {
return mock(PartitionDao.class);
}
@Provides
@Singleton
IndexDao indexDao() {
return mock(IndexDao.class);
}
@Provides
@Singleton
IndexService indexService() {
return mock(IndexService.class);
}
@Provides
@Singleton
FileStorage fileStorage() {
return mock(FileStorage.class);
}
@Provides
@Singleton
ClusterMembershipService clusterMembershipService() {
return mock(ClusterMembershipService.class);
}
@Provides
@Singleton
JChannel jChannel() {
return mock(JChannel.class);
}
@Provides
@Singleton
ReplicationHandlerService replicationHandlerService() {
return mock(ReplicationHandlerService.class);
}
@Provides
@Singleton
PartitionService partitionService() {
return mock(PartitionService.class);
}
@Provides
@Singleton
FileListService fileListService() {
return mock(FileListService.class);
}
@Provides
@Singleton
DiskService diskService() {
return mock(DiskService.class);
}
@Provides
@Singleton
ReplicationClientService replicationClientService() {
return mock(ReplicationClientService.class);
}
@Override
protected void configure() {
}
}
}