/**
* 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 org.apache.falcon.execution;
import org.apache.falcon.FalconException;
import org.apache.falcon.entity.EntityNotRegisteredException;
import org.apache.falcon.entity.EntityUtil;
import org.apache.falcon.entity.v0.Entity;
import org.apache.falcon.entity.v0.process.Process;
import org.apache.falcon.exception.StateStoreException;
import org.apache.falcon.notification.service.event.Event;
import org.apache.falcon.service.FalconService;
import org.apache.falcon.state.EntityClusterID;
import org.apache.falcon.state.EntityState;
import org.apache.falcon.state.EntityStateChangeHandler;
import org.apache.falcon.state.InstanceID;
import org.apache.falcon.state.StateService;
import org.apache.falcon.state.store.AbstractStateStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* This singleton is the entry point for all callbacks from the notification services.
* The execution service handles any system level events that apply to all entities.
* It is responsible for creation of entity executors one per entity, per cluster.
*/
public final class FalconExecutionService implements FalconService, EntityStateChangeHandler, NotificationHandler {
private static final Logger LOG = LoggerFactory.getLogger(FalconExecutionService.class);
// Stores all entity executors in memory
private ConcurrentMap<EntityClusterID, EntityExecutor> executors = new ConcurrentHashMap<>();
private static FalconExecutionService executionService = new FalconExecutionService();
@Override
public String getName() {
return "FalconExecutionService";
}
public void init() {
LOG.debug("State store instance being used : {}", AbstractStateStore.get());
// Initialize all executors from store
try {
for (Entity entity : AbstractStateStore.get().getEntities(EntityState.STATE.SCHEDULED)) {
try {
for (String cluster : EntityUtil.getClustersDefinedInColos(entity)) {
EntityExecutor executor = createEntityExecutor(entity, cluster);
executors.put(new EntityClusterID(entity, cluster), executor);
executor.schedule();
}
} catch (FalconException e) {
LOG.error("Unable to load entity : " + entity.getName(), e);
throw new RuntimeException(e);
}
}
} catch (StateStoreException e) {
LOG.error("Unable to get Entities from State Store ", e);
throw new RuntimeException(e);
}
// TODO : During migration, the state store itself may not have been completely bootstrapped.
}
/**
* Returns an EntityExecutor implementation based on the entity type.
*
* @param entity
* @param cluster
* @return
* @throws FalconException
*/
private EntityExecutor createEntityExecutor(Entity entity, String cluster) throws FalconException {
switch (entity.getEntityType()) {
case FEED:
throw new UnsupportedOperationException("No support yet for feed.");
case PROCESS:
return new ProcessExecutor(((Process)entity), cluster);
default:
throw new IllegalArgumentException("Unhandled type " + entity.getEntityType().name());
}
}
@Override
public void destroy() throws FalconException {
}
/**
* @return - An instance(singleton) of FalconExecutionService
*/
public static FalconExecutionService get() {
return executionService;
}
private FalconExecutionService() {}
@Override
public void onEvent(Event event) throws FalconException {
// Currently, simply passes along the event to the appropriate executor
EntityClusterID id = null;
if (event.getTarget() instanceof EntityClusterID) {
id = (EntityClusterID) event.getTarget();
} else if (event.getTarget() instanceof InstanceID) {
id = ((InstanceID) event.getTarget()).getEntityClusterID();
}
if (id != null) {
EntityExecutor executor = executors.get(id);
if (executor == null) {
// The executor has gone away or entity was not scheduled on native scheduler,
// throw an exception so the notification service knows.
throw new EntityNotRegisteredException("Target executor for " + event.getTarget() + " does not exist.");
}
executor.onEvent(event);
}
}
@Override
public PRIORITY getPriority() {
return PRIORITY.HIGH;
}
@Override
public void onSubmit(Entity entity) throws FalconException {
// Do nothing
}
@Override
public void onSchedule(Entity entity) throws FalconException {
for (String cluster : EntityUtil.getClustersDefinedInColos(entity)) {
EntityClusterID id = new EntityClusterID(entity, cluster);
if (executors.containsKey(id)) {
LOG.info("Entity {} is already scheduled on cluster {}.", id, cluster);
continue;
}
EntityExecutor executor = createEntityExecutor(entity, cluster);
executors.put(id, executor);
LOG.info("Scheduling entity {} on cluster {}.", id, cluster);
executor.schedule();
}
}
@Override
public void onSuspend(Entity entity) throws FalconException {
for (String cluster : EntityUtil.getClustersDefinedInColos(entity)) {
EntityClusterID id = new EntityClusterID(entity, cluster);
if (!executors.containsKey(id)) {
LOG.info("Entity {} is already suspended on cluster {}.", id, cluster);
continue;
}
EntityExecutor executor = getEntityExecutor(entity, cluster);
LOG.info("Suspending entity, {} on cluster {}.", id, cluster);
executor.suspendAll();
}
}
@Override
public void onResume(Entity entity) throws FalconException {
for (String cluster : EntityUtil.getClustersDefinedInColos(entity)) {
EntityClusterID id = new EntityClusterID(entity, cluster);
// Create even if it exists in cache, as the instances need to be refreshed.
EntityExecutor executor = createEntityExecutor(entity, cluster);
executors.put(new EntityClusterID(entity, cluster), executor);
LOG.info("Resuming entity, {} on cluster {}.", id, cluster);
executor.resumeAll();
}
}
@Override
public void onKill(Entity entity) throws FalconException {
for (String cluster : EntityUtil.getClustersDefinedInColos(entity)) {
EntityClusterID id = new EntityClusterID(entity, cluster);
if (!executors.containsKey(id)) {
LOG.info("Entity {} is already deleted on cluster {}.", id, cluster);
continue;
}
EntityExecutor executor = getEntityExecutor(entity, cluster);
executor.killAll();
executors.remove(executor.getId());
}
}
/**
* Schedules an entity.
*
* @param entity
* @param properties
* @throws FalconException
*/
public void schedule(Entity entity, Properties properties) throws FalconException {
StateService.get().handleStateChange(entity, EntityState.EVENT.SCHEDULE, this, properties);
}
/**
* Suspends an entity.
*
* @param entity
* @throws FalconException
*/
public void suspend(Entity entity) throws FalconException {
StateService.get().handleStateChange(entity, EntityState.EVENT.SUSPEND, this);
}
/**
* Resumes an entity.
*
* @param entity
* @throws FalconException
*/
public void resume(Entity entity) throws FalconException {
StateService.get().handleStateChange(entity, EntityState.EVENT.RESUME, this);
}
/**
* Deletes an entity from the execution service.
*
* @param entity
* @throws FalconException
*/
public void delete(Entity entity) throws FalconException {
StateService.get().handleStateChange(entity, EntityState.EVENT.KILL, this);
}
/**
* Returns the instance of {@link EntityExecutor} for a given entity and cluster.
*
* @param entity
* @param cluster
* @return
* @throws FalconException
*/
public EntityExecutor getEntityExecutor(Entity entity, String cluster) throws FalconException {
EntityClusterID id = new EntityClusterID(entity, cluster);
if (executors.containsKey(id)) {
return executors.get(id);
} else {
throw new FalconException("Entity executor for entity cluster key : " + id.getKey() + " does not exist.");
}
}
/**
* Schedules an entity.
*
* @param entity
* @throws FalconException
*/
public void schedule(Process entity) throws FalconException {
schedule(entity, null);
}
}