From 53cf24fc816b3b5702b403c3cd719cd3752936b5 Mon Sep 17 00:00:00 2001 From: Jim Hahn Date: Thu, 14 Jan 2021 08:32:40 -0500 Subject: Add class to manage policy deployment records Issue-ID: POLICY-2648 Change-Id: Icbb9545e3df6942e6f9cf98689607c461f6c8cdd Signed-off-by: Jim Hahn --- .../pap/main/notification/DeploymentStatus.java | 320 +++++++++++++ .../policy/pap/main/notification/StatusAction.java | 4 + .../policy/pap/main/notification/StatusKey.java | 2 + .../main/notification/DeploymentStatusTest.java | 529 +++++++++++++++++++++ 4 files changed, 855 insertions(+) create mode 100644 main/src/main/java/org/onap/policy/pap/main/notification/DeploymentStatus.java create mode 100644 main/src/test/java/org/onap/policy/pap/main/notification/DeploymentStatusTest.java diff --git a/main/src/main/java/org/onap/policy/pap/main/notification/DeploymentStatus.java b/main/src/main/java/org/onap/policy/pap/main/notification/DeploymentStatus.java new file mode 100644 index 00000000..ef97fae9 --- /dev/null +++ b/main/src/main/java/org/onap/policy/pap/main/notification/DeploymentStatus.java @@ -0,0 +1,320 @@ +/*- + * ============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.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.function.BiPredicate; +import java.util.stream.Collectors; +import lombok.AccessLevel; +import lombok.Getter; +import org.onap.policy.models.base.PfModelException; +import org.onap.policy.models.pap.concepts.PolicyNotification; +import org.onap.policy.models.pdp.concepts.PdpPolicyStatus; +import org.onap.policy.models.pdp.concepts.PdpPolicyStatus.State; +import org.onap.policy.models.provider.PolicyModelsProvider; +import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier; +import org.onap.policy.pap.main.notification.StatusAction.Action; + +/** + * Collection of Policy Deployment Status records. + */ +public class DeploymentStatus { + /** + * Tracks the groups that have been loaded. + */ + private final Set pdpGroupLoaded = new HashSet<>(); + + /** + * Records, mapped by PDP/Policy pair. + */ + @Getter(AccessLevel.PROTECTED) + private final Map recordMap = new HashMap<>(); + + /** + * Records the policy status so that notifications can be generated. When + * {@link #loadByGroup(String)} is invoked, records are added to this. Other than + * that, this is not updated until {@link #addNotifications(PolicyNotification)} is + * invoked. + */ + private DeploymentTracker tracker = new DeploymentTracker(); + + private PolicyModelsProvider provider; + + + /** + * Constructs the object. + * + * @param provider the provider to use to access the DB + */ + public DeploymentStatus(PolicyModelsProvider provider) { + this.provider = provider; + } + + /** + * Adds new policy status to a notification. + * + * @param notif notification to which to add policy status + */ + protected void addNotifications(PolicyNotification notif) { + DeploymentTracker newTracker = new DeploymentTracker(); + recordMap.values().forEach(newTracker::add); + + tracker.addNotifications(notif, newTracker); + + tracker = newTracker; + } + + /** + * Loads policy deployment status associated with the given PDP group. + * + * @param pdpGroup group whose records are to be loaded + * @throws PfModelException if an error occurs + */ + public void loadByGroup(String pdpGroup) throws PfModelException { + if (pdpGroupLoaded.contains(pdpGroup)) { + return; + } + + pdpGroupLoaded.add(pdpGroup); + + for (PdpPolicyStatus status : provider.getGroupPolicyStatus(pdpGroup)) { + StatusAction status2 = new StatusAction(Action.UNCHANGED, status); + recordMap.put(new StatusKey(status), status2); + tracker.add(status2); + } + } + + /** + * Flushes changes to the DB, adding policy status to the notification. + * + * @param notif notification to which to add policy status + */ + public void flush(PolicyNotification notif) { + addNotifications(notif); + deleteUndeployments(); + flush(); + } + + /** + * Flushes changes to the DB. + */ + protected void flush() { + // categorize the records + List created = new ArrayList<>(); + List updated = new ArrayList<>(); + List deleted = new ArrayList<>(); + + for (StatusAction status : recordMap.values()) { + switch (status.getAction()) { + case CREATED: + created.add(status.getStatus()); + break; + case UPDATED: + updated.add(status.getStatus()); + break; + case DELETED: + deleted.add(status.getStatus()); + break; + default: + break; + } + } + + provider.cudPolicyStatus(created, updated, deleted); + + /* + * update the records to indicate everything is now unchanged (i.e., matches what + * is in the DB) + */ + + Iterator iter = recordMap.values().iterator(); + while (iter.hasNext()) { + StatusAction status = iter.next(); + + if (status.getAction() == Action.DELETED) { + iter.remove(); + } else { + status.setAction(Action.UNCHANGED); + } + } + } + + /** + * Deletes records for any policies that have been completely undeployed. + */ + protected void deleteUndeployments() { + // identify the incomplete policies + + // @formatter:off + Set incomplete = recordMap.values().stream() + .filter(status -> status.getAction() != Action.DELETED) + .map(StatusAction::getStatus) + .filter(status -> status.getState() == State.WAITING) + .map(PdpPolicyStatus::getPolicy) + .collect(Collectors.toSet()); + // @formatter:on + + // delete if UNDEPLOYED and not incomplete + deleteDeployment((key, status) -> !status.getStatus().isDeploy() && !incomplete.contains(key.getPolicy())); + } + + /** + * Delete deployment records for a PDP. + * + * @param pdpId PDP whose records are to be deleted + */ + public void deleteDeployment(String pdpId) { + deleteDeployment((key, status) -> key.getPdpId().equals(pdpId)); + } + + /** + * Delete deployment records for a policy. + * + * @param policy policy whose records are to be deleted + * @param deploy {@code true} to delete deployment records, {@code false} to delete + * undeployment records + */ + public void deleteDeployment(ToscaConceptIdentifier policy, boolean deploy) { + deleteDeployment((key, status) -> status.getStatus().isDeploy() == deploy && key.getPolicy().equals(policy)); + } + + /** + * Delete deployment records for a policy. + * + * @param filter filter to identify records to be deleted + */ + private void deleteDeployment(BiPredicate filter) { + Iterator> iter = recordMap.entrySet().iterator(); + while (iter.hasNext()) { + Entry entry = iter.next(); + StatusKey key = entry.getKey(); + StatusAction value = entry.getValue(); + + if (filter.test(key, value)) { + if (value.getAction() == Action.CREATED) { + // it's a new record - just remove it + iter.remove(); + } else { + // it's an existing record - mark it for deletion + value.setAction(Action.DELETED); + } + } + } + } + + /** + * Deploys/undeploys a policy to a PDP. Assumes that + * {@link #deleteDeployment(ToscaConceptIdentifier, boolean)} has already been invoked + * to delete any records having the wrong "deploy" value. + * + * @param pdpId PDP to which the policy is to be deployed + * @param policy policy to be deployed + * @param policyType policy's type + * @param pdpGroup PdpGroup containing the PDP of interest + * @param pdpType PDP type (i.e., PdpSubGroup) containing the PDP of interest + * @param deploy {@code true} if the policy is being deployed, {@code false} if + * undeployed + */ + public void deploy(String pdpId, ToscaConceptIdentifier policy, ToscaConceptIdentifier policyType, String pdpGroup, + String pdpType, boolean deploy) { + + recordMap.compute(new StatusKey(pdpId, policy), (key, status) -> { + + if (status == null) { + // no record yet - create one + + // @formatter:off + return new StatusAction(Action.CREATED, PdpPolicyStatus.builder() + .pdpGroup(pdpGroup) + .pdpId(pdpId) + .pdpType(pdpType) + .policy(policy) + .policyType(policyType) + .deploy(deploy) + .state(State.WAITING) + .build()); + // @formatter:on + } + + PdpPolicyStatus status2 = status.getStatus(); + + // record already exists - see if the deployment flag should be changed + + if (status2.isDeploy() != deploy) { + // deployment flag has changed + status.setChanged(); + status2.setDeploy(deploy); + status2.setState(State.WAITING); + + + } else if (status.getAction() == Action.DELETED) { + // deployment flag is unchanged + status.setAction(Action.UPDATED); + } + + return status; + }); + } + + /** + * Indicates the deployment/undeployment of a set of policies to a PDP has completed. + * + * @param pdpId PDP of interest + * @param expectedPolicies policies that we expected to be deployed to the PDP + * @param actualPolicies policies that were actually deployed to the PDP + */ + public void completeDeploy(String pdpId, Set expectedPolicies, + Set actualPolicies) { + + for (StatusAction status : recordMap.values()) { + PdpPolicyStatus status2 = status.getStatus(); + + if (!status.getStatus().getPdpId().equals(pdpId) + || expectedPolicies.contains(status2.getPolicy()) != status2.isDeploy()) { + /* + * The policy is "expected" to be deployed, but the record is not marked + * for deployment (or vice versa), which means the expected policy is out + * of date with the DB, thus we'll ignore this policy for now. + */ + continue; + } + + State state; + if (actualPolicies.contains(status2.getPolicy())) { + state = (status.getStatus().isDeploy() ? State.SUCCESS : State.FAILURE); + } else { + state = (status.getStatus().isDeploy() ? State.FAILURE : State.SUCCESS); + } + + if (status2.getState() != state) { + status.setChanged(); + status2.setState(state); + } + } + } +} 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 index c2f1edcf..05482269 100644 --- 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 @@ -21,12 +21,16 @@ package org.onap.policy.pap.main.notification; import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; +import lombok.ToString; import org.onap.policy.models.pdp.concepts.PdpPolicyStatus; @Getter @AllArgsConstructor +@EqualsAndHashCode +@ToString public class StatusAction { public enum Action { // existing record; matches what is in the DB 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 index 27a33b05..d99e3634 100644 --- 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 @@ -23,6 +23,7 @@ package org.onap.policy.pap.main.notification; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; import org.onap.policy.models.pdp.concepts.PdpPolicyStatus; import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier; @@ -32,6 +33,7 @@ import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier; @Getter @EqualsAndHashCode @AllArgsConstructor +@ToString public class StatusKey { private String pdpId; private ToscaConceptIdentifier policy; diff --git a/main/src/test/java/org/onap/policy/pap/main/notification/DeploymentStatusTest.java b/main/src/test/java/org/onap/policy/pap/main/notification/DeploymentStatusTest.java new file mode 100644 index 00000000..e191e02b --- /dev/null +++ b/main/src/test/java/org/onap/policy/pap/main/notification/DeploymentStatusTest.java @@ -0,0 +1,529 @@ +/*- + * ============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 static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import lombok.NonNull; +import org.apache.commons.lang3.builder.CompareToBuilder; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.onap.policy.models.base.PfModelException; +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.provider.PolicyModelsProvider; +import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier; +import org.onap.policy.pap.main.notification.StatusAction.Action; + +public class DeploymentStatusTest { + + private static final String VERSION = "1.2.3"; + private static final @NonNull String GROUP_A = "groupA"; + private static final String PDP_A = "pdpA"; + private static final String PDP_B = "pdpB"; + private static final String PDP_C = "pdpC"; + private static final String PDP_D = "pdpD"; + private static final String PDP_TYPE = "MyPdpType"; + private static final ToscaConceptIdentifier POLICY_A = new ToscaConceptIdentifier("MyPolicyA", VERSION); + private static final ToscaConceptIdentifier POLICY_B = new ToscaConceptIdentifier("MyPolicyB", VERSION); + private static final ToscaConceptIdentifier POLICY_C = new ToscaConceptIdentifier("MyPolicyC", VERSION); + private static final ToscaConceptIdentifier POLICY_D = new ToscaConceptIdentifier("MyPolicyD", VERSION); + private static final ToscaConceptIdentifier POLICY_TYPE = new ToscaConceptIdentifier("MyPolicyType", VERSION); + + private PdpPolicyStatusBuilder builder; + + @Captor + private ArgumentCaptor> created; + @Captor + private ArgumentCaptor> updated; + @Captor + private ArgumentCaptor> deleted; + + @Mock + private PolicyModelsProvider provider; + + private DeploymentStatus tracker; + + /** + * Sets up. + */ + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + tracker = new DeploymentStatus(provider); + + // @formatter:off + builder = PdpPolicyStatus.builder() + .pdpGroup(GROUP_A) + .pdpId(PDP_A) + .pdpType(PDP_TYPE) + .policy(POLICY_A) + .policyType(POLICY_TYPE) + .deploy(true) + .state(State.SUCCESS); + // @formatter:on + } + + @Test + public void testAddNotifications() { + PdpPolicyStatus create = builder.pdpId("created").state(State.FAILURE).build(); + PdpPolicyStatus update = builder.pdpId("updated").state(State.SUCCESS).build(); + PdpPolicyStatus delete = builder.pdpId("deleted").state(State.SUCCESS).build(); + PdpPolicyStatus unchange = builder.pdpId("unchanged").state(State.FAILURE).build(); + + // @formatter:off + tracker.getRecordMap().putAll(makeMap( + Action.CREATED, create, + Action.UPDATED, update, + Action.DELETED, delete, + Action.UNCHANGED, unchange + )); + // @formatter:on + + PolicyNotification notif = new PolicyNotification(); + + tracker.addNotifications(notif); + assertThat(notif.getAdded()).hasSize(1); + assertThat(notif.getDeleted()).isEmpty(); + + PolicyStatus status = notif.getAdded().get(0); + assertThat(status.getFailureCount()).isEqualTo(2); + assertThat(status.getIncompleteCount()).isZero(); + assertThat(status.getSuccessCount()).isEqualTo(1); + assertThat(status.getPolicy()).isEqualTo(POLICY_A); + assertThat(status.getPolicyType()).isEqualTo(POLICY_TYPE); + + /* + * repeat - should be no notifications + */ + notif = new PolicyNotification(); + tracker.addNotifications(notif); + assertThat(notif.getAdded()).isEmpty(); + assertThat(notif.getDeleted()).isEmpty(); + } + + @Test + public void testLoadByGroup() throws PfModelException { + PdpPolicyStatus status1 = builder.build(); + PdpPolicyStatus status2 = builder.policy(POLICY_B).build(); + PdpPolicyStatus status3 = builder.policy(POLICY_A).pdpId(PDP_B).build(); + + when(provider.getGroupPolicyStatus(GROUP_A)).thenReturn(List.of(status1, status2, status3)); + + tracker.loadByGroup(GROUP_A); + + // @formatter:off + assertThat(tracker.getRecordMap()).isEqualTo(makeMap( + Action.UNCHANGED, status1, + Action.UNCHANGED, status2, + Action.UNCHANGED, status3 + )); + // @formatter:on + + // try again - should not reload + tracker.loadByGroup(GROUP_A); + verify(provider).getGroupPolicyStatus(anyString()); + } + + @Test + public void testFlushPdpNotification() { + PdpPolicyStatus create = builder.pdpId("created").state(State.FAILURE).build(); + tracker.getRecordMap().putAll(makeMap(Action.CREATED, create)); + + PolicyNotification notif = new PolicyNotification(); + + tracker.flush(notif); + + assertThat(notif.getAdded()).hasSize(1); + assertThat(notif.getDeleted()).isEmpty(); + } + + @Test + public void testFlush() throws PfModelException { + PdpPolicyStatus create1 = builder.pdpId("createA").build(); + PdpPolicyStatus create2 = builder.pdpId("createB").build(); + PdpPolicyStatus update1 = builder.pdpId("updateA").build(); + PdpPolicyStatus update2 = builder.pdpId("updateB").build(); + PdpPolicyStatus delete1 = builder.pdpId("deleteA").build(); + PdpPolicyStatus delete2 = builder.pdpId("deleteB").build(); + PdpPolicyStatus unchange1 = builder.pdpId("unchangeA").build(); + PdpPolicyStatus unchange2 = builder.pdpId("unchangeB").build(); + + // @formatter:off + tracker.getRecordMap().putAll(makeMap( + Action.CREATED, create1, + Action.CREATED, create2, + Action.UPDATED, update1, + Action.UPDATED, update2, + Action.DELETED, delete1, + Action.DELETED, delete2, + Action.UNCHANGED, unchange1, + Action.UNCHANGED, unchange2 + )); + // @formatter:on + + tracker.flush(); + + verify(provider).cudPolicyStatus(created.capture(), updated.capture(), deleted.capture()); + + assertThat(sort(created.getValue())).isEqualTo(List.of(create1, create2)); + assertThat(sort(updated.getValue())).isEqualTo(List.of(update1, update2)); + assertThat(sort(deleted.getValue())).isEqualTo(List.of(delete1, delete2)); + + // @formatter:off + assertThat(tracker.getRecordMap()).isEqualTo(makeMap( + Action.UNCHANGED, create1, + Action.UNCHANGED, create2, + Action.UNCHANGED, update1, + Action.UNCHANGED, update2, + Action.UNCHANGED, unchange1, + Action.UNCHANGED, unchange2 + )); + // @formatter:on + } + + @Test + public void testDeleteUndeployments() { + builder.deploy(true); + PdpPolicyStatus delete = builder.policy(POLICY_A).build(); + PdpPolicyStatus deployedComplete = builder.policy(POLICY_B).build(); + + builder.deploy(false); + PdpPolicyStatus undepComplete1 = builder.policy(POLICY_C).build(); + PdpPolicyStatus undepIncomplete1 = builder.policy(POLICY_D).build(); + + builder.pdpId(PDP_B); + PdpPolicyStatus undepComplete2 = builder.policy(POLICY_C).build(); + PdpPolicyStatus undepIncomplete2 = builder.policy(POLICY_D).state(State.WAITING).build(); + + // @formatter:off + Map map = makeMap( + Action.DELETED, delete, + Action.UNCHANGED, deployedComplete, + Action.UNCHANGED, undepComplete1, + Action.UNCHANGED, undepComplete2, + Action.UNCHANGED, undepIncomplete1, + Action.UNCHANGED, undepIncomplete2 + ); + // @formatter:on + + tracker.getRecordMap().putAll(map); + + tracker.deleteUndeployments(); + + // the completed undeployments should now be marked DELETED + + // @formatter:off + assertThat(tracker.getRecordMap()).isEqualTo(makeMap( + Action.DELETED, delete, + Action.UNCHANGED, deployedComplete, + Action.DELETED, undepComplete1, + Action.DELETED, undepComplete2, + Action.UNCHANGED, undepIncomplete1, + Action.UNCHANGED, undepIncomplete2 + )); + // @formatter:on + } + + @Test + public void testDeleteDeploymentString() { + PdpPolicyStatus statusaa = builder.pdpId(PDP_A).policy(POLICY_A).build(); + PdpPolicyStatus statusab = builder.pdpId(PDP_A).policy(POLICY_B).build(); + PdpPolicyStatus statusba = builder.pdpId(PDP_B).policy(POLICY_A).build(); + PdpPolicyStatus statuscb = builder.pdpId(PDP_C).policy(POLICY_B).build(); + + // @formatter:off + tracker.getRecordMap().putAll(makeMap( + Action.UNCHANGED, statusaa, + Action.UNCHANGED, statusab, + Action.UNCHANGED, statusba, + Action.UNCHANGED, statuscb + )); + // @formatter:on + + tracker.deleteDeployment(PDP_A); + + // @formatter:off + assertThat(tracker.getRecordMap()).isEqualTo(makeMap( + Action.DELETED, statusaa, + Action.DELETED, statusab, + Action.UNCHANGED, statusba, + Action.UNCHANGED, statuscb + )); + // @formatter:on + } + + @Test + public void testDeleteDeploymentToscaConceptIdentifierBoolean() { + PdpPolicyStatus deploy1A = builder.policy(POLICY_A).build(); + PdpPolicyStatus deploy2A = builder.policy(POLICY_A).pdpId(PDP_B).build(); + PdpPolicyStatus deployB = builder.policy(POLICY_B).pdpId(PDP_A).build(); + + builder.deploy(false); + PdpPolicyStatus undeployA = builder.policy(POLICY_A).build(); + PdpPolicyStatus undeployB = builder.policy(POLICY_B).build(); + + // @formatter:off + tracker.getRecordMap().putAll(makeMap( + Action.UNCHANGED, deploy1A, + Action.UNCHANGED, deploy2A, + Action.UNCHANGED, deployB, + Action.UNCHANGED, undeployA, + Action.UNCHANGED, undeployB + )); + // @formatter:on + + tracker.deleteDeployment(POLICY_A, true); + + // @formatter:off + assertThat(tracker.getRecordMap()).isEqualTo(makeMap( + Action.DELETED, deploy1A, + Action.DELETED, deploy2A, + Action.UNCHANGED, deployB, + Action.UNCHANGED, undeployA, + Action.UNCHANGED, undeployB + )); + // @formatter:on + + tracker.deleteDeployment(POLICY_B, false); + + // @formatter:off + assertThat(tracker.getRecordMap()).isEqualTo(makeMap( + Action.DELETED, deploy1A, + Action.DELETED, deploy2A, + Action.UNCHANGED, deployB, + Action.UNCHANGED, undeployA, + Action.DELETED, undeployB + )); + // @formatter:on + } + + @Test + public void testDeleteDeploymentBiPredicateOfStatusKeyStatusAction() { + PdpPolicyStatus create1 = builder.pdpId(PDP_A).build(); + PdpPolicyStatus delete = builder.pdpId(PDP_B).build(); + PdpPolicyStatus update = builder.pdpId(PDP_C).build(); + PdpPolicyStatus unchange = builder.pdpId(PDP_D).build(); + + PdpPolicyStatus create2 = builder.pdpId(PDP_B).build(); + + // @formatter:off + tracker.getRecordMap().putAll(makeMap( + Action.CREATED, create1, + Action.CREATED, create2, + Action.DELETED, delete, + Action.UPDATED, update, + Action.UNCHANGED, unchange + )); + // @formatter:on + + tracker.deleteDeployment(POLICY_A, true); + + // @formatter:off + assertThat(tracker.getRecordMap()).isEqualTo(makeMap( + Action.DELETED, delete, + Action.DELETED, update, + Action.DELETED, unchange + )); + // @formatter:on + } + + @Test + public void testDeploy() { + tracker.deploy(PDP_A, POLICY_A, POLICY_TYPE, GROUP_A, PDP_TYPE, true); + + assertThat(tracker.getRecordMap()).hasSize(1); + + StatusAction status2 = tracker.getRecordMap().values().iterator().next(); + + assertThat(status2.getAction()).isEqualTo(Action.CREATED); + assertThat(status2.getStatus().getState()).isEqualTo(State.WAITING); + assertThat(status2.getStatus().isDeploy()).isTrue(); + + /* + * repeat - should be the same status + */ + tracker.deploy(PDP_A, POLICY_A, POLICY_TYPE, GROUP_A, PDP_TYPE, true); + + assertThat(tracker.getRecordMap()).hasSize(1); + assertThat(tracker.getRecordMap().values().iterator().next()).isSameAs(status2); + assertThat(status2.getAction()).isEqualTo(Action.CREATED); + assertThat(status2.getStatus().getState()).isEqualTo(State.WAITING); + assertThat(status2.getStatus().isDeploy()).isTrue(); + + /* + * repeat, with different values - should be unchanged + */ + status2.setAction(Action.UNCHANGED); + status2.getStatus().setDeploy(true); + status2.getStatus().setState(State.SUCCESS); + + tracker.deploy(PDP_A, POLICY_A, POLICY_TYPE, GROUP_A, PDP_TYPE, true); + + assertThat(tracker.getRecordMap()).hasSize(1); + assertThat(tracker.getRecordMap().values().iterator().next()).isSameAs(status2); + assertThat(status2.getAction()).isEqualTo(Action.UNCHANGED); + assertThat(status2.getStatus().getState()).isEqualTo(State.SUCCESS); + assertThat(status2.getStatus().isDeploy()).isTrue(); + + /* + * incorrect "deploy" value - should update it + */ + status2.setAction(Action.UNCHANGED); + status2.getStatus().setDeploy(true); + + tracker.deploy(PDP_A, POLICY_A, POLICY_TYPE, GROUP_A, PDP_TYPE, false); + + assertThat(status2.getAction()).isEqualTo(Action.UPDATED); + assertThat(status2.getStatus().getState()).isEqualTo(State.WAITING); + assertThat(status2.getStatus().isDeploy()).isFalse(); + + /* + * marked for deletion - should reinstate it + */ + status2.setAction(Action.DELETED); + status2.getStatus().setState(State.FAILURE); + status2.getStatus().setDeploy(false); + + tracker.deploy(PDP_A, POLICY_A, POLICY_TYPE, GROUP_A, PDP_TYPE, false); + + assertThat(status2.getAction()).isEqualTo(Action.UPDATED); + assertThat(status2.getStatus().getState()).isEqualTo(State.FAILURE); + assertThat(status2.getStatus().isDeploy()).isFalse(); + } + + @Test + public void testCompleteDeploy() { + tracker.deploy(PDP_A, POLICY_A, POLICY_TYPE, GROUP_A, PDP_TYPE, true); + assertThat(tracker.getRecordMap()).hasSize(1); + + // deployed, but not expected to be deployed - record should be left as is + checkCompleteDeploy(true, Set.of(), Set.of(), Action.UNCHANGED, State.WAITING); + checkCompleteDeploy(true, Set.of(), Set.of(POLICY_A), Action.UNCHANGED, State.WAITING); + + // expected, but not actually deployed - failure + checkCompleteDeploy(true, Set.of(POLICY_A), Set.of(), Action.UPDATED, State.FAILURE); + + // expected and actually deployed - success + checkCompleteDeploy(true, Set.of(POLICY_A), Set.of(POLICY_A), Action.UPDATED, State.SUCCESS); + checkCompleteDeploy(true, Set.of(POLICY_A, POLICY_B), Set.of(POLICY_A), Action.UPDATED, State.SUCCESS); + + // not expected and not actually deployed - success + checkCompleteDeploy(false, Set.of(), Set.of(), Action.UPDATED, State.SUCCESS); + + // not expected, but actually deployed - failure + checkCompleteDeploy(false, Set.of(), Set.of(POLICY_A), Action.UPDATED, State.FAILURE); + + // undeployed, but expected to be deployed - record should be left as is + checkCompleteDeploy(false, Set.of(POLICY_A), Set.of(), Action.UNCHANGED, State.WAITING); + checkCompleteDeploy(false, Set.of(POLICY_A), Set.of(POLICY_A), Action.UNCHANGED, State.WAITING); + checkCompleteDeploy(false, Set.of(POLICY_A, POLICY_B), Set.of(POLICY_A), Action.UNCHANGED, State.WAITING); + + /* + * Try a case where the state is already correct. + */ + StatusAction status = tracker.getRecordMap().values().iterator().next(); + status.getStatus().setDeploy(false); + status.setAction(Action.UNCHANGED); + status.getStatus().setState(State.SUCCESS); + + tracker.completeDeploy(PDP_A, Set.of(), Set.of()); + + assertThat(status.getAction()).isEqualTo(Action.UNCHANGED); + assertThat(status.getStatus().getState()).isEqualTo(State.SUCCESS); + + /* + * Try a case where the PDP does not match the record. + */ + status.getStatus().setDeploy(false); + status.setAction(Action.UNCHANGED); + status.getStatus().setState(State.WAITING); + + tracker.completeDeploy(PDP_B, Set.of(), Set.of()); + + assertThat(status.getAction()).isEqualTo(Action.UNCHANGED); + assertThat(status.getStatus().getState()).isEqualTo(State.WAITING); + } + + private void checkCompleteDeploy(boolean deploy, Set expected, + Set actual, Action action, State state) { + + StatusAction status = tracker.getRecordMap().values().iterator().next(); + status.getStatus().setDeploy(deploy); + status.setAction(Action.UNCHANGED); + status.getStatus().setState(State.WAITING); + + tracker.completeDeploy(PDP_A, expected, actual); + + assertThat(status.getAction()).isEqualTo(action); + assertThat(status.getStatus().getState()).isEqualTo(state); + } + + private List sort(List list) { + + Collections.sort(list, (rec1, rec2) -> { + + // @formatter:off + return new CompareToBuilder() + .append(rec1.getPdpId(), rec2.getPdpId()) + .append(rec1.getPolicy(), rec2.getPolicy()) + .toComparison(); + // @formatter:on + }); + + return list; + } + + /** + * Makes a map. + * + * @param data pairs of (Action, PdpPolicyStatus) + * @return a new map containing the given data + */ + private Map makeMap(Object... data) { + Map map = new HashMap<>(); + + assert (data.length % 2 == 0); + + for (int idata = 0; idata < data.length; idata += 2) { + Action action = (Action) data[idata]; + PdpPolicyStatus status = (PdpPolicyStatus) data[idata + 1]; + map.put(new StatusKey(status), new StatusAction(action, status)); + } + + return map; + } +} -- cgit 1.2.3-korg