| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.metron.management; |
| |
| import com.google.common.collect.ImmutableMap; |
| import java.nio.charset.StandardCharsets; |
| import org.adrianwalker.multilinestring.Multiline; |
| import org.apache.commons.cli.CommandLine; |
| import org.apache.commons.cli.PosixParser; |
| import org.apache.curator.framework.CuratorFramework; |
| import org.apache.curator.test.TestingServer; |
| import org.apache.log4j.Level; |
| import org.apache.metron.common.cli.ConfigurationManager; |
| import org.apache.metron.common.configuration.ConfigurationsUtils; |
| import org.apache.metron.common.configuration.SensorParserConfig; |
| import org.apache.metron.common.configuration.enrichment.SensorEnrichmentConfig; |
| import org.apache.metron.common.configuration.profiler.ProfilerConfig; |
| import org.apache.metron.stellar.dsl.Context; |
| import org.apache.metron.stellar.dsl.ParseException; |
| import org.apache.metron.test.utils.UnitTestHelper; |
| import org.json.simple.JSONObject; |
| import org.json.simple.parser.JSONParser; |
| import org.junit.jupiter.api.BeforeEach; |
| import org.junit.jupiter.api.BeforeAll; |
| import org.junit.jupiter.api.Test; |
| |
| import java.util.Collections; |
| import java.util.Map; |
| |
| import static org.apache.metron.TestConstants.PARSER_CONFIGS_PATH; |
| import static org.apache.metron.TestConstants.SAMPLE_CONFIG_PATH; |
| import static org.apache.metron.common.configuration.ConfigurationType.GLOBAL; |
| import static org.apache.metron.common.configuration.ConfigurationType.PROFILER; |
| import static org.apache.metron.common.configuration.ConfigurationsUtils.writeProfilerConfigToZookeeper; |
| import static org.apache.metron.management.utils.FileUtils.slurp; |
| import static org.apache.metron.stellar.common.utils.StellarProcessorUtils.run; |
| import static org.junit.jupiter.api.Assertions.*; |
| |
| /** |
| * Tests the ConfigurationFunctions class. |
| */ |
| public class ConfigurationFunctionsTest { |
| |
| private static TestingServer testZkServer; |
| private static String zookeeperUrl; |
| private static CuratorFramework client; |
| private static String goodGlobalConfig = slurp( SAMPLE_CONFIG_PATH+ "/global.json"); |
| private static String goodTestEnrichmentConfig = slurp( SAMPLE_CONFIG_PATH + "/enrichments/test.json"); |
| private static String goodBroParserConfig = slurp(PARSER_CONFIGS_PATH + "/parsers/bro.json"); |
| private static String goodTestIndexingConfig = slurp( SAMPLE_CONFIG_PATH + "/indexing/test.json"); |
| |
| private Context context; |
| private JSONParser parser; |
| |
| /** |
| * { |
| * "profiles" : [ |
| * { |
| * "profile" : "counter", |
| * "foreach" : "ip_src_addr", |
| * "init" : { "counter" : 0 }, |
| * "update" : { "counter" : "counter + 1" }, |
| * "result" : "counter" |
| * } |
| * ], |
| * "timestampField" : "timestamp" |
| * } |
| */ |
| @Multiline |
| private static String goodProfilerConfig; |
| |
| @BeforeAll |
| public static void setupZookeeper() throws Exception { |
| |
| // zookeeper server |
| testZkServer = new TestingServer(true); |
| zookeeperUrl = testZkServer.getConnectString(); |
| |
| // zookeeper client |
| client = ConfigurationsUtils.getClient(zookeeperUrl); |
| client.start(); |
| } |
| |
| @BeforeEach |
| public void setup() throws Exception { |
| |
| context = new Context.Builder() |
| .with(Context.Capabilities.ZOOKEEPER_CLIENT, () -> client) |
| .build(); |
| |
| parser = new JSONParser(); |
| |
| // push configs to zookeeper |
| pushConfigs(SAMPLE_CONFIG_PATH, zookeeperUrl); |
| pushConfigs(PARSER_CONFIGS_PATH, zookeeperUrl); |
| writeProfilerConfigToZookeeper(goodProfilerConfig.getBytes(StandardCharsets.UTF_8), client); |
| } |
| |
| /** |
| * Deletes a path within Zookeeper. |
| * |
| * @param path The path within Zookeeper to delete. |
| * @throws Exception |
| */ |
| private void deletePath(String path) throws Exception { |
| client.delete().forPath(path); |
| } |
| |
| /** |
| * Transforms a String to a {@link JSONObject}. |
| * |
| * @param input The input String to transform |
| * @return A {@link JSONObject}. |
| * @throws org.json.simple.parser.ParseException |
| */ |
| private JSONObject toJSONObject(String input) throws org.json.simple.parser.ParseException { |
| |
| if(input == null) { |
| return null; |
| } |
| return (JSONObject) parser.parse(input.trim()); |
| } |
| |
| /** |
| * Push configuration values to Zookeeper. |
| * |
| * @param inputPath The local filesystem path to the configurations. |
| * @param zookeeperUrl The URL of Zookeeper. |
| * @throws Exception |
| */ |
| private static void pushConfigs(String inputPath, String zookeeperUrl) throws Exception { |
| |
| String[] args = new String[] { |
| "-z", zookeeperUrl, |
| "--mode", "PUSH", |
| "--input_dir", inputPath |
| }; |
| CommandLine cli = ConfigurationManager.ConfigurationOptions.parse(new PosixParser(), args); |
| |
| ConfigurationManager manager = new ConfigurationManager(); |
| manager.run(cli); |
| } |
| |
| /** |
| * The CONFIG_GET function should be able to return the Parser configuration |
| * for a given sensor. |
| */ |
| @Test |
| public void testGetParser() throws Exception { |
| |
| String out = (String) run("CONFIG_GET('PARSER', 'bro')", context); |
| |
| SensorParserConfig actual = SensorParserConfig.fromBytes(out.getBytes(StandardCharsets.UTF_8)); |
| SensorParserConfig expected = SensorParserConfig.fromBytes(goodBroParserConfig.getBytes( |
| StandardCharsets.UTF_8)); |
| assertEquals(expected, actual); |
| } |
| |
| /** |
| * The CONFIG_GET function should NOT return any configuration when the |
| * Parser configuration for a given sensor is missing AND emptyIfNotPresent = false. |
| */ |
| @Test |
| public void testGetParserMissWithoutDefault() { |
| |
| // expect null because emptyIfNotPresent = false |
| Object out = run("CONFIG_GET('PARSER', 'sensor', false)", context); |
| assertNull(out); |
| } |
| |
| /** |
| * The CONFIG_GET function should return a default configuration when none |
| * currently exists. |
| */ |
| @Test |
| public void testGetParserMissWithDefault() throws Exception { |
| |
| SensorParserConfig expected = new SensorParserConfig(); |
| { |
| Object out = run("CONFIG_GET('PARSER', 'sensor')", context); |
| SensorParserConfig actual = SensorParserConfig.fromBytes(out.toString().getBytes( |
| StandardCharsets.UTF_8)); |
| assertEquals(expected, actual); |
| } |
| { |
| Object out = run("CONFIG_GET('PARSER', 'sensor', true)", context); |
| SensorParserConfig actual = SensorParserConfig.fromBytes(out.toString().getBytes( |
| StandardCharsets.UTF_8)); |
| assertEquals(expected, actual); |
| } |
| } |
| |
| /** |
| * The CONFIG_GET function should be able to return the Enrichment configuration |
| * for a given sensor. |
| */ |
| @Test |
| public void testGetEnrichment() throws Exception { |
| |
| String out = (String) run("CONFIG_GET('ENRICHMENT', 'test')", context); |
| |
| SensorEnrichmentConfig actual = SensorEnrichmentConfig.fromBytes(out.getBytes( |
| StandardCharsets.UTF_8)); |
| SensorEnrichmentConfig expected = SensorEnrichmentConfig.fromBytes(goodTestEnrichmentConfig.getBytes( |
| StandardCharsets.UTF_8)); |
| assertEquals(expected, actual); |
| } |
| |
| /** |
| * No default configuration should be provided in this case. |
| */ |
| @Test |
| public void testGetEnrichmentMissWithoutDefault() { |
| |
| // expect null because emptyIfNotPresent = false |
| Object out = run("CONFIG_GET('ENRICHMENT', 'sense', false)", context); |
| assertNull(out); |
| } |
| |
| /** |
| * A default empty configuration should be provided, if one does not exist. |
| */ |
| @Test |
| public void testGetEnrichmentMissWithDefault() throws Exception { |
| |
| // expect an empty configuration to be returned |
| SensorEnrichmentConfig expected = new SensorEnrichmentConfig(); |
| { |
| String out = (String) run("CONFIG_GET('ENRICHMENT', 'missing-sensor')", context); |
| SensorEnrichmentConfig actual = SensorEnrichmentConfig.fromBytes(out.getBytes( |
| StandardCharsets.UTF_8)); |
| assertEquals(expected, actual); |
| } |
| { |
| String out = (String) run("CONFIG_GET('ENRICHMENT', 'missing-sensor', true)", context); |
| SensorEnrichmentConfig actual = SensorEnrichmentConfig.fromBytes(out.getBytes( |
| StandardCharsets.UTF_8)); |
| assertEquals(expected, actual); |
| } |
| } |
| |
| /** |
| * The CONFIG_GET function should be able to return the Indexing configuration |
| * for a given sensor. |
| */ |
| @Test |
| public void testGetIndexing() throws Exception { |
| |
| String out = (String) run("CONFIG_GET('INDEXING', 'test')", context); |
| |
| Map<String, Object> actual = toJSONObject(out); |
| Map<String, Object> expected = toJSONObject(goodTestIndexingConfig); |
| assertEquals(expected, actual); |
| } |
| |
| /** |
| * No default configuration should be provided in this case. |
| */ |
| @Test |
| public void testGetIndexingMissWithoutDefault() { |
| |
| // expect null because emptyIfNotPresent = false |
| Object out = run("CONFIG_GET('INDEXING', 'sense', false)", context); |
| assertNull(out); |
| } |
| |
| /** |
| * A default empty configuration should be provided, if one does not exist. |
| */ |
| @Test |
| public void testGetIndexingtMissWithDefault() throws Exception { |
| |
| // expect an empty configuration to be returned |
| Map<String, Object> expected = Collections.emptyMap(); |
| { |
| String out = (String) run("CONFIG_GET('INDEXING', 'missing-sensor')", context); |
| Map<String, Object> actual = toJSONObject(out); |
| assertEquals(expected, actual); |
| } |
| { |
| String out = (String) run("CONFIG_GET('INDEXING', 'missing-sensor', true)", context); |
| Map<String, Object> actual = toJSONObject(out); |
| assertEquals(expected, actual); |
| } |
| } |
| |
| /** |
| * The CONFIG_GET function should be able to return the Profiler configuration. |
| */ |
| @Test |
| public void testGetProfiler() throws Exception { |
| |
| String out = (String) run("CONFIG_GET('PROFILER')", context); |
| |
| ProfilerConfig actual = ProfilerConfig.fromBytes(out.getBytes(StandardCharsets.UTF_8)); |
| ProfilerConfig expected = ProfilerConfig.fromBytes(goodProfilerConfig.getBytes( |
| StandardCharsets.UTF_8)); |
| assertEquals(expected, actual); |
| } |
| |
| /** |
| * No default configuration should be provided in this case. |
| */ |
| @Test |
| public void testGetProfilerMissWithoutDefault() throws Exception { |
| |
| deletePath(PROFILER.getZookeeperRoot()); |
| |
| // expect null because emptyIfNotPresent = false |
| String out = (String) run("CONFIG_GET('PROFILER', false)", context); |
| assertNull(out); |
| } |
| |
| /** |
| * A default empty configuration should be provided, if one does not exist. |
| */ |
| @Test |
| public void testGetProfilerMissWithDefault() throws Exception { |
| |
| // there is no profiler config in zookeeper |
| deletePath(PROFILER.getZookeeperRoot()); |
| |
| // expect an empty configuration to be returned |
| ProfilerConfig expected = new ProfilerConfig(); |
| { |
| String out = (String) run("CONFIG_GET('PROFILER', true)", context); |
| ProfilerConfig actual = ProfilerConfig.fromJSON(out); |
| assertEquals(expected, actual); |
| } |
| { |
| String out = (String) run("CONFIG_GET('PROFILER')", context); |
| ProfilerConfig actual = ProfilerConfig.fromJSON(out); |
| assertEquals(expected, actual); |
| } |
| } |
| |
| @Test |
| public void testGetGlobal() throws Exception { |
| |
| String out = (String) run("CONFIG_GET('GLOBAL')", context); |
| |
| Map<String, Object> actual = toJSONObject(out); |
| Map<String, Object> expected = toJSONObject(goodGlobalConfig); |
| assertEquals(expected, actual); |
| } |
| |
| /** |
| * No default configuration should be provided in this case. |
| */ |
| @Test |
| public void testGetGlobalMissWithoutDefault() throws Exception { |
| |
| // there is no global config in zookeeper |
| deletePath(GLOBAL.getZookeeperRoot()); |
| |
| // expect null because emptyIfNotPresent = false |
| Object out = run("CONFIG_GET('GLOBAL', false)", context); |
| assertNull(out); |
| } |
| |
| /** |
| * A default empty configuration should be provided, if one does not exist. |
| */ |
| @Test |
| public void testGetGlobalMissWithDefault() throws Exception { |
| |
| // there is no global config in zookeeper |
| deletePath(GLOBAL.getZookeeperRoot()); |
| |
| // expect an empty configuration to be returned |
| Map<String, Object> expected = Collections.emptyMap(); |
| { |
| String out = (String) run("CONFIG_GET('GLOBAL')", context); |
| Map<String, Object> actual = toJSONObject(out); |
| assertEquals(expected, actual); |
| } |
| { |
| String out = (String) run("CONFIG_GET('GLOBAL', true)", context); |
| Map<String, Object> actual = toJSONObject(out); |
| assertEquals(expected, actual); |
| } |
| } |
| |
| @Test |
| public void testPutGlobal() throws Exception { |
| |
| String out = (String) run("CONFIG_GET('GLOBAL')", context); |
| |
| Map<String, Object> actual = toJSONObject(out); |
| Map<String, Object> expected = toJSONObject(goodGlobalConfig); |
| assertEquals(expected, actual); |
| } |
| |
| @Test |
| public void testPutGlobalBad() { |
| { |
| UnitTestHelper.setLog4jLevel(ConfigurationFunctions.class, Level.FATAL); |
| assertThrows(ParseException.class, () -> run("CONFIG_PUT('GLOBAL', 'foo bar')", context)); |
| UnitTestHelper.setLog4jLevel(ConfigurationFunctions.class, Level.ERROR); |
| } |
| } |
| |
| @Test |
| public void testPutIndexing() throws InterruptedException { |
| String brop= (String) run("CONFIG_GET('INDEXING', 'testIndexingPut')", context); |
| run("CONFIG_PUT('INDEXING', config, 'testIndexingPut')", ImmutableMap.of("config", brop), context); |
| boolean foundMatch = false; |
| for(int i = 0;i < 10 && !foundMatch;++i) { |
| String bropNew = (String) run("CONFIG_GET('INDEXING', 'testIndexingPut', false)", context); |
| foundMatch = brop.equals(bropNew); |
| if(foundMatch) { |
| break; |
| } |
| Thread.sleep(2000); |
| } |
| assertTrue(foundMatch); |
| } |
| |
| @Test |
| public void testPutIndexingBad() { |
| { |
| { |
| UnitTestHelper.setLog4jLevel(ConfigurationFunctions.class, Level.FATAL); |
| assertThrows( |
| ParseException.class, |
| () -> |
| run( |
| "CONFIG_PUT('INDEXING', config, 'brop')", |
| ImmutableMap.of("config", "foo bar"), |
| context)); |
| UnitTestHelper.setLog4jLevel(ConfigurationFunctions.class, Level.ERROR); |
| } |
| } |
| } |
| |
| @Test |
| public void testPutEnrichment() throws InterruptedException { |
| String config = (String) run("CONFIG_GET('ENRICHMENT', 'sensor')", context); |
| assertNotNull(config); |
| |
| run("CONFIG_PUT('ENRICHMENT', config, 'sensor')", ImmutableMap.of("config", config), context); |
| |
| boolean foundMatch = false; |
| for(int i = 0;i < 10 && !foundMatch;++i) { |
| String newConfig = (String) run("CONFIG_GET('ENRICHMENT', 'sensor', false)", context); |
| foundMatch = config.equals(newConfig); |
| if(foundMatch) { |
| break; |
| } |
| Thread.sleep(2000); |
| } |
| assertTrue(foundMatch); |
| } |
| |
| @Test |
| public void testPutEnrichmentBad() { |
| { |
| { |
| UnitTestHelper.setLog4jLevel(ConfigurationFunctions.class, Level.FATAL); |
| assertThrows( |
| ParseException.class, |
| () -> |
| run( |
| "CONFIG_PUT('ENRICHMENT', config, 'brop')", |
| ImmutableMap.of("config", "foo bar"), |
| context)); |
| UnitTestHelper.setLog4jLevel(ConfigurationFunctions.class, Level.ERROR); |
| } |
| } |
| } |
| |
| @Test |
| public void testPutParser() throws InterruptedException { |
| String brop= (String) run("CONFIG_GET('PARSER', 'testParserPut')", context); |
| run("CONFIG_PUT('PARSER', config, 'testParserPut')", ImmutableMap.of("config", brop), context); |
| boolean foundMatch = false; |
| for(int i = 0;i < 10 && !foundMatch;++i) { |
| String bropNew = (String) run("CONFIG_GET('PARSER', 'testParserPut', false)", context); |
| foundMatch = brop.equals(bropNew); |
| if(foundMatch) { |
| break; |
| } |
| Thread.sleep(2000); |
| } |
| assertTrue(foundMatch); |
| } |
| |
| @Test |
| public void testPutParserBad() { |
| { |
| UnitTestHelper.setLog4jLevel(ConfigurationFunctions.class, Level.FATAL); |
| assertThrows( |
| ParseException.class, |
| () -> |
| run( |
| "CONFIG_PUT('PARSER', config, 'brop')", |
| ImmutableMap.of("config", "foo bar"), |
| context)); |
| UnitTestHelper.setLog4jLevel(ConfigurationFunctions.class, Level.ERROR); |
| } |
| } |
| } |