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;fp=opendaylight%2Fconfig%2Fyang-store-impl%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fcontroller%2Fconfig%2Fyang%2Fstore%2Fimpl%2FExtenderYangTracker.java;h=2ead596d05f7be50610b574177be44a0555e466c;hb=4b66c069b5bd80e18c3c038186d44c52bb689a7b;hp=0000000000000000000000000000000000000000;hpb=4e6f0838e508e0dce5dfa62faa29b43e979f5ef8;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 new file mode 100644 index 0000000000..2ead596d05 --- /dev/null +++ b/opendaylight/config/yang-store-impl/src/main/java/org/opendaylight/controller/config/yang/store/impl/ExtenderYangTracker.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2013 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.config.yang.store.impl; + +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.YangStoreService; +import org.opendaylight.controller.config.yang.store.api.YangStoreSnapshot; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleEvent; +import org.osgi.util.tracker.BundleTracker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +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.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 { + + 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 YangStoreCache cache = new YangStoreCache(); + private final MbeParser mbeParser; + + + public ExtenderYangTracker(Optional maybeBlacklist, BundleContext bundleContext) { + this(new MbeParser(), maybeBlacklist, bundleContext); + } + + @GuardedBy("this") + private Optional maybeBlacklist; + + @VisibleForTesting + ExtenderYangTracker(MbeParser mbeParser, Optional maybeBlacklist, BundleContext bundleContext) { + super(bundleContext, BundleEvent.RESOLVED | BundleEvent.UNRESOLVED, null); + this.mbeParser = mbeParser; + this.maybeBlacklist = maybeBlacklist; + open(); + } + + @Override + public synchronized Object addingBundle(Bundle bundle, BundleEvent event) { + + // Ignore system bundle: + // system bundle might have config-api on classpath && + // config-api contains yang files => + // system bundle might contain yang files from that bundle + if (bundle.getBundleId() == 0) + return bundle; + + 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) { + 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"); + boolean success; + String failureReason = null; + try(YangStoreSnapshotImpl snapshot = createSnapshot(mbeParser, proposedNewState)) { + updateCache(snapshot); + success = true; + } catch(YangStoreException e) { + failureReason = e.toString(); + success = false; + } + if (success){ + // consistent state + // merge into + consistentBundlesToYangURLs.clear(); + consistentBundlesToYangURLs.putAll(proposedNewState); + inconsistentBundlesToYangURLs.clear(); + + logger.info("Yang store updated to new consistent state containing {} yang files", consistentBundlesToYangURLs.size()); + logger.trace("Yang store updated to new consistent state containing {}", consistentBundlesToYangURLs); + } else { + // inconsistent state + logger.debug("Yang store is falling back on last consistent state containing {}, inconsistent yang files {}, reason {}", + 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); + inconsistentBundlesToYangURLs.putAll(bundle, addedURLs); + } + } + } + return bundle; + } + + private 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) { + inconsistentBundlesToYangURLs.removeAll(bundle); + consistentBundlesToYangURLs.removeAll(bundle); + } + + @Override + public synchronized YangStoreSnapshot getYangStoreSnapshot() + throws YangStoreException { + Optional yangStoreOpt = cache.getSnapshotIfPossible(consistentBundlesToYangURLs); + if (yangStoreOpt.isPresent()) { + logger.trace("Returning cached yang store {}", yangStoreOpt.get()); + return yangStoreOpt.get(); + } + 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) { + throw new YangStoreException("Unable to parse yang files from following URLs: " + multimap, e); + } + } + + private static Collection fromUrlsToInputStreams(Multimap multimap) { + return Collections2.transform(multimap.values(), + new Function() { + + @Override + public InputStream apply(URL url) { + try { + return url.openStream(); + } catch (IOException e) { + logger.warn("Unable to open stream from {}", url); + throw new IllegalStateException( + "Unable to open stream from " + url, e); + } + } + }); + } + + public synchronized void setMaybeBlacklist(Optional maybeBlacklistPattern) { + maybeBlacklist = maybeBlacklistPattern; + cache.invalidate(); + } +} + +class YangStoreCache { + @GuardedBy("this") + private Set cachedUrls = Collections.emptySet(); + @GuardedBy("this") + private Optional cachedYangStoreSnapshot = Optional.absent(); + + synchronized Optional getSnapshotIfPossible(Multimap bundlesToYangURLs) { + Set urls = setFromMultimapValues(bundlesToYangURLs); + if (cachedUrls != null && cachedUrls.equals(urls)) { + Preconditions.checkState(cachedYangStoreSnapshot.isPresent()); + YangStoreSnapshot freshSnapshot = new YangStoreSnapshotImpl(cachedYangStoreSnapshot.get()); + return Optional.of(freshSnapshot); + } + return Optional.absent(); + } + + 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, + YangStoreSnapshotImpl yangStoreSnapshot) { + this.cachedUrls = setFromMultimapValues(urls); + this.cachedYangStoreSnapshot = Optional.of(yangStoreSnapshot); + } + + synchronized void invalidate() { + cachedUrls.clear(); + if (cachedYangStoreSnapshot.isPresent()){ + cachedYangStoreSnapshot.get().close(); + cachedYangStoreSnapshot = Optional.absent(); + } + } +}