From 9b99e3841bb3f703cc72883ef9a5ef00cfa32f04 Mon Sep 17 00:00:00 2001 From: Ed Warnicke Date: Sun, 10 Aug 2014 22:01:08 -0500 Subject: [PATCH] config-persister-feature-adapter to push configs from karaf features Change-Id: I66063a44210d1a6d7804903f17bc8fc0b2995af8 Signed-off-by: Ed Warnicke --- .../config-persister-feature-adapter/pom.xml | 74 ++++++ .../ConfigPusherFeatureActivator.java | 40 ++++ .../internal/AbstractFeatureWrapper.java | 214 ++++++++++++++++++ .../internal/ChildAwareFeatureWrapper.java | 109 +++++++++ .../internal/ConfigFeaturesListener.java | 49 ++++ .../internal/ConfigPusherCustomizer.java | 61 +++++ .../internal/ConfigPushingRunnable.java | 79 +++++++ .../internal/FeatureConfigPusher.java | 100 ++++++++ .../internal/FeatureConfigSnapshotHolder.java | 168 ++++++++++++++ .../internal/FeatureServiceCustomizer.java | 58 +++++ opendaylight/config/pom.xml | 1 + 11 files changed, 953 insertions(+) create mode 100644 opendaylight/config/config-persister-feature-adapter/pom.xml create mode 100644 opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/ConfigPusherFeatureActivator.java create mode 100644 opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/AbstractFeatureWrapper.java create mode 100644 opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/ChildAwareFeatureWrapper.java create mode 100644 opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/ConfigFeaturesListener.java create mode 100644 opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/ConfigPusherCustomizer.java create mode 100644 opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/ConfigPushingRunnable.java create mode 100644 opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/FeatureConfigPusher.java create mode 100644 opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/FeatureConfigSnapshotHolder.java create mode 100644 opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/FeatureServiceCustomizer.java diff --git a/opendaylight/config/config-persister-feature-adapter/pom.xml b/opendaylight/config/config-persister-feature-adapter/pom.xml new file mode 100644 index 0000000000..7412a51425 --- /dev/null +++ b/opendaylight/config/config-persister-feature-adapter/pom.xml @@ -0,0 +1,74 @@ + + + + 4.0.0 + + + org.opendaylight.controller + config-subsystem + 0.2.5-SNAPSHOT + .. + + + config-persister-feature-adapter + bundle + + + + org.osgi + org.osgi.core + provided + + + org.apache.karaf.features + org.apache.karaf.features.core + ${karaf.version} + provided + + + org.opendaylight.controller + config-persister-impl + + + org.opendaylight.controller + config-persister-api + + + org.opendaylight.controller + config-persister-directory-xml-adapter + + + org.apache.felix + org.apache.felix.utils + 1.6.0 + provided + + + com.google.guava + guava + + + + + + + org.apache.felix + maven-bundle-plugin + true + + + ${project.artifactId} + ${project.version} + org.opendaylight.controller.configpusherfeature.ConfigPusherFeatureActivator + + org.apache.karaf.features.internal.model, + org.apache.felix.utils.version, + org.opendaylight.controller.configpusherfeature.internal + + + + + + + + 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 index 0000000000..ea99579f16 --- /dev/null +++ b/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/ConfigPusherFeatureActivator.java @@ -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 cpst = null; + + public void start(BundleContext context) throws Exception { + bc = context; + cpc = new ConfigPusherCustomizer(); + cpst = new ServiceTracker(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 index 0000000000..1bf2025c46 --- /dev/null +++ b/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/AbstractFeatureWrapper.java @@ -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 getFeatureConfigSnapshotHolders() throws Exception { + LinkedHashSet snapShotHolders = new LinkedHashSet(); + 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 getDependencies() { + return feature.getDependencies(); + } + + /** + * @return + * @see org.apache.karaf.features.Feature#getBundles() + */ + public List getBundles() { + return feature.getBundles(); + } + + /** + * @return + * @see org.apache.karaf.features.Feature#getConfigurations() + */ + public Map> getConfigurations() { + return feature.getConfigurations(); + } + + /** + * @return + * @see org.apache.karaf.features.Feature#getConfigurationFiles() + */ + public List getConfigurationFiles() { + return feature.getConfigurationFiles(); + } + + /** + * @return + * @see org.apache.karaf.features.Feature#getConditional() + */ + public List 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 index 0000000000..8d2ae68a9a --- /dev/null +++ b/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/ChildAwareFeatureWrapper.java @@ -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 getChildFeatures() throws Exception { + List dependencies = feature.getDependencies(); + LinkedHashSet childFeatures = new LinkedHashSet(); + 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 getFeatureConfigSnapshotHolders() throws Exception { + LinkedHashSet snapShotHolders = new LinkedHashSet(); + 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 index 0000000000..f5f1b856ac --- /dev/null +++ b/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/ConfigFeaturesListener.java @@ -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 queue = new LinkedBlockingQueue(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 index 0000000000..d33a8cba92 --- /dev/null +++ b/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/ConfigPusherCustomizer.java @@ -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, AutoCloseable { + private static final Logger logger = LoggerFactory.getLogger(ConfigPusherCustomizer.class); + private ConfigFeaturesListener configFeaturesListener = null; + private FeatureServiceCustomizer featureServiceCustomizer = null; + private ServiceTracker fsst = null; + + @Override + public ConfigPusher addingService(ServiceReference configPusherServiceReference) { + logger.trace("Got ConfigPusherCustomizer.addingService {}", configPusherServiceReference); + BundleContext bc = configPusherServiceReference.getBundle().getBundleContext(); + ConfigPusher cpService = bc.getService(configPusherServiceReference); + featureServiceCustomizer = new FeatureServiceCustomizer(cpService); + fsst = new ServiceTracker(bc, FeaturesService.class.getName(), featureServiceCustomizer); + fsst.open(); + return cpService; + } + + @Override + public void modifiedService(ServiceReference configPusherServiceReference, ConfigPusher configPusher) { + // we don't care if the properties change + } + + @Override + public void removedService(ServiceReference 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 index 0000000000..06c5c920f4 --- /dev/null +++ b/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/ConfigPushingRunnable.java @@ -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 queue; + private FeatureConfigPusher configPusher; + public ConfigPushingRunnable(ConfigPusher p, FeaturesService f,BlockingQueue q) { + queue = q; + configPusher = new FeatureConfigPusher(p, f); + } + + @Override + public void run() { + List toInstall = new ArrayList(); + 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 toInstall) throws InterruptedException, Exception { + if(event.getType() == EventType.FeatureInstalled) { + toInstall.add(event.getFeature()); + LinkedHashMultimap result = configPusher.pushConfigs(toInstall); + toInstall.removeAll(result.keySet()); + } else if(event.getType() == EventType.FeatureUninstalled) { + toInstall.remove(event.getFeature()); + } + } + + protected void logPushResult(LinkedHashMultimap 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 index 0000000000..1c094ad2dc --- /dev/null +++ b/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/FeatureConfigPusher.java @@ -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 pushedConfigs = new LinkedHashSet(); + /* + * LinkedHashMultimap to track which configs we pushed for each Feature installation + * For future use + */ + LinkedHashMultimap 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 pushConfigs(List features) throws Exception, InterruptedException { + LinkedHashMultimap pushedFeatures = LinkedHashMultimap.create(); + for(Feature feature: features) { + LinkedHashSet configSnapShots = pushConfig(feature); + if(!configSnapShots.isEmpty()) { + pushedFeatures.putAll(feature,configSnapShots); + } + } + return pushedFeatures; + } + + private LinkedHashSet pushConfig(Feature feature) throws Exception, InterruptedException { + LinkedHashSet configs = new LinkedHashSet(); + 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 installedFeatures = Arrays.asList(featuresService.listInstalledFeatures()); + return installedFeatures.contains(feature); + } + + private LinkedHashSet pushConfig(LinkedHashSet configs) throws InterruptedException { + LinkedHashSet configsToPush = new LinkedHashSet(configs); + configsToPush.removeAll(pushedConfigs); + if(!configsToPush.isEmpty()) { + pusher.pushConfigs(new ArrayList(configsToPush)); + pushedConfigs.addAll(configsToPush); + } + LinkedHashSet configsPushed = new LinkedHashSet(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 index 0000000000..d1a92ebe7f --- /dev/null +++ b/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/FeatureConfigSnapshotHolder.java @@ -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 featureChain = new ArrayList(); + + /* + * @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 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 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 index 0000000000..e72c8278e5 --- /dev/null +++ b/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/FeatureServiceCustomizer.java @@ -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, AutoCloseable { + private ConfigPusher configPusher = null; + private ConfigFeaturesListener configFeaturesListener = null; + private ServiceRegistration registration; + + FeatureServiceCustomizer(ConfigPusher c) { + configPusher = c; + } + + + @Override + public FeaturesService addingService(ServiceReference 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 reference, + FeaturesService service) { + // we don't care if the properties change + + } + + @Override + public void removedService(ServiceReference reference, + FeaturesService service) { + close(); + } + + @Override + public void close() { + if(registration != null) { + registration.unregister(); + registration = null; + } + } + +} diff --git a/opendaylight/config/pom.xml b/opendaylight/config/pom.xml index 343d13e9c1..b8ad26116a 100644 --- a/opendaylight/config/pom.xml +++ b/opendaylight/config/pom.xml @@ -23,6 +23,7 @@ config-util config-persister-api config-persister-file-xml-adapter + config-persister-feature-adapter yang-jmx-generator yang-jmx-generator-plugin yang-test -- 2.36.6