package com.netflix.priam.dse; import java.io.*; import java.nio.charset.Charset; import java.util.*; import com.google.common.base.Joiner; import com.google.common.io.Files; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.inject.Inject; import com.netflix.priam.IConfiguration; import com.netflix.priam.defaultimpl.StandardTuner; import org.apache.cassandra.io.util.FileUtils; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; import static com.netflix.priam.dse.IDseConfiguration.NodeType; import static org.apache.cassandra.locator.SnitchProperties.RACKDC_PROPERTY_FILENAME; /** * Makes Datastax Enterprise-specific changes to the c* yaml and dse-yaml. * * @author jason brown */ public class DseTuner extends StandardTuner { private static final Logger logger = LoggerFactory.getLogger(DseTuner.class); protected static final String AUDIT_LOG_FILE = "/conf/log4j-server.properties"; protected static final String PRIMARY_AUDIT_LOG_ENTRY = "log4j.logger.DataAudit"; protected static final String AUDIT_LOG_ADDITIVE_ENTRY = "log4j.additivity.DataAudit"; private final IDseConfiguration dseConfig; @Inject public DseTuner(IConfiguration config, IDseConfiguration dseConfig) { super(config); this.dseConfig = dseConfig; } public void writeAllProperties(String yamlLocation, String hostname, String seedProvider) throws IOException { super.writeAllProperties(yamlLocation, hostname, seedProvider); writeDseYaml(); writeCassandraSnitchProperties(); writeAuditLogProperties(); } private void writeDseYaml() throws IOException { DumperOptions options = new DumperOptions(); options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); Yaml yaml = new Yaml(options); String dseYaml = dseConfig.getDseYamlLocation(); @SuppressWarnings("rawtypes") Map map = (Map) yaml.load(new FileInputStream(dseYaml)); map.put("delegated_snitch", config.getSnitch()); logger.info("Updating dse-yaml:\n" + yaml.dump(map)); yaml.dump(map, new FileWriter(dseYaml)); } private void writeCassandraSnitchProperties() { final NodeType nodeType = dseConfig.getNodeType(); if(nodeType == NodeType.REAL_TIME_QUERY) return; Reader reader = null; try { String filePath = config.getCassHome() + "/conf/" + RACKDC_PROPERTY_FILENAME; reader = new FileReader(filePath); Properties properties = new Properties(); properties.load(reader); String suffix = ""; if(nodeType == NodeType.SEARCH) suffix = "_solr"; if(nodeType == NodeType.ANALYTIC) suffix = "_hadoop"; properties.put("dc_suffix", suffix); properties.store(new FileWriter(filePath), ""); } catch (Exception e) { throw new RuntimeException("Unable to read " + RACKDC_PROPERTY_FILENAME, e); } finally { FileUtils.closeQuietly(reader); } } /** * Note: supporting the direct hacking of a log4j props file is far from elegant, * but seems less odious than other solutions I've come up with. * Operates under the assumption that the only people mucking with the audit log * entries in the value are DataStax themselves and this program, and that the original * property names are somehow still preserved. Otherwise, YMMV. */ protected void writeAuditLogProperties() { BufferedWriter writer = null; try { final File srcFile = new File(config.getCassHome() + AUDIT_LOG_FILE); final List<String> lines = Files.readLines(srcFile, Charset.defaultCharset()); final File backupFile = new File(config.getCassHome() + AUDIT_LOG_FILE + "." + System.currentTimeMillis()); Files.move(srcFile, backupFile); writer = Files.newWriter(srcFile, Charset.defaultCharset()); String loggerPrefix = "log4j.appender."; try { loggerPrefix += findAuditLoggerName(lines); } catch (IllegalStateException ise) { logger.warn(String.format("cannot locate %s property, will ignore any audit log updating", PRIMARY_AUDIT_LOG_ENTRY)); return; } for(String line : lines) { if(line.contains(loggerPrefix) || line.contains(PRIMARY_AUDIT_LOG_ENTRY) || line.contains(AUDIT_LOG_ADDITIVE_ENTRY)) { if(dseConfig.isAuditLogEnabled()) { //first, check to see if we need to uncomment the line while(line.startsWith("#")) { line = line.substring(1); } //next, check if we need to change the prop's value if(line.contains("ActiveCategories")) { final String cats = Joiner.on(",").join(dseConfig.getAuditLogCategories()); line = line.substring(0, line.indexOf("=") + 1).concat(cats); } else if(line.contains("ExemptKeyspaces")) { line = line.substring(0, line.indexOf("=") + 1).concat(dseConfig.getAuditLogExemptKeyspaces()); } } else { if(line.startsWith("#")) { //make sure there's only one # at the beginning of the line while(line.charAt(1) == '#') line = line.substring(1); } else { line = "#" + line; } } } writer.append(line); writer.newLine(); } } catch (Exception e) { throw new RuntimeException("Unable to read " + AUDIT_LOG_FILE, e); } finally { FileUtils.closeQuietly(writer); } } private final String findAuditLoggerName(List<String> lines) throws IllegalStateException { for(final String l : lines) { if(l.contains(PRIMARY_AUDIT_LOG_ENTRY)) { final String[] valTokens = l.split(","); return valTokens[valTokens.length -1].trim(); } } throw new IllegalStateException(); } protected String getSnitch() { return dseConfig.getDseDelegatingSnitch(); } }