--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>config-subsystem</artifactId>
+ <version>0.2.5-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+
+ <artifactId>config-persister-feature-adapter</artifactId>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.karaf.features</groupId>
+ <artifactId>org.apache.karaf.features.core</artifactId>
+ <version>${karaf.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>config-persister-impl</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>config-persister-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>config-persister-directory-xml-adapter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.utils</artifactId>
+ <version>1.6.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
+ <Bundle-Version>${project.version}</Bundle-Version>
+ <Bundle-Activator>org.opendaylight.controller.configpusherfeature.ConfigPusherFeatureActivator</Bundle-Activator>
+ <Private-Package>
+ org.apache.karaf.features.internal.model,
+ org.apache.felix.utils.version,
+ org.opendaylight.controller.configpusherfeature.internal
+ </Private-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.configpusherfeature;
+
+import org.opendaylight.controller.config.persist.api.ConfigPusher;
+import org.opendaylight.controller.configpusherfeature.internal.ConfigPusherCustomizer;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class ConfigPusherFeatureActivator implements BundleActivator {
+
+ BundleContext bc = null;
+ ConfigPusherCustomizer cpc = null;
+ ServiceTracker<ConfigPusher,ConfigPusher> cpst = null;
+
+ public void start(BundleContext context) throws Exception {
+ bc = context;
+ cpc = new ConfigPusherCustomizer();
+ cpst = new ServiceTracker<ConfigPusher, ConfigPusher>(bc, ConfigPusher.class.getName(), cpc);
+ cpst.open();
+ }
+
+ public void stop(BundleContext context) throws Exception {
+ if(cpst != null) {
+ cpst.close();
+ cpst = null;
+ }
+ if(cpc != null) {
+ cpc.close();
+ cpc = null;
+ }
+ bc = null;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.configpusherfeature.internal;
+
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.bind.JAXBException;
+
+import org.apache.karaf.features.BundleInfo;
+import org.apache.karaf.features.Conditional;
+import org.apache.karaf.features.ConfigFileInfo;
+import org.apache.karaf.features.Dependency;
+import org.apache.karaf.features.Feature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Preconditions;
+
+/*
+ * Wrap a Feature for the purposes of extracting the FeatureConfigSnapshotHolders from
+ * its underlying ConfigFileInfo's
+ *
+ * Delegates the the contained feature and provides additional methods.
+ */
+public class AbstractFeatureWrapper implements Feature {
+ private static final Logger logger = LoggerFactory.getLogger(AbstractFeatureWrapper.class);
+ protected Feature feature = null;
+
+ protected AbstractFeatureWrapper() {
+ // prevent instantiation without Feature
+ }
+
+ /*
+ * @param f Feature to wrap
+ */
+ public AbstractFeatureWrapper(Feature f) {
+ Preconditions.checkNotNull(f,"FeatureWrapper requires non-null Feature in constructor");
+ this.feature = f;
+ }
+
+ /*
+ * Get FeatureConfigSnapshotHolders appropriate to feed to the config subsystem
+ * from the underlying Feature Config files
+ */
+ public LinkedHashSet<FeatureConfigSnapshotHolder> getFeatureConfigSnapshotHolders() throws Exception {
+ LinkedHashSet <FeatureConfigSnapshotHolder> snapShotHolders = new LinkedHashSet<FeatureConfigSnapshotHolder>();
+ for(ConfigFileInfo c: getConfigurationFiles()) {
+ try {
+ snapShotHolders.add(new FeatureConfigSnapshotHolder(c,this));
+ } catch (JAXBException e) {
+ logger.debug("{} is not a config subsystem config file",c.getFinalname());
+ }
+ }
+ return snapShotHolders;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((feature == null) ? 0 : feature.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ AbstractFeatureWrapper other = (AbstractFeatureWrapper) obj;
+ if (feature == null) {
+ if (other.feature != null)
+ return false;
+ } else if (!feature.equals(other.feature))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return feature.getName();
+ }
+
+ /**
+ * @return
+ * @see org.apache.karaf.features.Feature#getId()
+ */
+ public String getId() {
+ return feature.getId();
+ }
+
+ /**
+ * @return
+ * @see org.apache.karaf.features.Feature#getName()
+ */
+ public String getName() {
+ return feature.getName();
+ }
+
+ /**
+ * @return
+ * @see org.apache.karaf.features.Feature#getDescription()
+ */
+ public String getDescription() {
+ return feature.getDescription();
+ }
+
+ /**
+ * @return
+ * @see org.apache.karaf.features.Feature#getDetails()
+ */
+ public String getDetails() {
+ return feature.getDetails();
+ }
+
+ /**
+ * @return
+ * @see org.apache.karaf.features.Feature#getVersion()
+ */
+ public String getVersion() {
+ return feature.getVersion();
+ }
+
+ /**
+ * @return
+ * @see org.apache.karaf.features.Feature#hasVersion()
+ */
+ public boolean hasVersion() {
+ return feature.hasVersion();
+ }
+
+ /**
+ * @return
+ * @see org.apache.karaf.features.Feature#getResolver()
+ */
+ public String getResolver() {
+ return feature.getResolver();
+ }
+
+ /**
+ * @return
+ * @see org.apache.karaf.features.Feature#getInstall()
+ */
+ public String getInstall() {
+ return feature.getInstall();
+ }
+
+ /**
+ * @return
+ * @see org.apache.karaf.features.Feature#getDependencies()
+ */
+ public List<Dependency> getDependencies() {
+ return feature.getDependencies();
+ }
+
+ /**
+ * @return
+ * @see org.apache.karaf.features.Feature#getBundles()
+ */
+ public List<BundleInfo> getBundles() {
+ return feature.getBundles();
+ }
+
+ /**
+ * @return
+ * @see org.apache.karaf.features.Feature#getConfigurations()
+ */
+ public Map<String, Map<String, String>> getConfigurations() {
+ return feature.getConfigurations();
+ }
+
+ /**
+ * @return
+ * @see org.apache.karaf.features.Feature#getConfigurationFiles()
+ */
+ public List<ConfigFileInfo> getConfigurationFiles() {
+ return feature.getConfigurationFiles();
+ }
+
+ /**
+ * @return
+ * @see org.apache.karaf.features.Feature#getConditional()
+ */
+ public List<? extends Conditional> getConditional() {
+ return feature.getConditional();
+ }
+
+ /**
+ * @return
+ * @see org.apache.karaf.features.Feature#getStartLevel()
+ */
+ public int getStartLevel() {
+ return feature.getStartLevel();
+ }
+
+ /**
+ * @return
+ * @see org.apache.karaf.features.Feature#getRegion()
+ */
+ public String getRegion() {
+ return feature.getRegion();
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.configpusherfeature.internal;
+
+import java.util.LinkedHashSet;
+import java.util.List;
+
+import javax.xml.bind.JAXBException;
+
+import org.apache.felix.utils.version.VersionRange;
+import org.apache.felix.utils.version.VersionTable;
+import org.apache.karaf.features.Dependency;
+import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.FeaturesService;
+import org.osgi.framework.Version;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Preconditions;
+
+/*
+ * Wrap a Feature for the purposes of extracting the FeatureConfigSnapshotHolders from
+ * its underlying ConfigFileInfo's and those of its children recursively
+ *
+ * Delegates the the contained feature and provides additional methods.
+ */
+public class ChildAwareFeatureWrapper extends AbstractFeatureWrapper implements Feature {
+ private static final Logger logger = LoggerFactory.getLogger(ChildAwareFeatureWrapper.class);
+ private FeaturesService featuresService= null;
+
+ protected ChildAwareFeatureWrapper(Feature f) {
+ // Don't use without a feature service
+ }
+
+ /*
+ * @param f Feature to wrap
+ * @param s FeaturesService to look up dependencies
+ */
+ ChildAwareFeatureWrapper(Feature f, FeaturesService s) throws Exception {
+ super(s.getFeature(f.getName(), f.getVersion()));
+ Preconditions.checkNotNull(s, "FeatureWrapper requires non-null FeatureService in constructor");
+ this.featuresService = s;
+ }
+
+ protected FeaturesService getFeaturesService() {
+ return featuresService;
+ }
+
+ /*
+ * Get FeatureConfigSnapshotHolders appropriate to feed to the config subsystem
+ * from the underlying Feature Config files and those of its children recursively
+ */
+ public LinkedHashSet <? extends ChildAwareFeatureWrapper> getChildFeatures() throws Exception {
+ List<Dependency> dependencies = feature.getDependencies();
+ LinkedHashSet <ChildAwareFeatureWrapper> childFeatures = new LinkedHashSet<ChildAwareFeatureWrapper>();
+ if(dependencies != null) {
+ for(Dependency dependency: dependencies) {
+ Feature fi = extractFeatureFromDependency(dependency);
+ if(fi != null){
+ ChildAwareFeatureWrapper wrappedFeature = new ChildAwareFeatureWrapper(fi,featuresService);
+ childFeatures.add(wrappedFeature);
+ }
+ }
+ }
+ return childFeatures;
+ }
+
+ public LinkedHashSet<FeatureConfigSnapshotHolder> getFeatureConfigSnapshotHolders() throws Exception {
+ LinkedHashSet <FeatureConfigSnapshotHolder> snapShotHolders = new LinkedHashSet<FeatureConfigSnapshotHolder>();
+ for(ChildAwareFeatureWrapper c: getChildFeatures()) {
+ for(FeatureConfigSnapshotHolder h: c.getFeatureConfigSnapshotHolders()) {
+ FeatureConfigSnapshotHolder f;
+ try {
+ f = new FeatureConfigSnapshotHolder(h,this);
+ snapShotHolders.add(f);
+ } catch (JAXBException e) {
+ logger.debug("{} is not a config subsystem config file",h.getFileInfo().getFinalname());
+ }
+ }
+ }
+ snapShotHolders.addAll(super.getFeatureConfigSnapshotHolders());
+ return snapShotHolders;
+ }
+
+ protected Feature extractFeatureFromDependency(Dependency dependency) throws Exception {
+ Feature[] features = featuresService.listFeatures();
+ VersionRange range = org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION.equals(dependency.getVersion())
+ ? VersionRange.ANY_VERSION : new VersionRange(dependency.getVersion(), true, true);
+ Feature fi = null;
+ for(Feature f: features) {
+ if (f.getName().equals(dependency.getName())) {
+ Version v = VersionTable.getVersion(f.getVersion());
+ if (range.contains(v)) {
+ if (fi == null || VersionTable.getVersion(fi.getVersion()).compareTo(v) < 0) {
+ fi = f;
+ break;
+ }
+ }
+ }
+ }
+ return fi;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.configpusherfeature.internal;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.apache.karaf.features.FeatureEvent;
+import org.apache.karaf.features.FeaturesListener;
+import org.apache.karaf.features.FeaturesService;
+import org.apache.karaf.features.RepositoryEvent;
+import org.opendaylight.controller.config.persist.api.ConfigPusher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ConfigFeaturesListener implements FeaturesListener, AutoCloseable {
+ private static final Logger logger = LoggerFactory.getLogger(ConfigFeaturesListener.class);
+ private static final int QUEUE_SIZE = 100;
+ private BlockingQueue<FeatureEvent> queue = new LinkedBlockingQueue<FeatureEvent>(QUEUE_SIZE);
+ Thread pushingThread = null;
+
+ public ConfigFeaturesListener(ConfigPusher p, FeaturesService f) {
+ pushingThread = new Thread(new ConfigPushingRunnable(p, f, queue), "ConfigFeatureListener - ConfigPusher");
+ pushingThread.start();
+ }
+
+ @Override
+ public void featureEvent(FeatureEvent event) {
+ queue.offer(event);
+ }
+
+ @Override
+ public void repositoryEvent(RepositoryEvent event) {
+ logger.debug("Repository: " + event.getType() + " " + event.getRepository());
+ }
+
+ @Override
+ public void close() {
+ if(pushingThread != null) {
+ pushingThread.interrupt();
+ pushingThread = null;
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.configpusherfeature.internal;
+
+import org.apache.karaf.features.FeaturesService;
+import org.opendaylight.controller.config.persist.api.ConfigPusher;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ConfigPusherCustomizer implements ServiceTrackerCustomizer<ConfigPusher, ConfigPusher>, AutoCloseable {
+ private static final Logger logger = LoggerFactory.getLogger(ConfigPusherCustomizer.class);
+ private ConfigFeaturesListener configFeaturesListener = null;
+ private FeatureServiceCustomizer featureServiceCustomizer = null;
+ private ServiceTracker<FeaturesService,FeaturesService> fsst = null;
+
+ @Override
+ public ConfigPusher addingService(ServiceReference<ConfigPusher> configPusherServiceReference) {
+ logger.trace("Got ConfigPusherCustomizer.addingService {}", configPusherServiceReference);
+ BundleContext bc = configPusherServiceReference.getBundle().getBundleContext();
+ ConfigPusher cpService = bc.getService(configPusherServiceReference);
+ featureServiceCustomizer = new FeatureServiceCustomizer(cpService);
+ fsst = new ServiceTracker<FeaturesService, FeaturesService>(bc, FeaturesService.class.getName(), featureServiceCustomizer);
+ fsst.open();
+ return cpService;
+ }
+
+ @Override
+ public void modifiedService(ServiceReference<ConfigPusher> configPusherServiceReference, ConfigPusher configPusher) {
+ // we don't care if the properties change
+ }
+
+ @Override
+ public void removedService(ServiceReference<ConfigPusher> configPusherServiceReference, ConfigPusher configPusher) {
+ this.close();
+ }
+
+ @Override
+ public void close() {
+ if(fsst != null) {
+ fsst.close();
+ fsst = null;
+ }
+ if(configFeaturesListener != null) {
+ configFeaturesListener.close();
+ configFeaturesListener = null;
+ }
+ if(featureServiceCustomizer != null) {
+ featureServiceCustomizer.close();
+ featureServiceCustomizer = null;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.configpusherfeature.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.FeatureEvent;
+import org.apache.karaf.features.FeatureEvent.EventType;
+import org.apache.karaf.features.FeaturesService;
+import org.opendaylight.controller.config.persist.api.ConfigPusher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.LinkedHashMultimap;
+
+public class ConfigPushingRunnable implements Runnable {
+ private static final Logger logger = LoggerFactory.getLogger(ConfigPushingRunnable.class);
+ private static final int POLL_TIME = 1;
+ private BlockingQueue<FeatureEvent> queue;
+ private FeatureConfigPusher configPusher;
+ public ConfigPushingRunnable(ConfigPusher p, FeaturesService f,BlockingQueue<FeatureEvent> q) {
+ queue = q;
+ configPusher = new FeatureConfigPusher(p, f);
+ }
+
+ @Override
+ public void run() {
+ List<Feature> toInstall = new ArrayList<Feature>();
+ FeatureEvent event;
+ boolean interuppted = false;
+ while(true) {
+ try {
+ if(!interuppted) {
+ if(toInstall.isEmpty()) {
+ event = queue.take();
+ } else {
+ event = queue.poll(POLL_TIME, TimeUnit.MILLISECONDS);
+ }
+ if(event != null && event.getFeature() !=null) {
+ processFeatureEvent(event,toInstall);
+ }
+ } else if(toInstall.isEmpty()) {
+ logger.error("ConfigPushingRunnable - exiting");
+ return;
+ }
+ } catch (InterruptedException e) {
+ logger.error("ConfigPushingRunnable - interupted");
+ interuppted = true;
+ } catch (Exception e) {
+ logger.error("Exception while processing features {}", e);
+ }
+ }
+ }
+
+ protected void processFeatureEvent(FeatureEvent event, List<Feature> toInstall) throws InterruptedException, Exception {
+ if(event.getType() == EventType.FeatureInstalled) {
+ toInstall.add(event.getFeature());
+ LinkedHashMultimap<Feature,FeatureConfigSnapshotHolder> result = configPusher.pushConfigs(toInstall);
+ toInstall.removeAll(result.keySet());
+ } else if(event.getType() == EventType.FeatureUninstalled) {
+ toInstall.remove(event.getFeature());
+ }
+ }
+
+ protected void logPushResult(LinkedHashMultimap<Feature,FeatureConfigSnapshotHolder> results) {
+ for(Feature f:results.keySet()) {
+ logger.info("Pushed configs for feature {} {}",f,results.get(f));
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.configpusherfeature.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.List;
+
+import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.FeaturesService;
+import org.opendaylight.controller.config.persist.api.ConfigPusher;
+import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.LinkedHashMultimap;
+
+/*
+ * Simple class to push configs to the config subsystem from Feature's configfiles
+ */
+public class FeatureConfigPusher {
+ private static final Logger logger = LoggerFactory.getLogger(FeatureConfigPusher.class);
+ private FeaturesService featuresService = null;
+ private ConfigPusher pusher = null;
+ /*
+ * A LinkedHashSet (to preserve order and insure uniqueness) of the pushedConfigs
+ * This is used to prevent pushing duplicate configs if a Feature is in multiple dependency
+ * chains. Also, preserves the *original* Feature chain for which we pushed the config.
+ * (which is handy for logging).
+ */
+ LinkedHashSet<FeatureConfigSnapshotHolder> pushedConfigs = new LinkedHashSet<FeatureConfigSnapshotHolder>();
+ /*
+ * LinkedHashMultimap to track which configs we pushed for each Feature installation
+ * For future use
+ */
+ LinkedHashMultimap<Feature,FeatureConfigSnapshotHolder> feature2configs = LinkedHashMultimap.create();
+
+ /*
+ * @param p - ConfigPusher to push ConfigSnapshotHolders
+ */
+ public FeatureConfigPusher(ConfigPusher p, FeaturesService f) {
+ pusher = p;
+ featuresService = f;
+ }
+ /*
+ * Push config files from Features to config subsystem
+ * @param features - list of Features to extract config files from recursively and push
+ * to the config subsystem
+ *
+ * @return A LinkedHashMultimap of Features to the FeatureConfigSnapshotHolder actually pushed
+ * 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(List<Feature> features) throws Exception, InterruptedException {
+ LinkedHashMultimap<Feature,FeatureConfigSnapshotHolder> pushedFeatures = LinkedHashMultimap.create();
+ for(Feature feature: features) {
+ LinkedHashSet<FeatureConfigSnapshotHolder> configSnapShots = pushConfig(feature);
+ if(!configSnapShots.isEmpty()) {
+ pushedFeatures.putAll(feature,configSnapShots);
+ }
+ }
+ return pushedFeatures;
+ }
+
+ private LinkedHashSet<FeatureConfigSnapshotHolder> pushConfig(Feature feature) throws Exception, InterruptedException {
+ LinkedHashSet<FeatureConfigSnapshotHolder> configs = new LinkedHashSet<FeatureConfigSnapshotHolder>();
+ if(isInstalled(feature)) {
+ ChildAwareFeatureWrapper wrappedFeature = new ChildAwareFeatureWrapper(feature,featuresService);
+ configs = wrappedFeature.getFeatureConfigSnapshotHolders();
+ if(!configs.isEmpty()) {
+ configs = pushConfig(configs);
+ feature2configs.putAll(feature, configs);
+ }
+ }
+ return configs;
+ }
+
+ private boolean isInstalled(Feature feature) {
+ List<Feature> installedFeatures = Arrays.asList(featuresService.listInstalledFeatures());
+ return installedFeatures.contains(feature);
+ }
+
+ private LinkedHashSet<FeatureConfigSnapshotHolder> pushConfig(LinkedHashSet<FeatureConfigSnapshotHolder> configs) throws InterruptedException {
+ LinkedHashSet<FeatureConfigSnapshotHolder> configsToPush = new LinkedHashSet<FeatureConfigSnapshotHolder>(configs);
+ configsToPush.removeAll(pushedConfigs);
+ if(!configsToPush.isEmpty()) {
+ pusher.pushConfigs(new ArrayList<ConfigSnapshotHolder>(configsToPush));
+ pushedConfigs.addAll(configsToPush);
+ }
+ LinkedHashSet<FeatureConfigSnapshotHolder> configsPushed = new LinkedHashSet<FeatureConfigSnapshotHolder>(pushedConfigs);
+ configsPushed.retainAll(configs);
+ return configsPushed;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.configpusherfeature.internal;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.SortedSet;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+
+import org.apache.karaf.features.ConfigFileInfo;
+import org.apache.karaf.features.Feature;
+import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder;
+import org.opendaylight.controller.config.persist.storage.file.xml.model.ConfigSnapshot;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+/*
+ * A ConfigSnapshotHolder that can track all the additional information
+ * relavent to the fact we are getting these from a Feature.
+ *
+ * Includes tracking the 'featureChain' - an reverse ordered list of the dependency
+ * graph of features that caused us to push this FeatureConfigSnapshotHolder.
+ * So if A -> B -> C, then the feature chain would be C -> B -> A
+ */
+public class FeatureConfigSnapshotHolder implements ConfigSnapshotHolder {
+ private ConfigSnapshot unmarshalled = null;
+ private ConfigFileInfo fileInfo = null;
+ private List<Feature> featureChain = new ArrayList<Feature>();
+
+ /*
+ * @param holder - FeatureConfigSnapshotHolder that we
+ * @param feature - new
+ */
+ public FeatureConfigSnapshotHolder(final FeatureConfigSnapshotHolder holder, final Feature feature) throws JAXBException {
+ this(holder.fileInfo,holder.getFeature());
+ this.featureChain.add(feature);
+ }
+
+ /*
+ * Create a FeatureConfigSnapshotHolder for a given ConfigFileInfo and record the associated
+ * feature we are creating it from.
+ * @param fileInfo - ConfigFileInfo to read into the ConfigSnapshot
+ * @param feature - Feature the ConfigFileInfo was attached to
+ */
+ public FeatureConfigSnapshotHolder(final ConfigFileInfo fileInfo, final Feature feature) throws JAXBException {
+ Preconditions.checkNotNull(fileInfo);
+ Preconditions.checkNotNull(fileInfo.getFinalname());
+ Preconditions.checkNotNull(feature);
+ this.fileInfo = fileInfo;
+ this.featureChain.add(feature);
+ JAXBContext jaxbContext = JAXBContext.newInstance(ConfigSnapshot.class);
+ Unmarshaller um = jaxbContext.createUnmarshaller();
+ File file = new File(fileInfo.getFinalname());
+ unmarshalled = ((ConfigSnapshot) um.unmarshal(file));
+ }
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ *
+ * We really care most about the underlying ConfigShapshot, so compute hashcode on that
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((unmarshalled != null && unmarshalled.getConfigSnapshot() == null) ? 0 : unmarshalled.getConfigSnapshot().hashCode());
+ return result;
+ }
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ * *
+ * We really care most about the underlying ConfigShapshot, so compute equality on that
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ FeatureConfigSnapshotHolder fcsh = (FeatureConfigSnapshotHolder)obj;
+ if(this.unmarshalled.getConfigSnapshot().equals(fcsh.unmarshalled.getConfigSnapshot())) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ Path p = Paths.get(fileInfo.getFinalname());
+ b.append(p.getFileName())
+ .append("(")
+ .append(getCauseFeature())
+ .append(",")
+ .append(getFeature())
+ .append(")");
+ return b.toString();
+
+ }
+
+ @Override
+ public String getConfigSnapshot() {
+ return unmarshalled.getConfigSnapshot();
+ }
+
+ @Override
+ public SortedSet<String> getCapabilities() {
+ return unmarshalled.getCapabilities();
+ }
+
+ public ConfigFileInfo getFileInfo() {
+ return fileInfo;
+ }
+
+ /*
+ * @returns The original feature to which the ConfigFileInfo was attached
+ * Example:
+ * A -> B -> C, ConfigFileInfo Foo is attached to C.
+ * feature:install A
+ * thus C is the 'Feature' Foo was attached.
+ */
+ public Feature getFeature() {
+ return featureChain.get(0);
+ }
+
+ /*
+ * @return The dependency chain of the features that caused the ConfigFileInfo to be pushed in reverse order.
+ * Example:
+ * A -> B -> C, ConfigFileInfo Foo is attached to C.
+ * The returned list is
+ * [C,B,A]
+ */
+ public ImmutableList<Feature> getFeatureChain() {
+ return ImmutableList.copyOf(Lists.reverse(featureChain));
+ }
+
+ /*
+ * @return The feature the installation of which was the root cause
+ * of this pushing of the ConfigFileInfo.
+ * Example:
+ * A -> B -> C, ConfigFileInfo Foo is attached to C.
+ * feature:install A
+ * this A is the 'Cause' of the installation of Foo.
+ */
+ public Feature getCauseFeature() {
+ return Iterables.getLast(featureChain);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.configpusherfeature.internal;
+
+import org.apache.karaf.features.FeaturesListener;
+import org.apache.karaf.features.FeaturesService;
+import org.opendaylight.controller.config.persist.api.ConfigPusher;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+
+public class FeatureServiceCustomizer implements ServiceTrackerCustomizer<FeaturesService, FeaturesService>, AutoCloseable {
+ private ConfigPusher configPusher = null;
+ private ConfigFeaturesListener configFeaturesListener = null;
+ private ServiceRegistration<?> registration;
+
+ FeatureServiceCustomizer(ConfigPusher c) {
+ configPusher = c;
+ }
+
+
+ @Override
+ public FeaturesService addingService(ServiceReference<FeaturesService> reference) {
+ BundleContext bc = reference.getBundle().getBundleContext();
+ FeaturesService featureService = bc.getService(reference);
+ configFeaturesListener = new ConfigFeaturesListener(configPusher,featureService);
+ registration = bc.registerService(FeaturesListener.class.getCanonicalName(), configFeaturesListener, null);
+ return featureService;
+ }
+
+ @Override
+ public void modifiedService(ServiceReference<FeaturesService> reference,
+ FeaturesService service) {
+ // we don't care if the properties change
+
+ }
+
+ @Override
+ public void removedService(ServiceReference<FeaturesService> reference,
+ FeaturesService service) {
+ close();
+ }
+
+ @Override
+ public void close() {
+ if(registration != null) {
+ registration.unregister();
+ registration = null;
+ }
+ }
+
+}
<module>config-util</module>
<module>config-persister-api</module>
<module>config-persister-file-xml-adapter</module>
+ <module>config-persister-feature-adapter</module>
<module>yang-jmx-generator</module>
<module>yang-jmx-generator-plugin</module>
<module>yang-test</module>