Support group-oriented REST API 37/85237/7
authorJim Hahn <jrh3@att.com>
Fri, 12 Apr 2019 14:00:05 +0000 (10:00 -0400)
committerJim Hahn <jrh3@att.com>
Sun, 14 Apr 2019 00:21:36 +0000 (20:21 -0400)
Added code to support PDP group create/update & delete REST API calls.
The create/update request can also be used to deploy/undeploy policies.
It is assumed that the create/update request specifies a valid
list of supported policy types for each subgroup (i.e., PDP type).
Updated due to elimination of "version" from group in policy/models.
Added/updated junits.

Change-Id: I1916d9b17dfd5f12129c6f6a2fcf54e706662c10
Issue-ID: POLICY-1542
Signed-off-by: Jim Hahn <jrh3@att.com>
22 files changed:
main/src/main/java/org/onap/policy/pap/main/rest/depundep/GroupData.java
main/src/main/java/org/onap/policy/pap/main/rest/depundep/PdpGroupDeleteControllerV1.java
main/src/main/java/org/onap/policy/pap/main/rest/depundep/PdpGroupDeleteProvider.java
main/src/main/java/org/onap/policy/pap/main/rest/depundep/PdpGroupDeployControllerV1.java
main/src/main/java/org/onap/policy/pap/main/rest/depundep/PdpGroupDeployProvider.java
main/src/main/java/org/onap/policy/pap/main/rest/depundep/ProviderBase.java
main/src/main/java/org/onap/policy/pap/main/rest/depundep/SessionData.java
main/src/test/java/org/onap/policy/pap/main/rest/depundep/ProviderSuper.java
main/src/test/java/org/onap/policy/pap/main/rest/depundep/TestGroupData.java
main/src/test/java/org/onap/policy/pap/main/rest/depundep/TestPdpGroupDeleteControllerV1.java
main/src/test/java/org/onap/policy/pap/main/rest/depundep/TestPdpGroupDeleteProvider.java
main/src/test/java/org/onap/policy/pap/main/rest/depundep/TestPdpGroupDeployProvider.java
main/src/test/java/org/onap/policy/pap/main/rest/depundep/TestSessionData.java
main/src/test/resources/simpleDeploy/createGroupNewPolicy.json [new file with mode: 0644]
main/src/test/resources/simpleDeploy/createGroups.json [new file with mode: 0644]
main/src/test/resources/simpleDeploy/createGroupsNewSub.json [new file with mode: 0644]
main/src/test/resources/simpleDeploy/deleteGroup.json [new file with mode: 0644]
main/src/test/resources/simpleDeploy/group1.json [new file with mode: 0644]
main/src/test/resources/simpleDeploy/group2.json [new file with mode: 0644]
main/src/test/resources/simpleDeploy/group3.json [new file with mode: 0644]
main/src/test/resources/simpleDeploy/group4.json [new file with mode: 0644]
main/src/test/resources/simpleDeploy/group5.json [new file with mode: 0644]

index 25a886b..f97107f 100644 (file)
@@ -24,26 +24,58 @@ import lombok.Getter;
 import org.onap.policy.models.pdp.concepts.PdpGroup;
 
 /**
- * PdpGroup data, which includes the old group, that's in the DB, and possibly a new
- * group, that must be added to the DB.
+ * PdpGroup data, only the latest copy of the group is retained.
  */
-@Getter
 public class GroupData {
+    @Getter
     private PdpGroup group;
 
+    private enum State {
+        UNCHANGED, CREATED, UPDATED
+    }
+
     /**
-     * {@code True} if the group has been updated, {@code false} otherwise.
+     * State of the group.
      */
-    private boolean updated = false;
+    private State state;
 
 
     /**
-     * Constructs the object.
+     * Constructs the object, for an existing group.
      *
      * @param group the group that is in the DB
      */
     public GroupData(PdpGroup group) {
+        this(group, false);
+    }
+
+    /**
+     * Constructs the object.
+     *
+     * @param group the group that is in, or to be added to, the DB
+     * @param isNew {@code true} if this is a new group, {@code false} otherwise
+     */
+    public GroupData(PdpGroup group, boolean isNew) {
         this.group = group;
+        this.state = (isNew ? State.CREATED : State.UNCHANGED);
+    }
+
+    /**
+     * Determines if the group is new.
+     *
+     * @return {@code true} if the group is new, {@code false} otherwise
+     */
+    public boolean isNew() {
+        return (state == State.CREATED);
+    }
+
+    /**
+     * Determines if the group has been updated.
+     *
+     * @return {@code true} if the group has been updated, {@code false} otherwise
+     */
+    public boolean isUpdated() {
+        return (state == State.UPDATED);
     }
 
     /**
@@ -57,7 +89,10 @@ public class GroupData {
                             "expected group " + this.group.getName() + ", but received " + newGroup.getName());
         }
 
-        this.updated = true;
         this.group = newGroup;
+
+        if (state == State.UNCHANGED) {
+            state = State.UPDATED;
+        }
     }
 }
index ca638f3..0878185 100644 (file)
@@ -82,51 +82,7 @@ public class PdpGroupDeleteControllerV1 extends PapRestControllerV1 {
     public Response deleteGroup(@HeaderParam(REQUEST_ID_NAME) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId,
                     @ApiParam(value = "PDP Group Name", required = true) @PathParam("name") String groupName) {
 
-        Pair<Status, PdpGroupDeleteResponse> pair = provider.deleteGroup(groupName, null);
-
-        return addLoggingHeaders(addVersionControlHeaders(Response.status(pair.getLeft())), requestId)
-                        .entity(pair.getRight()).build();
-    }
-
-    /**
-     * Deletes a particular version of a PDP group.
-     *
-     * @param requestId request ID used in ONAP logging
-     * @param groupName name of the PDP group to be deleted
-     * @param version version to be deleted
-     * @return a response
-     */
-    // @formatter:off
-    @DELETE
-    @Path("pdps/groups/{name}/versions/{version}")
-    @ApiOperation(value = "Delete version of a PDP Group",
-        notes = "Deletes a version of PDP Group, returning optional error details",
-        response = PdpGroupDeleteResponse.class,
-        tags = {"Policy Administration (PAP) API"},
-        authorizations = @Authorization(value = AUTHORIZATION_TYPE),
-        responseHeaders = {
-            @ResponseHeader(name = VERSION_MINOR_NAME, description = VERSION_MINOR_DESCRIPTION,
-                            response = String.class),
-            @ResponseHeader(name = VERSION_PATCH_NAME, description = VERSION_PATCH_DESCRIPTION,
-                            response = String.class),
-            @ResponseHeader(name = VERSION_LATEST_NAME, description = VERSION_LATEST_DESCRIPTION,
-                            response = String.class),
-            @ResponseHeader(name = REQUEST_ID_NAME, description = REQUEST_ID_HDR_DESCRIPTION,
-                            response = UUID.class)},
-        extensions = {@Extension(name = EXTENSION_NAME,
-            properties = {@ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION),
-                @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE)})})
-    @ApiResponses(value = {@ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE),
-                    @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE),
-                    @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE)})
-    // @formatter:on
-
-    public Response deleteGroupVersion(
-                    @HeaderParam(REQUEST_ID_NAME) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId,
-                    @ApiParam(value = "PDP Group Name", required = true) @PathParam("name") String groupName,
-                    @ApiParam(value = "PDP Group Version", required = true) @PathParam("version") String version) {
-
-        Pair<Status, PdpGroupDeleteResponse> pair = provider.deleteGroup(groupName, version);
+        Pair<Status, PdpGroupDeleteResponse> pair = provider.deleteGroup(groupName);
 
         return addLoggingHeaders(addVersionControlHeaders(Response.status(pair.getLeft())), requestId)
                         .entity(pair.getRight()).build();
index 6457efa..7d47b3e 100644 (file)
@@ -27,6 +27,7 @@ import org.onap.policy.models.base.PfModelException;
 import org.onap.policy.models.pap.concepts.PdpGroupDeleteResponse;
 import org.onap.policy.models.pdp.concepts.PdpGroup;
 import org.onap.policy.models.pdp.concepts.PdpSubGroup;
+import org.onap.policy.models.pdp.enums.PdpState;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifier;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifierOptVersion;
@@ -52,47 +53,73 @@ public class PdpGroupDeleteProvider extends ProviderBase<PdpGroupDeleteResponse>
      * Deletes a PDP group.
      *
      * @param groupName name of the PDP group to be deleted
-     * @param version group version to delete; may be {@code null} if the group has only
-     *        one version
      * @return a pair containing the status and the response
      */
-    public Pair<Response.Status, PdpGroupDeleteResponse> deleteGroup(String groupName, String version) {
+    public Pair<Response.Status, PdpGroupDeleteResponse> deleteGroup(String groupName) {
 
-        PdpGroupDeleteResponse resp = new PdpGroupDeleteResponse();
-        resp.setErrorDetails("not implemented yet");
+        return process(groupName, this::deleteGroup);
+    }
+
+    /**
+     * Deletes a PDP group.
+     *
+     * @param data session data
+     * @param groupName name of the PDP group to be deleted
+     */
+    private void deleteGroup(SessionData data, String groupName) {
+        try {
+            PdpGroup group = data.getGroup(groupName);
+            if (group == null) {
+                throw new PolicyPapRuntimeException("group not found");
+            }
+
+            if (group.getPdpGroupState() == PdpState.ACTIVE) {
+                throw new PolicyPapRuntimeException("group is still " + PdpState.ACTIVE);
+            }
 
-        return Pair.of(Response.Status.INTERNAL_SERVER_ERROR, resp);
+            data.deleteGroupFromDb(group);
+
+        } catch (PfModelException e) {
+            // no need to log the error here, as it will be logged by the invoker
+            logger.warn("failed to delete group: {}", groupName);
+            throw new PolicyPapRuntimeException(DB_ERROR_MSG, e);
+
+        } catch (RuntimeException e) {
+            // no need to log the error here, as it will be logged by the invoker
+            logger.warn("failed to delete group: {}", groupName);
+            throw e;
+        }
     }
 
     /**
-     * Deletes a PDP policy.
+     * Undeploys a policy.
      *
      * @param policyIdent identifier of the policy to be undeployed
      * @return a pair containing the status and the response
      */
     public Pair<Response.Status, PdpGroupDeleteResponse> undeploy(ToscaPolicyIdentifierOptVersion policyIdent) {
 
-        return process(policyIdent, this::deletePolicy);
+        return process(policyIdent, this::undeployPolicy);
     }
 
     /**
-     * Deletes a policy from its groups.
+     * Undeploys a policy from its groups.
      *
      * @param data session data
      * @param ident identifier of the policy to be deleted
      */
-    private void deletePolicy(SessionData data, ToscaPolicyIdentifierOptVersion ident) {
+    private void undeployPolicy(SessionData data, ToscaPolicyIdentifierOptVersion ident) {
         try {
             processPolicy(data, ident);
 
         } catch (PfModelException e) {
             // no need to log the error here, as it will be logged by the invoker
-            logger.warn("failed to deploy policy: {}", ident);
+            logger.warn("failed to undeploy policy: {}", ident);
             throw new PolicyPapRuntimeException(DB_ERROR_MSG, e);
 
         } catch (RuntimeException e) {
             // no need to log the error here, as it will be logged by the invoker
-            logger.warn("failed to deploy policy: {}", ident);
+            logger.warn("failed to undeploy policy: {}", ident);
             throw e;
         }
     }
@@ -104,6 +131,9 @@ public class PdpGroupDeleteProvider extends ProviderBase<PdpGroupDeleteResponse>
         return resp;
     }
 
+    /**
+     * Returns a function that will remove the specified policy from a subgroup.
+     */
     @Override
     protected BiFunction<PdpGroup, PdpSubGroup, Boolean> makeUpdater(ToscaPolicy policy) {
         ToscaPolicyIdentifier desiredIdent = policy.getIdentifier();
index 6d4dc7e..bdec356 100644 (file)
@@ -82,7 +82,7 @@ public class PdpGroupDeployControllerV1 extends PapRestControllerV1 {
     public Response deployGroup(@HeaderParam(REQUEST_ID_NAME) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId,
                     @ApiParam(value = "List of PDP Group Configuration", required = true) PdpGroups groups) {
 
-        Pair<Status, PdpGroupDeployResponse> pair = provider.deployGroup(groups);
+        Pair<Status, PdpGroupDeployResponse> pair = provider.createOrUpdateGroups(groups);
 
         return addLoggingHeaders(addVersionControlHeaders(Response.status(pair.getLeft())), requestId)
                         .entity(pair.getRight()).build();
index eef1438..cb2d1e3 100644 (file)
 
 package org.onap.policy.pap.main.rest.depundep;
 
+import com.att.aft.dme2.internal.apache.commons.lang.ObjectUtils;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.function.BiFunction;
+import java.util.function.Consumer;
 import javax.ws.rs.core.Response;
 import org.apache.commons.lang3.tuple.Pair;
+import org.onap.policy.common.parameters.BeanValidationResult;
+import org.onap.policy.common.parameters.ObjectValidationResult;
+import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.common.parameters.ValidationStatus;
 import org.onap.policy.common.utils.services.Registry;
 import org.onap.policy.models.base.PfModelException;
 import org.onap.policy.models.pap.concepts.PdpDeployPolicies;
@@ -31,7 +43,7 @@ import org.onap.policy.models.pap.concepts.PdpGroupDeployResponse;
 import org.onap.policy.models.pdp.concepts.PdpGroup;
 import org.onap.policy.models.pdp.concepts.PdpGroups;
 import org.onap.policy.models.pdp.concepts.PdpSubGroup;
-import org.onap.policy.models.pdp.concepts.PdpUpdate;
+import org.onap.policy.models.pdp.enums.PdpState;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifier;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifierOptVersion;
@@ -61,28 +73,297 @@ public class PdpGroupDeployProvider extends ProviderBase<PdpGroupDeployResponse>
     }
 
     /**
-     * Deploys or updates PDP groups.
+     * Creates or updates PDP groups.
      *
-     * @param groups PDP group configurations
+     * @param groups PDP group configurations to be created or updated
      * @return a pair containing the status and the response
      */
-    public Pair<Response.Status, PdpGroupDeployResponse> deployGroup(PdpGroups groups) {
-        return process(groups, this::deployGroups);
+    public Pair<Response.Status, PdpGroupDeployResponse> createOrUpdateGroups(PdpGroups groups) {
+        ValidationResult result = groups.validatePapRest();
+
+        if (!result.isValid()) {
+            String msg = result.getResult().trim();
+            logger.warn(msg);
+            return Pair.of(Response.Status.INTERNAL_SERVER_ERROR, makeResponse(msg));
+        }
+
+        return process(groups, this::createOrUpdate);
     }
 
     /**
-     * Deploys or updates PDP groups.
+     * Creates or updates PDP groups. This is the method that does the actual work.
      *
      * @param data session data
      * @param groups PDP group configurations
-     * @return a list of requests that should be sent to configure the PDPs
      */
-    private List<PdpUpdate> deployGroups(SessionData data, PdpGroups groups) {
-        throw new PolicyPapRuntimeException("not implemented yet");
+    private void createOrUpdate(SessionData data, PdpGroups groups) {
+        BeanValidationResult result = new BeanValidationResult("groups", groups);
+
+        for (PdpGroup group : groups.getGroups()) {
+            PdpGroup dbgroup = data.getGroup(group.getName());
+
+            if (dbgroup == null) {
+                result.addResult(addGroup(data, group));
+
+            } else {
+                result.addResult(updateGroup(data, dbgroup, group));
+            }
+        }
+
+        if (!result.isValid()) {
+            throw new PolicyPapRuntimeException(result.getResult().trim());
+        }
+    }
+
+    /**
+     * Adds a new group.
+     *
+     * @param data session data
+     * @param group the group to be added
+     * @return the validation result
+     */
+    private ValidationResult addGroup(SessionData data, PdpGroup group) {
+        BeanValidationResult result = new BeanValidationResult(group.getName(), group);
+
+        validateGroupOnly(group, result);
+        if (!result.isValid()) {
+            return result;
+        }
+
+        // default to active
+        if (group.getPdpGroupState() == null) {
+            group.setPdpGroupState(PdpState.ACTIVE);
+        }
+
+        for (PdpSubGroup subgrp : group.getPdpSubgroups()) {
+            result.addResult(addSubGroup(data, subgrp));
+        }
+
+        if (result.isValid()) {
+            data.create(group);
+        }
+
+        return result;
+    }
+
+    /**
+     * Performs additional validations of a group, but does not examine the subgroups.
+     *
+     * @param group the group to be validated
+     * @param result the validation result
+     */
+    private void validateGroupOnly(PdpGroup group, BeanValidationResult result) {
+        if (group.getPdpGroupState() == null) {
+            return;
+        }
+
+        switch (group.getPdpGroupState()) {
+            case ACTIVE:
+            case PASSIVE:
+                break;
+
+            default:
+                result.addResult(new ObjectValidationResult("pdpGroupState", group.getPdpGroupState(),
+                                ValidationStatus.INVALID, "must be null, ACTIVE, or PASSIVE"));
+                break;
+        }
+    }
+
+    /**
+     * Updates an existing group.
+     *
+     * @param data session data
+     * @param dbgroup the group, as it appears within the DB
+     * @param group the group to be added
+     * @return the validation result
+     */
+    private ValidationResult updateGroup(SessionData data, PdpGroup dbgroup, PdpGroup group) {
+        BeanValidationResult result = new BeanValidationResult(group.getName(), group);
+
+        if (!ObjectUtils.equals(dbgroup.getProperties(), group.getProperties())) {
+            result.addResult(new ObjectValidationResult("properties", "", ValidationStatus.INVALID,
+                            "cannot change properties"));
+        }
+
+        // create a map of existing subgroups
+        Map<String, PdpSubGroup> type2sub = new HashMap<>();
+        dbgroup.getPdpSubgroups().forEach(subgrp -> type2sub.put(subgrp.getPdpType(), subgrp));
+
+        boolean updated = updateField(dbgroup.getDescription(), group.getDescription(), dbgroup::setDescription);
+
+        for (PdpSubGroup subgrp : group.getPdpSubgroups()) {
+            PdpSubGroup dbsub = type2sub.get(subgrp.getPdpType());
+            BeanValidationResult subResult = new BeanValidationResult(subgrp.getPdpType(), subgrp);
+
+            if (dbsub == null) {
+                updated = true;
+                subResult.addResult(addSubGroup(data, subgrp));
+                dbgroup.getPdpSubgroups().add(subgrp);
+
+            } else {
+                updated = updateSubGroup(data, group, dbsub, subgrp, subResult) || updated;
+            }
+
+            result.addResult(subResult);
+        }
+
+        if (result.isValid() && updated) {
+            data.update(group);
+        }
+
+        return result;
+    }
+
+    /**
+     * Updates a field, if the new value is different than the old value.
+     *
+     * @param oldValue old value
+     * @param newValue new value
+     * @param setter function to set the field to the new value
+     * @return {@code true} if the field was updated, {@code false} if it already matched
+     *         the new value
+     */
+    private <T> boolean updateField(T oldValue, T newValue, Consumer<T> setter) {
+        if (oldValue == newValue) {
+            return false;
+        }
+
+        if (oldValue != null && oldValue.equals(newValue)) {
+            return false;
+        }
+
+        setter.accept(newValue);
+        return true;
+    }
+
+    /**
+     * Adds a new subgroup.
+     *
+     * @param data session data
+     * @param subgrp the subgroup to be added
+     * @return the validation result
+     */
+    private ValidationResult addSubGroup(SessionData data, PdpSubGroup subgrp) {
+        subgrp.setCurrentInstanceCount(0);
+        subgrp.setPdpInstances(Collections.emptyList());
+
+        BeanValidationResult result = new BeanValidationResult(subgrp.getPdpType(), subgrp);
+
+        result.addResult(validatePolicies(data, subgrp));
+
+        return result;
     }
 
     /**
-     * Deploys or updates PDP policies.
+     * Updates an existing subgroup.
+     *
+     * @param data session data
+     * @param dbgroup the group, from the DB, containing the subgroup
+     * @param dbsub the subgroup, from the DB
+     * @param subgrp the subgroup to be updated
+     * @param container container for additional validation results
+     * @return {@code true} if the subgroup content was changed, {@code false} if there
+     *         were no changes
+     */
+    private boolean updateSubGroup(SessionData data, PdpGroup dbgroup, PdpSubGroup dbsub, PdpSubGroup subgrp,
+                    BeanValidationResult container) {
+
+        // perform additional validations first
+        if (!validateSubGroup(data, dbsub, subgrp, container)) {
+            return false;
+        }
+
+        /*
+         * first, apply the changes about which the PDPs care
+         */
+        boolean updated = updateList(dbsub.getPolicies(), subgrp.getPolicies(), dbsub::setPolicies);
+
+        // publish any changes to the PDPs
+        if (updated) {
+            makeUpdates(data, dbgroup, dbsub);
+        }
+
+        /*
+         * now make any remaining changes
+         */
+        updated = updateList(dbsub.getSupportedPolicyTypes(), subgrp.getSupportedPolicyTypes(),
+                        dbsub::setSupportedPolicyTypes) || updated;
+
+        return updateField(dbsub.getDesiredInstanceCount(), subgrp.getDesiredInstanceCount(),
+                        dbsub::setDesiredInstanceCount) || updated;
+    }
+
+    /**
+     * Performs additional validations of a subgroup.
+     *
+     * @param data session data
+     * @param dbsub the subgroup, from the DB
+     * @param subgrp the subgroup to be validated
+     * @param container container for additional validation results
+     * @return {@code true} if the subgroup is valid, {@code false} otherwise
+     */
+    private boolean validateSubGroup(SessionData data, PdpSubGroup dbsub, PdpSubGroup subgrp,
+                    BeanValidationResult container) {
+
+        BeanValidationResult result = new BeanValidationResult(subgrp.getPdpType(), subgrp);
+
+        if (!ObjectUtils.equals(dbsub.getProperties(), subgrp.getProperties())) {
+            result.addResult(new ObjectValidationResult("properties", "", ValidationStatus.INVALID,
+                            "cannot change properties"));
+        }
+
+        result.addResult(validatePolicies(data, subgrp));
+        container.addResult(result);
+
+        return result.isValid();
+    }
+
+    /**
+     * Updates a DB list with items from a new list.
+     *
+     * @param dblist the list from the DB
+     * @param newList the new list
+     * @param setter function to set the new list
+     * @return {@code true} if the list changed, {@code false} if the lists were the same
+     */
+    private <T> boolean updateList(List<T> dblist, List<T> newList, Consumer<List<T>> setter) {
+
+        Set<T> dbTypes = new HashSet<T>(dblist);
+        Set<T> newTypes = new HashSet<T>(newList);
+
+        if (dbTypes.equals(newTypes)) {
+            return false;
+        }
+
+        setter.accept(new ArrayList<>(newTypes));
+
+        return true;
+    }
+
+    /**
+     * Performs additional validations of the policies within a subgroup.
+     *
+     * @param data session data
+     * @param subgrp the subgroup to be validated
+     * @param result the validation result
+     */
+    private ValidationResult validatePolicies(SessionData data, PdpSubGroup subgrp) {
+        BeanValidationResult result = new BeanValidationResult(subgrp.getPdpType(), subgrp);
+
+        for (ToscaPolicyIdentifier ident : subgrp.getPolicies()) {
+            ToscaPolicy policy = data.getPolicy(ident);
+
+            if (!subgrp.getSupportedPolicyTypes().contains(policy.getTypeIdentifier())) {
+                result.addResult(new ObjectValidationResult("policy", ident, ValidationStatus.INVALID,
+                                "not a supported policy for the subgroup"));
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Deploys or updates PDP policies using the simple API.
      *
      * @param policies PDP policies
      * @return a pair containing the status and the response
@@ -92,7 +373,8 @@ public class PdpGroupDeployProvider extends ProviderBase<PdpGroupDeployResponse>
     }
 
     /**
-     * Deploys or updates PDP policies using the simple API.
+     * Deploys or updates PDP policies using the simple API. This is the method that does
+     * the actual work.
      *
      * @param data session data
      * @param extPolicies external PDP policies
@@ -118,6 +400,9 @@ public class PdpGroupDeployProvider extends ProviderBase<PdpGroupDeployResponse>
         }
     }
 
+    /**
+     * Adds a policy to a subgroup, if it isn't there already.
+     */
     @Override
     protected BiFunction<PdpGroup, PdpSubGroup, Boolean> makeUpdater(ToscaPolicy policy) {
         ToscaPolicyIdentifier desiredIdent = policy.getIdentifier();
index d351919..5e0fb71 100644 (file)
@@ -32,6 +32,7 @@ import org.onap.policy.models.base.PfModelException;
 import org.onap.policy.models.pap.concepts.SimpleResponse;
 import org.onap.policy.models.pdp.concepts.Pdp;
 import org.onap.policy.models.pdp.concepts.PdpGroup;
+import org.onap.policy.models.pdp.concepts.PdpStateChange;
 import org.onap.policy.models.pdp.concepts.PdpSubGroup;
 import org.onap.policy.models.pdp.concepts.PdpUpdate;
 import org.onap.policy.models.provider.PolicyModelsProvider;
@@ -59,7 +60,6 @@ import org.slf4j.LoggerFactory;
  */
 public abstract class ProviderBase<R extends SimpleResponse> {
     private static final String DEPLOY_FAILED = "failed to deploy/undeploy policies";
-
     public static final String DB_ERROR_MSG = "DB error";
 
     private static final Logger logger = LoggerFactory.getLogger(ProviderBase.class);
@@ -100,7 +100,7 @@ public abstract class ProviderBase<R extends SimpleResponse> {
 
         synchronized (updateLock) {
             // list of requests to be published to the PDPs
-            Collection<PdpUpdate> requests = Collections.emptyList();
+            Collection<Pair<PdpUpdate, PdpStateChange>> requests = Collections.emptyList();
 
             try (PolicyModelsProvider dao = daoFactory.create()) {
 
@@ -110,7 +110,7 @@ public abstract class ProviderBase<R extends SimpleResponse> {
                 // make all of the DB updates
                 data.updateDb();
 
-                requests = data.getPdpUpdates();
+                requests = data.getPdpRequests();
 
             } catch (PfModelException e) {
                 logger.warn(DEPLOY_FAILED, e);
@@ -127,7 +127,7 @@ public abstract class ProviderBase<R extends SimpleResponse> {
 
 
             // publish the requests
-            requests.forEach(requestMap::addRequest);
+            requests.forEach(pair -> requestMap.addRequest(pair.getLeft(), pair.getRight()));
         }
 
         return Pair.of(Response.Status.OK, makeResponse(null));
@@ -236,13 +236,7 @@ public abstract class ProviderBase<R extends SimpleResponse> {
 
             updated = true;
 
-            /*
-             * generate an UPDATE for each PDP instance. Since the group is active, we
-             * assume that the PDP is, too, thus no need for a STATE-CHANGE.
-             */
-            for (Pdp pdpInstance : subgroup.getPdpInstances()) {
-                data.addUpdate(makeUpdate(data, group, subgroup, pdpInstance));
-            }
+            makeUpdates(data, group, subgroup);
         }
 
 
@@ -252,20 +246,33 @@ public abstract class ProviderBase<R extends SimpleResponse> {
         }
     }
 
+    /**
+     * Makes UPDATE messages for each PDP in a subgroup.
+     *
+     * @param data session data
+     * @param group group containing the subgroup
+     * @param subgroup subgroup whose PDPs should receive messages
+     */
+    protected void makeUpdates(SessionData data, PdpGroup group, PdpSubGroup subgroup) {
+        for (Pdp pdp : subgroup.getPdpInstances()) {
+            data.addUpdate(makeUpdate(data, group, subgroup, pdp));
+        }
+    }
+
     /**
      * Makes an UPDATE message for a particular PDP.
      *
      * @param data session data
      * @param group group to which the PDP should belong
      * @param subgroup subgroup to which the PDP should belong
-     * @param pdpInstance identifies the PDP of interest
+     * @param pdp the PDP of interest
      * @return a new UPDATE message
      */
-    private PdpUpdate makeUpdate(SessionData data, PdpGroup group, PdpSubGroup subgroup, Pdp pdpInstance) {
+    private PdpUpdate makeUpdate(SessionData data, PdpGroup group, PdpSubGroup subgroup, Pdp pdp) {
 
         PdpUpdate update = new PdpUpdate();
 
-        update.setName(pdpInstance.getInstanceId());
+        update.setName(pdp.getInstanceId());
         update.setDescription(group.getDescription());
         update.setPdpGroup(group.getName());
         update.setPdpSubgroup(subgroup.getPdpType());
index 6b42b19..0537c80 100644 (file)
@@ -26,9 +26,11 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
+import org.apache.commons.lang3.tuple.Pair;
 import org.onap.policy.models.base.PfModelException;
 import org.onap.policy.models.pdp.concepts.PdpGroup;
 import org.onap.policy.models.pdp.concepts.PdpGroupFilter;
+import org.onap.policy.models.pdp.concepts.PdpStateChange;
 import org.onap.policy.models.pdp.concepts.PdpUpdate;
 import org.onap.policy.models.pdp.enums.PdpState;
 import org.onap.policy.models.provider.PolicyModelsProvider;
@@ -57,9 +59,9 @@ public class SessionData {
     private final Map<ToscaPolicyTypeIdentifier, List<GroupData>> type2groups = new HashMap<>();
 
     /**
-     * Maps a PDP name to its most recently generated update request.
+     * Maps a PDP name to its most recently generated update and state-change requests.
      */
-    private final Map<String, PdpUpdate> pdpUpdates = new HashMap<>();
+    private final Map<String, Pair<PdpUpdate, PdpStateChange>> pdpRequests = new HashMap<>();
 
     /**
      * Maps a policy's identifier to the policy.
@@ -96,17 +98,11 @@ public class SessionData {
 
             try {
                 List<ToscaPolicy> lst = dao.getPolicyList(ident.getName(), ident.getVersion());
-
                 if (lst.isEmpty()) {
                     throw new PolicyPapRuntimeException(
                                     "cannot find policy: " + ident.getName() + " " + ident.getVersion());
                 }
 
-                if (lst.size() > 1) {
-                    throw new PolicyPapRuntimeException(
-                                    "too many policies match: " + ident.getName() + " " + ident.getVersion());
-                }
-
                 return lst.get(0);
 
             } catch (PfModelException e) {
@@ -116,6 +112,21 @@ public class SessionData {
         });
     }
 
+    /**
+     * Adds an update and state-change to the sets, replacing any previous entries for the
+     * given PDP.
+     *
+     * @param update the update to be added
+     * @param change the state-change to be added
+     */
+    public void addRequests(PdpUpdate update, PdpStateChange change) {
+        if (!update.getName().equals(change.getName())) {
+            throw new IllegalArgumentException("PDP name mismatch " + update.getName() + ", " + change.getName());
+        }
+
+        pdpRequests.put(update.getName(), Pair.of(update, change));
+    }
+
     /**
      * Adds an update to the set of updates, replacing any previous entry for the given
      * PDP.
@@ -123,16 +134,46 @@ public class SessionData {
      * @param update the update to be added
      */
     public void addUpdate(PdpUpdate update) {
-        pdpUpdates.put(update.getName(), update);
+        pdpRequests.compute(update.getName(), (name, data) -> Pair.of(update, (data == null ? null : data.getRight())));
     }
 
     /**
-     * Gets the accumulated UPDATE requests.
+     * Adds a state-change to the set of state-change requests, replacing any previous entry for the given
+     * PDP.
      *
-     * @return the UPDATE requests
+     * @param change the state-change to be added
      */
-    public Collection<PdpUpdate> getPdpUpdates() {
-        return pdpUpdates.values();
+    public void addStateChange(PdpStateChange change) {
+        pdpRequests.compute(change.getName(), (name, data) -> Pair.of((data == null ? null : data.getLeft()), change));
+    }
+
+    /**
+     * Gets the accumulated PDP requests.
+     *
+     * @return the PDP requests
+     */
+    public Collection<Pair<PdpUpdate, PdpStateChange>> getPdpRequests() {
+        return pdpRequests.values();
+    }
+
+    /**
+     * Gets the accumulated PDP update requests.
+     *
+     * @return the PDP requests
+     */
+    public List<PdpUpdate> getPdpUpdates() {
+        return pdpRequests.values().stream().filter(req -> req.getLeft() != null).map(Pair::getLeft)
+                        .collect(Collectors.toList());
+    }
+
+    /**
+     * Gets the accumulated PDP state-change requests.
+     *
+     * @return the PDP requests
+     */
+    public List<PdpStateChange> getPdpStateChanges() {
+        return pdpRequests.values().stream().filter(req -> req.getRight() != null).map(Pair::getRight)
+                        .collect(Collectors.toList());
     }
 
     /**
@@ -162,6 +203,19 @@ public class SessionData {
         return policy;
     }
 
+    /**
+     * Creates a group.
+     *
+     * @param newGroup the new group
+     */
+    public void create(PdpGroup newGroup) {
+        String name = newGroup.getName();
+
+        if (groupCache.put(name, new GroupData(newGroup, true)) != null) {
+            throw new IllegalStateException("group already cached: " + name);
+        }
+    }
+
     /**
      * Updates a group.
      *
@@ -177,6 +231,34 @@ public class SessionData {
         data.update(newGroup);
     }
 
+    /**
+     * Gets the group by the given name.
+     *
+     * @param name name of the group to get
+     * @return the group, or {@code null} if it does not exist
+     * @throws PolicyPapRuntimeException if an error occurs
+     */
+    public PdpGroup getGroup(String name) {
+
+        GroupData data = groupCache.computeIfAbsent(name, key -> {
+
+            try {
+                List<PdpGroup> lst = dao.getPdpGroups(key);
+                if (lst.isEmpty()) {
+                    return null;
+                }
+
+                return new GroupData(lst.get(0));
+
+            } catch (PfModelException e) {
+                throw new PolicyPapRuntimeException("cannot get group: " + name, e);
+            }
+
+        });
+
+        return (data == null ? null : data.getGroup());
+    }
+
     /**
      * Gets the active groups supporting the given policy.
      *
@@ -225,13 +307,28 @@ public class SessionData {
      * @throws PfModelException if an error occurs
      */
     public void updateDb() throws PfModelException {
-        List<GroupData> updatedGroups =
+        // create new groups
+        List<GroupData> created = groupCache.values().stream().filter(GroupData::isNew).collect(Collectors.toList());
+        if (!created.isEmpty()) {
+            dao.createPdpGroups(created.stream().map(GroupData::getGroup).collect(Collectors.toList()));
+        }
+
+        // update existing groups
+        List<GroupData> updated =
                         groupCache.values().stream().filter(GroupData::isUpdated).collect(Collectors.toList());
-        if (updatedGroups.isEmpty()) {
-            return;
+        if (!updated.isEmpty()) {
+            dao.updatePdpGroups(updated.stream().map(GroupData::getGroup).collect(Collectors.toList()));
         }
+    }
 
-        // update the groups
-        dao.updatePdpGroups(updatedGroups.stream().map(GroupData::getGroup).collect(Collectors.toList()));
+    /**
+     * Deletes a group from the DB, immediately (i.e., without caching the request to be
+     * executed later).
+     *
+     * @param group the group to be deleted
+     * @throws PfModelException if an error occurs
+     */
+    public void deleteGroupFromDb(PdpGroup group) throws PfModelException {
+        dao.deletePdpGroup(group.getName());
     }
 }
index 65b1234..256d3af 100644 (file)
@@ -1,4 +1,4 @@
-/*
+/*-
  * ============LICENSE_START=======================================================
  * ONAP PAP
  * ================================================================================
@@ -31,6 +31,7 @@ import java.io.File;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 import org.junit.Before;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
@@ -43,6 +44,7 @@ import org.onap.policy.common.utils.resources.ResourceUtils;
 import org.onap.policy.common.utils.services.Registry;
 import org.onap.policy.models.pdp.concepts.PdpGroup;
 import org.onap.policy.models.pdp.concepts.PdpGroups;
+import org.onap.policy.models.pdp.concepts.PdpStateChange;
 import org.onap.policy.models.pdp.concepts.PdpUpdate;
 import org.onap.policy.models.provider.PolicyModelsProvider;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
@@ -61,7 +63,7 @@ public class ProviderSuper {
 
 
     /**
-     * Used to capture input to dao.updatePdpGroups().
+     * Used to capture input to dao.updatePdpGroups() and dao.createPdpGroups().
      */
     @Captor
     private ArgumentCaptor<List<PdpGroup>> updateCaptor;
@@ -118,7 +120,19 @@ public class ProviderSuper {
     }
 
     /**
-     * Gets the input to the method.
+     * Gets the input to the create() method.
+     *
+     * @return the input that was passed to the dao.updatePdpGroups() method
+     * @throws Exception if an error occurred
+     */
+    protected List<PdpGroup> getGroupCreates() throws Exception {
+        verify(dao).createPdpGroups(updateCaptor.capture());
+
+        return copyList(updateCaptor.getValue());
+    }
+
+    /**
+     * Gets the input to the update() method.
      *
      * @return the input that was passed to the dao.updatePdpGroups() method
      * @throws Exception if an error occurred
@@ -129,6 +143,20 @@ public class ProviderSuper {
         return copyList(updateCaptor.getValue());
     }
 
+    /**
+     * Gets the state-changes that were added to the request map.
+     *
+     * @param count the number of times the method is expected to have been called
+     * @return the state-changes that were added to the request map
+     */
+    protected List<PdpStateChange> getStateChangeRequests(int count) {
+        ArgumentCaptor<PdpStateChange> captor = ArgumentCaptor.forClass(PdpStateChange.class);
+
+        verify(reqmap, times(count)).addRequest(any(), captor.capture());
+
+        return captor.getAllValues().stream().filter(req -> req != null).collect(Collectors.toList());
+    }
+
     /**
      * Gets the updates that were added to the request map.
      *
@@ -138,9 +166,9 @@ public class ProviderSuper {
     protected List<PdpUpdate> getUpdateRequests(int count) {
         ArgumentCaptor<PdpUpdate> captor = ArgumentCaptor.forClass(PdpUpdate.class);
 
-        verify(reqmap, times(count)).addRequest(captor.capture());
+        verify(reqmap, times(count)).addRequest(captor.capture(), any());
 
-        return new ArrayList<>(captor.getAllValues());
+        return captor.getAllValues().stream().filter(req -> req != null).collect(Collectors.toList());
     }
 
     /**
@@ -162,7 +190,17 @@ public class ProviderSuper {
      * @return a list of groups
      */
     protected List<PdpGroup> loadGroups(String fileName) {
-        return loadFile(fileName, PdpGroups.class).getGroups();
+        return loadPdpGroups(fileName).getGroups();
+    }
+
+    /**
+     * Loads a PdpGroups.
+     *
+     * @param fileName name of the file from which to load
+     * @return a PdpGroups
+     */
+    protected PdpGroups loadPdpGroups(String fileName) {
+        return loadFile(fileName, PdpGroups.class);
     }
 
     /**
index 8313d19..0c14aa2 100644 (file)
@@ -50,19 +50,42 @@ public class TestGroupData {
     }
 
     @Test
-    public void test() {
+    public void testNew() {
+        data = new GroupData(oldGroup, true);
+        assertSame(oldGroup, data.getGroup());
+
+        assertTrue(data.isNew());
+        assertFalse(data.isUpdated());
+
+        data.update(newGroup);
+        assertTrue(data.isNew());
+        assertFalse(data.isUpdated());
+        assertSame(newGroup, data.getGroup());
+
+        // repeat with a new group
+        newGroup = new PdpGroup(oldGroup);
+        data.update(newGroup);
+        assertTrue(data.isNew());
+        assertFalse(data.isUpdated());
+        assertSame(newGroup, data.getGroup());
+    }
+
+    @Test
+    public void testUpdateOnly() {
         assertFalse(data.isUpdated());
         assertSame(oldGroup, data.getGroup());
 
         data.update(newGroup);
 
         assertTrue(data.isUpdated());
+        assertFalse(data.isNew());
         assertSame(newGroup, data.getGroup());
 
         // repeat
         newGroup = new PdpGroup(oldGroup);
         data.update(newGroup);
         assertTrue(data.isUpdated());
+        assertFalse(data.isNew());
         assertSame(newGroup, data.getGroup());
 
         // incorrect name
index c392878..458ca9e 100644 (file)
@@ -31,14 +31,13 @@ import org.onap.policy.pap.main.rest.CommonPapRestServer;
 
 public class TestPdpGroupDeleteControllerV1 extends CommonPapRestServer {
 
-    private static final String NOT_IMPLEMENTED_YET = "not implemented yet";
+    private static final String GROUP_NOT_FOUND = "group not found";
     private static final String DELETE_GROUP_ENDPOINT = "pdps/groups";
     private static final String DELETE_POLICIES_ENDPOINT = "pdps/policies";
 
     @Test
     public void testSwagger() throws Exception {
         super.testSwagger(DELETE_GROUP_ENDPOINT + "/{name}");
-        super.testSwagger(DELETE_GROUP_ENDPOINT + "/{name}/versions/{version}");
 
         super.testSwagger(DELETE_POLICIES_ENDPOINT + "/{name}");
         super.testSwagger(DELETE_POLICIES_ENDPOINT + "/{name}/versions/{version}");
@@ -52,31 +51,12 @@ public class TestPdpGroupDeleteControllerV1 extends CommonPapRestServer {
         Response rawresp = invocationBuilder.delete();
         PdpGroupDeleteResponse resp = rawresp.readEntity(PdpGroupDeleteResponse.class);
         assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), rawresp.getStatus());
-        assertEquals(NOT_IMPLEMENTED_YET, resp.getErrorDetails());
+        assertEquals(GROUP_NOT_FOUND, resp.getErrorDetails());
 
         rawresp = invocationBuilder.delete();
         resp = rawresp.readEntity(PdpGroupDeleteResponse.class);
         assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), rawresp.getStatus());
-        assertEquals(NOT_IMPLEMENTED_YET, resp.getErrorDetails());
-
-        // verify it fails when no authorization info is included
-        checkUnauthRequest(uri, req -> req.delete());
-    }
-
-    @Test
-    public void testDeleteGroupVersion() throws Exception {
-        String uri = DELETE_GROUP_ENDPOINT + "/my-name/versions/1.2.3";
-
-        Invocation.Builder invocationBuilder = sendRequest(uri);
-        Response rawresp = invocationBuilder.delete();
-        PdpGroupDeleteResponse resp = rawresp.readEntity(PdpGroupDeleteResponse.class);
-        assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), rawresp.getStatus());
-        assertEquals(NOT_IMPLEMENTED_YET, resp.getErrorDetails());
-
-        rawresp = invocationBuilder.delete();
-        resp = rawresp.readEntity(PdpGroupDeleteResponse.class);
-        assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), rawresp.getStatus());
-        assertEquals(NOT_IMPLEMENTED_YET, resp.getErrorDetails());
+        assertEquals(GROUP_NOT_FOUND, resp.getErrorDetails());
 
         // verify it fails when no authorization info is included
         checkUnauthRequest(uri, req -> req.delete());
index dbb7951..c0b0864 100644 (file)
@@ -1,4 +1,4 @@
-/*
+/*-
  * ============LICENSE_START=======================================================
  * ONAP PAP
  * ================================================================================
@@ -29,7 +29,9 @@ import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import java.util.Arrays;
@@ -57,7 +59,6 @@ public class TestPdpGroupDeleteProvider extends ProviderSuper {
     private static final String POLICY1_NAME = "policyA";
     private static final String POLICY1_VERSION = "1.2.3";
     private static final String GROUP1_NAME = "groupA";
-    private static final String GROUP1_VERSION = "200.2.3";
 
     private MyProvider prov;
     private SessionData session;
@@ -91,10 +92,62 @@ public class TestPdpGroupDeleteProvider extends ProviderSuper {
     }
 
     @Test
-    public void testDeleteGroup() {
-        Pair<Status, PdpGroupDeleteResponse> pair = prov.deleteGroup(GROUP1_NAME, GROUP1_VERSION);
-        assertEquals(Status.INTERNAL_SERVER_ERROR, pair.getLeft());
-        assertEquals("not implemented yet", pair.getRight().getErrorDetails());
+    public void testDeleteGroup_Inctive() throws Exception {
+        PdpGroup group = loadGroup("deleteGroup.json");
+
+        when(session.getGroup(GROUP1_NAME)).thenReturn(group);
+
+        prov.deleteGroup(GROUP1_NAME);
+
+        verify(session).deleteGroupFromDb(group);
+
+        // should be no PDP requests
+        verify(session, never()).addRequests(any(), any());
+    }
+
+    @Test
+    public void testDeleteGroup_Active() throws Exception {
+        PdpGroup group = loadGroup("deleteGroup.json");
+
+        group.setPdpGroupState(PdpState.ACTIVE);
+
+        when(session.getGroup(GROUP1_NAME)).thenReturn(group);
+
+        assertThatThrownBy(() -> prov.deleteGroup(GROUP1_NAME)).isInstanceOf(PolicyPapRuntimeException.class)
+                        .hasMessage("group is still ACTIVE");
+    }
+
+    @Test
+    public void testDeleteGroup_NotFound() throws Exception {
+        assertThatThrownBy(() -> prov.deleteGroup(GROUP1_NAME)).isInstanceOf(PolicyPapRuntimeException.class)
+                        .hasMessage("group not found");
+    }
+
+    @Test
+    public void testDeleteGroup_Inactive() throws Exception {
+        PdpGroup group = loadGroup("deleteGroup.json");
+
+        when(session.getGroup(GROUP1_NAME)).thenReturn(group);
+
+        prov.deleteGroup(GROUP1_NAME);
+
+        verify(session).deleteGroupFromDb(group);
+
+        // should done no requests for the PDPs
+        verify(session, never()).addRequests(any(), any());
+    }
+
+    @Test
+    public void testDeleteGroup_DaoEx() throws Exception {
+        PdpGroup group = loadGroup("deleteGroup.json");
+
+        when(session.getGroup(GROUP1_NAME)).thenReturn(group);
+
+        PfModelException ex = new PfModelException(Status.BAD_REQUEST, EXPECTED_EXCEPTION);
+        doThrow(ex).when(session).deleteGroupFromDb(group);
+
+        assertThatThrownBy(() -> prov.deleteGroup(GROUP1_NAME)).isInstanceOf(PolicyPapRuntimeException.class)
+                        .hasMessage(ProviderBase.DB_ERROR_MSG);
     }
 
     @Test
index 8ca205c..cad73d9 100644 (file)
@@ -22,6 +22,7 @@ package org.onap.policy.pap.main.rest.depundep;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.never;
@@ -29,7 +30,12 @@ import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.onap.policy.pap.main.rest.depundep.ProviderBase.DB_ERROR_MSG;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
 import javax.ws.rs.core.Response.Status;
 import org.apache.commons.lang3.tuple.Pair;
 import org.junit.AfterClass;
@@ -39,14 +45,21 @@ import org.onap.policy.common.utils.services.Registry;
 import org.onap.policy.models.base.PfModelException;
 import org.onap.policy.models.pap.concepts.PdpDeployPolicies;
 import org.onap.policy.models.pap.concepts.PdpGroupDeployResponse;
+import org.onap.policy.models.pdp.concepts.PdpGroup;
 import org.onap.policy.models.pdp.concepts.PdpGroups;
+import org.onap.policy.models.pdp.concepts.PdpSubGroup;
 import org.onap.policy.models.pdp.concepts.PdpUpdate;
+import org.onap.policy.models.pdp.enums.PdpState;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifier;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyTypeIdentifier;
 
 public class TestPdpGroupDeployProvider extends ProviderSuper {
     private static final String EXPECTED_EXCEPTION = "expected exception";
     private static final Object REQUEST_FAILED_MSG = "request failed";
 
     private static final String POLICY1_NAME = "policyA";
+    private static final String POLICY2_NAME = "policyB";
     private static final String POLICY1_VERSION = "1.2.3";
     private static final String GROUP1_NAME = "groupA";
     private static final String PDP1_TYPE = "pdpTypeA";
@@ -79,10 +92,365 @@ public class TestPdpGroupDeployProvider extends ProviderSuper {
     }
 
     @Test
-    public void testDeployGroup() {
-        Pair<Status, PdpGroupDeployResponse> pair = prov.deployGroup(new PdpGroups());
+    public void testCreateOrUpdateGroups() throws Exception {
+        Pair<Status, PdpGroupDeployResponse> pair = prov.createOrUpdateGroups(loadPdpGroups("emptyGroups.json"));
+        assertEquals(Status.OK, pair.getLeft());
+        assertNull(pair.getRight().getErrorDetails());
+
+        // no groups, so no action should have been taken
+        assertNoGroupAction();
+    }
+
+    @Test
+    public void testCreateOrUpdateGroups_InvalidRequest() throws Exception {
+        Pair<Status, PdpGroupDeployResponse> pair = prov.createOrUpdateGroups(new PdpGroups());
+        assertEquals(Status.INTERNAL_SERVER_ERROR, pair.getLeft());
+        assertTrue(pair.getRight().getErrorDetails().contains("is null"));
+
+        assertNoGroupAction();
+    }
+
+    @Test
+    public void testCreateOrUpdate_Invalid() throws Exception {
+        PdpGroups groups = loadPdpGroups("createGroups.json");
+        groups.getGroups().get(0).setPdpGroupState(PdpState.TERMINATED);
+
+        Pair<Status, PdpGroupDeployResponse> pair = prov.createOrUpdateGroups(groups);
         assertEquals(Status.INTERNAL_SERVER_ERROR, pair.getLeft());
-        assertEquals("not implemented yet", pair.getRight().getErrorDetails());
+        assertTrue(pair.getRight().getErrorDetails().contains("pdpGroupState"));
+
+        assertNoGroupAction();
+    }
+
+    @Test
+    public void testAddGroup() throws Exception {
+        PdpGroups groups = loadPdpGroups("createGroups.json");
+        PdpGroup group = groups.getGroups().get(0);
+        group.setPdpGroupState(PdpState.PASSIVE);
+
+        assertEquals(Status.OK, prov.createOrUpdateGroups(groups).getLeft());
+
+        // should not have updated the state
+        assertEquals(PdpState.PASSIVE, group.getPdpGroupState());
+
+        assertSame(group, getGroupCreates().get(0));
+    }
+
+    @Test
+    public void testAddGroup_Invalid() throws Exception {
+        PdpGroups groups = loadPdpGroups("createGroups.json");
+        groups.getGroups().get(0).setPdpGroupState(PdpState.TERMINATED);
+
+        Pair<Status, PdpGroupDeployResponse> pair = prov.createOrUpdateGroups(groups);
+        assertEquals(Status.INTERNAL_SERVER_ERROR, pair.getLeft());
+        assertTrue(pair.getRight().getErrorDetails().contains("pdpGroupState"));
+
+        assertNoGroupAction();
+    }
+
+    @Test
+    public void testAddGroup_InvalidSubGroup() throws Exception {
+        PdpGroups groups = loadPdpGroups("createGroups.json");
+
+        // policy won't match supported type
+        groups.getGroups().get(0).getPdpSubgroups().get(0).getSupportedPolicyTypes().get(0).setVersion("99.99.99");
+
+        Pair<Status, PdpGroupDeployResponse> pair = prov.createOrUpdateGroups(groups);
+        assertEquals(Status.INTERNAL_SERVER_ERROR, pair.getLeft());
+        assertTrue(pair.getRight().getErrorDetails().contains("supported policy"));
+
+        assertNoGroupAction();
+    }
+
+    @Test
+    public void testValidateGroupOnly_NullState() {
+        PdpGroups groups = loadPdpGroups("createGroups.json");
+        groups.getGroups().get(0).setPdpGroupState(null);
+        assertEquals(Status.OK, prov.createOrUpdateGroups(groups).getLeft());
+    }
+
+    @Test
+    public void testValidateGroupOnly_Active() {
+        PdpGroups groups = loadPdpGroups("createGroups.json");
+        groups.getGroups().get(0).setPdpGroupState(PdpState.ACTIVE);
+        assertEquals(Status.OK, prov.createOrUpdateGroups(groups).getLeft());
+    }
+
+    @Test
+    public void testValidateGroupOnly_Passive() {
+        PdpGroups groups = loadPdpGroups("createGroups.json");
+        groups.getGroups().get(0).setPdpGroupState(PdpState.PASSIVE);
+        assertEquals(Status.OK, prov.createOrUpdateGroups(groups).getLeft());
+    }
+
+    @Test
+    public void testValidateGroupOnly_Invalid() {
+        PdpGroups groups = loadPdpGroups("createGroups.json");
+        groups.getGroups().get(0).setPdpGroupState(PdpState.TERMINATED);
+
+        Pair<Status, PdpGroupDeployResponse> pair = prov.createOrUpdateGroups(groups);
+        assertEquals(Status.INTERNAL_SERVER_ERROR, pair.getLeft());
+        assertTrue(pair.getRight().getErrorDetails().contains("pdpGroupState"));
+    }
+
+    @Test
+    public void testUpdateGroup() throws Exception {
+        PdpGroups groups = loadPdpGroups("createGroups.json");
+
+        // DB group = new group
+        PdpGroup group = new PdpGroup(groups.getGroups().get(0));
+        when(dao.getPdpGroups(group.getName())).thenReturn(Arrays.asList(group));
+
+        assertEquals(Status.OK, prov.createOrUpdateGroups(groups).getLeft());
+
+        assertNoGroupAction();
+    }
+
+    @Test
+    public void testUpdateGroup_PropertiesChanged() throws Exception {
+        PdpGroups groups = loadPdpGroups("createGroups.json");
+
+        PdpGroup group = new PdpGroup(groups.getGroups().get(0));
+        group.setProperties(new TreeMap<>());
+
+        when(dao.getPdpGroups(group.getName())).thenReturn(Arrays.asList(group));
+
+        Pair<Status, PdpGroupDeployResponse> pair = prov.createOrUpdateGroups(groups);
+        assertEquals(Status.INTERNAL_SERVER_ERROR, pair.getLeft());
+        assertTrue(pair.getRight().getErrorDetails().contains("properties"));
+
+        assertNoGroupAction();
+    }
+
+    @Test
+    public void testUpdateGroup_NewDescription() throws Exception {
+        PdpGroups groups = loadPdpGroups("createGroups.json");
+        PdpGroup newgrp = groups.getGroups().get(0);
+        PdpGroup group = new PdpGroup(newgrp);
+        group.setDescription("old description");
+        when(dao.getPdpGroups(group.getName())).thenReturn(Arrays.asList(group));
+
+        assertEquals(Status.OK, prov.createOrUpdateGroups(groups).getLeft());
+
+        assertGroupUpdateOnly(group);
+
+        assertEquals(group.getDescription(), "my description");
+        assertEquals(newgrp.toString(), group.toString());
+    }
+
+    @Test
+    public void testUpdateGroup_NewSubGroup() throws Exception {
+        PdpGroups groups = loadPdpGroups("createGroupsNewSub.json");
+        PdpGroup group = loadPdpGroups("createGroups.json").getGroups().get(0);
+        when(dao.getPdpGroups(group.getName())).thenReturn(Arrays.asList(group));
+
+        assertEquals(Status.OK, prov.createOrUpdateGroups(groups).getLeft());
+
+        PdpGroup newgrp = groups.getGroups().get(0);
+        assertEquals(newgrp.toString(), group.toString());
+        assertGroupUpdateOnly(group);
+    }
+
+    @Test
+    public void testUpdateGroup_UpdatedSubGroup() throws Exception {
+        PdpGroups groups = loadPdpGroups("createGroups.json");
+        PdpGroup newgrp = groups.getGroups().get(0);
+        PdpGroup group = new PdpGroup(newgrp);
+        when(dao.getPdpGroups(group.getName())).thenReturn(Arrays.asList(group));
+
+        // something different in this subgroup
+        group.getPdpSubgroups().get(0).setDesiredInstanceCount(10);
+
+        assertEquals(Status.OK, prov.createOrUpdateGroups(groups).getLeft());
+
+        assertEquals(newgrp.toString(), group.toString());
+        assertGroupUpdateOnly(group);
+    }
+
+    @Test
+    public void testUpdateGroup_MultipleChanges() throws Exception {
+        PdpGroups groups = loadPdpGroups("createGroups.json");
+        PdpGroup newgrp = groups.getGroups().get(0);
+        PdpGroup group = new PdpGroup(newgrp);
+        when(dao.getPdpGroups(group.getName())).thenReturn(Arrays.asList(group));
+
+        PdpSubGroup subgrp = newgrp.getPdpSubgroups().get(0);
+        subgrp.setDesiredInstanceCount(30);
+        subgrp.getPolicies().add(new ToscaPolicyIdentifier(POLICY2_NAME, POLICY1_VERSION));
+        subgrp.getSupportedPolicyTypes().add(new ToscaPolicyTypeIdentifier("typeX", "9.8.7"));
+
+        when(dao.getPolicyList(POLICY2_NAME, POLICY1_VERSION)).thenReturn(loadPolicies("createGroupNewPolicy.json"));
+
+        assertEquals(Status.OK, prov.createOrUpdateGroups(groups).getLeft());
+
+        Collections.sort(newgrp.getPdpSubgroups().get(0).getPolicies());
+        Collections.sort(group.getPdpSubgroups().get(0).getPolicies());
+
+        assertEquals(newgrp.toString(), group.toString());
+
+        // this requires a PDP UPDATE message
+        assertGroupUpdate(group, subgrp);
+    }
+
+    @Test
+    public void testUpdateField_Unchanged() throws Exception {
+        PdpGroups groups = loadPdpGroups("createGroups.json");
+        PdpGroup newgrp = groups.getGroups().get(0);
+        PdpGroup group = new PdpGroup(newgrp);
+        when(dao.getPdpGroups(group.getName())).thenReturn(Arrays.asList(group));
+
+        assertEquals(Status.OK, prov.createOrUpdateGroups(groups).getLeft());
+
+        assertNoGroupAction();
+    }
+
+    @Test
+    public void testUpdateField_WasNull() throws Exception {
+        PdpGroups groups = loadPdpGroups("createGroups.json");
+        PdpGroup newgrp = groups.getGroups().get(0);
+        PdpGroup group = new PdpGroup(newgrp);
+        when(dao.getPdpGroups(group.getName())).thenReturn(Arrays.asList(group));
+
+        group.setDescription(null);
+
+        assertEquals(Status.OK, prov.createOrUpdateGroups(groups).getLeft());
+
+        assertEquals(newgrp.toString(), group.toString());
+        assertGroupUpdateOnly(group);
+    }
+
+    @Test
+    public void testUpdateField_NowNull() throws Exception {
+        PdpGroups groups = loadPdpGroups("createGroups.json");
+        PdpGroup newgrp = groups.getGroups().get(0);
+        PdpGroup group = new PdpGroup(newgrp);
+        when(dao.getPdpGroups(group.getName())).thenReturn(Arrays.asList(group));
+
+        newgrp.setDescription(null);
+
+        assertEquals(Status.OK, prov.createOrUpdateGroups(groups).getLeft());
+
+        assertEquals(newgrp.toString(), group.toString());
+        assertGroupUpdateOnly(group);
+    }
+
+    @Test
+    public void testUpdateField_Changed() throws Exception {
+        PdpGroups groups = loadPdpGroups("createGroups.json");
+        PdpGroup newgrp = groups.getGroups().get(0);
+        PdpGroup group = new PdpGroup(newgrp);
+        when(dao.getPdpGroups(group.getName())).thenReturn(Arrays.asList(group));
+
+        newgrp.setDescription(group.getDescription() + "-changed");
+
+        assertEquals(Status.OK, prov.createOrUpdateGroups(groups).getLeft());
+
+        assertEquals(newgrp.toString(), group.toString());
+        assertGroupUpdateOnly(group);
+    }
+
+    @Test
+    public void testAddSubGroup() throws Exception {
+        PdpGroups groups = loadPdpGroups("createGroupsNewSub.json");
+        PdpGroup group = loadPdpGroups("createGroups.json").getGroups().get(0);
+        when(dao.getPdpGroups(group.getName())).thenReturn(Arrays.asList(group));
+
+        assertEquals(Status.OK, prov.createOrUpdateGroups(groups).getLeft());
+
+        PdpGroup newgrp = groups.getGroups().get(0);
+
+        PdpSubGroup newsub = newgrp.getPdpSubgroups().get(1);
+        newsub.setCurrentInstanceCount(0);
+        newsub.setPdpInstances(new ArrayList<>(0));
+
+        assertEquals(newgrp.toString(), group.toString());
+        assertGroupUpdateOnly(group);
+    }
+
+    @Test
+    public void testUpdateSubGroup_Invalid() throws Exception {
+        PdpGroups groups = loadPdpGroups("createGroups.json");
+        PdpGroup newgrp = groups.getGroups().get(0);
+        PdpGroup group = new PdpGroup(newgrp);
+        when(dao.getPdpGroups(group.getName())).thenReturn(Arrays.asList(group));
+
+        // change properties
+        newgrp.getPdpSubgroups().get(0).setProperties(new TreeMap<>());
+
+        Pair<Status, PdpGroupDeployResponse> pair = prov.createOrUpdateGroups(groups);
+        assertEquals(Status.INTERNAL_SERVER_ERROR, pair.getLeft());
+        assertTrue(pair.getRight().getErrorDetails().contains("properties"));
+
+        assertNoGroupAction();
+    }
+
+    @Test
+    public void testUpdateSubGroup_SupportedPolicies() throws Exception {
+        PdpGroups groups = loadPdpGroups("createGroups.json");
+        PdpGroup newgrp = groups.getGroups().get(0);
+        PdpGroup group = new PdpGroup(newgrp);
+        when(dao.getPdpGroups(group.getName())).thenReturn(Arrays.asList(group));
+
+        newgrp.getPdpSubgroups().get(0).getSupportedPolicyTypes().add(new ToscaPolicyTypeIdentifier("typeX", "9.8.7"));
+
+        assertEquals(Status.OK, prov.createOrUpdateGroups(groups).getLeft());
+
+        assertEquals(newgrp.toString(), group.toString());
+        assertGroupUpdateOnly(group);
+    }
+
+    @Test
+    public void testUpdateSubGroup_DesiredCount() throws Exception {
+        PdpGroups groups = loadPdpGroups("createGroups.json");
+        PdpGroup newgrp = groups.getGroups().get(0);
+        PdpGroup group = new PdpGroup(newgrp);
+        when(dao.getPdpGroups(group.getName())).thenReturn(Arrays.asList(group));
+
+        newgrp.getPdpSubgroups().get(0).setDesiredInstanceCount(20);
+
+        assertEquals(Status.OK, prov.createOrUpdateGroups(groups).getLeft());
+
+        assertEquals(newgrp.toString(), group.toString());
+        assertGroupUpdateOnly(group);
+    }
+
+    @Test
+    public void testUpdateSubGroup_Policies() throws Exception {
+        PdpGroups groups = loadPdpGroups("createGroups.json");
+        PdpGroup newgrp = groups.getGroups().get(0);
+        PdpGroup group = new PdpGroup(newgrp);
+        when(dao.getPdpGroups(group.getName())).thenReturn(Arrays.asList(group));
+
+        PdpSubGroup subgrp = newgrp.getPdpSubgroups().get(0);
+        subgrp.getPolicies().add(new ToscaPolicyIdentifier(POLICY2_NAME, POLICY1_VERSION));
+
+        when(dao.getPolicyList(POLICY2_NAME, POLICY1_VERSION)).thenReturn(loadPolicies("createGroupNewPolicy.json"));
+
+        assertEquals(Status.OK, prov.createOrUpdateGroups(groups).getLeft());
+
+        Collections.sort(newgrp.getPdpSubgroups().get(0).getPolicies());
+        Collections.sort(group.getPdpSubgroups().get(0).getPolicies());
+
+        assertEquals(newgrp.toString(), group.toString());
+
+        // this requires a PDP UPDATE message
+        assertGroupUpdate(group, subgrp);
+    }
+
+    @Test
+    public void testValidateSubGroup_PropertiesMismatch() throws Exception {
+        PdpGroups groups = loadPdpGroups("createGroups.json");
+        PdpGroup newgrp = groups.getGroups().get(0);
+        PdpGroup group = new PdpGroup(newgrp);
+        when(dao.getPdpGroups(group.getName())).thenReturn(Arrays.asList(group));
+
+        newgrp.setProperties(new TreeMap<>());
+
+        Pair<Status, PdpGroupDeployResponse> pair = prov.createOrUpdateGroups(groups);
+        assertEquals(Status.INTERNAL_SERVER_ERROR, pair.getLeft());
+        assertTrue(pair.getRight().getErrorDetails().contains("properties"));
+
+        assertNoGroupAction();
     }
 
     @Test
@@ -180,6 +548,44 @@ public class TestPdpGroupDeployProvider extends ProviderSuper {
         assertTrue(update.getPolicies().contains(policy1));
     }
 
+    private void assertNoGroupAction() throws Exception {
+        verify(dao, never()).createPdpGroups(any());
+        verify(dao, never()).updatePdpGroups(any());
+        verify(reqmap, never()).addRequest(any(), any());
+    }
+
+    private void assertGroupUpdate(PdpGroup group, PdpSubGroup subgrp) throws Exception {
+        verify(dao, never()).createPdpGroups(any());
+
+        assertEquals(0, getStateChangeRequests(1).size());
+
+        List<PdpUpdate> pdpUpdates = getUpdateRequests(1);
+        assertEquals(1, pdpUpdates.size());
+
+        PdpUpdate pdpUpdate = pdpUpdates.get(0);
+        assertEquals("pdpA", pdpUpdate.getName());
+        assertEquals(group.getName(), pdpUpdate.getPdpGroup());
+
+        assertEquals(subgrp.getPdpType(), pdpUpdate.getPdpSubgroup());
+
+        List<ToscaPolicyIdentifier> pdpPolicies =
+                        pdpUpdate.getPolicies().stream().map(ToscaPolicy::getIdentifier).collect(Collectors.toList());
+        Collections.sort(pdpPolicies);
+
+        assertEquals(subgrp.getPolicies().toString(), pdpPolicies.toString());
+
+        List<PdpGroup> updates = getGroupUpdates();
+        assertEquals(Arrays.asList(group), updates);
+    }
+
+    private void assertGroupUpdateOnly(PdpGroup group) throws Exception {
+        verify(dao, never()).createPdpGroups(any());
+        verify(reqmap, never()).addRequest(any(), any());
+
+        List<PdpGroup> updates = getGroupUpdates();
+        assertEquals(Arrays.asList(group), updates);
+    }
+
     /**
      * Loads a standard request.
      *
index fd351c8..2eac432 100644 (file)
 
 package org.onap.policy.pap.main.rest.depundep;
 
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
 import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.never;
@@ -35,20 +37,23 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.Iterator;
 import java.util.List;
 import javax.ws.rs.core.Response.Status;
+import org.apache.commons.lang3.tuple.Pair;
 import org.junit.Before;
 import org.junit.Test;
 import org.onap.policy.models.base.PfModelException;
 import org.onap.policy.models.pdp.concepts.PdpGroup;
+import org.onap.policy.models.pdp.concepts.PdpStateChange;
 import org.onap.policy.models.pdp.concepts.PdpUpdate;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifier;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyTypeIdentifier;
+import org.onap.policy.pap.main.PolicyPapRuntimeException;
 
 public class TestSessionData extends ProviderSuper {
     private static final String GROUP_NAME = "groupA";
-    private static final String GROUP_NAME2 = "groupB";
     private static final String PDP1 = "pdp_1";
     private static final String PDP2 = "pdp_2";
     private static final String PDP3 = "pdp_3";
@@ -58,6 +63,7 @@ public class TestSessionData extends ProviderSuper {
     private static final String POLICY_VERSION2 = POLICY_VERSION_PREFIX + "4";
     private static final String POLICY_TYPE = "myType";
     private static final String POLICY_TYPE_VERSION = "10.20.30";
+    private static final String EXPECTED_EXCEPTION = "expected exception";
 
     private SessionData session;
     private ToscaPolicyIdentifier ident;
@@ -78,8 +84,8 @@ public class TestSessionData extends ProviderSuper {
         ident = new ToscaPolicyIdentifier(POLICY_NAME, POLICY_VERSION);
         type = new ToscaPolicyTypeIdentifier(POLICY_TYPE, POLICY_TYPE_VERSION);
         type2 = new ToscaPolicyTypeIdentifier(POLICY_TYPE, POLICY_TYPE_VERSION + "0");
-        group1 = makeGroup(GROUP_NAME);
-        group2 = makeGroup(GROUP_NAME2);
+        group1 = loadGroup("group1.json");
+        group2 = loadGroup("group2.json");
 
         session = new SessionData(dao);
     }
@@ -115,14 +121,6 @@ public class TestSessionData extends ProviderSuper {
         assertThatThrownBy(() -> session.getPolicy(ident)).hasMessage("cannot find policy: myPolicy 1.2.3");
     }
 
-    @Test
-    public void testGetPolicy_TooMany() throws Exception {
-        ToscaPolicy policy = new ToscaPolicy();
-        when(dao.getPolicyList(any(), any())).thenReturn(Arrays.asList(policy, policy));
-
-        assertThatThrownBy(() -> session.getPolicy(ident)).hasMessage("too many policies match: myPolicy 1.2.3");
-    }
-
     @Test
     public void testGetPolicy_DaoEx() throws Exception {
         PfModelException ex = new PfModelException(Status.INTERNAL_SERVER_ERROR, "expected exception");
@@ -131,6 +129,71 @@ public class TestSessionData extends ProviderSuper {
         assertThatThrownBy(() -> session.getPolicy(ident)).hasMessage("cannot get policy: myPolicy 1.2.3").hasCause(ex);
     }
 
+    @Test
+    public void testAddRequests_testGetPdpStateChanges_testGetPdpUpdates() {
+        // pre-load with a update and state-change for other PDPs
+        PdpUpdate update2 = makeUpdate(PDP2);
+        session.addUpdate(update2);
+
+        PdpStateChange change3 = makeStateChange(PDP3);
+        session.addStateChange(change3);
+
+        // add requests
+        PdpUpdate update = makeUpdate(PDP1);
+        PdpStateChange change = makeStateChange(PDP1);
+        session.addRequests(update, change);
+        verifyRequests(update, update2, change, change3);
+
+        /*
+         * repeat with a new pair
+         */
+        update = makeUpdate(PDP1);
+        change = makeStateChange(PDP1);
+        session.addRequests(update, change);
+        verifyRequests(update, update2, change, change3);
+
+        // just make an update this time
+        update = makeUpdate(PDP1);
+        session.addUpdate(update);
+        verifyRequests(update, update2, change, change3);
+    }
+
+    private void verifyRequests(PdpUpdate update, PdpUpdate update2, PdpStateChange change, PdpStateChange change3) {
+        List<Pair<PdpUpdate, PdpStateChange>> requests = sort(session.getPdpRequests(), this::compare);
+        assertEquals(3, requests.size());
+
+        System.out.println(requests);
+        System.out.println(update);
+
+        Iterator<Pair<PdpUpdate, PdpStateChange>> reqiter = requests.iterator();
+        Pair<PdpUpdate, PdpStateChange> pair = reqiter.next();
+        assertSame(update, pair.getLeft());
+        assertSame(change, pair.getRight());
+
+        pair = reqiter.next();
+        assertSame(update2, pair.getLeft());
+        assertSame(null, pair.getRight());
+
+        pair = reqiter.next();
+        assertSame(null, pair.getLeft());
+        assertSame(change3, pair.getRight());
+
+        // verify individual lists
+        List<PdpUpdate> updates = Arrays.asList(update, update2);
+        assertEquals(sort(updates, this::compare), sort(session.getPdpUpdates(), this::compare));
+
+        List<PdpStateChange> changes = Arrays.asList(change, change3);
+        assertEquals(sort(changes, this::compare), sort(session.getPdpStateChanges(), this::compare));
+    }
+
+    @Test
+    public void testAddRequests_MismatchedNames() {
+        PdpUpdate update = makeUpdate(PDP1);
+        PdpStateChange change = makeStateChange(PDP2);
+        assertThatIllegalArgumentException().isThrownBy(() -> session.addRequests(update, change))
+                        .withMessage("PDP name mismatch pdp_1, pdp_2");
+    }
+
     @Test
     public void testAddUpdate_testGetPdpUpdates() {
         // several different updates, but one duplicate
@@ -143,17 +206,40 @@ public class TestSessionData extends ProviderSuper {
         PdpUpdate update3 = makeUpdate(PDP3);
         session.addUpdate(update3);
 
-        List<PdpUpdate> lst = sort(session.getPdpUpdates(), this::compare);
+        List<PdpUpdate> lst = sort(getUpdateRequests(), this::compare);
         assertEquals(Arrays.asList(update1, update2, update3).toString(), lst.toString());
 
         // overwrite one
         update2 = makeUpdate(PDP2);
         session.addUpdate(update2);
 
-        lst = sort(session.getPdpUpdates(), this::compare);
+        lst = sort(getUpdateRequests(), this::compare);
         assertEquals(Arrays.asList(update1, update2, update3).toString(), lst.toString());
     }
 
+    @Test
+    public void testAddStateChange_testGetPdpStateChanges() {
+        // several different changes, but one duplicate
+        PdpStateChange change1 = makeStateChange(PDP1);
+        session.addStateChange(change1);
+
+        PdpStateChange change2 = makeStateChange(PDP2);
+        session.addStateChange(change2);
+
+        PdpStateChange change3 = makeStateChange(PDP3);
+        session.addStateChange(change3);
+
+        List<PdpStateChange> lst = sort(getStateChangeRequests(), this::compare);
+        assertEquals(Arrays.asList(change1, change2, change3).toString(), lst.toString());
+
+        // overwrite one
+        change2 = makeStateChange(PDP2);
+        session.addStateChange(change2);
+
+        lst = sort(getStateChangeRequests(), this::compare);
+        assertEquals(Arrays.asList(change1, change2, change3).toString(), lst.toString());
+    }
+
     private ToscaPolicy makePolicy(String name, String version) {
         ToscaPolicy policy = new ToscaPolicy();
 
@@ -183,12 +269,19 @@ public class TestSessionData extends ProviderSuper {
         assertThatThrownBy(() -> session.getPolicyMaxVersion(POLICY_NAME)).hasMessage("cannot find policy: myPolicy");
     }
 
-    private PdpGroup makeGroup(String name) {
-        PdpGroup group = new PdpGroup();
-
-        group.setName(name);
-
-        return group;
+    @Test
+    public void testCreate() throws Exception {
+        session.create(group1);
+        assertSame(group1, session.getGroup(group1.getName()));
+
+        // can add another
+        session.create(group2);
+        assertSame(group1, session.getGroup(group1.getName()));
+        assertSame(group2, session.getGroup(group2.getName()));
+
+        // cannot overwrite
+        assertThatIllegalStateException().isThrownBy(() -> session.create(group1))
+                        .withMessage("group already cached: groupA");
     }
 
     @Test
@@ -228,6 +321,48 @@ public class TestSessionData extends ProviderSuper {
                         .withMessage("group not cached: groupA");
     }
 
+    @Test
+    public void testGetGroup() throws Exception {
+        when(dao.getPdpGroups(GROUP_NAME)).thenReturn(Arrays.asList(group1));
+
+        assertSame(group1, session.getGroup(GROUP_NAME));
+        verify(dao).getPdpGroups(any());
+
+        // repeat
+        assertSame(group1, session.getGroup(GROUP_NAME));
+
+        // should not access dao again
+        verify(dao, times(1)).getPdpGroups(any());
+    }
+
+    @Test
+    public void testGetGroup_NotFound() throws Exception {
+        when(dao.getPdpGroups(GROUP_NAME)).thenReturn(Collections.emptyList());
+
+        assertNull(session.getGroup(GROUP_NAME));
+        verify(dao).getPdpGroups(any());
+
+        // repeat
+        assertNull(session.getGroup(GROUP_NAME));
+
+        // SHOULD access dao again
+        verify(dao, times(2)).getPdpGroups(GROUP_NAME);
+
+        // find it this time
+        when(dao.getPdpGroups(GROUP_NAME)).thenReturn(Arrays.asList(group1));
+        assertSame(group1, session.getGroup(GROUP_NAME));
+        verify(dao, times(3)).getPdpGroups(GROUP_NAME);
+    }
+
+    @Test
+    public void testGetGroup_DaoEx() throws Exception {
+        PfModelException ex = new PfModelException(Status.BAD_REQUEST, EXPECTED_EXCEPTION);
+        when(dao.getPdpGroups(GROUP_NAME)).thenThrow(ex);
+
+        assertThatThrownBy(() -> session.getGroup(GROUP_NAME)).isInstanceOf(PolicyPapRuntimeException.class)
+                        .hasCause(ex);
+    }
+
     @Test
     public void testGetActivePdpGroupsByPolicyType() throws Exception {
         List<PdpGroup> groups = Arrays.asList(group1, group2);
@@ -263,10 +398,17 @@ public class TestSessionData extends ProviderSuper {
     @Test
     public void testUpdateDb() throws Exception {
         // force the groups into the cache
-        PdpGroup group3 = makeGroup("groupC");
+        PdpGroup group3 = loadGroup("group3.json");
         when(dao.getFilteredPdpGroups(any())).thenReturn(Arrays.asList(group1, group2, group3));
         session.getActivePdpGroupsByPolicyType(type);
 
+        // create groups 4 & 5
+        PdpGroup group4 = loadGroup("group4.json");
+        session.create(group4);
+
+        PdpGroup group5 = loadGroup("group5.json");
+        session.create(group5);
+
         // update group 1
         when(dao.getFilteredPdpGroups(any())).thenReturn(Arrays.asList(group1));
         PdpGroup newgrp1 = new PdpGroup(group1);
@@ -281,13 +423,25 @@ public class TestSessionData extends ProviderSuper {
         PdpGroup newgrp3 = new PdpGroup(group3);
         session.update(newgrp3);
 
+        // update group 5
+        when(dao.getFilteredPdpGroups(any())).thenReturn(Arrays.asList(group5));
+        PdpGroup newgrp5 = new PdpGroup(group5);
+        session.update(newgrp5);
+
         // push the changes to the DB
         session.updateDb();
 
+        // expect one create for groups 4 & 5 (group5 replaced by newgrp5)
+        List<PdpGroup> creates = getGroupCreates();
+        assertEquals(2, creates.size());
+        assertSame(group4, creates.get(0));
+        assertSame(newgrp5, creates.get(1));
+
         // expect one update for groups 1 & 3
-        List<PdpGroup> changes = getGroupUpdates();
-        assertSame(newgrp1, changes.get(0));
-        assertSame(newgrp3, changes.get(1));
+        List<PdpGroup> updates = getGroupUpdates();
+        assertEquals(2, updates.size());
+        assertSame(newgrp1, updates.get(0));
+        assertSame(newgrp3, updates.get(1));
     }
 
     @Test
@@ -301,6 +455,13 @@ public class TestSessionData extends ProviderSuper {
         verify(dao, never()).updatePdpGroups(any());
     }
 
+    @Test
+    public void testDeleteGroupFromDb() throws Exception {
+        session.deleteGroupFromDb(group1);
+
+        verify(dao).deletePdpGroup(group1.getName());
+    }
+
     private PdpUpdate makeUpdate(String pdpName) {
         PdpUpdate update = new PdpUpdate();
 
@@ -309,6 +470,22 @@ public class TestSessionData extends ProviderSuper {
         return update;
     }
 
+    private PdpStateChange makeStateChange(String pdpName) {
+        PdpStateChange change = new PdpStateChange();
+
+        change.setName(pdpName);
+
+        return change;
+    }
+
+    private List<PdpUpdate> getUpdateRequests() {
+        return session.getPdpUpdates();
+    }
+
+    private List<PdpStateChange> getStateChangeRequests() {
+        return session.getPdpStateChanges();
+    }
+
     private <T> List<T> sort(Collection<T> collection, Comparator<T> comparator) {
         List<T> lst = new ArrayList<>(collection);
         Collections.sort(lst, comparator);
@@ -316,7 +493,19 @@ public class TestSessionData extends ProviderSuper {
         return lst;
     }
 
+    private int compare(Pair<PdpUpdate, PdpStateChange> left, Pair<PdpUpdate, PdpStateChange> right) {
+        return getName(left).compareTo(getName(right));
+    }
+
     private int compare(PdpUpdate left, PdpUpdate right) {
         return left.getName().compareTo(right.getName());
     }
+
+    private int compare(PdpStateChange left, PdpStateChange right) {
+        return left.getName().compareTo(right.getName());
+    }
+
+    private String getName(Pair<PdpUpdate, PdpStateChange> pair) {
+        return (pair.getKey() != null ? pair.getKey().getName() : pair.getValue().getName());
+    }
 }
diff --git a/main/src/test/resources/simpleDeploy/createGroupNewPolicy.json b/main/src/test/resources/simpleDeploy/createGroupNewPolicy.json
new file mode 100644 (file)
index 0000000..285db31
--- /dev/null
@@ -0,0 +1,10 @@
+{
+    "policies":  [
+        {
+            "name": "policyB",
+            "version": "1.2.3",
+            "type": "typeA",
+            "typeVersion": "100.2.3"
+        }
+    ]
+}
diff --git a/main/src/test/resources/simpleDeploy/createGroups.json b/main/src/test/resources/simpleDeploy/createGroups.json
new file mode 100644 (file)
index 0000000..59c4eb8
--- /dev/null
@@ -0,0 +1,39 @@
+{
+    "groups": [
+        {
+            "name": "groupA",
+            "version": "200.2.3",
+            "description": "my description",
+            "pdpGroupState": "ACTIVE",
+            "properties": {
+                "hello": "world"
+            },
+            "pdpSubgroups": [
+                {
+                    "pdpType": "pdpTypeA",
+                    "desiredInstanceCount": 1,
+                    "properties": {
+                        "abc": "def"
+                    },
+                    "supportedPolicyTypes": [
+                        {
+                            "name": "typeA",
+                            "version": "100.2.3"
+                        }
+                    ],
+                    "pdpInstances": [
+                        {
+                            "instanceId": "pdpA"
+                        }
+                    ],
+                    "policies": [
+                        {
+                            "name": "policyA",
+                            "version": "1.2.3"
+                        }
+                    ]
+                }
+            ]
+        }
+    ]
+}
diff --git a/main/src/test/resources/simpleDeploy/createGroupsNewSub.json b/main/src/test/resources/simpleDeploy/createGroupsNewSub.json
new file mode 100644 (file)
index 0000000..033ced8
--- /dev/null
@@ -0,0 +1,56 @@
+{
+    "groups": [
+        {
+            "name": "groupA",
+            "version": "200.2.3",
+            "description": "my description",
+            "pdpGroupState": "ACTIVE",
+            "properties": {
+                "hello": "world"
+            },
+            "pdpSubgroups": [
+                {
+                    "pdpType": "pdpTypeA",
+                    "desiredInstanceCount": 1,
+                    "properties": {
+                        "abc": "def"
+                    },
+                    "supportedPolicyTypes": [
+                        {
+                            "name": "typeA",
+                            "version": "100.2.3"
+                        }
+                    ],
+                    "pdpInstances": [
+                        {
+                            "instanceId": "pdpA"
+                        }
+                    ],
+                    "policies": [
+                        {
+                            "name": "policyA",
+                            "version": "1.2.3"
+                        }
+                    ]
+                },
+                {
+                    "pdpType": "pdpTypeB",
+                    "desiredInstanceCount": 1,
+                    "currentInstanceCount": 22,
+                    "supportedPolicyTypes": [
+                        {
+                            "name": "typeA",
+                            "version": "100.2.3"
+                        }
+                    ],
+                    "pdpInstances": [
+                        {
+                            "instanceId": "pdpB"
+                        }
+                    ],
+                    "policies": []
+                }
+            ]
+        }
+    ]
+}
diff --git a/main/src/test/resources/simpleDeploy/deleteGroup.json b/main/src/test/resources/simpleDeploy/deleteGroup.json
new file mode 100644 (file)
index 0000000..6def351
--- /dev/null
@@ -0,0 +1,29 @@
+{
+    "name": "groupA",
+    "pdpGroupState": "PASSIVE",
+    "pdpSubgroups": [
+        {
+            "pdpType": "pdpTypeA",
+            "pdpInstances": [
+                {
+                    "instanceId": "pdpA_1"
+                },
+                {
+                    "instanceId": "pdpA_2"
+                }
+            ]
+        },
+        {
+            "pdpType": "pdpTypeB",
+            "pdpInstances": []
+        },
+        {
+            "pdpType": "pdpTypeC",
+            "pdpInstances": [
+                {
+                    "instanceId": "pdpC_1"
+                }
+            ]
+        }
+    ]
+}
diff --git a/main/src/test/resources/simpleDeploy/group1.json b/main/src/test/resources/simpleDeploy/group1.json
new file mode 100644 (file)
index 0000000..8df853c
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "name": "groupA"
+}
diff --git a/main/src/test/resources/simpleDeploy/group2.json b/main/src/test/resources/simpleDeploy/group2.json
new file mode 100644 (file)
index 0000000..fca12ad
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "name": "groupB"
+}
diff --git a/main/src/test/resources/simpleDeploy/group3.json b/main/src/test/resources/simpleDeploy/group3.json
new file mode 100644 (file)
index 0000000..4a6ac2d
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "name": "groupC"
+}
diff --git a/main/src/test/resources/simpleDeploy/group4.json b/main/src/test/resources/simpleDeploy/group4.json
new file mode 100644 (file)
index 0000000..c73ab0a
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "name": "groupD"
+}
diff --git a/main/src/test/resources/simpleDeploy/group5.json b/main/src/test/resources/simpleDeploy/group5.json
new file mode 100644 (file)
index 0000000..4bd5627
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "name": "groupE"
+}