/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package gobblin.runtime.instance; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import com.google.common.util.concurrent.Service; import com.google.common.util.concurrent.ServiceManager; import com.typesafe.config.ConfigFactory; import gobblin.broker.SharedResourcesBrokerFactory; import gobblin.broker.SharedResourcesBrokerImpl; import gobblin.broker.SimpleScope; import gobblin.broker.gobblin_scopes.GobblinScopeTypes; import gobblin.broker.iface.SharedResourcesBroker; import gobblin.instrumented.Instrumented; import gobblin.metrics.GobblinMetrics; import gobblin.metrics.MetricContext; import gobblin.metrics.Tag; import gobblin.runtime.api.Configurable; import gobblin.runtime.api.GobblinInstanceEnvironment; import gobblin.runtime.api.GobblinInstanceLauncher; import gobblin.runtime.api.GobblinInstancePlugin; import gobblin.runtime.api.GobblinInstancePluginFactory; import gobblin.runtime.api.JobCatalog; import gobblin.runtime.api.JobExecutionLauncher; import gobblin.runtime.api.JobSpecScheduler; import gobblin.runtime.job_catalog.FSJobCatalog; import gobblin.runtime.job_catalog.ImmutableFSJobCatalog; import gobblin.runtime.job_catalog.InMemoryJobCatalog; import gobblin.runtime.job_exec.JobLauncherExecutionDriver; import gobblin.runtime.plugins.email.EmailNotificationPlugin; import gobblin.runtime.scheduler.ImmediateJobSpecScheduler; import gobblin.runtime.scheduler.QuartzJobSpecScheduler; import gobblin.runtime.std.DefaultConfigurableImpl; import gobblin.util.ClassAliasResolver; import gobblin.util.ConfigUtils; /** A simple wrapper {@link DefaultGobblinInstanceDriverImpl} that will instantiate necessary * sub-components (e.g. {@link JobCatalog}, {@link JobSpecScheduler}, {@link JobExecutionLauncher} * and it will manage their lifecycle. */ public class StandardGobblinInstanceDriver extends DefaultGobblinInstanceDriverImpl { public static final String INSTANCE_CFG_PREFIX = "gobblin.instance"; /** A comma-separated list of class names or aliases of {@link GobblinInstancePluginFactory} for * plugins to be instantiated with this instance. */ public static final String PLUGINS_KEY = "plugins"; public static final String PLUGINS_FULL_KEY = INSTANCE_CFG_PREFIX + "." + PLUGINS_KEY; private ServiceManager _subservices; private final List<GobblinInstancePlugin> _plugins; protected StandardGobblinInstanceDriver(String instanceName, Configurable sysConfig, JobCatalog jobCatalog, JobSpecScheduler jobScheduler, JobExecutionLauncher jobLauncher, Optional<MetricContext> instanceMetricContext, Optional<Logger> log, List<GobblinInstancePluginFactory> plugins, SharedResourcesBroker<GobblinScopeTypes> instanceBroker) { super(instanceName, sysConfig, jobCatalog, jobScheduler, jobLauncher, instanceMetricContext, log, instanceBroker); List<Service> componentServices = new ArrayList<>(); checkComponentService(getJobCatalog(), componentServices); checkComponentService(getJobScheduler(), componentServices); checkComponentService(getJobLauncher(), componentServices); _plugins = createPlugins(plugins, componentServices); if (componentServices.size() > 0) { _subservices = new ServiceManager(componentServices); } } private List<GobblinInstancePlugin> createPlugins(List<GobblinInstancePluginFactory> plugins, List<Service> componentServices) { List<GobblinInstancePlugin> res = new ArrayList<>(); for (GobblinInstancePluginFactory pluginFactory: plugins) { Optional<GobblinInstancePlugin> plugin = createPlugin(this, pluginFactory, componentServices); if (plugin.isPresent()) { res.add(plugin.get()); } } return res; } static Optional<GobblinInstancePlugin> createPlugin(StandardGobblinInstanceDriver instance, GobblinInstancePluginFactory pluginFactory, List<Service> componentServices) { instance.getLog().info("Instantiating a plugin of type: " + pluginFactory); try { GobblinInstancePlugin plugin = pluginFactory.createPlugin(instance); componentServices.add(plugin); instance.getLog().info("Instantiated plugin: " + plugin); return Optional.of(plugin); } catch (RuntimeException e) { instance.getLog().warn("Failed to create plugin: " + e, e); } return Optional.absent(); } @Override protected void startUp() throws Exception { getLog().info("Starting driver ..."); if (null != _subservices) { getLog().info("Starting subservices"); _subservices.startAsync(); _subservices.awaitHealthy(getInstanceCfg().getStartTimeoutMs(), TimeUnit.MILLISECONDS); getLog().info("All subservices have been started."); } else { getLog().info("No subservices found."); } super.startUp(); } private void checkComponentService(Object component, List<Service> componentServices) { if (component instanceof Service) { componentServices.add((Service)component); } } @Override protected void shutDown() throws Exception { getLog().info("Shutting down driver ..."); super.shutDown(); if (null != _subservices) { getLog().info("Shutting down subservices ..."); _subservices.stopAsync(); _subservices.awaitStopped(getInstanceCfg().getShutdownTimeoutMs(), TimeUnit.MILLISECONDS); getLog().info("All subservices have been shutdown."); } } public static Builder builder() { return new Builder(); } /** * A builder for StandardGobblinInstanceDriver instances. The goal is to be convention driven * rather than configuration. * * <p>Conventions: * <ul> * <li> Logger uses the instance name as a category * <li> Default implementations of JobCatalog, JobSpecScheduler, JobExecutionLauncher use the * logger as their logger. * </ul> * */ public static class Builder implements GobblinInstanceEnvironment { private static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger(0); private Optional<GobblinInstanceEnvironment> _instanceEnv = Optional.<GobblinInstanceEnvironment>absent(); private Optional<String> _instanceName = Optional.absent(); private Optional<Logger> _log = Optional.absent(); private Optional<JobCatalog> _jobCatalog = Optional.absent(); private Optional<JobSpecScheduler> _jobScheduler = Optional.absent(); private Optional<JobExecutionLauncher> _jobLauncher = Optional.absent(); private Optional<MetricContext> _metricContext = Optional.absent(); private Optional<Boolean> _instrumentationEnabled = Optional.absent(); private Optional<SharedResourcesBroker<GobblinScopeTypes>> _instanceBroker = Optional.absent(); private List<GobblinInstancePluginFactory> _plugins = new ArrayList<>(); private final ClassAliasResolver<GobblinInstancePluginFactory> _aliasResolver = new ClassAliasResolver<>(GobblinInstancePluginFactory.class); public Builder(Optional<GobblinInstanceEnvironment> instanceLauncher) { _instanceEnv = instanceLauncher; } /** Constructor with no Gobblin instance launcher */ public Builder() { } /** Constructor with a launcher */ public Builder(GobblinInstanceLauncher instanceLauncher) { this(); withInstanceEnvironment(instanceLauncher); } public Builder withInstanceEnvironment(GobblinInstanceEnvironment instanceLauncher) { Preconditions.checkNotNull(instanceLauncher); _instanceEnv = Optional.of(instanceLauncher); return this; } public Optional<GobblinInstanceEnvironment> getInstanceEnvironment() { return _instanceEnv; } public String getDefaultInstanceName() { if (_instanceEnv.isPresent()) { return _instanceEnv.get().getInstanceName(); } else { return StandardGobblinInstanceDriver.class.getName() + "-" + INSTANCE_COUNTER.getAndIncrement(); } } @Override public String getInstanceName() { if (! _instanceName.isPresent()) { _instanceName = Optional.of(getDefaultInstanceName()); } return _instanceName.get(); } public Builder withInstanceName(String instanceName) { _instanceName = Optional.of(instanceName); return this; } public Logger getDefaultLog() { return _instanceEnv.isPresent() ? _instanceEnv.get().getLog() : LoggerFactory.getLogger(getInstanceName()); } @Override public Logger getLog() { if (! _log.isPresent()) { _log = Optional.of(getDefaultLog()); } return _log.get(); } public Builder withLog(Logger log) { _log = Optional.of(log); return this; } public JobCatalog getDefaultJobCatalog() { return new InMemoryJobCatalog(this); } public JobCatalog getJobCatalog() { if (! _jobCatalog.isPresent()) { _jobCatalog = Optional.of(getDefaultJobCatalog()); } return _jobCatalog.get(); } public Builder withJobCatalog(JobCatalog jobCatalog) { _jobCatalog = Optional.of(jobCatalog); return this; } public Builder withInMemoryJobCatalog() { return withJobCatalog(new InMemoryJobCatalog(this)); } public Builder withFSJobCatalog() { try { return withJobCatalog(new FSJobCatalog(this)); } catch (IOException e) { throw new RuntimeException("Unable to create FS Job Catalog: " + e, e); } } public Builder withImmutableFSJobCatalog() { try { return withJobCatalog(new ImmutableFSJobCatalog(this)); } catch (IOException e) { throw new RuntimeException("Unable to create FS Job Catalog: " + e, e); } } public JobSpecScheduler getDefaultJobScheduler() { return new ImmediateJobSpecScheduler(Optional.of(getLog())); } public JobSpecScheduler getJobScheduler() { if (!_jobScheduler.isPresent()) { _jobScheduler = Optional.of(getDefaultJobScheduler()); } return _jobScheduler.get(); } public Builder withJobScheduler(JobSpecScheduler jobScheduler) { _jobScheduler = Optional.of(jobScheduler); return this; } public Builder withImmediateJobScheduler() { return withJobScheduler(new ImmediateJobSpecScheduler(Optional.of(getLog()))); } public Builder withQuartzJobScheduler() { return withJobScheduler(new QuartzJobSpecScheduler(this)); } public JobExecutionLauncher getDefaultJobLauncher() { JobLauncherExecutionDriver.Launcher res = new JobLauncherExecutionDriver.Launcher().withGobblinInstanceEnvironment(this); return res; } public JobExecutionLauncher getJobLauncher() { if (! _jobLauncher.isPresent()) { _jobLauncher = Optional.of(getDefaultJobLauncher()); } return _jobLauncher.get(); } public Builder withJobLauncher(JobExecutionLauncher jobLauncher) { _jobLauncher = Optional.of(jobLauncher); return this; } public Builder withMetricContext(MetricContext instanceMetricContext) { _metricContext = Optional.of(instanceMetricContext); return this; } @Override public MetricContext getMetricContext() { if (!_metricContext.isPresent()) { _metricContext = Optional.of(getDefaultMetricContext()); } return _metricContext.get(); } public MetricContext getDefaultMetricContext() { gobblin.configuration.State fakeState = new gobblin.configuration.State(getSysConfig().getConfigAsProperties()); List<Tag<?>> tags = new ArrayList<>(); tags.add(new Tag<>(StandardMetrics.INSTANCE_NAME_TAG, getInstanceName())); MetricContext res = Instrumented.getMetricContext(fakeState, StandardGobblinInstanceDriver.class, tags); return res; } public Builder withInstanceBroker(SharedResourcesBroker<GobblinScopeTypes> broker) { _instanceBroker = Optional.of(broker); return this; } @Override public SharedResourcesBroker<GobblinScopeTypes> getInstanceBroker() { if (!_instanceBroker.isPresent()) { _instanceBroker = Optional.of(getDefaultInstanceBroker()); } return _instanceBroker.get(); } public SharedResourcesBroker<GobblinScopeTypes> getDefaultInstanceBroker() { SharedResourcesBrokerImpl<GobblinScopeTypes> globalBroker = SharedResourcesBrokerFactory.createDefaultTopLevelBroker(getSysConfig().getConfig(), GobblinScopeTypes.GLOBAL.defaultScopeInstance()); return globalBroker.newSubscopedBuilder(new SimpleScope<>(GobblinScopeTypes.INSTANCE, getInstanceName())).build(); } public StandardGobblinInstanceDriver build() { Configurable sysConfig = getSysConfig(); return new StandardGobblinInstanceDriver(getInstanceName(), sysConfig, getJobCatalog(), getJobScheduler(), getJobLauncher(), isInstrumentationEnabled() ? Optional.of(getMetricContext()) : Optional.<MetricContext>absent(), Optional.of(getLog()), getPlugins(), getInstanceBroker() ); } @Override public Configurable getSysConfig() { return _instanceEnv.isPresent() ? _instanceEnv.get().getSysConfig() : DefaultConfigurableImpl.createFromConfig(ConfigFactory.load()); } public Builder withInstrumentationEnabled(boolean enabled) { _instrumentationEnabled = Optional.of(enabled); return this; } public boolean getDefaultInstrumentationEnabled() { return GobblinMetrics.isEnabled(getSysConfig().getConfig()); } @Override public boolean isInstrumentationEnabled() { if (!_instrumentationEnabled.isPresent()) { _instrumentationEnabled = Optional.of(getDefaultInstrumentationEnabled()); } return _instrumentationEnabled.get(); } @Override public List<Tag<?>> generateTags(gobblin.configuration.State state) { return Collections.emptyList(); } @Override public void switchMetricContext(List<Tag<?>> tags) { throw new UnsupportedOperationException(); } @Override public void switchMetricContext(MetricContext context) { throw new UnsupportedOperationException(); } /** * Returns the list of plugins as defined in the system configuration. These are the * defined in the PLUGINS_FULL_KEY config option. * The list also includes plugins that are automatically added by gobblin. * */ public List<GobblinInstancePluginFactory> getDefaultPlugins() { List<String> pluginNames = ConfigUtils.getStringList(getSysConfig().getConfig(), PLUGINS_FULL_KEY); List<GobblinInstancePluginFactory> pluginFactories = Lists.newArrayList(); // By default email notification plugin is added. if (!ConfigUtils.getBoolean(getSysConfig().getConfig(), EmailNotificationPlugin.EMAIL_NOTIFICATIONS_DISABLED_KEY, EmailNotificationPlugin.EMAIL_NOTIFICATIONS_DISABLED_DEFAULT)) { pluginFactories.add(new EmailNotificationPlugin.Factory()); } pluginFactories.addAll(Lists.transform(pluginNames, new Function<String, GobblinInstancePluginFactory>() { @Override public GobblinInstancePluginFactory apply(String input) { Class<? extends GobblinInstancePluginFactory> factoryClass; try { factoryClass = _aliasResolver.resolveClass(input); return factoryClass.newInstance(); } catch (ClassNotFoundException|InstantiationException|IllegalAccessException e) { throw new RuntimeException("Unable to instantiate plugin factory " + input + ": " + e, e); } } })); return pluginFactories; } public List<GobblinInstancePluginFactory> getPlugins() { List<GobblinInstancePluginFactory> res = new ArrayList<>(getDefaultPlugins()); res.addAll(_plugins); return res; } public Builder addPlugin(GobblinInstancePluginFactory pluginFactory) { _plugins.add(pluginFactory); return this; } } public List<GobblinInstancePlugin> getPlugins() { return _plugins; } }