/**
* Copyright 2011 LiveRamp
*
* 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 com.liveramp.hank.partition_server;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.junit.Before;
import org.junit.Test;
import com.liveramp.hank.coordinator.Domain;
import com.liveramp.hank.coordinator.DomainAndVersion;
import com.liveramp.hank.coordinator.DomainGroup;
import com.liveramp.hank.coordinator.DomainVersion;
import com.liveramp.hank.coordinator.Host;
import com.liveramp.hank.coordinator.HostDomain;
import com.liveramp.hank.coordinator.HostDomainPartition;
import com.liveramp.hank.coordinator.PartitionServerAddress;
import com.liveramp.hank.coordinator.Ring;
import com.liveramp.hank.coordinator.RingGroup;
import com.liveramp.hank.coordinator.mock.MockDomain;
import com.liveramp.hank.coordinator.mock.MockDomainGroup;
import com.liveramp.hank.coordinator.mock.MockDomainVersion;
import com.liveramp.hank.partitioner.ConstantPartitioner;
import com.liveramp.hank.storage.Deleter;
import com.liveramp.hank.storage.PartitionUpdater;
import com.liveramp.hank.storage.StorageEngine;
import com.liveramp.hank.storage.mock.MockDeleter;
import com.liveramp.hank.storage.mock.MockStorageEngine;
import com.liveramp.hank.test.BaseTestCase;
import com.liveramp.hank.test.coordinator.MockHost;
import com.liveramp.hank.test.coordinator.MockHostDomain;
import com.liveramp.hank.test.coordinator.MockHostDomainPartition;
import com.liveramp.hank.test.coordinator.MockRing;
import com.liveramp.hank.test.coordinator.MockRingGroup;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class TestUpdateManager extends BaseTestCase {
private Fixtures fixtures;
@Before
public void setUp() throws Exception {
this.fixtures = new Fixtures();
}
private static class Fixtures {
public class MockRingGroupLocal extends MockRingGroup {
private Ring ringSingleton = null;
private MockRingGroupLocal(DomainGroup dcg, String name) {
super(dcg, name, Collections.<Ring>emptySet());
}
@Override
public Ring getRingForHost(PartitionServerAddress hostAddress) {
return ringSingleton;
}
public void setRing(Ring ring) {
this.ringSingleton = ring;
}
}
private MockRingGroupLocal getMockRingGroup(DomainGroup domainGroup) {
return new MockRingGroupLocal(domainGroup, "myRingGroup") {
};
}
private Ring getMockRing(final Host host, final RingGroup ringGroup) {
return new MockRing(null, ringGroup, 0) {
@Override
public Host getHostByAddress(PartitionServerAddress address) {
return host;
}
};
}
private final MockDeleter MOCK_DELETER = new MockDeleter(1);
protected final class MSE extends MockStorageEngine {
private final PartitionUpdater updater;
private MSE(PartitionUpdater updater) {
this.updater = updater;
}
@Override
public PartitionUpdater getUpdater(DiskPartitionAssignment assignment, int partitionNumber) {
return updater;
}
@Override
public Deleter getDeleter(DiskPartitionAssignment assignment, int partitionNumber)
throws IOException {
return MOCK_DELETER;
}
}
protected StorageEngine getMockStorageEngine(PartitionUpdater updater) {
return new MSE(updater);
}
private final MockHostDomainPartition HOST_DOMAIN_PARTITION = new MockHostDomainPartition(0, 0);
private final MockHostDomainPartition PARTITION_FOR_DELETION = new MockHostDomainPartition(1, 0);
public class MockHostDomainLocal extends MockHostDomain {
private boolean emptyMode = false;
public MockHostDomainLocal(Domain domain) {
super(domain);
}
@Override
public Set<HostDomainPartition> getPartitions() throws IOException {
Set<HostDomainPartition> partitions = new HashSet<HostDomainPartition>();
if (!emptyMode) {
partitions.add(HOST_DOMAIN_PARTITION);
partitions.add(PARTITION_FOR_DELETION);
}
return partitions;
}
@Override
public HostDomainPartition addPartition(int partitionNumber) {
return null;
}
public void setEmptyMode(boolean emptyMode) {
this.emptyMode = emptyMode;
}
}
private MockHostDomainLocal getMockHostDomain(Domain domain) {
return new MockHostDomainLocal(domain);
}
private MockHost getMockHost(final HostDomain hostDomain) {
return new MockHost(new PartitionServerAddress("localhost", 1)) {
@Override
public HostDomain getHostDomain(Domain domain) {
return hostDomain;
}
@Override
public Set<HostDomain> getAssignedDomains() {
return Collections.singleton(hostDomain);
}
};
}
private Domain getMockDomain(final StorageEngine storageEngine) {
return new MockDomain("myDomain", 1, 1,
new ConstantPartitioner(), storageEngine, null, null) {
@Override
public StorageEngine getStorageEngine() {
return storageEngine;
}
@Override
public SortedSet<DomainVersion> getVersions() {
return new TreeSet<DomainVersion>(
Arrays.asList(new MockDomainVersion(2, null) {
@Override
public boolean isDefunct() throws IOException {
return true;
}
}));
}
};
}
private DomainGroup getMockDomainGroup(final Domain domain) {
// the domain version for this domain group version will be 2
return new MockDomainGroup("myDomainGroup") {
@Override
public Set<DomainAndVersion> getDomainVersions() {
Set<DomainAndVersion> result = new HashSet<DomainAndVersion>();
result.add(new DomainAndVersion(domain, 2));
return result;
}
};
}
}
@Test
public void testUpdate() throws Exception {
final MockPartitionUpdater mockUpdater = new MockPartitionUpdater();
StorageEngine mockStorageEngine = fixtures.getMockStorageEngine(mockUpdater);
Domain mockDomain = fixtures.getMockDomain(mockStorageEngine);
MockHostDomain mockHostDomain = fixtures.getMockHostDomain(mockDomain);
Host mockHost = fixtures.getMockHost(mockHostDomain);
DomainGroup mockDomainGroup = fixtures.getMockDomainGroup(mockDomain);
Fixtures.MockRingGroupLocal mockRingGroup = fixtures.getMockRingGroup(mockDomainGroup);
Ring mockRing = fixtures.getMockRing(mockHost, mockRingGroup);
mockRingGroup.setRing(mockRing);
UpdateManager ud = new UpdateManager(new MockPartitionServerConfigurator(1,
null, "myRingGroup", "/local/data/dir"), mockHost,
mockRingGroup);
ud.update();
assertTrue("update() was called on the storage engine",
mockUpdater.isUpdated());
assertEquals("update() called with proper args", Integer.valueOf(2),
mockUpdater.updatedToVersion);
assertEquals("current version", Integer.valueOf(2),
fixtures.HOST_DOMAIN_PARTITION.getCurrentDomainVersion());
assertFalse("host domain contains the partition", fixtures.MOCK_DELETER.hasDeleted());
assertFalse("host domain partition has not yet been deleted",
mockHostDomain.isRemoved(fixtures.PARTITION_FOR_DELETION.getPartitionNumber()));
// Test partition deletion
fixtures.PARTITION_FOR_DELETION.setDeletable(true);
ud.update();
assertTrue("host domain does not contain the partition",
fixtures.MOCK_DELETER.hasDeleted());
assertTrue("host domain partition has been deleted",
mockHostDomain.isRemoved(fixtures.PARTITION_FOR_DELETION.getPartitionNumber()));
}
@Test
public void testGarbageCollectDomain() throws Exception {
final MockPartitionUpdater mockUpdater = new MockPartitionUpdater();
StorageEngine mockStorageEngine = fixtures.getMockStorageEngine(mockUpdater);
Domain mockDomain = fixtures.getMockDomain(mockStorageEngine);
Fixtures.MockHostDomainLocal mockHostDomain = fixtures.getMockHostDomain(mockDomain);
MockHost mockHost = fixtures.getMockHost(mockHostDomain);
// Empty domain group version
DomainGroup mockDomainGroup = new MockDomainGroup("myDomainGroup") {
@Override
public Set<DomainAndVersion> getDomainVersions() {
return Collections.emptySet();
}
};
Fixtures.MockRingGroupLocal mockRingGroup = fixtures.getMockRingGroup(mockDomainGroup);
Ring mockRing = fixtures.getMockRing(mockHost, mockRingGroup);
mockRingGroup.setRing(mockRing);
UpdateManager ud = new UpdateManager(new MockPartitionServerConfigurator(1,
null, "myRingGroup", "/local/data/dir"), mockHost,
mockRingGroup);
ud.update();
assertFalse("update() was not called on the storage engine",
mockUpdater.isUpdated());
assertTrue("the partition was deleted", fixtures.MOCK_DELETER.hasDeleted());
assertTrue("host domain partition has been deleted", mockHostDomain.isRemoved(fixtures.PARTITION_FOR_DELETION.getPartitionNumber()));
assertFalse("host domain has not been deleted", mockHost.isRemoved(mockHostDomain.getDomain()));
mockHostDomain.setEmptyMode(true);
ud.update();
assertTrue("host domain has been deleted", mockHost.isRemoved(mockHostDomain.getDomain()));
}
@Test
public void testFailedUpdateTask() throws Exception {
final MockPartitionUpdater failingUpdater = new MockPartitionUpdater() {
@Override
public void updateTo(DomainVersion updatingToVersion, PartitionUpdateTaskStatistics statistics) throws IOException {
super.updateTo(updatingToVersion, statistics);
throw new IOException("Failed to update.");
}
};
StorageEngine mockStorageEngine = fixtures.getMockStorageEngine(failingUpdater);
Domain mockDomain = fixtures.getMockDomain(mockStorageEngine);
HostDomain mockHostDomain = fixtures.getMockHostDomain(mockDomain);
Host mockHost = fixtures.getMockHost(mockHostDomain);
DomainGroup mockDomainGroup = fixtures.getMockDomainGroup(mockDomain);
Fixtures.MockRingGroupLocal mockRingGroup = fixtures.getMockRingGroup(mockDomainGroup);
Ring mockRing = fixtures.getMockRing(mockHost, mockRingGroup);
mockRingGroup.setRing(mockRing);
UpdateManager ud = new UpdateManager(new MockPartitionServerConfigurator(1,
null, "myRingGroup", "/local/data/dir"), mockHost,
mockRingGroup);
try {
ud.update();
assertTrue("update() should have been called on the storage engine", failingUpdater.isUpdated());
fail("Should throw an IOException when a task update fails.");
} catch (IOException e) {
// Correct behavior
}
// All updates have failed, so all versions should be set to null
for (HostDomainPartition partition : mockHostDomain.getPartitions()) {
assertEquals(null, partition.getCurrentDomainVersion());
}
}
@Test
public void testInterruptedUpdateTask() throws Exception {
final MockPartitionUpdater mockUpdater = new MockPartitionUpdater();
StorageEngine mockStorageEngine = fixtures.getMockStorageEngine(mockUpdater);
Domain mockDomain = fixtures.getMockDomain(mockStorageEngine);
HostDomain mockHostDomain = fixtures.getMockHostDomain(mockDomain);
Host mockHost = fixtures.getMockHost(mockHostDomain);
DomainGroup mockDomainGroup = fixtures.getMockDomainGroup(mockDomain);
Fixtures.MockRingGroupLocal mockRingGroup = fixtures.getMockRingGroup(mockDomainGroup);
Ring mockRing = fixtures.getMockRing(mockHost, mockRingGroup);
mockRingGroup.setRing(mockRing);
UpdateManager ud = new UpdateManager(new MockPartitionServerConfigurator(1,
null, "myRingGroup", "/local/data/dir"), mockHost,
mockRingGroup);
try {
// Interrupt to simulate update cancellation
Thread.currentThread().interrupt();
// Launch update
ud.update();
assertTrue("update() should have been called on the storage engine", mockUpdater.isUpdated());
fail("Should throw an IOException when update is interrupted.");
} catch (IOException e) {
// Correct behavior
}
}
}