/*
* 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 gobblin.runtime.job_catalog;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.Semaphore;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.io.FileUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.io.Files;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import gobblin.configuration.ConfigurationKeys;
import gobblin.runtime.api.JobSpec;
import gobblin.runtime.job_catalog.FSJobCatalog;
import gobblin.runtime.job_catalog.ImmutableFSJobCatalog;
import gobblin.util.ConfigUtils;
import gobblin.util.PullFileLoader;
import gobblin.util.filesystem.PathAlterationObserverScheduler;
import gobblin.util.filesystem.PathAlterationListener;
import gobblin.util.filesystem.PathAlterationListenerAdaptor;
import gobblin.util.filesystem.PathAlterationObserver;
/**
* Inherit original Testing for loading configurations, with .properties files.
* The testing folder structure is:
* /root
* - root.properties
* /test1
* - test11.pull
* - test12.pull
* /test11
* - test111.pull
* /test2
* - test.properties
* - test21.pull
* The new testing routine for JobSpec,
* is to create a jobSpec( Simulated as the result of external JobSpecMonitor)
* persist it, reload it from file system, and compare with the original JobSpec.
*
*/
@Test(groups = {"gobblin.runtime"})
public class FSJobCatalogHelperTest {
// For general type of File system
private File jobConfigDir;
private File subDir1;
private File subDir11;
private File subDir2;
private Config sysConfig;
private PullFileLoader loader;
private ImmutableFSJobCatalog.JobSpecConverter converter;
@BeforeClass
public void setUp()
throws IOException {
this.jobConfigDir = java.nio.file.Files.createTempDirectory(
String.format("gobblin-test_%s_job-conf", this.getClass().getSimpleName())).toFile();
FileUtils.forceDeleteOnExit(this.jobConfigDir);
this.subDir1 = new File(this.jobConfigDir, "test1");
this.subDir11 = new File(this.subDir1, "test11");
this.subDir2 = new File(this.jobConfigDir, "test2");
this.subDir1.mkdirs();
this.subDir11.mkdirs();
this.subDir2.mkdirs();
this.sysConfig = ConfigFactory.parseMap(ImmutableMap.<String, Object>builder()
.put(ConfigurationKeys.JOB_CONFIG_FILE_GENERAL_PATH_KEY, this.jobConfigDir.getAbsolutePath())
.build());
ImmutableFSJobCatalog.ConfigAccessor cfgAccess =
new ImmutableFSJobCatalog.ConfigAccessor(this.sysConfig);
this.loader = new PullFileLoader(new Path(jobConfigDir.toURI()), FileSystem.get(new Configuration()),
cfgAccess.getJobConfigurationFileExtensions(),
PullFileLoader.DEFAULT_HOCON_PULL_FILE_EXTENSIONS);
this.converter = new ImmutableFSJobCatalog.JobSpecConverter(new Path(this.jobConfigDir.toURI()), Optional.of(
FSJobCatalog.CONF_EXTENSION));
Properties rootProps = new Properties();
rootProps.setProperty("k1", "a1");
rootProps.setProperty("k2", "a2");
// test-job-conf-dir/root.properties
rootProps.store(new FileWriter(new File(this.jobConfigDir, "root.properties")), "");
Properties jobProps1 = new Properties();
jobProps1.setProperty("k1", "c1");
jobProps1.setProperty("k3", "b3");
jobProps1.setProperty("k6", "a6");
// test-job-conf-dir/test1/test11.pull
jobProps1.store(new FileWriter(new File(this.subDir1, "test11.pull")), "");
Properties jobProps2 = new Properties();
jobProps2.setProperty("k7", "a7");
// test-job-conf-dir/test1/test12.PULL
jobProps2.store(new FileWriter(new File(this.subDir1, "test12.PULL")), "");
Properties jobProps3 = new Properties();
jobProps3.setProperty("k1", "d1");
jobProps3.setProperty("k8", "a8");
jobProps3.setProperty("k9", "${k8}");
// test-job-conf-dir/test1/test11/test111.pull
jobProps3.store(new FileWriter(new File(this.subDir11, "test111.pull")), "");
Properties props2 = new Properties();
props2.setProperty("k2", "b2");
props2.setProperty("k5", "a5");
// test-job-conf-dir/test2/test.properties
props2.store(new FileWriter(new File(this.subDir2, "test.PROPERTIES")), "");
Properties jobProps4 = new Properties();
jobProps4.setProperty("k5", "b5");
// test-job-conf-dir/test2/test21.PULL
jobProps4.store(new FileWriter(new File(this.subDir2, "test21.PULL")), "");
}
// This test doesn't delete framework attributes and
@Test
public void testloadGenericJobConfigs()
throws ConfigurationException, IOException, URISyntaxException {
Properties properties = new Properties();
properties.setProperty(ConfigurationKeys.JOB_CONFIG_FILE_GENERAL_PATH_KEY, this.jobConfigDir.getAbsolutePath());
List<JobSpec> jobSpecs = Lists.transform(
Lists.newArrayList(loader.loadPullFilesRecursively(loader.getRootDirectory(), this.sysConfig, false)),
this.converter);
List<Properties> jobConfigs = convertJobSpecList2PropList(jobSpecs);
Assert.assertEquals(jobConfigs.size(), 4);
// test-job-conf-dir/test1/test11/test111.pull
Properties jobProps1 = getJobConfigForFile(jobConfigs, "test111.pull");
//5 is consisting of three attributes, plus ConfigurationKeys.JOB_CONFIG_FILE_GENERAL_PATH_KEY
// which is on purpose to keep
// plus ConfigurationKeys.JOB_CONFIG_FILE_PATH_KEY, which is not necessary to convert into JobSpec
// but keep it here to avoid NullPointer exception and validation purpose for testing.
Assert.assertEquals(jobProps1.stringPropertyNames().size(), 5);
Assert.assertTrue(jobProps1.containsKey(ConfigurationKeys.JOB_CONFIG_FILE_PATH_KEY));
Assert.assertEquals(jobProps1.getProperty(ConfigurationKeys.JOB_CONFIG_FILE_GENERAL_PATH_KEY),
this.jobConfigDir.getAbsolutePath());
Assert.assertEquals(jobProps1.getProperty("k1"), "d1");
Assert.assertEquals(jobProps1.getProperty("k8"), "a8");
Assert.assertEquals(jobProps1.getProperty("k9"), "a8");
// test-job-conf-dir/test1/test11.pull
Properties jobProps2 = getJobConfigForFile(jobConfigs, "test11.pull");
Assert.assertEquals(jobProps2.stringPropertyNames().size(), 5);
Assert.assertTrue(jobProps2.containsKey(ConfigurationKeys.JOB_CONFIG_FILE_PATH_KEY));
Assert.assertEquals(jobProps2.getProperty(ConfigurationKeys.JOB_CONFIG_FILE_GENERAL_PATH_KEY),
this.jobConfigDir.getAbsolutePath());
Assert.assertEquals(jobProps2.getProperty("k1"), "c1");
Assert.assertEquals(jobProps2.getProperty("k3"), "b3");
Assert.assertEquals(jobProps2.getProperty("k6"), "a6");
// test-job-conf-dir/test1/test12.PULL
Properties jobProps3 = getJobConfigForFile(jobConfigs, "test12.PULL");
Assert.assertEquals(jobProps3.stringPropertyNames().size(), 3);
Assert.assertTrue(jobProps3.containsKey(ConfigurationKeys.JOB_CONFIG_FILE_PATH_KEY));
Assert.assertEquals(jobProps3.getProperty(ConfigurationKeys.JOB_CONFIG_FILE_GENERAL_PATH_KEY),
this.jobConfigDir.getAbsolutePath());
Assert.assertEquals(jobProps3.getProperty("k7"), "a7");
// test-job-conf-dir/test2/test21.PULL
Properties jobProps4 = getJobConfigForFile(jobConfigs, "test21.PULL");
Assert.assertEquals(jobProps4.stringPropertyNames().size(), 3);
Assert.assertTrue(jobProps4.containsKey(ConfigurationKeys.JOB_CONFIG_FILE_PATH_KEY));
Assert.assertEquals(jobProps4.getProperty(ConfigurationKeys.JOB_CONFIG_FILE_GENERAL_PATH_KEY),
this.jobConfigDir.getAbsolutePath());
Assert.assertEquals(jobProps4.getProperty("k5"), "b5");
}
@Test(dependsOnMethods = {"testloadGenericJobConfigs"})
public void testloadGenericJobConfig()
throws ConfigurationException, IOException {
Path jobConfigPath = new Path(this.subDir11.getAbsolutePath(), "test111.pull");
Properties jobProps =
ConfigUtils.configToProperties(loader.loadPullFile(jobConfigPath, this.sysConfig, false));
Assert.assertEquals(jobProps.stringPropertyNames().size(), 5);
Assert.assertEquals(jobProps.getProperty(ConfigurationKeys.JOB_CONFIG_FILE_GENERAL_PATH_KEY),
this.jobConfigDir.getAbsolutePath());
Assert.assertEquals(jobProps.getProperty("k1"), "d1");
Assert.assertEquals(jobProps.getProperty("k8"), "a8");
Assert.assertEquals(jobProps.getProperty("k9"), "a8");
}
@Test(dependsOnMethods = {"testloadGenericJobConfig"})
public void testPathAlterationObserver()
throws Exception {
PathAlterationObserverScheduler detector = new PathAlterationObserverScheduler(1000);
final Set<Path> fileAltered = Sets.newHashSet();
final Semaphore semaphore = new Semaphore(0);
PathAlterationListener listener = new PathAlterationListenerAdaptor() {
@Override
public void onFileCreate(Path path) {
fileAltered.add(path);
semaphore.release();
}
@Override
public void onFileChange(Path path) {
fileAltered.add(path);
semaphore.release();
}
};
detector.addPathAlterationObserver(listener, Optional.<PathAlterationObserver>absent(),
new Path(this.jobConfigDir.getPath()));
try {
detector.start();
// Give the monitor some time to start
Thread.sleep(1000);
File jobConfigFile = new File(this.subDir11, "test111.pull");
Files.touch(jobConfigFile);
File newJobConfigFile = new File(this.subDir11, "test112.pull");
Files.append("k1=v1", newJobConfigFile, ConfigurationKeys.DEFAULT_CHARSET_ENCODING);
semaphore.acquire(2);
Assert.assertEquals(fileAltered.size(), 2);
Assert.assertTrue(fileAltered.contains(new Path("file:" + jobConfigFile)));
Assert.assertTrue(fileAltered.contains(new Path("file:" + newJobConfigFile)));
} finally {
detector.stop();
}
}
@AfterClass
public void tearDown()
throws IOException {
if (this.jobConfigDir != null) {
FileUtils.forceDelete(this.jobConfigDir);
}
}
private Properties getJobConfigForFile(List<Properties> jobConfigs, String fileName) {
for (Properties jobConfig : jobConfigs) {
if (jobConfig.getProperty(ConfigurationKeys.JOB_CONFIG_FILE_PATH_KEY).endsWith(fileName)) {
return jobConfig;
}
}
return null;
}
/**
* Suppose in the testing routine, each JobSpec will at least have either config or properties.
* @param jobConfigs
* @return
*/
private List<Properties> convertJobSpecList2PropList(List<JobSpec> jobConfigs) {
List<Properties> result = Lists.newArrayList();
for (JobSpec js : jobConfigs) {
Properties propToBeAdded;
if (js.getConfigAsProperties() != null) {
propToBeAdded = js.getConfigAsProperties();
} else {
propToBeAdded = ConfigUtils.configToProperties(js.getConfig());
}
// For the testing purpose, added it back when doing the comparison.
propToBeAdded.setProperty(ConfigurationKeys.JOB_CONFIG_FILE_PATH_KEY, js.getUri().toString());
result.add(propToBeAdded);
}
return result;
}
}