Add trackers for generating notifications 61/97561/2
authorJim Hahn <jrh3@att.com>
Thu, 24 Oct 2019 15:17:14 +0000 (11:17 -0400)
committerJim Hahn <jrh3@att.com>
Thu, 24 Oct 2019 15:31:58 +0000 (11:31 -0400)
Issue-ID: POLICY-1841
Signed-off-by: Jim Hahn <jrh3@att.com>
Change-Id: I7d6fe29707685e4711120b6a1e8448f25870a0ef
Signed-off-by: Jim Hahn <jrh3@att.com>
main/src/main/java/org/onap/policy/pap/main/notification/PolicyCommonTracker.java [new file with mode: 0644]
main/src/main/java/org/onap/policy/pap/main/notification/PolicyDeployTracker.java [new file with mode: 0644]
main/src/main/java/org/onap/policy/pap/main/notification/PolicyUndeployTracker.java [new file with mode: 0644]
main/src/test/java/org/onap/policy/pap/main/notification/PolicyCommonSupport.java [new file with mode: 0644]
main/src/test/java/org/onap/policy/pap/main/notification/PolicyCommonTrackerTest.java [new file with mode: 0644]
main/src/test/java/org/onap/policy/pap/main/notification/PolicyDeployTrackerTest.java [new file with mode: 0644]
main/src/test/java/org/onap/policy/pap/main/notification/PolicyUndeployTrackerTest.java [new file with mode: 0644]

diff --git a/main/src/main/java/org/onap/policy/pap/main/notification/PolicyCommonTracker.java b/main/src/main/java/org/onap/policy/pap/main/notification/PolicyCommonTracker.java
new file mode 100644 (file)
index 0000000..0404e1f
--- /dev/null
@@ -0,0 +1,202 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP PAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.pap.main.notification;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.function.BiPredicate;
+import org.onap.policy.models.pap.concepts.PolicyStatus;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifier;
+
+/**
+ * Common super class for deploy and undeploy trackers.
+ */
+public abstract class PolicyCommonTracker {
+
+    /**
+     * Maps a policy id to its deployment data. The subclass determines when an entry is
+     * removed.
+     *
+     * <p/>
+     * Use a LinkedHashMap, because we'll be doing lots of iteration over the map, and
+     * iteration over a LinkedHashMap is faster than over a plain HashMap.
+     */
+    private final Map<ToscaPolicyIdentifier, PolicyTrackerData> policy2data = new LinkedHashMap<>();
+
+
+    /**
+     * Constructs the object.
+     */
+    public PolicyCommonTracker() {
+        super();
+    }
+
+    /**
+     * Adds data to the tracker.
+     *
+     * @param data data to be added to the tracker
+     */
+    public void addData(PolicyPdpNotificationData data) {
+        policy2data.computeIfAbsent(data.getPolicyId(), policyId -> new PolicyTrackerData(data.getPolicyType()))
+                        .addPdps(data.getPdps());
+    }
+
+    /**
+     * Removes a set of PDPs from all policies within the tracker.
+     *
+     * @param notifyData data identifying the policy and the PDPs to be removed from it
+     * @param statusList status messages are added here if policies become complete as a
+     *        result of this operation
+     */
+    public void removeData(PolicyPdpNotificationData notifyData, List<PolicyStatus> statusList) {
+
+        policy2data.computeIfPresent(notifyData.getPolicyId(), (policyId, data) -> {
+
+            if (!data.removePdps(notifyData.getPdps())) {
+                // not complete yet
+                return data;
+            }
+
+            // this policy is complete - notify
+            statusList.add(makeStatus(policyId, data));
+
+            return (shouldRemove(data) ? null : data);
+        });
+    }
+
+    /**
+     * Removes a PDP from all policies within the tracker.
+     *
+     * @param pdp PDP to be removed
+     * @param statusList status messages are added here if policies become complete as a
+     *        result of this operation
+     */
+    public void removePdp(String pdp, List<PolicyStatus> statusList) {
+        updateMap(statusList, (policyId, data) -> data.removePdp(pdp));
+    }
+
+    /**
+     * Processes a response from a PDP.
+     *
+     * @param pdp PDP of interest
+     * @param activePolicies policies that are still active on the PDP, as specified in
+     *        the response
+     * @param statusList status messages are added here if policies become complete as a
+     *        result of this operation
+     */
+    public void processResponse(String pdp, Collection<ToscaPolicyIdentifier> activePolicies,
+                    List<PolicyStatus> statusList) {
+        processResponse(pdp, new HashSet<>(activePolicies), statusList);
+    }
+
+    /**
+     * Processes a response from a PDP.
+     *
+     * @param pdp PDP of interest
+     * @param activePolicies policies that are still active on the PDP, as specified in
+     *        the response
+     * @param statusList status messages are added here if policies become complete as a
+     *        result of this operation
+     */
+    public void processResponse(String pdp, Set<ToscaPolicyIdentifier> activePolicies, List<PolicyStatus> statusList) {
+        updateMap(statusList, (policyId, data) -> updateData(pdp, data, activePolicies.contains(policyId)));
+    }
+
+    /**
+     * Updates the map.
+     *
+     * <p/>
+     * Note: this iterates through the whole map. While it may be more efficient to
+     * iterate through just the policies relevant to the PDP, that would complicate the
+     * code and complicate the testing. In addition, this should still perform well
+     * enough, but if not, it can always be enhanced.
+     *
+     * @param statusList status messages are added here if policies become complete as a
+     *        result of this operation
+     * @param updater function to update a policy's data. Returns {@code true} if the
+     *        policy is complete (i.e., no longer awaiting any responses)
+     */
+    private void updateMap(List<PolicyStatus> statusList,
+                    BiPredicate<ToscaPolicyIdentifier, PolicyTrackerData> updater) {
+
+        Iterator<Entry<ToscaPolicyIdentifier, PolicyTrackerData>> iter = policy2data.entrySet().iterator();
+        while (iter.hasNext()) {
+            Entry<ToscaPolicyIdentifier, PolicyTrackerData> ent = iter.next();
+
+            ToscaPolicyIdentifier policyId = ent.getKey();
+            PolicyTrackerData data = ent.getValue();
+
+            if (!updater.test(policyId, data)) {
+                // not complete yet
+                continue;
+            }
+
+            // this policy is complete - notify
+            statusList.add(makeStatus(policyId, data));
+
+            if (shouldRemove(data)) {
+                iter.remove();
+            }
+        }
+    }
+
+    /**
+     * Updates the policy data, based on a response from a PDP.
+     *
+     * @param pdp PDP whose response was just received
+     * @param data data associated with the policy of interest
+     * @param stillActive {@code true} if the policy is still active for the PDP,
+     *        {@code false} otherwise
+     * @return {@code true} if the policy is complete (i.e., no longer awaiting any
+     *         responses), {@code false} otherwise
+     */
+    protected abstract boolean updateData(String pdp, PolicyTrackerData data, boolean stillActive);
+
+    /**
+     * Determines if a policy should be removed from the tracker, based on the state of
+     * its data.
+     *
+     * @param data data associated with the policy of interest
+     * @return {@code true} if the policy should be removed from the tracker,
+     *         {@code false} otherwise
+     */
+    protected abstract boolean shouldRemove(PolicyTrackerData data);
+
+    /**
+     * Makes a status notification for the given policy.
+     *
+     * @param policyId policy ID
+     * @param data data to be used to set the status fields
+     * @return a new status notification
+     */
+    private synchronized PolicyStatus makeStatus(ToscaPolicyIdentifier policyId, PolicyTrackerData data) {
+
+        PolicyStatus status = new PolicyStatus(data.getPolicyType(), policyId);
+        data.putValuesInto(status);
+        return status;
+    }
+}
diff --git a/main/src/main/java/org/onap/policy/pap/main/notification/PolicyDeployTracker.java b/main/src/main/java/org/onap/policy/pap/main/notification/PolicyDeployTracker.java
new file mode 100644 (file)
index 0000000..17a5e21
--- /dev/null
@@ -0,0 +1,54 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP PAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.pap.main.notification;
+
+/**
+ * Tracker for policy deployments to PDPs.
+ *
+ * <p/>
+ * Policies are not removed from the internal map until all of the sets contained within
+ * the data are empty. This may be the result of a PDP being removed from the system
+ * because it is no longer responsive, or because the PDP Subgroup that contained it is
+ * deleted.
+ */
+public class PolicyDeployTracker extends PolicyCommonTracker {
+
+    /**
+     * Constructs the object.
+     */
+    public PolicyDeployTracker() {
+        super();
+    }
+
+    @Override
+    protected boolean updateData(String pdp, PolicyTrackerData data, boolean stillActive) {
+        return (stillActive ? data.success(pdp) : data.fail(pdp));
+    }
+
+    /**
+     * Returns {@code true} only when the data is <i>completely empty</i> (i.e., it has no
+     * more PDPs)
+     */
+    @Override
+    protected boolean shouldRemove(PolicyTrackerData data) {
+        return data.isEmpty();
+    }
+}
diff --git a/main/src/main/java/org/onap/policy/pap/main/notification/PolicyUndeployTracker.java b/main/src/main/java/org/onap/policy/pap/main/notification/PolicyUndeployTracker.java
new file mode 100644 (file)
index 0000000..964ff44
--- /dev/null
@@ -0,0 +1,53 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP PAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.pap.main.notification;
+
+/**
+ * Tracker for policy undeployments from PDPs.
+ *
+ * <p/>
+ * Policies are removed from the internal map when they are no longer waiting for
+ * responses from any PDPs.
+ */
+public class PolicyUndeployTracker extends PolicyCommonTracker {
+
+    /**
+     * Constructs the object.
+     */
+    public PolicyUndeployTracker() {
+        super();
+    }
+
+    @Override
+    protected boolean updateData(String pdp, PolicyTrackerData data, boolean stillActive) {
+        // note: still active means the policy wasn't undeployed, thus it's a failure
+        return (stillActive ? data.fail(pdp) : data.success(pdp));
+    }
+
+    /**
+     * Returns {@code true} only when the data is "complete" (i.e., not awaiting responses
+     * from any other PDPs).
+     */
+    @Override
+    protected boolean shouldRemove(PolicyTrackerData data) {
+        return data.isComplete();
+    }
+}
diff --git a/main/src/test/java/org/onap/policy/pap/main/notification/PolicyCommonSupport.java b/main/src/test/java/org/onap/policy/pap/main/notification/PolicyCommonSupport.java
new file mode 100644 (file)
index 0000000..14f3055
--- /dev/null
@@ -0,0 +1,94 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP PAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.pap.main.notification;
+
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Before;
+import org.onap.policy.models.pap.concepts.PolicyStatus;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifier;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyTypeIdentifier;
+
+/**
+ * Super class for policy notification test classes.
+ */
+public class PolicyCommonSupport {
+    protected static final String MAP_FIELD = "policy2data";
+    protected static final String PDP1 = "pdp-1";
+    protected static final String PDP2 = "pdp-2";
+    protected static final String PDP3 = "pdp-3";
+    protected static final String PDP4 = "pdp-4";
+
+    protected ToscaPolicyTypeIdentifier type;
+    protected ToscaPolicyIdentifier policy1;
+    protected ToscaPolicyIdentifier policy2;
+    protected ToscaPolicyIdentifier policy3;
+    protected ToscaPolicyIdentifier policy4;
+
+    /**
+     * Creates various objects.
+     */
+    @Before
+    public void setUp() {
+        type = new ToscaPolicyTypeIdentifier("my-type", "3.2.1");
+        policy1 = new ToscaPolicyIdentifier("my-id-a", "1.2.0");
+        policy2 = new ToscaPolicyIdentifier("my-id-b", "1.2.1");
+        policy3 = new ToscaPolicyIdentifier("my-id-c", "1.2.2");
+        policy4 = new ToscaPolicyIdentifier("my-id-d", "1.2.3");
+    }
+
+    /**
+     * Makes notification data.
+     *
+     * @param policyId ID of the policy with which the data should be associated
+     * @param pdps PDPs to be included within the data
+     * @return a new notification data structure
+     */
+    protected PolicyPdpNotificationData makeData(ToscaPolicyIdentifier policyId, String... pdps) {
+        PolicyPdpNotificationData data = new PolicyPdpNotificationData(policyId, type);
+        data.addAll(Arrays.asList(pdps));
+        return data;
+    }
+
+    /**
+     * Extracts the counts from the sets contained within tracker data.
+     *
+     * @param data data from which to extract the sets
+     * @return a list containing the number of successes, failures, and incomplete PDPs,
+     *         in that order
+     */
+    protected List<Integer> getCounts(PolicyTrackerData data) {
+        PolicyStatus status = new PolicyStatus();
+        data.putValuesInto(status);
+        return getCounts(status);
+    }
+
+    /**
+     * Extracts the counts from within a status notification.
+     *
+     * @param status status from which to extract the counts
+     * @return a list containing the number of successes, failures, and incomplete PDPs,
+     *         in that order
+     */
+    protected List<Integer> getCounts(PolicyStatus status) {
+        return Arrays.asList(status.getSuccessCount(), status.getFailureCount(), status.getIncompleteCount());
+    }
+}
diff --git a/main/src/test/java/org/onap/policy/pap/main/notification/PolicyCommonTrackerTest.java b/main/src/test/java/org/onap/policy/pap/main/notification/PolicyCommonTrackerTest.java
new file mode 100644 (file)
index 0000000..cfcf447
--- /dev/null
@@ -0,0 +1,297 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP PAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.pap.main.notification;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.models.pap.concepts.PolicyStatus;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifier;
+import org.powermock.reflect.Whitebox;
+
+public class PolicyCommonTrackerTest extends PolicyCommonSupport {
+
+    private MyTracker tracker;
+    private Map<ToscaPolicyIdentifier, PolicyTrackerData> map;
+
+    /**
+     * Creates various objects, including {@link #tracker}.
+     */
+    @Before
+    public void setUp() {
+        super.setUp();
+
+        tracker = new MyTracker();
+    }
+
+    @Test
+    public void testAddData() {
+        tracker.addData(makeData(policy1, PDP1, PDP2));
+        assertEquals(1, map.size());
+
+        PolicyTrackerData data = map.get(policy1);
+        assertNotNull(data);
+        assertFalse(data.isComplete());
+
+        // add same policy - should still have the same data object
+        tracker.addData(makeData(policy1, PDP1, PDP3));
+        assertEquals(1, map.size());
+        assertSame(data, map.get(policy1));
+        assertFalse(data.isComplete());
+
+        // add another policy
+        tracker.addData(makeData(policy2, PDP2));
+        assertEquals(2, map.size());
+
+        // data for policy 1 is unchanged
+        assertSame(data, map.get(policy1));
+        assertFalse(data.isComplete());
+
+        // policy 2 should have its own data
+        assertTrue(map.get(policy2) != data);
+        data = map.get(policy2);
+        assertNotNull(data);
+        assertFalse(data.isComplete());
+    }
+
+    /**
+     * Tests removeData() when the policy isn't in the map.
+     */
+    @Test
+    public void testRemoveDataUnknownPolicy() {
+        tracker.addData(makeData(policy1, PDP1, PDP2));
+        tracker.addData(makeData(policy2, PDP1, PDP3));
+
+        // remove a policy that isn't in the map
+        List<PolicyStatus> statusList = new ArrayList<>();
+        tracker.removeData(makeData(policy3, PDP1), statusList);
+        assertTrue(statusList.isEmpty());
+        assertEquals(2, map.size());
+    }
+
+    /**
+     * Tests removeData() when only some PDPs are removed from the policy.
+     */
+    @Test
+    public void testRemoveDataRemoveSomePdps() {
+        tracker.addData(makeData(policy1, PDP1, PDP2));
+        tracker.addData(makeData(policy2, PDP1, PDP3));
+
+        // remove some PDPs from a policy - no notifications and no changes to the map
+        List<PolicyStatus> statusList = new ArrayList<>();
+        tracker.removeData(makeData(policy2, PDP1), statusList);
+        assertTrue(statusList.isEmpty());
+        assertTrue(map.containsKey(policy1));
+        assertTrue(map.containsKey(policy2));
+    }
+
+    /**
+     * Tests removeData() when the subclass indicates that the policy should NOT be removed.
+     */
+    @Test
+    public void testRemoveDataDoNotRemovePolicy() {
+        tracker = new MyTracker() {
+            @Override
+            protected boolean shouldRemove(PolicyTrackerData data) {
+                return false;
+            }
+        };
+
+        tracker.addData(makeData(policy1, PDP1, PDP2));
+        tracker.addData(makeData(policy2, PDP1, PDP3));
+
+        // remove all the PDPs from one policy, but do NOT remove the policy
+        List<PolicyStatus> statusList = new ArrayList<>();
+        tracker.removeData(makeData(policy2, PDP1, PDP3), statusList);
+        assertEquals(1, statusList.size());
+        assertEquals(policy2, statusList.get(0).getPolicy());
+        assertTrue(map.containsKey(policy1));
+        assertTrue(map.containsKey(policy2));
+    }
+
+    /**
+     * Tests removeData() when the subclass indicates that the policy should be removed.
+     */
+    @Test
+    public void testRemoveDataRemovePolicy() {
+        tracker.addData(makeData(policy1, PDP1, PDP2));
+        tracker.addData(makeData(policy2, PDP1, PDP3));
+
+        // remove all the PDPs from one policy, and remove the policy
+        List<PolicyStatus> statusList = new ArrayList<>();
+        tracker.removeData(makeData(policy1, PDP1, PDP2, PDP3), statusList);
+        assertEquals(1, statusList.size());
+        assertEquals(policy1, statusList.get(0).getPolicy());
+        assertFalse(map.containsKey(policy1));
+        assertTrue(map.containsKey(policy2));
+    }
+
+    @Test
+    public void testRemovePdp() {
+        tracker.addData(makeData(policy1, PDP1));
+        tracker.addData(makeData(policy2, PDP1, PDP3));
+        tracker.addData(makeData(policy3, PDP1, PDP2, PDP3));
+        tracker.addData(makeData(policy4, PDP4, PDP2, PDP3));
+
+        List<PolicyStatus> statusList = new ArrayList<>();
+        tracker.removePdp(PDP1, statusList);
+
+        assertEquals(1, statusList.size());
+        assertEquals(policy1, statusList.get(0).getPolicy());
+
+        assertEquals(3, map.size());
+        assertFalse(map.containsKey(policy1));
+        assertEquals("[0, 0, 1]", getCounts(map.get(policy2)).toString());
+        assertEquals("[0, 0, 2]", getCounts(map.get(policy3)).toString());
+        assertEquals("[0, 0, 3]", getCounts(map.get(policy4)).toString());
+    }
+
+    @Test
+    public void testProcessResponse() {
+        tracker.addData(makeData(policy1, PDP1));
+        tracker.addData(makeData(policy2, PDP1, PDP3));
+        tracker.addData(makeData(policy3, PDP1, PDP2, PDP3));
+        tracker.addData(makeData(policy4, PDP4, PDP2, PDP3));
+
+        List<PolicyStatus> statusList = new ArrayList<>();
+        tracker.processResponse(PDP1, Arrays.asList(policy3), statusList);
+
+        assertEquals(1, statusList.size());
+        assertEquals(policy1, statusList.get(0).getPolicy());
+
+        assertEquals(3, map.size());
+        assertFalse(map.containsKey(policy1));
+        assertEquals("[0, 1, 1]", getCounts(map.get(policy2)).toString());
+        assertEquals("[1, 0, 2]", getCounts(map.get(policy3)).toString());
+        assertEquals("[0, 0, 3]", getCounts(map.get(policy4)).toString());
+    }
+
+    @Test
+    public void testUpdateMap() {
+        tracker.addData(makeData(policy1, PDP1));
+        tracker.addData(makeData(policy2, PDP1, PDP3));
+        tracker.addData(makeData(policy3, PDP1, PDP2, PDP3));
+        tracker.addData(makeData(policy4, PDP4, PDP2, PDP3));
+
+        List<PolicyStatus> statusList = new ArrayList<>();
+        tracker.processResponse(PDP1, Arrays.asList(policy3), statusList);
+
+        assertEquals(1, statusList.size());
+        assertEquals(policy1, statusList.get(0).getPolicy());
+
+        assertEquals(3, map.size());
+        assertFalse(map.containsKey(policy1));
+        assertEquals("[0, 1, 1]", getCounts(map.get(policy2)).toString());
+        assertEquals("[1, 0, 2]", getCounts(map.get(policy3)).toString());
+        assertEquals("[0, 0, 3]", getCounts(map.get(policy4)).toString());
+    }
+
+    /**
+     * Tests updateMap() when the policy should NOT be removed.
+     */
+    @Test
+    public void testUpdateMapDoNotRemove() {
+        tracker = new MyTracker() {
+            @Override
+            protected boolean shouldRemove(PolicyTrackerData data) {
+                return false;
+            }
+        };
+
+        tracker.addData(makeData(policy1, PDP1, PDP2));
+
+        // indicate that PDP2 has succeeded
+        tracker.processResponse(PDP2, Arrays.asList(policy1), new ArrayList<>(0));
+
+        // indicate that PDP1 has succeeded
+        List<PolicyStatus> statusList = new ArrayList<>();
+        tracker.processResponse(PDP1, Arrays.asList(policy1), statusList);
+
+        assertEquals(1, statusList.size());
+        assertEquals(policy1, statusList.get(0).getPolicy());
+
+        assertEquals(1, map.size());
+        assertTrue(map.containsKey(policy1));
+        assertEquals("[2, 0, 0]", getCounts(map.get(policy1)).toString());
+    }
+
+    /**
+     * Tests updateMap() when the policy SHOULD be removed.
+     */
+    @Test
+    public void testUpdateMapRemovePolicy() {
+        tracker.addData(makeData(policy1, PDP1, PDP2));
+
+        // indicate that PDP2 has succeeded
+        tracker.processResponse(PDP2, Arrays.asList(policy1), new ArrayList<>(0));
+
+        // indicate that PDP1 has succeeded
+        List<PolicyStatus> statusList = new ArrayList<>();
+        tracker.processResponse(PDP1, Arrays.asList(policy1), statusList);
+
+        assertEquals(1, statusList.size());
+        assertEquals(policy1, statusList.get(0).getPolicy());
+        assertEquals("[2, 0, 0]", getCounts(statusList.get(0)).toString());
+
+        assertTrue(map.isEmpty());
+    }
+
+    @Test
+    public void testMakeStatus() {
+        tracker.addData(makeData(policy1, PDP1, PDP2));
+
+        // indicate that PDP2 has succeeded
+        List<PolicyStatus> statusList = new ArrayList<>();
+        tracker.processResponse(PDP2, Arrays.asList(policy1), statusList);
+        assertTrue(statusList.isEmpty());
+
+        // indicate that PDP1 has failed
+        tracker.processResponse(PDP1, Arrays.asList(policy2), statusList);
+        assertEquals(1, statusList.size());
+        assertEquals("[1, 1, 0]", getCounts(statusList.get(0)).toString());
+    }
+
+    private class MyTracker extends PolicyCommonTracker {
+
+        public MyTracker() {
+            map = Whitebox.getInternalState(this, MAP_FIELD);
+        }
+
+        @Override
+        protected boolean updateData(String pdp, PolicyTrackerData data, boolean stillActive) {
+            return (stillActive ? data.success(pdp) : data.fail(pdp));
+        }
+
+        @Override
+        protected boolean shouldRemove(PolicyTrackerData data) {
+            return data.isComplete();
+        }
+    }
+}
diff --git a/main/src/test/java/org/onap/policy/pap/main/notification/PolicyDeployTrackerTest.java b/main/src/test/java/org/onap/policy/pap/main/notification/PolicyDeployTrackerTest.java
new file mode 100644 (file)
index 0000000..bd09314
--- /dev/null
@@ -0,0 +1,120 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP PAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.pap.main.notification;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+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;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.models.pap.concepts.PolicyStatus;
+
+public class PolicyDeployTrackerTest extends PolicyCommonSupport {
+
+    @Mock
+    private PolicyTrackerData data;
+
+    private PolicyDeployTracker tracker;
+
+    /**
+     * Creates various objects, including {@link #tracker}.
+     */
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        super.setUp();
+
+        tracker = new PolicyDeployTracker();
+    }
+
+    @Test
+    public void test() {
+        tracker.addData(makeData(policy1, PDP1, PDP2));
+
+        // indicate that PDP2 has succeeded
+        tracker.processResponse(PDP2, Arrays.asList(policy1), new ArrayList<>(0));
+
+        // indicate that PDP1 has succeeded
+        List<PolicyStatus> statusList = new ArrayList<>();
+        tracker.processResponse(PDP1, Arrays.asList(policy1), statusList);
+
+        assertEquals(1, statusList.size());
+        assertEquals(policy1, statusList.get(0).getPolicy());
+        assertEquals("[2, 0, 0]", getCounts(statusList.get(0)).toString());
+
+        // indicate that PDP1 has failed - should get a notification, if still in the map
+        statusList.clear();
+        tracker.processResponse(PDP1, Collections.emptyList(), statusList);
+        assertEquals(1, statusList.size());
+        assertEquals(policy1, statusList.get(0).getPolicy());
+        assertEquals("[1, 1, 0]", getCounts(statusList.get(0)).toString());
+    }
+
+    @Test
+    public void testUpdateData() {
+        // when success returns false
+        assertFalse(tracker.updateData(PDP1, data, true));
+        verify(data).success(PDP1);
+        verify(data, never()).fail(any());
+
+        // when inactive
+        assertFalse(tracker.updateData(PDP1, data, false));
+        verify(data).success(PDP1);
+        verify(data).fail(any());
+
+        // when success & fail return true
+        when(data.success(PDP1)).thenReturn(true);
+        when(data.fail(PDP1)).thenReturn(true);
+        assertTrue(tracker.updateData(PDP1, data, true));
+        verify(data, times(2)).success(PDP1);
+        verify(data, times(1)).fail(PDP1);
+
+        // when inactive
+        assertTrue(tracker.updateData(PDP1, data, false));
+        verify(data, times(2)).success(PDP1);
+        verify(data, times(2)).fail(PDP1);
+    }
+
+    @Test
+    public void testShouldRemove() {
+        // when data is complete, but not empty
+        when(data.isComplete()).thenReturn(true);
+        when(data.isEmpty()).thenReturn(false);
+        assertFalse(tracker.shouldRemove(data));
+
+        // when data is empty
+        when(data.isEmpty()).thenReturn(true);
+        assertTrue(tracker.shouldRemove(data));
+    }
+}
diff --git a/main/src/test/java/org/onap/policy/pap/main/notification/PolicyUndeployTrackerTest.java b/main/src/test/java/org/onap/policy/pap/main/notification/PolicyUndeployTrackerTest.java
new file mode 100644 (file)
index 0000000..d837cba
--- /dev/null
@@ -0,0 +1,118 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP PAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.pap.main.notification;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+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;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.models.pap.concepts.PolicyStatus;
+
+public class PolicyUndeployTrackerTest extends PolicyCommonSupport {
+
+    @Mock
+    private PolicyTrackerData data;
+
+    private PolicyUndeployTracker tracker;
+
+    /**
+     * Creates various objects, including {@link #tracker}.
+     */
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        super.setUp();
+
+        tracker = new PolicyUndeployTracker();
+    }
+
+    @Test
+    public void test() {
+        tracker.addData(makeData(policy1, PDP1, PDP2));
+
+        // indicate that PDP2 has been undeployed
+        tracker.processResponse(PDP2, Collections.emptyList(), new ArrayList<>(0));
+
+        // indicate that PDP1 has been undeployed
+        List<PolicyStatus> statusList = new ArrayList<>();
+        tracker.processResponse(PDP1, Collections.emptyList(), statusList);
+
+        assertEquals(1, statusList.size());
+        assertEquals(policy1, statusList.get(0).getPolicy());
+        assertEquals("[2, 0, 0]", getCounts(statusList.get(0)).toString());
+
+        // indicate that PDP1 has been re-deployed - should not get a notification,
+        // because policy
+        // is gone
+        statusList.clear();
+        tracker.processResponse(PDP1, Arrays.asList(policy1), statusList);
+        assertTrue(statusList.isEmpty());
+    }
+
+    @Test
+    public void testUpdateData() {
+        // when success returns false
+        assertFalse(tracker.updateData(PDP1, data, false));
+        verify(data).success(PDP1);
+        verify(data, never()).fail(any());
+
+        // when inactive
+        assertFalse(tracker.updateData(PDP1, data, true));
+        verify(data).success(PDP1);
+        verify(data).fail(any());
+
+        // when success & fail return true
+        when(data.success(PDP1)).thenReturn(true);
+        when(data.fail(PDP1)).thenReturn(true);
+        assertTrue(tracker.updateData(PDP1, data, false));
+        verify(data, times(2)).success(PDP1);
+        verify(data, times(1)).fail(PDP1);
+
+        // when inactive
+        assertTrue(tracker.updateData(PDP1, data, true));
+        verify(data, times(2)).success(PDP1);
+        verify(data, times(2)).fail(PDP1);
+    }
+
+    @Test
+    public void testShouldRemove() {
+        // when data is not complete
+        assertFalse(tracker.shouldRemove(data));
+
+        // when data is complete
+        when(data.isComplete()).thenReturn(true);
+        assertTrue(tracker.shouldRemove(data));
+    }
+}