*/
package org.opendaylight.controller.configpusherfeature.internal;
+import com.google.common.base.Optional;
import com.google.common.collect.LinkedHashMultimap;
import java.util.ArrayList;
import java.util.Arrays;
import org.apache.karaf.features.FeaturesService;
import org.opendaylight.controller.config.persist.api.ConfigPusher;
import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder;
+import org.opendaylight.controller.config.persist.storage.file.xml.XmlFileStorageAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
* If a Feature is not in the returned LinkedHashMultimap then we couldn't push its configs
* (Ususally because it was not yet installed)
*/
- public LinkedHashMultimap<Feature,FeatureConfigSnapshotHolder> pushConfigs(final List<Feature> features) throws Exception, InterruptedException {
- LinkedHashMultimap<Feature,FeatureConfigSnapshotHolder> pushedFeatures = LinkedHashMultimap.create();
- for(Feature feature: features) {
+ public LinkedHashMultimap<Feature, FeatureConfigSnapshotHolder> pushConfigs(final List<Feature> features) throws Exception {
+ LinkedHashMultimap<Feature, FeatureConfigSnapshotHolder> pushedFeatures = LinkedHashMultimap.create();
+ for (Feature feature : features) {
+
+
LinkedHashSet<FeatureConfigSnapshotHolder> configSnapShots = pushConfig(feature);
- if(!configSnapShots.isEmpty()) {
- pushedFeatures.putAll(feature,configSnapShots);
+ if (!configSnapShots.isEmpty()) {
+ pushedFeatures.putAll(feature, configSnapShots);
}
}
return pushedFeatures;
}
- private LinkedHashSet<FeatureConfigSnapshotHolder> pushConfig(final Feature feature) throws Exception, InterruptedException {
- LinkedHashSet<FeatureConfigSnapshotHolder> configs = new LinkedHashSet<FeatureConfigSnapshotHolder>();
+ private LinkedHashSet<FeatureConfigSnapshotHolder> pushConfig(final Feature feature) throws Exception {
+ LinkedHashSet<FeatureConfigSnapshotHolder> configs = new LinkedHashSet<>();
if(isInstalled(feature)) {
// FIXME Workaround for BUG-2836, features service returns null for feature: standard-condition-webconsole_0_0_0, 3.0.1
if(featuresService.getFeature(feature.getName(), feature.getVersion()) == null) {
ChildAwareFeatureWrapper wrappedFeature = new ChildAwareFeatureWrapper(feature, featuresService);
configs = wrappedFeature.getFeatureConfigSnapshotHolders();
if (!configs.isEmpty()) {
- configs = pushConfig(configs);
+ configs = pushConfig(configs, feature);
feature2configs.putAll(feature, configs);
}
}
return false;
}
- private LinkedHashSet<FeatureConfigSnapshotHolder> pushConfig(final LinkedHashSet<FeatureConfigSnapshotHolder> configs) throws InterruptedException {
+ private LinkedHashSet<FeatureConfigSnapshotHolder> pushConfig(final LinkedHashSet<FeatureConfigSnapshotHolder> configs, final Feature feature) throws InterruptedException {
LinkedHashSet<FeatureConfigSnapshotHolder> configsToPush = new LinkedHashSet<FeatureConfigSnapshotHolder>(configs);
configsToPush.removeAll(pushedConfigs);
- if(!configsToPush.isEmpty()) {
- pusher.pushConfigs(new ArrayList<ConfigSnapshotHolder>(configsToPush));
+ if (!configsToPush.isEmpty()) {
+
+ // Ignore features that are present in persisted current config
+ final Optional<XmlFileStorageAdapter> currentCfgPusher = XmlFileStorageAdapter.getInstance();
+ if (currentCfgPusher.isPresent() &&
+ currentCfgPusher.get().getPersistedFeatures().contains(feature.getId())) {
+ LOG.warn("Ignoring default configuration {} for feature {}, the configuration is present in {}",
+ configsToPush, feature.getId(), currentCfgPusher.get());
+ } else {
+ pusher.pushConfigs(new ArrayList<ConfigSnapshotHolder>(configsToPush));
+ }
+
pushedConfigs.addAll(configsToPush);
}
LinkedHashSet<FeatureConfigSnapshotHolder> configsPushed = new LinkedHashSet<FeatureConfigSnapshotHolder>(pushedConfigs);
*/
package org.opendaylight.controller.configpusherfeature.internal;
+import com.google.common.base.Optional;
+import com.google.common.collect.Sets;
+import java.util.Set;
+import org.apache.karaf.features.Feature;
import org.apache.karaf.features.FeaturesListener;
import org.apache.karaf.features.FeaturesService;
import org.opendaylight.controller.config.persist.api.ConfigPusher;
+import org.opendaylight.controller.config.persist.storage.file.xml.FeatureListProvider;
+import org.opendaylight.controller.config.persist.storage.file.xml.XmlFileStorageAdapter;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
@Override
public FeaturesService addingService(ServiceReference<FeaturesService> reference) {
BundleContext bc = reference.getBundle().getBundleContext();
- FeaturesService featureService = bc.getService(reference);
+ final FeaturesService featureService = bc.getService(reference);
+ final Optional<XmlFileStorageAdapter> currentPersister = XmlFileStorageAdapter.getInstance();
+
+ if(XmlFileStorageAdapter.getInstance().isPresent()) {
+ final Set<String> installedFeatureIds = Sets.newHashSet();
+ for (final Feature installedFeature : featureService.listInstalledFeatures()) {
+ installedFeatureIds.add(installedFeature.getId());
+ }
+
+ currentPersister.get().setFeaturesService(new FeatureListProvider() {
+ @Override
+ public Set<String> listFeatures() {
+ return installedFeatureIds;
+ }
+ });
+ }
configFeaturesListener = new ConfigFeaturesListener(configPusher,featureService);
registration = bc.registerService(FeaturesListener.class.getCanonicalName(), configFeaturesListener, null);
return featureService;
--- /dev/null
+package org.opendaylight.controller.config.persist.storage.file.xml;
+
+import java.util.Set;
+
+/**
+ * Wrapper for services providing list of features to be stored along with the config snapshot
+ */
+public interface FeatureListProvider {
+
+ Set<String> listFeatures();
+}
import java.io.IOException;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
import java.util.SortedSet;
import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder;
import org.opendaylight.controller.config.persist.api.Persister;
private static Integer numberOfStoredBackups;
private File storage;
+ private static volatile XmlFileStorageAdapter instance;
+ private volatile ConfigSnapshot lastCfgSnapshotCache;
+ private volatile Optional<FeatureListProvider> featuresService = Optional.absent();
+
+ @VisibleForTesting
+ public void reset() {
+ instance = null;
+ lastCfgSnapshotCache = null;
+ featuresService = null;
+ }
+
@Override
public Persister instantiate(PropertiesProvider propertiesProvider) {
+ if(instance != null) {
+ return instance;
+ }
+
File storage = extractStorageFileFromProperties(propertiesProvider);
LOG.debug("Using file {}", storage.getAbsolutePath());
// Create file if it does not exist
+ " property should be either set to positive value, or ommited. Can not be set to 0.");
}
setFileStorage(storage);
+
+ instance = this;
return this;
}
+ public static Optional<XmlFileStorageAdapter> getInstance() {
+ return Optional.fromNullable(instance);
+ }
+
+ public Set<String> getPersistedFeatures() {
+ return lastCfgSnapshotCache == null ? Collections.<String>emptySet() : lastCfgSnapshotCache.getFeatures();
+ }
+
+ public void setFeaturesService(final FeatureListProvider featuresService) {
+ this.featuresService = Optional.of(featuresService);
+ }
+
@VisibleForTesting
public void setFileStorage(File storage) {
this.storage = storage;
public void persistConfig(ConfigSnapshotHolder holder) throws IOException {
Preconditions.checkNotNull(storage, "Storage file is null");
+ Set<String> installedFeatureIds = Collections.emptySet();
+ if(featuresService.isPresent()) {
+ installedFeatureIds = featuresService.get().listFeatures();
+ }
+
Config cfg = Config.fromXml(storage);
- cfg.addConfigSnapshot(ConfigSnapshot.fromConfigSnapshot(holder), numberOfStoredBackups);
+ cfg.addConfigSnapshot(ConfigSnapshot.fromConfigSnapshot(holder, installedFeatureIds), numberOfStoredBackups);
cfg.toXml(storage);
}
Optional<ConfigSnapshot> lastSnapshot = Config.fromXml(storage).getLastSnapshot();
if (lastSnapshot.isPresent()) {
- return Lists.newArrayList(toConfigSnapshot(lastSnapshot.get()));
+ lastCfgSnapshotCache = lastSnapshot.get();
+ return Lists.newArrayList(toConfigSnapshot(lastCfgSnapshotCache));
} else {
return Collections.emptyList();
}
*/
package org.opendaylight.controller.config.persist.storage.file.xml.model;
+import java.util.HashSet;
+import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.xml.bind.annotation.XmlAnyElement;
private String configSnapshot;
private SortedSet<String> capabilities = new TreeSet<>();
+ private Set<String> features = new HashSet<>();
ConfigSnapshot(String configXml, SortedSet<String> capabilities) {
this.configSnapshot = configXml;
this.capabilities = capabilities;
}
+ ConfigSnapshot(String configXml, SortedSet<String> capabilities, Set<String> features) {
+ this.configSnapshot = configXml;
+ this.capabilities = capabilities;
+ this.features = features;
+ }
+
public ConfigSnapshot() {
}
return new ConfigSnapshot(cfg.getConfigSnapshot(), cfg.getCapabilities());
}
+ public static ConfigSnapshot fromConfigSnapshot(ConfigSnapshotHolder cfg, Set<String> features) {
+ return new ConfigSnapshot(cfg.getConfigSnapshot(), cfg.getCapabilities(), features);
+ }
@XmlAnyElement(SnapshotHandler.class)
public String getConfigSnapshot() {
this.capabilities = capabilities;
}
+ @XmlElement(name = "feature")
+ @XmlElementWrapper(name = "features")
+ @XmlJavaTypeAdapter(value=StringTrimAdapter.class)
+ public Set<String> getFeatures() {
+ return features;
+ }
+
+ public void setFeatures(final Set<String> features) {
+ this.features = features;
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("ConfigSnapshot{");
sb.append("configSnapshot='").append(configSnapshot).append('\'');
sb.append(", capabilities=").append(capabilities);
+ sb.append(", features=").append(features);
sb.append('}');
return sb.toString();
}
package org.opendaylight.controller.config.persist.storage.file.xml;
+import static junit.framework.Assert.assertTrue;
import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import com.google.common.base.Charsets;
+import com.google.common.collect.Sets;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-import org.mockito.Mockito;
import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder;
import org.opendaylight.controller.config.persist.test.PropertiesProviderTest;
private File file;
private static final String NON_EXISTENT_DIRECTORY = "./nonExistentDir/";
private static final String NON_EXISTENT_FILE = "nonExistent.txt";
+ private XmlFileStorageAdapter storage;
@Before
public void setUp() throws Exception {
}
com.google.common.io.Files.write("", file, Charsets.UTF_8);
i = 1;
+ storage = new XmlFileStorageAdapter();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ storage.reset();
}
@Test
public void testNewFile() throws Exception {
- XmlFileStorageAdapter storage = new XmlFileStorageAdapter();
PropertiesProviderTest pp = new PropertiesProviderTest();
pp.addProperty("fileStorage",NON_EXISTENT_DIRECTORY+NON_EXISTENT_FILE);
pp.addProperty("numberOfBackups",Integer.toString(Integer.MAX_VALUE));
assertEquals(storage.toString().replace("\\","/"),"XmlFileStorageAdapter [storage="+NON_EXISTENT_DIRECTORY+NON_EXISTENT_FILE+"]");
delete(new File(NON_EXISTENT_DIRECTORY));
}
+
@Test
public void testFileAdapter() throws Exception {
- XmlFileStorageAdapter storage = new XmlFileStorageAdapter();
PropertiesProviderTest pp = new PropertiesProviderTest();
pp.addProperty("fileStorage",file.getPath());
pp.addProperty("numberOfBackups",Integer.toString(Integer.MAX_VALUE));
storage.persistConfig(holder);
- assertEquals(27, com.google.common.io.Files.readLines(file, Charsets.UTF_8).size());
+ assertEquals(29, com.google.common.io.Files.readLines(file, Charsets.UTF_8).size());
List<ConfigSnapshotHolder> lastConf = storage.loadLastConfigs();
assertEquals(1, lastConf.size());
ConfigSnapshotHolder configSnapshotHolder = lastConf.get(0);
@Test
public void testFileAdapterOneBackup() throws Exception {
- XmlFileStorageAdapter storage = new XmlFileStorageAdapter();
-
PropertiesProviderTest pp = new PropertiesProviderTest();
pp.addProperty("fileStorage",file.getPath());
pp.addProperty("numberOfBackups",Integer.toString(Integer.MAX_VALUE));
storage.persistConfig(holder);
- assertEquals(27, com.google.common.io.Files.readLines(file, Charsets.UTF_8).size());
+ assertEquals(29, com.google.common.io.Files.readLines(file, Charsets.UTF_8).size());
List<ConfigSnapshotHolder> lastConf = storage.loadLastConfigs();
assertEquals(1, lastConf.size());
assertXMLEqual("<config>2</config>", configSnapshotHolder.getConfigSnapshot());
}
+ @Test
+ public void testWithFeatures() throws Exception {
+ PropertiesProviderTest pp = new PropertiesProviderTest();
+ pp.addProperty("fileStorage",file.getPath());
+ pp.addProperty("numberOfBackups",Integer.toString(Integer.MAX_VALUE));
+ storage.instantiate(pp);
+
+ final ConfigSnapshotHolder holder = new ConfigSnapshotHolder() {
+ @Override
+ public String getConfigSnapshot() {
+ return createConfig();
+ }
+
+ @Override
+ public SortedSet<String> getCapabilities() {
+ return createCaps();
+ }
+ };
+ final FeatureListProvider mock = mock(FeatureListProvider.class);
+
+ doReturn(Sets.newHashSet("f1-11", "f2-22")).when(mock).listFeatures();
+ storage.setFeaturesService(mock);
+ storage.persistConfig(holder);
+
+ assertEquals(20, com.google.common.io.Files.readLines(file, Charsets.UTF_8).size());
+
+ List<ConfigSnapshotHolder> lastConf = storage.loadLastConfigs();
+ assertEquals(1, lastConf.size());
+ ConfigSnapshotHolder configSnapshotHolder = lastConf.get(0);
+ assertXMLEqual("<config>1</config>", configSnapshotHolder.getConfigSnapshot());
+ assertEquals(Sets.newHashSet("f1-11", "f2-22"), storage.getPersistedFeatures());
+ }
+
+ @Test
+ public void testNoFeaturesStored() throws Exception {
+ PropertiesProviderTest pp = new PropertiesProviderTest();
+ pp.addProperty("fileStorage",file.getPath());
+ pp.addProperty("numberOfBackups",Integer.toString(Integer.MAX_VALUE));
+ storage.instantiate(pp);
+
+ com.google.common.io.Files.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+ "<persisted-snapshots>\n" +
+ " <snapshots>\n" +
+ " <snapshot>\n" +
+ " <required-capabilities>\n" +
+ " <capability>cap12</capability>\n" +
+ " </required-capabilities>\n" +
+ " <configuration>\n" +
+ " <config>1</config>\n" +
+ " </configuration>\n" +
+ " </snapshot>\n" +
+ " </snapshots>\n" +
+ "</persisted-snapshots>", file, Charsets.UTF_8);
+
+ List<ConfigSnapshotHolder> lastConf = storage.loadLastConfigs();
+ assertEquals(1, lastConf.size());
+ ConfigSnapshotHolder configSnapshotHolder = lastConf.get(0);
+ assertXMLEqual("<config>1</config>", configSnapshotHolder.getConfigSnapshot());
+ assertTrue(storage.getPersistedFeatures().isEmpty());
+ }
+
@Test
public void testFileAdapterOnlyTwoBackups() throws Exception {
- XmlFileStorageAdapter storage = new XmlFileStorageAdapter();
storage.setFileStorage(file);
storage.setNumberOfBackups(2);
final ConfigSnapshotHolder holder = new ConfigSnapshotHolder() {
storage.persistConfig(holder);
List<String> readLines = com.google.common.io.Files.readLines(file, Charsets.UTF_8);
- assertEquals(27, readLines.size());
+ assertEquals(29, readLines.size());
List<ConfigSnapshotHolder> lastConf = storage.loadLastConfigs();
assertEquals(1, lastConf.size());
ConfigSnapshotHolder configSnapshotHolder = lastConf.get(0);
assertXMLEqual("<config>3</config>", configSnapshotHolder.getConfigSnapshot());
assertFalse(readLines.contains(holder.getConfigSnapshot()));
+ assertTrue(storage.getPersistedFeatures().isEmpty());
}
@Test
storage.persistConfig(new ConfigSnapshotHolder() {
@Override
public String getConfigSnapshot() {
- return Mockito.mock(String.class);
+ return mock(String.class);
}
@Override
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
+import org.junit.Before;
import org.junit.Test;
import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder;
import org.opendaylight.controller.config.persist.api.Persister;
}
}
+ @Before
+ public void setUp() throws Exception {
+ if(XmlFileStorageAdapter.getInstance().isPresent()) {
+ XmlFileStorageAdapter.getInstance().get().reset();
+ }
+ }
+
@Test
public void testDummyAdapter() throws Exception {
PersisterAggregator persisterAggregator = PersisterAggregator.createFromProperties(TestingPropertiesProvider.loadFile("test1.properties"));