// Copyright 2016 Twitter. All rights reserved.
//
// 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.twitter.heron.spi.common;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Set;
import java.util.TreeSet;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isNotNull;
import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@RunWith(PowerMockRunner.class)
@PrepareForTest(ConfigLoader.class)
public class ConfigLoaderTest {
private static final String TEST_DATA_PATH =
"/__main__/heron/spi/tests/java/com/twitter/heron/spi/common/testdata";
private final String heronHome =
Paths.get(System.getenv("JAVA_RUNFILES"), TEST_DATA_PATH).toString();
private final String configPath = Paths.get(heronHome, "local").toString();
private Config basicConfig;
@Before
public void setUp() {
PowerMockito.spy(ConfigLoader.class);
basicConfig = Config.toLocalMode(ConfigLoader.loadConfig(
heronHome, configPath, "/release/file", "/override/file"));
}
@Test
public void testLoadClusterConfig() {
PowerMockito.spy(ConfigLoader.class);
Config config = Config.toClusterMode(ConfigLoader.loadConfig(
heronHome, configPath, "/release/file", "/override/file"));
assertConfig(config, "./heron-core", "./heron-conf");
}
@Test
public void testLoadDefaultConfig() {
assertConfig(basicConfig, heronHome, configPath);
assertKeyValue(basicConfig, Key.PACKING_CLASS,
"com.twitter.heron.packing.roundrobin.RoundRobinPacking");
assertKeyValue(basicConfig, Key.SCHEDULER_CLASS,
"com.twitter.heron.scheduler.local.LocalScheduler");
assertKeyValue(basicConfig, Key.LAUNCHER_CLASS,
"com.twitter.heron.scheduler.local.LocalLauncher");
assertKeyValue(basicConfig, Key.STATE_MANAGER_CLASS,
"com.twitter.heron.state.localfile.LocalFileStateManager");
assertKeyValue(basicConfig, Key.UPLOADER_CLASS,
"com.twitter.heron.uploader.localfs.FileSystemUploader");
}
private static void assertConfig(Config config,
String heronHome,
String heronConfigPath) {
// assert that the config filenames passed to loadConfig are never null. If they are, the
// configs defaults are not producing the config files.
PowerMockito.verifyStatic(times(9));
ConfigLoader.loadConfig(isNotNull(String.class));
PowerMockito.verifyStatic(never());
ConfigLoader.loadConfig(isNull(String.class));
// addFromFile with an empty map means that the config file was not found. Of the 9 files that
// are attempted to be loaded, all but 3 should be found (clientConfig, overrideConfigFile and
// releaseFile do not exist)
PowerMockito.verifyStatic(times(3));
ConfigLoader.addFromFile(eq(new HashMap<String, Object>()));
Set<String> tokenizedValues = new TreeSet<>();
for (Key key : Key.values()) {
if (key.getType() == Key.Type.STRING) {
String value = config.getStringValue(key);
// assert all tokens got replaced, except JAVA_HOME which might not be set on CI hosts
if (value != null && value.contains("${") && !value.contains("${JAVA_HOME}")) {
tokenizedValues.add(value);
}
}
}
assertTrue("Default config values have not all had tokens replaced: " + tokenizedValues,
tokenizedValues.isEmpty());
assertKeyValue(config, Key.HERON_HOME, heronHome);
assertKeyValue(config, Key.HERON_CONF, heronConfigPath);
assertKeyValue(config, Key.HERON_BIN, heronHome + "/bin");
assertKeyValue(config, Key.HERON_DIST, heronHome + "/dist");
assertKeyValue(config, Key.HERON_LIB, heronHome + "/lib");
assertKeyValue(config, Key.HERON_ETC, heronHome + "/etc");
assertKeyValue(config, Key.CLUSTER_YAML, heronConfigPath + "/cluster.yaml");
assertKeyValue(config, Key.CLIENT_YAML, heronConfigPath + "/client.yaml");
assertKeyValue(config, Key.METRICS_YAML, heronConfigPath + "/metrics_sinks.yaml");
assertKeyValue(config, Key.PACKING_YAML, heronConfigPath + "/packing.yaml");
assertKeyValue(config, Key.SCHEDULER_YAML, heronConfigPath + "/scheduler.yaml");
assertKeyValue(config, Key.STATEMGR_YAML, heronConfigPath + "/statemgr.yaml");
assertKeyValue(config, Key.SYSTEM_YAML, heronConfigPath + "/heron_internals.yaml");
assertKeyValue(config, Key.UPLOADER_YAML, heronConfigPath + "/uploader.yaml");
String binPath = config.getStringValue(Key.HERON_BIN);
assertKeyValue(config, Key.EXECUTOR_BINARY, binPath + "/heron-executor");
assertKeyValue(config, Key.STMGR_BINARY, binPath + "/heron-stmgr");
assertKeyValue(config, Key.TMASTER_BINARY, binPath + "/heron-tmaster");
assertKeyValue(config, Key.SHELL_BINARY, binPath + "/heron-shell");
assertKeyValue(config, Key.PYTHON_INSTANCE_BINARY, binPath + "/heron-python-instance");
String libPath = config.getStringValue(Key.HERON_LIB);
assertKeyValue(config, Key.SCHEDULER_JAR, libPath + "/scheduler/heron-scheduler.jar");
assertKeyValue(config, Key.INSTANCE_CLASSPATH, libPath + "/instance/*");
assertKeyValue(config, Key.METRICSMGR_CLASSPATH, libPath + "/metricsmgr/*");
assertKeyValue(config, Key.PACKING_CLASSPATH, libPath + "/packing/*");
assertKeyValue(config, Key.SCHEDULER_CLASSPATH, libPath + "/scheduler/*");
assertKeyValue(config, Key.STATEMGR_CLASSPATH, libPath + "/statemgr/*");
assertKeyValue(config, Key.UPLOADER_CLASSPATH, libPath + "/uploader/*");
}
private static void assertKeyValue(Config config, Key key, String expected) {
assertEquals("Unexpected value for key " + key, expected, config.get(key));
}
/**
* Test reading the cluster.yaml file
*/
@Test
public void testClusterFile() throws Exception {
Config props = ConfigLoader.loadConfig(Context.clusterFile(basicConfig));
assertEquals(4, props.size());
assertEquals(
"com.twitter.heron.uploader.localfs.FileSystemUploader",
Context.uploaderClass(props)
);
}
@Test
public void testSchedulerFile() throws Exception {
Config props = ConfigLoader.loadConfig(Context.schedulerFile(basicConfig));
assertEquals(2, props.size());
assertEquals(
"com.twitter.heron.scheduler.local.LocalScheduler",
Context.schedulerClass(props)
);
assertEquals(
"com.twitter.heron.scheduler.local.LocalLauncher",
Context.launcherClass(props)
);
}
@Test
public void testPackingFile() throws Exception {
Config props = ConfigLoader.loadConfig(Context.packingFile(basicConfig));
assertEquals(1, props.size());
assertEquals(
"com.twitter.heron.packing.roundrobin.RoundRobinPacking",
props.getStringValue("heron.class.packing.algorithm")
);
}
@Test
public void testUploaderFile() throws Exception {
Config props = ConfigLoader.loadConfig(Context.uploaderFile(basicConfig));
assertEquals(2, props.size());
assertEquals(
"/vagrant/heron/jobs",
props.getStringValue("heron.uploader.file.system.path")
);
}
}