/**
* 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.metadata;
import com.linkedin.pinot.common.config.AbstractTableConfig;
import com.linkedin.pinot.common.config.TableNameBuilder;
import com.linkedin.pinot.common.data.Schema;
import com.linkedin.pinot.common.metadata.instance.InstanceZKMetadata;
import com.linkedin.pinot.common.metadata.segment.LLCRealtimeSegmentZKMetadata;
import com.linkedin.pinot.common.metadata.segment.OfflineSegmentZKMetadata;
import com.linkedin.pinot.common.metadata.segment.RealtimeSegmentZKMetadata;
import com.linkedin.pinot.common.utils.SchemaUtils;
import com.linkedin.pinot.common.utils.SegmentName;
import com.linkedin.pinot.common.utils.StringUtil;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.helix.AccessOption;
import org.apache.helix.ZNRecord;
import org.apache.helix.store.zk.ZkHelixPropertyStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ZKMetadataProvider {
private ZKMetadataProvider() {
}
private static final Logger LOGGER = LoggerFactory.getLogger(ZKMetadataProvider.class);
private static final String CLUSTER_TENANT_ISOLATION_ENABLED_KEY = "tenantIsolationEnabled";
private static final String PROPERTYSTORE_SEGMENTS_PREFIX = "/SEGMENTS";
private static final String PROPERTYSTORE_SCHEMAS_PREFIX = "/SCHEMAS";
private static final String PROPERTYSTORE_KAFKA_PARTITIONS_PREFIX = "/KAFKA_PARTITIONS";
private static final String PROPERTYSTORE_TABLE_CONFIGS_PREFIX = "/CONFIGS/TABLE";
private static final String PROPERTYSTORE_INSTANCE_CONFIGS_PREFIX = "/CONFIGS/INSTANCE";
private static final String PROPERTYSTORE_CLUSTER_CONFIGS_PREFIX = "/CONFIGS/CLUSTER";
public static void setRealtimeTableConfig(ZkHelixPropertyStore<ZNRecord> propertyStore, String realtimeTableName, ZNRecord znRecord) {
propertyStore.set(constructPropertyStorePathForResourceConfig(realtimeTableName), znRecord, AccessOption.PERSISTENT);
}
public static void setOfflineTableConfig(ZkHelixPropertyStore<ZNRecord> propertyStore, String offlineTableName, ZNRecord znRecord) {
propertyStore.set(constructPropertyStorePathForResourceConfig(offlineTableName), znRecord, AccessOption.PERSISTENT);
}
public static void setInstanceZKMetadata(ZkHelixPropertyStore<ZNRecord> propertyStore, InstanceZKMetadata instanceZKMetadata) {
ZNRecord znRecord = instanceZKMetadata.toZNRecord();
propertyStore.set(StringUtil.join("/", PROPERTYSTORE_INSTANCE_CONFIGS_PREFIX, instanceZKMetadata.getId()), znRecord, AccessOption.PERSISTENT);
}
public static InstanceZKMetadata getInstanceZKMetadata(ZkHelixPropertyStore<ZNRecord> propertyStore, String instanceId) {
ZNRecord znRecord = propertyStore.get(StringUtil.join("/", PROPERTYSTORE_INSTANCE_CONFIGS_PREFIX, instanceId), null,
AccessOption.PERSISTENT);
if (znRecord == null) {
return null;
}
return new InstanceZKMetadata(znRecord);
}
public static String constructPropertyStorePathForSegment(String resourceName, String segmentName) {
return StringUtil.join("/", PROPERTYSTORE_SEGMENTS_PREFIX, resourceName, segmentName);
}
public static String constructPropertyStorePathForSchema(String schemaName) {
return StringUtil.join("/", PROPERTYSTORE_SCHEMAS_PREFIX, schemaName);
}
public static String constructPropertyStorePathForKafkaPartitions(String realtimeTableName) {
return StringUtil.join("/", PROPERTYSTORE_KAFKA_PARTITIONS_PREFIX, realtimeTableName);
}
public static String constructPropertyStorePathForResource(String resourceName) {
return StringUtil.join("/", PROPERTYSTORE_SEGMENTS_PREFIX, resourceName);
}
public static String constructPropertyStorePathForResourceConfig(String resourceName) {
return StringUtil.join("/", PROPERTYSTORE_TABLE_CONFIGS_PREFIX, resourceName);
}
public static String constructPropertyStorePathForControllerConfig(String controllerConfigKey) {
return StringUtil.join("/", PROPERTYSTORE_CLUSTER_CONFIGS_PREFIX, controllerConfigKey);
}
public static boolean isSegmentExisted(ZkHelixPropertyStore<ZNRecord> propertyStore, String resourceNameForResource, String segmentName) {
return propertyStore.exists(constructPropertyStorePathForSegment(resourceNameForResource, segmentName),
AccessOption.PERSISTENT);
}
public static void removeResourceSegmentsFromPropertyStore(ZkHelixPropertyStore<ZNRecord> propertyStore, String resourceName) {
String propertyStorePath = constructPropertyStorePathForResource(resourceName);
if (propertyStore.exists(propertyStorePath, AccessOption.PERSISTENT)) {
propertyStore.remove(propertyStorePath, AccessOption.PERSISTENT);
}
}
public static void removeResourceConfigFromPropertyStore(ZkHelixPropertyStore<ZNRecord> propertyStore, String resourceName) {
String propertyStorePath = constructPropertyStorePathForResourceConfig(resourceName);
if (propertyStore.exists(propertyStorePath, AccessOption.PERSISTENT)) {
propertyStore.remove(propertyStorePath, AccessOption.PERSISTENT);
}
}
public static void removeKafkaPartitionAssignmentFromPropertyStore(ZkHelixPropertyStore<ZNRecord> propertyStore, String realtimeTableName) {
String propertyStorePath = constructPropertyStorePathForKafkaPartitions(realtimeTableName);
if (propertyStore.exists(propertyStorePath, AccessOption.PERSISTENT)) {
propertyStore.remove(propertyStorePath, AccessOption.PERSISTENT);
}
}
public static void setOfflineSegmentZKMetadata(ZkHelixPropertyStore<ZNRecord> propertyStore,
OfflineSegmentZKMetadata offlineSegmentZKMetadata) {
propertyStore.set(constructPropertyStorePathForSegment(
TableNameBuilder.OFFLINE.tableNameWithType(offlineSegmentZKMetadata.getTableName()),
offlineSegmentZKMetadata.getSegmentName()), offlineSegmentZKMetadata.toZNRecord(), AccessOption.PERSISTENT);
}
public static void setRealtimeSegmentZKMetadata(ZkHelixPropertyStore<ZNRecord> propertyStore,
RealtimeSegmentZKMetadata realtimeSegmentZKMetadata) {
propertyStore.set(constructPropertyStorePathForSegment(
TableNameBuilder.REALTIME.tableNameWithType(realtimeSegmentZKMetadata.getTableName()),
realtimeSegmentZKMetadata.getSegmentName()), realtimeSegmentZKMetadata.toZNRecord(), AccessOption.PERSISTENT);
}
@Nullable
public static OfflineSegmentZKMetadata getOfflineSegmentZKMetadata(
@Nonnull ZkHelixPropertyStore<ZNRecord> propertyStore, @Nonnull String tableName, @Nonnull String segmentName) {
String offlineTableName = TableNameBuilder.OFFLINE.tableNameWithType(tableName);
ZNRecord znRecord = propertyStore.get(constructPropertyStorePathForSegment(offlineTableName, segmentName), null,
AccessOption.PERSISTENT);
if (znRecord == null) {
return null;
}
return new OfflineSegmentZKMetadata(znRecord);
}
@Nullable
public static RealtimeSegmentZKMetadata getRealtimeSegmentZKMetadata(
@Nonnull ZkHelixPropertyStore<ZNRecord> propertyStore, @Nonnull String tableName, @Nonnull String segmentName) {
String realtimeTableName = TableNameBuilder.REALTIME.tableNameWithType(tableName);
ZNRecord znRecord = propertyStore.get(constructPropertyStorePathForSegment(realtimeTableName, segmentName), null,
AccessOption.PERSISTENT);
// It is possible that the segment metadata has just been deleted due to retention.
if (znRecord == null) {
return null;
}
if (SegmentName.isHighLevelConsumerSegmentName(segmentName)) {
return new RealtimeSegmentZKMetadata(znRecord);
} else {
return new LLCRealtimeSegmentZKMetadata(znRecord);
}
}
@Nullable
public static AbstractTableConfig getOfflineTableConfig(@Nonnull ZkHelixPropertyStore<ZNRecord> propertyStore,
@Nonnull String tableName) {
String offlineTableName = TableNameBuilder.OFFLINE.tableNameWithType(tableName);
ZNRecord znRecord =
propertyStore.get(constructPropertyStorePathForResourceConfig(offlineTableName), null, AccessOption.PERSISTENT);
if (znRecord == null) {
return null;
}
try {
return AbstractTableConfig.fromZnRecord(znRecord);
} catch (Exception e) {
LOGGER.error("Caught exception while getting offline table configuration for table: {}", tableName, e);
return null;
}
}
@Nullable
public static AbstractTableConfig getRealtimeTableConfig(@Nonnull ZkHelixPropertyStore<ZNRecord> propertyStore,
@Nonnull String tableName) {
String realtimeTableName = TableNameBuilder.REALTIME.tableNameWithType(tableName);
ZNRecord znRecord = propertyStore.get(constructPropertyStorePathForResourceConfig(realtimeTableName), null,
AccessOption.PERSISTENT);
if (znRecord == null) {
return null;
}
try {
return AbstractTableConfig.fromZnRecord(znRecord);
} catch (Exception e) {
LOGGER.error("Caught exception while getting realtime table configuration for table: {}", tableName, e);
return null;
}
}
@Nullable
public static Schema getSchema(@Nonnull ZkHelixPropertyStore<ZNRecord> propertyStore, @Nonnull String schemaName) {
try {
ZNRecord schemaZNRecord =
propertyStore.get(constructPropertyStorePathForSchema(schemaName), null, AccessOption.PERSISTENT);
if (schemaZNRecord == null) {
return null;
}
return SchemaUtils.fromZNRecord(schemaZNRecord);
} catch (Exception e) {
LOGGER.error("Caught exception while getting schema: {}", schemaName, e);
return null;
}
}
@Nullable
public static Schema getOfflineTableSchema(@Nonnull ZkHelixPropertyStore<ZNRecord> propertyStore,
@Nonnull String tableName) {
// TODO: After uniform schema, always use raw table name
String offlineTableName = TableNameBuilder.OFFLINE.tableNameWithType(tableName);
// First try to get schema using offline table name
Schema schema = getSchema(propertyStore, offlineTableName);
if (schema != null) {
return schema;
}
// If cannot find schema using offline table name, try with raw table name
return getSchema(propertyStore, TableNameBuilder.extractRawTableName(offlineTableName));
}
@Nullable
public static Schema getRealtimeTableSchema(@Nonnull ZkHelixPropertyStore<ZNRecord> propertyStore,
@Nonnull String tableName) {
// TODO: After uniform schema, get schema using raw table name
String realtimeTableName = TableNameBuilder.REALTIME.tableNameWithType(tableName);
try {
AbstractTableConfig realtimeTableConfig = getRealtimeTableConfig(propertyStore, realtimeTableName);
if (realtimeTableConfig == null) {
return null;
}
String schemaName = realtimeTableConfig.getValidationConfig().getSchemaName();
return getSchema(propertyStore, schemaName);
} catch (Exception e) {
LOGGER.error("Caught exception while getting schema for realtime table: {}", realtimeTableName);
return null;
}
}
public static List<OfflineSegmentZKMetadata> getOfflineSegmentZKMetadataListForTable(
ZkHelixPropertyStore<ZNRecord> propertyStore, String tableName) {
List<OfflineSegmentZKMetadata> resultList = new ArrayList<>();
if (propertyStore == null) {
return resultList;
}
String offlineTableName = TableNameBuilder.OFFLINE.tableNameWithType(tableName);
if (propertyStore.exists(constructPropertyStorePathForResource(offlineTableName), AccessOption.PERSISTENT)) {
List<ZNRecord> znRecordList =
propertyStore.getChildren(constructPropertyStorePathForResource(offlineTableName), null,
AccessOption.PERSISTENT);
if (znRecordList != null) {
for (ZNRecord record : znRecordList) {
resultList.add(new OfflineSegmentZKMetadata(record));
}
}
}
return resultList;
}
public static List<RealtimeSegmentZKMetadata> getRealtimeSegmentZKMetadataListForTable(
ZkHelixPropertyStore<ZNRecord> propertyStore, String resourceName) {
List<RealtimeSegmentZKMetadata> resultList = new ArrayList<>();
if (propertyStore == null) {
return resultList;
}
String realtimeTableName = TableNameBuilder.REALTIME.tableNameWithType(resourceName);
if (propertyStore.exists(constructPropertyStorePathForResource(realtimeTableName), AccessOption.PERSISTENT)) {
List<ZNRecord> znRecordList =
propertyStore.getChildren(constructPropertyStorePathForResource(realtimeTableName), null,
AccessOption.PERSISTENT);
if (znRecordList != null) {
for (ZNRecord record : znRecordList) {
resultList.add(new RealtimeSegmentZKMetadata(record));
}
}
}
return resultList;
}
public static void setClusterTenantIsolationEnabled(ZkHelixPropertyStore<ZNRecord> propertyStore, boolean isSingleTenantCluster) {
final ZNRecord znRecord;
final String path = constructPropertyStorePathForControllerConfig(CLUSTER_TENANT_ISOLATION_ENABLED_KEY);
if (!propertyStore.exists(path, AccessOption.PERSISTENT)) {
znRecord = new ZNRecord(CLUSTER_TENANT_ISOLATION_ENABLED_KEY);
} else {
znRecord = propertyStore.get(path, null, AccessOption.PERSISTENT);
}
znRecord.setBooleanField(CLUSTER_TENANT_ISOLATION_ENABLED_KEY, isSingleTenantCluster);
propertyStore.set(path, znRecord, AccessOption.PERSISTENT);
}
public static Boolean getClusterTenantIsolationEnabled(ZkHelixPropertyStore<ZNRecord> propertyStore) {
String controllerConfigPath = constructPropertyStorePathForControllerConfig(CLUSTER_TENANT_ISOLATION_ENABLED_KEY);
if (propertyStore.exists(controllerConfigPath, AccessOption.PERSISTENT)) {
ZNRecord znRecord = propertyStore.get(controllerConfigPath, null, AccessOption.PERSISTENT);
if (znRecord.getSimpleFields().keySet().contains(CLUSTER_TENANT_ISOLATION_ENABLED_KEY)) {
return znRecord.getBooleanField(CLUSTER_TENANT_ISOLATION_ENABLED_KEY, true);
} else {
return true;
}
} else {
return true;
}
}
}