/* * 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.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; 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.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 { 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 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"); try(YangStoreSnapshotImpl snapshot = createSnapshot(mbeParser, proposedNewState)) { onSnapshotSuccess(proposedNewState, snapshot); } catch(YangStoreException e) { onSnapshotFailure(bundle, addedURLs, e); } } } return bundle; } private 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 {}, 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.toString()); } private void onSnapshotSuccess(Multimap proposedNewState, YangStoreSnapshotImpl snapshot) { // consistent state // merge into consistentBundlesToYangURLs.clear(); consistentBundlesToYangURLs.putAll(proposedNewState); inconsistentBundlesToYangURLs.clear(); updateCache(snapshot); 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 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) { logger.debug("Removed bundle {} {} {}", bundle, event, object); inconsistentBundlesToYangURLs.removeAll(bundle); consistentBundlesToYangURLs.removeAll(bundle); } @Override public synchronized YangStoreSnapshot getYangStoreSnapshot() throws YangStoreException { Optional yangStoreOpt = cache.getSnapshotIfPossible(consistentBundlesToYangURLs); if (yangStoreOpt.isPresent()) { logger.debug("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 = null; @GuardedBy("this") private Optional cachedYangStoreSnapshot = getInitialSnapshot(); 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, 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(); } } 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); } }