blob: 831128e6a0a11e05d226cc9057eb341bfa7e83fe [file] [log] [blame]
/*
* 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.ace.configurator;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Dictionary;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.ace.test.utils.FileUtils;
import org.apache.ace.test.utils.TestUtils;
import org.osgi.framework.BundleContext;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.log.LogService;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class ConfiguratorTest {
private Configurator m_configurator;
private File m_configDir;
private MockConfigAdmin m_configAdmin;
private volatile CountDownLatch m_deleteLatch;
private volatile CountDownLatch m_updateLatch;
@Test()
public void testAddConfiguration() throws Exception {
String pid = "test-add";
Properties initialConfiguration = createProperties();
saveConfiguration(pid, initialConfiguration);
Dictionary<String, ?> configuration = getAndWaitForConfigurationUpdate(pid);
assertNotNull(configuration, "No configuration received from configurator");
assertEquals(createProperties(), configuration, "Configuration content is unexpected");
}
@Test()
public void testAddFactoryConfiguration() throws Exception {
String pid = "test-add";
String factoryPID = "testFactory";
Properties props = createProperties();
saveConfiguration(pid, "testFactory", props);
Dictionary<String, ?> configuration = getAndWaitForConfigurationUpdate(factoryPID);
assertNotNull(configuration, "No configuration received from configurator");
assertEquals(configuration.remove("factory.instance.pid"), "testFactory_test-add", "Incorrect factory instance pid was added to the configuration");
assertEquals(createProperties(), configuration, "Configuration content is unexpected");
}
// update a configuration, only adding a key (this is allowed in all cases)
@Test()
public void testChangeConfigurationUsingNewKey() throws Exception {
String pid = "test-change";
Properties initialConfiguration = createProperties();
saveConfiguration(pid, initialConfiguration);
Dictionary<String, ?> configuration = getAndWaitForConfigurationUpdate(pid);
assertNotNull(configuration, "No configuration received from configurator");
assertEquals(initialConfiguration, configuration);
initialConfiguration.put("anotherKey", "anotherValue");
saveConfiguration("test-change", initialConfiguration);
// now the configuration should be updated
configuration = getAndWaitForConfigurationUpdate(pid);
assertNotNull(configuration, "No configuration received from configurator");
assertEquals(initialConfiguration, configuration);
}
// update a configuration, changing an already existing key, not using reconfiguration
@Test()
public void testChangeConfigurationUsingSameKeyNoReconfigure() throws Exception {
String pid = "test-change";
Properties configurationValues = createProperties();
Properties initialConfigurationValues = new Properties();
initialConfigurationValues.putAll(configurationValues);
saveConfiguration(pid, configurationValues);
Dictionary<String, ?> configuration = getAndWaitForConfigurationUpdate(pid);
assertNotNull(configuration, "No configuration received from configurator");
assertEquals(configurationValues, configuration);
configurationValues.put("test", "value42");
saveConfiguration("test-change", configurationValues);
// The update should have been ignored, and the old values should still be present.
configuration = getAndWaitForConfigurationUpdate(pid);
assertNotNull(configuration, "No configuration received from configurator");
assertEquals(initialConfigurationValues, configuration);
}
// update a configuration, changing an already existing key, using reconfiguration
@Test()
public void testChangeConfigurationUsingSameKeyWithReconfigure() throws Exception {
String pid = "test-change";
setUp(true); // Instruct the configurator to reconfigure
Properties configurationValues = createProperties();
saveConfiguration(pid, configurationValues);
Dictionary<String, ?> configuration = getAndWaitForConfigurationUpdate(pid);
assertNotNull(configuration, "No configuration received from configurator");
assertEquals(configurationValues, configuration);
configurationValues.put("test", "value42");
saveConfiguration(pid, configurationValues);
// now the configuration should be updated
configuration = getAndWaitForConfigurationUpdate(pid);
assertNotNull(configuration, "No configuration received from configurator");
assertEquals(configurationValues, configuration);
}
@Test()
public void testPropertySubstitution() throws Exception {
String pid = "test-subst";
Properties initial = new Properties();
initial.put("key1", "leading ${foo.${bar}} middle ${baz} trailing");
initial.put("bar", "a");
initial.put("foo.a", "text");
initial.put("baz", "word");
// ACE-401: use some weird log4j conversion pattern in our config file, should not confuse the Configurator's
// substitution algorithm...
initial.put("key2", "%d{ISO8601} | %-5.5p | %C | %X{bundle.name} | %m%n");
// unknown and partially unknown variables shouldn't get substituted...
initial.put("key3", "${qux} ${quu.${bar}} ${baz.${bar}}");
saveConfiguration(pid, initial);
Dictionary<String, ?> config = getAndWaitForConfigurationUpdate(pid);
assertNotNull(config, "No configuration received from configurator");
assertEquals(config.get("key1"), "leading text middle word trailing", "Substitution failed!");
assertEquals(config.get("key2"), "%d{ISO8601} | %-5.5p | %C | %X{bundle.name} | %m%n", "Substitution failed!");
assertEquals(config.get("key3"), "${qux} ${quu.${bar}} ${baz.${bar}}", "Substitution failed!");
}
@Test()
public void testPropertySubstitutionFromContext() throws Exception {
String pid = "test-subst";
Properties initialConfiguration = createProperties();
initialConfiguration.put("subst", "${contextProp}");
saveConfiguration(pid, initialConfiguration);
Dictionary<String, ?> configuration = getAndWaitForConfigurationUpdate(pid);
assertNotNull(configuration, "No configuration received from configurator");
assertEquals(configuration.get("subst"), "contextVal", "Substitution failed");
}
// remove a configuration
@Test()
public void testRemoveConfiguration() throws Exception {
String pid = "test-remove";
Properties initialConfiguration = createProperties();
saveConfiguration(pid, initialConfiguration);
Dictionary<String, ?> configuration = getAndWaitForConfigurationUpdate(pid);
assertNotNull(configuration, "No configuration received from configurator");
assertEquals(createProperties(), configuration);
// ok, the configuration is done.
// now try to remove it.
removeConfiguration(pid);
// after some processing time, we should get a message that the configuration is now removed.
waitForConfigurationDelete(pid);
}
// remove a configuration
@Test()
public void testRemoveFactoryConfiguration() throws Exception {
String pid = "test-remove";
String factoryPID = "testFactory";
Properties props = createProperties();
saveConfiguration(pid, factoryPID, props);
getAndWaitForConfigurationUpdate(factoryPID);
removeConfiguration(pid, factoryPID);
// after some processing time, we should get a message that the configuration is now removed.
waitForConfigurationDelete(factoryPID);
}
@BeforeMethod(alwaysRun = true)
protected void setUp() throws Exception {
setUp(false);
}
/**
* Sets up the environment for testing.
*
* @param reconfig
* Indicates whether or not the configurator should use reconfiguration.
*/
protected void setUp(boolean reconfig) throws Exception {
m_configAdmin = new MockConfigAdmin() {
@Override
void configDeleted(MockConfiguration config) {
m_deleteLatch.countDown();
}
@Override
void configUpdated(MockConfiguration config) {
m_updateLatch.countDown();
}
};
m_configDir = FileUtils.createTempFile(null);
m_configDir.mkdir();
m_configurator = new Configurator(m_configDir, 200, reconfig);
TestUtils.configureObject(m_configurator, ConfigurationAdmin.class, m_configAdmin);
TestUtils.configureObject(m_configurator, LogService.class);
TestUtils.configureObject(m_configurator, BundleContext.class, TestUtils.createMockObjectAdapter(BundleContext.class, new Object() {
@SuppressWarnings("unused")
public String getProperty(String key) {
if ("contextProp".equals(key)) {
return "contextVal";
}
return null;
}
}));
m_configurator.start();
}
@AfterMethod(alwaysRun = true)
protected void tearDown() throws Exception {
m_configurator.stop();
FileUtils.removeDirectoryWithContent(m_configDir);
m_deleteLatch = null;
m_updateLatch = null;
}
// set some standard properties for testing
private Properties createProperties() {
Properties props = new Properties();
props.put("test", "value1");
props.put("test2", "value2");
return props;
}
/**
* Get the configuration and if it not available yet wait for it. If there is still no configuration after the wait
* time, null is returned.
*/
private Dictionary<String, ?> getAndWaitForConfigurationUpdate(String pid) throws Exception {
assertTrue(m_updateLatch.await(2, TimeUnit.SECONDS));
return m_configAdmin.getConfiguration(pid).getProperties();
}
// remove a created configuration file
private void removeConfiguration(String servicePid) {
removeConfiguration(servicePid, null);
}
private void removeConfiguration(String servicePid, String factoryPid) {
if (factoryPid != null) {
new File(m_configDir, factoryPid + File.separator + servicePid + ".cfg").delete();
}
else {
new File(m_configDir, servicePid + ".cfg").delete();
}
m_deleteLatch = new CountDownLatch(1);
}
/**
* Renames a given source file to a new destination file, using Commons-IO.
* <p>
* This avoids the problem mentioned in ACE-155.
* </p>
*
* @param source
* the file to rename;
* @param dest
* the file to rename to.
*/
private void renameFile(File source, File dest) {
try {
org.apache.commons.io.FileUtils.moveFile(source, dest);
}
catch (IOException e) {
throw new RuntimeException("Failed to rename file!", e);
}
}
/**
* save the properties into a configuration file the configurator can read. The file is first created and then moved
* to make sure the configuration doesn't read an empty file
*/
private void saveConfiguration(String servicePid, Properties configuration) {
saveConfiguration(servicePid, null, configuration);
}
/**
* save the properties into a configuration file stored in a directory reflecting the factory pid
*/
private void saveConfiguration(String servicePid, String factoryPid, Properties configuration) {
OutputStream fileOutputStream = null;
File outFile = null;
try {
outFile = FileUtils.createTempFile(null);
fileOutputStream = new FileOutputStream(outFile);
configuration.store(fileOutputStream, null);
}
catch (IOException ioe) {
// the test will fail, ignore this.
}
finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
}
catch (IOException e) {
// nothing we can do
}
}
}
if (outFile != null) {
if (factoryPid == null) {
File dest = new File(m_configDir, servicePid + ".cfg");
if (dest.exists()) {
dest.delete();
}
renameFile(outFile, dest);
}
else {
File file = new File(m_configDir, factoryPid);
file.mkdirs();
File dest = new File(file, servicePid + ".cfg");
if (dest.exists()) {
dest.delete();
}
renameFile(outFile, dest);
}
}
m_updateLatch = new CountDownLatch(1);
}
/**
* Get the configuration and if it not available yet wait for it. If there is still no configuration after the wait
* time, null is returned.
*/
private void waitForConfigurationDelete(String pid) throws Exception {
assertTrue(m_deleteLatch.await(2, TimeUnit.SECONDS));
}
}