com.linkedin.pinot.common.partition.PartitionAssignmentGeneratorTest.java Source code

Java tutorial

Introduction

Here is the source code for com.linkedin.pinot.common.partition.PartitionAssignmentGeneratorTest.java

Source

/**
 * Copyright (C) 2014-2016 LinkedIn Corp. (pinot-core@linkedin.com)
 *
 * 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.linkedin.pinot.common.partition;

import com.google.common.collect.Lists;
import com.linkedin.pinot.common.config.RealtimeTagConfig;
import com.linkedin.pinot.common.config.SegmentsValidationAndRetentionConfig;
import com.linkedin.pinot.common.config.TableConfig;
import com.linkedin.pinot.common.config.TenantConfig;
import com.linkedin.pinot.common.utils.LLCSegmentName;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.helix.HelixManager;
import org.apache.helix.model.IdealState;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import static org.mockito.Mockito.*;

public class PartitionAssignmentGeneratorTest {

    private String aServerTenant = "aTenant";
    private HelixManager _mockHelixManager;
    private String[] consumingServerNames;

    private List<String> getConsumingInstanceList(final int nServers) {
        Assert.assertTrue(nServers <= consumingServerNames.length);
        String[] instanceArray = Arrays.copyOf(consumingServerNames, nServers);
        return Lists.newArrayList(instanceArray);
    }

    @BeforeMethod
    public void setUp() throws Exception {
        _mockHelixManager = mock(HelixManager.class);

        final int maxInstances = 20;
        consumingServerNames = new String[maxInstances];
        for (int i = 0; i < maxInstances; i++) {
            consumingServerNames[i] = "ConsumingServer_" + i;
        }
    }

    /**
     * Given an ideal state, constructs the partition assignment for the table
     */
    @Test
    public void testGetPartitionAssignmentFromIdealState() {

        String tableName = "aTableName_REALTIME";
        TableConfig tableConfig = mock(TableConfig.class);
        when(tableConfig.getTableName()).thenReturn(tableName);

        IdealStateBuilderUtil idealStateBuilder = new IdealStateBuilderUtil(tableName);
        IdealState idealState;

        int numPartitions = 0;
        int numReplicas = 2;

        // empty ideal state
        idealState = idealStateBuilder.build();
        verifyPartitionAssignmentFromIdealState(tableConfig, idealState, numPartitions);

        // 3 partitions 2 replicas, all on 0th seq number (6 segments)
        numPartitions = 3;
        List<String> instances = getConsumingInstanceList(4);
        idealState = idealStateBuilder.addConsumingSegments(numPartitions, 0, numReplicas, instances).build();
        verifyPartitionAssignmentFromIdealState(tableConfig, idealState, numPartitions);

        // 3 partitions 2 replicas, 2 on 0th seq number 1 on 1st seq number (8 segments)
        instances = idealStateBuilder.getInstances(0, 0);
        idealState = idealStateBuilder.transitionToState(0, 0, "ONLINE")
                .addConsumingSegment(0, 1, numReplicas, instances).build();
        verifyPartitionAssignmentFromIdealState(tableConfig, idealState, numPartitions);

        // 3 partitions 2 replicas, all on 1st seg num (12 segments)
        instances = idealStateBuilder.getInstances(1, 0);
        idealStateBuilder.transitionToState(1, 0, "ONLINE").addConsumingSegment(1, 1, numReplicas, instances);
        instances = idealStateBuilder.getInstances(2, 0);
        idealState = idealStateBuilder.transitionToState(2, 0, "ONLINE")
                .addConsumingSegment(2, 1, numReplicas, instances).build();
        verifyPartitionAssignmentFromIdealState(tableConfig, idealState, numPartitions);

        // 3 partitions 2 replicas, seq 0 has moved to COMPLETED servers, seq 1 on CONSUMING instances
        instances = Lists.newArrayList("s1_completed", "s2_completed");
        idealState = idealStateBuilder.moveToServers(0, 0, instances).build();
        verifyPartitionAssignmentFromIdealState(tableConfig, idealState, numPartitions);

        // status of latest segments OFFLINE/ERROR
        idealState = idealStateBuilder.transitionToState(1, 1, "OFFLINE").transitionToState(2, 1, "OFFLINE")
                .build();
        verifyPartitionAssignmentFromIdealState(tableConfig, idealState, numPartitions);

        // all non llc segments
        String aNonLLCSegment = "randomName";
        Map<String, String> instanceStateMap = new HashMap<>(1);
        instanceStateMap.put("s1", "CONSUMING");
        instanceStateMap.put("s2", "CONSUMING");
        idealState = idealStateBuilder.clear().addSegment(aNonLLCSegment, instanceStateMap).build();
        numPartitions = 0;
        verifyPartitionAssignmentFromIdealState(tableConfig, idealState, numPartitions);

        // some non llc segments, some llc segments
        numPartitions = 1;
        instances = getConsumingInstanceList(6);
        idealState = idealStateBuilder.addConsumingSegments(numPartitions, 0, numReplicas, instances).build();
        verifyPartitionAssignmentFromIdealState(tableConfig, idealState, numPartitions);
    }

    private void verifyPartitionAssignmentFromIdealState(TableConfig tableConfig, IdealState idealState,
            int numPartitions) {
        TestPartitionAssignmentGenerator partitionAssignmentGenerator = new TestPartitionAssignmentGenerator(
                _mockHelixManager);
        PartitionAssignment partitionAssignmentFromIdealState = partitionAssignmentGenerator
                .getPartitionAssignmentFromIdealState(tableConfig, idealState);
        Assert.assertEquals(tableConfig.getTableName(), partitionAssignmentFromIdealState.getTableName());
        Assert.assertEquals(partitionAssignmentFromIdealState.getNumPartitions(), numPartitions);
        // check that latest segments are honoring partition assignment
        Map<String, LLCSegmentName> partitionIdToLatestLLCSegment = partitionAssignmentGenerator
                .getPartitionToLatestSegments(idealState);
        for (Map.Entry<String, LLCSegmentName> entry : partitionIdToLatestLLCSegment.entrySet()) {
            Set<String> idealStateInstances = idealState.getInstanceStateMap(entry.getValue().getSegmentName())
                    .keySet();
            List<String> partitionAssignmentInstances = partitionAssignmentFromIdealState
                    .getInstancesListForPartition(entry.getKey());
            Assert.assertEquals(idealStateInstances.size(), partitionAssignmentInstances.size());
            Assert.assertTrue(idealStateInstances.containsAll(partitionAssignmentInstances));
        }
    }

    @Test
    public void testGeneratePartitionAssignment() {
        RandomStringUtils.randomAlphabetic(10);
        for (int i = 0; i < 20; i++) {
            String tableName = RandomStringUtils.randomAlphabetic(10) + "_REALTIME";
            testGeneratePartitionAssignmentForTable(tableName);
        }
    }

    private void testGeneratePartitionAssignmentForTable(String tableName) {
        TableConfig tableConfig = mock(TableConfig.class);
        when(tableConfig.getTableName()).thenReturn(tableName);
        TenantConfig mockTenantConfig = mock((TenantConfig.class));
        when(mockTenantConfig.getServer()).thenReturn(aServerTenant);
        when(tableConfig.getTenantConfig()).thenReturn(mockTenantConfig);
        SegmentsValidationAndRetentionConfig mockValidationConfig = mock(
                SegmentsValidationAndRetentionConfig.class);
        when(mockValidationConfig.getReplicasPerPartitionNumber()).thenReturn(2);
        when(tableConfig.getValidationConfig()).thenReturn(mockValidationConfig);
        int numPartitions = 0;
        List<String> consumingInstanceList = getConsumingInstanceList(0);
        PartitionAssignment previousPartitionAssignment = new PartitionAssignment(tableName);
        boolean exceptionExpected;
        boolean unchanged = false;

        // 0 consuming instances - error not enough instances
        exceptionExpected = true;
        previousPartitionAssignment = verifyGeneratePartitionAssignment(tableConfig, numPartitions,
                consumingInstanceList, previousPartitionAssignment, exceptionExpected, unchanged);

        // 1 consuming instance - error not enough instances
        consumingInstanceList = getConsumingInstanceList(1);
        previousPartitionAssignment = verifyGeneratePartitionAssignment(tableConfig, numPartitions,
                consumingInstanceList, previousPartitionAssignment, exceptionExpected, unchanged);

        // 0 partitions - 3 consuming instances - empty partition assignment
        consumingInstanceList = getConsumingInstanceList(3);
        exceptionExpected = false;
        unchanged = true;
        previousPartitionAssignment = verifyGeneratePartitionAssignment(tableConfig, numPartitions,
                consumingInstanceList, previousPartitionAssignment, exceptionExpected, unchanged);

        // 3 partitions - 3 consuming instances
        numPartitions = 3;
        unchanged = false;
        previousPartitionAssignment = verifyGeneratePartitionAssignment(tableConfig, numPartitions,
                consumingInstanceList, previousPartitionAssignment, exceptionExpected, unchanged);

        // same - shouldn't change
        unchanged = true;
        previousPartitionAssignment = verifyGeneratePartitionAssignment(tableConfig, numPartitions,
                consumingInstanceList, previousPartitionAssignment, exceptionExpected, unchanged);

        // 3 partitions - 12 consuming instances
        consumingInstanceList = getConsumingInstanceList(12);
        unchanged = false;
        previousPartitionAssignment = verifyGeneratePartitionAssignment(tableConfig, numPartitions,
                consumingInstanceList, previousPartitionAssignment, exceptionExpected, unchanged);

        // same - shouldn't change
        unchanged = true;
        previousPartitionAssignment = verifyGeneratePartitionAssignment(tableConfig, numPartitions,
                consumingInstanceList, previousPartitionAssignment, exceptionExpected, unchanged);

        // 3 partitions - 6 consuming instances
        consumingInstanceList = getConsumingInstanceList(6);
        unchanged = false;
        previousPartitionAssignment = verifyGeneratePartitionAssignment(tableConfig, numPartitions,
                consumingInstanceList, previousPartitionAssignment, exceptionExpected, unchanged);

        // same - shouldn't change
        unchanged = true;
        previousPartitionAssignment = verifyGeneratePartitionAssignment(tableConfig, numPartitions,
                consumingInstanceList, previousPartitionAssignment, exceptionExpected, unchanged);

        String server0 = consumingInstanceList.get(0);
        consumingInstanceList.set(0, server0 + "_replaced");
        unchanged = false;
        previousPartitionAssignment = verifyGeneratePartitionAssignment(tableConfig, numPartitions,
                consumingInstanceList, previousPartitionAssignment, exceptionExpected, unchanged);

        // increase in partitions - 4
        numPartitions = 4;
        unchanged = false;
        previousPartitionAssignment = verifyGeneratePartitionAssignment(tableConfig, numPartitions,
                consumingInstanceList, previousPartitionAssignment, exceptionExpected, unchanged);

        // same - shouldn't change
        unchanged = true;
        previousPartitionAssignment = verifyGeneratePartitionAssignment(tableConfig, numPartitions,
                consumingInstanceList, previousPartitionAssignment, exceptionExpected, unchanged);
    }

    private PartitionAssignment verifyGeneratePartitionAssignment(TableConfig tableConfig, int numPartitions,
            List<String> consumingInstanceList, PartitionAssignment previousPartitionAssignment,
            boolean exceptionExpected, boolean unchanged) {
        String tableName = tableConfig.getTableName();
        TestPartitionAssignmentGenerator partitionAssignmentGenerator = new TestPartitionAssignmentGenerator(
                _mockHelixManager);
        partitionAssignmentGenerator.setConsumingInstances(consumingInstanceList);
        PartitionAssignment partitionAssignment;
        try {
            partitionAssignment = partitionAssignmentGenerator.generatePartitionAssignment(tableConfig,
                    numPartitions);
            Assert.assertFalse(exceptionExpected, "Unexpected exception for table " + tableName);
            verify(tableName, partitionAssignment, numPartitions, consumingInstanceList, unchanged,
                    previousPartitionAssignment);
        } catch (Exception e) {
            Assert.assertTrue(exceptionExpected, "Expected exception for table " + tableName);
            partitionAssignment = previousPartitionAssignment;
        }

        return partitionAssignment;
    }

    private void verify(String tableName, PartitionAssignment partitionAssignment, int numPartitions,
            List<String> consumingInstanceList, boolean unchanged,
            PartitionAssignment previousPartitionAssignment) {
        Assert.assertEquals(partitionAssignment.getTableName(), tableName);

        // check num partitions equal
        Assert.assertEquals(partitionAssignment.getNumPartitions(), numPartitions,
                "NumPartitions do not match for table " + tableName);

        List<String> instancesUsed = new ArrayList<>();
        for (int p = 0; p < partitionAssignment.getNumPartitions(); p++) {
            for (String instance : partitionAssignment.getInstancesListForPartition(String.valueOf(p))) {
                if (!instancesUsed.contains(instance)) {
                    instancesUsed.add(instance);
                }
            }
        }

        // check all instances belong to the super set
        Assert.assertTrue(consumingInstanceList.containsAll(instancesUsed),
                "Instances test failed for table " + tableName);

        // verify strategy is uniform
        int serverId = 0;
        for (int p = 0; p < partitionAssignment.getNumPartitions(); p++) {
            for (String instance : partitionAssignment.getInstancesListForPartition(String.valueOf(p))) {
                Assert.assertTrue(instance.equals(instancesUsed.get(serverId++)),
                        "Uniform strategy test failed for table " + tableName);
                if (serverId == instancesUsed.size()) {
                    serverId = 0;
                }
            }
        }

        // if nothing changed, should be same as before
        if (unchanged) {
            Assert.assertEquals(partitionAssignment, previousPartitionAssignment,
                    "Partition assignment should have been unchanged for table " + tableName);
        }
    }

    private class TestPartitionAssignmentGenerator extends PartitionAssignmentGenerator {

        private HelixManager _helixManager;
        private List<String> _consumingInstances;
        private RealtimeTagConfig _realtimeTagConfig;

        public TestPartitionAssignmentGenerator(HelixManager helixManager) {
            super(helixManager);
            _helixManager = helixManager;
            _consumingInstances = new ArrayList<>();
        }

        @Override
        protected List<String> getConsumingTaggedInstances(RealtimeTagConfig realtimeTagConfig) {
            return _consumingInstances;
        }

        void setConsumingInstances(List<String> consumingInstances) {
            _consumingInstances = consumingInstances;
        }
    }
}