/** * 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.metadata; import com.tinkerpop.blueprints.Graph; import com.tinkerpop.blueprints.Vertex; import org.apache.falcon.entity.FeedHelper; import org.apache.falcon.entity.ProcessHelper; import org.apache.falcon.entity.v0.Entity; import org.apache.falcon.entity.v0.EntityType; import org.apache.falcon.entity.v0.cluster.Cluster; import org.apache.falcon.entity.v0.datasource.Datasource; import org.apache.falcon.entity.v0.feed.ClusterType; import org.apache.falcon.entity.v0.feed.Feed; import org.apache.falcon.entity.v0.process.Input; import org.apache.falcon.entity.v0.process.Inputs; import org.apache.falcon.entity.v0.process.Output; import org.apache.falcon.entity.v0.process.Outputs; import org.apache.falcon.entity.v0.process.Process; import org.apache.falcon.entity.v0.process.Workflow; import org.apache.falcon.workflow.WorkflowExecutionArgs; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; /** * Entity Metadata relationship mapping helper. */ public class EntityRelationshipGraphBuilder extends RelationshipGraphBuilder { private static final Logger LOG = LoggerFactory.getLogger(EntityRelationshipGraphBuilder.class); public EntityRelationshipGraphBuilder(Graph graph, boolean preserveHistory) { super(graph, preserveHistory); } public void addEntity(Entity entity) { EntityType entityType = entity.getEntityType(); switch (entityType) { case CLUSTER: addClusterEntity((Cluster) entity); break; case PROCESS: addProcessEntity((Process) entity); break; case FEED: addFeedEntity((Feed) entity); break; case DATASOURCE: addDatasourceEntity((Datasource) entity); break; default: throw new IllegalArgumentException("Invalid EntityType " + entityType); } } public void addClusterEntity(Cluster clusterEntity) { LOG.info("Adding cluster entity: {}", clusterEntity.getName()); Vertex clusterVertex = addVertex(clusterEntity.getName(), RelationshipType.CLUSTER_ENTITY); addUserRelation(clusterVertex); addColoRelation(clusterEntity.getColo(), clusterVertex, RelationshipLabel.CLUSTER_COLO); addDataClassification(clusterEntity.getTags(), clusterVertex); } public void addFeedEntity(Feed feed) { LOG.info("Adding feed entity: {}", feed.getName()); Vertex feedVertex = addVertex(feed.getName(), RelationshipType.FEED_ENTITY); addUserRelation(feedVertex); addDataClassification(feed.getTags(), feedVertex); addGroups(feed.getGroups(), feedVertex); for (org.apache.falcon.entity.v0.feed.Cluster feedCluster : feed.getClusters().getClusters()) { if (ClusterType.TARGET != feedCluster.getType()) { addRelationToCluster(feedVertex, feedCluster.getName(), RelationshipLabel.FEED_CLUSTER_EDGE); } } for (org.apache.falcon.entity.v0.feed.Cluster feedCluster : feed.getClusters().getClusters()) { if (FeedHelper.isImportEnabled(feedCluster)) { addRelationToDatasource(feedVertex, FeedHelper.getImportDatasourceName(feedCluster), RelationshipLabel.DATASOURCE_IMPORT_EDGE); } } } public void addDatasourceEntity(Datasource dsEntity) { LOG.info("Adding datasource entity: {}", dsEntity.getName()); Vertex dsVertex = addVertex(dsEntity.getName(), RelationshipType.DATASOURCE_ENTITY); addUserRelation(dsVertex); addColoRelation(dsEntity.getColo(), dsVertex, RelationshipLabel.DATASOURCE_COLO); addDataClassification(dsEntity.getTags(), dsVertex); } public void updateEntity(Entity oldEntity, Entity newEntity) { EntityType entityType = oldEntity.getEntityType(); switch (entityType) { case CLUSTER: updateClusterEntity((Cluster) oldEntity, (Cluster) newEntity); break; case PROCESS: updateProcessEntity((Process) oldEntity, (Process) newEntity); break; case FEED: updateFeedEntity((Feed) oldEntity, (Feed) newEntity); break; case DATASOURCE: updateDatasourceEntity((Datasource) oldEntity, (Datasource) newEntity); break; default: throw new IllegalArgumentException("Invalid EntityType " + entityType); } } private void updateDatasourceEntity(Datasource oldDatasource, Datasource newDatasouce) { LOG.info("Updating Datasource entity: {}", newDatasouce.getName()); Vertex dsEntityVertex = findVertex(oldDatasource.getName(), RelationshipType.DATASOURCE_ENTITY); if (dsEntityVertex == null) { LOG.error("Illegal State: Datasource entity vertex must exist for {}", oldDatasource.getName()); throw new IllegalStateException(oldDatasource.getName() + " entity vertex must exist."); } updateColoEdge(oldDatasource.getColo(), newDatasouce.getColo(), dsEntityVertex, RelationshipLabel.DATASOURCE_COLO); updateDataClassification(oldDatasource.getTags(), newDatasouce.getTags(), dsEntityVertex); } private void updateClusterEntity(Cluster oldCluster, Cluster newCluster) { LOG.info("Updating Cluster entity: {}", newCluster.getName()); Vertex clusterEntityVertex = findVertex(oldCluster.getName(), RelationshipType.CLUSTER_ENTITY); if (clusterEntityVertex == null) { LOG.error("Illegal State: Cluster entity vertex must exist for {}", oldCluster.getName()); throw new IllegalStateException(oldCluster.getName() + " entity vertex must exist."); } updateColoEdge(oldCluster.getColo(), newCluster.getColo(), clusterEntityVertex, RelationshipLabel.CLUSTER_COLO); updateDataClassification(oldCluster.getTags(), newCluster.getTags(), clusterEntityVertex); } private void updateColoEdge(String oldColo, String newColo, Vertex clusterEntityVertex, RelationshipLabel relLabel) { if (areSame(oldColo, newColo)) { return; } Vertex oldColoVertex = findVertex(oldColo, RelationshipType.COLO); if (oldColoVertex != null) { removeEdge(clusterEntityVertex, oldColoVertex, relLabel.getName()); } Vertex newColoVertex = findVertex(newColo, RelationshipType.COLO); if (newColoVertex == null) { newColoVertex = addVertex(newColo, RelationshipType.COLO); } addEdge(clusterEntityVertex, newColoVertex, relLabel.getName()); } public void updateFeedEntity(Feed oldFeed, Feed newFeed) { LOG.info("Updating feed entity: {}", newFeed.getName()); Vertex feedEntityVertex = findVertex(oldFeed.getName(), RelationshipType.FEED_ENTITY); if (feedEntityVertex == null) { LOG.error("Illegal State: Feed entity vertex must exist for {}", oldFeed.getName()); throw new IllegalStateException(oldFeed.getName() + " entity vertex must exist."); } updateDataClassification(oldFeed.getTags(), newFeed.getTags(), feedEntityVertex); updateGroups(oldFeed.getGroups(), newFeed.getGroups(), feedEntityVertex); updateFeedClusters(oldFeed.getClusters().getClusters(), newFeed.getClusters().getClusters(), feedEntityVertex); } public void addProcessEntity(Process process) { String processName = process.getName(); LOG.info("Adding process entity: {}", processName); Vertex processVertex = addVertex(processName, RelationshipType.PROCESS_ENTITY); addWorkflowProperties(process.getWorkflow(), processVertex, processName); addUserRelation(processVertex); addDataClassification(process.getTags(), processVertex); addPipelines(process.getPipelines(), processVertex); for (org.apache.falcon.entity.v0.process.Cluster cluster : process.getClusters().getClusters()) { addRelationToCluster(processVertex, cluster.getName(), RelationshipLabel.PROCESS_CLUSTER_EDGE); } addInputFeeds(process.getInputs(), processVertex); addOutputFeeds(process.getOutputs(), processVertex); } public void updateProcessEntity(Process oldProcess, Process newProcess) { LOG.info("Updating process entity: {}", newProcess.getName()); Vertex processEntityVertex = findVertex(oldProcess.getName(), RelationshipType.PROCESS_ENTITY); if (processEntityVertex == null) { LOG.error("Illegal State: Process entity vertex must exist for {}", oldProcess.getName()); throw new IllegalStateException(oldProcess.getName() + " entity vertex must exist"); } updateWorkflowProperties(oldProcess.getWorkflow(), newProcess.getWorkflow(), processEntityVertex, newProcess.getName()); updateDataClassification(oldProcess.getTags(), newProcess.getTags(), processEntityVertex); updatePipelines(oldProcess.getPipelines(), newProcess.getPipelines(), processEntityVertex); updateProcessClusters(oldProcess.getClusters().getClusters(), newProcess.getClusters().getClusters(), processEntityVertex); updateProcessInputs(oldProcess.getInputs(), newProcess.getInputs(), processEntityVertex); updateProcessOutputs(oldProcess.getOutputs(), newProcess.getOutputs(), processEntityVertex); } public void addColoRelation(String colo, Vertex fromVertex, RelationshipLabel relLabel) { Vertex coloVertex = addVertex(colo, RelationshipType.COLO); addEdge(fromVertex, coloVertex, relLabel.getName()); } public void addRelationToCluster(Vertex fromVertex, String clusterName, RelationshipLabel edgeLabel) { Vertex clusterVertex = findVertex(clusterName, RelationshipType.CLUSTER_ENTITY); if (clusterVertex == null) { // cluster must exist before adding other entities LOG.error("Illegal State: Cluster entity vertex must exist for {}", clusterName); throw new IllegalStateException("Cluster entity vertex must exist: " + clusterName); } addEdge(fromVertex, clusterVertex, edgeLabel.getName()); } public void addRelationToDatasource(Vertex fromVertex, String datasourceName, RelationshipLabel edgeLabel) { Vertex clusterVertex = findVertex(datasourceName, RelationshipType.DATASOURCE_ENTITY); if (clusterVertex == null) { // cluster must exist before adding other entities LOG.error("Illegal State: Datasource entity vertex must exist for {}", datasourceName); throw new IllegalStateException("Datasource entity vertex must exist: " + datasourceName); } addEdge(fromVertex, clusterVertex, edgeLabel.getName()); } public void addInputFeeds(Inputs inputs, Vertex processVertex) { if (inputs == null) { return; } for (Input input : inputs.getInputs()) { addProcessFeedEdge(processVertex, input.getFeed(), RelationshipLabel.FEED_PROCESS_EDGE); } } public void addOutputFeeds(Outputs outputs, Vertex processVertex) { if (outputs == null) { return; } for (Output output : outputs.getOutputs()) { addProcessFeedEdge(processVertex, output.getFeed(), RelationshipLabel.PROCESS_FEED_EDGE); } } public void addProcessFeedEdge(Vertex processVertex, String feedName, RelationshipLabel edgeLabel) { Vertex feedVertex = findVertex(feedName, RelationshipType.FEED_ENTITY); if (feedVertex == null) { LOG.error("Illegal State: Feed entity vertex must exist for {}", feedName); throw new IllegalStateException("Feed entity vertex must exist: " + feedName); } addProcessFeedEdge(processVertex, feedVertex, edgeLabel); } public void addWorkflowProperties(Workflow workflow, Vertex processVertex, String processName) { processVertex.setProperty(WorkflowExecutionArgs.USER_WORKFLOW_NAME.getName(), ProcessHelper.getProcessWorkflowName(workflow.getName(), processName)); processVertex.setProperty(RelationshipProperty.VERSION.getName(), workflow.getVersion()); processVertex.setProperty(WorkflowExecutionArgs.USER_WORKFLOW_ENGINE.getName(), workflow.getEngine().value()); } public void updateWorkflowProperties(Workflow oldWorkflow, Workflow newWorkflow, Vertex processEntityVertex, String processName) { if (areSame(oldWorkflow, newWorkflow)) { return; } LOG.info("Updating workflow properties for: {}", processEntityVertex); addWorkflowProperties(newWorkflow, processEntityVertex, processName); } public void updateDataClassification(String oldClassification, String newClassification, Vertex entityVertex) { if (areSame(oldClassification, newClassification)) { return; } removeDataClassification(oldClassification, entityVertex); addDataClassification(newClassification, entityVertex); } private void removeDataClassification(String classification, Vertex entityVertex) { if (classification == null || classification.length() == 0) { return; } String[] oldTags = classification.split(","); for (String oldTag : oldTags) { int index = oldTag.indexOf("="); String tagKey = oldTag.substring(0, index); String tagValue = oldTag.substring(index + 1, oldTag.length()); removeEdge(entityVertex, tagValue, tagKey); } } public void updateGroups(String oldGroups, String newGroups, Vertex entityVertex) { if (areSame(oldGroups, newGroups)) { return; } removeGroups(oldGroups, entityVertex); addGroups(newGroups, entityVertex); } public void updatePipelines(String oldPipelines, String newPipelines, Vertex entityVertex) { if (areSame(oldPipelines, newPipelines)) { return; } removePipelines(oldPipelines, entityVertex); addPipelines(newPipelines, entityVertex); } private void removeGroups(String groups, Vertex entityVertex) { removeGroupsOrPipelines(groups, entityVertex, RelationshipLabel.GROUPS); } private void removePipelines(String pipelines, Vertex entityVertex) { removeGroupsOrPipelines(pipelines, entityVertex, RelationshipLabel.PIPELINES); } private void removeGroupsOrPipelines(String groupsOrPipelines, Vertex entityVertex, RelationshipLabel edgeLabel) { if (StringUtils.isEmpty(groupsOrPipelines)) { return; } String[] oldGroupOrPipelinesTags = groupsOrPipelines.split(","); for (String groupOrPipelineTag : oldGroupOrPipelinesTags) { removeEdge(entityVertex, groupOrPipelineTag, edgeLabel.getName()); } } public static boolean areSame(String oldValue, String newValue) { return oldValue == null && newValue == null || oldValue != null && newValue != null && oldValue.equals(newValue); } public void updateFeedClusters(List<org.apache.falcon.entity.v0.feed.Cluster> oldClusters, List<org.apache.falcon.entity.v0.feed.Cluster> newClusters, Vertex feedEntityVertex) { if (areFeedClustersSame(oldClusters, newClusters)) { return; } // remove edges to old clusters for (org.apache.falcon.entity.v0.feed.Cluster oldCuster : oldClusters) { if (ClusterType.TARGET != oldCuster.getType()) { removeEdge(feedEntityVertex, oldCuster.getName(), RelationshipLabel.FEED_CLUSTER_EDGE.getName()); } } // add edges to new clusters for (org.apache.falcon.entity.v0.feed.Cluster newCluster : newClusters) { if (ClusterType.TARGET != newCluster.getType()) { addRelationToCluster(feedEntityVertex, newCluster.getName(), RelationshipLabel.FEED_CLUSTER_EDGE); } } } public boolean areFeedClustersSame(List<org.apache.falcon.entity.v0.feed.Cluster> oldClusters, List<org.apache.falcon.entity.v0.feed.Cluster> newClusters) { if (oldClusters.size() != newClusters.size()) { return false; } List<String> oldClusterNames = getFeedClusterNames(oldClusters); List<String> newClusterNames = getFeedClusterNames(newClusters); return oldClusterNames.size() == newClusterNames.size() && oldClusterNames.containsAll(newClusterNames) && newClusterNames.containsAll(oldClusterNames); } public List<String> getFeedClusterNames(List<org.apache.falcon.entity.v0.feed.Cluster> clusters) { List<String> clusterNames = new ArrayList<String>(clusters.size()); for (org.apache.falcon.entity.v0.feed.Cluster cluster : clusters) { clusterNames.add(cluster.getName()); } return clusterNames; } public void updateProcessClusters(List<org.apache.falcon.entity.v0.process.Cluster> oldClusters, List<org.apache.falcon.entity.v0.process.Cluster> newClusters, Vertex processEntityVertex) { if (areProcessClustersSame(oldClusters, newClusters)) { return; } // remove old clusters for (org.apache.falcon.entity.v0.process.Cluster oldCuster : oldClusters) { removeEdge(processEntityVertex, oldCuster.getName(), RelationshipLabel.PROCESS_CLUSTER_EDGE.getName()); } // add new clusters for (org.apache.falcon.entity.v0.process.Cluster newCluster : newClusters) { addRelationToCluster(processEntityVertex, newCluster.getName(), RelationshipLabel.PROCESS_CLUSTER_EDGE); } } public boolean areProcessClustersSame(List<org.apache.falcon.entity.v0.process.Cluster> oldClusters, List<org.apache.falcon.entity.v0.process.Cluster> newClusters) { if (oldClusters.size() != newClusters.size()) { return false; } List<String> oldClusterNames = getProcessClusterNames(oldClusters); List<String> newClusterNames = getProcessClusterNames(newClusters); return oldClusterNames.size() == newClusterNames.size() && oldClusterNames.containsAll(newClusterNames) && newClusterNames.containsAll(oldClusterNames); } public List<String> getProcessClusterNames(List<org.apache.falcon.entity.v0.process.Cluster> clusters) { List<String> clusterNames = new ArrayList<String>(clusters.size()); for (org.apache.falcon.entity.v0.process.Cluster cluster : clusters) { clusterNames.add(cluster.getName()); } return clusterNames; } public static boolean areSame(Workflow oldWorkflow, Workflow newWorkflow) { return areSame(oldWorkflow.getName(), newWorkflow.getName()) && areSame(oldWorkflow.getVersion(), newWorkflow.getVersion()) && areSame(oldWorkflow.getEngine().value(), newWorkflow.getEngine().value()); } private void updateProcessInputs(Inputs oldProcessInputs, Inputs newProcessInputs, Vertex processEntityVertex) { if (areSame(oldProcessInputs, newProcessInputs)) { return; } removeInputFeeds(oldProcessInputs, processEntityVertex); addInputFeeds(newProcessInputs, processEntityVertex); } public static boolean areSame(Inputs oldProcessInputs, Inputs newProcessInputs) { if (oldProcessInputs == null && newProcessInputs == null) { return true; } if (oldProcessInputs == null || newProcessInputs == null || oldProcessInputs.getInputs().size() != newProcessInputs.getInputs().size()) { return false; } List<Input> oldInputs = oldProcessInputs.getInputs(); List<Input> newInputs = newProcessInputs.getInputs(); return oldInputs.size() == newInputs.size() && oldInputs.containsAll(newInputs) && newInputs.containsAll(oldInputs); } public void removeInputFeeds(Inputs inputs, Vertex processVertex) { if (inputs == null) { return; } for (Input input : inputs.getInputs()) { removeProcessFeedEdge(processVertex, input.getFeed(), RelationshipLabel.FEED_PROCESS_EDGE); } } public void removeOutputFeeds(Outputs outputs, Vertex processVertex) { if (outputs == null) { return; } for (Output output : outputs.getOutputs()) { removeProcessFeedEdge(processVertex, output.getFeed(), RelationshipLabel.PROCESS_FEED_EDGE); } } public void removeProcessFeedEdge(Vertex processVertex, String feedName, RelationshipLabel edgeLabel) { Vertex feedVertex = findVertex(feedName, RelationshipType.FEED_ENTITY); if (feedVertex == null) { LOG.error("Illegal State: Feed entity vertex must exist for {}", feedName); throw new IllegalStateException("Feed entity vertex must exist: " + feedName); } if (edgeLabel == RelationshipLabel.FEED_PROCESS_EDGE) { removeEdge(feedVertex, processVertex, edgeLabel.getName()); } else { removeEdge(processVertex, feedVertex, edgeLabel.getName()); } } private void updateProcessOutputs(Outputs oldProcessOutputs, Outputs newProcessOutputs, Vertex processEntityVertex) { if (areSame(oldProcessOutputs, newProcessOutputs)) { return; } removeOutputFeeds(oldProcessOutputs, processEntityVertex); addOutputFeeds(newProcessOutputs, processEntityVertex); } public static boolean areSame(Outputs oldProcessOutputs, Outputs newProcessOutputs) { if (oldProcessOutputs == null && newProcessOutputs == null) { return true; } if (oldProcessOutputs == null || newProcessOutputs == null || oldProcessOutputs.getOutputs().size() != newProcessOutputs.getOutputs().size()) { return false; } List<Output> oldOutputs = oldProcessOutputs.getOutputs(); List<Output> newOutputs = newProcessOutputs.getOutputs(); return oldOutputs.size() == newOutputs.size() && oldOutputs.containsAll(newOutputs) && newOutputs.containsAll(oldOutputs); } }