/*
* 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.cluster;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.hadoop.fs.Path;
import org.apache.helix.HelixManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Maps;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import gobblin.annotation.Alpha;
import gobblin.configuration.ConfigurationKeys;
import gobblin.metrics.Tag;
import gobblin.runtime.JobException;
import gobblin.runtime.JobLauncher;
import gobblin.runtime.api.MutableJobCatalog;
import gobblin.runtime.listeners.JobListener;
import gobblin.scheduler.JobScheduler;
import gobblin.cluster.event.DeleteJobConfigArrivalEvent;
import gobblin.cluster.event.NewJobConfigArrivalEvent;
import gobblin.cluster.event.UpdateJobConfigArrivalEvent;
import gobblin.scheduler.SchedulerService;
/**
* An extension to {@link JobScheduler} that schedules and runs Gobblin jobs on Helix using
* {@link GobblinHelixJobLauncher}s.
*
* @author Yinan Li
*/
@Alpha
public class GobblinHelixJobScheduler extends JobScheduler {
private static final Logger LOGGER = LoggerFactory.getLogger(GobblinHelixJobScheduler.class);
static final String HELIX_MANAGER_KEY = "helixManager";
static final String APPLICATION_WORK_DIR_KEY = "applicationWorkDir";
static final String METADATA_TAGS = "metadataTags";
private final Properties properties;
private final HelixManager helixManager;
private final EventBus eventBus;
private final Path appWorkDir;
private final List<? extends Tag<?>> metadataTags;
private final MutableJobCatalog jobCatalog;
public GobblinHelixJobScheduler(Properties properties, HelixManager helixManager, EventBus eventBus,
Path appWorkDir, List<? extends Tag<?>> metadataTags, SchedulerService schedulerService,
MutableJobCatalog jobCatalog) throws Exception {
super(properties, schedulerService);
this.properties = properties;
this.helixManager = helixManager;
this.eventBus = eventBus;
this.appWorkDir = appWorkDir;
this.metadataTags = metadataTags;
this.jobCatalog = jobCatalog;
}
@Override
protected void startUp() throws Exception {
this.eventBus.register(this);
super.startUp();
}
@Override
public void scheduleJob(Properties jobProps, JobListener jobListener) throws JobException {
Map<String, Object> additionalJobDataMap = Maps.newHashMap();
additionalJobDataMap.put(HELIX_MANAGER_KEY, this.helixManager);
additionalJobDataMap.put(APPLICATION_WORK_DIR_KEY, this.appWorkDir);
additionalJobDataMap.put(METADATA_TAGS, this.metadataTags);
try {
scheduleJob(jobProps, jobListener, additionalJobDataMap, GobblinHelixJob.class);
} catch (Exception e) {
throw new JobException("Failed to schedule job " + jobProps.getProperty(ConfigurationKeys.JOB_NAME_KEY), e);
}
}
@Override
public void runJob(Properties jobProps, JobListener jobListener) throws JobException {
try {
JobLauncher jobLauncher = buildGobblinHelixJobLauncher(jobProps);
runJob(jobProps, jobListener, jobLauncher);
} catch (Exception e) {
throw new JobException("Failed to run job " + jobProps.getProperty(ConfigurationKeys.JOB_NAME_KEY), e);
}
}
private GobblinHelixJobLauncher buildGobblinHelixJobLauncher(Properties jobProps)
throws Exception {
return new GobblinHelixJobLauncher(jobProps, this.helixManager, this.appWorkDir, this.metadataTags);
}
@Subscribe
public void handleNewJobConfigArrival(NewJobConfigArrivalEvent newJobArrival) {
LOGGER.info("Received new job configuration of job " + newJobArrival.getJobName());
try {
Properties jobConfig = new Properties();
jobConfig.putAll(this.properties);
jobConfig.putAll(newJobArrival.getJobConfig());
if (jobConfig.containsKey(ConfigurationKeys.JOB_SCHEDULE_KEY)) {
LOGGER.info("Scheduling job " + newJobArrival.getJobName());
scheduleJob(jobConfig, null);
} else {
LOGGER.info("No job schedule found, so running job " + newJobArrival.getJobName());
this.jobExecutor.execute(new NonScheduledJobRunner(newJobArrival.getJobName(), jobConfig, null));
}
} catch (JobException je) {
LOGGER.error("Failed to schedule or run job " + newJobArrival.getJobName(), je);
}
}
@Subscribe
public void handleUpdateJobConfigArrival(UpdateJobConfigArrivalEvent updateJobArrival) {
LOGGER.info("Received update for job configuration of job " + updateJobArrival.getJobName());
try {
handleDeleteJobConfigArrival(new DeleteJobConfigArrivalEvent(updateJobArrival.getJobName(),
updateJobArrival.getJobConfig()));
} catch (Exception je) {
LOGGER.error("Failed to update job " + updateJobArrival.getJobName(), je);
}
try {
handleNewJobConfigArrival(new NewJobConfigArrivalEvent(updateJobArrival.getJobName(),
updateJobArrival.getJobConfig()));
} catch (Exception je) {
LOGGER.error("Failed to update job " + updateJobArrival.getJobName(), je);
}
}
@Subscribe
public void handleDeleteJobConfigArrival(DeleteJobConfigArrivalEvent deleteJobArrival) {
LOGGER.info("Received delete for job configuration of job " + deleteJobArrival.getJobName());
try {
unscheduleJob(deleteJobArrival.getJobName());
} catch (JobException je) {
LOGGER.error("Failed to unschedule job " + deleteJobArrival.getJobName());
}
}
/**
* This class is responsible for running non-scheduled jobs.
*/
class NonScheduledJobRunner implements Runnable {
private final String jobUri;
private final Properties jobConfig;
private final JobListener jobListener;
public NonScheduledJobRunner(String jobUri, Properties jobConfig, JobListener jobListener) {
this.jobUri = jobUri;
this.jobConfig = jobConfig;
this.jobListener = jobListener;
}
@Override
public void run() {
try {
GobblinHelixJobScheduler.this.runJob(this.jobConfig, this.jobListener);
// remove non-scheduled job catalog once done so it won't be re-executed
if (GobblinHelixJobScheduler.this.jobCatalog != null) {
try {
GobblinHelixJobScheduler.this.jobCatalog.remove(new URI(jobUri));
} catch (URISyntaxException e) {
LOGGER.error("Failed to remove job with bad uri " + jobUri, e);
}
}
} catch (JobException je) {
LOGGER.error("Failed to run job " + this.jobConfig.getProperty(ConfigurationKeys.JOB_NAME_KEY), je);
}
}
}
}