This is an automated email from the ASF dual-hosted git repository.

chaitalicod pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/atlas.git


The following commit(s) were added to refs/heads/master by this push:
     new df4d824878 ATLAS-5375: Fix stale PUT response for 
/api/atlas/v2/relationship block/unblock propagation (#729)
df4d824878 is described below

commit df4d8248780a2d1d516400603166805fa645d7fb
Author: sheetalshah1007 <[email protected]>
AuthorDate: Wed Aug 26 12:12:41 2026 +0530

    ATLAS-5375: Fix stale PUT response for /api/atlas/v2/relationship 
block/unblock propagation (#729)
    
    * ATLAS-5375: Fix stale PUT response for /api/atlas/v2/relationship 
block/unblock propagation
    
    * ATLAS-5375: Review fixes — Step 4 comment and null guard for task queue
---
 .../atlas/model/instance/AtlasRelationship.java    |  14 +
 .../apache/atlas/repository/graph/GraphHelper.java |  10 +-
 .../repository/store/graph/v1/DeleteHandlerV1.java | 142 +++++---
 .../store/graph/v2/AtlasGraphUtilsV2.java          |  20 +-
 .../store/graph/v2/AtlasRelationshipStoreV2.java   |  13 +-
 .../store/graph/v2/EntityGraphMapper.java          |   4 +-
 .../store/graph/v2/EntityGraphRetriever.java       |   9 +-
 .../v2/tasks/ClassificationPropagationTasks.java   |   8 +-
 .../store/graph/v2/tasks/ClassificationTask.java   |  13 +
 .../tasks/ClassificationPropagationTasksTest.java  |  10 +-
 .../graph/v2/tasks/ClassificationTaskTest.java     |   2 +-
 .../ClassificationPropagationWithTasksTest.java    | 367 ++++++++++++++++++++-
 12 files changed, 532 insertions(+), 80 deletions(-)

diff --git 
a/intg/src/main/java/org/apache/atlas/model/instance/AtlasRelationship.java 
b/intg/src/main/java/org/apache/atlas/model/instance/AtlasRelationship.java
index 87ebbb871e..2bb8ade1b8 100644
--- a/intg/src/main/java/org/apache/atlas/model/instance/AtlasRelationship.java
+++ b/intg/src/main/java/org/apache/atlas/model/instance/AtlasRelationship.java
@@ -87,6 +87,7 @@ public class AtlasRelationship extends AtlasStruct implements 
Serializable {
     private Long          version;
     private Set<AtlasClassification> propagatedClassifications;
     private Set<AtlasClassification> blockedPropagatedClassifications;
+    private Set<String>              pendingTasks; // read-only field i.e. 
value provided is ignored during relationship create/update
 
     public AtlasRelationship() {
         super();
@@ -239,6 +240,7 @@ public class AtlasRelationship extends AtlasStruct 
implements Serializable {
         if (other != null) {
             init(other.guid, other.homeId, other.provenanceType, other.end1, 
other.end2, other.label, other.propagateTags, other.status, other.createdBy, 
other.updatedBy,
                     other.createTime, other.updateTime, other.version, 
other.propagatedClassifications, other.blockedPropagatedClassifications);
+            setPendingTasks(other.getPendingTasks());
         }
     }
 
@@ -362,6 +364,14 @@ public class AtlasRelationship extends AtlasStruct 
implements Serializable {
         this.blockedPropagatedClassifications = 
blockedPropagatedClassifications;
     }
 
+    public Set<String> getPendingTasks() {
+        return pendingTasks;
+    }
+
+    public void setPendingTasks(Set<String> pendingTasks) {
+        this.pendingTasks = pendingTasks;
+    }
+
     @Override
     public StringBuilder toString(StringBuilder sb) {
         if (sb == null) {
@@ -389,6 +399,9 @@ public class AtlasRelationship extends AtlasStruct 
implements Serializable {
         sb.append(", blockedPropagatedClassifications=[");
         dumpObjects(blockedPropagatedClassifications, sb);
         sb.append("]");
+        sb.append(", pendingTasks=[");
+        dumpObjects(pendingTasks, sb);
+        sb.append("]");
         sb.append('}');
 
         return sb;
@@ -460,6 +473,7 @@ public class AtlasRelationship extends AtlasStruct 
implements Serializable {
         setVersion(version);
         setPropagatedClassifications(propagatedClassifications);
         setBlockedPropagatedClassifications(blockedPropagatedClassifications);
+        setPendingTasks(null);
     }
 
     public enum Status { ACTIVE, DELETED }
diff --git 
a/repository/src/main/java/org/apache/atlas/repository/graph/GraphHelper.java 
b/repository/src/main/java/org/apache/atlas/repository/graph/GraphHelper.java
index 303a9f520f..0e5f71c0c1 100755
--- 
a/repository/src/main/java/org/apache/atlas/repository/graph/GraphHelper.java
+++ 
b/repository/src/main/java/org/apache/atlas/repository/graph/GraphHelper.java
@@ -428,10 +428,16 @@ public final class GraphHelper {
     }
 
     public static List<AtlasVertex> getPropagatableClassifications(AtlasEdge 
edge) {
+        if (edge != null && getStatus(edge) != DELETED) {
+            return getPropagatableClassifications(edge, 
getPropagateTags(edge));
+        }
+        return new ArrayList<>();
+    }
+
+    public static List<AtlasVertex> getPropagatableClassifications(AtlasEdge 
edge, PropagateTags propagateTags) {
         List<AtlasVertex> ret = new ArrayList<>();
 
-        if (edge != null && getStatus(edge) != DELETED) {
-            PropagateTags propagateTags = getPropagateTags(edge);
+        if (edge != null && getStatus(edge) != DELETED && propagateTags != 
null) {
             AtlasVertex   outVertex     = edge.getOutVertex();
             AtlasVertex   inVertex      = edge.getInVertex();
 
diff --git 
a/repository/src/main/java/org/apache/atlas/repository/store/graph/v1/DeleteHandlerV1.java
 
b/repository/src/main/java/org/apache/atlas/repository/store/graph/v1/DeleteHandlerV1.java
index fece9705b5..3844b24dd7 100644
--- 
a/repository/src/main/java/org/apache/atlas/repository/store/graph/v1/DeleteHandlerV1.java
+++ 
b/repository/src/main/java/org/apache/atlas/repository/store/graph/v1/DeleteHandlerV1.java
@@ -125,6 +125,7 @@ import static 
org.apache.atlas.repository.store.graph.v2.AtlasGraphUtilsV2.getSt
 import static 
org.apache.atlas.repository.store.graph.v2.AtlasGraphUtilsV2.isReference;
 import static 
org.apache.atlas.repository.store.graph.v2.tasks.ClassificationPropagateTaskFactory.CLASSIFICATION_PROPAGATION_ADD;
 import static 
org.apache.atlas.repository.store.graph.v2.tasks.ClassificationPropagateTaskFactory.CLASSIFICATION_PROPAGATION_DELETE;
+import static 
org.apache.atlas.repository.store.graph.v2.tasks.ClassificationPropagateTaskFactory.CLASSIFICATION_PROPAGATION_RELATIONSHIP_UPDATE;
 import static 
org.apache.atlas.type.AtlasStructType.AtlasAttribute.AtlasRelationshipEdgeDirection.OUT;
 import static org.apache.atlas.type.Constants.PENDING_TASKS_PROPERTY_KEY;
 
@@ -950,59 +951,107 @@ public abstract class DeleteHandlerV1 {
     }
 
     public void updateTagPropagations(AtlasEdge edge, AtlasRelationship 
relationship) throws AtlasBaseException {
-        PropagateTags oldTagPropagation = getPropagateTags(edge);
-        PropagateTags newTagPropagation = relationship.getPropagateTags();
+        updateTagPropagations(edge, relationship, false, null, null);
+    }
 
-        if (newTagPropagation != oldTagPropagation) {
-            List<AtlasVertex>                   currentClassificationVertices 
= getPropagatableClassifications(edge);
-            Map<AtlasVertex, List<AtlasVertex>> currentClassificationsMap     
= 
entityRetriever.getClassificationPropagatedEntitiesMapping(currentClassificationVertices);
+    public void updateTagPropagations(AtlasEdge edge, AtlasRelationship 
relationship, boolean isAsyncExecution, String oldTagPropStr, 
java.util.List<String> oldBlockedList) throws AtlasBaseException {
+        // isAsyncExecution=false: relationship update — write edge, queue 
background task (or update entities inline if tasks disabled).
+        // isAsyncExecution=true:  background task — edge already updated; 
update entity tags using old state from task-params (oldTagPropStr / 
oldBlockedList).
+        boolean isLegacyTask = isAsyncExecution && oldTagPropStr == null;
 
-            // Update propagation edge
-            AtlasGraphUtilsV2.setEncodedProperty(edge, 
RELATIONSHIPTYPE_TAG_PROPAGATION_KEY, newTagPropagation.name());
+        // Step 1: Capture the edge's propagation settings before the update 
(propagateTags and blocked IDs).
+        // Read from the graph on a relationship update, or from task params 
on a background task.
+        PropagateTags oldTagPropagation = isAsyncExecution && !isLegacyTask ? 
PropagateTags.valueOf(oldTagPropStr) : getPropagateTags(edge);
+        List<String>  oldBlocked        = isAsyncExecution && !isLegacyTask ? 
oldBlockedList : getBlockedClassificationIds(edge);
 
-            List<AtlasVertex>                   updatedClassificationVertices 
= getPropagatableClassifications(edge);
-            List<AtlasVertex>                   classificationVerticesUnion   
= (List<AtlasVertex>) CollectionUtils.union(currentClassificationVertices, 
updatedClassificationVertices);
-            Map<AtlasVertex, List<AtlasVertex>> updatedClassificationsMap     
= 
entityRetriever.getClassificationPropagatedEntitiesMapping(classificationVerticesUnion);
+        if (oldBlocked == null) {
+            oldBlocked = Collections.emptyList();
+        }
 
-            // compute add/remove propagations list
-            Map<AtlasVertex, List<AtlasVertex>> addPropagationsMap    = new 
HashMap<>();
-            Map<AtlasVertex, List<AtlasVertex>> removePropagationsMap = new 
HashMap<>();
+        PropagateTags newTagPropagation = relationship.getPropagateTags();
+        boolean       propagationChanged = oldTagPropagation != 
newTagPropagation;
 
-            if (MapUtils.isEmpty(currentClassificationsMap) && 
MapUtils.isNotEmpty(updatedClassificationsMap)) {
-                addPropagationsMap.putAll(updatedClassificationsMap);
-            } else if (MapUtils.isNotEmpty(currentClassificationsMap) && 
MapUtils.isEmpty(updatedClassificationsMap)) {
-                removePropagationsMap.putAll(currentClassificationsMap);
-            } else {
-                for (AtlasVertex classificationVertex : 
updatedClassificationsMap.keySet()) {
-                    List<AtlasVertex> currentPropagatingEntities = 
currentClassificationsMap.getOrDefault(classificationVertex, 
Collections.emptyList());
-                    List<AtlasVertex> updatedPropagatingEntities = 
updatedClassificationsMap.getOrDefault(classificationVertex, 
Collections.emptyList());
-                    List<AtlasVertex> entitiesAdded              = 
(List<AtlasVertex>) CollectionUtils.subtract(updatedPropagatingEntities, 
currentPropagatingEntities);
-                    List<AtlasVertex> entitiesRemoved            = 
(List<AtlasVertex>) CollectionUtils.subtract(currentPropagatingEntities, 
updatedPropagatingEntities);
-
-                    if (CollectionUtils.isNotEmpty(entitiesAdded)) {
-                        addPropagationsMap.put(classificationVertex, 
entitiesAdded);
-                    }
+        // Step 2: When the client sends blockedPropagatedClassifications; 
validate those tags against
+        // propagatable classifications, compare to the pre-update blocked 
list, and set blockedChanged plus the
+        // vertex lists needed for edge write (Step 4) and entity updates 
(Step 6). Null field = omit, preserve existing blocks.
+        List<AtlasVertex> classificationsToBlock   = new ArrayList<>();
+        List<String>      classificationIdsToBlock = new ArrayList<>();
 
-                    if (CollectionUtils.isNotEmpty(entitiesRemoved)) {
-                        removePropagationsMap.put(classificationVertex, 
entitiesRemoved);
-                    }
+        Set<AtlasClassification> newBlockedClassifications = 
relationship.getBlockedPropagatedClassifications();
+        boolean                  blockedChanged            = false;
+        List<AtlasVertex>        propagatableClassifications = null;
+        List<AtlasVertex>        currBlockedClassifications  = 
Collections.emptyList();
+
+        if (newBlockedClassifications != null) {
+            propagatableClassifications = getPropagatableClassifications(edge, 
oldTagPropagation);
+
+            for (AtlasClassification blockedClassification : 
newBlockedClassifications) {
+                AtlasVertex classificationVertex = 
validateBlockedPropagatedClassification(propagatableClassifications, 
blockedClassification);
+
+                if (classificationVertex != null) {
+                    classificationsToBlock.add(classificationVertex);
+                    
classificationIdsToBlock.add(classificationVertex.getIdForDisplay());
                 }
             }
 
-            for (AtlasVertex classificationVertex : 
addPropagationsMap.keySet()) {
-                List<AtlasVertex> entitiesToAddPropagation = 
addPropagationsMap.get(classificationVertex);
+            blockedChanged = !CollectionUtils.isEqualCollection(oldBlocked, 
classificationIdsToBlock);
 
-                addTagPropagation(classificationVertex, 
entitiesToAddPropagation);
+            if (blockedChanged) {
+                currBlockedClassifications = 
getVerticesForIds(propagatableClassifications, oldBlocked);
             }
+        }
 
-            for (AtlasVertex classificationVertex : 
removePropagationsMap.keySet()) {
-                List<AtlasVertex> entitiesToRemovePropagation = 
removePropagationsMap.get(classificationVertex);
+        // Step 3: No-op — nothing changed; skip edge write, task queue, and 
entity propagation.
+        if (!propagationChanged && !blockedChanged) {
+            return;
+        }
 
-                removeTagPropagation(classificationVertex, 
entitiesToRemovePropagation);
+        // Step 4: Write propagateTags or blocked list to the edge (not both; 
propagateTags wins if both changed). Skipped on background task unless legacy.
+        if (!isAsyncExecution || isLegacyTask) {
+            if (propagationChanged) {
+                AtlasGraphUtilsV2.setEncodedProperty(edge, 
RELATIONSHIPTYPE_TAG_PROPAGATION_KEY, newTagPropagation.name());
+            } else if (blockedChanged) {
+                setBlockedClassificationIds(edge, classificationIdsToBlock);
+            }
+
+            // Step 5: Tasks enabled — edge written above; defer entity tags 
to background task with pre-change state.
+            if (!isAsyncExecution && DEFERRED_ACTION_ENABLED) {
+                
createAndQueueTask(CLASSIFICATION_PROPAGATION_RELATIONSHIP_UPDATE, edge, 
relationship,
+                        oldTagPropagation != null ? oldTagPropagation.name() : 
null, oldBlocked);
+                return;
+            }
+        }
+
+        // Step 6: Add/remove propagated tags on downstream entities. — inline 
when tasks disabled, or in the background task.
+        List<AtlasVertex> affectedClassifications = new ArrayList<>();
+
+        if (propagationChanged) {
+            if (propagatableClassifications == null) {
+                propagatableClassifications = 
getPropagatableClassifications(edge, oldTagPropagation);
+            }
+
+            affectedClassifications.addAll(propagatableClassifications);
+            
affectedClassifications.addAll(getPropagatableClassifications(edge, 
newTagPropagation));
+        } else if (blockedChanged) {
+            List<AtlasVertex> blockedDiff = (List<AtlasVertex>) 
CollectionUtils.disjunction(currBlockedClassifications, classificationsToBlock);
+            affectedClassifications.addAll(blockedDiff);
+        }
+
+        Set<AtlasVertex> uniqueAffectedClassifications = new 
HashSet<>(affectedClassifications);
+
+        for (AtlasVertex classificationVertex : uniqueAffectedClassifications) 
{
+            List<AtlasVertex> propagationsToRemove = new ArrayList<>();
+            List<AtlasVertex> propagationsToAdd    = new ArrayList<>();
+
+            
entityRetriever.evaluateClassificationPropagation(classificationVertex, 
propagationsToAdd, propagationsToRemove);
+
+            if (CollectionUtils.isNotEmpty(propagationsToAdd)) {
+                addTagPropagation(classificationVertex, propagationsToAdd);
+            }
+
+            if (CollectionUtils.isNotEmpty(propagationsToRemove)) {
+                removeTagPropagation(classificationVertex, 
propagationsToRemove);
             }
-        } else {
-            // update blocked propagated classifications only if there is no 
change is tag propagation (don't update both)
-            handleBlockedClassifications(edge, 
relationship.getBlockedPropagatedClassifications());
         }
     }
 
@@ -1056,9 +1105,13 @@ public abstract class DeleteHandlerV1 {
     }
 
     public void createAndQueueTask(String taskType, AtlasEdge 
relationshipEdge, AtlasRelationship relationship) {
+        createAndQueueTask(taskType, relationshipEdge, relationship, null, 
null);
+    }
+
+    public void createAndQueueTask(String taskType, AtlasEdge 
relationshipEdge, AtlasRelationship relationship, String oldTagPropagation, 
java.util.List<String> oldBlockedClassifications) {
         String              currentUser        = 
RequestContext.getCurrentUser();
         String              relationshipEdgeId = 
relationshipEdge.getIdForDisplay();
-        Map<String, Object> taskParams         = 
ClassificationTask.toParameters(relationshipEdgeId, relationship);
+        Map<String, Object> taskParams         = 
ClassificationTask.toParameters(relationshipEdgeId, relationship, 
oldTagPropagation, oldBlockedClassifications);
         AtlasTask           task               = 
taskManagement.createTask(taskType, currentUser, taskParams);
 
         AtlasGraphUtilsV2.addItemToListProperty(relationshipEdge, 
EDGE_PENDING_TASKS_PROPERTY_KEY, task.getGuid());
@@ -1577,19 +1630,20 @@ public abstract class DeleteHandlerV1 {
 
     // propagated classifications should contain blocked propagated 
classification
     private AtlasVertex 
validateBlockedPropagatedClassification(List<AtlasVertex> 
classificationVertices, AtlasClassification classification) {
-        AtlasVertex ret = null;
+        if (classification == null || 
CollectionUtils.isEmpty(classificationVertices)) {
+            return null;
+        }
 
         for (AtlasVertex vertex : classificationVertices) {
             String classificationName = getClassificationName(vertex);
             String entityGuid         = getClassificationEntityGuid(vertex);
 
             if (classificationName.equals(classification.getTypeName()) && 
entityGuid.equals(classification.getEntityGuid())) {
-                ret = vertex;
-                break;
+                return vertex;
             }
         }
 
-        return ret;
+        return null;
     }
 
     private void setBlockedClassificationIds(AtlasEdge edge, List<String> 
classificationIds) {
diff --git 
a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasGraphUtilsV2.java
 
b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasGraphUtilsV2.java
index 2bf3e2dff0..b75a47d0b3 100644
--- 
a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasGraphUtilsV2.java
+++ 
b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasGraphUtilsV2.java
@@ -911,20 +911,26 @@ public class AtlasGraphUtilsV2 {
     public static void addItemToListProperty(AtlasEdge edge, String property, 
String value) {
         List<String> list = (List<String>) getListFromProperty(edge, property);
 
-        list.add(value);
+        if (!list.contains(value)) {
+            List<String> newList = new ArrayList<>(list);
+            newList.add(value);
 
-        edge.setListProperty(property, list);
+            edge.setListProperty(property, newList);
+        }
     }
 
     public static void removeItemFromListProperty(AtlasEdge edge, String 
property, String value) {
         List<String> list = (List<String>) getListFromProperty(edge, property);
 
-        list.remove(value);
+        if (list.contains(value)) {
+            List<String> newList = new ArrayList<>(list);
+            newList.remove(value);
 
-        if (CollectionUtils.isEmpty(list)) {
-            edge.removeProperty(property);
-        } else {
-            edge.setListProperty(property, list);
+            if (CollectionUtils.isEmpty(newList)) {
+                edge.removeProperty(property);
+            } else {
+                edge.setListProperty(property, newList);
+            }
         }
     }
 
diff --git 
a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasRelationshipStoreV2.java
 
b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasRelationshipStoreV2.java
index de8b3b2a61..1fb68ca063 100644
--- 
a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasRelationshipStoreV2.java
+++ 
b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasRelationshipStoreV2.java
@@ -17,7 +17,6 @@
  */
 package org.apache.atlas.repository.store.graph.v2;
 
-import org.apache.atlas.AtlasConfiguration;
 import org.apache.atlas.AtlasErrorCode;
 import org.apache.atlas.RequestContext;
 import org.apache.atlas.annotation.GraphTransaction;
@@ -83,7 +82,6 @@ import static 
org.apache.atlas.repository.Constants.RELATIONSHIP_TYPE_PROPERTY_K
 import static org.apache.atlas.repository.Constants.VERSION_PROPERTY_KEY;
 import static 
org.apache.atlas.repository.store.graph.v2.AtlasGraphUtilsV2.getState;
 import static 
org.apache.atlas.repository.store.graph.v2.AtlasGraphUtilsV2.getTypeName;
-import static 
org.apache.atlas.repository.store.graph.v2.tasks.ClassificationPropagateTaskFactory.CLASSIFICATION_PROPAGATION_RELATIONSHIP_UPDATE;
 
 @Component
 public class AtlasRelationshipStoreV2 implements AtlasRelationshipStore {
@@ -91,7 +89,6 @@ public class AtlasRelationshipStoreV2 implements 
AtlasRelationshipStore {
 
     private static final Long    DEFAULT_RELATIONSHIP_VERSION = 0L;
     private static final boolean NOTIFICATIONS_ENABLED        = 
NOTIFICATION_RELATIONSHIPS_ENABLED.getBoolean();
-    private static final boolean DEFERRED_ACTION_ENABLED      = 
AtlasConfiguration.TASKS_USE_ENABLED.getBoolean();
 
     private final AtlasGraph                 graph;
     private final AtlasTypeRegistry          typeRegistry;
@@ -449,11 +446,7 @@ public class AtlasRelationshipStoreV2 implements 
AtlasRelationshipStore {
     }
 
     private void updateTagPropagations(AtlasEdge relationshipEdge, 
AtlasRelationship relationship) throws AtlasBaseException {
-        if (DEFERRED_ACTION_ENABLED) {
-            createAndQueueTask(CLASSIFICATION_PROPAGATION_RELATIONSHIP_UPDATE, 
relationshipEdge, relationship);
-        } else {
-            
deleteDelegate.getHandler().updateTagPropagations(relationshipEdge, 
relationship);
-        }
+        deleteDelegate.getHandler().updateTagPropagations(relationshipEdge, 
relationship);
     }
 
     private void validateRelationship(AtlasRelationship relationship) throws 
AtlasBaseException {
@@ -771,10 +764,6 @@ public class AtlasRelationshipStoreV2 implements 
AtlasRelationshipStore {
         }
     }
 
-    private void createAndQueueTask(String taskType, AtlasEdge 
relationshipEdge, AtlasRelationship relationship) {
-        deleteDelegate.getHandler().createAndQueueTask(taskType, 
relationshipEdge, relationship);
-    }
-
     private void verifyRelationshipReadAccess(AtlasEdge edge) throws 
AtlasBaseException {
         AtlasEntityHeader end1Entity = 
entityRetriever.toAtlasEntityHeaderWithClassifications(edge.getOutVertex());
         AtlasEntityHeader end2Entity = 
entityRetriever.toAtlasEntityHeaderWithClassifications(edge.getInVertex());
diff --git 
a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/EntityGraphMapper.java
 
b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/EntityGraphMapper.java
index caa7e8f5d9..c65330f42e 100644
--- 
a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/EntityGraphMapper.java
+++ 
b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/EntityGraphMapper.java
@@ -1364,10 +1364,10 @@ public class EntityGraphMapper {
     }
 
     @GraphTransaction
-    public void updateTagPropagations(String relationshipEdgeId, 
AtlasRelationship relationship) throws AtlasBaseException {
+    public void updateTagPropagations(String relationshipEdgeId, 
AtlasRelationship relationship, String oldTagPropagation, 
java.util.List<String> oldBlockedClassifications) throws AtlasBaseException {
         AtlasEdge relationshipEdge = graph.getEdge(relationshipEdgeId);
 
-        deleteDelegate.getHandler().updateTagPropagations(relationshipEdge, 
relationship);
+        deleteDelegate.getHandler().updateTagPropagations(relationshipEdge, 
relationship, true, oldTagPropagation, oldBlockedClassifications);
 
         entityChangeNotifier.notifyPropagatedEntities();
     }
diff --git 
a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/EntityGraphRetriever.java
 
b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/EntityGraphRetriever.java
index ffd5296b07..6063477933 100644
--- 
a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/EntityGraphRetriever.java
+++ 
b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/EntityGraphRetriever.java
@@ -1864,6 +1864,11 @@ public class EntityGraphRetriever {
         // set propagated and blocked propagated classifications
         readClassificationsFromEdge(edge, relationshipWithExtInfo, 
extendedInfo);
 
+        List<String> pendingTasks = 
edge.getListProperty(org.apache.atlas.repository.Constants.EDGE_PENDING_TASKS_PROPERTY_KEY);
+        if (CollectionUtils.isNotEmpty(pendingTasks)) {
+            relationship.setPendingTasks(new HashSet<>(pendingTasks));
+        }
+
         return relationshipWithExtInfo;
     }
 
@@ -1873,6 +1878,8 @@ public class EntityGraphRetriever {
         AtlasRelationship        relationship              = 
relationshipWithExtInfo.getRelationship();
         Set<AtlasClassification> propagatedClassifications = new HashSet<>();
         Set<AtlasClassification> blockedClassifications    = new HashSet<>();
+        Set<String>              blockedIdSet               = 
CollectionUtils.isEmpty(blockedClassificationIds)
+                ? Collections.emptySet() : new 
HashSet<>(blockedClassificationIds);
 
         for (AtlasVertex classificationVertex : classificationVertices) {
             String              classificationId = 
classificationVertex.getIdForDisplay();
@@ -1882,7 +1889,7 @@ public class EntityGraphRetriever {
                 continue;
             }
 
-            if (blockedClassificationIds.contains(classificationId)) {
+            if (blockedIdSet.contains(classificationId)) {
                 blockedClassifications.add(classification);
             } else {
                 propagatedClassifications.add(classification);
diff --git 
a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/tasks/ClassificationPropagationTasks.java
 
b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/tasks/ClassificationPropagationTasks.java
index 2118bc42e0..96ebc39e73 100644
--- 
a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/tasks/ClassificationPropagationTasks.java
+++ 
b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/tasks/ClassificationPropagationTasks.java
@@ -67,8 +67,14 @@ public class ClassificationPropagationTasks {
         protected void run(Map<String, Object> parameters) throws 
AtlasBaseException {
             String            relationshipEdgeId = (String) 
parameters.get(PARAM_RELATIONSHIP_EDGE_ID);
             AtlasRelationship relationship       = AtlasType.fromJson((String) 
parameters.get(PARAM_RELATIONSHIP_OBJECT), AtlasRelationship.class);
+            String            oldTagPropagation  = (String) 
parameters.get(PARAM_OLD_TAG_PROPAGATION);
 
-            entityGraphMapper.updateTagPropagations(relationshipEdgeId, 
relationship);
+            java.util.List<String> oldBlockedClassifications = null;
+            if (parameters.containsKey(PARAM_OLD_BLOCKED_CLASSIFICATIONS)) {
+                oldBlockedClassifications = AtlasType.fromJson((String) 
parameters.get(PARAM_OLD_BLOCKED_CLASSIFICATIONS), java.util.List.class);
+            }
+
+            entityGraphMapper.updateTagPropagations(relationshipEdgeId, 
relationship, oldTagPropagation, oldBlockedClassifications);
         }
     }
 }
diff --git 
a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/tasks/ClassificationTask.java
 
b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/tasks/ClassificationTask.java
index 5a4981c294..098a798633 100644
--- 
a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/tasks/ClassificationTask.java
+++ 
b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/tasks/ClassificationTask.java
@@ -50,6 +50,8 @@ public abstract class ClassificationTask extends AbstractTask 
{
     public static final String PARAM_RELATIONSHIP_GUID        = 
"relationshipGuid";
     public static final String PARAM_RELATIONSHIP_OBJECT      = 
"relationshipObject";
     public static final String PARAM_RELATIONSHIP_EDGE_ID     = 
"relationshipEdgeId";
+    public static final String PARAM_OLD_TAG_PROPAGATION      = 
"oldTagPropagation";
+    public static final String PARAM_OLD_BLOCKED_CLASSIFICATIONS = 
"oldBlockedClassifications";
     public static final String PARAM_IMPORT_IN_PROGRESS       = 
"isImportInProgress";
     public static final boolean DEFAULT_IMPORT_IN_PROGRESS    = false;
 
@@ -84,11 +86,22 @@ public abstract class ClassificationTask extends 
AbstractTask {
     }
 
     public static Map<String, Object> toParameters(String relationshipEdgeId, 
AtlasRelationship relationship) {
+        return toParameters(relationshipEdgeId, relationship, null, null);
+    }
+
+    public static Map<String, Object> toParameters(String relationshipEdgeId, 
AtlasRelationship relationship, String oldTagPropagation, 
java.util.List<String> oldBlockedClassifications) {
         Map<String, Object>  ret = new HashMap<>();
 
         ret.put(PARAM_RELATIONSHIP_EDGE_ID, relationshipEdgeId);
         ret.put(PARAM_RELATIONSHIP_OBJECT, AtlasType.toJson(relationship));
 
+        if (oldTagPropagation != null) {
+            ret.put(PARAM_OLD_TAG_PROPAGATION, oldTagPropagation);
+        }
+        if (oldBlockedClassifications != null) {
+            ret.put(PARAM_OLD_BLOCKED_CLASSIFICATIONS, 
AtlasType.toJson(oldBlockedClassifications));
+        }
+
         return ret;
     }
 
diff --git 
a/repository/src/test/java/org/apache/atlas/repository/store/graph/v2/tasks/ClassificationPropagationTasksTest.java
 
b/repository/src/test/java/org/apache/atlas/repository/store/graph/v2/tasks/ClassificationPropagationTasksTest.java
index 601a224930..86dfb1cc11 100644
--- 
a/repository/src/test/java/org/apache/atlas/repository/store/graph/v2/tasks/ClassificationPropagationTasksTest.java
+++ 
b/repository/src/test/java/org/apache/atlas/repository/store/graph/v2/tasks/ClassificationPropagationTasksTest.java
@@ -184,11 +184,11 @@ public class ClassificationPropagationTasksTest {
         parameters.put(PARAM_RELATIONSHIP_EDGE_ID, "relationship-edge-id");
         parameters.put(PARAM_RELATIONSHIP_OBJECT, relationshipJson);
 
-        doNothing().when(entityGraphMapper).updateTagPropagations(anyString(), 
any(AtlasRelationship.class));
+        doNothing().when(entityGraphMapper).updateTagPropagations(anyString(), 
any(AtlasRelationship.class), any(), any());
 
         invokeRunMethod(updateTask, parameters);
 
-        
verify(entityGraphMapper).updateTagPropagations(eq("relationship-edge-id"), 
eq(relationship));
+        
verify(entityGraphMapper).updateTagPropagations(eq("relationship-edge-id"), 
eq(relationship), any(), any());
     }
 
     @Test
@@ -205,7 +205,7 @@ public class ClassificationPropagationTasksTest {
         parameters.put(PARAM_RELATIONSHIP_EDGE_ID, "relationship-edge-id");
         parameters.put(PARAM_RELATIONSHIP_OBJECT, relationshipJson);
 
-        doThrow(new AtlasBaseException("Test 
exception")).when(entityGraphMapper).updateTagPropagations(anyString(), 
any(AtlasRelationship.class));
+        doThrow(new AtlasBaseException("Test 
exception")).when(entityGraphMapper).updateTagPropagations(anyString(), 
any(AtlasRelationship.class), any(), any());
 
         expectThrows(AtlasBaseException.class, () -> 
invokeRunMethod(updateTask, parameters));
     }
@@ -222,7 +222,7 @@ public class ClassificationPropagationTasksTest {
 
         invokeRunMethod(updateTask, parameters);
 
-        verify(entityGraphMapper).updateTagPropagations(null, null);
+        verify(entityGraphMapper).updateTagPropagations(null, null, null, 
null);
     }
 
     @Test
@@ -293,7 +293,7 @@ public class ClassificationPropagationTasksTest {
 
         invokeRunMethod(updateTask, parameters);
 
-        verify(entityGraphMapper).updateTagPropagations(null, null);
+        verify(entityGraphMapper).updateTagPropagations(null, null, null, 
null);
     }
 
     // Helper methods
diff --git 
a/repository/src/test/java/org/apache/atlas/repository/store/graph/v2/tasks/ClassificationTaskTest.java
 
b/repository/src/test/java/org/apache/atlas/repository/store/graph/v2/tasks/ClassificationTaskTest.java
index 23c79dd318..3f38c70abe 100644
--- 
a/repository/src/test/java/org/apache/atlas/repository/store/graph/v2/tasks/ClassificationTaskTest.java
+++ 
b/repository/src/test/java/org/apache/atlas/repository/store/graph/v2/tasks/ClassificationTaskTest.java
@@ -278,7 +278,7 @@ public class ClassificationTaskTest {
 
     @Test
     public void testToParametersWithNullValues() {
-        Map<String, Object> result = ClassificationTask.toParameters(null, 
null, null, null);
+        Map<String, Object> result = ClassificationTask.toParameters((String) 
null, (String) null, null, null);
 
         assertNotNull(result);
         assertEquals(result.get(PARAM_ENTITY_GUID), null);
diff --git 
a/repository/src/test/java/org/apache/atlas/repository/tagpropagation/ClassificationPropagationWithTasksTest.java
 
b/repository/src/test/java/org/apache/atlas/repository/tagpropagation/ClassificationPropagationWithTasksTest.java
index 1ecb42bd8b..3eba0446e4 100644
--- 
a/repository/src/test/java/org/apache/atlas/repository/tagpropagation/ClassificationPropagationWithTasksTest.java
+++ 
b/repository/src/test/java/org/apache/atlas/repository/tagpropagation/ClassificationPropagationWithTasksTest.java
@@ -20,23 +20,33 @@ package org.apache.atlas.repository.tagpropagation;
 import org.apache.atlas.AtlasErrorCode;
 import org.apache.atlas.RequestContext;
 import org.apache.atlas.TestModules;
+import org.apache.atlas.discovery.AtlasLineageService;
 import org.apache.atlas.exception.AtlasBaseException;
 import org.apache.atlas.exception.EntityNotFoundException;
 import org.apache.atlas.model.instance.AtlasClassification;
 import org.apache.atlas.model.instance.AtlasEntity;
+import org.apache.atlas.model.instance.AtlasRelationship;
+import org.apache.atlas.model.lineage.AtlasLineageInfo;
+import org.apache.atlas.model.lineage.AtlasLineageInfo.LineageRelation;
 import org.apache.atlas.model.typedef.AtlasClassificationDef;
+import org.apache.atlas.model.typedef.AtlasRelationshipDef.PropagateTags;
+import org.apache.atlas.model.typedef.AtlasStructDef.AtlasAttributeDef;
 import org.apache.atlas.model.typedef.AtlasTypesDef;
 import org.apache.atlas.repository.AtlasTestBase;
 import org.apache.atlas.repository.graph.GraphHelper;
+import org.apache.atlas.repository.graphdb.AtlasEdge;
+import org.apache.atlas.repository.graphdb.AtlasGraph;
 import org.apache.atlas.repository.graphdb.AtlasVertex;
 import org.apache.atlas.repository.impexp.ImportService;
 import org.apache.atlas.repository.impexp.ZipFileResourceTestUtils;
 import org.apache.atlas.repository.store.graph.AtlasEntityStore;
+import org.apache.atlas.repository.store.graph.AtlasRelationshipStore;
 import org.apache.atlas.repository.store.graph.v2.AtlasGraphUtilsV2;
 import org.apache.atlas.repository.store.graph.v2.EntityGraphMapper;
 import org.apache.atlas.store.AtlasTypeDefStore;
 import org.apache.atlas.tasks.TaskManagement;
 import org.apache.atlas.type.AtlasTypeRegistry;
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.testng.SkipException;
 import org.testng.annotations.BeforeClass;
@@ -49,8 +59,13 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
+import static org.apache.atlas.model.lineage.AtlasLineageInfo.LineageDirection;
+import static 
org.apache.atlas.model.typedef.AtlasRelationshipDef.PropagateTags.NONE;
+import static 
org.apache.atlas.model.typedef.AtlasRelationshipDef.PropagateTags.ONE_TO_TWO;
 import static 
org.apache.atlas.repository.graph.GraphHelper.getClassificationVertex;
 import static 
org.apache.atlas.repository.graph.GraphHelper.getPropagatedTraitNames;
 import static 
org.apache.atlas.repository.impexp.ZipFileResourceTestUtils.runImportWithNoParameters;
@@ -60,6 +75,7 @@ import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
 
 @Guice(modules = TestModules.TestOnlyModule.class)
 public class ClassificationPropagationWithTasksTest extends AtlasTestBase {
@@ -67,12 +83,17 @@ public class ClassificationPropagationWithTasksTest extends 
AtlasTestBase {
 
     private static final String IMPORT_DELETE_FILE = 
"deleted_tab_propagation.zip";
 
-    private static final String HDFS_PATH_EMPLOYEES = 
"a3955120-ac17-426f-a4af-972ec8690e5f";
-
-    private static final String HIVE_TABLE = 
"089c1ad4-9dde-4f9e-80c8-12a3046be337";
+    private static final String HDFS_PATH_EMPLOYEES     = 
"a3955120-ac17-426f-a4af-972ec8690e5f";
+    private static final String EMPLOYEES1_TABLE        = 
"cdf0040e-739e-4590-a137-964d10e73573";
+    private static final String EMPLOYEES2_TABLE        = 
"0a3e66b6-472c-48b3-8453-abdd24f9494f";
+    private static final String EMPLOYEES_UNION_TABLE   = 
"1ceac963-1a2b-476a-a269-10396187d406";
+    private static final String EMPLOYEES_UNION_PROCESS = 
"470a2d1e-b1fd-47de-8f2d-8dfd0a0275a7";
 
+    private static final String HIVE_TABLE     = 
"089c1ad4-9dde-4f9e-80c8-12a3046be337";
     private static final String HIVE_TABLE_CTAS = 
"e83551b7-bbef-45aa-99d5-3d98c0ac737b";
 
+    private AtlasLineageInfo lineageInfo;
+
     @Inject
     private AtlasTypeDefStore typeDefStore;
 
@@ -91,6 +112,15 @@ public class ClassificationPropagationWithTasksTest extends 
AtlasTestBase {
     @Inject
     private TaskManagement tasksManagement;
 
+    @Inject
+    private AtlasRelationshipStore relationshipStore;
+
+    @Inject
+    private AtlasLineageService lineageService;
+
+    @Inject
+    private AtlasGraph graph;
+
     public static InputStream getZipSource(String fileName) throws IOException 
{
         return ZipFileResourceTestUtils.getFileInputStream(fileName);
     }
@@ -219,7 +249,7 @@ public class ClassificationPropagationWithTasksTest extends 
AtlasTestBase {
         assertNotNull(impactedEntities);
     }
 
-    @Test
+    @Test(priority = 100)
     public void runImportForDeletedEntityLineage() throws Exception {
         runImportWithNoParameters(importService, 
getZipSource(IMPORT_DELETE_FILE));
         final String tagName = "classification1";
@@ -241,6 +271,258 @@ public class ClassificationPropagationWithTasksTest 
extends AtlasTestBase {
         assertNotNull(propagatedTraitNames);
     }
 
+    @Test(dependsOnMethods = 
"updateRelationship_afterTaskCompletion_entitiesAndPendingTasksCleared")
+    public void 
updateRelationship_propagateTagsChange_ignoresBlockedInSamePut() throws 
Exception {
+        setupRelationshipBlockScenario();
+
+        AtlasRelationship relationship = 
getRelationship(EMPLOYEES_UNION_PROCESS, EMPLOYEES_UNION_TABLE);
+
+        assertEquals(relationship.getPropagateTags(), ONE_TO_TWO);
+        
assertFalse(relationship.getBlockedPropagatedClassifications().isEmpty());
+
+        List<String> blockedIdsBefore = 
GraphHelper.getBlockedClassificationIds(
+                new GraphHelper(graph).getEdgeForGUID(relationship.getGuid()));
+
+        assertFalse(blockedIdsBefore.isEmpty());
+
+        relationship.setPropagateTags(NONE);
+        
relationship.setBlockedPropagatedClassifications(Collections.emptySet());
+
+        AtlasRelationship putReturn = relationshipStore.update(relationship);
+
+        assertEquals(putReturn.getPropagateTags(), NONE);
+
+        List<String> blockedIdsAfter = GraphHelper.getBlockedClassificationIds(
+                new GraphHelper(graph).getEdgeForGUID(putReturn.getGuid()));
+
+        assertEquals(blockedIdsAfter, blockedIdsBefore,
+                "Blocked list must not change when propagateTags also changed 
in same PUT");
+    }
+
+    @Test
+    public void updateRelationship_unblock_putAndGetBeforeTaskCompletion() 
throws Exception {
+        setupRelationshipBlockScenario();
+
+        AtlasEntity employees1 = getEntity(EMPLOYEES1_TABLE);
+        AtlasEntity employees2 = getEntity(EMPLOYEES2_TABLE);
+
+        AtlasClassification piiTag2 = new AtlasClassification("PII");
+        piiTag2.setEntityGuid(employees1.getGuid());
+
+        AtlasClassification piiTag3 = new AtlasClassification("PII");
+        piiTag3.setEntityGuid(employees2.getGuid());
+
+        AtlasRelationship relationship = 
getRelationship(EMPLOYEES_UNION_PROCESS, EMPLOYEES_UNION_TABLE);
+
+        
relationship.setBlockedPropagatedClassifications(Collections.emptySet());
+
+        AtlasRelationship putReturn = relationshipStore.update(relationship);
+
+        assertNotNull(putReturn);
+        assertTrue(putReturn.getBlockedPropagatedClassifications().isEmpty());
+        
assertClassificationExistInList(putReturn.getPropagatedClassifications(), 
piiTag2);
+        
assertClassificationExistInList(putReturn.getPropagatedClassifications(), 
piiTag3);
+
+        AtlasRelationship getReturn = 
relationshipStore.getById(putReturn.getGuid());
+
+        assertTrue(getReturn.getBlockedPropagatedClassifications().isEmpty());
+
+        if (putReturn.getPendingTasks() != null && 
!putReturn.getPendingTasks().isEmpty()) {
+            assertNotNull(getReturn.getPendingTasks());
+            assertFalse(getReturn.getPendingTasks().isEmpty());
+            assertClassificationNotExistInEntity(EMPLOYEES_UNION_TABLE, 
piiTag2);
+            assertClassificationNotExistInEntity(EMPLOYEES_UNION_TABLE, 
piiTag3);
+        } else {
+            assertClassificationExistInEntity(EMPLOYEES_UNION_TABLE, piiTag2);
+            assertClassificationExistInEntity(EMPLOYEES_UNION_TABLE, piiTag3);
+        }
+    }
+
+    @Test(dependsOnMethods = 
"updateRelationship_unblock_putAndGetBeforeTaskCompletion")
+    public void 
updateRelationship_afterTaskCompletion_entitiesAndPendingTasksCleared() throws 
Exception {
+        setupRelationshipBlockScenario();
+
+        AtlasEntity employees1 = getEntity(EMPLOYEES1_TABLE);
+        AtlasEntity employees2 = getEntity(EMPLOYEES2_TABLE);
+
+        AtlasClassification piiTag2 = new AtlasClassification("PII");
+        piiTag2.setEntityGuid(employees1.getGuid());
+
+        AtlasClassification piiTag3 = new AtlasClassification("PII");
+        piiTag3.setEntityGuid(employees2.getGuid());
+
+        AtlasRelationship relationship = 
getRelationship(EMPLOYEES_UNION_PROCESS, EMPLOYEES_UNION_TABLE);
+        PropagateTags propagateTags = relationship.getPropagateTags();
+        List<String>  oldBlockedIds  = GraphHelper.getBlockedClassificationIds(
+                new GraphHelper(graph).getEdgeForGUID(relationship.getGuid()));
+
+        
relationship.setBlockedPropagatedClassifications(Collections.emptySet());
+
+        AtlasRelationship putReturn = relationshipStore.update(relationship);
+
+        AtlasRelationship unblockRequest = 
relationshipStore.getById(putReturn.getGuid());
+        
unblockRequest.setBlockedPropagatedClassifications(Collections.emptySet());
+
+        waitForRelationshipTasksToComplete(putReturn.getGuid(), 
unblockRequest, propagateTags, oldBlockedIds);
+
+        AtlasRelationship finalRelationship = 
relationshipStore.getById(putReturn.getGuid());
+
+        assertNull(finalRelationship.getPendingTasks());
+        assertClassificationExistInEntity(EMPLOYEES_UNION_TABLE, piiTag2);
+        assertClassificationExistInEntity(EMPLOYEES_UNION_TABLE, piiTag3);
+    }
+
+    private void setupRelationshipBlockScenario() throws AtlasBaseException {
+        AtlasEntity hdfsPath   = getEntity(HDFS_PATH_EMPLOYEES);
+        AtlasEntity employees1 = getEntity(EMPLOYEES1_TABLE);
+        AtlasEntity employees2 = getEntity(EMPLOYEES2_TABLE);
+
+        AtlasClassification piiTag1 = new AtlasClassification("PII");
+        piiTag1.setPropagate(true);
+        piiTag1.setEntityGuid(hdfsPath.getGuid());
+        piiTag1.setAttribute("type", "from hdfs_path entity");
+        piiTag1.setAttribute("valid", true);
+
+        AtlasClassification piiTag2 = new AtlasClassification("PII");
+        piiTag2.setPropagate(true);
+        piiTag2.setEntityGuid(employees1.getGuid());
+        piiTag2.setAttribute("type", "from employees1 entity");
+        piiTag2.setAttribute("valid", true);
+
+        AtlasClassification piiTag3 = new AtlasClassification("PII");
+        piiTag3.setPropagate(true);
+        piiTag3.setEntityGuid(employees2.getGuid());
+        piiTag3.setAttribute("type", "from employees2 entity");
+        piiTag3.setAttribute("valid", true);
+
+        addClassificationIfMissing(hdfsPath, piiTag1);
+        addClassificationIfMissing(employees1, piiTag2);
+        addClassificationIfMissing(employees2, piiTag3);
+
+        propagateClassificationIfPresent(hdfsPath.getGuid(), 
piiTag1.getTypeName());
+        propagateClassificationIfPresent(employees1.getGuid(), 
piiTag2.getTypeName());
+        propagateClassificationIfPresent(employees2.getGuid(), 
piiTag3.getTypeName());
+
+        assertClassificationExistInEntity(EMPLOYEES_UNION_TABLE, piiTag2);
+        assertClassificationExistInEntity(EMPLOYEES_UNION_TABLE, piiTag3);
+
+        AtlasRelationship relationship   = 
getRelationship(EMPLOYEES_UNION_PROCESS, EMPLOYEES_UNION_TABLE);
+        PropagateTags       oldPropagateTags = relationship.getPropagateTags();
+        List<String>        oldBlockedIds    = Collections.emptyList();
+
+        Set<AtlasClassification> blockTags = 
selectPropagatedClassifications(relationship, employees1.getGuid(), 
employees2.getGuid());
+
+        assertFalse(blockTags.isEmpty(), "Expected propagated PII on 
process->union relationship before block");
+
+        relationship.setBlockedPropagatedClassifications(blockTags);
+
+        AtlasRelationship blockReturn = relationshipStore.update(relationship);
+
+        if (blockReturn.getPendingTasks() != null && 
!blockReturn.getPendingTasks().isEmpty()) {
+            applyRelationshipTaskIfPending(blockReturn, oldPropagateTags, 
oldBlockedIds);
+        }
+
+        assertClassificationNotExistInEntity(EMPLOYEES_UNION_TABLE, piiTag2);
+        assertClassificationNotExistInEntity(EMPLOYEES_UNION_TABLE, piiTag3);
+    }
+
+    private void addClassificationIfMissing(AtlasEntity entity, 
AtlasClassification classification) throws AtlasBaseException {
+        List<AtlasClassification> classifications = 
entity.getClassifications();
+
+        if (CollectionUtils.isNotEmpty(classifications)) {
+            for (AtlasClassification existing : classifications) {
+                if 
(existing.getTypeName().equals(classification.getTypeName())) {
+                    return;
+                }
+            }
+        }
+
+        entityStore.addClassifications(entity.getGuid(), 
Collections.singletonList(classification));
+    }
+
+    private void propagateClassificationIfPresent(String entityGuid, String 
classificationName) throws AtlasBaseException {
+        AtlasVertex entityVertex = AtlasGraphUtilsV2.findByGuid(entityGuid);
+
+        if (entityVertex == null) {
+            return;
+        }
+
+        AtlasVertex classificationVertex = 
getClassificationVertex(entityVertex, classificationName);
+
+        if (classificationVertex != null) {
+            entityGraphMapper.propagateClassification(entityGuid, 
classificationVertex.getId().toString(), StringUtils.EMPTY);
+        }
+    }
+
+    private Set<AtlasClassification> 
selectPropagatedClassifications(AtlasRelationship relationship, String... 
sourceEntityGuids) {
+        Set<AtlasClassification> result     = new HashSet<>();
+        Set<AtlasClassification> propagated = 
relationship.getPropagatedClassifications();
+
+        if (CollectionUtils.isEmpty(propagated)) {
+            return result;
+        }
+
+        Set<String> sourceGuids = new 
HashSet<>(Arrays.asList(sourceEntityGuids));
+
+        for (AtlasClassification classification : propagated) {
+            if ("PII".equals(classification.getTypeName()) && 
sourceGuids.contains(classification.getEntityGuid())) {
+                result.add(classification);
+            }
+        }
+
+        return result;
+    }
+
+    private void applyRelationshipTaskIfPending(AtlasRelationship 
updateRequest, PropagateTags oldPropagateTags,
+                                              List<String> oldBlockedIds) 
throws AtlasBaseException {
+        AtlasEdge edge = new 
GraphHelper(graph).getEdgeForGUID(updateRequest.getGuid());
+
+        entityGraphMapper.updateTagPropagations(edge.getIdForDisplay(), 
updateRequest,
+                oldPropagateTags != null ? oldPropagateTags.name() : null, 
oldBlockedIds);
+    }
+
+    private void waitForRelationshipTasksToComplete(String relationshipGuid, 
AtlasRelationship updateRequest,
+                                                    PropagateTags 
oldPropagateTags, List<String> oldBlockedIds)
+            throws AtlasBaseException, InterruptedException {
+        AtlasRelationship relationship = 
relationshipStore.getById(relationshipGuid);
+
+        if (relationship.getPendingTasks() != null && 
!relationship.getPendingTasks().isEmpty()) {
+            applyRelationshipTaskIfPending(updateRequest, oldPropagateTags, 
oldBlockedIds);
+            relationship = relationshipStore.getById(relationshipGuid);
+        }
+
+        if (relationship.getPendingTasks() == null || 
relationship.getPendingTasks().isEmpty()) {
+            return;
+        }
+
+        for (int i = 0; i < 30; i++) {
+            relationship = relationshipStore.getById(relationshipGuid);
+
+            if (relationship.getPendingTasks() == null || 
relationship.getPendingTasks().isEmpty()) {
+                return;
+            }
+
+            Thread.sleep(100);
+        }
+
+        fail("Relationship tasks did not complete in time for guid: " + 
relationshipGuid);
+    }
+
+    private void assertClassificationExistInList(Set<AtlasClassification> 
classifications, AtlasClassification expected) {
+        if (classifications == null) {
+            fail("Propagated classifications list is null");
+        }
+
+        for (AtlasClassification classification : classifications) {
+            if (classification.getTypeName().equals(expected.getTypeName())
+                    && 
classification.getEntityGuid().equals(expected.getEntityGuid())) {
+                return;
+            }
+        }
+
+        fail("Propagated classification not found in relationship response");
+    }
+
     private void loadModelFilesAndImportTestData() {
         try {
             loadModelFromJson("0000-Area0/0010-base_model.json", typeDefStore, 
typeRegistry);
@@ -250,6 +532,8 @@ public class ClassificationPropagationWithTasksTest extends 
AtlasTestBase {
             loadSampleClassificationDefs();
 
             runImportWithNoParameters(importService, 
getZipSource(IMPORT_FILE));
+
+            initializeLineageInfo();
         } catch (AtlasBaseException | IOException e) {
             throw new SkipException("Model loading failed!");
         }
@@ -258,8 +542,17 @@ public class ClassificationPropagationWithTasksTest 
extends AtlasTestBase {
     private void loadSampleClassificationDefs() throws AtlasBaseException {
         AtlasClassificationDef tagX = new AtlasClassificationDef("tagX");
         AtlasClassificationDef tagY = new AtlasClassificationDef("tagY");
+        AtlasClassificationDef pii  = new AtlasClassificationDef("PII");
 
-        typeDefStore.createTypesDef(new AtlasTypesDef(Collections.emptyList(), 
Collections.emptyList(), Arrays.asList(tagX, tagY), Collections.emptyList(), 
Collections.emptyList()));
+        pii.addAttribute(new AtlasAttributeDef("type", "string"));
+        pii.addAttribute(new AtlasAttributeDef("valid", "boolean"));
+
+        typeDefStore.createTypesDef(new AtlasTypesDef(Collections.emptyList(), 
Collections.emptyList(),
+                Arrays.asList(tagX, tagY, pii), Collections.emptyList(), 
Collections.emptyList()));
+    }
+
+    private void initializeLineageInfo() throws AtlasBaseException {
+        lineageInfo = lineageService.getAtlasLineageInfo(HDFS_PATH_EMPLOYEES, 
LineageDirection.BOTH, 3);
     }
 
     private AtlasEntity getEntity(String entityGuid) throws AtlasBaseException 
{
@@ -267,4 +560,68 @@ public class ClassificationPropagationWithTasksTest 
extends AtlasTestBase {
 
         return entityWithExtInfo.getEntity();
     }
+
+    private AtlasRelationship getRelationship(String fromEntityGuid, String 
toEntityGuid) throws AtlasBaseException {
+        String relationshipGuid = 
findRelationshipGuid(lineageInfo.getRelations(), fromEntityGuid, toEntityGuid);
+
+        if (relationshipGuid == null) {
+            relationshipGuid = 
findRelationshipGuid(lineageInfo.getRelations(), toEntityGuid, fromEntityGuid);
+        }
+
+        if (relationshipGuid == null) {
+            AtlasLineageInfo outputLineage = 
lineageService.getAtlasLineageInfo(fromEntityGuid, LineageDirection.OUTPUT, 1);
+
+            relationshipGuid = 
findRelationshipGuid(outputLineage.getRelations(), fromEntityGuid, 
toEntityGuid);
+
+            if (relationshipGuid == null) {
+                relationshipGuid = 
findRelationshipGuid(outputLineage.getRelations(), toEntityGuid, 
fromEntityGuid);
+            }
+        }
+
+        assertNotNull(relationshipGuid, "Relationship not found between " + 
fromEntityGuid + " and " + toEntityGuid);
+
+        return relationshipStore.getById(relationshipGuid);
+    }
+
+    private String findRelationshipGuid(Set<LineageRelation> relations, String 
fromEntityGuid, String toEntityGuid) {
+        if (relations == null) {
+            return null;
+        }
+
+        for (LineageRelation relation : relations) {
+            if (relation.getFromEntityId().equals(fromEntityGuid) && 
relation.getToEntityId().equals(toEntityGuid)) {
+                return relation.getRelationshipId();
+            }
+        }
+
+        return null;
+    }
+
+    private void assertClassificationExistInEntity(String entityGuid, 
AtlasClassification classification) throws AtlasBaseException {
+        List<AtlasClassification> classifications = 
getEntity(entityGuid).getClassifications();
+
+        if (CollectionUtils.isNotEmpty(classifications)) {
+            for (AtlasClassification c : classifications) {
+                if (c.getTypeName().equals(classification.getTypeName())
+                        && 
c.getEntityGuid().equals(classification.getEntityGuid())) {
+                    return;
+                }
+            }
+        }
+
+        fail("Propagated classification is not present in entity!");
+    }
+
+    private void assertClassificationNotExistInEntity(String entityGuid, 
AtlasClassification classification) throws AtlasBaseException {
+        List<AtlasClassification> classifications = 
getEntity(entityGuid).getClassifications();
+
+        if (CollectionUtils.isNotEmpty(classifications)) {
+            for (AtlasClassification c : classifications) {
+                if (c.getTypeName().equals(classification.getTypeName())
+                        && 
c.getEntityGuid().equals(classification.getEntityGuid())) {
+                    fail("Propagated classification should not be present in 
entity!");
+                }
+            }
+        }
+    }
 }

Reply via email to