Merge "Add SO actor"
authorLiam Fallon <liam.fallon@est.tech>
Thu, 20 Feb 2020 13:16:30 +0000 (13:16 +0000)
committerGerrit Code Review <gerrit@onap.org>
Thu, 20 Feb 2020 13:16:30 +0000 (13:16 +0000)
21 files changed:
models-interactions/model-actors/actor.so/pom.xml
models-interactions/model-actors/actor.so/src/main/java/org/onap/policy/controlloop/actor/so/SoActorParams.java [new file with mode: 0644]
models-interactions/model-actors/actor.so/src/main/java/org/onap/policy/controlloop/actor/so/SoActorServiceProvider.java
models-interactions/model-actors/actor.so/src/main/java/org/onap/policy/controlloop/actor/so/SoOperation.java [new file with mode: 0644]
models-interactions/model-actors/actor.so/src/main/java/org/onap/policy/controlloop/actor/so/SoOperator.java [new file with mode: 0644]
models-interactions/model-actors/actor.so/src/main/java/org/onap/policy/controlloop/actor/so/SoParams.java [new file with mode: 0644]
models-interactions/model-actors/actor.so/src/main/java/org/onap/policy/controlloop/actor/so/VfModuleCreate.java [new file with mode: 0644]
models-interactions/model-actors/actor.so/src/test/java/org/onap/policy/controlloop/actor/so/BasicSoOperation.java [new file with mode: 0644]
models-interactions/model-actors/actor.so/src/test/java/org/onap/policy/controlloop/actor/so/SoActorParamsTest.java [new file with mode: 0644]
models-interactions/model-actors/actor.so/src/test/java/org/onap/policy/controlloop/actor/so/SoActorServiceProviderTest.java
models-interactions/model-actors/actor.so/src/test/java/org/onap/policy/controlloop/actor/so/SoOperationTest.java [new file with mode: 0644]
models-interactions/model-actors/actor.so/src/test/java/org/onap/policy/controlloop/actor/so/SoOperatorTest.java [new file with mode: 0644]
models-interactions/model-actors/actor.so/src/test/java/org/onap/policy/controlloop/actor/so/SoParamsTest.java [new file with mode: 0644]
models-interactions/model-actors/actor.so/src/test/java/org/onap/policy/controlloop/actor/so/VfModuleCreateTest.java [new file with mode: 0644]
models-interactions/model-actors/actor.so/src/test/resources/model.json [new file with mode: 0644]
models-interactions/model-actors/actor.so/src/test/resources/reqinfo.json [new file with mode: 0644]
models-interactions/model-actors/actor.so/src/test/resources/reqparams.json [new file with mode: 0644]
models-interactions/model-actors/actor.so/src/test/resources/vfModuleCreate.json [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContext.java
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperator.java
models-interactions/model-impl/so/src/main/java/org/onap/policy/so/SoManager.java

index dd7bddc..6d11c13 100644 (file)
   ============LICENSE_END=========================================================
   -->
 
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-  <modelVersion>4.0.0</modelVersion>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
 
-  <parent>
-   <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
-    <artifactId>model-actors</artifactId>
-    <version>2.2.1-SNAPSHOT</version>
-  </parent>
+    <parent>
+        <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+        <artifactId>model-actors</artifactId>
+        <version>2.2.1-SNAPSHOT</version>
+    </parent>
 
-  <artifactId>actor.so</artifactId>
+    <artifactId>actor.so</artifactId>
 
-  <dependencies>
-    <dependency>
-     <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
-      <artifactId>actorServiceProvider</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
-      <artifactId>aai</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
-      <artifactId>events</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
-      <artifactId>so</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>com.google.code.gson</groupId>
-      <artifactId>gson</artifactId>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.onap.policy.common</groupId>
-      <artifactId>policy-endpoints</artifactId>
-      <version>${policy.common.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.onap.policy.models.policy-models-interactions</groupId>
-      <artifactId>simulators</artifactId>
-      <version>${project.version}</version>
-      <scope>test</scope>
-    </dependency>
-  </dependencies>
+    <dependencies>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+            <artifactId>actorServiceProvider</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+            <artifactId>aai</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+            <artifactId>events</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+            <artifactId>so</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+            <artifactId>actor.aai</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.common</groupId>
+            <artifactId>policy-endpoints</artifactId>
+            <version>${policy.common.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+            <artifactId>actor.test</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions</groupId>
+            <artifactId>simulators</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-api-mockito2</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
 </project>
diff --git a/models-interactions/model-actors/actor.so/src/main/java/org/onap/policy/controlloop/actor/so/SoActorParams.java b/models-interactions/model-actors/actor.so/src/main/java/org/onap/policy/controlloop/actor/so/SoActorParams.java
new file mode 100644 (file)
index 0000000..3e82fe1
--- /dev/null
@@ -0,0 +1,56 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 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.controlloop.actor.so;
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
+import org.onap.policy.common.parameters.annotations.Min;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpActorParams;
+
+@Getter
+@Setter
+@EqualsAndHashCode(callSuper = true)
+public class SoActorParams extends HttpActorParams {
+
+    /*
+     * Optional, default values that are used if missing from the operation-specific
+     * parameters.
+     */
+
+    /**
+     * Path to use for the "get" request.
+     */
+    private String pathGet = "/orchestrationRequests/v5/";
+
+    /**
+     * Maximum number of "get" requests permitted, after the initial request, to retrieve
+     * the response.
+     */
+    @Min(0)
+    private int maxGets = 20;
+
+    /**
+     * Time, in seconds, to wait between issuing "get" requests.
+     */
+    @Min(1)
+    private int waitSecGet = 20;
+}
index 51d14a2..c7c6b00 100644 (file)
@@ -92,8 +92,13 @@ public class SoActorServiceProvider extends ActorImpl {
 
     // **HERE**
 
+    /**
+     * Constructs the object.
+     */
     public SoActorServiceProvider() {
         super(NAME);
+
+        addOperator(SoOperator.makeSoOperator(NAME, VfModuleCreate.NAME, VfModuleCreate::new));
     }
 
     // TODO old code: remove lines down to **HERE**
diff --git a/models-interactions/model-actors/actor.so/src/main/java/org/onap/policy/controlloop/actor/so/SoOperation.java b/models-interactions/model-actors/actor.so/src/main/java/org/onap/policy/controlloop/actor/so/SoOperation.java
new file mode 100644 (file)
index 0000000..510a737
--- /dev/null
@@ -0,0 +1,340 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 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.controlloop.actor.so;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import javax.ws.rs.core.Response;
+import lombok.Getter;
+import org.onap.aai.domain.yang.CloudRegion;
+import org.onap.aai.domain.yang.GenericVnf;
+import org.onap.aai.domain.yang.ServiceInstance;
+import org.onap.aai.domain.yang.Tenant;
+import org.onap.policy.aai.AaiCqResponse;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
+import org.onap.policy.common.utils.coder.Coder;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperation;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.onap.policy.controlloop.policy.Target;
+import org.onap.policy.so.SoModelInfo;
+import org.onap.policy.so.SoRequest;
+import org.onap.policy.so.SoRequestInfo;
+import org.onap.policy.so.SoRequestParameters;
+import org.onap.policy.so.SoRequestStatus;
+import org.onap.policy.so.SoResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Superclass for SDNC Operators. Note: subclasses should invoke {@link #resetGetCount()}
+ * each time they issue an HTTP request.
+ */
+public abstract class SoOperation extends HttpOperation<SoResponse> {
+    private static final Logger logger = LoggerFactory.getLogger(SoOperation.class);
+    private static final Coder coder = new StandardCoder();
+
+    public static final String FAILED = "FAILED";
+    public static final String COMPLETE = "COMPLETE";
+    public static final int SO_RESPONSE_CODE = 999;
+
+    // fields within the policy payload
+    public static final String REQ_PARAM_NM = "requestParameters";
+    public static final String CONFIG_PARAM_NM = "configurationParameters";
+
+    @Getter
+    private final SoOperator operator;
+
+    /**
+     * Number of "get" requests issued so far, on the current operation attempt.
+     */
+    @Getter
+    private int getCount;
+
+
+    /**
+     * Constructs the object.
+     *
+     * @param params operation parameters
+     * @param operator operator that created this operation
+     */
+    public SoOperation(ControlLoopOperationParams params, SoOperator operator) {
+        super(params, operator, SoResponse.class);
+        this.operator = operator;
+    }
+
+    /**
+     * Subclasses should invoke this before issuing their first HTTP request.
+     */
+    protected void resetGetCount() {
+        getCount = 0;
+    }
+
+    /**
+     * Starts the GUARD.
+     */
+    @Override
+    protected CompletableFuture<OperationOutcome> startPreprocessorAsync() {
+        return startGuardAsync();
+    }
+
+    /**
+     * If the response does not indicate that the request has been completed, then sleep a
+     * bit and issue a "get".
+     */
+    @Override
+    protected CompletableFuture<OperationOutcome> postProcessResponse(OperationOutcome outcome, String url,
+                    Response rawResponse, SoResponse response) {
+
+        // see if the request has "completed", whether or not it was successful
+        if (rawResponse.getStatus() == 200) {
+            String requestState = getRequestState(response);
+            if (COMPLETE.equalsIgnoreCase(requestState)) {
+                return CompletableFuture
+                                .completedFuture(setOutcome(outcome, PolicyResult.SUCCESS, rawResponse, response));
+            }
+
+            if (FAILED.equalsIgnoreCase(requestState)) {
+                return CompletableFuture
+                                .completedFuture(setOutcome(outcome, PolicyResult.FAILURE, rawResponse, response));
+            }
+        }
+
+        // still incomplete
+
+        // need a request ID with which to query
+        if (response.getRequestReferences() == null || response.getRequestReferences().getRequestId() == null) {
+            throw new IllegalArgumentException("missing request ID in response");
+        }
+
+        // see if the limit for the number of "gets" has been reached
+        if (getCount++ >= operator.getMaxGets()) {
+            logger.warn("{}: execeeded 'get' limit {} for {}", getFullName(), operator.getMaxGets(),
+                            params.getRequestId());
+            setOutcome(outcome, PolicyResult.FAILURE_TIMEOUT);
+            outcome.setMessage(SO_RESPONSE_CODE + " " + outcome.getMessage());
+            return CompletableFuture.completedFuture(outcome);
+        }
+
+        // sleep and then perform a "get" operation
+        Function<Void, CompletableFuture<OperationOutcome>> doGet = unused -> issueGet(outcome, response);
+        return sleep(getWaitMsGet(), TimeUnit.MILLISECONDS).thenComposeAsync(doGet);
+    }
+
+    /**
+     * Issues a "get" request to see if the original request is complete yet.
+     *
+     * @param outcome outcome to be populated with the response
+     * @param response previous response
+     * @return a future that can be used to cancel the "get" request or await its response
+     */
+    private CompletableFuture<OperationOutcome> issueGet(OperationOutcome outcome, SoResponse response) {
+        String path = operator.getPathGet() + response.getRequestReferences().getRequestId();
+        String url = operator.getClient().getBaseUrl() + path;
+
+        logger.debug("{}: 'get' count {} for {}", getFullName(), getCount, params.getRequestId());
+
+        logMessage(EventType.OUT, CommInfrastructure.REST, url, null);
+
+        // TODO should this use "path" or the full "url"?
+        return handleResponse(outcome, url, callback -> operator.getClient().get(callback, path, null));
+    }
+
+    /**
+     * Gets the request state of a response.
+     *
+     * @param response response from which to get the state
+     * @return the request state of the response, or {@code null} if it does not exist
+     */
+    protected String getRequestState(SoResponse response) {
+        SoRequest request = response.getRequest();
+        if (request == null) {
+            return null;
+        }
+
+        SoRequestStatus status = request.getRequestStatus();
+        if (status == null) {
+            return null;
+        }
+
+        return status.getRequestState();
+    }
+
+    /**
+     * Treats everything as a success, so we always go into
+     * {@link #postProcessResponse(OperationOutcome, String, Response, SoResponse)}.
+     */
+    @Override
+    protected boolean isSuccess(Response rawResponse, SoResponse response) {
+        return true;
+    }
+
+    /**
+     * Prepends the message with the http status code.
+     */
+    @Override
+    public OperationOutcome setOutcome(OperationOutcome outcome, PolicyResult result, Response rawResponse,
+                    SoResponse response) {
+
+        // set default result and message
+        setOutcome(outcome, result);
+
+        outcome.setMessage(rawResponse.getStatus() + " " + outcome.getMessage());
+        return outcome;
+    }
+
+    protected SoModelInfo prepareSoModelInfo() {
+        Target target = params.getTarget();
+        if (target == null) {
+            throw new IllegalArgumentException("missing Target");
+        }
+
+        if (target.getModelCustomizationId() == null || target.getModelInvariantId() == null
+                        || target.getModelName() == null || target.getModelVersion() == null
+                        || target.getModelVersionId() == null) {
+            throw new IllegalArgumentException("missing VF Module model");
+        }
+
+        SoModelInfo soModelInfo = new SoModelInfo();
+        soModelInfo.setModelCustomizationId(target.getModelCustomizationId());
+        soModelInfo.setModelInvariantId(target.getModelInvariantId());
+        soModelInfo.setModelName(target.getModelName());
+        soModelInfo.setModelVersion(target.getModelVersion());
+        soModelInfo.setModelVersionId(target.getModelVersionId());
+        soModelInfo.setModelType("vfModule");
+        return soModelInfo;
+    }
+
+    /**
+     * Construct requestInfo for the SO requestDetails.
+     *
+     * @return SO request information
+     */
+    protected SoRequestInfo constructRequestInfo() {
+        SoRequestInfo soRequestInfo = new SoRequestInfo();
+        soRequestInfo.setSource("POLICY");
+        soRequestInfo.setSuppressRollback(false);
+        soRequestInfo.setRequestorId("policy");
+        return soRequestInfo;
+    }
+
+    /**
+     * Builds the request parameters from the policy payload.
+     */
+    protected SoRequestParameters buildRequestParameters() {
+        if (params.getPayload() == null) {
+            return null;
+        }
+
+        String json = params.getPayload().get(REQ_PARAM_NM);
+        if (json == null) {
+            return null;
+        }
+
+        try {
+            return coder.decode(json, SoRequestParameters.class);
+        } catch (CoderException e) {
+            throw new IllegalArgumentException("invalid payload value: " + REQ_PARAM_NM);
+        }
+    }
+
+    /**
+     * Builds the configuration parameters from the policy payload.
+     */
+    protected List<Map<String, String>> buildConfigurationParameters() {
+        if (params.getPayload() == null) {
+            return null;
+        }
+
+        String json = params.getPayload().get(CONFIG_PARAM_NM);
+        if (json == null) {
+            return null;
+        }
+
+        try {
+            @SuppressWarnings("unchecked")
+            List<Map<String, String>> result = coder.decode(json, ArrayList.class);
+            return result;
+        } catch (CoderException | RuntimeException e) {
+            throw new IllegalArgumentException("invalid payload value: " + CONFIG_PARAM_NM);
+        }
+    }
+
+    /*
+     * These methods extract data from the Custom Query and throw an
+     * IllegalArgumentException if the desired data item is not found.
+     */
+
+    protected GenericVnf getVnfItem(AaiCqResponse aaiCqResponse, SoModelInfo soModelInfo) {
+        GenericVnf vnf = aaiCqResponse.getGenericVnfByVfModuleModelInvariantId(soModelInfo.getModelInvariantId());
+        if (vnf == null) {
+            throw new IllegalArgumentException("missing generic VNF");
+        }
+
+        return vnf;
+    }
+
+    protected ServiceInstance getServiceInstance(AaiCqResponse aaiCqResponse) {
+        ServiceInstance vnfService = aaiCqResponse.getServiceInstance();
+        if (vnfService == null) {
+            throw new IllegalArgumentException("missing VNF Service Item");
+        }
+
+        return vnfService;
+    }
+
+    protected Tenant getDefaultTenant(AaiCqResponse aaiCqResponse) {
+        Tenant tenant = aaiCqResponse.getDefaultTenant();
+        if (tenant == null) {
+            throw new IllegalArgumentException("missing Tenant Item");
+        }
+
+        return tenant;
+    }
+
+    protected CloudRegion getDefaultCloudRegion(AaiCqResponse aaiCqResponse) {
+        CloudRegion cloudRegion = aaiCqResponse.getDefaultCloudRegion();
+        if (cloudRegion == null) {
+            throw new IllegalArgumentException("missing Cloud Region");
+        }
+
+        return cloudRegion;
+    }
+
+    // these may be overridden by junit tests
+
+    /**
+     * Gets the wait time, in milliseconds, between "get" requests.
+     *
+     * @return the wait time, in milliseconds, between "get" requests
+     */
+    public long getWaitMsGet() {
+        return TimeUnit.MILLISECONDS.convert(operator.getWaitSecGet(), TimeUnit.SECONDS);
+    }
+}
diff --git a/models-interactions/model-actors/actor.so/src/main/java/org/onap/policy/controlloop/actor/so/SoOperator.java b/models-interactions/model-actors/actor.so/src/main/java/org/onap/policy/controlloop/actor/so/SoOperator.java
new file mode 100644 (file)
index 0000000..011201f
--- /dev/null
@@ -0,0 +1,90 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 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.controlloop.actor.so;
+
+import java.util.Map;
+import java.util.function.BiFunction;
+import lombok.Getter;
+import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
+
+@Getter
+public abstract class SoOperator extends HttpOperator {
+
+    /**
+     * Path to use for the "get" request. A trailing "/" is added, if it is missing.
+     */
+    private String pathGet;
+
+    /**
+     * Maximum number of "get" requests permitted, after the initial request, to retrieve
+     * the response.
+     */
+    private int maxGets;
+
+    /**
+     * Time, in seconds, to wait between issuing "get" requests.
+     */
+    private int waitSecGet;
+
+
+    public SoOperator(String actorName, String name) {
+        super(actorName, name);
+    }
+
+    @Override
+    protected void doConfigure(Map<String, Object> parameters) {
+        SoParams params = Util.translate(getFullName(), parameters, SoParams.class);
+        ValidationResult result = params.validate(getFullName());
+        if (!result.isValid()) {
+            throw new ParameterValidationRuntimeException("invalid parameters", result);
+        }
+
+        this.pathGet = params.getPathGet() + (params.getPathGet().endsWith("/") ? "" : "/");
+        this.maxGets = params.getMaxGets();
+        this.waitSecGet = params.getWaitSecGet();
+
+        super.doConfigure(params);
+    }
+
+    /**
+     * Makes an operator that will construct operations.
+     *
+     * @param actorName actor name
+     * @param operation operation name
+     * @param operationMaker function to make an operation
+     * @return a new operator
+     */
+    public static SoOperator makeSoOperator(String actorName, String operation,
+                    BiFunction<ControlLoopOperationParams, SoOperator, SoOperation> operationMaker) {
+
+        return new SoOperator(actorName, operation) {
+            @Override
+            public Operation buildOperation(ControlLoopOperationParams params) {
+                return operationMaker.apply(params, this);
+            }
+        };
+    }
+}
diff --git a/models-interactions/model-actors/actor.so/src/main/java/org/onap/policy/controlloop/actor/so/SoParams.java b/models-interactions/model-actors/actor.so/src/main/java/org/onap/policy/controlloop/actor/so/SoParams.java
new file mode 100644 (file)
index 0000000..53f6e93
--- /dev/null
@@ -0,0 +1,55 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 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.controlloop.actor.so;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.SuperBuilder;
+import org.onap.policy.common.parameters.annotations.Min;
+import org.onap.policy.common.parameters.annotations.NotBlank;
+import org.onap.policy.common.parameters.annotations.NotNull;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams;
+
+@NotNull
+@NotBlank
+@Data
+@EqualsAndHashCode(callSuper = true)
+@SuperBuilder(toBuilder = true)
+public class SoParams extends HttpParams {
+
+    /**
+     * Path to use for the "get" request.
+     */
+    private String pathGet;
+
+    /**
+     * Maximum number of "get" requests permitted, after the initial request, to retrieve
+     * the response.
+     */
+    @Min(0)
+    private int maxGets;
+
+    /**
+     * Time, in seconds, to wait between issuing "get" requests.
+     */
+    @Min(1)
+    private int waitSecGet;
+}
diff --git a/models-interactions/model-actors/actor.so/src/main/java/org/onap/policy/controlloop/actor/so/VfModuleCreate.java b/models-interactions/model-actors/actor.so/src/main/java/org/onap/policy/controlloop/actor/so/VfModuleCreate.java
new file mode 100644 (file)
index 0000000..f356dce
--- /dev/null
@@ -0,0 +1,186 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 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.controlloop.actor.so;
+
+import java.util.concurrent.CompletableFuture;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.MediaType;
+import org.apache.commons.lang3.tuple.Pair;
+import org.onap.aai.domain.yang.CloudRegion;
+import org.onap.aai.domain.yang.GenericVnf;
+import org.onap.aai.domain.yang.ServiceInstance;
+import org.onap.aai.domain.yang.Tenant;
+import org.onap.policy.aai.AaiConstants;
+import org.onap.policy.aai.AaiCqResponse;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
+import org.onap.policy.controlloop.actor.aai.AaiCustomQueryOperation;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.so.SoCloudConfiguration;
+import org.onap.policy.so.SoModelInfo;
+import org.onap.policy.so.SoOperationType;
+import org.onap.policy.so.SoRelatedInstance;
+import org.onap.policy.so.SoRelatedInstanceListElement;
+import org.onap.policy.so.SoRequest;
+import org.onap.policy.so.SoRequestDetails;
+import org.onap.policy.so.SoRequestParameters;
+
+public class VfModuleCreate extends SoOperation {
+    public static final String NAME = "VF Module Create";
+
+    public VfModuleCreate(ControlLoopOperationParams params, SoOperator operator) {
+        super(params, operator);
+    }
+
+    /**
+     * Ensures that A&AI customer query has been performed, and then runs the guard query.
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    protected CompletableFuture<OperationOutcome> startPreprocessorAsync() {
+        ControlLoopOperationParams cqParams = params.toBuilder().actor(AaiConstants.ACTOR_NAME)
+                        .operation(AaiCustomQueryOperation.NAME).payload(null).retry(null).timeoutSec(null).build();
+
+        // run Custom Query and Guard, in parallel
+        return allOf(() -> params.getContext().obtain(AaiCqResponse.CONTEXT_KEY, cqParams), this::startGuardAsync);
+    }
+
+    @Override
+    protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+
+        // starting a whole new attempt - reset the count
+        resetGetCount();
+
+        Pair<String, SoRequest> pair = makeRequest();
+        String path = pair.getLeft();
+        SoRequest request = pair.getRight();
+
+        Entity<SoRequest> entity = Entity.entity(request, MediaType.APPLICATION_JSON);
+        String url = getOperator().getClient().getBaseUrl() + path;
+
+        logMessage(EventType.OUT, CommInfrastructure.REST, url, request);
+
+        // TODO should this use "path" or the full "url"?
+
+        return handleResponse(outcome, url, callback -> getOperator().getClient().post(callback, path, entity, null));
+    }
+
+    /**
+     * Makes a request.
+     *
+     * @return a pair containing the request URL and the new request
+     */
+    protected Pair<String, SoRequest> makeRequest() {
+        final AaiCqResponse aaiCqResponse = params.getContext().getProperty(AaiCqResponse.CONTEXT_KEY);
+        final SoModelInfo soModelInfo = prepareSoModelInfo();
+        final GenericVnf vnfItem = getVnfItem(aaiCqResponse, soModelInfo);
+        final ServiceInstance vnfServiceItem = getServiceInstance(aaiCqResponse);
+        final Tenant tenantItem = getDefaultTenant(aaiCqResponse);
+        final CloudRegion cloudRegionItem = getDefaultCloudRegion(aaiCqResponse);
+
+        SoRequest request = new SoRequest();
+        request.setOperationType(SoOperationType.SCALE_OUT);
+
+        //
+        //
+        // Do NOT send SO the requestId, they do not support this field
+        //
+        request.setRequestDetails(new SoRequestDetails());
+        request.getRequestDetails().setRequestParameters(new SoRequestParameters());
+        request.getRequestDetails().getRequestParameters().setUserParams(null);
+
+        // cloudConfiguration
+        request.getRequestDetails().setCloudConfiguration(constructCloudConfigurationCq(tenantItem, cloudRegionItem));
+
+        // modelInfo
+        request.getRequestDetails().setModelInfo(soModelInfo);
+
+        // requestInfo
+        request.getRequestDetails().setRequestInfo(constructRequestInfo());
+        request.getRequestDetails().getRequestInfo().setInstanceName("vfModuleName");
+
+        // relatedInstanceList
+        SoRelatedInstanceListElement relatedInstanceListElement1 = new SoRelatedInstanceListElement();
+        SoRelatedInstanceListElement relatedInstanceListElement2 = new SoRelatedInstanceListElement();
+        relatedInstanceListElement1.setRelatedInstance(new SoRelatedInstance());
+        relatedInstanceListElement2.setRelatedInstance(new SoRelatedInstance());
+
+        // Service Item
+        relatedInstanceListElement1.getRelatedInstance().setInstanceId(vnfServiceItem.getServiceInstanceId());
+        relatedInstanceListElement1.getRelatedInstance().setModelInfo(new SoModelInfo());
+        relatedInstanceListElement1.getRelatedInstance().getModelInfo().setModelType("service");
+        relatedInstanceListElement1.getRelatedInstance().getModelInfo()
+                        .setModelInvariantId(vnfServiceItem.getModelInvariantId());
+        relatedInstanceListElement1.getRelatedInstance().getModelInfo()
+                        .setModelVersionId(vnfServiceItem.getModelVersionId());
+        relatedInstanceListElement1.getRelatedInstance().getModelInfo().setModelName(
+                        aaiCqResponse.getModelVerByVersionId(vnfServiceItem.getModelVersionId()).getModelName());
+        relatedInstanceListElement1.getRelatedInstance().getModelInfo().setModelVersion(
+                        aaiCqResponse.getModelVerByVersionId(vnfServiceItem.getModelVersionId()).getModelVersion());
+
+        // VNF Item
+        relatedInstanceListElement2.getRelatedInstance().setInstanceId(vnfItem.getVnfId());
+        relatedInstanceListElement2.getRelatedInstance().setModelInfo(new SoModelInfo());
+        relatedInstanceListElement2.getRelatedInstance().getModelInfo().setModelType("vnf");
+        relatedInstanceListElement2.getRelatedInstance().getModelInfo()
+                        .setModelInvariantId(vnfItem.getModelInvariantId());
+        relatedInstanceListElement2.getRelatedInstance().getModelInfo().setModelVersionId(vnfItem.getModelVersionId());
+
+        relatedInstanceListElement2.getRelatedInstance().getModelInfo()
+                        .setModelName(aaiCqResponse.getModelVerByVersionId(vnfItem.getModelVersionId()).getModelName());
+        relatedInstanceListElement2.getRelatedInstance().getModelInfo().setModelVersion(
+                        aaiCqResponse.getModelVerByVersionId(vnfItem.getModelVersionId()).getModelVersion());
+
+        relatedInstanceListElement2.getRelatedInstance().getModelInfo()
+                        .setModelCustomizationId(vnfItem.getModelCustomizationId());
+
+        // Insert the Service Item and VNF Item
+        request.getRequestDetails().getRelatedInstanceList().add(relatedInstanceListElement1);
+        request.getRequestDetails().getRelatedInstanceList().add(relatedInstanceListElement2);
+
+        // Request Parameters
+        request.getRequestDetails().setRequestParameters(buildRequestParameters());
+
+        // Configuration Parameters
+        request.getRequestDetails().setConfigurationParameters(buildConfigurationParameters());
+
+        // compute the path
+        String path = "/serviceInstantiation/v7/serviceInstances/" + vnfServiceItem.getServiceInstanceId() + "/vnfs/"
+                        + vnfItem.getVnfId() + "/vfModules/scaleOut";
+
+        return Pair.of(path, request);
+    }
+
+    /**
+     * Construct cloudConfiguration for the SO requestDetails. Overridden for custom
+     * query.
+     *
+     * @param tenantItem tenant item from A&AI named-query response
+     * @return SO cloud configuration
+     */
+    private SoCloudConfiguration constructCloudConfigurationCq(Tenant tenantItem, CloudRegion cloudRegionItem) {
+        SoCloudConfiguration cloudConfiguration = new SoCloudConfiguration();
+        cloudConfiguration.setTenantId(tenantItem.getTenantId());
+        cloudConfiguration.setLcpCloudRegionId(cloudRegionItem.getCloudRegionId());
+        return cloudConfiguration;
+    }
+}
diff --git a/models-interactions/model-actors/actor.so/src/test/java/org/onap/policy/controlloop/actor/so/BasicSoOperation.java b/models-interactions/model-actors/actor.so/src/test/java/org/onap/policy/controlloop/actor/so/BasicSoOperation.java
new file mode 100644 (file)
index 0000000..0894704
--- /dev/null
@@ -0,0 +1,144 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 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.controlloop.actor.so;
+
+import static org.mockito.Mockito.when;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import org.mockito.Mock;
+import org.onap.policy.controlloop.actor.test.BasicHttpOperation;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.onap.policy.controlloop.policy.Target;
+import org.onap.policy.so.SoRequest;
+import org.onap.policy.so.SoRequestParameters;
+import org.onap.policy.so.SoRequestReferences;
+import org.onap.policy.so.SoRequestStatus;
+import org.onap.policy.so.SoResponse;
+
+/**
+ * Superclass for various operator tests.
+ */
+public abstract class BasicSoOperation extends BasicHttpOperation<SoRequest> {
+    protected static final String[] IGNORE_FIELDS = {"RequestID", "subRequestID", "seconds", "nanos"};
+
+    public static final String MODEL_CUSTOM_ID = "my-model-customization-id";
+    public static final String MODEL_INVAR_ID = "my-model-invariant-id";
+    public static final String MODEL_NAME = "my-model-name";
+    public static final String MODEL_VERSION = "my-model-version";
+    public static final String MODEL_VERS_ID = "my-model-version-id";
+    public static final String SUBSCRIPTION_SVC_TYPE = "my-subscription-service-type";
+    public static final String PATH_GET = "my-path-get/";
+    public static final int MAX_GETS = 3;
+    public static final int WAIT_SEC_GETS = 20;
+
+    @Mock
+    protected SoOperator soOperator;
+
+    protected Target target;
+    protected SoResponse response;
+
+    /**
+     * Constructs the object using a default actor and operation name.
+     */
+    public BasicSoOperation() {
+        super();
+    }
+
+    /**
+     * Constructs the object.
+     *
+     * @param actor actor name
+     * @param operation operation name
+     */
+    public BasicSoOperation(String actor, String operation) {
+        super(actor, operation);
+    }
+
+    /**
+     * Initializes mocks and sets up.
+     */
+    public void setUp() throws Exception {
+        super.setUp();
+
+        response = new SoResponse();
+
+        SoRequest request = new SoRequest();
+        response.setRequest(request);
+
+        SoRequestStatus status = new SoRequestStatus();
+        request.setRequestStatus(status);
+        status.setRequestState(SoOperation.COMPLETE);
+
+        SoRequestReferences ref = new SoRequestReferences();
+        response.setRequestReferences(ref);
+        ref.setRequestId(REQ_ID.toString());
+
+        when(rawResponse.getStatus()).thenReturn(200);
+        when(rawResponse.readEntity(String.class)).thenReturn(coder.encode(response));
+
+        operator = soOperator;
+
+        initOperator();
+    }
+
+    @Override
+    protected void initOperator() {
+        super.initOperator();
+        when(soOperator.getMaxGets()).thenReturn(MAX_GETS);
+        when(soOperator.getPathGet()).thenReturn(PATH_GET);
+        when(soOperator.getWaitSecGet()).thenReturn(WAIT_SEC_GETS);
+    }
+
+    @Override
+    protected void makeContext() {
+        super.makeContext();
+
+        target = new Target();
+        target.setModelCustomizationId(MODEL_CUSTOM_ID);
+        target.setModelInvariantId(MODEL_INVAR_ID);
+        target.setModelName(MODEL_NAME);
+        target.setModelVersion(MODEL_VERSION);
+        target.setModelVersionId(MODEL_VERS_ID);
+
+        params = params.toBuilder().target(target).build();
+    }
+
+    @Override
+    protected Map<String, String> makePayload() {
+        Map<String, String> payload = new HashMap<>();
+
+        // request parameters
+        SoRequestParameters reqParams = new SoRequestParameters();
+        reqParams.setSubscriptionServiceType(SUBSCRIPTION_SVC_TYPE);
+        payload.put(SoOperation.REQ_PARAM_NM, Util.translate("", reqParams, String.class));
+
+        // config parameters
+        List<Map<String, String>> config = new LinkedList<>();
+        config.add(Collections.emptyMap());
+        payload.put(SoOperation.CONFIG_PARAM_NM, Util.translate("", config, String.class));
+
+        return payload;
+    }
+}
diff --git a/models-interactions/model-actors/actor.so/src/test/java/org/onap/policy/controlloop/actor/so/SoActorParamsTest.java b/models-interactions/model-actors/actor.so/src/test/java/org/onap/policy/controlloop/actor/so/SoActorParamsTest.java
new file mode 100644 (file)
index 0000000..f463fcb
--- /dev/null
@@ -0,0 +1,116 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 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.controlloop.actor.so;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.function.Consumer;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+
+
+public class SoActorParamsTest {
+    private static final String CONTAINER = "my-container";
+    private static final String CLIENT = "my-client";
+    private static final String PATH_GET = "my-path-get";
+    private static final int MAX_GETS = 3;
+    private static final int WAIT_SEC_GETS = 20;
+    private static final int TIMEOUT = 10;
+
+    private static final String PATH1 = "path #1";
+    private static final String PATH2 = "path #2";
+    private static final String URI1 = "uri #1";
+    private static final String URI2 = "uri #2";
+
+    private Map<String, Map<String, Object>> operations;
+    private SoActorParams params;
+
+    /**
+     * Initializes {@link #operations} with two items and {@link params} with a fully
+     * populated object.
+     */
+    @Before
+    public void setUp() {
+        operations = new TreeMap<>();
+        operations.put(PATH1, Map.of("path", URI1));
+        operations.put(PATH2, Map.of("path", URI2));
+
+        params = makeSoActorParams();
+    }
+
+    @Test
+    public void testValidate() {
+        assertTrue(params.validate(CONTAINER).isValid());
+
+        // only a few fields are required
+        SoActorParams sparse = Util.translate(CONTAINER, Map.of("operation", operations), SoActorParams.class);
+        assertTrue(sparse.validate(CONTAINER).isValid());
+
+        testValidateField("maxGets", "minimum", params2 -> params2.setMaxGets(-1));
+        testValidateField("waitSecGet", "minimum", params2 -> params2.setWaitSecGet(0));
+
+        // check fields from superclass
+        testValidateField("operation", "null", params2 -> params2.setOperation(null));
+        testValidateField("timeoutSec", "minimum", params2 -> params2.setTimeoutSec(-1));
+
+        // check edge cases
+        params.setMaxGets(0);
+        assertTrue(params.validate(CONTAINER).isValid());
+        params.setMaxGets(MAX_GETS);
+
+        params.setWaitSecGet(1);
+        assertTrue(params.validate(CONTAINER).isValid());
+        params.setWaitSecGet(WAIT_SEC_GETS);
+    }
+
+    private void testValidateField(String fieldName, String expected, Consumer<SoActorParams> makeInvalid) {
+
+        // original params should be valid
+        ValidationResult result = params.validate(CONTAINER);
+        assertTrue(fieldName, result.isValid());
+
+        // make invalid params
+        SoActorParams params2 = makeSoActorParams();
+        makeInvalid.accept(params2);
+        result = params2.validate(CONTAINER);
+        assertFalse(fieldName, result.isValid());
+        assertThat(result.getResult()).contains(CONTAINER).contains(fieldName).contains(expected);
+    }
+
+    private SoActorParams makeSoActorParams() {
+        SoActorParams params2 = new SoActorParams();
+        params2.setClientName(CLIENT);
+        params2.setTimeoutSec(TIMEOUT);
+        params2.setOperation(operations);
+
+        params2.setWaitSecGet(WAIT_SEC_GETS);
+        params2.setMaxGets(MAX_GETS);
+        params2.setPathGet(PATH_GET);
+
+        return params2;
+    }
+}
index b46ac52..a9d5b81 100644 (file)
@@ -29,11 +29,13 @@ import static org.junit.Assert.assertNull;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
 import java.util.UUID;
+import java.util.stream.Collectors;
 import org.apache.commons.io.IOUtils;
 import org.junit.Test;
 import org.onap.policy.aai.AaiCqResponse;
@@ -65,6 +67,17 @@ public class SoActorServiceProviderTest {
         policy.setTarget(target);
     }
 
+    @Test
+    public void testConstructor() {
+        SoActorServiceProvider prov = new SoActorServiceProvider();
+
+        // verify that it has the operators we expect
+        var expected = Arrays.asList(VfModuleCreate.NAME).stream().sorted().collect(Collectors.toList());
+        var actual = prov.getOperationNames().stream().sorted().collect(Collectors.toList());
+
+        assertEquals(expected.toString(), actual.toString());
+    }
+
     @Test
     public void testSendRequest() {
         SoActorServiceProvider.sendRequest(UUID.randomUUID().toString(), null, null, null, null, null);
diff --git a/models-interactions/model-actors/actor.so/src/test/java/org/onap/policy/controlloop/actor/so/SoOperationTest.java b/models-interactions/model-actors/actor.so/src/test/java/org/onap/policy/controlloop/actor/so/SoOperationTest.java
new file mode 100644 (file)
index 0000000..e704138
--- /dev/null
@@ -0,0 +1,353 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 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.controlloop.actor.so;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+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.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.aai.domain.yang.CloudRegion;
+import org.onap.aai.domain.yang.GenericVnf;
+import org.onap.aai.domain.yang.ServiceInstance;
+import org.onap.aai.domain.yang.Tenant;
+import org.onap.policy.aai.AaiCqResponse;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.controlloop.ControlLoopOperation;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.onap.policy.so.SoModelInfo;
+import org.onap.policy.so.SoRequest;
+import org.onap.policy.so.SoRequestInfo;
+import org.onap.policy.so.SoRequestParameters;
+import org.onap.policy.so.SoRequestReferences;
+import org.onap.policy.so.SoRequestStatus;
+import org.onap.policy.so.SoResponse;
+
+public class SoOperationTest extends BasicSoOperation {
+
+    private SoOperation oper;
+
+    /**
+     * Sets up.
+     */
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        initOperator();
+
+        oper = new SoOperation(params, soOperator) {};
+    }
+
+    @Test
+    public void testConstructor_testGetWaitMsGet() {
+        assertEquals(DEFAULT_ACTOR, oper.getActorName());
+        assertEquals(DEFAULT_OPERATION, oper.getName());
+        assertSame(soOperator, oper.getOperator());
+        assertEquals(1000 * WAIT_SEC_GETS, oper.getWaitMsGet());
+    }
+
+    @Test
+    public void testStartPreprocessorAsync() {
+        AtomicBoolean guardStarted = new AtomicBoolean();
+
+        oper = new SoOperation(params, soOperator) {
+            @Override
+            protected CompletableFuture<OperationOutcome> startGuardAsync() {
+                guardStarted.set(true);
+                return super.startGuardAsync();
+            }
+        };
+
+        assertNull(oper.startPreprocessorAsync());
+        assertTrue(guardStarted.get());
+    }
+
+    @Test
+    public void testPostProcess() throws Exception {
+        // completed
+        CompletableFuture<OperationOutcome> future2 = oper.postProcessResponse(outcome, PATH, rawResponse, response);
+        assertTrue(future2.isDone());
+        assertSame(outcome, future2.get());
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+
+        // failed
+        response.getRequest().getRequestStatus().setRequestState(SoOperation.FAILED);
+        future2 = oper.postProcessResponse(outcome, PATH, rawResponse, response);
+        assertTrue(future2.isDone());
+        assertSame(outcome, future2.get());
+        assertEquals(PolicyResult.FAILURE, outcome.getResult());
+
+        // no request id in the response
+        response.getRequestReferences().setRequestId(null);
+        response.getRequest().getRequestStatus().setRequestState("unknown");
+        assertThatIllegalArgumentException()
+                        .isThrownBy(() -> oper.postProcessResponse(outcome, PATH, rawResponse, response))
+                        .withMessage("missing request ID in response");
+        response.getRequestReferences().setRequestId(REQ_ID.toString());
+
+        // status = 500
+        when(rawResponse.getStatus()).thenReturn(500);
+
+        // null request reference
+        SoRequestReferences ref = response.getRequestReferences();
+        response.setRequestReferences(null);
+        assertThatIllegalArgumentException()
+                        .isThrownBy(() -> oper.postProcessResponse(outcome, PATH, rawResponse, response))
+                        .withMessage("missing request ID in response");
+        response.setRequestReferences(ref);
+    }
+
+    /**
+     * Tests postProcess() when the "get" is repeated a couple of times.
+     */
+    @Test
+    public void testPostProcessRepeated_testResetGetCount() throws Exception {
+        /*
+         * Two failures and then a success - should result in two "get" calls.
+         *
+         * Note: getStatus() is invoked twice during each call, so have to double up the
+         * return values.
+         */
+        when(rawResponse.getStatus()).thenReturn(500, 500, 500, 500, 200, 200);
+
+        when(client.get(any(), any(), any())).thenAnswer(provideResponse(rawResponse));
+
+        // use a real executor
+        params = params.toBuilder().executor(ForkJoinPool.commonPool()).build();
+
+        oper = new SoOperation(params, soOperator) {
+            @Override
+            public long getWaitMsGet() {
+                return 1;
+            }
+        };
+
+        CompletableFuture<OperationOutcome> future2 = oper.postProcessResponse(outcome, PATH, rawResponse, response);
+
+        assertSame(outcome, future2.get(5, TimeUnit.SECONDS));
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+        assertEquals(2, oper.getGetCount());
+
+        /*
+         * repeat - this time, the "get" operations will be exhausted, so it should fail
+         */
+        when(rawResponse.getStatus()).thenReturn(500);
+
+        future2 = oper.postProcessResponse(outcome, PATH, rawResponse, response);
+
+        assertSame(outcome, future2.get(5, TimeUnit.SECONDS));
+        assertEquals(PolicyResult.FAILURE_TIMEOUT, outcome.getResult());
+        assertEquals(MAX_GETS + 1, oper.getGetCount());
+
+        oper.resetGetCount();
+        assertEquals(0, oper.getGetCount());
+    }
+
+    @Test
+    public void testGetRequestState() {
+        SoResponse resp = new SoResponse();
+        assertNull(oper.getRequestState(resp));
+
+        SoRequest req = new SoRequest();
+        resp.setRequest(req);
+        assertNull(oper.getRequestState(resp));
+
+        SoRequestStatus status = new SoRequestStatus();
+        req.setRequestStatus(status);
+        assertNull(oper.getRequestState(resp));
+
+        status.setRequestState("my-state");
+        assertEquals("my-state", oper.getRequestState(resp));
+    }
+
+    @Test
+    public void testIsSuccess() {
+        // always true
+
+        assertTrue(oper.isSuccess(rawResponse, response));
+
+        when(rawResponse.getStatus()).thenReturn(500);
+        assertTrue(oper.isSuccess(rawResponse, response));
+    }
+
+    @Test
+    public void testSetOutcome() {
+        // success case
+        when(rawResponse.getStatus()).thenReturn(200);
+        assertSame(outcome, oper.setOutcome(outcome, PolicyResult.SUCCESS, rawResponse, response));
+
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+        assertEquals("200 " + ControlLoopOperation.SUCCESS_MSG, outcome.getMessage());
+
+        // failure case
+        when(rawResponse.getStatus()).thenReturn(500);
+        assertSame(outcome, oper.setOutcome(outcome, PolicyResult.FAILURE, rawResponse, response));
+
+        assertEquals(PolicyResult.FAILURE, outcome.getResult());
+        assertEquals("500 " + ControlLoopOperation.FAILED_MSG, outcome.getMessage());
+    }
+
+    @Test
+    public void testPrepareSoModelInfo() throws CoderException {
+        verifyMissingModelInfo(target::getModelCustomizationId, target::setModelCustomizationId);
+        verifyMissingModelInfo(target::getModelInvariantId, target::setModelInvariantId);
+        verifyMissingModelInfo(target::getModelName, target::setModelName);
+        verifyMissingModelInfo(target::getModelVersion, target::setModelVersion);
+        verifyMissingModelInfo(target::getModelVersionId, target::setModelVersionId);
+
+        // valid data
+        SoModelInfo info = oper.prepareSoModelInfo();
+        verifyRequest("model.json", info);
+
+        // try with null target
+        params = params.toBuilder().target(null).build();
+        oper = new SoOperation(params, soOperator) {};
+
+        assertThatIllegalArgumentException().isThrownBy(() -> oper.prepareSoModelInfo()).withMessage("missing Target");
+    }
+
+    private void verifyMissingModelInfo(Supplier<String> getter, Consumer<String> setter) {
+        String original = getter.get();
+
+        setter.accept(null);
+        assertThatIllegalArgumentException().isThrownBy(() -> oper.prepareSoModelInfo())
+                        .withMessage("missing VF Module model");
+
+        setter.accept(original);
+    }
+
+    @Test
+    public void testConstructRequestInfo() throws CoderException {
+        SoRequestInfo info = oper.constructRequestInfo();
+        verifyRequest("reqinfo.json", info);
+    }
+
+    @Test
+    public void testBuildRequestParameters() throws CoderException {
+        // valid data
+        SoRequestParameters reqParams = oper.buildRequestParameters();
+        verifyRequest("reqparams.json", reqParams);
+
+        // invalid json
+        params.getPayload().put(SoOperation.REQ_PARAM_NM, "{invalid json");
+        assertThatIllegalArgumentException().isThrownBy(() -> oper.buildRequestParameters())
+                        .withMessage("invalid payload value: " + SoOperation.REQ_PARAM_NM);
+
+        // missing data
+        params.getPayload().remove(SoOperation.REQ_PARAM_NM);
+        assertNull(oper.buildRequestParameters());
+
+        // null payload
+        params = params.toBuilder().payload(null).build();
+        oper = new SoOperation(params, soOperator) {};
+        assertNull(oper.buildRequestParameters());
+    }
+
+    @Test
+    public void testBuildConfigurationParameters() {
+        // valid data
+        List<Map<String, String>> result = oper.buildConfigurationParameters();
+        assertEquals(List.of(Collections.emptyMap()), result);
+
+        // invalid json
+        params.getPayload().put(SoOperation.CONFIG_PARAM_NM, "{invalid json");
+        assertThatIllegalArgumentException().isThrownBy(() -> oper.buildConfigurationParameters())
+                        .withMessage("invalid payload value: " + SoOperation.CONFIG_PARAM_NM);
+
+        // missing data
+        params.getPayload().remove(SoOperation.CONFIG_PARAM_NM);
+        assertNull(oper.buildConfigurationParameters());
+
+        // null payload
+        params = params.toBuilder().payload(null).build();
+        oper = new SoOperation(params, soOperator) {};
+        assertNull(oper.buildConfigurationParameters());
+    }
+
+    @Test
+    public void testGetVnfItem() {
+        // missing data
+        AaiCqResponse cq = mock(AaiCqResponse.class);
+        assertThatIllegalArgumentException().isThrownBy(() -> oper.getVnfItem(cq, oper.prepareSoModelInfo()))
+                        .withMessage("missing generic VNF");
+
+        // valid data
+        GenericVnf vnf = new GenericVnf();
+        when(cq.getGenericVnfByVfModuleModelInvariantId(MODEL_INVAR_ID)).thenReturn(vnf);
+        assertSame(vnf, oper.getVnfItem(cq, oper.prepareSoModelInfo()));
+    }
+
+    @Test
+    public void testGetServiceInstance() {
+        // missing data
+        AaiCqResponse cq = mock(AaiCqResponse.class);
+        assertThatIllegalArgumentException().isThrownBy(() -> oper.getServiceInstance(cq))
+                        .withMessage("missing VNF Service Item");
+
+        // valid data
+        ServiceInstance instance = new ServiceInstance();
+        when(cq.getServiceInstance()).thenReturn(instance);
+        assertSame(instance, oper.getServiceInstance(cq));
+    }
+
+    @Test
+    public void testGetDefaultTenant() {
+        // missing data
+        AaiCqResponse cq = mock(AaiCqResponse.class);
+        assertThatIllegalArgumentException().isThrownBy(() -> oper.getDefaultTenant(cq))
+                        .withMessage("missing Tenant Item");
+
+        // valid data
+        Tenant tenant = new Tenant();
+        when(cq.getDefaultTenant()).thenReturn(tenant);
+        assertSame(tenant, oper.getDefaultTenant(cq));
+    }
+
+    @Test
+    public void testGetDefaultCloudRegion() {
+        // missing data
+        AaiCqResponse cq = mock(AaiCqResponse.class);
+        assertThatIllegalArgumentException().isThrownBy(() -> oper.getDefaultCloudRegion(cq))
+                        .withMessage("missing Cloud Region");
+
+        // valid data
+        CloudRegion region = new CloudRegion();
+        when(cq.getDefaultCloudRegion()).thenReturn(region);
+        assertSame(region, oper.getDefaultCloudRegion(cq));
+    }
+}
diff --git a/models-interactions/model-actors/actor.so/src/test/java/org/onap/policy/controlloop/actor/so/SoOperatorTest.java b/models-interactions/model-actors/actor.so/src/test/java/org/onap/policy/controlloop/actor/so/SoOperatorTest.java
new file mode 100644 (file)
index 0000000..16bbdea
--- /dev/null
@@ -0,0 +1,146 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 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.controlloop.actor.so;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Mockito.when;
+
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.endpoints.http.client.HttpClient;
+import org.onap.policy.common.endpoints.http.client.HttpClientFactory;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
+
+public class SoOperatorTest {
+    private static final String ACTOR = "my-actor";
+    private static final String OPERATION = "my-name";
+    private static final String CLIENT = "my-client";
+    private static final String PATH = "/my-path";
+    private static final String PATH_GET = "my-path-get/";
+    private static final int MAX_GETS = 3;
+    private static final int WAIT_SEC_GETS = 20;
+    private static final int TIMEOUT = 100;
+
+    @Mock
+    private HttpClient client;
+
+    @Mock
+    private HttpClientFactory factory;
+
+
+    private SoOperator oper;
+
+    /**
+     * Initializes fields, including {@link #oper}, and resets the static fields used by
+     * the REST server.
+     */
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        when(factory.get(CLIENT)).thenReturn(client);
+
+        oper = new MyOperator();
+
+        SoParams params = SoParams.builder().pathGet(PATH_GET).maxGets(MAX_GETS).waitSecGet(WAIT_SEC_GETS)
+                        .clientName(CLIENT).path(PATH).timeoutSec(TIMEOUT).build();
+        Map<String, Object> paramMap = Util.translateToMap(OPERATION, params);
+        oper.configure(paramMap);
+    }
+
+    @Test
+    public void testConstructor() {
+        assertEquals(ACTOR, oper.getActorName());
+        assertEquals(OPERATION, oper.getName());
+        assertEquals(ACTOR + "." + OPERATION, oper.getFullName());
+    }
+
+    @Test
+    public void testMakeSoOperator() {
+        oper = SoOperator.makeSoOperator(ACTOR, OPERATION, MyOperation::new);
+
+        VirtualControlLoopEvent event = new VirtualControlLoopEvent();
+        ControlLoopEventContext context = new ControlLoopEventContext(event);
+        ControlLoopOperationParams params =
+                        ControlLoopOperationParams.builder().actor(ACTOR).operation(OPERATION).context(context).build();
+
+        Operation operation1 = oper.buildOperation(params);
+        assertNotNull(operation1);
+
+        Operation operation2 = oper.buildOperation(params);
+        assertNotNull(operation2);
+        assertNotSame(operation1, operation2);
+    }
+
+    @Test
+    public void testDoConfigure_testGetters() {
+        // should use given values
+        assertSame(client, oper.getClient());
+        assertEquals(PATH_GET, oper.getPathGet());
+        assertEquals(MAX_GETS, oper.getMaxGets());
+        assertEquals(WAIT_SEC_GETS, oper.getWaitSecGet());
+
+        SoParams params = SoParams.builder().pathGet("unslashed").maxGets(MAX_GETS).waitSecGet(WAIT_SEC_GETS)
+                        .clientName(CLIENT).path(PATH).timeoutSec(TIMEOUT).build();
+        Map<String, Object> paramMap = Util.translateToMap(OPERATION, params);
+        oper.configure(paramMap);
+        assertEquals("unslashed/", oper.getPathGet());
+
+        // test invalid parameters
+        Map<String, Object> paramMap2 = Util.translateToMap(OPERATION, SoParams.builder().build());
+        assertThatThrownBy(() -> oper.configure(paramMap2)).isInstanceOf(ParameterValidationRuntimeException.class);
+    }
+
+
+    private class MyOperator extends SoOperator {
+        public MyOperator() {
+            super(ACTOR, OPERATION);
+        }
+
+        @Override
+        public Operation buildOperation(ControlLoopOperationParams params) {
+            return null;
+        }
+
+        @Override
+        protected HttpClientFactory getClientFactory() {
+            return factory;
+        }
+    }
+
+    private class MyOperation extends SoOperation {
+        public MyOperation(ControlLoopOperationParams params, SoOperator operator) {
+            super(params, operator);
+        }
+    }
+}
diff --git a/models-interactions/model-actors/actor.so/src/test/java/org/onap/policy/controlloop/actor/so/SoParamsTest.java b/models-interactions/model-actors/actor.so/src/test/java/org/onap/policy/controlloop/actor/so/SoParamsTest.java
new file mode 100644 (file)
index 0000000..2b02ad8
--- /dev/null
@@ -0,0 +1,92 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 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.controlloop.actor.so;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.function.Function;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.controlloop.actor.so.SoParams.SoParamsBuilder;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams.HttpParamsBuilder;
+
+public class SoParamsTest {
+    private static final String CONTAINER = "my-container";
+    private static final String CLIENT = "my-client";
+    private static final String PATH = "my-path";
+    private static final String PATH_GET = "my-path-get";
+    private static final int MAX_GETS = 3;
+    private static final int WAIT_SEC_GETS = 20;
+    private static final int TIMEOUT = 10;
+
+    private SoParams params;
+
+    @Before
+    public void setUp() {
+        params = SoParams.builder().pathGet(PATH_GET).maxGets(MAX_GETS).waitSecGet(WAIT_SEC_GETS).clientName(CLIENT)
+                        .path(PATH).timeoutSec(TIMEOUT).build();
+    }
+
+    @Test
+    public void testValidate() {
+        assertTrue(params.validate(CONTAINER).isValid());
+
+        testValidateField("pathGet", "null", bldr -> bldr.pathGet(null));
+        testValidateField("maxGets", "minimum", bldr -> bldr.maxGets(-1));
+        testValidateField("waitSecGet", "minimum", bldr -> bldr.waitSecGet(-1));
+
+        // validate one of the superclass fields
+        testValidateField("clientName", "null", bldr -> bldr.clientName(null));
+
+        // check edge cases
+        assertTrue(params.toBuilder().maxGets(0).build().validate(CONTAINER).isValid());
+        assertFalse(params.toBuilder().waitSecGet(0).build().validate(CONTAINER).isValid());
+        assertTrue(params.toBuilder().waitSecGet(1).build().validate(CONTAINER).isValid());
+    }
+
+    @Test
+    public void testBuilder_testToBuilder() {
+        assertEquals(CLIENT, params.getClientName());
+
+        assertEquals(PATH_GET, params.getPathGet());
+        assertEquals(MAX_GETS, params.getMaxGets());
+        assertEquals(WAIT_SEC_GETS, params.getWaitSecGet());
+
+        assertEquals(params, params.toBuilder().build());
+    }
+
+    private void testValidateField(String fieldName, String expected,
+                    @SuppressWarnings("rawtypes") Function<SoParamsBuilder, HttpParamsBuilder> makeInvalid) {
+
+        // original params should be valid
+        ValidationResult result = params.validate(CONTAINER);
+        assertTrue(fieldName, result.isValid());
+
+        // make invalid params
+        result = makeInvalid.apply(params.toBuilder()).build().validate(CONTAINER);
+        assertFalse(fieldName, result.isValid());
+        assertThat(result.getResult()).contains(fieldName).contains(expected);
+    }
+}
diff --git a/models-interactions/model-actors/actor.so/src/test/java/org/onap/policy/controlloop/actor/so/VfModuleCreateTest.java b/models-interactions/model-actors/actor.so/src/test/java/org/onap/policy/controlloop/actor/so/VfModuleCreateTest.java
new file mode 100644 (file)
index 0000000..40efdc8
--- /dev/null
@@ -0,0 +1,182 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 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.controlloop.actor.so;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.commons.lang3.tuple.Pair;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.aai.domain.yang.CloudRegion;
+import org.onap.aai.domain.yang.GenericVnf;
+import org.onap.aai.domain.yang.ModelVer;
+import org.onap.aai.domain.yang.ServiceInstance;
+import org.onap.aai.domain.yang.Tenant;
+import org.onap.policy.aai.AaiCqResponse;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.onap.policy.so.SoRequest;
+
+public class VfModuleCreateTest extends BasicSoOperation {
+    private static final String MODEL_NAME2 = "my-model-name-B";
+    private static final String MODEL_VERS2 = "my-model-version-B";
+    private static final String SVC_INSTANCE_ID = "my-service-instance-id";
+    private static final String VNF_ID = "my-vnf-id";
+
+    private VfModuleCreate oper;
+
+    public VfModuleCreateTest() {
+        super(DEFAULT_ACTOR, VfModuleCreate.NAME);
+    }
+
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        oper = new VfModuleCreate(params, soOperator);
+    }
+
+    @Test
+    public void testConstructor() {
+        assertEquals(DEFAULT_ACTOR, oper.getActorName());
+        assertEquals(VfModuleCreate.NAME, oper.getName());
+    }
+
+    @Test
+    public void testStartPreprocessorAsync() {
+        CompletableFuture<OperationOutcome> future = new CompletableFuture<>();
+        context = mock(ControlLoopEventContext.class);
+        when(context.obtain(eq(AaiCqResponse.CONTEXT_KEY), any())).thenReturn(future);
+        params = params.toBuilder().context(context).build();
+
+        AtomicBoolean guardStarted = new AtomicBoolean();
+
+        oper = new VfModuleCreate(params, soOperator) {
+            @Override
+            protected CompletableFuture<OperationOutcome> startGuardAsync() {
+                guardStarted.set(true);
+                return super.startGuardAsync();
+            }
+        };
+
+        assertSame(future, oper.startPreprocessorAsync());
+        assertFalse(future.isDone());
+        assertTrue(guardStarted.get());
+    }
+
+    @Test
+    public void testStartOperationAsync() throws Exception {
+        when(client.post(any(), any(), any(), any())).thenAnswer(provideResponse(rawResponse));
+
+        // use a real executor
+        params = params.toBuilder().executor(ForkJoinPool.commonPool()).build();
+
+        oper = new VfModuleCreate(params, soOperator) {
+            @Override
+            public long getWaitMsGet() {
+                return 1;
+            }
+        };
+
+        CompletableFuture<OperationOutcome> future2 = oper.start();
+
+        outcome = future2.get(500, TimeUnit.SECONDS);
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+    }
+
+    /**
+     * Tests startOperationAsync() when "get" operations are required.
+     */
+    @Test
+    public void testStartOperationAsyncWithGets() throws Exception {
+        when(rawResponse.getStatus()).thenReturn(500, 500, 500, 500, 200, 200);
+
+        when(client.post(any(), any(), any(), any())).thenAnswer(provideResponse(rawResponse));
+        when(client.get(any(), any(), any())).thenAnswer(provideResponse(rawResponse));
+
+        // use a real executor
+        params = params.toBuilder().executor(ForkJoinPool.commonPool()).build();
+
+        oper = new VfModuleCreate(params, soOperator) {
+            @Override
+            public long getWaitMsGet() {
+                return 1;
+            }
+        };
+
+        CompletableFuture<OperationOutcome> future2 = oper.start();
+
+        outcome = future2.get(500, TimeUnit.SECONDS);
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+    }
+
+    @Test
+    public void testMakeRequest() throws CoderException {
+        Pair<String, SoRequest> pair = oper.makeRequest();
+
+        // @formatter:off
+        assertEquals(
+            "/serviceInstantiation/v7/serviceInstances/my-service-instance-id/vnfs/my-vnf-id/vfModules/scaleOut",
+            pair.getLeft());
+        // @formatter:on
+
+        verifyRequest("vfModuleCreate.json", pair.getRight());
+    }
+
+
+    @Override
+    protected void makeContext() {
+        super.makeContext();
+
+        AaiCqResponse cq = mock(AaiCqResponse.class);
+
+        GenericVnf vnf = new GenericVnf();
+        when(cq.getGenericVnfByVfModuleModelInvariantId(MODEL_INVAR_ID)).thenReturn(vnf);
+        vnf.setVnfId(VNF_ID);
+
+        ServiceInstance instance = new ServiceInstance();
+        when(cq.getServiceInstance()).thenReturn(instance);
+        instance.setServiceInstanceId(SVC_INSTANCE_ID);
+
+        when(cq.getDefaultTenant()).thenReturn(new Tenant());
+        when(cq.getDefaultCloudRegion()).thenReturn(new CloudRegion());
+
+        ModelVer modelVers = new ModelVer();
+        when(cq.getModelVerByVersionId(any())).thenReturn(modelVers);
+        modelVers.setModelName(MODEL_NAME2);
+        modelVers.setModelVersion(MODEL_VERS2);
+
+        params.getContext().setProperty(AaiCqResponse.CONTEXT_KEY, cq);
+    }
+}
diff --git a/models-interactions/model-actors/actor.so/src/test/resources/model.json b/models-interactions/model-actors/actor.so/src/test/resources/model.json
new file mode 100644 (file)
index 0000000..133b0fe
--- /dev/null
@@ -0,0 +1,8 @@
+{
+  "modelType": "vfModule",
+  "modelInvariantId": "my-model-invariant-id",
+  "modelVersionId": "my-model-version-id",
+  "modelName": "my-model-name",
+  "modelVersion": "my-model-version",
+  "modelCustomizationId": "my-model-customization-id"
+}
\ No newline at end of file
diff --git a/models-interactions/model-actors/actor.so/src/test/resources/reqinfo.json b/models-interactions/model-actors/actor.so/src/test/resources/reqinfo.json
new file mode 100644 (file)
index 0000000..62d6eb1
--- /dev/null
@@ -0,0 +1,5 @@
+{
+  "source": "POLICY",
+  "suppressRollback": false,
+  "requestorId": "policy"
+}
\ No newline at end of file
diff --git a/models-interactions/model-actors/actor.so/src/test/resources/reqparams.json b/models-interactions/model-actors/actor.so/src/test/resources/reqparams.json
new file mode 100644 (file)
index 0000000..44598ac
--- /dev/null
@@ -0,0 +1,5 @@
+{
+  "subscriptionServiceType": "my-subscription-service-type",
+  "usePreload": false,
+  "userParams": []
+}
\ No newline at end of file
diff --git a/models-interactions/model-actors/actor.so/src/test/resources/vfModuleCreate.json b/models-interactions/model-actors/actor.so/src/test/resources/vfModuleCreate.json
new file mode 100644 (file)
index 0000000..06258f3
--- /dev/null
@@ -0,0 +1,49 @@
+{
+  "requestDetails": {
+    "modelInfo": {
+      "modelType": "vfModule",
+      "modelInvariantId": "my-model-invariant-id",
+      "modelVersionId": "my-model-version-id",
+      "modelName": "my-model-name",
+      "modelVersion": "my-model-version",
+      "modelCustomizationId": "my-model-customization-id"
+    },
+    "cloudConfiguration": {},
+    "requestInfo": {
+      "instanceName": "vfModuleName",
+      "source": "POLICY",
+      "suppressRollback": false,
+      "requestorId": "policy"
+    },
+    "relatedInstanceList": [
+      {
+        "relatedInstance": {
+          "instanceId": "my-service-instance-id",
+          "modelInfo": {
+            "modelType": "service",
+            "modelName": "my-model-name-B",
+            "modelVersion": "my-model-version-B"
+          }
+        }
+      },
+      {
+        "relatedInstance": {
+          "instanceId": "my-vnf-id",
+          "modelInfo": {
+            "modelType": "vnf",
+            "modelName": "my-model-name-B",
+            "modelVersion": "my-model-version-B"
+          }
+        }
+      }
+    ],
+    "requestParameters": {
+      "subscriptionServiceType": "my-subscription-service-type",
+      "usePreload": false,
+      "userParams": []
+    },
+    "configurationParameters": [
+      {}
+    ]
+  }
+}
\ No newline at end of file
index 3e02da6..8099ea7 100644 (file)
@@ -137,7 +137,7 @@ public class ControlLoopEventContext implements Serializable {
 
         // @formatter:off
         CompletableFuture<OperationOutcome> oldFuture =
-            retrievers.compute(name, (key, future) -> (future == null || future.isCancelled() ? null : future));
+            retrievers.computeIfPresent(name, (key, future) -> future.isCancelled() ? null : future);
         // @formatter:on
 
         if (oldFuture != null) {
index add74aa..b4a3318 100644 (file)
@@ -95,6 +95,15 @@ public abstract class HttpOperator extends OperatorPartial {
             throw new ParameterValidationRuntimeException("invalid parameters", result);
         }
 
+        doConfigure(params);
+    }
+
+    /**
+     * Configures the operator using the specified parameters.
+     *
+     * @param params operator parameters
+     */
+    protected void doConfigure(HttpParams params) {
         client = getClientFactory().get(params.getClientName());
         path = params.getPath();
         timeoutMs = TimeUnit.MILLISECONDS.convert(params.getTimeoutSec(), TimeUnit.SECONDS);
index d8933c8..51cca52 100644 (file)
@@ -47,6 +47,9 @@ import org.slf4j.LoggerFactory;
  *
  */
 public final class SoManager {
+
+    // TODO remove this class
+
     private static final Logger logger = LoggerFactory.getLogger(SoManager.class);
 
     private static ExecutorService executors = Executors.newCachedThreadPool();