aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJim Hahn <jrh3@att.com>2021-01-13 10:40:01 -0500
committerJim Hahn <jrh3@att.com>2021-01-13 10:41:39 -0500
commit73f14b607460ddae6416b7e0fb8854c2ab4f316e (patch)
treedcffd01604cf108c89c42291a989c3ffdbbebbb6
parent789140d611b6221d8a1c230cc02cfd46c9229633 (diff)
Add notification tracking classes
When PAP is made stateless, a new mechanism will be needed to track and generate notifications. Added some classes to facilitate that. Issue-ID: POLICY-2648 Change-Id: Ib7b707f68a557e7b306dfdd1c6e6e9abd4671ec1 Signed-off-by: Jim Hahn <jrh3@att.com>
-rw-r--r--main/src/main/java/org/onap/policy/pap/main/notification/DeploymentTracker.java202
-rw-r--r--main/src/main/java/org/onap/policy/pap/main/notification/StatusAction.java59
-rw-r--r--main/src/main/java/org/onap/policy/pap/main/notification/StatusKey.java43
-rw-r--r--main/src/test/java/org/onap/policy/pap/main/notification/DeploymentTrackerTest.java273
-rw-r--r--main/src/test/java/org/onap/policy/pap/main/notification/StatusActionTest.java55
-rw-r--r--main/src/test/java/org/onap/policy/pap/main/notification/StatusKeyTest.java43
6 files changed, 675 insertions, 0 deletions
diff --git a/main/src/main/java/org/onap/policy/pap/main/notification/DeploymentTracker.java b/main/src/main/java/org/onap/policy/pap/main/notification/DeploymentTracker.java
new file mode 100644
index 00000000..cef70a39
--- /dev/null
+++ b/main/src/main/java/org/onap/policy/pap/main/notification/DeploymentTracker.java
@@ -0,0 +1,202 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2021 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.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import org.onap.policy.models.pap.concepts.PolicyNotification;
+import org.onap.policy.models.pap.concepts.PolicyStatus;
+import org.onap.policy.models.pdp.concepts.PdpPolicyStatus;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
+
+/**
+ * Tracks policy status so that notifications can be generated.
+ */
+public class DeploymentTracker {
+ public final Map<ToscaConceptIdentifier, PolicyStatus> deployMap = new HashMap<>();
+ public final Map<ToscaConceptIdentifier, PolicyStatus> undeployMap = new HashMap<>();
+
+
+ /**
+ * Gets the status of all deployments.
+ *
+ * @return the status of all deployments
+ */
+ public Collection<PolicyStatus> getDeploymentStatus() {
+ return deployMap.values();
+ }
+
+ /**
+ * Gets the status of all undeployments.
+ *
+ * @return the status of all undeployments
+ */
+ public Collection<PolicyStatus> getUndeploymentStatus() {
+ return undeployMap.values();
+ }
+
+ /**
+ * Compares this tracking data with new tracking data, adding new policy status to a
+ * notification based on the differences.
+ *
+ * @param notif notification to which to add policy status
+ * @param newTracker new tracking data
+ */
+ public void addNotifications(PolicyNotification notif, DeploymentTracker newTracker) {
+ merge(notif.getAdded(), deployMap, newTracker.deployMap);
+ merge(notif.getDeleted(), undeployMap, newTracker.undeployMap);
+
+ // include those that no longer exist
+ addMissing(notif, newTracker);
+ }
+
+ /**
+ * Merges original tracking data with new tracking data, adding new policy status to
+ * the given list.
+ *
+ * @param list list to which to add policy status
+ * @param originalMap original tracking data
+ * @param newMap new tracking data
+ */
+ private void merge(List<PolicyStatus> list, Map<ToscaConceptIdentifier, PolicyStatus> originalMap,
+ Map<ToscaConceptIdentifier, PolicyStatus> newMap) {
+
+ for (Entry<ToscaConceptIdentifier, PolicyStatus> entry : newMap.entrySet()) {
+ ToscaConceptIdentifier policy = entry.getKey();
+ PolicyStatus newStat = entry.getValue();
+ PolicyStatus oldStat = originalMap.get(policy);
+
+ if (needNotification(oldStat, newStat)) {
+ list.add(newStat);
+ }
+ }
+ }
+
+ /**
+ * Determines if a notification is needed.
+ *
+ * @param oldStat original status, or {@code null}
+ * @param newStat new status
+ * @return {@code true} if a notification is needed for the policy, {@code false}
+ * otherwise
+ */
+ protected boolean needNotification(PolicyStatus oldStat, PolicyStatus newStat) {
+ if (oldStat == null) {
+ // new policy - need notification if it's complete now
+ return (newStat.getIncompleteCount() == 0);
+ }
+
+ if (newStat.getIncompleteCount() == 0) {
+ // don't care if the success count changes (i.e., new PDPs can be added
+ // without requiring a notification)
+ return (oldStat.getIncompleteCount() > 0 || newStat.getFailureCount() != oldStat.getFailureCount());
+ }
+
+ // something is incomplete - only notify if it was previously complete
+ return (oldStat.getIncompleteCount() == 0);
+ }
+
+ /**
+ * Adds notifications for previously deployed policies that are missing from the new
+ * tracker.
+ *
+ * @param notif notification to which to add policy status
+ * @param newTracker new tracking data
+ */
+ private void addMissing(PolicyNotification notif, DeploymentTracker newTracker) {
+ Map<ToscaConceptIdentifier, PolicyStatus> newDeployMap = newTracker.deployMap;
+
+ for (Entry<ToscaConceptIdentifier, PolicyStatus> entry : deployMap.entrySet()) {
+ if (entry.getValue().getIncompleteCount() != 0 || newDeployMap.containsKey(entry.getKey())) {
+ /*
+ * This policy deployment was previously incomplete, or it still exists in
+ * the new tracker. Either way, it needs no notification.
+ */
+ continue;
+ }
+
+ // no longer deployed
+ PolicyStatus status = entry.getValue();
+
+ // create a status with counts that are all zero
+ PolicyStatus newStatus = new PolicyStatus();
+ newStatus.setPolicyId(status.getPolicyId());
+ newStatus.setPolicyVersion(status.getPolicyVersion());
+ newStatus.setPolicyTypeId(status.getPolicyTypeId());
+ newStatus.setPolicyTypeVersion(status.getPolicyTypeVersion());
+
+ notif.getAdded().add(newStatus);
+ }
+ }
+
+ /**
+ * Adds status to the tracking data. Assumes the associated PDP/policy pair has not be
+ * added before.
+ *
+ * @param status status to be added
+ */
+ public void add(StatusAction status) {
+ if (status.getAction() == StatusAction.Action.DELETED) {
+ return;
+ }
+
+ add(status.getStatus());
+ }
+
+ /**
+ * Adds status to the tracking data. Assumes the associated PDP/policy pair has not be
+ * added before.
+ *
+ * @param status status to be added
+ */
+ public void add(PdpPolicyStatus status) {
+
+ ToscaConceptIdentifier policy = status.getPolicy();
+
+ // get the entry from the relevant map, creating an entry if necessary
+ Map<ToscaConceptIdentifier, PolicyStatus> map = (status.isDeploy() ? deployMap : undeployMap);
+
+ PolicyStatus newStat = map.computeIfAbsent(policy, key -> {
+ PolicyStatus value = new PolicyStatus();
+ value.setPolicyId(policy.getName());
+ value.setPolicyVersion(policy.getVersion());
+ value.setPolicyTypeId(status.getPolicyType().getName());
+ value.setPolicyTypeVersion(status.getPolicyType().getVersion());
+ return value;
+ });
+
+ // bump the relevant count
+ switch (status.getState()) {
+ case SUCCESS:
+ newStat.setSuccessCount(newStat.getSuccessCount() + 1);
+ break;
+ case FAILURE:
+ newStat.setFailureCount(newStat.getFailureCount() + 1);
+ break;
+ default:
+ newStat.setIncompleteCount(newStat.getIncompleteCount() + 1);
+ break;
+ }
+ }
+}
diff --git a/main/src/main/java/org/onap/policy/pap/main/notification/StatusAction.java b/main/src/main/java/org/onap/policy/pap/main/notification/StatusAction.java
new file mode 100644
index 00000000..c2f1edcf
--- /dev/null
+++ b/main/src/main/java/org/onap/policy/pap/main/notification/StatusAction.java
@@ -0,0 +1,59 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2021 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 lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import org.onap.policy.models.pdp.concepts.PdpPolicyStatus;
+
+@Getter
+@AllArgsConstructor
+public class StatusAction {
+ public enum Action {
+ // existing record; matches what is in the DB
+ UNCHANGED,
+ // new record; to be added to the DB
+ CREATED,
+ // existing record; to be updated
+ UPDATED,
+ // existing record; to be deleted
+ DELETED,
+ }
+
+ @Setter
+ private Action action;
+
+ private PdpPolicyStatus status;
+
+ /**
+ * Sets the action to indicate that a field within the contained status has changed.
+ */
+ public void setChanged() {
+ /*
+ * if it's a new record (i.e., action=CREATED), then it should remain new. In all
+ * other cases (even action=DELETED), it should be marked for update
+ */
+ if (action != Action.CREATED) {
+ action = Action.UPDATED;
+ }
+ }
+}
diff --git a/main/src/main/java/org/onap/policy/pap/main/notification/StatusKey.java b/main/src/main/java/org/onap/policy/pap/main/notification/StatusKey.java
new file mode 100644
index 00000000..27a33b05
--- /dev/null
+++ b/main/src/main/java/org/onap/policy/pap/main/notification/StatusKey.java
@@ -0,0 +1,43 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2021 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 lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import org.onap.policy.models.pdp.concepts.PdpPolicyStatus;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
+
+/**
+ * Unique key for a Policy status record.
+ */
+@Getter
+@EqualsAndHashCode
+@AllArgsConstructor
+public class StatusKey {
+ private String pdpId;
+ private ToscaConceptIdentifier policy;
+
+ public StatusKey(PdpPolicyStatus status) {
+ pdpId = status.getPdpId();
+ policy = status.getPolicy();
+ }
+}
diff --git a/main/src/test/java/org/onap/policy/pap/main/notification/DeploymentTrackerTest.java b/main/src/test/java/org/onap/policy/pap/main/notification/DeploymentTrackerTest.java
new file mode 100644
index 00000000..22eb8786
--- /dev/null
+++ b/main/src/test/java/org/onap/policy/pap/main/notification/DeploymentTrackerTest.java
@@ -0,0 +1,273 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2021 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.assertj.core.api.Assertions.assertThat;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.models.pap.concepts.PolicyNotification;
+import org.onap.policy.models.pap.concepts.PolicyStatus;
+import org.onap.policy.models.pdp.concepts.PdpPolicyStatus;
+import org.onap.policy.models.pdp.concepts.PdpPolicyStatus.PdpPolicyStatusBuilder;
+import org.onap.policy.models.pdp.concepts.PdpPolicyStatus.State;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
+
+public class DeploymentTrackerTest {
+ private static final String VERSION = "1.2.3";
+ private static final String MY_GROUP = "MyGroup";
+ private static final String MY_PDP_TYPE = "MyPdpType";
+ private static final ToscaConceptIdentifier POLICY_A = new ToscaConceptIdentifier("PolicyA", VERSION);
+ private static final ToscaConceptIdentifier POLICY_B = new ToscaConceptIdentifier("PolicyB", VERSION);
+ private static final ToscaConceptIdentifier POLICY_TYPE = new ToscaConceptIdentifier("MyPolicyType", VERSION);
+ private static final String PDP_A = "pdpA";
+ private static final String PDP_B = "pdpB";
+
+ private DeploymentTracker tracker;
+ private PdpPolicyStatusBuilder builder;
+
+ /**
+ * Sets up test objects.
+ */
+ @Before
+ public void setUp() {
+ tracker = new DeploymentTracker();
+ builder = PdpPolicyStatus.builder().deploy(true).state(State.SUCCESS).pdpGroup(MY_GROUP).pdpType(MY_PDP_TYPE)
+ .policy(POLICY_A).policyType(POLICY_TYPE).pdpId(PDP_A);
+ }
+
+ @Test
+ public void testGetDeploymentStatus() {
+ assertThat(tracker.getDeploymentStatus()).isEmpty();
+
+ tracker.add(builder.build());
+ tracker.add(builder.policy(POLICY_B).build());
+ assertThat(tracker.getDeploymentStatus()).hasSize(2);
+
+ assertThat(tracker.getUndeploymentStatus()).isEmpty();
+ }
+
+ @Test
+ public void testGetUndeploymentStatus() {
+ builder.deploy(false);
+
+ assertThat(tracker.getUndeploymentStatus()).isEmpty();
+
+ tracker.add(builder.build());
+ tracker.add(builder.policy(POLICY_B).build());
+ assertThat(tracker.getUndeploymentStatus()).hasSize(2);
+
+ assertThat(tracker.getDeploymentStatus()).isEmpty();
+ }
+
+ @Test
+ public void testAddNotifications() {
+ DeploymentTracker newTracker = new DeploymentTracker();
+
+ newTracker.add(builder.build());
+ newTracker.add(builder.policy(POLICY_B).deploy(false).build());
+
+ PolicyNotification notif = new PolicyNotification();
+ tracker.addNotifications(notif, newTracker);
+
+ assertThat(notif.getAdded()).hasSize(1);
+ assertThat(notif.getAdded().get(0).getPolicy()).isEqualTo(POLICY_A);
+
+ assertThat(notif.getDeleted()).hasSize(1);
+ assertThat(notif.getDeleted().get(0).getPolicy()).isEqualTo(POLICY_B);
+ }
+
+ @Test
+ public void testMerge() {
+ DeploymentTracker newTracker = new DeploymentTracker();
+
+ // appears in both
+ tracker.add(builder.build());
+ newTracker.add(builder.build());
+
+ // only appears in the new tracker
+ newTracker.add(builder.policy(POLICY_B).build());
+
+ PolicyNotification notif = new PolicyNotification();
+ tracker.addNotifications(notif, newTracker);
+
+ assertThat(notif.getDeleted()).isEmpty();
+
+ // only policy B should appear
+ assertThat(notif.getAdded()).hasSize(1);
+ assertThat(notif.getAdded().get(0).getPolicy()).isEqualTo(POLICY_B);
+ }
+
+ @Test
+ public void testNeedNotification() {
+ final PolicyStatus oldStat = new PolicyStatus();
+ final PolicyStatus newStat = new PolicyStatus();
+
+ // new, complete policy - notify
+ assertThat(tracker.needNotification(null, newStat)).isTrue();
+
+ // new, incomplete policy - don't notify
+ newStat.setIncompleteCount(1);
+ assertThat(tracker.needNotification(null, newStat)).isFalse();
+ newStat.setIncompleteCount(0);
+
+ // unchanged - don't notify
+ assertThat(tracker.needNotification(oldStat, newStat)).isFalse();
+
+ // was incomplete, now complete - notify
+ oldStat.setIncompleteCount(1);
+ assertThat(tracker.needNotification(oldStat, newStat)).isTrue();
+ oldStat.setIncompleteCount(0);
+
+ // was failed, now ok - notify
+ oldStat.setFailureCount(1);
+ assertThat(tracker.needNotification(oldStat, newStat)).isTrue();
+ oldStat.setFailureCount(0);
+
+ // was failed & incomplete, now complete - notify
+ oldStat.setIncompleteCount(1);
+ oldStat.setFailureCount(1);
+ assertThat(tracker.needNotification(oldStat, newStat)).isTrue();
+ oldStat.setIncompleteCount(0);
+ oldStat.setFailureCount(0);
+
+ // was complete, now incomplete - notify
+ newStat.setIncompleteCount(1);
+ assertThat(tracker.needNotification(oldStat, newStat)).isTrue();
+ newStat.setIncompleteCount(0);
+
+ // was incomplete, still incomplete - don't notify
+ newStat.setIncompleteCount(1);
+ oldStat.setIncompleteCount(1);
+ assertThat(tracker.needNotification(oldStat, newStat)).isFalse();
+ newStat.setIncompleteCount(0);
+ oldStat.setIncompleteCount(0);
+ }
+
+ @Test
+ public void testAddMissing() {
+ DeploymentTracker newTracker = new DeploymentTracker();
+
+ // appears in both, not waiting
+ tracker.add(builder.build());
+ newTracker.add(builder.build());
+
+ // appears only in the tracker, not waiting
+ tracker.add(builder.policy(POLICY_B).build());
+
+ // appears in both, waiting
+ tracker.add(builder.policy(new ToscaConceptIdentifier("PolicyX", VERSION)).state(State.WAITING).build());
+ newTracker.add(builder.build());
+
+ // appears only in the tracker, waiting
+ tracker.add(builder.policy(new ToscaConceptIdentifier("PolicyY", VERSION)).state(State.WAITING).build());
+
+ // now extract the notifications
+ PolicyNotification notif = new PolicyNotification();
+ tracker.addNotifications(notif, newTracker);
+
+ assertThat(notif.getDeleted()).isEmpty();
+
+ // only policy B should appear
+ assertThat(notif.getAdded()).hasSize(1);
+ assertThat(notif.getAdded().get(0).getPolicy()).isEqualTo(POLICY_B);
+ }
+
+ @Test
+ public void testAddStatusAction() {
+ tracker.add(new StatusAction(StatusAction.Action.DELETED, builder.build()));
+ assertThat(tracker.getDeploymentStatus()).isEmpty();
+
+ tracker.add(new StatusAction(StatusAction.Action.UNCHANGED, builder.build()));
+ tracker.add(new StatusAction(StatusAction.Action.CREATED, builder.build()));
+ tracker.add(new StatusAction(StatusAction.Action.UPDATED, builder.build()));
+
+ Collection<PolicyStatus> result = tracker.getDeploymentStatus();
+ assertThat(result).hasSize(1);
+ PolicyStatus status = result.iterator().next();
+ assertThat(status.getSuccessCount()).isEqualTo(3);
+ }
+
+ @Test
+ public void testAddPdpPolicyStatus() {
+ tracker.add(builder.build());
+ Collection<PolicyStatus> result = tracker.getDeploymentStatus();
+ assertThat(result).hasSize(1);
+ PolicyStatus status = result.iterator().next();
+
+ assertThat(status.getFailureCount()).isZero();
+ assertThat(status.getIncompleteCount()).isZero();
+ assertThat(status.getSuccessCount()).isEqualTo(1);
+ assertThat(status.getPolicy()).isEqualTo(POLICY_A);
+ assertThat(status.getPolicyType()).isEqualTo(POLICY_TYPE);
+
+ // add another, failed
+ tracker.add(builder.pdpId(PDP_B).state(State.FAILURE).build());
+ result = tracker.getDeploymentStatus();
+ assertThat(result).hasSize(1);
+ status = result.iterator().next();
+
+ assertThat(status.getFailureCount()).isEqualTo(1);
+ assertThat(status.getIncompleteCount()).isZero();
+ assertThat(status.getSuccessCount()).isEqualTo(1);
+
+ // add another, waiting
+ tracker.add(builder.pdpId(PDP_A).state(State.WAITING).build());
+ result = tracker.getDeploymentStatus();
+ assertThat(result).hasSize(1);
+ status = result.iterator().next();
+
+ assertThat(status.getFailureCount()).isEqualTo(1);
+ assertThat(status.getIncompleteCount()).isEqualTo(1);
+ assertThat(status.getSuccessCount()).isEqualTo(1);
+
+ // different policy
+ tracker.add(builder.policy(POLICY_B).pdpId(PDP_A).state(State.WAITING).build());
+ result = tracker.getDeploymentStatus();
+ assertThat(result).hasSize(2);
+
+ List<PolicyStatus> list = new ArrayList<>(result);
+ Collections.sort(list, (rec1, rec2) -> rec1.getPolicy().compareTo(rec2.getPolicy()));
+ Iterator<PolicyStatus> iter = list.iterator();
+
+ status = iter.next();
+ assertThat(status.getPolicy()).isEqualTo(POLICY_A);
+ assertThat(status.getFailureCount()).isEqualTo(1);
+ assertThat(status.getIncompleteCount()).isEqualTo(1);
+ assertThat(status.getSuccessCount()).isEqualTo(1);
+
+ status = iter.next();
+ assertThat(status.getPolicy()).isEqualTo(POLICY_B);
+ assertThat(status.getFailureCount()).isZero();
+ assertThat(status.getIncompleteCount()).isEqualTo(1);
+ assertThat(status.getSuccessCount()).isZero();
+
+ // add undeployment record
+ tracker.add(builder.deploy(false).build());
+ assertThat(tracker.getDeploymentStatus()).hasSize(2);
+ assertThat(tracker.getUndeploymentStatus()).hasSize(1);
+ }
+}
diff --git a/main/src/test/java/org/onap/policy/pap/main/notification/StatusActionTest.java b/main/src/test/java/org/onap/policy/pap/main/notification/StatusActionTest.java
new file mode 100644
index 00000000..3303a9b2
--- /dev/null
+++ b/main/src/test/java/org/onap/policy/pap/main/notification/StatusActionTest.java
@@ -0,0 +1,55 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2021 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.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Test;
+
+/**
+ * Only test the methods that are not generated by lombok.
+ */
+public class StatusActionTest {
+
+ @Test
+ public void testSetChanged() {
+ StatusAction status = new StatusAction(StatusAction.Action.UNCHANGED, null);
+
+ // new records should always remain new - action should not change
+ status.setAction(StatusAction.Action.CREATED);
+ status.setChanged();
+ assertThat(status.getAction()).isEqualTo(StatusAction.Action.CREATED);
+
+ // all other actions should change
+
+ status.setAction(StatusAction.Action.UNCHANGED);
+ status.setChanged();
+ assertThat(status.getAction()).isEqualTo(StatusAction.Action.UPDATED);
+
+ status.setAction(StatusAction.Action.DELETED);
+ status.setChanged();
+ assertThat(status.getAction()).isEqualTo(StatusAction.Action.UPDATED);
+
+ status.setAction(StatusAction.Action.UPDATED);
+ status.setChanged();
+ assertThat(status.getAction()).isEqualTo(StatusAction.Action.UPDATED);
+ }
+}
diff --git a/main/src/test/java/org/onap/policy/pap/main/notification/StatusKeyTest.java b/main/src/test/java/org/onap/policy/pap/main/notification/StatusKeyTest.java
new file mode 100644
index 00000000..558c6bc2
--- /dev/null
+++ b/main/src/test/java/org/onap/policy/pap/main/notification/StatusKeyTest.java
@@ -0,0 +1,43 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2021 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.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Test;
+import org.onap.policy.models.pdp.concepts.PdpPolicyStatus;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
+
+/**
+ * Only test the methods that are not generated by lombok.
+ */
+public class StatusKeyTest {
+
+ @Test
+ public void testStatusKeyPdpPolicyStatus() {
+ ToscaConceptIdentifier myPolicy = new ToscaConceptIdentifier();
+
+ StatusKey key = new StatusKey(PdpPolicyStatus.builder().pdpId("myPdp").policy(myPolicy).build());
+
+ assertThat(key.getPdpId()).isEqualTo("myPdp");
+ assertThat(key.getPolicy()).isSameAs(myPolicy);
+ }
+}