/** * 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.unit; import org.apache.commons.lang.StringUtils; import org.apache.falcon.ExtensionHandler; import org.apache.falcon.FalconException; import org.apache.falcon.LifeCycle; import org.apache.falcon.client.AbstractFalconClient; import org.apache.falcon.client.FalconCLIException; import org.apache.falcon.entity.EntityUtil; import org.apache.falcon.entity.store.ConfigurationStore; import org.apache.falcon.entity.v0.Entity; import org.apache.falcon.entity.v0.EntityType; import org.apache.falcon.entity.v0.SchemaHelper; import org.apache.falcon.entity.v0.feed.Feed; import org.apache.falcon.entity.v0.process.Cluster; import org.apache.falcon.entity.v0.process.Process; import org.apache.falcon.entity.v0.process.Validity; import org.apache.falcon.extensions.store.ExtensionStore; import org.apache.falcon.resource.APIResult; import org.apache.falcon.resource.EntityList; import org.apache.falcon.resource.EntitySummaryResult; import org.apache.falcon.resource.ExtensionJobList; import org.apache.falcon.resource.FeedInstanceResult; import org.apache.falcon.resource.FeedLookupResult; import org.apache.falcon.resource.InstanceDependencyResult; import org.apache.falcon.resource.InstancesResult; import org.apache.falcon.resource.InstancesSummaryResult; import org.apache.falcon.resource.LineageGraphResult; import org.apache.falcon.resource.SchedulableEntityInstanceResult; import org.apache.falcon.resource.TriageResult; import org.apache.falcon.resource.admin.AdminResource; import org.apache.falcon.util.DateUtil; import org.apache.falcon.workflow.WorkflowEngineFactory; import org.apache.falcon.workflow.engine.AbstractWorkflowEngine; import org.apache.hadoop.security.authorize.AuthorizationException; import org.json.simple.JSONValue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URISyntaxException; import java.util.TreeMap; import java.util.ArrayList; import java.util.Properties; import java.util.List; import java.util.Map; import java.util.Date; import java.util.LinkedHashMap; import java.util.TimeZone; import java.util.SortedMap; /** * Client for Falcon Unit. */ public class FalconUnitClient extends AbstractFalconClient { private static final Logger LOG = LoggerFactory.getLogger(FalconUnitClient.class); protected static final int XML_DEBUG_LEN = 10 * 1024; private static final String DEFAULT_ORDER_BY = "status"; private static final String DEFAULT_SORTED_ORDER = "asc"; private ConfigurationStore configStore; private AbstractWorkflowEngine workflowEngine; private LocalSchedulableEntityManager localSchedulableEntityManager; private LocalInstanceManager localInstanceManager; private LocalExtensionManager localExtensionManager; FalconUnitClient() throws FalconException { configStore = ConfigurationStore.get(); workflowEngine = WorkflowEngineFactory.getWorkflowEngine(); localSchedulableEntityManager = new LocalSchedulableEntityManager(); localInstanceManager = new LocalInstanceManager(); localExtensionManager = new LocalExtensionManager(); } public ConfigurationStore getConfigStore() { return this.configStore; } /** * Submit a new entity. Entities can be of type feed, process or data end * points. Entity definitions are validated structurally against schema and * subsequently for other rules before they are admitted into the system * * @param type entity type * @param filePath path for the definition of entity * @return boolean */ @Override public APIResult submit(String type, String filePath, String doAsUser) { try { return localSchedulableEntityManager.submit(type, filePath, doAsUser); } catch (FalconException | IOException e) { throw new FalconCLIException("FAILED", e); } } /** * Schedules submitted entity. * * @param entityType entity Type * @param entityName entity name * @param cluster cluster on which it has to be scheduled * @return */ @Override public APIResult schedule(EntityType entityType, String entityName, String cluster, Boolean skipDryRun, String doAsUser, String properties) { try { return localSchedulableEntityManager.schedule(entityType, entityName, skipDryRun, properties); } catch (FalconException | AuthorizationException e) { throw new FalconCLIException(e); } } @Override public APIResult delete(EntityType entityType, String entityName, String colo) { return localSchedulableEntityManager.delete(entityType, entityName, colo); } @Override public APIResult validate(String entityType, String filePath, Boolean skipDryRun, String doAsUser) { try { return localSchedulableEntityManager.validate(entityType, filePath, skipDryRun, doAsUser); } catch (FalconException e) { throw new FalconCLIException(e); } } @Override public APIResult update(String entityType, String entityName, String filePath, Boolean skipDryRun, String doAsUser) { try { return localSchedulableEntityManager.update(entityType, entityName, filePath, skipDryRun, "local", doAsUser); } catch (FalconException e) { throw new FalconCLIException(e); } } @Override public Entity getDefinition(String entityType, String entityName, String doAsUser) { String entity = localSchedulableEntityManager.getEntityDefinition(entityType, entityName); return Entity.fromString(EntityType.getEnum(entityType), entity); } //SUSPEND CHECKSTYLE CHECK ParameterNumberCheck @Override public InstancesResult getStatusOfInstances(String type, String entity, String start, String end, String colo, List<LifeCycle> lifeCycles, String filterBy, String orderBy, String sortOrder, Integer offset, Integer numResults, String doAsUser, Boolean allAttempts) { if (orderBy == null) { orderBy = DEFAULT_ORDER_BY; } if (sortOrder == null) { sortOrder = DEFAULT_SORTED_ORDER; } if (offset == null) { offset = 0; } if (numResults == null) { numResults = 1; } return localInstanceManager.getStatusOfInstances(type, entity, start, end, colo, lifeCycles, filterBy, orderBy, sortOrder, offset, numResults, allAttempts); } /** * Schedules an submitted process entity immediately. * * @param entityName entity name * @param startTime start time for process while scheduling * @param numInstances numInstances of process to be scheduled * @param cluster cluster on which process to be scheduled * @return boolean */ public APIResult schedule(EntityType entityType, String entityName, String startTime, int numInstances, String cluster, Boolean skipDryRun, String properties) { try { FalconUnitHelper.checkSchedulableEntity(entityType.toString()); Entity entity = EntityUtil.getEntity(entityType, entityName); boolean clusterPresent = checkAndUpdateCluster(entity, entityType, cluster); if (!clusterPresent) { LOG.warn("Cluster is not registered with this entity " + entityName); return new APIResult(APIResult.Status.FAILED, entity + "Cluster is not registered with this entity " + entityName); } if (StringUtils.isNotEmpty(startTime) && entityType == EntityType.PROCESS) { updateStartAndEndTime((Process) entity, startTime, numInstances, cluster); } workflowEngine.schedule(entity, skipDryRun, EntityUtil.getPropertyMap(properties)); LOG.info(entityName + " is scheduled successfully"); return new APIResult(APIResult.Status.SUCCEEDED, entity + "(" + "PROCESS" + ") scheduled successfully"); } catch (FalconException e) { throw new FalconCLIException("FAILED", e); } } /** * Instance status for a given nominalTime. * * @param entityType entity type * @param entityName entity name * @param nominalTime nominal time of process * @return InstancesResult.WorkflowStatus */ public InstancesResult.WorkflowStatus getInstanceStatus(String entityType, String entityName, String nominalTime) throws Exception { Date startTime = SchemaHelper.parseDateUTC(nominalTime); Date endTimeDate = DateUtil.getNextMinute(startTime); String endTime = DateUtil.getDateFormatFromTime(endTimeDate.getTime()); InstancesResult instancesResult = getStatusOfInstances(entityType, entityName, nominalTime, endTime, null, null, null, null, null, null, null, null, null); if (instancesResult.getInstances() != null && instancesResult.getInstances().length > 0 && instancesResult.getInstances()[0] != null) { LOG.info("Instance status is " + instancesResult.getInstances()[0].getStatus()); return instancesResult.getInstances()[0].getStatus(); } return null; } @Override public APIResult suspend(EntityType entityType, String entityName, String colo, String doAsUser) { return localSchedulableEntityManager.suspend(entityType.name(), entityName, colo); } @Override public APIResult resume(EntityType entityType, String entityName, String colo, String doAsUser) { return localSchedulableEntityManager.resume(entityType.name(), entityName, colo); } @Override public APIResult getStatus(EntityType entityType, String entityName, String colo, String doAsUser, boolean showScheduler) { return localSchedulableEntityManager.getStatus(entityType.name(), entityName, colo, showScheduler); } @Override public APIResult submitAndSchedule(String entityType, String filePath, Boolean skipDryRun, String doAsUser, String properties) { try { return localSchedulableEntityManager.submitAndSchedule(entityType, filePath, skipDryRun, doAsUser, properties); } catch (FalconException | IOException e) { throw new FalconCLIException(e); } } @Override public APIResult registerExtension(String extensionName, String packagePath, String description) { return localExtensionManager.registerExtensionMetadata(extensionName, packagePath, description); } @Override public APIResult unregisterExtension(String extensionName) { try { return localExtensionManager.unRegisterExtension(extensionName); } catch (FalconException e) { throw new FalconCLIException("Failed in unRegistering the extension"+ e.getMessage()); } } @Override public APIResult enableExtension(String extensionName) { return localExtensionManager.enableExtension(extensionName); } @Override public APIResult disableExtension(String extensionName) { return localExtensionManager.disableExtension(extensionName); } @Override public APIResult submitExtensionJob(String extensionName, String jobName, String configPath, String doAsUser) { InputStream configStream = getServletInputStream(configPath); try { SortedMap<EntityType, List<Entity>> entityMap = getEntityTypeListMap(extensionName, jobName, configStream); return localExtensionManager.submitExtensionJob(extensionName, jobName, configStream, entityMap); } catch (FalconException | IOException e) { throw new FalconCLIException("Failed in submitting extension job " + jobName); } } private SortedMap<EntityType, List<Entity>> getEntityTypeListMap(String extensionName, String jobName, InputStream configStream) { List<Entity> entities = getEntities(extensionName, jobName, configStream); List<Entity> feeds = new ArrayList<>(); List<Entity> processes = new ArrayList<>(); for (Entity entity : entities) { if (EntityType.FEED.equals(entity.getEntityType())) { feeds.add(entity); } else if (EntityType.PROCESS.equals(entity.getEntityType())) { processes.add(entity); } } SortedMap<EntityType, List<Entity>> entityMap = new TreeMap<>(); entityMap.put(EntityType.PROCESS, processes); entityMap.put(EntityType.FEED, feeds); return entityMap; } private List<Entity> getEntities(String extensionName, String jobName, InputStream configStream) { String packagePath = ExtensionStore.getMetaStore().getDetail(extensionName).getLocation(); List<Entity> entities; try { entities = ExtensionHandler.loadAndPrepare(extensionName, jobName, configStream, packagePath); } catch (FalconException | IOException | URISyntaxException e) { throw new FalconCLIException("Failed in generating entities for job:" + jobName, e); } return entities; } @Override public APIResult scheduleExtensionJob(String jobName, String coloExpr, String doAsUser) { try { return localExtensionManager.scheduleExtensionJob(jobName, coloExpr, doAsUser); } catch (FalconException | IOException e) { throw new FalconCLIException("Failed to delete the extension job:" + coloExpr); } } @Override public APIResult submitAndScheduleExtensionJob(String extensionName, String jobName, String configPath, String doAsUser) { InputStream configStream = getServletInputStream(configPath); try { SortedMap<EntityType, List<Entity>> entityMap = getEntityTypeListMap(extensionName, jobName, configStream); return localExtensionManager.submitAndSchedulableExtensionJob(extensionName, jobName, configStream, entityMap); } catch (FalconException | IOException e) { throw new FalconCLIException("Failed in submitting extension job " + jobName); } } @Override public APIResult updateExtensionJob(String jobName, String configPath, String doAsUser) { InputStream configStream = getServletInputStream(configPath); try { String extensionName = ExtensionStore.getMetaStore().getExtensionJobDetails(jobName).getExtensionName(); SortedMap<EntityType, List<Entity>> entityMap = getEntityTypeListMap(extensionName, jobName, configStream); return localExtensionManager.updateExtensionJob(extensionName, jobName, configStream, entityMap); } catch (FalconException | IOException e) { throw new FalconCLIException("Failed in updating the extension job:" + jobName); } } @Override public APIResult deleteExtensionJob(String jobName, String doAsUser) { try { return localExtensionManager.deleteExtensionJob(jobName); } catch (FalconException | IOException e) { throw new FalconCLIException("Failed to delete the extension job:" + jobName); } } @Override public APIResult suspendExtensionJob(String jobName, String coloExpr, String doAsUser) { try { return localExtensionManager.suspendExtensionJob(jobName, coloExpr, doAsUser); } catch (FalconException e) { throw new FalconCLIException("Failed in suspending the extension job:" + jobName); } } @Override public APIResult resumeExtensionJob(String jobName, String coloExpr, String doAsUser) { try { return localExtensionManager.resumeExtensionJob(jobName, coloExpr, doAsUser); } catch (FalconException e) { throw new FalconCLIException("Failed in resuming the extension job:" + jobName); } } @Override public APIResult getExtensionJobDetails(final String jobName) { return localExtensionManager.getExtensionJobDetails(jobName); } @Override public APIResult getExtensionDetail(String extensionName) { return localExtensionManager.getExtensionDetails(extensionName); } @Override public APIResult enumerateExtensions() { return localExtensionManager.getExtensions(); } @Override public ExtensionJobList getExtensionJobs(String extensionName, String sortOrder, String doAsUser) { return localExtensionManager.getExtensionJobs(extensionName, sortOrder, doAsUser); } @Override public EntityList getEntityList(String entityType, String fields, String nameSubsequence, String tagKeywords, String filterBy, String filterTags, String orderBy, String sortOrder, Integer offset, Integer numResults, String doAsUser) { return localSchedulableEntityManager.getEntityList(fields, nameSubsequence, tagKeywords, entityType, filterTags, filterBy, orderBy, sortOrder, offset, numResults, doAsUser); } @Override public EntitySummaryResult getEntitySummary(String entityType, String cluster, String start, String end, String fields, String filterBy, String filterTags, String orderBy, String sortOrder, Integer offset, Integer numResults, Integer numInstances, String doAsUser) { return localSchedulableEntityManager.getEntitySummary(entityType, cluster, start, end, fields, filterBy, filterTags, orderBy, sortOrder, offset, numResults, numInstances, doAsUser); } @Override public APIResult touch(String entityType, String entityName, String colo, Boolean skipDryRun, String doAsUser) { return localSchedulableEntityManager.touch(entityType, entityName, colo, skipDryRun); } public InstancesResult killInstances(String type, String entity, String start, String end, String colo, String clusters, String sourceClusters, List<LifeCycle> lifeCycles, String doAsUser) throws UnsupportedEncodingException { Properties props = getProperties(clusters, sourceClusters); return localInstanceManager.killInstance(props, type, entity, start, end, colo, lifeCycles); } public InstancesResult suspendInstances(String type, String entity, String start, String end, String colo, String clusters, String sourceClusters, List<LifeCycle> lifeCycles, String doAsUser) throws UnsupportedEncodingException { Properties props = getProperties(clusters, sourceClusters); return localInstanceManager.suspendInstance(props, type, entity, start, end, colo, lifeCycles); } public InstancesResult resumeInstances(String type, String entity, String start, String end, String colo, String clusters, String sourceClusters, List<LifeCycle> lifeCycles, String doAsUser) throws UnsupportedEncodingException { Properties props = getProperties(clusters, sourceClusters); return localInstanceManager.resumeInstance(props, type, entity, start, end, colo, lifeCycles); } public InstancesResult rerunInstances(String type, String entity, String start, String end, String filePath, String colo, String clusters, String sourceClusters, List<LifeCycle> lifeCycles, Boolean isForced, String doAsUser) throws IOException { Properties props = getProperties(clusters, sourceClusters); return localInstanceManager.reRunInstance(type, entity, start, end, props, colo, lifeCycles, isForced); } public InstancesSummaryResult getSummaryOfInstances(String type, String entity, String start, String end, String colo, List<LifeCycle> lifeCycles, String filterBy, String orderBy, String sortOrder, String doAsUser) { if (StringUtils.isBlank(orderBy)) { orderBy = DEFAULT_ORDER_BY; } if (StringUtils.isBlank(sortOrder)) { sortOrder = DEFAULT_SORTED_ORDER; } return localInstanceManager.getSummary(type, entity, start, end, colo, lifeCycles, filterBy, orderBy, sortOrder); } public FeedInstanceResult getFeedListing(String type, String entity, String start, String end, String colo, String doAsUser) { return localInstanceManager.getListing(type, entity, start, end, colo); } public InstancesResult getLogsOfInstances(String type, String entity, String start, String end, String colo, String runId, List<LifeCycle> lifeCycles, String filterBy, String orderBy, String sortOrder, Integer offset, Integer numResults, String doAsUser) { return localInstanceManager.getLogs(type, entity, start, end, colo, runId, lifeCycles, filterBy, orderBy, sortOrder, offset, numResults); } public InstancesResult getParamsOfInstance(String type, String entity, String start, String colo, List<LifeCycle> lifeCycles, String doAsUser) throws UnsupportedEncodingException { return localInstanceManager.getInstanceParams(type, entity, start, colo, lifeCycles); } //RESUME CHECKSTYLE CHECK ParameterNumberCheck public InstanceDependencyResult getInstanceDependencies(String entityType, String entityName, String instanceTime, String colo) { return localInstanceManager.getInstanceDependencies(entityType, entityName, instanceTime, colo); } @Override public String getVersion(String doAsUser) { AdminResource resource = new AdminResource(); AdminResource.PropertyList propertyList = resource.getVersion(); Map<String, String> version = new LinkedHashMap<>(); List<String> list = new ArrayList<>(); for (AdminResource.Property property : propertyList.properties) { Map<String, String> map = new LinkedHashMap<>(); map.put("key", property.key); map.put("value", property.value); list.add(JSONValue.toJSONString(map)); } version.put("properties", list.toString()); return version.toString(); } @Override public SchedulableEntityInstanceResult getFeedSlaMissPendingAlerts(String entityType, String entityName, String start, String end, String colo) { return null; } @Override public FeedLookupResult reverseLookUp(String entityType, String path, String doAs) { return null; } @Override public EntityList getDependency(String entityType, String entityName, String doAs) { return null; } @Override public TriageResult triage(String name, String entityName, String start, String colo) { return null; } // SUSPEND CHECKSTYLE CHECK ParameterNumberCheck @Override public InstancesResult getRunningInstances(String type, String entity, String colo, List<LifeCycle> lifeCycles, String filterBy, String orderBy, String sortOrder, Integer offset, Integer numResults, String doAsUser) { return null; } // RESUME CHECKSTYLE CHECK ParameterNumberCheck @Override public FeedInstanceResult getFeedInstanceListing(String type, String entity, String start, String end, String colo, String doAsUser) { return null; } @Override public int getStatus(String doAsUser) { return 200; } @Override public String getThreadDump(String doAs) { return ""; } @Override public LineageGraphResult getEntityLineageGraph(String pipeline, String doAs) { return null; } @Override public String getDimensionList(String dimensionType, String cluster, String doAs) { return null; } @Override public String getReplicationMetricsDimensionList(String schedEntityType, String schedEntityName, Integer numResults, String doAs) { return null; } @Override public String getDimensionRelations(String dimensionType, String dimensionName, String doAs) { return null; } @Override public String getVertex(String id, String doAs) { return null; } @Override public String getVertices(String key, String value, String doAs) { return null; } @Override public String getVertexEdges(String id, String direction, String doAs) { return null; } @Override public String getEdge(String id, String doAs) { return null; } private boolean checkAndUpdateCluster(Entity entity, EntityType entityType, String cluster) { if (entityType == EntityType.FEED) { return checkAndUpdateFeedClusters(entity, cluster); } else if (entityType == EntityType.PROCESS) { return checkAndUpdateProcessClusters(entity, cluster); } else { throw new IllegalArgumentException("entity type {} is not supported " + entityType); } } private boolean checkAndUpdateProcessClusters(Entity entity, String cluster) { Process processEntity = (Process) entity; List<Cluster> clusters = processEntity.getClusters().getClusters(); List<Cluster> newClusters = new ArrayList<>(); if (clusters != null) { for (Cluster processCluster : clusters) { if (processCluster.getName().equalsIgnoreCase(cluster)) { newClusters.add(processCluster); } } } if (newClusters.isEmpty()) { LOG.warn("Cluster is not registered with this entity " + entity.getName()); return false; } processEntity.getClusters().getClusters().removeAll(clusters); processEntity.getClusters().getClusters().addAll(newClusters); return true; } private boolean checkAndUpdateFeedClusters(Entity entity, String cluster) { Feed feedEntity = (Feed) entity; List<org.apache.falcon.entity.v0.feed.Cluster> clusters = feedEntity.getClusters().getClusters(); List<org.apache.falcon.entity.v0.feed.Cluster> newClusters = new ArrayList<>(); if (clusters != null) { for (org.apache.falcon.entity.v0.feed.Cluster feedClusters : clusters) { if (feedClusters.getName().equalsIgnoreCase(cluster)) { newClusters.add(feedClusters); } } } if (newClusters.isEmpty()) { LOG.warn("Cluster is not registered with this entity " + entity.getName()); return false; } feedEntity.getClusters().getClusters().removeAll(clusters); feedEntity.getClusters().getClusters().addAll(newClusters); return true; } private void updateStartAndEndTime(Process processEntity, String startTimeStr, int numInstances, String cluster) { List<Cluster> clusters = processEntity.getClusters().getClusters(); if (clusters != null) { for (Cluster processCluster : clusters) { if (processCluster.getName().equalsIgnoreCase(cluster)) { Validity validity = new Validity(); Date startTime = SchemaHelper.parseDateUTC(startTimeStr); validity.setStart(startTime); Date endTime = EntityUtil.getNextInstanceTime(startTime, processEntity.getFrequency(), TimeZone.getTimeZone("UTC"), numInstances); validity.setEnd(endTime); processCluster.setValidity(validity); } } } } private Properties getProperties(String clusters, String sourceClusters) { Properties props = new Properties(); if (StringUtils.isNotEmpty(clusters)) { props.setProperty(FALCON_INSTANCE_ACTION_CLUSTERS, clusters); } if (StringUtils.isNotEmpty(sourceClusters)) { props.setProperty(FALCON_INSTANCE_SOURCE_CLUSTERS, sourceClusters); } return props; } }