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-disc/src/main/java/com/yahoo/container |
Publish
Diffstat (limited to 'container-disc/src/main/java/com/yahoo/container')
29 files changed, 1789 insertions, 0 deletions
diff --git a/container-disc/src/main/java/com/yahoo/container/FilterConfigProvider.java b/container-disc/src/main/java/com/yahoo/container/FilterConfigProvider.java new file mode 100644 index 00000000000..0b27d83630b --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/FilterConfigProvider.java @@ -0,0 +1,77 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container; + +import com.yahoo.container.core.http.HttpFilterConfig; +import com.yahoo.container.di.componentgraph.Provider; +import com.yahoo.jdisc.http.filter.FilterConfig; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * @author tonytv + */ +public final class FilterConfigProvider implements Provider<FilterConfig> { + + private static class MapFilterConfig implements FilterConfig { + + private final Map<String, String> initParameters; + private final String filterName; + private final String filterClass; + + MapFilterConfig(Map<String, String> initParameters, String filterName, String filterClass) { + this.initParameters = initParameters; + this.filterName = filterName; + this.filterClass = filterClass; + } + + @Override + public String getFilterName() { + return filterName; + } + + @Override + public String getFilterClass() { + return filterClass; + } + + @Override + public String getInitParameter(String name) { + return initParameters.get(name); + } + + @Override + public boolean getBooleanInitParameter(String name, boolean defaultValue) { + return initParameters.containsKey(name) ? + Boolean.parseBoolean(initParameters.get(name)) : + defaultValue; + } + + @Override + public Collection<String> getInitParameterNames() { + return initParameters.keySet(); + } + } + + private final FilterConfig filterConfig; + + public FilterConfigProvider(HttpFilterConfig vespaConfig) { + filterConfig = new MapFilterConfig(toMap(vespaConfig), vespaConfig.filterName(), vespaConfig.filterClass()); + } + + private static Map<String, String> toMap(HttpFilterConfig vespaConfig) { + Map<String, String> parameters = new HashMap<>(); + for (HttpFilterConfig.Param param : vespaConfig.param()) + parameters.put(param.name(), param.value()); + return parameters; + } + + @Override + public FilterConfig get() { + return filterConfig; + } + + @Override + public void deconstruct() {} +} diff --git a/container-disc/src/main/java/com/yahoo/container/config/jersey/package-info.java b/container-disc/src/main/java/com/yahoo/container/config/jersey/package-info.java new file mode 100644 index 00000000000..ab8b41b3a18 --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/config/jersey/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.config.jersey; + +import com.yahoo.osgi.annotation.ExportPackage; diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/CertificateStoreProvider.java b/container-disc/src/main/java/com/yahoo/container/jdisc/CertificateStoreProvider.java new file mode 100644 index 00000000000..251fbc83d2e --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/CertificateStoreProvider.java @@ -0,0 +1,34 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc; + +import com.yahoo.container.di.componentgraph.Provider; +import com.yahoo.jdisc.http.CertificateStore; + +/** + * An certificate store provider which provides a factory which throws exception on + * invocation - as no certificate store is currently provided by default. + * The purpose of this is to provide a certificate store for injection in the case where + * no certificate store component is provided. + * + * @author bratseth + */ +public class CertificateStoreProvider implements Provider<CertificateStore> { + + private static final ThrowingCertificateStore instance = new ThrowingCertificateStore(); + + @Override + public CertificateStore get() { return instance; } + + @Override + public void deconstruct() { } + + private static final class ThrowingCertificateStore implements CertificateStore { + + @Override + public String getCertificate(String key, long ttl, long retry) { + throw new UnsupportedOperationException("A certificate store is not available"); + } + + } + +} diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java b/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java new file mode 100644 index 00000000000..62fdb36cbad --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java @@ -0,0 +1,328 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc; + +import com.google.common.collect.MapMaker; +import com.google.inject.AbstractModule; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.yahoo.component.provider.ComponentRegistry; +import com.yahoo.concurrent.DaemonThreadFactory; +import com.yahoo.config.ConfigInstance; +import com.yahoo.config.subscription.ConfigInterruptedException; +import com.yahoo.container.Container; +import com.yahoo.container.QrConfig; +import com.yahoo.container.core.ChainsConfig; +import com.yahoo.container.core.config.HandlersConfigurerDi; +import com.yahoo.container.di.config.Subscriber; +import com.yahoo.container.di.config.SubscriberFactory; +import com.yahoo.container.http.filter.FilterChainRepository; +import com.yahoo.container.jdisc.component.Deconstructor; +import com.yahoo.container.jdisc.config.PortOverridesConfig; +import com.yahoo.container.jdisc.metric.DisableGuiceMetric; +import com.yahoo.jdisc.Metric; +import com.yahoo.jdisc.application.Application; +import com.yahoo.jdisc.application.BindingRepository; +import com.yahoo.jdisc.application.ContainerActivator; +import com.yahoo.jdisc.application.ContainerBuilder; +import com.yahoo.jdisc.application.GuiceRepository; +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.jrt.ListenFailedException; +import com.yahoo.log.LogSetup; +import com.yahoo.osgi.OsgiImpl; +import com.yahoo.vespa.config.ConfigKey; +import com.yahoo.yolean.Exceptions; + +import java.util.Collections; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static com.yahoo.collections.CollectionUtil.first; + +/** + * @author tonytv + */ +public final class ConfiguredApplication implements Application { + + public static final class ApplicationContext { + + final JdiscBindingsConfig discBindingsConfig; + + public ApplicationContext(com.yahoo.container.jdisc.JdiscBindingsConfig discBindingsConfig) { + this.discBindingsConfig = discBindingsConfig; + } + } + + private static final Logger log = Logger.getLogger(ConfiguredApplication.class.getName()); + private static final Set<ClientProvider> startedClients = newWeakIdentitySet(); + + private static final Set<ServerProvider> startedServers = Collections.newSetFromMap(new IdentityHashMap<>()); + private final SubscriberFactory subscriberFactory; + private final ContainerActivator activator; + private final String configId; + private final ContainerDiscApplication applicationWithLegacySetup; + private final OsgiFramework osgiFramework; + private final com.yahoo.jdisc.Timer timerSingleton; + //TODO: FilterChainRepository should instead always be set up in the model. + private final FilterChainRepository defaultFilterChainRepository = + new FilterChainRepository(new ChainsConfig(new ChainsConfig.Builder()), + new ComponentRegistry<>(), + new ComponentRegistry<>(), + new ComponentRegistry<>(), + new ComponentRegistry<>()); + private final ContainerPortsOverride defaultContainerPortsOverride = new ContainerPortsOverride( + new PortOverridesConfig(new PortOverridesConfig.Builder())); + private final OsgiFramework restrictedOsgiFramework; + private volatile int applicationSerialNo = 0; + private HandlersConfigurerDi configurer; + private ScheduledThreadPoolExecutor shutdownDeadlineExecutor; + private Thread reconfigurerThread; + + static { + LogSetup.initVespaLogging("Container"); + } + + /** + * Do not delete this method even if its empty. + * Calling this methods forces this class to be loaded, + * which runs the static block. + */ + @SuppressWarnings("UnusedDeclaration") + public static void ensureVespaLoggingInitialized() { + + } + + @Inject + public ConfiguredApplication(ContainerActivator activator, + OsgiFramework osgiFramework, + com.yahoo.jdisc.Timer timer, + SubscriberFactory subscriberFactory) throws ListenFailedException { + this.activator = activator; + this.osgiFramework = osgiFramework; + this.timerSingleton = timer; + this.subscriberFactory = subscriberFactory; + this.configId = System.getProperty("config.id"); + this.restrictedOsgiFramework = new DisableOsgiFramework(new RestrictedBundleContext(osgiFramework.bundleContext())); + Container.get().setOsgi(new OsgiImpl(osgiFramework)); + + applicationWithLegacySetup = new ContainerDiscApplication(configId); + } + + @Override + public void start() { + try { + ContainerDiscApplication.hackToInitializeServer(getConfig(QrConfig.class)); + + ContainerBuilder builder = createBuilderWithGuiceBindings(); + configureComponents(builder.guiceModules().activate()); + + intitializeAndActivateContainer(builder); + startReconfigurerThread(); + } catch (Exception e) { + com.yahoo.protect.Process.logAndDie("Failed starting container:", e); + } + } + + private <T extends ConfigInstance> T getConfig(Class<T> configClass) { + Subscriber subscriber = subscriberFactory.getSubscriber( + Collections.singleton(new ConfigKey<>(configClass, configId))); + try { + subscriber.waitNextGeneration(); + return configClass.cast(first(subscriber.config().values())); + } finally { + subscriber.close(); + } + } + + private void intitializeAndActivateContainer(ContainerBuilder builder) { + addHandlerBindings(builder, Container.get().getRequestHandlerRegistry(), + configurer.getComponent(ApplicationContext.class).discBindingsConfig); + installServerProviders(builder); + + activator.activateContainer(builder); // TODO: .notifyTermination(.. decompose previous component graph ..) + + startClients(); + startAndStopServers(); + + log.info("Switching to the latest deployed set of configurations and components. " + + "Application switch number: " + (applicationSerialNo++)); + } + + private ContainerBuilder createBuilderWithGuiceBindings() { + ContainerBuilder builder = activator.newContainerBuilder(); + setupGuiceBindings(builder.guiceModules()); + return builder; + } + + private void startReconfigurerThread() { + reconfigurerThread = new Thread(() -> { + while (!Thread.interrupted()) { + try { + ContainerBuilder builder = createBuilderWithGuiceBindings(); + configurer.runOnceAndEnsureRegistryHackRun(builder.guiceModules().activate()); + intitializeAndActivateContainer(builder); + } catch (ConfigInterruptedException | InterruptedException e) { + break; + } catch (Exception e) { + log.log(Level.SEVERE, + "Reconfiguration failed, your application package must be fixed, unless this is a " + + "JNI reload issue: " + Exceptions.toMessageString(e), e); + } + } + log.fine("Shutting down HandlersConfigurerDi"); + }); + reconfigurerThread.start(); + } + + private static void installServerProviders(ContainerBuilder builder) { + List<ServerProvider> serverProviders = Container.get().getServerProviderRegistry().allComponents(); + for (ServerProvider server : serverProviders) { + builder.serverProviders().install(server); + } + } + + private static void startClients() { + for (ClientProvider client : Container.get().getClientProviderRegistry().allComponents()) { + if (!startedClients.contains(client)) { + client.start(); + startedClients.add(client); + } + } + } + + private static void startAndStopServers() { + List<ServerProvider> currentServers = Container.get().getServerProviderRegistry().allComponents(); + HashSet<ServerProvider> serversToClose = new HashSet<>(startedServers); + serversToClose.removeAll(currentServers); + for (ServerProvider server : serversToClose) { + closeServer(server); + } + for (ServerProvider server : currentServers) { + if (!startedServers.contains(server)) { + server.start(); + startedServers.add(server); + } + } + } + + private static void closeServer(ServerProvider server) { + server.close(); + startedServers.remove(server); + } + + private void configureComponents(Injector discInjector) { + configurer = new HandlersConfigurerDi( + subscriberFactory, + Container.get(), + configId, + new Deconstructor(true), + discInjector, + osgiFramework); + } + + private void setupGuiceBindings(GuiceRepository modules) { + modules.install(new AbstractModule() { + + @Override + protected void configure() { + bind(Metric.class).to(DisableGuiceMetric.class); + bind(OsgiFramework.class).toInstance(restrictedOsgiFramework); + bind(com.yahoo.jdisc.Timer.class).toInstance(timerSingleton); + bind(FilterChainRepository.class).toInstance(defaultFilterChainRepository); + bind(ContainerPortsOverride.class).toInstance(defaultContainerPortsOverride); + } + }); + modules.install(applicationWithLegacySetup.getMbusBindings()); + } + + @Override + public void stop() { + startShutdownDeadlineExecutor(); + shutdownReconfigurerThread(); + + configurer.shutdown(new Deconstructor(false)); + + for (ServerProvider server : Container.get().getServerProviderRegistry().allComponents()) { + if (startedServers.contains(server)) { + closeServer(server); + } + } + Container.get().shutdown(); + } + + private void shutdownReconfigurerThread() { + reconfigurerThread.interrupt(); + try { + //Workaround for component constructors masking InterruptedException. + while (reconfigurerThread.isAlive()) { + reconfigurerThread.interrupt(); + long millis = 200; + reconfigurerThread.join(millis); + } + } catch (InterruptedException e) { + log.info("Interrupted while joining on HandlersConfigurer reconfigure thread."); + Thread.currentThread().interrupt(); + } + } + + @Override + public void destroy() { + if (shutdownDeadlineExecutor != null) { //stop() is not called when exception happens during start + shutdownDeadlineExecutor.shutdownNow(); + } + } + + // Workaround for ApplicationLoader.stop not being able to shutdown + private void startShutdownDeadlineExecutor() { + shutdownDeadlineExecutor = new ScheduledThreadPoolExecutor(1, new DaemonThreadFactory("Shutdown deadline timer")); + shutdownDeadlineExecutor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); + long delayMillis = 50 * 1000; + shutdownDeadlineExecutor.schedule(new Runnable() { + + @Override + public void run() { + com.yahoo.protect.Process.logAndDie( + "Timed out waiting for application shutdown. Please check that all your request handlers " + + "drain their request content channels."); + } + }, delayMillis, TimeUnit.MILLISECONDS); + } + + private static void addHandlerBindings(ContainerBuilder builder, + ComponentRegistry<RequestHandler> requestHandlerRegistry, + JdiscBindingsConfig discBindingsConfig) { + for (Map.Entry<String, JdiscBindingsConfig.Handlers> handlerEntry : discBindingsConfig.handlers().entrySet()) { + String id = handlerEntry.getKey(); + JdiscBindingsConfig.Handlers handlerConfig = handlerEntry.getValue(); + + RequestHandler handler = requestHandlerRegistry.getComponent(id); + if (handler == null) { + throw new RuntimeException("Binding configured for non-jdisc request handler " + id); + } + bindUri(builder.serverBindings(), handlerConfig.serverBindings(), handler); + bindUri(builder.clientBindings(), handlerConfig.clientBindings(), handler); + } + } + + private static void bindUri(BindingRepository<RequestHandler> bindings, List<String> uriPatterns, + RequestHandler target) { + for (String uri : uriPatterns) { + bindings.bind(uri, target); + } + } + + private static <T> Set<T> newWeakIdentitySet() { + Map<T, Boolean> weakIdentityHashMap = new MapMaker().weakKeys().makeMap(); + return Collections.newSetFromMap(weakIdentityHashMap); + } + +} diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/ContainerDiscApplication.java b/container-disc/src/main/java/com/yahoo/container/jdisc/ContainerDiscApplication.java new file mode 100644 index 00000000000..0311eedd726 --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/ContainerDiscApplication.java @@ -0,0 +1,51 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc; + + +import com.google.inject.AbstractModule; +import com.google.inject.Inject; +import com.yahoo.container.QrConfig; +import com.yahoo.container.Server; +import com.yahoo.container.jdisc.messagebus.SessionCache; +import com.yahoo.jrt.ListenFailedException; +import com.yahoo.log.LogLevel; + +import java.util.logging.Logger; + + +/** + * The application which sets up the jDisc container + * + * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a> + */ +public class ContainerDiscApplication { + + private static final Logger log = Logger.getLogger(ContainerDiscApplication.class.getName()); + + private SessionCache sessionCache; + + + @Inject + public ContainerDiscApplication(String configId) throws ListenFailedException { + sessionCache = new SessionCache(configId); + } + + AbstractModule getMbusBindings() { + return new AbstractModule() { + @Override + protected void configure() { + bind(SessionCache.class).toInstance(sessionCache); + } + }; + } + + public static void hackToInitializeServer(QrConfig config) { + try { + Server.get().initialize(config); + } catch (Exception e) { + log.log(LogLevel.ERROR, "Caught exception when initializing server. Exiting.", e); + Runtime.getRuntime().halt(1); + } + } + +} diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/ContainerPortsOverride.java b/container-disc/src/main/java/com/yahoo/container/jdisc/ContainerPortsOverride.java new file mode 100644 index 00000000000..421a268587e --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/ContainerPortsOverride.java @@ -0,0 +1,19 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc; + +import com.yahoo.component.AbstractComponent; +import com.yahoo.container.jdisc.config.PortOverridesConfig; + +/** + * Holds PortsOverrideConfig for the current container. + * HttpServerConfigProvider can't depend on PortsOverrideConfig directly, + * since it's config producer is not under the container in the model. + * @author tonytv + */ +public final class ContainerPortsOverride extends AbstractComponent { + public final PortOverridesConfig config; + + public ContainerPortsOverride(PortOverridesConfig portOverridesConfigConfig) { + this.config = portOverridesConfigConfig; + } +} diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/ContainerThreadFactory.java b/container-disc/src/main/java/com/yahoo/container/jdisc/ContainerThreadFactory.java new file mode 100644 index 00000000000..4b4403bc15b --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/ContainerThreadFactory.java @@ -0,0 +1,33 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc; + +import com.yahoo.container.jdisc.metric.MetricConsumerProvider; +import com.yahoo.jdisc.application.ContainerThread; +import com.yahoo.jdisc.application.MetricConsumer; + +import java.util.concurrent.ThreadFactory; + +/** + * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a> + */ +public class ContainerThreadFactory implements ThreadFactory { + + private final ThreadFactory delegate; + + public ContainerThreadFactory(MetricConsumerProvider metricConsumerProvider) { + metricConsumerProvider.getClass(); // throws NullPointerException + delegate = new ContainerThread.Factory(new com.google.inject.Provider<MetricConsumer>() { + + @Override + public MetricConsumer get() { + return metricConsumerProvider.newInstance(); + } + }); + } + + @Override + public Thread newThread(Runnable target) { + return delegate.newThread(target); + } + +} diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/DisableOsgiFramework.java b/container-disc/src/main/java/com/yahoo/container/jdisc/DisableOsgiFramework.java new file mode 100644 index 00000000000..6ed8e721e34 --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/DisableOsgiFramework.java @@ -0,0 +1,66 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc; + +import com.yahoo.jdisc.application.OsgiFramework; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleException; + +import java.util.List; + +/** + * @author tonytv + */ +public final class DisableOsgiFramework implements OsgiFramework { + private final RestrictedBundleContext restrictedBundleContext; + + public DisableOsgiFramework() { + this.restrictedBundleContext = null; + } + + public DisableOsgiFramework(RestrictedBundleContext restrictedBundleContext) { + this.restrictedBundleContext = restrictedBundleContext; + } + + @Override + public List<Bundle> installBundle(String bundleLocation) throws BundleException { + throw newException(); + } + + @Override + public void startBundles(List<Bundle> bundles, boolean privileged) throws BundleException { + throw newException(); + } + + @Override + public void refreshPackages() { + throw newException(); + } + + @Override + public BundleContext bundleContext() { + if (restrictedBundleContext == null) { + throw newException(); + } + return restrictedBundleContext; + } + + @Override + public List<Bundle> bundles() { + throw newException(); + } + + @Override + public void start() throws BundleException { + throw newException(); + } + + @Override + public void stop() throws BundleException { + throw newException(); + } + + private RuntimeException newException() { + return new UnsupportedOperationException("The OSGi framework is not available to components."); + } +} diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/FilterBindingsProvider.java b/container-disc/src/main/java/com/yahoo/container/jdisc/FilterBindingsProvider.java new file mode 100644 index 00000000000..838593ab28a --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/FilterBindingsProvider.java @@ -0,0 +1,61 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc; + +import com.yahoo.component.ComponentId; +import com.yahoo.component.provider.ComponentRegistry; +import com.yahoo.container.di.componentgraph.Provider; +import com.yahoo.container.http.filter.FilterChainRepository; +import com.yahoo.jdisc.application.BindingRepository; +import com.yahoo.jdisc.http.ServerConfig; +import com.yahoo.jdisc.http.filter.RequestFilter; +import com.yahoo.jdisc.http.filter.ResponseFilter; +import com.yahoo.jdisc.http.filter.SecurityRequestFilter; +import com.yahoo.jdisc.http.server.FilterBindings; + +import java.util.ArrayList; +import java.util.List; + +/** + * Provides filter bindings based on vespa config. + * + * @author <a href="mailto:bakksjo@yahoo-inc.com">Oyvind Bakksjo</a> + */ +public class FilterBindingsProvider implements Provider<FilterBindings> { + final BindingRepository<RequestFilter> requestFilters = new BindingRepository<>(); + final BindingRepository<ResponseFilter> responseFilters = new BindingRepository<>(); + + public FilterBindingsProvider( + final ComponentId componentId, + final ServerConfig config, + final FilterChainRepository filterChainRepository, + final ComponentRegistry<SecurityRequestFilter> legacyRequestFilters) { + final ComponentId serverId = componentId.getNamespace(); + try { + FilterUtil.setupFilters( + componentId, + legacyRequestFilters, + toFilterSpecs(config.filter()), + filterChainRepository, + requestFilters, + responseFilters); + } catch (Exception e) { + throw new RuntimeException("Invalid config for http server " + serverId, e); + } + } + + private List<FilterUtil.FilterSpec> toFilterSpecs(List<ServerConfig.Filter> inFilters) { + List<FilterUtil.FilterSpec> outFilters = new ArrayList<>(); + for (ServerConfig.Filter inFilter : inFilters) { + outFilters.add(new FilterUtil.FilterSpec(inFilter.id(), inFilter.binding())); + } + return outFilters; + } + + @Override + public FilterBindings get() { + return new FilterBindings(requestFilters, responseFilters); + } + + @Override + public void deconstruct() {} +} diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/FilterUtil.java b/container-disc/src/main/java/com/yahoo/container/jdisc/FilterUtil.java new file mode 100644 index 00000000000..7254d438ac0 --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/FilterUtil.java @@ -0,0 +1,109 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc; + +import com.yahoo.component.ComponentId; +import com.yahoo.component.ComponentSpecification; +import com.yahoo.component.provider.ComponentRegistry; +import com.yahoo.container.http.filter.FilterChainRepository; +import com.yahoo.jdisc.application.BindingRepository; +import com.yahoo.jdisc.application.UriPattern; +import com.yahoo.jdisc.http.filter.RequestFilter; +import com.yahoo.jdisc.http.filter.ResponseFilter; +import com.yahoo.jdisc.http.filter.SecurityRequestFilter; +import com.yahoo.jdisc.http.filter.SecurityRequestFilterChain; + +import java.util.List; + +/** + * Helper class to set up filter binding repositories based on config. + * + * @author <a href="mailto:bakksjo@yahoo-inc.com">Oyvind Bakksjo</a> + */ +class FilterUtil { + private static final ComponentId SEARCH_SERVER_COMPONENT_ID = ComponentId.fromString("SearchServer"); + + private final BindingRepository<RequestFilter> requestFilters; + private final BindingRepository<ResponseFilter> responseFilters; + + private FilterUtil( + final BindingRepository<RequestFilter> requestFilters, + final BindingRepository<ResponseFilter> responseFilters) { + this.requestFilters = requestFilters; + this.responseFilters = responseFilters; + } + + private void configureFilters( + final List<FilterSpec> filtersConfig, + final FilterChainRepository filterChainRepository) { + for (final FilterSpec filterConfig : filtersConfig) { + final Object filter = filterChainRepository.getFilter(ComponentSpecification.fromString(filterConfig.getId())); + if (filter == null) { + throw new RuntimeException("No http filter with id " + filterConfig.getId()); + } + addFilter(filter, filterConfig.getBinding()); + } + } + + private void addFilter(final Object filter, final String binding) { + if (filter instanceof RequestFilter && filter instanceof ResponseFilter) { + throw new RuntimeException("The filter " + filter.getClass().getName() + " is unsupported since it's both a RequestFilter and a ResponseFilter."); + } else if (filter instanceof RequestFilter) { + requestFilters.put(new UriPattern(binding), (RequestFilter) filter); + } else if (filter instanceof ResponseFilter) { + responseFilters.put(new UriPattern(binding), (ResponseFilter) filter); + } else { + throw new RuntimeException("Unknown filter type " + filter.getClass().getName()); + } + } + + //TVT: remove + private void configureLegacyFilters( + final ComponentId id, + final ComponentRegistry<SecurityRequestFilter> legacyRequestFilters) { + final ComponentId serverName = id.getNamespace(); + if (SEARCH_SERVER_COMPONENT_ID.equals(serverName) && !legacyRequestFilters.allComponents().isEmpty()) { + requestFilters.bind( + "http://*/*", + SecurityRequestFilterChain.newInstance(legacyRequestFilters.allComponents())); + } + } + + /** + * Populates binding repositories with filters based on config. + * + * @param requestFilters output argument that will be mutated + * @param responseFilters output argument that will be mutated + */ + public static void setupFilters( + final ComponentId componentId, + final ComponentRegistry<SecurityRequestFilter> legacyRequestFilters, + final List<FilterSpec> filtersConfig, + final FilterChainRepository filterChainRepository, + final BindingRepository<RequestFilter> requestFilters, + final BindingRepository<ResponseFilter> responseFilters) { + final FilterUtil filterUtil = new FilterUtil(requestFilters, responseFilters); + + // TODO: remove + filterUtil.configureLegacyFilters(componentId, legacyRequestFilters); + + filterUtil.configureFilters(filtersConfig, filterChainRepository); + } + + public static class FilterSpec { + private final String id; + private final String binding; + + public FilterSpec(String id, String binding) { + this.id = id; + this.binding = binding; + } + + public String getId() { + return id; + } + + public String getBinding() { + return binding; + } + } +} diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/Main.java b/container-disc/src/main/java/com/yahoo/container/jdisc/Main.java new file mode 100644 index 00000000000..d4322e4980b --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/Main.java @@ -0,0 +1,56 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc; + +import com.google.inject.AbstractModule; +import com.google.inject.name.Names; +import com.yahoo.jdisc.test.TestDriver; +import org.apache.commons.daemon.Daemon; +import org.apache.commons.daemon.DaemonContext; + +/** + * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a> + */ +public class Main implements Daemon { + + public static void main(String[] args) { + startContainer(System.getProperty("config.id")); + } + + public static TestDriver startContainer(final String configId) { + System.setProperty("config.id", configId); + + @SuppressWarnings("unused") + TestDriver driver = TestDriver.newInjectedApplicationInstance(ConfiguredApplication.class, + new AbstractModule() { + @Override + protected void configure() { + bind(String.class).annotatedWith(Names.named("configId")).toInstance(configId); + + } + }); + + return driver; + } + + @Override + public void init(DaemonContext context) throws Exception { + //nada + } + + @Override + public void start() throws Exception { + main(new String[0]); + } + + @Override + public void stop() throws Exception { + //TODO: Implement this method: + + } + + @Override + public void destroy() { + //nada + } + +} diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/RestrictedBundleContext.java b/container-disc/src/main/java/com/yahoo/container/jdisc/RestrictedBundleContext.java new file mode 100644 index 00000000000..8fad9a48376 --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/RestrictedBundleContext.java @@ -0,0 +1,190 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc; + +import org.osgi.framework.*; + +import java.io.File; +import java.io.InputStream; +import java.util.Collection; +import java.util.Collections; +import java.util.Dictionary; + +/** + * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a> + * @since 5.22.0 + */ +public class RestrictedBundleContext implements BundleContext { + private final BundleContext wrapped; + + public RestrictedBundleContext(BundleContext wrapped) { + this.wrapped = wrapped; + } + + @Override + public ServiceRegistration<?> registerService(String[] strings, Object o, Dictionary<String, ?> stringDictionary) { + if (wrapped == null) { + return null; + } + return wrapped.registerService(strings, o, stringDictionary); + } + + @Override + public ServiceRegistration<?> registerService(String localHostname, Object o, Dictionary<String, ?> stringDictionary) { + if (wrapped == null) { + return null; + } + return wrapped.registerService(localHostname, o, stringDictionary); + } + + @Override + public <S> ServiceRegistration<S> registerService(Class<S> sClass, S s, Dictionary<String, ?> stringDictionary) { + if (wrapped == null) { + return null; + } + return wrapped.registerService(sClass, s, stringDictionary); + } + + @Override + public ServiceReference<?>[] getServiceReferences(String localHostname, String localHostname2) throws InvalidSyntaxException { + if (wrapped == null) { + return new ServiceReference<?>[0]; + } + return wrapped.getServiceReferences(localHostname, localHostname2); + } + + @Override + public ServiceReference<?>[] getAllServiceReferences(String localHostname, String localHostname2) throws InvalidSyntaxException { + if (wrapped == null) { + return new ServiceReference<?>[0]; + } + return wrapped.getAllServiceReferences(localHostname, localHostname2); + } + + @Override + public ServiceReference<?> getServiceReference(String localHostname) { + if (wrapped == null) { + return null; + } + return wrapped.getServiceReference(localHostname); + } + + @Override + public <S> ServiceReference<S> getServiceReference(Class<S> sClass) { + if (wrapped == null) { + return null; + } + return wrapped.getServiceReference(sClass); + } + + @Override + public <S> Collection<ServiceReference<S>> getServiceReferences(Class<S> sClass, String localHostname) throws InvalidSyntaxException { + if (wrapped == null) { + return Collections.<ServiceReference<S>>emptyList(); + } + return wrapped.getServiceReferences(sClass, localHostname); + } + + @Override + public <S> S getService(ServiceReference<S> sServiceReference) { + if (wrapped == null) { + return null; + } + return wrapped.getService(sServiceReference); + } + + @Override + public boolean ungetService(ServiceReference<?> serviceReference) { + if (wrapped == null) { + return false; + } + return wrapped.ungetService(serviceReference); + } + + + //--------------------- + + + @Override + public String getProperty(String localHostname) { + throw newException(); + } + + @Override + public Bundle getBundle() { + throw newException(); + } + + @Override + public Bundle installBundle(String localHostname, InputStream inputStream) throws BundleException { + throw newException(); + } + + @Override + public Bundle installBundle(String localHostname) throws BundleException { + throw newException(); + } + + @Override + public Bundle getBundle(long l) { + throw newException(); + } + + @Override + public Bundle[] getBundles() { + throw newException(); + } + + @Override + public void addServiceListener(ServiceListener serviceListener, String localHostname) throws InvalidSyntaxException { + throw newException(); + } + + @Override + public void addServiceListener(ServiceListener serviceListener) { + throw newException(); + } + + @Override + public void removeServiceListener(ServiceListener serviceListener) { + throw newException(); + } + + @Override + public void addBundleListener(BundleListener bundleListener) { + throw newException(); + } + + @Override + public void removeBundleListener(BundleListener bundleListener) { + throw newException(); + } + + @Override + public void addFrameworkListener(FrameworkListener frameworkListener) { + throw newException(); + } + + @Override + public void removeFrameworkListener(FrameworkListener frameworkListener) { + throw newException(); + } + + @Override + public File getDataFile(String localHostname) { + throw newException(); + } + + @Override + public Filter createFilter(String localHostname) throws InvalidSyntaxException { + throw newException(); + } + + @Override + public Bundle getBundle(String localHostname) { + throw newException(); + } + + private RuntimeException newException() { + return new UnsupportedOperationException("This BundleContext operation is not available to components."); + } +} diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/SecretStoreProvider.java b/container-disc/src/main/java/com/yahoo/container/jdisc/SecretStoreProvider.java new file mode 100644 index 00000000000..e229e049bfe --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/SecretStoreProvider.java @@ -0,0 +1,34 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc; + +import com.yahoo.container.di.componentgraph.Provider; +import com.yahoo.jdisc.http.SecretStore; + +/** + * An secret store provider which provides a factory which throws exception on + * invocation - as no secret store is currently provided by default. + * The purpose of this is to provide a secret store for injection in the case where + * no secret store component is provided. + * + * @author bratseth + */ +public class SecretStoreProvider implements Provider<SecretStore> { + + private static final ThrowingSecretStore instance = new ThrowingSecretStore(); + + @Override + public SecretStore get() { return instance; } + + @Override + public void deconstruct() { } + + private static final class ThrowingSecretStore implements SecretStore { + + @Override + public String getSecret(String key) { + throw new UnsupportedOperationException("A secret store is not available"); + } + + } + +} diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/SslKeyStoreFactoryProvider.java b/container-disc/src/main/java/com/yahoo/container/jdisc/SslKeyStoreFactoryProvider.java new file mode 100644 index 00000000000..a92f95e4416 --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/SslKeyStoreFactoryProvider.java @@ -0,0 +1,41 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc; + +import com.yahoo.container.di.componentgraph.Provider; +import com.yahoo.jdisc.http.ssl.ReaderForPath; +import com.yahoo.jdisc.http.ssl.SslKeyStore; +import com.yahoo.jdisc.http.ssl.SslKeyStoreFactory; + +/** + * An SSL key store provider which provides a factory which throws exception on + * invocation - as no SSL key store is currently provided by default. + * The purpose of this is to provide a ssl store factory for injection in the case where + * no secret store component is provided. + * + * @author bratseth + */ +public class SslKeyStoreFactoryProvider implements Provider<SslKeyStoreFactory> { + + private static final ThrowingSslKeyStoreFactory instance = new ThrowingSslKeyStoreFactory(); + + @Override + public SslKeyStoreFactory get() { return instance; } + + @Override + public void deconstruct() { } + + private static final class ThrowingSslKeyStoreFactory implements SslKeyStoreFactory { + + @Override + public SslKeyStore createKeyStore(ReaderForPath certificateFile, ReaderForPath keyFile) { + throw new UnsupportedOperationException("A SSL key store factory component is not available"); + } + + @Override + public SslKeyStore createTrustStore(ReaderForPath certificateFile) { + throw new UnsupportedOperationException("A SSL key store factory component is not available"); + } + + } + +} diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/component/Deconstructor.java b/container-disc/src/main/java/com/yahoo/container/jdisc/component/Deconstructor.java new file mode 100644 index 00000000000..c5796ffd290 --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/component/Deconstructor.java @@ -0,0 +1,56 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc.component; + +import com.yahoo.component.AbstractComponent; +import com.yahoo.concurrent.DaemonThreadFactory; +import com.yahoo.concurrent.ThreadFactoryFactory; +import com.yahoo.container.di.ComponentDeconstructor; +import com.yahoo.container.di.componentgraph.Provider; +import com.yahoo.jdisc.SharedResource; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** +* @author tonyv +* @author gv +*/ +public class Deconstructor implements ComponentDeconstructor { + private final ScheduledExecutorService executor = + Executors.newScheduledThreadPool(1, ThreadFactoryFactory.getDaemonThreadFactory("deconstructor")); + + private final int delay; + + public Deconstructor(boolean delayDeconstruction) { + delay = delayDeconstruction ? 60 : 0; + } + + + @Override + public void deconstruct(Object component) { + if (component instanceof AbstractComponent) { + AbstractComponent abstractComponent = (AbstractComponent) component; + if (abstractComponent.isDeconstructable()) + executor.schedule( + new DestructComponentTask(abstractComponent), delay, TimeUnit.SECONDS); + } else if (component instanceof Provider) { + ((Provider)component).deconstruct(); + } else if (component instanceof SharedResource) { + // No need to delay release, as jdisc does ref-counting + ((SharedResource)component).release(); + } + } + + private static class DestructComponentTask implements Runnable { + private final AbstractComponent component; + + DestructComponentTask(AbstractComponent component) { + this.component = component; + } + + public void run() { + component.deconstruct(); + } + } +} diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/config/package-info.java b/container-disc/src/main/java/com/yahoo/container/jdisc/config/package-info.java new file mode 100644 index 00000000000..004a9030a4b --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/config/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.jdisc.config; + +import com.yahoo.osgi.annotation.ExportPackage; diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/metric/DisableGuiceMetric.java b/container-disc/src/main/java/com/yahoo/container/jdisc/metric/DisableGuiceMetric.java new file mode 100644 index 00000000000..09cad401bc1 --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/metric/DisableGuiceMetric.java @@ -0,0 +1,31 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc.metric; + +import com.yahoo.jdisc.Metric; + +import java.util.Map; + +/** + * @author tonytv + */ +public class DisableGuiceMetric implements Metric { + + @Override + public void set(String s, Number number, Context context) { + throw newException(); + } + + @Override + public void add(String s, Number number, Context context) { + throw newException(); + } + + @Override + public Context createContext(Map<String, ?> stringMap) { + throw newException(); + } + + private static RuntimeException newException() { + return new UnsupportedOperationException("The Metric framework is only available to components."); + } +} diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/metric/ForwardingMetricConsumer.java b/container-disc/src/main/java/com/yahoo/container/jdisc/metric/ForwardingMetricConsumer.java new file mode 100644 index 00000000000..87f2e27a471 --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/metric/ForwardingMetricConsumer.java @@ -0,0 +1,58 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc.metric; + +import com.yahoo.container.jdisc.MetricConsumerFactory; +import com.yahoo.jdisc.Metric; +import com.yahoo.jdisc.application.MetricConsumer; + +import java.util.Map; + +/** + * <p>If more than one {@link MetricConsumerFactory} is registered in a container, calls to <tt>Metric</tt> need to be + * forwarded to all the underlying <tt>MetricConsumers</tt>. That is the responsibility of this class. Instances of this + * class is created by the {@link MetricConsumerProvider} in those cases.</p> + * + * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a> + */ +public final class ForwardingMetricConsumer implements MetricConsumer { + + private final MetricConsumer[] consumers; + + public ForwardingMetricConsumer(MetricConsumer[] consumers) { + this.consumers = consumers; + } + + @Override + public void set(String key, Number val, Metric.Context ctx) { + ForwardingContext fwd = (ForwardingContext)ctx; + for (int i = 0; i < consumers.length; ++i) { + consumers[i].set(key, val, fwd != null ? fwd.contexts[i] : null); + } + } + + @Override + public void add(String key, Number val, Metric.Context ctx) { + ForwardingContext fwd = (ForwardingContext)ctx; + for (int i = 0; i < consumers.length; ++i) { + consumers[i].add(key, val, fwd != null ? fwd.contexts[i] : null); + } + } + + @Override + public Metric.Context createContext(Map<String, ?> properties) { + Metric.Context[] contexts = new Metric.Context[consumers.length]; + for (int i = 0; i < consumers.length; ++i) { + contexts[i] = consumers[i].createContext(properties); + } + return new ForwardingContext(contexts); + } + + private static class ForwardingContext implements Metric.Context { + + final Metric.Context[] contexts; + + ForwardingContext(Metric.Context[] contexts) { + this.contexts = contexts; + } + } +} diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/metric/MetricConsumerProvider.java b/container-disc/src/main/java/com/yahoo/container/jdisc/metric/MetricConsumerProvider.java new file mode 100644 index 00000000000..9cb2a768528 --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/metric/MetricConsumerProvider.java @@ -0,0 +1,55 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc.metric; + +import com.google.inject.Inject; +import com.yahoo.component.provider.ComponentRegistry; +import com.yahoo.container.jdisc.MetricConsumerFactory; +import com.yahoo.container.jdisc.metric.state.StateMetricConsumerFactory; +import com.yahoo.container.jdisc.state.StateMonitor; +import com.yahoo.jdisc.application.MetricConsumer; +import com.yahoo.metrics.MetricsPresentationConfig; + + +/** + * <p>The purpose of this class it to be the only provider for the <tt>MetricConsumer</tt> interface in a component + * graph. This component is automatically registered in the graph by the config server. Configuring a different + * <tt>MetricConsumer</tt> is done by registering one or more {@link MetricConsumerFactory} in the services-file.</p> + * + * <p>Because this class depends on the {@link ComponentRegistry} of {@link MetricConsumerFactory}, any added or removed + * {@link MetricConsumerFactory} will cause this component to be reconfigured. Because {@link MetricProvider} depends on + * this class, which means any component that uses <tt>Metric</tt> will be reconfigured. Any component that depends + * directly on <tt>MetricConsumer</tt> will also be reconfigured.</p> + * + * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a> + */ +public class MetricConsumerProvider { + + private final MetricConsumerFactory[] factories; + + @Inject + public MetricConsumerProvider(ComponentRegistry<MetricConsumerFactory> factoryRegistry, + MetricsPresentationConfig presentationConfig, + StateMonitor stateMonitor) { + MetricConsumerFactory[] factories; + if (factoryRegistry.getComponentCount() == 0 || ! presentationConfig.slidingwindow()) { + factories = new MetricConsumerFactory[1]; + factories[0] = new StateMetricConsumerFactory(stateMonitor); + } else { + factories = new MetricConsumerFactory[factoryRegistry.getComponentCount()]; + factoryRegistry.allComponents().toArray(factories); + } + this.factories = factories; + } + + public MetricConsumer newInstance() { + if (factories.length == 1) { + return factories[0].newInstance(); + } + MetricConsumer[] consumers = new MetricConsumer[factories.length]; + for (int i = 0; i < factories.length; ++i) { + consumers[i] = factories[i].newInstance(); + } + return new ForwardingMetricConsumer(consumers); + } + +} diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/metric/MetricConsumerProviderProvider.java b/container-disc/src/main/java/com/yahoo/container/jdisc/metric/MetricConsumerProviderProvider.java new file mode 100644 index 00000000000..930773e8546 --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/metric/MetricConsumerProviderProvider.java @@ -0,0 +1,36 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc.metric; + +import com.google.inject.Inject; +import com.yahoo.component.provider.ComponentRegistry; +import com.yahoo.container.di.componentgraph.Provider; +import com.yahoo.container.jdisc.MetricConsumerFactory; +import com.yahoo.container.jdisc.state.StateMonitor; +import com.yahoo.metrics.MetricsPresentationConfig; + +/** + * A dependency injection-provider which providers the default metrics provider + * if no provider is set up explicitly in the application package. + * The purpose of this is to be a fallback if nothing is set up explicitly. + * + * @author bratseth + */ +public class MetricConsumerProviderProvider implements Provider<MetricConsumerProvider> { + + private final MetricConsumerProvider provided; + + @Inject + public MetricConsumerProviderProvider(ComponentRegistry<MetricConsumerFactory> factoryRegistry, + MetricsPresentationConfig presentationConfig, + StateMonitor stateMonitor) { + provided = new MetricConsumerProvider(factoryRegistry, presentationConfig, stateMonitor); + } + + @Override + public MetricConsumerProvider get() { return provided; } + + @Override + public void deconstruct() { + } + +} diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/metric/MetricProvider.java b/container-disc/src/main/java/com/yahoo/container/jdisc/metric/MetricProvider.java new file mode 100644 index 00000000000..7fdf9821bb1 --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/metric/MetricProvider.java @@ -0,0 +1,39 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc.metric; + +import com.yahoo.container.di.componentgraph.Provider; +import com.yahoo.jdisc.Metric; +import com.yahoo.jdisc.application.MetricConsumer; + +/** + * <p>This class implements a {@link Provider} component of <tt>Metric</tt>. Because this class depends on {@link + * MetricConsumerProvider}, any change to the consumer configuration will trigger reconfiguration of this component, + * which in turn triggers reconfiguration of any component that depends on <tt>Metric</tt>.</p> + * + * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a> + */ +public final class MetricProvider implements Provider<Metric> { + + private final Metric metric; + + public MetricProvider(MetricConsumerProvider provider) { + metric = new com.yahoo.jdisc.application.MetricProvider(new com.google.inject.Provider<MetricConsumer>() { + + @Override + public MetricConsumer get() { + return provider.newInstance(); + } + }).get(); + } + + @Override + public Metric get() { + return metric; + } + + @Override + public void deconstruct() { + + } + +} diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/metric/MetricUpdater.java b/container-disc/src/main/java/com/yahoo/container/jdisc/metric/MetricUpdater.java new file mode 100644 index 00000000000..ab6e0b04085 --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/metric/MetricUpdater.java @@ -0,0 +1,66 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc.metric; + +import java.util.Timer; +import java.util.TimerTask; + +import com.google.inject.Inject; +import com.yahoo.component.AbstractComponent; +import com.yahoo.jdisc.Metric; + +/** + * Uses a timer to emit metrics + * + * @author vegardh + * @since 5.17 + * + */ +public class MetricUpdater extends AbstractComponent { + + private static final String FREE_MEMORY_BYTES = "freeMemoryBytes"; + private static final String USED_MEMORY_BYTES = "usedMemoryBytes"; + private static final String TOTAL_MEMORY_BYTES = "totalMemoryBytes"; + private static final String MANHATTAN_FREE_MEMORY_BYTES = "mem.heap.free"; + private static final String MANHATTAN_USED_MEMORY_BYTES = "mem.heap.used"; + private static final String MANHATTAN_TOTAL_MEMORY_BYTES = "mem.heap.total"; + + private final Metric metric; + private final Timer timer = new Timer(); + long freeMemory = -1; + long totalMemory = -1; + + @Inject + public MetricUpdater(Metric metric) { + this(metric, 10*1000); + } + + public MetricUpdater(Metric metric, long delayMillis) { + this.metric = metric; + timer.schedule(new UpdaterTask(), delayMillis, delayMillis); + } + + @Override + public void deconstruct() { + if (timer!=null) timer.cancel(); + } + + // For testing + long getFreeMemory() { return freeMemory; } + long getTotalMemory() { return totalMemory; } + + private class UpdaterTask extends TimerTask { + @Override + public void run() { + freeMemory = Runtime.getRuntime().freeMemory(); + totalMemory = Runtime.getRuntime().totalMemory(); + long usedMemory = totalMemory - freeMemory; + metric.set(FREE_MEMORY_BYTES, freeMemory, null); + metric.set(USED_MEMORY_BYTES, usedMemory, null); + metric.set(TOTAL_MEMORY_BYTES, totalMemory, null); + metric.set(MANHATTAN_FREE_MEMORY_BYTES, freeMemory, null); + metric.set(MANHATTAN_USED_MEMORY_BYTES, usedMemory, null); + metric.set(MANHATTAN_TOTAL_MEMORY_BYTES, totalMemory, null); + } + } +} + diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/metric/package-info.java b/container-disc/src/main/java/com/yahoo/container/jdisc/metric/package-info.java new file mode 100644 index 00000000000..512f283058b --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/metric/package-info.java @@ -0,0 +1,4 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +@com.yahoo.osgi.annotation.ExportPackage +package com.yahoo.container.jdisc.metric; + diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/metric/state/StateMetricConsumerFactory.java b/container-disc/src/main/java/com/yahoo/container/jdisc/metric/state/StateMetricConsumerFactory.java new file mode 100644 index 00000000000..ae4c7130c76 --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/metric/state/StateMetricConsumerFactory.java @@ -0,0 +1,23 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc.metric.state; + +import com.yahoo.container.jdisc.MetricConsumerFactory; +import com.yahoo.container.jdisc.state.StateMonitor; +import com.yahoo.jdisc.application.MetricConsumer; + +/** + * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a> + */ +public class StateMetricConsumerFactory implements MetricConsumerFactory { + + private final StateMonitor stateMonitor; + + public StateMetricConsumerFactory(final StateMonitor stateMonitor) { + this.stateMonitor = stateMonitor; + } + + @Override + public MetricConsumer newInstance() { + return stateMonitor.newMetricConsumer(); + } +} diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/metric/state/package-info.java b/container-disc/src/main/java/com/yahoo/container/jdisc/metric/state/package-info.java new file mode 100644 index 00000000000..2731740abd5 --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/metric/state/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.jdisc.metric.state; + +import com.yahoo.osgi.annotation.ExportPackage;
\ No newline at end of file diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/osgi/ContainerBundleActivator.java b/container-disc/src/main/java/com/yahoo/container/jdisc/osgi/ContainerBundleActivator.java new file mode 100644 index 00000000000..17e64426b88 --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/osgi/ContainerBundleActivator.java @@ -0,0 +1,31 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc.osgi; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; +import org.osgi.framework.hooks.resolver.ResolverHookFactory; + +import java.util.Hashtable; + +/** + * @author tonytv + */ +public class ContainerBundleActivator implements BundleActivator { + + private ServiceRegistration<ResolverHookFactory> resolverHookFactoryServiceRegistration; + + @Override + public void start(BundleContext bundleContext) throws Exception { + resolverHookFactoryServiceRegistration = bundleContext.registerService( + ResolverHookFactory.class, + new JacksonJaxrsResolverHook.Factory(), + new Hashtable<>()); + } + + @Override + public void stop(BundleContext bundleContext) throws Exception { + resolverHookFactoryServiceRegistration.unregister(); + } + +} diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/osgi/JacksonJaxrsResolverHook.java b/container-disc/src/main/java/com/yahoo/container/jdisc/osgi/JacksonJaxrsResolverHook.java new file mode 100644 index 00000000000..8096a9f399f --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/osgi/JacksonJaxrsResolverHook.java @@ -0,0 +1,74 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc.osgi; + +import com.yahoo.log.LogLevel; +import org.osgi.framework.Bundle; +import org.osgi.framework.hooks.resolver.ResolverHook; +import org.osgi.framework.hooks.resolver.ResolverHookFactory; +import org.osgi.framework.wiring.BundleCapability; +import org.osgi.framework.wiring.BundleRequirement; +import org.osgi.framework.wiring.BundleRevision; + +import java.util.Collection; +import java.util.Iterator; +import java.util.function.Predicate; +import java.util.logging.Logger; + +/** + * Makes jackson1 jaxrs integration use jersey1 and jackson2 jaxrs integration use jersey2 + * @author tonytv + */ +public class JacksonJaxrsResolverHook implements ResolverHook { + private static Logger log = Logger.getLogger(JacksonJaxrsResolverHook.class.getName()); + + public static class Factory implements ResolverHookFactory { + @Override + public ResolverHook begin(Collection<BundleRevision> bundleRevisions) { + return new JacksonJaxrsResolverHook(); + } + } + + @Override + public void filterResolvable(Collection<BundleRevision> bundleRevisions) {} + + @Override + public void filterSingletonCollisions(BundleCapability bundleCapability, Collection<BundleCapability> bundleCapabilities) {} + + @Override + public void filterMatches(BundleRequirement bundleRequirement, Collection<BundleCapability> bundleCapabilities) { + Bundle bundle = bundleRequirement.getRevision().getBundle(); + String symbolicName = bundle.getSymbolicName(); + + log.log(LogLevel.DEBUG, "Filtering matches for " + symbolicName); + + if (symbolicName.startsWith("com.fasterxml.jackson.jaxrs")) + removeBundlesMatching(bundleCapabilities, JacksonJaxrsResolverHook::isJaxRs1Bundle); + else if (symbolicName.equals("jackson-jaxrs") && bundle.getVersion().getMajor() == 1) { + removeBundlesMatching(bundleCapabilities, JacksonJaxrsResolverHook::isJaxRs2Bundle); + } + } + + private static boolean isJaxRs1Bundle(String bundleSymbolicName) { + return bundleSymbolicName.startsWith("com.sun.jersey."); + } + + private static boolean isJaxRs2Bundle(String bundleSymbolicName) { + return bundleSymbolicName.startsWith("org.glassfish.jersey.") || + bundleSymbolicName.equals("javax.ws.rs-api"); + } + + private void removeBundlesMatching(Collection<BundleCapability> bundleCapabilities, Predicate<String> symbolicNamePredicate) { + for (Iterator<BundleCapability> i = bundleCapabilities.iterator(); i.hasNext(); ) { + BundleCapability bundleCapability = i.next(); + String symbolicName = bundleCapability.getRevision().getSymbolicName(); + + if (symbolicNamePredicate.test(symbolicName)) { + log.log(LogLevel.DEBUG, "- Removing bundle " + symbolicName); + i.remove(); + } + } + } + + @Override + public void end() {} +} diff --git a/container-disc/src/main/java/com/yahoo/container/usability/BindingsOverviewHandler.java b/container-disc/src/main/java/com/yahoo/container/usability/BindingsOverviewHandler.java new file mode 100644 index 00000000000..8f4435a511a --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/usability/BindingsOverviewHandler.java @@ -0,0 +1,197 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.usability; + +import com.google.inject.Inject; +import com.yahoo.component.ComponentId; +import com.yahoo.component.provider.ComponentRegistry; +import com.yahoo.container.Container; +import com.yahoo.container.jdisc.JdiscBindingsConfig; +import com.yahoo.jdisc.handler.AbstractRequestHandler; +import com.yahoo.jdisc.handler.CompletionHandler; +import com.yahoo.jdisc.handler.ContentChannel; +import com.yahoo.jdisc.handler.FastContentWriter; +import com.yahoo.jdisc.handler.RequestHandler; +import com.yahoo.jdisc.handler.ResponseDispatch; +import com.yahoo.jdisc.handler.ResponseHandler; +import com.yahoo.jdisc.http.HttpRequest; +import com.yahoo.jdisc.http.HttpRequest.Method; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * @author gjoranv + * @since 5.18 + */ +public class BindingsOverviewHandler extends AbstractRequestHandler { + + private final JdiscBindingsConfig bindingsConfig; + + @Inject + public BindingsOverviewHandler(JdiscBindingsConfig bindingsConfig) { + this.bindingsConfig = bindingsConfig; + } + + @Override + public ContentChannel handleRequest(com.yahoo.jdisc.Request request, ResponseHandler handler) { + JSONObject json; + int statusToReturn; + + if (request instanceof HttpRequest && ((HttpRequest) request).getMethod() != Method.GET) { + json = errorMessageInJson(); + statusToReturn = com.yahoo.jdisc.Response.Status.METHOD_NOT_ALLOWED; + } else { + json = new StatusResponse(bindingsConfig).render(); + statusToReturn = com.yahoo.jdisc.Response.Status.OK; + } + + FastContentWriter writer = new FastContentWriter(new ResponseDispatch() { + @Override + protected com.yahoo.jdisc.Response newResponse() { + com.yahoo.jdisc.Response response = new com.yahoo.jdisc.Response(statusToReturn); + response.headers().add("Content-Type", Arrays.asList(new String[]{"application/json"})); + return response; + } + }.connect(handler)); + + try { + writer.write(json.toString()); + } finally { + writer.close(); + } + + return new IgnoredContent(); + } + + private JSONObject errorMessageInJson() { + JSONObject error = new JSONObject(); + try { + error.put("error", "This API, " + + this.getClass().getSimpleName() + + ", only supports HTTP GET." + + " You are probably looking for another API/path."); + } catch (org.json.JSONException e) { + // just ignore it + } + return error; + } + + static JSONArray renderRequestHandlers(JdiscBindingsConfig bindingsConfig, + Map<ComponentId, ? extends RequestHandler> handlersById) { + JSONArray ret = new JSONArray(); + + for (Map.Entry<ComponentId, ? extends RequestHandler> handlerEntry : handlersById.entrySet()) { + String id = handlerEntry.getKey().stringValue(); + RequestHandler handler = handlerEntry.getValue(); + + JSONObject handlerJson = renderComponent(handler, handlerEntry.getKey()); + addBindings(bindingsConfig, id, handlerJson); + ret.put(handlerJson); + } + return ret; + } + + private static void addBindings(JdiscBindingsConfig bindingsConfig, String id, JSONObject handlerJson) { + List<String> serverBindings = new ArrayList<>(); + + JdiscBindingsConfig.Handlers handlerConfig = bindingsConfig.handlers(id); + if (handlerConfig != null) { + serverBindings = handlerConfig.serverBindings(); + } + putJson(handlerJson, "serverBindings", renderBindings(serverBindings)); + } + + private static JSONArray renderBindings(List<String> bindings) { + JSONArray ret = new JSONArray(); + + for (String binding : bindings) + ret.put(binding); + + return ret; + } + + private static JSONObject renderComponent(Object component, ComponentId id) { + JSONObject jc = new JSONObject(); + putJson(jc, "id", id.stringValue()); + addBundleInfo(jc, component); + return jc; + } + + private static void addBundleInfo(JSONObject jsonObject, Object component) { + BundleInfo bundleInfo = bundleInfo(component); + putJson(jsonObject, "class", bundleInfo.className); + putJson(jsonObject, "bundle", bundleInfo.bundleName); + + } + + private static BundleInfo bundleInfo(Object component) { + try { + Bundle bundle = FrameworkUtil.getBundle(component.getClass()); + + String bundleName = bundle != null ? + bundle.getSymbolicName() + ":" + bundle.getVersion() : + "From classpath"; + return new BundleInfo(component.getClass().getName(), bundleName); + } catch (Exception | NoClassDefFoundError e) { + return new BundleInfo("Unavailable, reconfiguration in progress.", ""); + } + } + + private static void putJson(JSONObject json, String key, Object value) { + try { + json.put(key, value); + } catch (JSONException e) { + // The original JSONException lacks key-value info. + throw new RuntimeException("Trying to add invalid JSON object with key '" + key + "' and value '" + value + "' - " + e.getMessage(), e); + } + } + + static final class BundleInfo { + public final String className; + public final String bundleName; + BundleInfo(String className, String bundleName) { + this.className = className; + this.bundleName = bundleName; + } + } + + static final class StatusResponse { + + private final JdiscBindingsConfig bindingsConfig; + + StatusResponse(JdiscBindingsConfig bindingsConfig) { + this.bindingsConfig = bindingsConfig; + } + + public JSONObject render() { + JSONObject root = new JSONObject(); + + putJson(root, "handlers", + renderRequestHandlers(bindingsConfig, Container.get().getRequestHandlerRegistry().allComponentsById())); + + return root; + } + + } + + private class IgnoredContent implements ContentChannel { + @Override + public void write(ByteBuffer buf, CompletionHandler handler) { + handler.completed(); + } + + @Override + public void close(CompletionHandler handler) { + handler.completed(); + } + } +} diff --git a/container-disc/src/main/java/com/yahoo/container/usability/package-info.java b/container-disc/src/main/java/com/yahoo/container/usability/package-info.java new file mode 100644 index 00000000000..d5abbfccddd --- /dev/null +++ b/container-disc/src/main/java/com/yahoo/container/usability/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.usability; + +import com.yahoo.osgi.annotation.ExportPackage; |