/* * 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.job_monitor; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.Collection; import java.util.Map; import java.util.Properties; import java.util.regex.Pattern; import org.apache.hadoop.fs.Path; import com.codahale.metrics.Counter; import com.google.common.base.Charsets; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import com.typesafe.config.ConfigValue; import gobblin.metrics.GobblinTrackingEvent; import gobblin.metrics.event.sla.SlaEventKeys; import gobblin.metrics.reporter.util.FixedSchemaVersionWriter; import gobblin.metrics.reporter.util.NoopSchemaVersionWriter; import gobblin.metrics.reporter.util.SchemaVersionWriter; import gobblin.runtime.api.GobblinInstanceDriver; import gobblin.runtime.api.JobSpec; import gobblin.runtime.api.JobSpecMonitor; import gobblin.runtime.api.JobSpecMonitorFactory; import gobblin.runtime.api.MutableJobCatalog; import gobblin.runtime.api.SpecExecutorInstance; import gobblin.runtime.api.SpecExecutorInstance.Verb; import gobblin.runtime.job_spec.AvroJobSpec; import gobblin.runtime.metrics.RuntimeMetrics; import gobblin.util.Either; import gobblin.util.PathUtils; import gobblin.util.reflection.GobblinConstructorUtils; import kafka.message.MessageAndMetadata; import lombok.Getter; import lombok.extern.slf4j.Slf4j; /** * A {@link KafkaJobMonitor} that parses {@link AvroJobSpec}s and generates {@link JobSpec}s. */ @Getter @Slf4j public class AvroJobSpecKafkaJobMonitor extends KafkaAvroJobMonitor<AvroJobSpec> { public static final String CONFIG_PREFIX = "gobblin.jobMonitor.avroJobSpec"; public static final String TOPIC_KEY = "topic"; public static final String SCHEMA_VERSION_READER_CLASS = "versionReaderClass"; protected static final String VERB_KEY = "Verb"; private static final Config DEFAULTS = ConfigFactory.parseMap(ImmutableMap.of( SCHEMA_VERSION_READER_CLASS, FixedSchemaVersionWriter.class.getName())); public static class Factory implements JobSpecMonitorFactory { @Override public JobSpecMonitor forJobCatalog(GobblinInstanceDriver instanceDriver, MutableJobCatalog jobCatalog) throws IOException { Config config = instanceDriver.getSysConfig().getConfig().getConfig(CONFIG_PREFIX).withFallback(DEFAULTS); return forConfig(config, jobCatalog); } /** * Create a {@link AvroJobSpecKafkaJobMonitor} from an input {@link Config}. Useful for multiple monitors, where * the configuration of each monitor is scoped. * @param localScopeConfig The sub-{@link Config} for this monitor without any namespacing (e.g. the key for * topic should simply be "topic"). * @throws IOException */ public JobSpecMonitor forConfig(Config localScopeConfig, MutableJobCatalog jobCatalog) throws IOException { Preconditions.checkArgument(localScopeConfig.hasPath(TOPIC_KEY)); Config config = localScopeConfig.withFallback(DEFAULTS); String topic = config.getString(TOPIC_KEY); SchemaVersionWriter versionWriter; try { versionWriter = (SchemaVersionWriter) GobblinConstructorUtils. invokeLongestConstructor(Class.forName(config.getString(SCHEMA_VERSION_READER_CLASS)), config); } catch (ReflectiveOperationException roe) { throw new IllegalArgumentException(roe); } return new AvroJobSpecKafkaJobMonitor(topic, jobCatalog, config, versionWriter); } } protected AvroJobSpecKafkaJobMonitor(String topic, MutableJobCatalog catalog, Config limitedScopeConfig, SchemaVersionWriter<?> versionWriter) throws IOException { super(topic, catalog, limitedScopeConfig, AvroJobSpec.SCHEMA$, versionWriter); } @Override protected void createMetrics() { super.createMetrics(); } /** * Creates a {@link JobSpec} or {@link URI} from the {@link AvroJobSpec} record. * @param record the record as an {@link AvroJobSpec} * @return a {@link JobSpec} or {@link URI} wrapped in a {@link Collection} of {@link Either} */ @Override public Collection<Either<JobSpec, URI>> parseJobSpec(AvroJobSpec record) { JobSpec.Builder jobSpecBuilder = JobSpec.builder(record.getUri()); Properties props = new Properties(); props.putAll(record.getProperties()); jobSpecBuilder.withJobCatalogURI(record.getUri()).withVersion(record.getVersion()) .withDescription(record.getDescription()).withConfigAsProperties(props); if (!record.getTemplateUri().isEmpty()) { try { jobSpecBuilder.withTemplate(new URI(record.getTemplateUri())); } catch (URISyntaxException e) { log.error("could not parse template URI " + record.getTemplateUri()); } } String verbName = record.getMetadata().get(VERB_KEY); Verb verb = Verb.valueOf(verbName); JobSpec jobSpec = jobSpecBuilder.build(); log.info("Parsed job spec " + jobSpec.toString()); if (verb == Verb.ADD || verb == Verb.UPDATE) { return Lists.newArrayList(Either.<JobSpec, URI>left(jobSpec)); } else { return Lists.newArrayList(Either.<JobSpec, URI>right(jobSpec.getUri())); } } }