Merge "config-persister-feature-adapter to push configs from karaf features"
authorDevin Avery <devin.avery@brocade.com>
Tue, 12 Aug 2014 15:48:31 +0000 (15:48 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Tue, 12 Aug 2014 15:48:31 +0000 (15:48 +0000)
opendaylight/config/config-persister-feature-adapter/pom.xml [new file with mode: 0644]
opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/ConfigPusherFeatureActivator.java [new file with mode: 0644]
opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/AbstractFeatureWrapper.java [new file with mode: 0644]
opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/ChildAwareFeatureWrapper.java [new file with mode: 0644]
opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/ConfigFeaturesListener.java [new file with mode: 0644]
opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/ConfigPusherCustomizer.java [new file with mode: 0644]
opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/ConfigPushingRunnable.java [new file with mode: 0644]
opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/FeatureConfigPusher.java [new file with mode: 0644]
opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/FeatureConfigSnapshotHolder.java [new file with mode: 0644]
opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/FeatureServiceCustomizer.java [new file with mode: 0644]
opendaylight/config/pom.xml

diff --git a/opendaylight/config/config-persister-feature-adapter/pom.xml b/opendaylight/config/config-persister-feature-adapter/pom.xml
new file mode 100644 (file)
index 0000000..7412a51
--- /dev/null
@@ -0,0 +1,74 @@
+<?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>
diff --git a/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/ConfigPusherFeatureActivator.java b/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/ConfigPusherFeatureActivator.java
new file mode 100644 (file)
index 0000000..ea99579
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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;
+    }
+}
diff --git a/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/AbstractFeatureWrapper.java b/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/AbstractFeatureWrapper.java
new file mode 100644 (file)
index 0000000..1bf2025
--- /dev/null
@@ -0,0 +1,214 @@
+/*
+ * 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
diff --git a/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/ChildAwareFeatureWrapper.java b/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/ChildAwareFeatureWrapper.java
new file mode 100644 (file)
index 0000000..8d2ae68
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * 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;
+    }
+
+}
diff --git a/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/ConfigFeaturesListener.java b/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/ConfigFeaturesListener.java
new file mode 100644 (file)
index 0000000..f5f1b85
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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;
+        }
+    }
+}
diff --git a/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/ConfigPusherCustomizer.java b/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/ConfigPusherCustomizer.java
new file mode 100644 (file)
index 0000000..d33a8cb
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * 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
diff --git a/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/ConfigPushingRunnable.java b/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/ConfigPushingRunnable.java
new file mode 100644 (file)
index 0000000..06c5c92
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * 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));
+        }
+    }
+}
diff --git a/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/FeatureConfigPusher.java b/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/FeatureConfigPusher.java
new file mode 100644 (file)
index 0000000..1c094ad
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * 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;
+    }
+}
diff --git a/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/FeatureConfigSnapshotHolder.java b/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/FeatureConfigSnapshotHolder.java
new file mode 100644 (file)
index 0000000..d1a92eb
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * 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);
+    }
+}
diff --git a/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/FeatureServiceCustomizer.java b/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/FeatureServiceCustomizer.java
new file mode 100644 (file)
index 0000000..e72c827
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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;
+        }
+    }
+
+}
index 343d13e9c19194ad67680801639fdf52df9563b3..b8ad26116a2915634be51f430d8d5769ce2e949b 100644 (file)
@@ -23,6 +23,7 @@
     <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>