/** * 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.broker.broker; import com.google.common.util.concurrent.Uninterruptibles; import com.linkedin.pinot.broker.broker.helix.DefaultHelixBrokerConfig; import com.linkedin.pinot.broker.broker.helix.HelixBrokerStarter; import com.linkedin.pinot.common.config.AbstractTableConfig; import com.linkedin.pinot.common.config.TableNameBuilder; import com.linkedin.pinot.common.segment.SegmentMetadata; import com.linkedin.pinot.common.utils.CommonConstants; import com.linkedin.pinot.common.utils.ZkStarter; import com.linkedin.pinot.controller.helix.ControllerRequestBuilderUtil; import com.linkedin.pinot.controller.helix.core.PinotHelixResourceManager; import com.linkedin.pinot.controller.helix.core.util.HelixSetupUtils; import com.linkedin.pinot.controller.helix.starter.HelixConfig; import com.linkedin.pinot.core.query.utils.SimpleSegmentMetadata; import com.linkedin.pinot.routing.HelixExternalViewBasedRouting; import com.linkedin.pinot.routing.ServerToSegmentSetMap; import java.io.BufferedReader; import java.io.InputStreamReader; import java.lang.reflect.Field; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import org.I0Itec.zkclient.ZkClient; import org.apache.commons.configuration.Configuration; import org.apache.helix.HelixAdmin; import org.apache.helix.HelixManager; import org.apache.helix.model.ExternalView; import org.apache.helix.model.IdealState; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; import org.testng.annotations.AfterTest; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; public class HelixBrokerStarterTest { private static final Logger LOGGER = LoggerFactory.getLogger(HelixBrokerStarterTest.class); private static final int SEGMENT_COUNT = 6; private PinotHelixResourceManager _pinotResourceManager; private final static String HELIX_CLUSTER_NAME = "TestHelixBrokerStarter"; private final static String DINING_TABLE_NAME = TableNameBuilder.OFFLINE.tableNameWithType("dining"); private final static String COFFEE_TABLE_NAME = TableNameBuilder.OFFLINE.tableNameWithType("coffee"); private ZkClient _zkClient; private HelixManager _helixZkManager; private HelixAdmin _helixAdmin; private HelixBrokerStarter _helixBrokerStarter; private ZkStarter.ZookeeperInstance _zookeeperInstance; @BeforeTest public void setUp() throws Exception { _zookeeperInstance = ZkStarter.startLocalZkServer(); _zkClient = new ZkClient(ZkStarter.DEFAULT_ZK_STR); final String instanceId = "localhost_helixController"; _pinotResourceManager = new PinotHelixResourceManager(ZkStarter.DEFAULT_ZK_STR, HELIX_CLUSTER_NAME, instanceId, null, 10000L, true, /*isUpdateStateModel=*/false); _pinotResourceManager.start(); final String helixZkURL = HelixConfig.getAbsoluteZkPathForHelix(ZkStarter.DEFAULT_ZK_STR); _helixZkManager = HelixSetupUtils.setup(HELIX_CLUSTER_NAME, helixZkURL, instanceId, /*isUpdateStateModel=*/false); _helixAdmin = _helixZkManager.getClusterManagmentTool(); Thread.sleep(3000); final Configuration pinotHelixBrokerProperties = DefaultHelixBrokerConfig.getDefaultBrokerConf(); pinotHelixBrokerProperties.addProperty(CommonConstants.Helix.KEY_OF_BROKER_QUERY_PORT, 8943); _helixBrokerStarter = new HelixBrokerStarter(HELIX_CLUSTER_NAME, ZkStarter.DEFAULT_ZK_STR, pinotHelixBrokerProperties); Thread.sleep(1000); ControllerRequestBuilderUtil.addFakeBrokerInstancesToAutoJoinHelixCluster(HELIX_CLUSTER_NAME, ZkStarter.DEFAULT_ZK_STR, 5, true); ControllerRequestBuilderUtil.addFakeDataInstancesToAutoJoinHelixCluster(HELIX_CLUSTER_NAME, ZkStarter.DEFAULT_ZK_STR, 1, true); final String tableName = "dining"; JSONObject buildCreateOfflineTableV2JSON = ControllerRequestBuilderUtil.buildCreateOfflineTableJSON(tableName, null, null, 1); AbstractTableConfig config = AbstractTableConfig.init(buildCreateOfflineTableV2JSON.toString()); _pinotResourceManager.addTable(config); for (int i = 1; i <= 5; i++) { addOneSegment(tableName); Thread.sleep(2000); final ExternalView externalView = _helixAdmin.getResourceExternalView(HELIX_CLUSTER_NAME, TableNameBuilder.OFFLINE.tableNameWithType(tableName)); Assert.assertEquals(externalView.getPartitionSet().size(), i); } } @AfterTest public void tearDown() { _pinotResourceManager.stop(); _zkClient.close(); ZkStarter.stopLocalZkServer(_zookeeperInstance); } @Test public void testResourceAndTagAssignment() throws Exception { IdealState idealState; Assert.assertEquals(_helixAdmin.getInstancesInClusterWithTag(HELIX_CLUSTER_NAME, "DefaultTenant_BROKER").size(), 6); idealState = _helixAdmin.getResourceIdealState(HELIX_CLUSTER_NAME, CommonConstants.Helix.BROKER_RESOURCE_INSTANCE); Assert.assertEquals(idealState.getInstanceSet(DINING_TABLE_NAME).size(), SEGMENT_COUNT); ExternalView externalView = _helixAdmin.getResourceExternalView(HELIX_CLUSTER_NAME, CommonConstants.Helix.BROKER_RESOURCE_INSTANCE); Assert.assertEquals(externalView.getStateMap(DINING_TABLE_NAME).size(), SEGMENT_COUNT); HelixExternalViewBasedRouting helixExternalViewBasedRouting = _helixBrokerStarter.getHelixExternalViewBasedRouting(); Field brokerRoutingTableField; brokerRoutingTableField = HelixExternalViewBasedRouting.class.getDeclaredField("_brokerRoutingTable"); brokerRoutingTableField.setAccessible(true); final Map<String, List<ServerToSegmentSetMap>> brokerRoutingTable = (Map<String, List<ServerToSegmentSetMap>>)brokerRoutingTableField.get(helixExternalViewBasedRouting); // Wait up to 30s for routing table to reach the expected size waitForPredicate(new Callable<Boolean>() { @Override public Boolean call() throws Exception { return brokerRoutingTable.size() == 1; } }, 30000L); Assert.assertEquals(Arrays.toString(brokerRoutingTable.keySet().toArray()), "[dining_OFFLINE]"); final String tableName = "coffee"; JSONObject buildCreateOfflineTableV2JSON = ControllerRequestBuilderUtil.buildCreateOfflineTableJSON(tableName, "testServer", "testBroker", 1); AbstractTableConfig config = AbstractTableConfig.init(buildCreateOfflineTableV2JSON.toString()); _pinotResourceManager.addTable(config); Assert.assertEquals(_helixAdmin.getInstancesInClusterWithTag(HELIX_CLUSTER_NAME, "DefaultTenant_BROKER").size(), 6); idealState = _helixAdmin.getResourceIdealState(HELIX_CLUSTER_NAME, CommonConstants.Helix.BROKER_RESOURCE_INSTANCE); Assert.assertEquals(idealState.getInstanceSet(COFFEE_TABLE_NAME).size(), SEGMENT_COUNT); Assert.assertEquals(idealState.getInstanceSet(DINING_TABLE_NAME).size(), SEGMENT_COUNT); // Wait up to 30s for broker external view to reach the expected size waitForPredicate(new Callable<Boolean>() { @Override public Boolean call() throws Exception { return _helixAdmin.getResourceExternalView(HELIX_CLUSTER_NAME, CommonConstants.Helix.BROKER_RESOURCE_INSTANCE) .getStateMap(COFFEE_TABLE_NAME).size() == SEGMENT_COUNT; } }, 30000L); externalView = _helixAdmin.getResourceExternalView(HELIX_CLUSTER_NAME, CommonConstants.Helix.BROKER_RESOURCE_INSTANCE); Assert.assertEquals(externalView.getStateMap(COFFEE_TABLE_NAME).size(), SEGMENT_COUNT); // Wait up to 30s for routing table to reach the expected size waitForPredicate(new Callable<Boolean>() { @Override public Boolean call() throws Exception { return brokerRoutingTable.size() == 2; } }, 30000L); Object[] tableArray = brokerRoutingTable.keySet().toArray(); Arrays.sort(tableArray); Assert.assertEquals(Arrays.toString(tableArray), "[coffee_OFFLINE, dining_OFFLINE]"); Set<String> serverSet = brokerRoutingTable.get(DINING_TABLE_NAME).get(0).getServerSet(); Assert.assertEquals(brokerRoutingTable.get(DINING_TABLE_NAME).get(0) .getSegmentSet(serverSet.iterator().next()).size(), 5); final String dataResource = DINING_TABLE_NAME; addOneSegment(dataResource); // Wait up to 30s for external view to reach the expected size waitForPredicate(new Callable<Boolean>() { @Override public Boolean call() throws Exception { return _helixAdmin.getResourceExternalView(HELIX_CLUSTER_NAME, DINING_TABLE_NAME).getPartitionSet().size() == SEGMENT_COUNT; } }, 30000L); externalView = _helixAdmin.getResourceExternalView(HELIX_CLUSTER_NAME, DINING_TABLE_NAME); Assert.assertEquals(externalView.getPartitionSet().size(), SEGMENT_COUNT); tableArray = brokerRoutingTable.keySet().toArray(); Arrays.sort(tableArray); Assert.assertEquals(Arrays.toString(tableArray), "[coffee_OFFLINE, dining_OFFLINE]"); // Wait up to 30s for routing table to reach the expected size waitForPredicate(new Callable<Boolean>() { @Override public Boolean call() throws Exception { ServerToSegmentSetMap routingTable = brokerRoutingTable.get(DINING_TABLE_NAME).get(0); String firstServer = routingTable.getServerSet().iterator().next(); return routingTable.getSegmentSet(firstServer).size() == SEGMENT_COUNT; } }, 30000L); serverSet = brokerRoutingTable.get(DINING_TABLE_NAME).get(0).getServerSet(); Assert.assertEquals(brokerRoutingTable.get(DINING_TABLE_NAME).get(0) .getSegmentSet(serverSet.iterator().next()).size(), SEGMENT_COUNT); } private void waitForPredicate(Callable<Boolean> predicate, long timeout) { long deadline = System.currentTimeMillis() + timeout; while (System.currentTimeMillis() < deadline) { try { if (predicate.call()) { return; } } catch (Exception e) { // Do nothing } Uninterruptibles.sleepUninterruptibly(500, TimeUnit.MILLISECONDS); } } public void testWithCmdLines() throws Exception { final BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); while (true) { final String command = br.readLine(); if ((command != null) && command.equals("exit")) { tearDown(); } } } private void addOneSegment(String tableName) { final SegmentMetadata segmentMetadata = new SimpleSegmentMetadata(tableName); LOGGER.info("Trying to add IndexSegment : " + segmentMetadata.getName()); _pinotResourceManager.addSegment(segmentMetadata, "http://localhost:something"); } }