diff options
author | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
---|---|---|
committer | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
commit | 72231250ed81e10d66bfe70701e64fa5fe50f712 (patch) | |
tree | 2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /container-core/src/main/java/com/yahoo/container/core |
Publish
Diffstat (limited to 'container-core/src/main/java/com/yahoo/container/core')
8 files changed, 574 insertions, 0 deletions
diff --git a/container-core/src/main/java/com/yahoo/container/core/BundleLoaderProperties.java b/container-core/src/main/java/com/yahoo/container/core/BundleLoaderProperties.java new file mode 100644 index 00000000000..d9ff342c107 --- /dev/null +++ b/container-core/src/main/java/com/yahoo/container/core/BundleLoaderProperties.java @@ -0,0 +1,14 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.core; + +/** + * @author gjoranv + * @since 5.46 + */ +public interface BundleLoaderProperties { + // TODO: This should be removed. The prefix is used to separate the bundles in BundlesConfig + // into those that are transferred with filedistribution and those that are preinstalled + // on disk. Instead, the model should have put them in two different configs. I.e. create a new + // config 'preinstalled-bundles.def'. + public static final String DISK_BUNDLE_PREFIX = "file:"; +} diff --git a/container-core/src/main/java/com/yahoo/container/core/config/BundleLoader.java b/container-core/src/main/java/com/yahoo/container/core/config/BundleLoader.java new file mode 100644 index 00000000000..682abcc53ac --- /dev/null +++ b/container-core/src/main/java/com/yahoo/container/core/config/BundleLoader.java @@ -0,0 +1,186 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.core.config; + +import com.yahoo.collections.PredicateSplit; +import com.yahoo.config.FileReference; +import com.yahoo.container.Container; +import com.yahoo.filedistribution.fileacquirer.FileAcquirer; +import com.yahoo.osgi.Osgi; +import org.osgi.framework.Bundle; +import org.osgi.framework.wiring.BundleRevision; + +import java.io.File; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +import static com.yahoo.collections.PredicateSplit.partition; +import static com.yahoo.container.core.BundleLoaderProperties.DISK_BUNDLE_PREFIX; + +/** + * Manages the set of installed 3rd-party component bundles. + * + * @author tonytv + */ +public class BundleLoader { + + private final List<Bundle> initialBundles; + + private final Map<FileReference, List<Bundle>> reference2Bundles = new LinkedHashMap<>(); + + private final Logger log = Logger.getLogger(BundleLoader.class.getName()); + private final Osgi osgi; + + public BundleLoader(Osgi osgi) { + this.osgi = osgi; + initialBundles = Arrays.asList(osgi.getBundles()); + } + + private List<Bundle> obtainBundles(FileReference reference, FileAcquirer fileAcquirer) + throws InterruptedException { + File file = fileAcquirer.waitFor(reference, 15, TimeUnit.MINUTES); + return osgi.install(file.getAbsolutePath()); + } + + /** + * @return the number of bundles installed by this call. + */ + private int install(List<FileReference> references) { + Set<FileReference> bundlesToInstall = new HashSet<>(references); + bundlesToInstall.removeAll(reference2Bundles.keySet()); + + PredicateSplit<FileReference> bundlesToInstall_isDisk = partition(bundlesToInstall, BundleLoader::isDiskBundle); + installBundlesFromDisk(bundlesToInstall_isDisk.trueValues); + installBundlesFromFileDistribution(bundlesToInstall_isDisk.falseValues); + + startBundles(); + return bundlesToInstall.size(); + } + + private static boolean isDiskBundle(FileReference fileReference) { + return fileReference.value().startsWith(DISK_BUNDLE_PREFIX); + } + + private void installBundlesFromDisk(List<FileReference> bundlesToInstall) { + for (FileReference reference : bundlesToInstall) { + try { + installBundleFromDisk(reference); + } + catch(Exception e) { + throw new RuntimeException("Could not install bundle '" + reference + "'", e); + } + } + } + + private void installBundlesFromFileDistribution(List<FileReference> bundlesToInstall) { + if (!bundlesToInstall.isEmpty()) { + FileAcquirer fileAcquirer = Container.get().getFileAcquirer(); + boolean hasFileDistribution = (fileAcquirer != null); + if (hasFileDistribution) { + installWithFileDistribution(bundlesToInstall, fileAcquirer); + } else { + log.warning("Can't retrieve bundles since file distribution is disabled."); + } + } + } + + private void installBundleFromDisk(FileReference reference) { + assert(reference.value().startsWith(DISK_BUNDLE_PREFIX)); + String referenceFileName = reference.value().substring(DISK_BUNDLE_PREFIX.length()); + log.info("Installing bundle from disk with reference '" + reference.value() + "'"); + + File file = new File(referenceFileName); + if (!file.exists()) { + throw new IllegalArgumentException("Reference '" + reference.value() + "' not found on disk."); + } + + List<Bundle> bundles = osgi.install(file.getAbsolutePath()); + reference2Bundles.put(reference, bundles); + } + + private void installWithFileDistribution(List<FileReference> bundlesToInstall, FileAcquirer fileAcquirer) { + for (FileReference reference : bundlesToInstall) { + try { + log.info("Installing bundle with reference '" + reference.value() + "'"); + List<Bundle> bundles = obtainBundles(reference, fileAcquirer); + reference2Bundles.put(reference, bundles); + } + catch(Exception e) { + throw new RuntimeException("Could not install bundle '" + reference + "'", e); + } + } + } + + //all bundles must have been started first to ensure correct package resolution. + private void startBundles() { + for (List<Bundle> bundles : reference2Bundles.values()) { + for (Bundle bundle : bundles) { + try { + if (!isFragment(bundle)) + bundle.start(); + } catch(Exception e) { + throw new RuntimeException("Could not start bundle '" + bundle.getSymbolicName() + "'", e); + } + } + } + } + + // The OSGi APIs are just getting worse... + private boolean isFragment(Bundle bundle) { + BundleRevision bundleRevision = bundle.adapt(BundleRevision.class); + if (bundleRevision == null) + throw new NullPointerException("Null bundle revision means that bundle has probably been uninstalled: " + + bundle.getSymbolicName() + ":" + bundle.getVersion()); + return (bundleRevision.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0; + } + + /** + * Returns the number of uninstalled bundles + */ + private int retainOnly(List<FileReference> newReferences) { + Set<Bundle> bundlesToRemove = new HashSet<>(Arrays.asList(osgi.getBundles())); + + for (FileReference fileReferenceToKeep: newReferences) { + if (reference2Bundles.containsKey(fileReferenceToKeep)) + bundlesToRemove.removeAll(reference2Bundles.get(fileReferenceToKeep)); + } + + bundlesToRemove.removeAll(initialBundles); + for (Bundle bundle : bundlesToRemove) { + log.info("Removing bundle '" + bundle.toString() + "'"); + osgi.uninstall(bundle); + } + + Set<FileReference> fileReferencesToRemove = new HashSet<>(reference2Bundles.keySet()); + fileReferencesToRemove.removeAll(newReferences); + + for (FileReference fileReferenceToRemove : fileReferencesToRemove) { + reference2Bundles.remove(fileReferenceToRemove); + } + return bundlesToRemove.size(); + } + + public synchronized int use(List<FileReference> bundles) { + int removedBundles = retainOnly(bundles); + int installedBundles = install(bundles); + startBundles(); + + log.info(removedBundles + " bundles were removed, and " + installedBundles + " bundles were installed."); + log.info(installedBundlesMessage()); + return removedBundles + installedBundles; + } + + private String installedBundlesMessage() { + StringBuilder sb = new StringBuilder("Installed bundles: {" ); + for (Bundle b : osgi.getBundles()) + sb.append("[" + b.getBundleId() + "]" + b.getSymbolicName() + ", "); + sb.setLength(sb.length() - 2); + sb.append("}"); + return sb.toString(); + } +} diff --git a/container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java b/container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java new file mode 100644 index 00000000000..0f56934c5bc --- /dev/null +++ b/container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java @@ -0,0 +1,201 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.core.config; + +import com.google.inject.AbstractModule; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.yahoo.component.AbstractComponent; +import com.yahoo.component.ComponentSpecification; +import com.yahoo.component.provider.ComponentRegistry; +import com.yahoo.concurrent.ThreadFactoryFactory; +import com.yahoo.config.FileReference; +import com.yahoo.container.core.DiagnosticsConfig; +import com.yahoo.container.di.ComponentDeconstructor; +import com.yahoo.container.di.Container; +import com.yahoo.container.di.componentgraph.core.ComponentGraph; +import com.yahoo.container.di.componentgraph.core.DotGraph; +import com.yahoo.container.di.config.SubscriberFactory; +import com.yahoo.container.di.osgi.OsgiUtil; +import com.yahoo.container.handler.observability.OverviewHandler; +import com.yahoo.container.logging.AccessLog; +import com.yahoo.container.logging.AccessLogInterface; +import com.yahoo.container.protect.FreezeDetector; +import com.yahoo.jdisc.application.OsgiFramework; +import com.yahoo.jdisc.handler.RequestHandler; +import com.yahoo.jdisc.service.ClientProvider; +import com.yahoo.jdisc.service.ServerProvider; +import com.yahoo.language.Linguistics; +import com.yahoo.language.simple.SimpleLinguistics; +import com.yahoo.log.LogLevel; +import com.yahoo.osgi.OsgiImpl; +import com.yahoo.statistics.Statistics; + +import org.osgi.framework.Bundle; +import org.osgi.framework.wiring.BundleWiring; +import scala.collection.immutable.Set; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.logging.Logger; + +import static com.yahoo.collections.CollectionUtil.first; +import static com.yahoo.container.util.Util.quote; + + +/** + * For internal use only. + * + * @author tonytv + * @author gjoranv + */ +//TODO: rename +public class HandlersConfigurerDi { + + private static final Logger log = Logger.getLogger(HandlersConfigurerDi.class.getName()); + + public static class RegistriesHack { + + @Inject + public RegistriesHack(com.yahoo.container.Container vespaContainer, + ComponentRegistry<AbstractComponent> allComponents, + ComponentRegistry<RequestHandler> requestHandlerRegistry, + ComponentRegistry<ClientProvider> clientProviderRegistry, + ComponentRegistry<ServerProvider> serverProviderRegistry) { + log.log(LogLevel.DEBUG, "RegistriesHack.init " + System.identityHashCode(this)); + + vespaContainer.setComponentRegistry(allComponents); + vespaContainer.setRequestHandlerRegistry(requestHandlerRegistry); + vespaContainer.setClientProviderRegistry(clientProviderRegistry); + vespaContainer.setServerProviderRegistry(serverProviderRegistry); + } + + } + + private final com.yahoo.container.Container vespaContainer; + private final OsgiWrapper osgiWrapper; + private final Container container; + + private volatile ComponentGraph currentGraph = new ComponentGraph(0); + + public HandlersConfigurerDi(SubscriberFactory subscriberFactory, + com.yahoo.container.Container vespaContainer, + String configId, + ComponentDeconstructor deconstructor, + Injector discInjector, + OsgiFramework osgiFramework) { + + this.vespaContainer = vespaContainer; + osgiWrapper = new OsgiWrapper(osgiFramework, vespaContainer.getBundleLoader()); + + container = new Container( + subscriberFactory, + configId, + deconstructor, + osgiWrapper + ); + try { + runOnceAndEnsureRegistryHackRun(discInjector); + } catch (InterruptedException e) { + throw new RuntimeException("Interrupted while setting up handlers for the first time."); + } + } + + private static class OsgiWrapper extends OsgiImpl implements com.yahoo.container.di.Osgi { + + private final OsgiFramework osgiFramework; + private final BundleLoader bundleLoader; + + public OsgiWrapper(OsgiFramework osgiFramework, BundleLoader bundleLoader) { + super(osgiFramework); + this.osgiFramework = osgiFramework; + this.bundleLoader = bundleLoader; + } + + + @Override + public BundleClasses getBundleClasses(ComponentSpecification bundleSpec, Set<String> packagesToScan) { + //Not written in an OO way since FelixFramework resides in JDisc core which for now is pure java, + //and to load from classpath one needs classes from scalalib. + + //Temporary hack: Using class name since ClassLoaderOsgiFramework is not available at compile time in this bundle. + if (osgiFramework.getClass().getName().equals("com.yahoo.application.container.impl.ClassLoaderOsgiFramework")) { + Bundle syntheticClassPathBundle = first(osgiFramework.bundles()); + ClassLoader classLoader = syntheticClassPathBundle.adapt(BundleWiring.class).getClassLoader(); + + return new BundleClasses( + syntheticClassPathBundle, + OsgiUtil.getClassEntriesForBundleUsingProjectClassPathMappings(classLoader, bundleSpec, packagesToScan)); + } else { + Bundle bundle = getBundle(bundleSpec); + if (bundle == null) + throw new RuntimeException("No bundle matching " + quote(bundleSpec)); + + return new BundleClasses(bundle, OsgiUtil.getClassEntriesInBundleClassPath(bundle, packagesToScan)); + } + } + + @Override + public void useBundles(Collection<FileReference> bundles) { + log.info("Installing bundles from the latest application"); + + List<FileReference> fileReferences = new ArrayList<>(bundles); + int bundlesRemovedOrInstalled = bundleLoader.use(fileReferences); + + if (bundlesRemovedOrInstalled > 0) { + refreshPackages(); + } + } + } + + public void runOnceAndEnsureRegistryHackRun(Injector discInjector) throws InterruptedException { + currentGraph = container.runOnce(currentGraph, createFallbackInjector(vespaContainer, discInjector)); + + RegistriesHack registriesHack = currentGraph.getInstance(RegistriesHack.class); + assert (registriesHack != null); + injectDotGraph(); + } + + @SuppressWarnings("deprecation") + private Injector createFallbackInjector(final com.yahoo.container.Container vespaContainer, Injector discInjector) { + return discInjector.createChildInjector(new AbstractModule() { + @Override + protected void configure() { + bind(com.yahoo.container.Container.class).toInstance(vespaContainer); + bind(com.yahoo.statistics.Statistics.class).toInstance(Statistics.nullImplementation); + bind(Linguistics.class).toInstance(new SimpleLinguistics()); + bind(FreezeDetector.class).toInstance(new FreezeDetector(new DiagnosticsConfig(new DiagnosticsConfig.Builder().disabled(true)))); + bind(AccessLog.class).toInstance(new AccessLog(new ComponentRegistry<>())); + bind(Executor.class).toInstance(Executors.newCachedThreadPool(ThreadFactoryFactory.getThreadFactory("HandlersConfigurerDI"))); + + if (vespaContainer.getFileAcquirer() != null) + bind(com.yahoo.filedistribution.fileacquirer.FileAcquirer.class).toInstance(vespaContainer.getFileAcquirer()); + } + }); + } + + private void injectDotGraph() { + try { + OverviewHandler overviewHandler = currentGraph.getInstance(OverviewHandler.class); + overviewHandler.setDotGraph(DotGraph.generate(currentGraph)); + } catch (Exception e) { + log.fine("No overview handler"); + } + + } + + public void reloadConfig(long generation) { + container.reloadConfig(generation); + } + + public <T> T getComponent(Class<T> componentClass) { + return currentGraph.getInstance(componentClass); + } + + public void shutdown(ComponentDeconstructor deconstructor) { + container.shutdown(currentGraph, deconstructor); + } + +} diff --git a/container-core/src/main/java/com/yahoo/container/core/config/testutil/HandlersConfigurerTestWrapper.java b/container-core/src/main/java/com/yahoo/container/core/config/testutil/HandlersConfigurerTestWrapper.java new file mode 100644 index 00000000000..b01b800a462 --- /dev/null +++ b/container-core/src/main/java/com/yahoo/container/core/config/testutil/HandlersConfigurerTestWrapper.java @@ -0,0 +1,132 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.core.config.testutil; + +import com.google.inject.Guice; +import com.yahoo.component.AbstractComponent; +import com.yahoo.component.provider.ComponentRegistry; +import com.yahoo.config.subscription.ConfigSourceSet; +import com.yahoo.container.Container; +import com.yahoo.container.di.CloudSubscriberFactory; +import com.yahoo.container.di.ComponentDeconstructor; +import com.yahoo.container.core.config.HandlersConfigurerDi; +import com.yahoo.jdisc.handler.RequestHandler; +import com.yahoo.osgi.MockOsgi; + +import java.io.File; +import java.io.IOException; +import java.util.LinkedHashSet; +import java.util.Random; +import java.util.Set; + +/** + * Class for testing HandlersConfigurer. + * Not for public use. + * + * If possible, please avoid using this class and HandlersConfigurer in your tests + * @author tonytv + * @author gjoranv + * +*/ +public class HandlersConfigurerTestWrapper { + private ConfigSourceSet configSources = + new ConfigSourceSet(this.getClass().getSimpleName() + ": " + new Random().nextLong()); + private HandlersConfigurerDi configurer; + + // TODO: Remove once tests use ConfigSet rather than dir: + private final static String testFiles[] = { + "components.cfg", + "handlers.cfg", + "bundles.cfg", + "string.cfg", + "int.cfg", + "renderers.cfg", + "diagnostics.cfg", + "qr-templates.cfg", + "documentmanager.cfg", + "schemamapping.cfg", + "chains.cfg", + "container-mbus.cfg", + "container-mbus.cfg", + "specialtokens.cfg", + "documentdb-info.cfg", + "qr-search.cfg", + "query-profiles.cfg" + }; + private final Set<File> createdFiles = new LinkedHashSet<>(); + private int lastGeneration = 0; + private final Container container; + + private void createFiles(String configId) { + if (configId.startsWith("dir:")) { + try { + System.setProperty("config.id", configId); + String dirName = configId.substring(4); + for (String file : testFiles) { + createIfNotExists(dirName, file); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + // TODO: Remove once tests use ConfigSet rather than dir: + private void createIfNotExists(String dir, String file) throws IOException { + final File f = new File(dir + "/" + file); + if (f.createNewFile()) { + createdFiles.add(f); + } + } + + public HandlersConfigurerTestWrapper(String configId) { + this(Container.get(), configId); + } + + public HandlersConfigurerTestWrapper(Container container, String configId) { + createFiles(configId); + MockOsgi mockOsgi = new MockOsgi(); + container.setOsgi(mockOsgi); + ComponentDeconstructor testDeconstructor = getTestDeconstructor(); + configurer = new HandlersConfigurerDi( + new CloudSubscriberFactory(configSources), + container, + configId, + testDeconstructor, + Guice.createInjector(), + mockOsgi); + this.container = container; + } + + private ComponentDeconstructor getTestDeconstructor() { + return new ComponentDeconstructor() { + @Override + public void deconstruct(Object component) { + if (component instanceof AbstractComponent) { + AbstractComponent abstractComponent = (AbstractComponent) component; + if (abstractComponent.isDeconstructable()) + ((AbstractComponent) component).deconstruct(); + } + }}; + } + + public void reloadConfig() { + configurer.reloadConfig(++lastGeneration); + try { + configurer.runOnceAndEnsureRegistryHackRun(Guice.createInjector()); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + public void shutdown() { + // TODO: Remove once tests use ConfigSet rather than dir: + for (File f : createdFiles) { + f.delete(); + } + } + + public ComponentRegistry<RequestHandler> getRequestHandlerRegistry() { + return container.getRequestHandlerRegistry(); + } + +} diff --git a/container-core/src/main/java/com/yahoo/container/core/document/package-info.java b/container-core/src/main/java/com/yahoo/container/core/document/package-info.java new file mode 100644 index 00000000000..ffb130493ed --- /dev/null +++ b/container-core/src/main/java/com/yahoo/container/core/document/package-info.java @@ -0,0 +1,5 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +@ExportPackage +package com.yahoo.container.core.document; + +import com.yahoo.osgi.annotation.ExportPackage; diff --git a/container-core/src/main/java/com/yahoo/container/core/http/package-info.java b/container-core/src/main/java/com/yahoo/container/core/http/package-info.java new file mode 100644 index 00000000000..2db74625480 --- /dev/null +++ b/container-core/src/main/java/com/yahoo/container/core/http/package-info.java @@ -0,0 +1,5 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +@ExportPackage +package com.yahoo.container.core.http; + +import com.yahoo.osgi.annotation.ExportPackage; diff --git a/container-core/src/main/java/com/yahoo/container/core/package-info.java b/container-core/src/main/java/com/yahoo/container/core/package-info.java new file mode 100644 index 00000000000..c9b43a8be66 --- /dev/null +++ b/container-core/src/main/java/com/yahoo/container/core/package-info.java @@ -0,0 +1,5 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +@ExportPackage +package com.yahoo.container.core; + +import com.yahoo.osgi.annotation.ExportPackage; diff --git a/container-core/src/main/java/com/yahoo/container/core/slobrok/SlobrokConfigurator.java b/container-core/src/main/java/com/yahoo/container/core/slobrok/SlobrokConfigurator.java new file mode 100644 index 00000000000..7e19df254c1 --- /dev/null +++ b/container-core/src/main/java/com/yahoo/container/core/slobrok/SlobrokConfigurator.java @@ -0,0 +1,26 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.core.slobrok; + +import com.yahoo.cloud.config.SlobroksConfig; +import com.yahoo.cloud.config.SlobroksConfig.Slobrok; +import com.yahoo.container.Container; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Configures which slobrok nodes the container should register with. + * @author tonytv + */ +public class SlobrokConfigurator { + public SlobrokConfigurator(SlobroksConfig config) { + Container.get().getRpcAdaptor().registerInSlobrok( + connectionSpecs(config.slobrok())); + } + + private static List<String> connectionSpecs(List<Slobrok> slobroks) { + return slobroks.stream(). + map(Slobrok::connectionspec). + collect(Collectors.toList()); + } +} |