/**
* 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.tools.admin.command;
import com.linkedin.pinot.common.config.AbstractTableConfig;
import com.linkedin.pinot.common.data.Schema;
import com.linkedin.pinot.common.utils.SchemaUtils;
import com.linkedin.pinot.tools.AbstractBaseCommand;
import com.linkedin.pinot.tools.Command;
import com.linkedin.pinot.tools.config.validator.SchemaValidator;
import com.linkedin.pinot.tools.config.validator.TableConfigValidator;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.helix.PropertyPathConfig;
import org.apache.helix.PropertyType;
import org.apache.helix.ZNRecord;
import org.apache.helix.manager.zk.ZNRecordSerializer;
import org.apache.helix.store.zk.ZkHelixPropertyStore;
import org.kohsuke.args4j.Option;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class for command to validate configs.
* <p>Validate the following configs:
* <ul>
* <li>Table Config</li>
* <li>Schema</li>
* </ul>
*/
public class ValidateConfigCommand extends AbstractBaseCommand implements Command {
private static final Logger LOGGER = LoggerFactory.getLogger(ValidateConfigCommand.class);
private static final String TABLE_CONFIG_PATH = "/CONFIGS/TABLE";
private static final String SCHEMA_PATH = "/SCHEMAS";
private ZkHelixPropertyStore<ZNRecord> _helixPropertyStore;
@Option(name = "-zkAddress", required = true, metaVar = "<string>", usage = "Zookeeper address")
private String _zkAddress;
@Option(name = "-cluster", required = true, metaVar = "<string>", usage = "Cluster name")
private String _clusterName;
@Option(name = "-tableConfig", required = false, usage = "Validate the table config")
private boolean _validateTableConfig;
@Option(name = "-tableNames", required = false, metaVar = "<string>",
usage = "Space separated table names to be validated (default to validate ALL)")
private String _tableNames;
@Option(name = "-schema", required = false, usage = "Validate the schema")
private boolean _validateSchema;
@Option(name = "-schemaNames", required = false, metaVar = "<string",
usage = "Space separated schema names to be validated (default to validate ALL)")
private String _schemaNames;
@Option(name = "-help", required = false, help = true, aliases = {"-h", "--h", "--help"},
usage = "Print this message.")
private boolean _help;
@Override
public boolean getHelp() {
return _help;
}
@Override
public String getName() {
return "ValidateConfig";
}
@Override
public String description() {
return "Validate configs on the specified Zookeeper.";
}
@Override
public boolean execute()
throws Exception {
if (!_validateTableConfig && !_validateSchema) {
throw new RuntimeException("Need to specify at least one of -schema and -tableConfig");
}
LOGGER.info("Connecting to Zookeeper: {}, cluster: ", _zkAddress, _clusterName);
ZNRecordSerializer serializer = new ZNRecordSerializer();
String path = PropertyPathConfig.getPath(PropertyType.PROPERTYSTORE, _clusterName);
_helixPropertyStore = new ZkHelixPropertyStore<>(_zkAddress, serializer, path);
LOGGER.info("\n\n-------------------- Starting Validation --------------------");
if (_validateTableConfig) {
validateTableConfig();
}
if (_validateSchema) {
validateSchema();
}
return true;
}
private void validateTableConfig()
throws Exception {
List<String> tableNames = getTableNames();
LOGGER.info("Validating table config for tables: " + tableNames);
for (String tableName : tableNames) {
LOGGER.info(" Validating table config for table: \"{}\"", tableName);
try {
ZNRecord record = _helixPropertyStore.get(TABLE_CONFIG_PATH + "/" + tableName, null, 0);
AbstractTableConfig tableConfig = AbstractTableConfig.fromZnRecord(record);
if (!TableConfigValidator.validate(tableConfig)) {
LOGGER.error(" Table config validation failed for table: \"{}\"", tableName);
}
} catch (Exception e) {
LOGGER.error(" Caught exception while validating table config for table: \"{}\"", tableName, e);
}
}
}
private void validateSchema()
throws Exception {
List<String> schemaNames = getSchemaNames();
LOGGER.info("Validating schemas: " + schemaNames);
for (String schemaName : schemaNames) {
LOGGER.info(" Validating schema: \"{}\"", schemaName);
try {
ZNRecord record = _helixPropertyStore.get(SCHEMA_PATH + "/" + schemaName, null, 0);
Schema schema = SchemaUtils.fromZNRecord(record);
if (!SchemaValidator.validate(schema)) {
LOGGER.error(" Schema validation failed for schema: \"{}\"", schemaName);
}
} catch (Exception e) {
LOGGER.error(" Caught exception while validating schema: \"{}\"", schemaName, e);
}
}
}
private List<String> getTableNames()
throws Exception {
if (_tableNames == null) {
// Get all table names.
return _helixPropertyStore.getChildNames(TABLE_CONFIG_PATH, 0);
} else {
// Extract space separated table names.
Set<String> tableNames = new HashSet<>();
for (String tableName : _tableNames.split(" ")) {
if (!tableName.isEmpty()) {
tableNames.add(tableName);
}
}
if (tableNames.size() == 0) {
throw new RuntimeException("No table name specified.");
}
return new ArrayList<>(tableNames);
}
}
private List<String> getSchemaNames()
throws Exception {
if (_schemaNames == null) {
// Get all schema names.
return _helixPropertyStore.getChildNames(SCHEMA_PATH, 0);
} else {
// Extract space separated schema names.
Set<String> schemaNames = new HashSet<>();
for (String schemaName : _schemaNames.split(" ")) {
if (!schemaName.isEmpty()) {
schemaNames.add(schemaName);
}
}
if (schemaNames.size() == 0) {
throw new RuntimeException("No schema name specified.");
}
return new ArrayList<>(schemaNames);
}
}
}