X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=opendaylight%2Fconfig%2Fyang-store-impl%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fcontroller%2Fconfig%2Fyang%2Fstore%2Fimpl%2FExtenderYangTracker.java;h=9356dd3752331f2f34fcc33b5df87151081a04b7;hb=286612c816cf7f4d85b1543897474d84b29a9ae3;hp=ee2864878d1c0a7b5e2bdfb27ad49344d79b46fc;hpb=85f16942a4a1ccdf1bc10213cdedfa09f7d325b8;p=controller.git diff --git a/opendaylight/config/yang-store-impl/src/main/java/org/opendaylight/controller/config/yang/store/impl/ExtenderYangTracker.java b/opendaylight/config/yang-store-impl/src/main/java/org/opendaylight/controller/config/yang/store/impl/ExtenderYangTracker.java index ee2864878d..9356dd3752 100644 --- a/opendaylight/config/yang-store-impl/src/main/java/org/opendaylight/controller/config/yang/store/impl/ExtenderYangTracker.java +++ b/opendaylight/config/yang-store-impl/src/main/java/org/opendaylight/controller/config/yang/store/impl/ExtenderYangTracker.java @@ -7,16 +7,19 @@ */ package org.opendaylight.controller.config.yang.store.impl; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.*; - +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Collections2; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; import org.opendaylight.controller.config.yang.store.api.YangStoreException; -import org.opendaylight.controller.config.yang.store.api.YangStoreListenerRegistration; import org.opendaylight.controller.config.yang.store.api.YangStoreService; import org.opendaylight.controller.config.yang.store.api.YangStoreSnapshot; -import org.opendaylight.controller.config.yang.store.spi.YangStoreListener; +import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry; +import org.opendaylight.yangtools.yang.model.api.Module; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; @@ -24,41 +27,58 @@ import org.osgi.util.tracker.BundleTracker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; -import com.google.common.base.Optional; -import com.google.common.base.Preconditions; -import com.google.common.collect.Collections2; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; -import com.google.common.collect.Sets; +import javax.annotation.concurrent.GuardedBy; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Note on consistency: + * When this bundle is activated after other bundles containing yang files, the resolving order + * is not preserved. We thus maintain two maps, one containing consistent snapshot, other inconsistent. The + * container should eventually send all events and thus making the inconsistent map redundant. + */ +public class ExtenderYangTracker extends BundleTracker implements YangStoreService, AutoCloseable { -public class ExtenderYangTracker extends BundleTracker implements - YangStoreService { + private static final Logger logger = LoggerFactory.getLogger(ExtenderYangTracker.class); - private static final Logger logger = LoggerFactory - .getLogger(ExtenderYangTracker.class); + private final Multimap consistentBundlesToYangURLs = HashMultimap.create(); + + /* + Map of currently problematic yang files that should get fixed eventually after all events are received. + */ + private final Multimap inconsistentBundlesToYangURLs = HashMultimap.create(); - private final Multimap bundlesToYangURLs = HashMultimap - .create(); private final YangStoreCache cache = new YangStoreCache(); private final MbeParser mbeParser; - private final List listeners = new ArrayList<>(); - public ExtenderYangTracker(BundleContext context) { - this(context, new MbeParser()); + public ExtenderYangTracker(Optional maybeBlacklist, BundleContext bundleContext) { + this(new MbeParser(), maybeBlacklist, bundleContext); } + @GuardedBy("this") + private Optional maybeBlacklist; + @VisibleForTesting - ExtenderYangTracker(BundleContext context, MbeParser mbeParser) { - super(context, Bundle.ACTIVE, null); + ExtenderYangTracker(MbeParser mbeParser, Optional maybeBlacklist, BundleContext bundleContext) { + super(bundleContext, BundleEvent.RESOLVED | BundleEvent.UNRESOLVED, null); this.mbeParser = mbeParser; - logger.trace("Registered as extender with context {}", context); + this.maybeBlacklist = maybeBlacklist; + open(); } @Override - public Object addingBundle(Bundle bundle, BundleEvent event) { + public synchronized Object addingBundle(Bundle bundle, BundleEvent event) { // Ignore system bundle: // system bundle might have config-api on classpath && @@ -67,80 +87,111 @@ public class ExtenderYangTracker extends BundleTracker implements if (bundle.getBundleId() == 0) return bundle; - Enumeration yangURLs = bundle.findEntries("META-INF/yang", "*.yang", false); - if (yangURLs != null) { + if (maybeBlacklist.isPresent()) { + Matcher m = maybeBlacklist.get().matcher(bundle.getSymbolicName()); + if (m.matches()) { + logger.debug("Ignoring {} because it is in blacklist {}", bundle, maybeBlacklist); + return bundle; + } + } + + Enumeration enumeration = bundle.findEntries("META-INF/yang", "*.yang", false); + if (enumeration != null && enumeration.hasMoreElements()) { synchronized (this) { - while (yangURLs.hasMoreElements()) { - URL url = yangURLs.nextElement(); - logger.debug("Bundle {} found yang file {}", bundle, url); - bundlesToYangURLs.put(bundle, url); + List addedURLs = new ArrayList<>(); + while (enumeration.hasMoreElements()) { + URL url = enumeration.nextElement(); + addedURLs.add(url); + } + logger.trace("Bundle {} has event {}, bundle state {}, URLs {}", bundle, event, bundle.getState(), addedURLs); + // test that yang store is consistent + Multimap proposedNewState = HashMultimap.create(consistentBundlesToYangURLs); + proposedNewState.putAll(inconsistentBundlesToYangURLs); + proposedNewState.putAll(bundle, addedURLs); + + Preconditions.checkArgument(addedURLs.size() > 0, "No change can occur when no URLs are changed"); + + try(YangStoreSnapshotImpl snapshot = createSnapshot(mbeParser, proposedNewState)) { + onSnapshotSuccess(proposedNewState, snapshot); + } catch(YangStoreException e) { + onSnapshotFailure(bundle, addedURLs, e); } - Collection urls = bundlesToYangURLs.get(bundle); - notifyListeners(urls, true); } } return bundle; } - private void notifyListeners(Collection urls, boolean adding) { - if (urls.size() > 0) { - RuntimeException potential = new RuntimeException("Error while notifying listeners"); - for (YangStoreListener listener : listeners) { - try { - if (adding) { - listener.onAddedYangURL(urls); - } else { - listener.onRemovedYangURL(urls); - } - } catch(RuntimeException e) { - potential.addSuppressed(e); - } - } - if (potential.getSuppressed().length > 0) { - throw potential; - } - } + private synchronized void onSnapshotFailure(Bundle bundle, List addedURLs, Exception failureReason) { + // inconsistent state + inconsistentBundlesToYangURLs.putAll(bundle, addedURLs); + + logger.debug("Yang store is falling back on last consistent state containing {}, inconsistent yang files {}", + consistentBundlesToYangURLs, inconsistentBundlesToYangURLs, failureReason); + logger.warn("Yang store is falling back on last consistent state containing {} files, inconsistent yang files size is {}, reason {}", + consistentBundlesToYangURLs.size(), inconsistentBundlesToYangURLs.size(), failureReason.toString()); + cache.setInconsistentURLsForReporting(inconsistentBundlesToYangURLs.values()); + } + + private synchronized void onSnapshotSuccess(Multimap proposedNewState, YangStoreSnapshotImpl snapshot) { + // consistent state + // merge into + consistentBundlesToYangURLs.clear(); + consistentBundlesToYangURLs.putAll(proposedNewState); + inconsistentBundlesToYangURLs.clear(); + + updateCache(snapshot); + cache.setInconsistentURLsForReporting(Collections. emptySet()); + logger.info("Yang store updated to new consistent state containing {} yang files", consistentBundlesToYangURLs.size()); + logger.debug("Yang store updated to new consistent state containing {}", consistentBundlesToYangURLs); + } + + private synchronized void updateCache(YangStoreSnapshotImpl snapshot) { + cache.cacheYangStore(consistentBundlesToYangURLs, snapshot); + } + + @Override + public void modifiedBundle(Bundle bundle, BundleEvent event, Object object) { + logger.debug("Modified bundle {} {} {}", bundle, event, object); } + /** + * If removing YANG files makes yang store inconsistent, method {@link #getYangStoreSnapshot()} + * will throw exception. There is no rollback. + */ @Override public synchronized void removedBundle(Bundle bundle, BundleEvent event, Object object) { - Collection urls = bundlesToYangURLs.removeAll(bundle); - if (urls.size() > 0) { - logger.debug("Removed following yang URLs {} because of removed bundle {}", urls, bundle); - notifyListeners(urls, false); - } + logger.debug("Removed bundle {} {} {}", bundle, event, object); + inconsistentBundlesToYangURLs.removeAll(bundle); + consistentBundlesToYangURLs.removeAll(bundle); } @Override public synchronized YangStoreSnapshot getYangStoreSnapshot() throws YangStoreException { - Optional yangStoreOpt = cache - .getCachedYangStore(bundlesToYangURLs); + Optional yangStoreOpt = cache.getSnapshotIfPossible(consistentBundlesToYangURLs); if (yangStoreOpt.isPresent()) { logger.debug("Returning cached yang store {}", yangStoreOpt.get()); return yangStoreOpt.get(); } - try { - YangStoreSnapshot yangStoreSnapshot = mbeParser - .parseYangFiles(fromUrlsToInputStreams()); - logger.debug( - "{} module entries parsed successfully from {} yang files", - yangStoreSnapshot.countModuleMXBeanEntries(), - bundlesToYangURLs.values().size()); - cache.cacheYangStore(bundlesToYangURLs, yangStoreSnapshot); + YangStoreSnapshotImpl snapshot = createSnapshot(mbeParser, consistentBundlesToYangURLs); + updateCache(snapshot); + return snapshot; + } + private static YangStoreSnapshotImpl createSnapshot(MbeParser mbeParser, Multimap multimap) throws YangStoreException { + try { + YangStoreSnapshotImpl yangStoreSnapshot = mbeParser.parseYangFiles(fromUrlsToInputStreams(multimap)); + logger.trace("{} module entries parsed successfully from {} yang files", + yangStoreSnapshot.countModuleMXBeanEntries(), multimap.values().size()); return yangStoreSnapshot; } catch (RuntimeException e) { - logger.warn( - "Unable to parse yang files, yang files that were picked up so far: {}", - bundlesToYangURLs, e); - throw new YangStoreException("Unable to parse yang files", e); + throw new YangStoreException("Unable to parse yang files from following URLs: " + multimap, e); } } - private Collection fromUrlsToInputStreams() { - return Collections2.transform(bundlesToYangURLs.values(), + private static Collection fromUrlsToInputStreams(Multimap multimap) { + return Collections2.transform(multimap.values(), new Function() { @Override @@ -156,44 +207,83 @@ public class ExtenderYangTracker extends BundleTracker implements }); } - @Override - public synchronized YangStoreListenerRegistration registerListener(final YangStoreListener listener) { - listeners.add(listener); - return new YangStoreListenerRegistration() { - @Override - public void close() { - listeners.remove(listener); - } - }; + public synchronized void setMaybeBlacklist(Optional maybeBlacklistPattern) { + maybeBlacklist = maybeBlacklistPattern; + cache.invalidate(); } +} + +class YangStoreCache { + private static final Logger logger = LoggerFactory.getLogger(YangStoreCache.class); - private static final class YangStoreCache { + @GuardedBy("this") + private Set cachedUrls = null; + @GuardedBy("this") + private Optional cachedYangStoreSnapshot = getInitialSnapshot(); + @GuardedBy("this") + private Collection inconsistentURLsForReporting = Collections.emptySet(); - Set cachedUrls; - YangStoreSnapshot cachedYangStoreSnapshot; + synchronized Optional getSnapshotIfPossible(Multimap bundlesToYangURLs) { + Set urls = setFromMultimapValues(bundlesToYangURLs); - Optional getCachedYangStore( - Multimap bundlesToYangURLs) { - Set urls = setFromMultimapValues(bundlesToYangURLs); - if (cachedUrls != null && cachedUrls.equals(urls)) { - Preconditions.checkState(cachedYangStoreSnapshot != null); - return Optional.of(cachedYangStoreSnapshot); + if (cachedUrls==null || cachedUrls.equals(urls)) { + Preconditions.checkState(cachedYangStoreSnapshot.isPresent()); + YangStoreSnapshot freshSnapshot = new YangStoreSnapshotImpl(cachedYangStoreSnapshot.get()); + if (inconsistentURLsForReporting.size() > 0){ + logger.warn("Some yang URLs are ignored: {}", inconsistentURLsForReporting); } - return Optional.absent(); + return Optional.of(freshSnapshot); } - private static Set setFromMultimapValues( - Multimap bundlesToYangURLs) { - Set urls = Sets.newHashSet(bundlesToYangURLs.values()); - Preconditions.checkState(bundlesToYangURLs.size() == urls.size()); - return urls; - } + return Optional.absent(); + } - void cacheYangStore(Multimap urls, - YangStoreSnapshot yangStoreSnapshot) { - this.cachedUrls = setFromMultimapValues(urls); - this.cachedYangStoreSnapshot = yangStoreSnapshot; + private static Set setFromMultimapValues( + Multimap bundlesToYangURLs) { + Set urls = Sets.newHashSet(bundlesToYangURLs.values()); + Preconditions.checkState(bundlesToYangURLs.size() == urls.size()); + return urls; + } + + synchronized void cacheYangStore(Multimap urls, + YangStoreSnapshot yangStoreSnapshot) { + this.cachedUrls = setFromMultimapValues(urls); + this.cachedYangStoreSnapshot = Optional.of(yangStoreSnapshot); + } + + synchronized void invalidate() { + cachedUrls.clear(); + if (cachedYangStoreSnapshot.isPresent()){ + cachedYangStoreSnapshot.get().close(); + cachedYangStoreSnapshot = Optional.absent(); } + } + + public synchronized void setInconsistentURLsForReporting(Collection urls){ + inconsistentURLsForReporting = urls; + } + + private Optional getInitialSnapshot() { + YangStoreSnapshot initialSnapshot = new YangStoreSnapshot() { + @Override + public Map> getModuleMXBeanEntryMap() { + return Collections.emptyMap(); + } + @Override + public Map> getModuleMap() { + return Collections.emptyMap(); + } + + @Override + public int countModuleMXBeanEntries() { + return 0; + } + + @Override + public void close() { + } + }; + return Optional.of(initialSnapshot); } }