Generate notifications when policies change

Updated existing PAP code to make use of new notification classes.

Change-Id: I4637ad92ac4f432f215cfc837e672c75074d88b5
Issue-ID: POLICY-1841
Signed-off-by: Jim Hahn <jrh3@att.com>
diff --git a/main/src/main/java/org/onap/policy/pap/main/PapConstants.java b/main/src/main/java/org/onap/policy/pap/main/PapConstants.java
index 3fc36f3..b8b199e 100644
--- a/main/src/main/java/org/onap/policy/pap/main/PapConstants.java
+++ b/main/src/main/java/org/onap/policy/pap/main/PapConstants.java
@@ -31,10 +31,12 @@
     public static final String REG_PDP_MODIFY_LOCK = "lock:pdp";
     public static final String REG_PDP_MODIFY_MAP = "object:pdp/modify/map";
     public static final String REG_PDP_TRACKER = "object:pdp/tracker";
+    public static final String REG_POLICY_NOTIFIER = "object:policy/notifier";
     public static final String REG_PAP_DAO_FACTORY = "object:pap/dao/factory";
 
     // topic names
     public static final String TOPIC_POLICY_PDP_PAP = "POLICY-PDP-PAP";
+    public static final String TOPIC_POLICY_NOTIFICATION = "POLICY-NOTIFICATION";
 
     private PapConstants() {
         super();
diff --git a/main/src/main/java/org/onap/policy/pap/main/comm/PdpModifyRequestMap.java b/main/src/main/java/org/onap/policy/pap/main/comm/PdpModifyRequestMap.java
index 3f0b5c1..f058da3 100644
--- a/main/src/main/java/org/onap/policy/pap/main/comm/PdpModifyRequestMap.java
+++ b/main/src/main/java/org/onap/policy/pap/main/comm/PdpModifyRequestMap.java
@@ -41,6 +41,7 @@
 import org.onap.policy.pap.main.comm.msgdata.RequestListener;
 import org.onap.policy.pap.main.comm.msgdata.StateChangeReq;
 import org.onap.policy.pap.main.comm.msgdata.UpdateReq;
+import org.onap.policy.pap.main.notification.PolicyNotifier;
 import org.onap.policy.pap.main.parameters.PdpModifyRequestMapParams;
 import org.onap.policy.pap.main.parameters.RequestParams;
 import org.slf4j.Logger;
@@ -74,6 +75,11 @@
      */
     private final PolicyModelsProviderFactoryWrapper daoFactory;
 
+    /**
+     * Used to notify when policy updates completes.
+     */
+    private final PolicyNotifier policyNotifier;
+
 
     /**
      * Constructs the object.
@@ -88,6 +94,7 @@
         this.params = params;
         this.modifyLock = params.getModifyLock();
         this.daoFactory = params.getDaoFactory();
+        this.policyNotifier = params.getPolicyNotifier();
     }
 
     /**
@@ -150,7 +157,7 @@
             .setMaxRetryCount(params.getParams().getUpdateParameters().getMaxRetryCount())
             .setTimers(params.getUpdateTimers())
             .setModifyLock(params.getModifyLock())
-            .setPublisher(params.getPublisher())
+            .setPdpPublisher(params.getPdpPublisher())
             .setResponseDispatcher(params.getResponseDispatcher());
         // @formatter:on
 
@@ -179,7 +186,7 @@
             .setMaxRetryCount(params.getParams().getStateChangeParameters().getMaxRetryCount())
             .setTimers(params.getStateChangeTimers())
             .setModifyLock(params.getModifyLock())
-            .setPublisher(params.getPublisher())
+            .setPdpPublisher(params.getPdpPublisher())
             .setResponseDispatcher(params.getResponseDispatcher());
         // @formatter:on
 
@@ -299,7 +306,7 @@
      * @return a new set of requests
      */
     protected PdpRequests makePdpRequests(String pdpName) {
-        return new PdpRequests(pdpName);
+        return new PdpRequests(pdpName, policyNotifier);
     }
 
     /**
@@ -359,6 +366,8 @@
          */
         private void disablePdp(PdpRequests requests) {
 
+            policyNotifier.removePdp(requests.getPdpName());
+
             // remove the requests from the map
             if (!pdp2requests.remove(requests.getPdpName(), requests)) {
                 // don't have the info we need to disable it
diff --git a/main/src/main/java/org/onap/policy/pap/main/comm/PdpRequests.java b/main/src/main/java/org/onap/policy/pap/main/comm/PdpRequests.java
index 5863b2c..4e1e923 100644
--- a/main/src/main/java/org/onap/policy/pap/main/comm/PdpRequests.java
+++ b/main/src/main/java/org/onap/policy/pap/main/comm/PdpRequests.java
@@ -23,6 +23,7 @@
 import lombok.Getter;
 import org.onap.policy.models.pdp.concepts.PdpMessage;
 import org.onap.policy.pap.main.comm.msgdata.Request;
+import org.onap.policy.pap.main.notification.PolicyNotifier;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -45,6 +46,12 @@
     private final String pdpName;
 
     /**
+     * Notifier for policy update completions.
+     */
+    @Getter
+    private final PolicyNotifier notifier;
+
+    /**
      * Index of request currently being published.
      */
     private int curIndex = 0;
@@ -60,8 +67,9 @@
      *
      * @param pdpName name of the PDP with which the requests are associated
      */
-    public PdpRequests(String pdpName) {
+    public PdpRequests(String pdpName, PolicyNotifier notifier) {
         this.pdpName = pdpName;
+        this.notifier = notifier;
     }
 
     /**
@@ -71,6 +79,8 @@
      */
     public void addSingleton(Request request) {
 
+        request.setNotifier(notifier);
+
         if (request.getMessage().getName() == null) {
             throw new IllegalArgumentException("unexpected broadcast for " + pdpName);
         }
@@ -86,7 +96,7 @@
         singletons[priority] = request;
 
         // stop publishing anything of a lower priority
-        QueueToken<PdpMessage> token = stopPublishingLowerPriority(priority);
+        final QueueToken<PdpMessage> token = stopPublishingLowerPriority(priority);
 
         // start publishing if nothing of higher priority
         if (higherPrioritySingleton(priority)) {
diff --git a/main/src/main/java/org/onap/policy/pap/main/comm/msgdata/Request.java b/main/src/main/java/org/onap/policy/pap/main/comm/msgdata/Request.java
index 1f69dcb..62aea78 100644
--- a/main/src/main/java/org/onap/policy/pap/main/comm/msgdata/Request.java
+++ b/main/src/main/java/org/onap/policy/pap/main/comm/msgdata/Request.java
@@ -23,6 +23,7 @@
 import org.onap.policy.models.pdp.concepts.PdpMessage;
 import org.onap.policy.models.pdp.concepts.PdpStatus;
 import org.onap.policy.pap.main.comm.QueueToken;
+import org.onap.policy.pap.main.notification.PolicyNotifier;
 
 /**
  * Request data, whose message may be changed at any point, possibly triggering a restart
@@ -61,6 +62,13 @@
     public void setListener(RequestListener listener);
 
     /**
+     * Sets the notifier to track responses to the request.
+     *
+     * @param notifier notifier used to publish notifications
+     */
+    public void setNotifier(PolicyNotifier notifier);
+
+    /**
      * Determines if this request is currently being published.
      *
      * @return {@code true} if this request is being published, {@code false} otherwise
diff --git a/main/src/main/java/org/onap/policy/pap/main/comm/msgdata/RequestImpl.java b/main/src/main/java/org/onap/policy/pap/main/comm/msgdata/RequestImpl.java
index 1945b32..a110ef4 100644
--- a/main/src/main/java/org/onap/policy/pap/main/comm/msgdata/RequestImpl.java
+++ b/main/src/main/java/org/onap/policy/pap/main/comm/msgdata/RequestImpl.java
@@ -30,6 +30,7 @@
 import org.onap.policy.models.pdp.concepts.PdpStatus;
 import org.onap.policy.pap.main.comm.QueueToken;
 import org.onap.policy.pap.main.comm.TimerManager;
+import org.onap.policy.pap.main.notification.PolicyNotifier;
 import org.onap.policy.pap.main.parameters.RequestParams;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -64,6 +65,13 @@
     private RequestListener listener;
 
     /**
+     * Notifier for policy update completions.
+     */
+    @Getter
+    @Setter
+    private PolicyNotifier notifier;
+
+    /**
      * Current retry count.
      */
     @Getter
@@ -217,7 +225,7 @@
 
         // couldn't take the other's place - add our own token to the queue
         token = new QueueToken<>(message);
-        params.getPublisher().enqueue(token);
+        params.getPdpPublisher().enqueue(token);
     }
 
     /**
diff --git a/main/src/main/java/org/onap/policy/pap/main/comm/msgdata/StateChangeReq.java b/main/src/main/java/org/onap/policy/pap/main/comm/msgdata/StateChangeReq.java
index e38cb0f..40acd3a 100644
--- a/main/src/main/java/org/onap/policy/pap/main/comm/msgdata/StateChangeReq.java
+++ b/main/src/main/java/org/onap/policy/pap/main/comm/msgdata/StateChangeReq.java
@@ -20,7 +20,6 @@
 
 package org.onap.policy.pap.main.comm.msgdata;
 
-import org.onap.policy.models.pdp.concepts.PdpMessage;
 import org.onap.policy.models.pdp.concepts.PdpStateChange;
 import org.onap.policy.models.pdp.concepts.PdpStatus;
 import org.onap.policy.pap.main.parameters.RequestParams;
@@ -39,7 +38,7 @@
      *
      * @throws IllegalArgumentException if a required parameter is not set
      */
-    public StateChangeReq(RequestParams params, String name, PdpMessage message) {
+    public StateChangeReq(RequestParams params, String name, PdpStateChange message) {
         super(params, name, message);
     }
 
diff --git a/main/src/main/java/org/onap/policy/pap/main/comm/msgdata/UpdateReq.java b/main/src/main/java/org/onap/policy/pap/main/comm/msgdata/UpdateReq.java
index 8efdb7c..6b04e72 100644
--- a/main/src/main/java/org/onap/policy/pap/main/comm/msgdata/UpdateReq.java
+++ b/main/src/main/java/org/onap/policy/pap/main/comm/msgdata/UpdateReq.java
@@ -26,7 +26,6 @@
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
-import org.onap.policy.models.pdp.concepts.PdpMessage;
 import org.onap.policy.models.pdp.concepts.PdpStatus;
 import org.onap.policy.models.pdp.concepts.PdpUpdate;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
@@ -48,7 +47,7 @@
      *
      * @throws IllegalArgumentException if a required parameter is not set
      */
-    public UpdateReq(RequestParams params, String name, PdpMessage message) {
+    public UpdateReq(RequestParams params, String name, PdpUpdate message) {
         super(params, name, message);
     }
 
@@ -61,10 +60,15 @@
     public String checkResponse(PdpStatus response) {
         String reason = super.checkResponse(response);
         if (reason != null) {
+            // response isn't for this PDP - don't generate notifications
             return reason;
         }
 
+        Set<ToscaPolicyIdentifier> actualSet = new HashSet<>(alwaysList(response.getPolicies()));
+        getNotifier().processResponse(getName(), actualSet);
+
         PdpUpdate message = getMessage();
+
         if (!StringUtils.equals(message.getPdpGroup(), response.getPdpGroup())) {
             return "group does not match";
         }
@@ -74,11 +78,11 @@
         }
 
         // see if the policies match
-        Set<ToscaPolicyIdentifier> set1 = new HashSet<>(alwaysList(response.getPolicies()));
-        Set<ToscaPolicyIdentifier> set2 = new HashSet<>(alwaysList(message.getPolicies()).stream()
+
+        Set<ToscaPolicyIdentifier> expectedSet = new HashSet<>(alwaysList(message.getPolicies()).stream()
                         .map(ToscaPolicy::getIdentifier).collect(Collectors.toSet()));
 
-        if (!set1.equals(set2)) {
+        if (!actualSet.equals(expectedSet)) {
             return "policies do not match";
         }
 
diff --git a/main/src/main/java/org/onap/policy/pap/main/parameters/PdpModifyRequestMapParams.java b/main/src/main/java/org/onap/policy/pap/main/parameters/PdpModifyRequestMapParams.java
index 2f74bf3..9a3e7a4 100644
--- a/main/src/main/java/org/onap/policy/pap/main/parameters/PdpModifyRequestMapParams.java
+++ b/main/src/main/java/org/onap/policy/pap/main/parameters/PdpModifyRequestMapParams.java
@@ -22,10 +22,12 @@
 
 import lombok.Getter;
 import org.onap.policy.common.endpoints.listeners.RequestIdDispatcher;
+import org.onap.policy.models.pdp.concepts.PdpMessage;
 import org.onap.policy.models.pdp.concepts.PdpStatus;
 import org.onap.policy.pap.main.PolicyModelsProviderFactoryWrapper;
 import org.onap.policy.pap.main.comm.Publisher;
 import org.onap.policy.pap.main.comm.TimerManager;
+import org.onap.policy.pap.main.notification.PolicyNotifier;
 
 
 /**
@@ -33,13 +35,14 @@
  */
 @Getter
 public class PdpModifyRequestMapParams {
-    private Publisher publisher;
+    private Publisher<PdpMessage> pdpPublisher;
     private RequestIdDispatcher<PdpStatus> responseDispatcher;
     private Object modifyLock;
     private PdpParameters params;
     private TimerManager updateTimers;
     private TimerManager stateChangeTimers;
     private PolicyModelsProviderFactoryWrapper daoFactory;
+    private PolicyNotifier policyNotifier;
 
     public PdpModifyRequestMapParams setParams(PdpParameters params) {
         this.params = params;
@@ -61,8 +64,13 @@
         return this;
     }
 
-    public PdpModifyRequestMapParams setPublisher(Publisher publisher) {
-        this.publisher = publisher;
+    public PdpModifyRequestMapParams setPolicyNotifier(PolicyNotifier policyNotifier) {
+        this.policyNotifier = policyNotifier;
+        return this;
+    }
+
+    public PdpModifyRequestMapParams setPdpPublisher(Publisher<PdpMessage> pdpPublisher) {
+        this.pdpPublisher = pdpPublisher;
         return this;
     }
 
@@ -80,7 +88,7 @@
      * Validates the parameters.
      */
     public void validate() {
-        if (publisher == null) {
+        if (pdpPublisher == null) {
             throw new IllegalArgumentException("missing publisher");
         }
 
@@ -103,5 +111,13 @@
         if (stateChangeTimers == null) {
             throw new IllegalArgumentException("missing stateChangeTimers");
         }
+
+        if (daoFactory == null) {
+            throw new IllegalArgumentException("missing DAO factory");
+        }
+
+        if (policyNotifier == null) {
+            throw new IllegalArgumentException("missing policy notifier");
+        }
     }
 }
diff --git a/main/src/main/java/org/onap/policy/pap/main/parameters/RequestParams.java b/main/src/main/java/org/onap/policy/pap/main/parameters/RequestParams.java
index b908386..57d4dda 100644
--- a/main/src/main/java/org/onap/policy/pap/main/parameters/RequestParams.java
+++ b/main/src/main/java/org/onap/policy/pap/main/parameters/RequestParams.java
@@ -22,6 +22,7 @@
 
 import lombok.Getter;
 import org.onap.policy.common.endpoints.listeners.RequestIdDispatcher;
+import org.onap.policy.models.pdp.concepts.PdpMessage;
 import org.onap.policy.models.pdp.concepts.PdpStatus;
 import org.onap.policy.pap.main.comm.Publisher;
 import org.onap.policy.pap.main.comm.TimerManager;
@@ -32,15 +33,15 @@
  */
 @Getter
 public class RequestParams {
-    private Publisher publisher;
+    private Publisher<PdpMessage> pdpPublisher;
     private RequestIdDispatcher<PdpStatus> responseDispatcher;
     private Object modifyLock;
     private TimerManager timers;
     private int maxRetryCount;
 
 
-    public RequestParams setPublisher(Publisher publisher) {
-        this.publisher = publisher;
+    public RequestParams setPdpPublisher(Publisher<PdpMessage> publisher) {
+        this.pdpPublisher = publisher;
         return this;
     }
 
@@ -68,7 +69,7 @@
      * Validates the parameters.
      */
     public void validate() {
-        if (publisher == null) {
+        if (pdpPublisher == null) {
             throw new IllegalArgumentException("missing publisher");
         }
 
diff --git a/main/src/main/java/org/onap/policy/pap/main/rest/depundep/PdpGroupDeleteProvider.java b/main/src/main/java/org/onap/policy/pap/main/rest/depundep/PdpGroupDeleteProvider.java
index 0ca3245..09ad862 100644
--- a/main/src/main/java/org/onap/policy/pap/main/rest/depundep/PdpGroupDeleteProvider.java
+++ b/main/src/main/java/org/onap/policy/pap/main/rest/depundep/PdpGroupDeleteProvider.java
@@ -21,12 +21,13 @@
 package org.onap.policy.pap.main.rest.depundep;
 
 import java.util.Iterator;
-import java.util.function.BiFunction;
+import java.util.Set;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
 import javax.ws.rs.core.Response.Status;
 import org.onap.policy.models.base.PfModelException;
+import org.onap.policy.models.pdp.concepts.Pdp;
 import org.onap.policy.models.pdp.concepts.PdpGroup;
-import org.onap.policy.models.pdp.concepts.PdpSubGroup;
 import org.onap.policy.models.pdp.enums.PdpState;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifier;
@@ -122,7 +123,7 @@
      * Returns a function that will remove the desired policy from a subgroup.
      */
     @Override
-    protected BiFunction<PdpGroup, PdpSubGroup, Boolean> makeUpdater(ToscaPolicy policy,
+    protected Updater makeUpdater(SessionData data, ToscaPolicy policy,
                     ToscaPolicyIdentifierOptVersion desiredIdent) {
 
         // construct a matcher based on whether or not the version was specified
@@ -142,6 +143,8 @@
         // return a function that will remove the policy from the subgroup
         return (group, subgroup) -> {
 
+            Set<String> pdps = subgroup.getPdpInstances().stream().map(Pdp::getInstanceId).collect(Collectors.toSet());
+
             boolean result = false;
 
             Iterator<ToscaPolicyIdentifier> iter = subgroup.getPolicies().iterator();
@@ -153,6 +156,8 @@
                     iter.remove();
                     logger.info("remove policy {} {} from subgroup {} {} count={}", ident.getName(), ident.getVersion(),
                                     group.getName(), subgroup.getPdpType(), subgroup.getPolicies().size());
+
+                    data.trackUndeploy(ident, pdps);
                 }
             }
 
diff --git a/main/src/main/java/org/onap/policy/pap/main/rest/depundep/PdpGroupDeployProvider.java b/main/src/main/java/org/onap/policy/pap/main/rest/depundep/PdpGroupDeployProvider.java
index bc3148e..1e00528 100644
--- a/main/src/main/java/org/onap/policy/pap/main/rest/depundep/PdpGroupDeployProvider.java
+++ b/main/src/main/java/org/onap/policy/pap/main/rest/depundep/PdpGroupDeployProvider.java
@@ -28,8 +28,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.BiFunction;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 import javax.ws.rs.core.Response.Status;
 import org.onap.policy.common.parameters.BeanValidationResult;
 import org.onap.policy.common.parameters.ObjectValidationResult;
@@ -269,8 +269,9 @@
      * @param dbgroup the group, as it appears within the DB
      * @param group the group being updated
      * @return {@code true} if a subgroup was removed, {@code false} otherwise
+     * @throws PfModelException if an error occurred
      */
-    private boolean notifyPdpsDelSubGroups(SessionData data, PdpGroup dbgroup, PdpGroup group) {
+    private boolean notifyPdpsDelSubGroups(SessionData data, PdpGroup dbgroup, PdpGroup group) throws PfModelException {
         boolean updated = false;
 
         // subgroups, as they appear within the updated group
@@ -284,6 +285,7 @@
                 // this subgroup no longer appears - notify its PDPs
                 updated = true;
                 notifyPdpsDelSubGroup(data, subgrp);
+                trackPdpsDelSubGroup(data, subgrp);
             }
         }
 
@@ -314,10 +316,26 @@
     }
 
     /**
+     * Tracks PDP responses when their subgroup is removed.
+     *
+     * @param data session data
+     * @param subgrp subgroup that is being removed
+     * @throws PfModelException if an error occurred
+     */
+    private void trackPdpsDelSubGroup(SessionData data, PdpSubGroup subgrp) throws PfModelException {
+        Set<String> pdps = subgrp.getPdpInstances().stream().map(Pdp::getInstanceId).collect(Collectors.toSet());
+
+        for (ToscaPolicyIdentifier policyId : subgrp.getPolicies()) {
+            data.trackUndeploy(policyId, pdps);
+        }
+    }
+
+    /**
      * Adds a new subgroup.
      *
      * @param data session data
-     * @param subgrp the subgroup to be added, updated to fully qualified versions upon return
+     * @param subgrp the subgroup to be added, updated to fully qualified versions upon
+     *        return
      * @return the validation result
      * @throws PfModelException if an error occurred
      */
@@ -339,7 +357,8 @@
      * @param data session data
      * @param dbgroup the group, from the DB, containing the subgroup
      * @param dbsub the subgroup, from the DB
-     * @param subgrp the subgroup to be updated, updated to fully qualified versions upon return
+     * @param subgrp the subgroup to be updated, updated to fully qualified versions upon
+     *        return
      * @param container container for additional validation results
      * @return {@code true} if the subgroup content was changed, {@code false} if there
      *         were no changes
@@ -356,7 +375,7 @@
         /*
          * first, apply the changes about which the PDPs care
          */
-        boolean updated = updateList(dbsub.getPolicies(), subgrp.getPolicies(), dbsub::setPolicies);
+        boolean updated = updatePolicies(data, dbsub, subgrp);
 
         // publish any changes to the PDPs
         if (updated) {
@@ -373,12 +392,40 @@
                         dbsub::setDesiredInstanceCount) || updated;
     }
 
+    private boolean updatePolicies(SessionData data, PdpSubGroup dbsub, PdpSubGroup subgrp) throws PfModelException {
+        Set<ToscaPolicyIdentifier> undeployed = new HashSet<>(dbsub.getPolicies());
+        undeployed.removeAll(subgrp.getPolicies());
+
+        Set<ToscaPolicyIdentifier> deployed = new HashSet<>(subgrp.getPolicies());
+        deployed.removeAll(dbsub.getPolicies());
+
+        if (deployed.isEmpty() && undeployed.isEmpty()) {
+            // lists are identical
+            return false;
+        }
+
+
+        Set<String> pdps = subgrp.getPdpInstances().stream().map(Pdp::getInstanceId).collect(Collectors.toSet());
+
+        for (ToscaPolicyIdentifier policyId : deployed) {
+            data.trackDeploy(policyId, pdps);
+        }
+
+        for (ToscaPolicyIdentifier policyId : undeployed) {
+            data.trackUndeploy(policyId, pdps);
+        }
+
+        dbsub.setPolicies(new ArrayList<>(subgrp.getPolicies()));
+        return true;
+    }
+
     /**
      * Performs additional validations of a subgroup.
      *
      * @param data session data
      * @param dbsub the subgroup, from the DB
-     * @param subgrp the subgroup to be validated, updated to fully qualified versions upon return
+     * @param subgrp the subgroup to be validated, updated to fully qualified versions
+     *        upon return
      * @param container container for additional validation results
      * @return {@code true} if the subgroup is valid, {@code false} otherwise
      * @throws PfModelException if an error occurred
@@ -455,7 +502,8 @@
     private ValidationResult validatePolicies(SessionData data, PdpSubGroup dbsub, PdpSubGroup subgrp)
                     throws PfModelException {
 
-        // build a map of the DB data, from policy name to (fully qualified) policy version
+        // build a map of the DB data, from policy name to (fully qualified) policy
+        // version
         Map<String, String> dbname2vers = new HashMap<>();
         if (dbsub != null) {
             dbsub.getPolicies().forEach(ident -> dbname2vers.put(ident.getName(), ident.getVersion()));
@@ -557,7 +605,7 @@
      * Adds a policy to a subgroup, if it isn't there already.
      */
     @Override
-    protected BiFunction<PdpGroup, PdpSubGroup, Boolean> makeUpdater(ToscaPolicy policy,
+    protected Updater makeUpdater(SessionData data, ToscaPolicy policy,
                     ToscaPolicyIdentifierOptVersion requestedIdent) {
 
         ToscaPolicyIdentifier desiredIdent = policy.getIdentifier();
@@ -586,6 +634,10 @@
             logger.info("add policy {} {} to subgroup {} {} count={}", desiredIdent.getName(),
                             desiredIdent.getVersion(), group.getName(), subgroup.getPdpType(),
                             subgroup.getPolicies().size());
+
+            Set<String> pdps = subgroup.getPdpInstances().stream().map(Pdp::getInstanceId).collect(Collectors.toSet());
+            data.trackDeploy(desiredIdent, pdps);
+
             return true;
         };
     }
diff --git a/main/src/main/java/org/onap/policy/pap/main/rest/depundep/ProviderBase.java b/main/src/main/java/org/onap/policy/pap/main/rest/depundep/ProviderBase.java
index 70ccd7a..fd8edf1 100644
--- a/main/src/main/java/org/onap/policy/pap/main/rest/depundep/ProviderBase.java
+++ b/main/src/main/java/org/onap/policy/pap/main/rest/depundep/ProviderBase.java
@@ -21,17 +21,13 @@
 package org.onap.policy.pap.main.rest.depundep;
 
 import java.util.Collection;
-import java.util.Collections;
-import java.util.function.BiFunction;
 import java.util.stream.Collectors;
 import javax.ws.rs.core.Response.Status;
-import org.apache.commons.lang3.tuple.Pair;
 import org.onap.policy.common.utils.services.Registry;
 import org.onap.policy.models.base.PfModelException;
 import org.onap.policy.models.base.PfModelRuntimeException;
 import org.onap.policy.models.pdp.concepts.Pdp;
 import org.onap.policy.models.pdp.concepts.PdpGroup;
-import org.onap.policy.models.pdp.concepts.PdpStateChange;
 import org.onap.policy.models.pdp.concepts.PdpSubGroup;
 import org.onap.policy.models.pdp.concepts.PdpUpdate;
 import org.onap.policy.models.provider.PolicyModelsProvider;
@@ -41,6 +37,7 @@
 import org.onap.policy.pap.main.PapConstants;
 import org.onap.policy.pap.main.PolicyModelsProviderFactoryWrapper;
 import org.onap.policy.pap.main.comm.PdpModifyRequestMap;
+import org.onap.policy.pap.main.notification.PolicyNotifier;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -70,6 +67,11 @@
     private final PdpModifyRequestMap requestMap;
 
     /**
+     * Generates policy notifications based on responses from PDPs.
+     */
+    private final PolicyNotifier notifier;
+
+    /**
      * Factory for PAP DAO.
      */
     private final PolicyModelsProviderFactoryWrapper daoFactory;
@@ -82,6 +84,7 @@
         this.updateLock = Registry.get(PapConstants.REG_PDP_MODIFY_LOCK, Object.class);
         this.requestMap = Registry.get(PapConstants.REG_PDP_MODIFY_MAP, PdpModifyRequestMap.class);
         this.daoFactory = Registry.get(PapConstants.REG_PAP_DAO_FACTORY, PolicyModelsProviderFactoryWrapper.class);
+        this.notifier = Registry.get(PapConstants.REG_POLICY_NOTIFIER, PolicyNotifier.class);
     }
 
     /**
@@ -94,19 +97,16 @@
     protected <T> void process(T request, BiConsumerWithEx<SessionData, T> processor) throws PfModelException {
 
         synchronized (updateLock) {
-            // list of requests to be published to the PDPs
-            Collection<Pair<PdpUpdate, PdpStateChange>> requests = Collections.emptyList();
+            SessionData data;
 
             try (PolicyModelsProvider dao = daoFactory.create()) {
 
-                SessionData data = new SessionData(dao);
+                data = new SessionData(dao);
                 processor.accept(data, request);
 
                 // make all of the DB updates
                 data.updateDb();
 
-                requests = data.getPdpRequests();
-
             } catch (PfModelException | PfModelRuntimeException e) {
                 logger.warn(DEPLOY_FAILED, e);
                 throw e;
@@ -116,9 +116,12 @@
                 throw new PfModelException(Status.INTERNAL_SERVER_ERROR, "request failed", e);
             }
 
+            // track responses for notification purposes
+            data.getDeployData().forEach(notifier::addDeploymentData);
+            data.getUndeployData().forEach(notifier::addUndeploymentData);
 
             // publish the requests
-            requests.forEach(pair -> requestMap.addRequest(pair.getLeft(), pair.getRight()));
+            data.getPdpRequests().forEach(pair -> requestMap.addRequest(pair.getLeft(), pair.getRight()));
         }
     }
 
@@ -140,7 +143,7 @@
                             + desiredPolicy.getName() + " " + desiredPolicy.getVersion());
         }
 
-        BiFunction<PdpGroup, PdpSubGroup, Boolean> updater = makeUpdater(policy, desiredPolicy);
+        Updater updater = makeUpdater(data, policy, desiredPolicy);
 
         for (PdpGroup group : groups) {
             upgradeGroup(data, group, updater);
@@ -152,11 +155,12 @@
      * {@code true} if the subgroup was updated, {@code false} if no update was
      * necessary/appropriate.
      *
+     * @param data session data
      * @param policy policy to be added to or removed from each subgroup
      * @param desiredPolicy request policy
      * @return a function to update a subgroup
      */
-    protected abstract BiFunction<PdpGroup, PdpSubGroup, Boolean> makeUpdater(ToscaPolicy policy,
+    protected abstract Updater makeUpdater(SessionData data, ToscaPolicy policy,
                     ToscaPolicyIdentifierOptVersion desiredPolicy);
 
     /**
@@ -180,9 +184,9 @@
      * @param data session data
      * @param group the original group, to be updated
      * @param updater function to update a group
-     * @throws PfModelRuntimeException if an error occurred
+     * @throws PfModelException if an error occurred
      */
-    private void upgradeGroup(SessionData data, PdpGroup group, BiFunction<PdpGroup, PdpSubGroup, Boolean> updater) {
+    private void upgradeGroup(SessionData data, PdpGroup group, Updater updater) throws PfModelException {
 
         boolean updated = false;
 
@@ -275,4 +279,9 @@
          */
         void accept(F firstArg, S secondArg) throws PfModelException;
     }
+
+    @FunctionalInterface
+    public static interface Updater {
+        boolean apply(PdpGroup group, PdpSubGroup subgroup) throws PfModelException;
+    }
 }
diff --git a/main/src/main/java/org/onap/policy/pap/main/rest/depundep/SessionData.java b/main/src/main/java/org/onap/policy/pap/main/rest/depundep/SessionData.java
index 437d7a1..5b2a8ee 100644
--- a/main/src/main/java/org/onap/policy/pap/main/rest/depundep/SessionData.java
+++ b/main/src/main/java/org/onap/policy/pap/main/rest/depundep/SessionData.java
@@ -23,8 +23,10 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.tuple.Pair;
@@ -38,9 +40,11 @@
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyFilter;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyFilter.ToscaPolicyFilterBuilder;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifier;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifierOptVersion;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyType;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyTypeIdentifier;
+import org.onap.policy.pap.main.notification.PolicyPdpNotificationData;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -88,6 +92,18 @@
      */
     private final Map<ToscaPolicyTypeIdentifier, ToscaPolicyType> typeCache = new HashMap<>();
 
+    /**
+     * Policies to be deployed. This is just used to build up the data, which is then
+     * passed to the notifier once the update is "committed".
+     */
+    private final Map<ToscaPolicyIdentifier, PolicyPdpNotificationData> deploy = new HashMap<>();
+
+    /**
+     * Policies to be undeployed. This is just used to build up the data, which is then
+     * passed to the notifier once the update is "committed".
+     */
+    private final Map<ToscaPolicyIdentifier, PolicyPdpNotificationData> undeploy = new HashMap<>();
+
 
     /**
      * Constructs the object.
@@ -402,4 +418,90 @@
         logger.info("deleting DB group {}", group.getName());
         dao.deletePdpGroup(group.getName());
     }
+
+    /**
+     * Adds policy deployment data.
+     *
+     * @param policyId ID of the policy being deployed
+     * @param pdps PDPs to which the policy is being deployed
+     * @throws PfModelException if an error occurred
+     */
+    protected void trackDeploy(ToscaPolicyIdentifier policyId, Collection<String> pdps) throws PfModelException {
+        trackDeploy(policyId, new HashSet<>(pdps));
+    }
+
+    /**
+     * Adds policy deployment data.
+     *
+     * @param policyId ID of the policy being deployed
+     * @param pdps PDPs to which the policy is being deployed
+     * @throws PfModelException if an error occurred
+     */
+    protected void trackDeploy(ToscaPolicyIdentifier policyId, Set<String> pdps) throws PfModelException {
+        addData(policyId, pdps, deploy, undeploy);
+    }
+
+    /**
+     * Adds policy undeployment data.
+     *
+     * @param policyId ID of the policy being undeployed
+     * @param pdps PDPs to which the policy is being undeployed
+     * @throws PfModelException if an error occurred
+     */
+    protected void trackUndeploy(ToscaPolicyIdentifier policyId, Collection<String> pdps) throws PfModelException {
+        trackUndeploy(policyId, new HashSet<>(pdps));
+    }
+
+    /**
+     * Adds policy undeployment data.
+     *
+     * @param policyId ID of the policy being undeployed
+     * @param pdps PDPs to which the policy is being undeployed
+     * @throws PfModelException if an error occurred
+     */
+    protected void trackUndeploy(ToscaPolicyIdentifier policyId, Set<String> pdps) throws PfModelException {
+        addData(policyId, pdps, undeploy, deploy);
+    }
+
+    /**
+     * Adds policy deployment/undeployment data.
+     *
+     * @param policyId ID of the policy being deployed/undeployed
+     * @param pdps PDPs to which the policy is being deployed/undeployed
+     * @param addMap map to which it should be added
+     * @param removeMap map from which it should be removed
+     * @throws PfModelException if an error occurred
+     */
+    private void addData(ToscaPolicyIdentifier policyId, Set<String> pdps,
+                    Map<ToscaPolicyIdentifier, PolicyPdpNotificationData> addMap,
+                    Map<ToscaPolicyIdentifier, PolicyPdpNotificationData> removeMap) throws PfModelException {
+
+        PolicyPdpNotificationData removeData = removeMap.get(policyId);
+        if (removeData != null) {
+            removeData.removeAll(pdps);
+        }
+
+        ToscaPolicyIdentifierOptVersion optid = new ToscaPolicyIdentifierOptVersion(policyId);
+        ToscaPolicyTypeIdentifier policyType = getPolicy(optid).getTypeIdentifier();
+
+        addMap.computeIfAbsent(policyId, key -> new PolicyPdpNotificationData(policyId, policyType)).addAll(pdps);
+    }
+
+    /**
+     * Gets the policies to be deployed.
+     *
+     * @return the policies to be deployed
+     */
+    public Collection<PolicyPdpNotificationData> getDeployData() {
+        return deploy.values();
+    }
+
+    /**
+     * Gets the policies to be undeployed.
+     *
+     * @return the policies to be undeployed
+     */
+    public Collection<PolicyPdpNotificationData> getUndeployData() {
+        return undeploy.values();
+    }
 }
diff --git a/main/src/main/java/org/onap/policy/pap/main/startstop/PapActivator.java b/main/src/main/java/org/onap/policy/pap/main/startstop/PapActivator.java
index d119044..8d2fd3e 100644
--- a/main/src/main/java/org/onap/policy/pap/main/startstop/PapActivator.java
+++ b/main/src/main/java/org/onap/policy/pap/main/startstop/PapActivator.java
@@ -31,6 +31,8 @@
 import org.onap.policy.common.parameters.ParameterService;
 import org.onap.policy.common.utils.services.Registry;
 import org.onap.policy.common.utils.services.ServiceManagerContainer;
+import org.onap.policy.models.pap.concepts.PolicyNotification;
+import org.onap.policy.models.pdp.concepts.PdpMessage;
 import org.onap.policy.models.pdp.concepts.PdpStatus;
 import org.onap.policy.models.pdp.enums.PdpMessageType;
 import org.onap.policy.pap.main.PapConstants;
@@ -41,6 +43,7 @@
 import org.onap.policy.pap.main.comm.PdpTracker;
 import org.onap.policy.pap.main.comm.Publisher;
 import org.onap.policy.pap.main.comm.TimerManager;
+import org.onap.policy.pap.main.notification.PolicyNotifier;
 import org.onap.policy.pap.main.parameters.PapParameterGroup;
 import org.onap.policy.pap.main.parameters.PdpModifyRequestMapParams;
 import org.onap.policy.pap.main.parameters.PdpParameters;
@@ -112,13 +115,15 @@
 
         final Object pdpUpdateLock = new Object();
         final PdpParameters pdpParams = papParameterGroup.getPdpParameters();
-        final AtomicReference<Publisher> pdpPub = new AtomicReference<>();
+        final AtomicReference<Publisher<PdpMessage>> pdpPub = new AtomicReference<>();
+        final AtomicReference<Publisher<PolicyNotification>> notifyPub = new AtomicReference<>();
         final AtomicReference<TimerManager> pdpUpdTimers = new AtomicReference<>();
         final AtomicReference<TimerManager> pdpStChgTimers = new AtomicReference<>();
         final AtomicReference<TimerManager> heartBeatTimers = new AtomicReference<>();
         final AtomicReference<PolicyModelsProviderFactoryWrapper> daoFactory = new AtomicReference<>();
         final AtomicReference<PdpModifyRequestMap> requestMap = new AtomicReference<>();
         final AtomicReference<RestServer> restServer = new AtomicReference<>();
+        final AtomicReference<PolicyNotifier> notifier = new AtomicReference<>();
 
         // @formatter:off
         addAction("PAP parameters",
@@ -156,11 +161,23 @@
 
         addAction("PDP publisher",
             () -> {
-                pdpPub.set(new Publisher(PapConstants.TOPIC_POLICY_PDP_PAP));
+                pdpPub.set(new Publisher<>(PapConstants.TOPIC_POLICY_PDP_PAP));
                 startThread(pdpPub.get());
             },
             () -> pdpPub.get().stop());
 
+        addAction("Policy Notification publisher",
+            () -> {
+                notifyPub.set(new Publisher<>(PapConstants.TOPIC_POLICY_NOTIFICATION));
+                startThread(notifyPub.get());
+                notifier.set(new PolicyNotifier(notifyPub.get()));
+            },
+            () -> notifyPub.get().stop());
+
+        addAction("Policy Notifier",
+            () -> Registry.register(PapConstants.REG_POLICY_NOTIFIER, notifier.get()),
+            () -> Registry.unregister(PapConstants.REG_POLICY_NOTIFIER));
+
         addAction("PDP heart beat timers",
             () -> {
                 long maxWaitHeartBeatMs = MAX_MISSED_HEARTBEATS * pdpParams.getHeartBeatMs();
@@ -194,7 +211,8 @@
                                     .setDaoFactory(daoFactory.get())
                                     .setModifyLock(pdpUpdateLock)
                                     .setParams(pdpParams)
-                                    .setPublisher(pdpPub.get())
+                                    .setPolicyNotifier(notifier.get())
+                                    .setPdpPublisher(pdpPub.get())
                                     .setResponseDispatcher(reqIdDispatcher)
                                     .setStateChangeTimers(pdpStChgTimers.get())
                                     .setUpdateTimers(pdpUpdTimers.get())));
diff --git a/main/src/test/java/org/onap/policy/pap/main/comm/CommonRequestBase.java b/main/src/test/java/org/onap/policy/pap/main/comm/CommonRequestBase.java
index 0337161..f10abdd 100644
--- a/main/src/test/java/org/onap/policy/pap/main/comm/CommonRequestBase.java
+++ b/main/src/test/java/org/onap/policy/pap/main/comm/CommonRequestBase.java
@@ -49,6 +49,7 @@
 import org.onap.policy.pap.main.comm.msgdata.RequestListener;
 import org.onap.policy.pap.main.comm.msgdata.StateChangeReq;
 import org.onap.policy.pap.main.comm.msgdata.UpdateReq;
+import org.onap.policy.pap.main.notification.PolicyNotifier;
 import org.onap.policy.pap.main.parameters.PdpModifyRequestMapParams;
 import org.onap.policy.pap.main.parameters.PdpParameters;
 import org.onap.policy.pap.main.parameters.PdpStateChangeParameters;
@@ -70,6 +71,7 @@
     protected static final int RETRIES = 1;
 
     protected Publisher<PdpMessage> publisher;
+    protected PolicyNotifier notifier;
     protected RequestIdDispatcher<PdpStatus> dispatcher;
     protected Object lock;
     protected TimerManager timers;
@@ -90,6 +92,7 @@
     @SuppressWarnings("unchecked")
     public void setUp() throws Exception {
         publisher = mock(Publisher.class);
+        notifier = mock(PolicyNotifier.class);
         dispatcher = mock(RequestIdDispatcher.class);
         lock = new Object();
         timers = mock(TimerManager.class);
@@ -121,12 +124,12 @@
         when(updateParams.getMaxRetryCount()).thenReturn(RETRIES);
         when(pdpParams.getUpdateParameters()).thenReturn(updateParams);
 
-        reqParams = new RequestParams().setMaxRetryCount(RETRIES).setModifyLock(lock).setPublisher(publisher)
+        reqParams = new RequestParams().setMaxRetryCount(RETRIES).setModifyLock(lock).setPdpPublisher(publisher)
                         .setResponseDispatcher(dispatcher).setTimers(timers);
 
-        mapParams = new PdpModifyRequestMapParams().setModifyLock(lock).setPublisher(publisher)
-                        .setResponseDispatcher(dispatcher).setDaoFactory(daoFactory).setUpdateTimers(timers)
-                        .setStateChangeTimers(timers).setParams(pdpParams);
+        mapParams = new PdpModifyRequestMapParams().setModifyLock(lock).setPdpPublisher(publisher)
+                        .setPolicyNotifier(notifier).setResponseDispatcher(dispatcher).setDaoFactory(daoFactory)
+                        .setUpdateTimers(timers).setStateChangeTimers(timers).setParams(pdpParams);
     }
 
     /**
diff --git a/main/src/test/java/org/onap/policy/pap/main/comm/PdpModifyRequestMapTest.java b/main/src/test/java/org/onap/policy/pap/main/comm/PdpModifyRequestMapTest.java
index 92f5c5f..d0bc200 100644
--- a/main/src/test/java/org/onap/policy/pap/main/comm/PdpModifyRequestMapTest.java
+++ b/main/src/test/java/org/onap/policy/pap/main/comm/PdpModifyRequestMapTest.java
@@ -305,6 +305,9 @@
         // should have stopped publishing
         verify(requests).stopPublishing();
 
+        // should have generated a notification
+        verify(notifier).removePdp(PDP1);
+
         // should have published a new update
         PdpMessage msg2 = getSingletons(3).get(1).getMessage();
         assertNotNull(msg2);
diff --git a/main/src/test/java/org/onap/policy/pap/main/comm/PdpRequestsTest.java b/main/src/test/java/org/onap/policy/pap/main/comm/PdpRequestsTest.java
index 1bf7322..ccb13fe 100644
--- a/main/src/test/java/org/onap/policy/pap/main/comm/PdpRequestsTest.java
+++ b/main/src/test/java/org/onap/policy/pap/main/comm/PdpRequestsTest.java
@@ -50,7 +50,7 @@
         update = makeUpdateReq(PDP1, MY_GROUP, MY_SUBGROUP);
         change = makeStateChangeReq(PDP1, MY_STATE);
 
-        data = new PdpRequests(PDP1);
+        data = new PdpRequests(PDP1, notifier);
     }
 
     @Test
@@ -62,6 +62,7 @@
     public void testAddSingleton() {
         data.addSingleton(update);
 
+        verify(update).setNotifier(notifier);
         verify(update).startPublishing(any());
     }
 
diff --git a/main/src/test/java/org/onap/policy/pap/main/comm/msgdata/RequestImplTest.java b/main/src/test/java/org/onap/policy/pap/main/comm/msgdata/RequestImplTest.java
index 3d90fcb..e47f879 100644
--- a/main/src/test/java/org/onap/policy/pap/main/comm/msgdata/RequestImplTest.java
+++ b/main/src/test/java/org/onap/policy/pap/main/comm/msgdata/RequestImplTest.java
@@ -383,7 +383,7 @@
 
     @Test
     public void testResetRetryCount_testBumpRetryCount() {
-        req = new MyRequest(new RequestParams().setMaxRetryCount(2).setModifyLock(lock).setPublisher(publisher)
+        req = new MyRequest(new RequestParams().setMaxRetryCount(2).setModifyLock(lock).setPdpPublisher(publisher)
                         .setResponseDispatcher(dispatcher).setTimers(timers), MY_REQ_NAME, msg);
         req.setListener(listener);
 
diff --git a/main/src/test/java/org/onap/policy/pap/main/comm/msgdata/UpdateReqTest.java b/main/src/test/java/org/onap/policy/pap/main/comm/msgdata/UpdateReqTest.java
index a798770..80ed0fb 100644
--- a/main/src/test/java/org/onap/policy/pap/main/comm/msgdata/UpdateReqTest.java
+++ b/main/src/test/java/org/onap/policy/pap/main/comm/msgdata/UpdateReqTest.java
@@ -25,9 +25,13 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Set;
 import java.util.stream.Collectors;
 import org.junit.Before;
 import org.junit.Test;
@@ -45,6 +49,7 @@
 
     /**
      * Sets up.
+     *
      * @throws Exception if an error occurs
      */
     @Before
@@ -62,6 +67,7 @@
                         update.getPolicies().stream().map(ToscaPolicy::getIdentifier).collect(Collectors.toList()));
 
         data = new UpdateReq(reqParams, MY_REQ_NAME, update);
+        data.setNotifier(notifier);
     }
 
     @Test
@@ -73,6 +79,7 @@
     @Test
     public void testCheckResponse() {
         assertNull(data.checkResponse(response));
+        verifyResponse();
 
         // both policy lists null
         update.setPolicies(null);
@@ -85,6 +92,7 @@
         response.setName(null);
 
         assertEquals("null PDP name", data.checkResponse(response));
+        verifyNoResponse();
     }
 
     @Test
@@ -92,6 +100,7 @@
         update.setName(null);
 
         assertEquals(null, data.checkResponse(response));
+        verifyResponse();
     }
 
     @Test
@@ -99,6 +108,7 @@
         response.setPdpGroup(DIFFERENT);
 
         assertEquals("group does not match", data.checkResponse(response));
+        verifyResponse();
     }
 
     @Test
@@ -106,6 +116,7 @@
         response.setPdpSubgroup(DIFFERENT);
 
         assertEquals("subgroup does not match", data.checkResponse(response));
+        verifyResponse();
     }
 
     @Test
@@ -116,6 +127,7 @@
         response.setPolicies(policies.stream().map(ToscaPolicy::getIdentifier).collect(Collectors.toList()));
 
         assertEquals("policies do not match", data.checkResponse(response));
+        verifyResponse();
     }
 
     @Test
@@ -123,6 +135,7 @@
         update.setPolicies(null);
 
         assertEquals("policies do not match", data.checkResponse(response));
+        verifyResponse();
     }
 
     @Test
@@ -130,6 +143,7 @@
         response.setPolicies(null);
 
         assertEquals("policies do not match", data.checkResponse(response));
+        verifyResponse();
     }
 
     @Test
@@ -222,6 +236,16 @@
         assertTrue(data.getPriority() > new StateChangeReq(reqParams, MY_REQ_NAME, new PdpStateChange()).getPriority());
     }
 
+    @SuppressWarnings("unchecked")
+    private void verifyResponse() {
+        verify(notifier).processResponse(any(), any(Set.class));
+    }
+
+    @SuppressWarnings("unchecked")
+    private void verifyNoResponse() {
+        verify(notifier, never()).processResponse(any(), any(Set.class));
+    }
+
     /**
      * Makes an update message.
      *
diff --git a/main/src/test/java/org/onap/policy/pap/main/parameters/TestPdpModifyRequestMapParams.java b/main/src/test/java/org/onap/policy/pap/main/parameters/TestPdpModifyRequestMapParams.java
index b9dde72..629cf24 100644
--- a/main/src/test/java/org/onap/policy/pap/main/parameters/TestPdpModifyRequestMapParams.java
+++ b/main/src/test/java/org/onap/policy/pap/main/parameters/TestPdpModifyRequestMapParams.java
@@ -27,18 +27,23 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.onap.policy.common.endpoints.listeners.RequestIdDispatcher;
+import org.onap.policy.models.pdp.concepts.PdpMessage;
 import org.onap.policy.models.pdp.concepts.PdpStatus;
+import org.onap.policy.pap.main.PolicyModelsProviderFactoryWrapper;
 import org.onap.policy.pap.main.comm.Publisher;
 import org.onap.policy.pap.main.comm.TimerManager;
+import org.onap.policy.pap.main.notification.PolicyNotifier;
 
 public class TestPdpModifyRequestMapParams {
     private PdpModifyRequestMapParams params;
-    private Publisher pub;
+    private Publisher<PdpMessage> pub;
     private RequestIdDispatcher<PdpStatus> disp;
     private Object lock;
     private PdpParameters pdpParams;
     private TimerManager updTimers;
     private TimerManager stateTimers;
+    private PolicyModelsProviderFactoryWrapper dao;
+    private PolicyNotifier notifier;
 
     /**
      * Sets up the objects and creates an empty {@link #params}.
@@ -52,19 +57,24 @@
         pdpParams = mock(PdpParameters.class);
         updTimers = mock(TimerManager.class);
         stateTimers = mock(TimerManager.class);
+        dao = mock(PolicyModelsProviderFactoryWrapper.class);
+        notifier = mock(PolicyNotifier.class);
 
-        params = new PdpModifyRequestMapParams().setModifyLock(lock).setPublisher(pub).setResponseDispatcher(disp)
-                        .setParams(pdpParams).setStateChangeTimers(stateTimers).setUpdateTimers(updTimers);
+        params = new PdpModifyRequestMapParams().setModifyLock(lock).setPdpPublisher(pub).setResponseDispatcher(disp)
+                        .setParams(pdpParams).setStateChangeTimers(stateTimers).setUpdateTimers(updTimers)
+                        .setDaoFactory(dao).setPolicyNotifier(notifier);
     }
 
     @Test
     public void testGettersSetters() {
-        assertSame(pub, params.getPublisher());
+        assertSame(pub, params.getPdpPublisher());
         assertSame(disp, params.getResponseDispatcher());
         assertSame(lock, params.getModifyLock());
         assertSame(pdpParams, params.getParams());
         assertSame(updTimers, params.getUpdateTimers());
         assertSame(stateTimers, params.getStateChangeTimers());
+        assertSame(dao, params.getDaoFactory());
+        assertSame(notifier, params.getPolicyNotifier());
     }
 
     @Test
@@ -75,7 +85,7 @@
 
     @Test
     public void testValidate_MissingPublisher() {
-        assertThatIllegalArgumentException().isThrownBy(() -> params.setPublisher(null).validate())
+        assertThatIllegalArgumentException().isThrownBy(() -> params.setPdpPublisher(null).validate())
                         .withMessageContaining("publisher");
     }
 
@@ -108,4 +118,16 @@
         assertThatIllegalArgumentException().isThrownBy(() -> params.setUpdateTimers(null).validate())
                         .withMessageContaining("update");
     }
+
+    @Test
+    public void testValidate_MissingDaoFactory() {
+        assertThatIllegalArgumentException().isThrownBy(() -> params.setDaoFactory(null).validate())
+                        .withMessageContaining("DAO");
+    }
+
+    @Test
+    public void testValidate_MissingNotifier() {
+        assertThatIllegalArgumentException().isThrownBy(() -> params.setPolicyNotifier(null).validate())
+                        .withMessageContaining("notifier");
+    }
 }
diff --git a/main/src/test/java/org/onap/policy/pap/main/parameters/TestRequestParams.java b/main/src/test/java/org/onap/policy/pap/main/parameters/TestRequestParams.java
index b4855e7..7c5a395 100644
--- a/main/src/test/java/org/onap/policy/pap/main/parameters/TestRequestParams.java
+++ b/main/src/test/java/org/onap/policy/pap/main/parameters/TestRequestParams.java
@@ -28,6 +28,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.onap.policy.common.endpoints.listeners.RequestIdDispatcher;
+import org.onap.policy.models.pdp.concepts.PdpMessage;
 import org.onap.policy.models.pdp.concepts.PdpStatus;
 import org.onap.policy.pap.main.comm.Publisher;
 import org.onap.policy.pap.main.comm.TimerManager;
@@ -36,7 +37,7 @@
     private static final int RETRIES = 1;
 
     private RequestParams params;
-    private Publisher pub;
+    private Publisher<PdpMessage> pub;
     private RequestIdDispatcher<PdpStatus> disp;
     private Object lock;
     private TimerManager timers;
@@ -52,15 +53,15 @@
         lock = new Object();
         timers = mock(TimerManager.class);
 
-        params = new RequestParams().setModifyLock(lock).setPublisher(pub).setResponseDispatcher(disp).setTimers(timers)
-                        .setMaxRetryCount(RETRIES);
+        params = new RequestParams().setModifyLock(lock).setPdpPublisher(pub).setResponseDispatcher(disp)
+                        .setTimers(timers).setMaxRetryCount(RETRIES);
     }
 
     @Test
     public void testGettersSetters() {
-        assertSame(params, params.setModifyLock(lock).setPublisher(pub).setResponseDispatcher(disp));
+        assertSame(params, params.setModifyLock(lock).setPdpPublisher(pub).setResponseDispatcher(disp));
 
-        assertSame(pub, params.getPublisher());
+        assertSame(pub, params.getPdpPublisher());
         assertSame(disp, params.getResponseDispatcher());
         assertSame(lock, params.getModifyLock());
         assertSame(timers, params.getTimers());
@@ -87,7 +88,7 @@
 
     @Test
     public void testValidate_MissingPublisher() {
-        assertThatIllegalArgumentException().isThrownBy(() -> params.setPublisher(null).validate())
+        assertThatIllegalArgumentException().isThrownBy(() -> params.setPdpPublisher(null).validate())
                         .withMessageContaining("publisher");
     }
 
diff --git a/main/src/test/java/org/onap/policy/pap/main/rest/depundep/ProviderSuper.java b/main/src/test/java/org/onap/policy/pap/main/rest/depundep/ProviderSuper.java
index 2fca684..3f29fb5 100644
--- a/main/src/test/java/org/onap/policy/pap/main/rest/depundep/ProviderSuper.java
+++ b/main/src/test/java/org/onap/policy/pap/main/rest/depundep/ProviderSuper.java
@@ -52,6 +52,7 @@
 import org.onap.policy.pap.main.PapConstants;
 import org.onap.policy.pap.main.PolicyModelsProviderFactoryWrapper;
 import org.onap.policy.pap.main.comm.PdpModifyRequestMap;
+import org.onap.policy.pap.main.notification.PolicyNotifier;
 
 /**
  * Super class for TestPdpGroupDeployProviderXxx classes.
@@ -62,6 +63,9 @@
     @Mock
     protected PolicyModelsProvider dao;
 
+    @Mock
+    protected PolicyNotifier notifier;
+
 
     /**
      * Used to capture input to dao.updatePdpGroups() and dao.createPdpGroups().
@@ -103,6 +107,7 @@
         Registry.register(PapConstants.REG_PDP_MODIFY_LOCK, lockit);
         Registry.register(PapConstants.REG_PDP_MODIFY_MAP, reqmap);
         Registry.register(PapConstants.REG_PAP_DAO_FACTORY, daofact);
+        Registry.register(PapConstants.REG_POLICY_NOTIFIER, notifier);
     }
 
     protected void assertGroup(List<PdpGroup> groups, String name) {
diff --git a/main/src/test/java/org/onap/policy/pap/main/rest/depundep/TestPdpGroupDeleteProvider.java b/main/src/test/java/org/onap/policy/pap/main/rest/depundep/TestPdpGroupDeleteProvider.java
index a577e07..cac1680 100644
--- a/main/src/test/java/org/onap/policy/pap/main/rest/depundep/TestPdpGroupDeleteProvider.java
+++ b/main/src/test/java/org/onap/policy/pap/main/rest/depundep/TestPdpGroupDeleteProvider.java
@@ -26,8 +26,8 @@
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -35,11 +35,14 @@
 
 import java.util.Arrays;
 import java.util.List;
-import java.util.function.BiFunction;
+import java.util.Set;
 import javax.ws.rs.core.Response.Status;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
 import org.onap.policy.common.utils.services.Registry;
 import org.onap.policy.models.base.PfModelException;
 import org.onap.policy.models.pdp.concepts.PdpGroup;
@@ -48,17 +51,23 @@
 import org.onap.policy.models.pdp.enums.PdpState;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifier;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifierOptVersion;
+import org.onap.policy.pap.main.rest.depundep.ProviderBase.Updater;
 
 public class TestPdpGroupDeleteProvider extends ProviderSuper {
     private static final String EXPECTED_EXCEPTION = "expected exception";
     private static final String GROUP1_NAME = "groupA";
 
-    private MyProvider prov;
+    @Mock
     private SessionData session;
+
+    @Captor
+    private ArgumentCaptor<Set<String>> pdpCaptor;
+
+    private MyProvider prov;
     private ToscaPolicyIdentifierOptVersion optIdent;
     private ToscaPolicyIdentifierOptVersion fullIdent;
     private ToscaPolicyIdentifier ident;
-    private BiFunction<PdpGroup, PdpSubGroup, Boolean> updater;
+    private Updater updater;
 
 
     @AfterClass
@@ -76,14 +85,13 @@
 
         super.setUp();
 
-        session = mock(SessionData.class);
         ident = policy1.getIdentifier();
         optIdent = new ToscaPolicyIdentifierOptVersion(ident.getName(), null);
         fullIdent = new ToscaPolicyIdentifierOptVersion(ident.getName(), ident.getVersion());
 
         prov = new MyProvider();
 
-        updater = prov.makeUpdater(policy1, fullIdent);
+        updater = prov.makeUpdater(session, policy1, fullIdent);
     }
 
     @Test
@@ -215,7 +223,7 @@
     }
 
     @Test
-    public void testMakeUpdater_WithVersion() {
+    public void testMakeUpdater_WithVersion() throws PfModelException {
         /*
          * this group has two matching policies and one policy with a different name.
          */
@@ -230,10 +238,13 @@
         // identified policy should have been removed
         assertEquals(origSize - 1, subgroup.getPolicies().size());
         assertFalse(subgroup.getPolicies().contains(ident));
+
+        verify(session).trackUndeploy(eq(ident), pdpCaptor.capture());
+        assertEquals("[pdpA]", pdpCaptor.getValue().toString());
     }
 
     @Test
-    public void testMakeUpdater_NullVersion() {
+    public void testMakeUpdater_NullVersion() throws PfModelException {
         /*
          * this group has two matching policies and one policy with a different name.
          */
@@ -243,7 +254,7 @@
         int origSize = subgroup.getPolicies().size();
 
         // invoke updater - matching the name, but with a null (i.e., wild-card) version
-        updater = prov.makeUpdater(policy1, optIdent);
+        updater = prov.makeUpdater(session, policy1, optIdent);
         assertTrue(updater.apply(group, subgroup));
 
         // identified policy should have been removed
@@ -252,7 +263,7 @@
     }
 
     @Test
-    public void testMakeUpdater_NotFound() {
+    public void testMakeUpdater_NotFound() throws PfModelException {
         /*
          * this group has one policy with a different name and one with a different
          * version, but not the policy of interest.
diff --git a/main/src/test/java/org/onap/policy/pap/main/rest/depundep/TestPdpGroupDeployProvider.java b/main/src/test/java/org/onap/policy/pap/main/rest/depundep/TestPdpGroupDeployProvider.java
index 90ea165..c2d368a 100644
--- a/main/src/test/java/org/onap/policy/pap/main/rest/depundep/TestPdpGroupDeployProvider.java
+++ b/main/src/test/java/org/onap/policy/pap/main/rest/depundep/TestPdpGroupDeployProvider.java
@@ -35,11 +35,13 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.TreeMap;
+import java.util.TreeSet;
 import java.util.stream.Collectors;
 import javax.ws.rs.core.Response.Status;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.onap.policy.common.utils.services.Registry;
 import org.onap.policy.models.base.PfModelException;
 import org.onap.policy.models.base.PfModelRuntimeException;
@@ -53,6 +55,7 @@
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifier;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyTypeIdentifier;
+import org.onap.policy.pap.main.notification.PolicyPdpNotificationData;
 
 public class TestPdpGroupDeployProvider extends ProviderSuper {
     private static final String EXPECTED_EXCEPTION = "expected exception";
@@ -279,6 +282,14 @@
 
         assertEquals(newgrp.toString(), dbgroup.toString());
 
+        // no deployment notifications
+        verify(notifier, never()).addDeploymentData(any());
+
+        // should have notified of deleted subgroup's policies/PDPs
+        ArgumentCaptor<PolicyPdpNotificationData> captor = ArgumentCaptor.forClass(PolicyPdpNotificationData.class);
+        verify(notifier).addUndeploymentData(captor.capture());
+        assertDeploymentData(captor, policy1.getIdentifier(), "[pdpB, pdpD]");
+
         // this requires a PDP UPDATE message
         List<PdpUpdate> pdpUpdates = getUpdateRequests(2);
         assertEquals(2, pdpUpdates.size());
@@ -540,16 +551,23 @@
 
     @Test
     public void testUpdateSubGroup_Policies() throws Exception {
-        PdpGroups groups = loadPdpGroups("createGroups.json");
+        PdpGroups groups = loadPdpGroups("createGroupsDelPolicy.json");
         PdpGroup newgrp = groups.getGroups().get(0);
         PdpGroup group = new PdpGroup(newgrp);
         when(dao.getPdpGroups(group.getName())).thenReturn(Arrays.asList(group));
 
         PdpSubGroup subgrp = newgrp.getPdpSubgroups().get(0);
-        subgrp.getPolicies().add(new ToscaPolicyIdentifier(POLICY2_NAME, POLICY1_VERSION));
+
+        // delete second policy
+        subgrp.setPolicies(subgrp.getPolicies().subList(0, 1));
+
+        // add new policy
+        ToscaPolicyIdentifier policyId2 = new ToscaPolicyIdentifier(POLICY2_NAME, POLICY1_VERSION);
+        subgrp.getPolicies().add(policyId2);
 
         when(dao.getFilteredPolicyList(any())).thenReturn(loadPolicies("createGroupNewPolicy.json"))
                         .thenReturn(loadPolicies("daoPolicyList.json"))
+                        .thenReturn(loadPolicies("daoPolicyListDelPolicy.json"))
                         .thenReturn(loadPolicies("createGroupNewPolicy.json"));
 
         prov.createOrUpdateGroups(groups);
@@ -559,11 +577,43 @@
 
         assertEquals(newgrp.toString(), group.toString());
 
+        // should have notified of added policy/PDPs
+        ArgumentCaptor<PolicyPdpNotificationData> captor = ArgumentCaptor.forClass(PolicyPdpNotificationData.class);
+        verify(notifier).addDeploymentData(captor.capture());
+        assertDeploymentData(captor, policyId2, "[pdpA]");
+
+        // should have notified of deleted policy/PDPs
+        captor = ArgumentCaptor.forClass(PolicyPdpNotificationData.class);
+        verify(notifier).addUndeploymentData(captor.capture());
+        assertDeploymentData(captor, new ToscaPolicyIdentifier("ToBeDeleted", POLICY1_VERSION), "[pdpA]");
+
         // this requires a PDP UPDATE message
         assertGroupUpdate(group, subgrp);
     }
 
     @Test
+    public void testUpdateSubGroup_Unchanged() throws Exception {
+        PdpGroups groups = loadPdpGroups("createGroups.json");
+        PdpGroup newgrp = groups.getGroups().get(0);
+        PdpGroup group = new PdpGroup(newgrp);
+        when(dao.getPdpGroups(group.getName())).thenReturn(Arrays.asList(group));
+
+        prov.createOrUpdateGroups(groups);
+
+        Collections.sort(newgrp.getPdpSubgroups().get(0).getPolicies());
+        Collections.sort(group.getPdpSubgroups().get(0).getPolicies());
+
+        assertEquals(newgrp.toString(), group.toString());
+
+        // no notifications
+        verify(notifier, never()).addDeploymentData(any());
+        verify(notifier, never()).addUndeploymentData(any());
+
+        // no group updates
+        assertNoGroupAction();
+    }
+
+    @Test
     public void testUpdateSubGroup_PolicyVersionMismatch() throws Exception {
         PdpGroups groups = loadPdpGroups("createGroups.json");
         PdpGroup newgrp = groups.getGroups().get(0);
@@ -662,6 +712,14 @@
         List<PdpUpdate> requests = getUpdateRequests(2);
         assertUpdate(requests, GROUP1_NAME, PDP2_TYPE, PDP2);
         assertUpdate(requests, GROUP1_NAME, PDP4_TYPE, PDP4);
+
+        // should have notified of added policy/PDPs
+        ArgumentCaptor<PolicyPdpNotificationData> captor = ArgumentCaptor.forClass(PolicyPdpNotificationData.class);
+        verify(notifier).addDeploymentData(captor.capture());
+        assertDeploymentData(captor, policy1.getIdentifier(), "[pdpB, pdpD]");
+
+        // no undeployment notifications
+        verify(notifier, never()).addUndeploymentData(any());
     }
 
     @Test
@@ -741,6 +799,14 @@
         assertEquals(Arrays.asList(group), updates);
     }
 
+    private void assertDeploymentData(ArgumentCaptor<PolicyPdpNotificationData> captor, ToscaPolicyIdentifier policyId,
+                    String expectedPdps) {
+        PolicyPdpNotificationData data = captor.getValue();
+        assertEquals(policyId, data.getPolicyId());
+        assertEquals(policy1.getTypeIdentifier(), data.getPolicyType());
+        assertEquals(expectedPdps, new TreeSet<>(data.getPdps()).toString());
+    }
+
     /**
      * Loads a standard request.
      *
diff --git a/main/src/test/java/org/onap/policy/pap/main/rest/depundep/TestProviderBase.java b/main/src/test/java/org/onap/policy/pap/main/rest/depundep/TestProviderBase.java
index 3a91363..9370a7b 100644
--- a/main/src/test/java/org/onap/policy/pap/main/rest/depundep/TestProviderBase.java
+++ b/main/src/test/java/org/onap/policy/pap/main/rest/depundep/TestProviderBase.java
@@ -26,6 +26,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -34,20 +35,22 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Queue;
-import java.util.function.BiFunction;
+import java.util.TreeSet;
 import javax.ws.rs.core.Response.Status;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.onap.policy.common.utils.services.Registry;
 import org.onap.policy.models.base.PfModelException;
 import org.onap.policy.models.base.PfModelRuntimeException;
 import org.onap.policy.models.pap.concepts.PdpDeployPolicies;
 import org.onap.policy.models.pdp.concepts.PdpGroup;
-import org.onap.policy.models.pdp.concepts.PdpSubGroup;
 import org.onap.policy.models.pdp.concepts.PdpUpdate;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifier;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifierOptVersion;
+import org.onap.policy.pap.main.notification.PolicyPdpNotificationData;
 import org.powermock.reflect.Whitebox;
 
 public class TestProviderBase extends ProviderSuper {
@@ -103,6 +106,24 @@
         assertGroup(getGroupUpdates(), GROUP1_NAME);
 
         assertUpdate(getUpdateRequests(1), GROUP1_NAME, PDP1_TYPE, PDP1);
+
+        ArgumentCaptor<PolicyPdpNotificationData> captor = ArgumentCaptor.forClass(PolicyPdpNotificationData.class);
+        verify(notifier, times(2)).addDeploymentData(captor.capture());
+        assertNotifier(captor, PDP1, PDP3);
+
+        captor = ArgumentCaptor.forClass(PolicyPdpNotificationData.class);
+        verify(notifier, times(2)).addUndeploymentData(captor.capture());
+        assertNotifier(captor, PDP2, PDP4);
+    }
+
+    private void assertNotifier(ArgumentCaptor<PolicyPdpNotificationData> captor, String firstPdp, String secondPdp) {
+        assertEquals(1, captor.getAllValues().get(0).getPdps().size());
+        assertEquals(1, captor.getAllValues().get(1).getPdps().size());
+
+        // ensure the order by using a TreeSet
+        TreeSet<String> pdps = new TreeSet<>(captor.getAllValues().get(0).getPdps());
+        pdps.addAll(captor.getAllValues().get(1).getPdps());
+        assertEquals("[" + firstPdp + ", " + secondPdp + "]", pdps.toString());
     }
 
     @Test
@@ -345,13 +366,20 @@
         }
 
         @Override
-        protected BiFunction<PdpGroup, PdpSubGroup, Boolean> makeUpdater(ToscaPolicy policy,
+        protected Updater makeUpdater(SessionData data, ToscaPolicy policy,
                         ToscaPolicyIdentifierOptVersion desiredPolicy) {
 
             return (group, subgroup) -> {
                 if (shouldUpdate.remove()) {
                     // queue indicated that the update should succeed
                     subgroup.getPolicies().add(policy.getIdentifier());
+
+                    data.trackDeploy(policy.getIdentifier(), Collections.singleton(PDP1));
+                    data.trackUndeploy(policy.getIdentifier(), Collections.singleton(PDP2));
+
+                    ToscaPolicyIdentifier ident2 = new ToscaPolicyIdentifier(POLICY1_NAME, "9.9.9");
+                    data.trackDeploy(ident2, Collections.singleton(PDP3));
+                    data.trackUndeploy(ident2, Collections.singleton(PDP4));
                     return true;
 
                 } else {
diff --git a/main/src/test/java/org/onap/policy/pap/main/rest/depundep/TestSessionData.java b/main/src/test/java/org/onap/policy/pap/main/rest/depundep/TestSessionData.java
index d7d9b67..4d353a6 100644
--- a/main/src/test/java/org/onap/policy/pap/main/rest/depundep/TestSessionData.java
+++ b/main/src/test/java/org/onap/policy/pap/main/rest/depundep/TestSessionData.java
@@ -41,6 +41,8 @@
 import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
+import java.util.TreeSet;
+import java.util.function.Supplier;
 import javax.ws.rs.core.Response.Status;
 import org.apache.commons.lang3.tuple.Pair;
 import org.junit.Before;
@@ -52,9 +54,11 @@
 import org.onap.policy.models.pdp.concepts.PdpUpdate;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyFilter;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifier;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifierOptVersion;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyType;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyTypeIdentifier;
+import org.onap.policy.pap.main.notification.PolicyPdpNotificationData;
 
 public class TestSessionData extends ProviderSuper {
     private static final String GROUP_NAME = "groupA";
@@ -533,6 +537,70 @@
         verify(dao).deletePdpGroup(group1.getName());
     }
 
+    @Test
+    public void testTrackDeploy() throws PfModelException {
+        testTrack(session::getDeployData, session::getUndeployData, session::trackDeploy);
+    }
+
+    /**
+     * Tests trackDeploy() when there is something in the undeployed list.
+     *
+     * @throws PfModelException if an error occurs
+     */
+    @Test
+    public void testTrackDeployRemoveUndeploy() throws PfModelException {
+        testTrack(session::getDeployData, session::getUndeployData, session::trackUndeploy, session::trackDeploy);
+    }
+
+    @Test
+    public void testTrackUndeploy() throws PfModelException {
+        testTrack(session::getUndeployData, session::getDeployData, session::trackUndeploy);
+    }
+
+    /**
+     * Tests trackUndeploy() when there is something in the deployed list.
+     *
+     * @throws PfModelException if an error occurs
+     */
+    @Test
+    public void testTrackUndeployRemoveUndeploy() throws PfModelException {
+        testTrack(session::getUndeployData, session::getDeployData, session::trackDeploy, session::trackUndeploy);
+    }
+
+    protected void testTrack(Supplier<Collection<PolicyPdpNotificationData>> expected,
+                    Supplier<Collection<PolicyPdpNotificationData>> unexpected, TrackEx... trackFuncs)
+                    throws PfModelException {
+
+        ToscaPolicy policy = makePolicy(POLICY_NAME, POLICY_VERSION);
+        policy.setType(POLICY_TYPE);
+        policy.setTypeVersion(POLICY_TYPE_VERSION);
+
+        when(dao.getFilteredPolicyList(any())).thenReturn(Arrays.asList(policy));
+
+        ToscaPolicyIdentifier policyId = new ToscaPolicyIdentifier(POLICY_NAME, POLICY_VERSION);
+        List<String> pdps = Arrays.asList(PDP1, PDP2);
+
+        for (TrackEx trackFunc : trackFuncs) {
+            trackFunc.accept(policyId, pdps);
+        }
+
+        // "unexpected" list should be empty of any PDPs
+        Collection<PolicyPdpNotificationData> dataList = unexpected.get();
+        assertTrue(dataList.size() <= 1);
+        if (!dataList.isEmpty()) {
+            PolicyPdpNotificationData data = dataList.iterator().next();
+            assertTrue(data.getPdps().isEmpty());
+        }
+
+        dataList = expected.get();
+        assertEquals(1, dataList.size());
+
+        PolicyPdpNotificationData data = dataList.iterator().next();
+        assertEquals(policyId, data.getPolicyId());
+        assertEquals(type, data.getPolicyType());
+        assertEquals("[pdp_1, pdp_2]", new TreeSet<>(data.getPdps()).toString());
+    }
+
     private PdpUpdate makeUpdate(String pdpName) {
         PdpUpdate update = new PdpUpdate();
 
@@ -586,4 +654,9 @@
     private String getName(Pair<PdpUpdate, PdpStateChange> pair) {
         return (pair.getKey() != null ? pair.getKey().getName() : pair.getValue().getName());
     }
+
+    @FunctionalInterface
+    private static interface TrackEx {
+        public void accept(ToscaPolicyIdentifier policyId, Collection<String> pdps) throws PfModelException;
+    }
 }
diff --git a/main/src/test/java/org/onap/policy/pap/main/startstop/TestPapActivator.java b/main/src/test/java/org/onap/policy/pap/main/startstop/TestPapActivator.java
index 13c0609..19668b3 100644
--- a/main/src/test/java/org/onap/policy/pap/main/startstop/TestPapActivator.java
+++ b/main/src/test/java/org/onap/policy/pap/main/startstop/TestPapActivator.java
@@ -36,6 +36,8 @@
 import org.onap.policy.pap.main.PapConstants;
 import org.onap.policy.pap.main.PolicyPapException;
 import org.onap.policy.pap.main.comm.PdpModifyRequestMap;
+import org.onap.policy.pap.main.comm.PdpTracker;
+import org.onap.policy.pap.main.notification.PolicyNotifier;
 import org.onap.policy.pap.main.parameters.CommonTestData;
 import org.onap.policy.pap.main.parameters.PapParameterGroup;
 import org.onap.policy.pap.main.parameters.PapParameterHandler;
@@ -92,6 +94,8 @@
         assertNotNull(Registry.get(PapConstants.REG_PDP_MODIFY_LOCK, Object.class));
         assertNotNull(Registry.get(PapConstants.REG_STATISTICS_MANAGER, PapStatisticsManager.class));
         assertNotNull(Registry.get(PapConstants.REG_PDP_MODIFY_MAP, PdpModifyRequestMap.class));
+        assertNotNull(Registry.get(PapConstants.REG_PDP_TRACKER, PdpTracker.class));
+        assertNotNull(Registry.get(PapConstants.REG_POLICY_NOTIFIER, PolicyNotifier.class));
 
         // repeat - should throw an exception
         assertThatIllegalStateException().isThrownBy(() -> activator.start());
@@ -109,6 +113,8 @@
         assertNull(Registry.getOrDefault(PapConstants.REG_PDP_MODIFY_LOCK, Object.class, null));
         assertNull(Registry.getOrDefault(PapConstants.REG_STATISTICS_MANAGER, PapStatisticsManager.class, null));
         assertNull(Registry.getOrDefault(PapConstants.REG_PDP_MODIFY_MAP, PdpModifyRequestMap.class, null));
+        assertNull(Registry.getOrDefault(PapConstants.REG_PDP_TRACKER, PdpTracker.class, null));
+        assertNull(Registry.getOrDefault(PapConstants.REG_POLICY_NOTIFIER, PolicyNotifier.class, null));
 
         // repeat - should throw an exception
         assertThatIllegalStateException().isThrownBy(() -> activator.stop());
diff --git a/main/src/test/resources/e2e/PapConfigParameters.json b/main/src/test/resources/e2e/PapConfigParameters.json
index 0c86e8f..17507bd 100644
--- a/main/src/test/resources/e2e/PapConfigParameters.json
+++ b/main/src/test/resources/e2e/PapConfigParameters.json
@@ -37,6 +37,10 @@
             "topic" : "POLICY-PDP-PAP",
             "servers" : [ "message-router" ],
             "topicCommInfrastructure" : "noop"
+        },{
+            "topic" : "POLICY-NOTIFICATION",
+            "servers" : [ "message-router" ],
+            "topicCommInfrastructure" : "noop"
         }]
     }
 }
diff --git a/main/src/test/resources/parameters/PapConfigParameters.json b/main/src/test/resources/parameters/PapConfigParameters.json
index f3dc775..1131502 100644
--- a/main/src/test/resources/parameters/PapConfigParameters.json
+++ b/main/src/test/resources/parameters/PapConfigParameters.json
@@ -37,6 +37,10 @@
             "topic" : "POLICY-PDP-PAP",
             "servers" : [ "message-router" ],
             "topicCommInfrastructure" : "noop"
+        },{
+            "topic" : "POLICY-NOTIFICATION",
+            "servers" : [ "message-router" ],
+            "topicCommInfrastructure" : "noop"
         }]
     }
 }
diff --git a/main/src/test/resources/parameters/PapConfigParametersStd.json b/main/src/test/resources/parameters/PapConfigParametersStd.json
index 309bdb0..828c1a4 100644
--- a/main/src/test/resources/parameters/PapConfigParametersStd.json
+++ b/main/src/test/resources/parameters/PapConfigParametersStd.json
@@ -37,6 +37,10 @@
             "topic" : "POLICY-PDP-PAP",
             "servers" : [ "message-router" ],
             "topicCommInfrastructure" : "noop"
+        },{
+            "topic" : "POLICY-NOTIFICATION",
+            "servers" : [ "message-router" ],
+            "topicCommInfrastructure" : "noop"
         }]
     }
 }
diff --git a/main/src/test/resources/simpleDeploy/createGroupsDelPolicy.json b/main/src/test/resources/simpleDeploy/createGroupsDelPolicy.json
new file mode 100644
index 0000000..8972997
--- /dev/null
+++ b/main/src/test/resources/simpleDeploy/createGroupsDelPolicy.json
@@ -0,0 +1,43 @@
+{
+    "groups": [
+        {
+            "name": "groupA",
+            "version": "200.2.3",
+            "description": "my description",
+            "pdpGroupState": "ACTIVE",
+            "properties": {
+                "hello": "world"
+            },
+            "pdpSubgroups": [
+                {
+                    "pdpType": "pdpTypeA",
+                    "desiredInstanceCount": 1,
+                    "properties": {
+                        "abc": "def"
+                    },
+                    "supportedPolicyTypes": [
+                        {
+                            "name": "typeA",
+                            "version": "100.2.3"
+                        }
+                    ],
+                    "pdpInstances": [
+                        {
+                            "instanceId": "pdpA"
+                        }
+                    ],
+                    "policies": [
+                        {
+                            "name": "policyA",
+                            "version": "1.2.3"
+                        },
+                        {
+                            "name": "ToBeDeleted",
+                            "version": "1.2.3"
+                        }
+                    ]
+                }
+            ]
+        }
+    ]
+}
diff --git a/main/src/test/resources/simpleDeploy/daoPolicyListDelPolicy.json b/main/src/test/resources/simpleDeploy/daoPolicyListDelPolicy.json
new file mode 100644
index 0000000..155912c
--- /dev/null
+++ b/main/src/test/resources/simpleDeploy/daoPolicyListDelPolicy.json
@@ -0,0 +1,10 @@
+{
+    "policies":  [
+        {
+            "name": "ToBeDeleted",
+            "version": "1.2.3",
+            "type": "typeA",
+            "type_version": "100.2.3"
+        }
+    ]
+}