/** * 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.controller.helix.core.util; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import org.apache.helix.AccessOption; import org.apache.helix.HelixAdmin; import org.apache.helix.HelixDataAccessor; import org.apache.helix.HelixManager; import org.apache.helix.PropertyKey; import org.apache.helix.PropertyPathConfig; import org.apache.helix.PropertyType; import org.apache.helix.ZNRecord; import org.apache.helix.controller.HelixControllerMain; import org.apache.helix.manager.zk.ZKHelixAdmin; import org.apache.helix.manager.zk.ZKHelixDataAccessor; import org.apache.helix.manager.zk.ZKHelixManager; import org.apache.helix.manager.zk.ZNRecordSerializer; import org.apache.helix.manager.zk.ZkBaseDataAccessor; import org.apache.helix.manager.zk.ZkClient; import org.apache.helix.messaging.handling.HelixTaskExecutor; import org.apache.helix.model.HelixConfigScope; import org.apache.helix.model.HelixConfigScope.ConfigScopeProperty; import org.apache.helix.model.IdealState; import org.apache.helix.model.Message.MessageType; import org.apache.helix.model.StateModelDefinition; import org.apache.helix.model.builder.HelixConfigScopeBuilder; import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.linkedin.pinot.common.utils.CommonConstants; import com.linkedin.pinot.common.utils.helix.HelixHelper; import com.linkedin.pinot.controller.helix.core.PinotHelixBrokerResourceOnlineOfflineStateModelGenerator; import com.linkedin.pinot.controller.helix.core.PinotHelixSegmentOnlineOfflineStateModelGenerator; import com.linkedin.pinot.controller.helix.core.PinotTableIdealStateBuilder; /** * HelixSetupUtils handles how to create or get a helixCluster in controller. * * */ public class HelixSetupUtils { private static final Logger LOGGER = LoggerFactory.getLogger(HelixSetupUtils.class); public static synchronized HelixManager setup(String helixClusterName, String zkPath, String pinotControllerInstanceId, boolean isUpdateStateModel) { try { createHelixClusterIfNeeded(helixClusterName, zkPath, isUpdateStateModel); } catch (final Exception e) { LOGGER.error("Caught exception", e); return null; } try { return startHelixControllerInStandadloneMode(helixClusterName, zkPath, pinotControllerInstanceId); } catch (final Exception e) { LOGGER.error("Caught exception", e); return null; } } public static void createHelixClusterIfNeeded(String helixClusterName, String zkPath) { createHelixClusterIfNeeded(helixClusterName, zkPath); } public static void createHelixClusterIfNeeded(String helixClusterName, String zkPath, boolean isUpdateStateModel) { final HelixAdmin admin = new ZKHelixAdmin(zkPath); final String segmentStateModelName = PinotHelixSegmentOnlineOfflineStateModelGenerator.PINOT_SEGMENT_ONLINE_OFFLINE_STATE_MODEL; if (admin.getClusters().contains(helixClusterName)) { LOGGER.info("cluster already exists ********************************************* "); if (isUpdateStateModel) { final StateModelDefinition curStateModelDef = admin.getStateModelDef(helixClusterName, segmentStateModelName); List<String> states = curStateModelDef.getStatesPriorityList(); if (states.contains(PinotHelixSegmentOnlineOfflineStateModelGenerator.CONSUMING_STATE)) { LOGGER.info("State model {} already updated to contain CONSUMING state", segmentStateModelName); return; } else { LOGGER.info("Updating {} to add states for low level kafka consumers", segmentStateModelName); StateModelDefinition newStateModelDef = PinotHelixSegmentOnlineOfflineStateModelGenerator.generatePinotStateModelDefinition(); ZkClient zkClient = new ZkClient(zkPath); zkClient.waitUntilConnected(20, TimeUnit.SECONDS); zkClient.setZkSerializer(new ZNRecordSerializer()); HelixDataAccessor accessor = new ZKHelixDataAccessor(helixClusterName, new ZkBaseDataAccessor<ZNRecord>(zkClient)); PropertyKey.Builder keyBuilder = accessor.keyBuilder(); accessor.setProperty(keyBuilder.stateModelDef(segmentStateModelName), newStateModelDef); LOGGER.info("Completed updating statemodel {}", segmentStateModelName); zkClient.close(); } } return; } LOGGER.info("Creating a new cluster, as the helix cluster : " + helixClusterName + " was not found ********************************************* "); admin.addCluster(helixClusterName, false); LOGGER.info("Enable auto join."); final HelixConfigScope scope = new HelixConfigScopeBuilder(ConfigScopeProperty.CLUSTER).forCluster(helixClusterName).build(); final Map<String, String> props = new HashMap<String, String>(); props.put(ZKHelixManager.ALLOW_PARTICIPANT_AUTO_JOIN, String.valueOf(true)); //we need only one segment to be loaded at a time props.put(MessageType.STATE_TRANSITION + "." + HelixTaskExecutor.MAX_THREADS, String.valueOf(1)); admin.setConfig(scope, props); LOGGER.info("Adding state model {} (with CONSUMED state) generated using {} **********************************************", segmentStateModelName , PinotHelixSegmentOnlineOfflineStateModelGenerator.class.toString()); // If this is a fresh cluster we are creating, then the cluster will see the CONSUMING state in the // state model. But then the servers will never be asked to go to that STATE (whether they have the code // to handle it or not) unil we complete the feature using low-level kafka consumers and turn the feature on. admin.addStateModelDef(helixClusterName, segmentStateModelName, PinotHelixSegmentOnlineOfflineStateModelGenerator.generatePinotStateModelDefinition()); LOGGER.info("Adding state model definition named : " + PinotHelixBrokerResourceOnlineOfflineStateModelGenerator.PINOT_BROKER_RESOURCE_ONLINE_OFFLINE_STATE_MODEL + " generated using : " + PinotHelixBrokerResourceOnlineOfflineStateModelGenerator.class.toString() + " ********************************************** "); admin.addStateModelDef(helixClusterName, PinotHelixBrokerResourceOnlineOfflineStateModelGenerator.PINOT_BROKER_RESOURCE_ONLINE_OFFLINE_STATE_MODEL, PinotHelixBrokerResourceOnlineOfflineStateModelGenerator.generatePinotStateModelDefinition()); LOGGER.info("Adding empty ideal state for Broker!"); HelixHelper.updateResourceConfigsFor(new HashMap<String, String>(), CommonConstants.Helix.BROKER_RESOURCE_INSTANCE, helixClusterName, admin); IdealState idealState = PinotTableIdealStateBuilder.buildEmptyIdealStateForBrokerResource(admin, helixClusterName); admin.setResourceIdealState(helixClusterName, CommonConstants.Helix.BROKER_RESOURCE_INSTANCE, idealState); initPropertyStorePath(helixClusterName, zkPath); LOGGER.info("New Cluster setup completed... ********************************************** "); } private static void initPropertyStorePath(String helixClusterName, String zkPath) { String propertyStorePath = PropertyPathConfig.getPath(PropertyType.PROPERTYSTORE, helixClusterName); ZkHelixPropertyStore<ZNRecord> propertyStore = new ZkHelixPropertyStore<ZNRecord>(zkPath, new ZNRecordSerializer(), propertyStorePath); propertyStore.create("/CONFIGS", new ZNRecord(""), AccessOption.PERSISTENT); propertyStore.create("/CONFIGS/CLUSTER", new ZNRecord(""), AccessOption.PERSISTENT); propertyStore.create("/CONFIGS/TABLE", new ZNRecord(""), AccessOption.PERSISTENT); propertyStore.create("/CONFIGS/INSTANCE", new ZNRecord(""), AccessOption.PERSISTENT); propertyStore.create("/SCHEMAS", new ZNRecord(""), AccessOption.PERSISTENT); propertyStore.create("/SEGMENTS", new ZNRecord(""), AccessOption.PERSISTENT); } private static HelixManager startHelixControllerInStandadloneMode(String helixClusterName, String zkUrl, String pinotControllerInstanceId) { LOGGER.info("Starting Helix Standalone Controller ... "); return HelixControllerMain.startHelixController(zkUrl, helixClusterName, pinotControllerInstanceId, HelixControllerMain.STANDALONE); } }