CDS Actor service-provider implemntation

https://wiki.onap.org/display/DW/CDS+actor+support+in+Policy

Issue-ID: POLICY-1763
Signed-off-by: Rashmi Pujar <rashmi.pujar@bell.ca>
Change-Id: Idcb6e9168b949745cc644e97ba77c479573a8bf5
diff --git a/models-interactions/model-actors/actor.cds/pom.xml b/models-interactions/model-actors/actor.cds/pom.xml
new file mode 100644
index 0000000..5c4a044
--- /dev/null
+++ b/models-interactions/model-actors/actor.cds/pom.xml
@@ -0,0 +1,71 @@
+<!--
+  ============LICENSE_START=======================================================
+  Copyright (C) 2019 Bell Canada. 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=========================================================
+  -->
+
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+    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.1.3-SNAPSHOT</version>
+    </parent>
+    <artifactId>actor.cds</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>cds</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</groupId>
+            <artifactId>simulators</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.common</groupId>
+            <artifactId>policy-endpoints</artifactId>
+            <version>${policy.common.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.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/models-interactions/model-actors/actor.cds/src/main/java/org/onap/policy/controlloop/actor/cds/CdsActorServiceProvider.java b/models-interactions/model-actors/actor.cds/src/main/java/org/onap/policy/controlloop/actor/cds/CdsActorServiceProvider.java
new file mode 100644
index 0000000..aaf07ac
--- /dev/null
+++ b/models-interactions/model-actors/actor.cds/src/main/java/org/onap/policy/controlloop/actor/cds/CdsActorServiceProvider.java
@@ -0,0 +1,240 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2019 Bell Canada. 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.cds;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.Struct;
+import com.google.protobuf.Struct.Builder;
+import com.google.protobuf.util.JsonFormat;
+import io.grpc.Status;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import org.onap.ccsdk.cds.controllerblueprints.common.api.ActionIdentifiers;
+import org.onap.ccsdk.cds.controllerblueprints.common.api.CommonHeader;
+import org.onap.ccsdk.cds.controllerblueprints.common.api.EventType;
+import org.onap.ccsdk.cds.controllerblueprints.processing.api.ExecutionServiceInput;
+import org.onap.ccsdk.cds.controllerblueprints.processing.api.ExecutionServiceOutput;
+import org.onap.policy.cds.api.CdsProcessorListener;
+import org.onap.policy.cds.client.CdsProcessorGrpcClient;
+import org.onap.policy.cds.properties.CdsServerProperties;
+import org.onap.policy.controlloop.ControlLoopOperation;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actor.cds.beans.ConfigDeployRequest;
+import org.onap.policy.controlloop.actor.cds.constants.CdsActorConstants;
+import org.onap.policy.controlloop.actorserviceprovider.spi.Actor;
+import org.onap.policy.controlloop.policy.Policy;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * CDS Actor service-provider implementation. This is a deploy dark feature for El-Alto release.
+ */
+public class CdsActorServiceProvider implements Actor {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(CdsActorServiceProvider.class);
+
+    /**
+     * {@inheritDoc}.
+     */
+    @Override
+    public String actor() {
+        return CdsActorConstants.CDS_ACTOR;
+    }
+
+    /**
+     * {@inheritDoc}. Note: This is a placeholder for now.
+     */
+    @Override
+    public List<String> recipes() {
+        return new ArrayList<>();
+    }
+
+    /**
+     * {@inheritDoc}. Note: This is a placeholder for now.
+     */
+    @Override
+    public List<String> recipeTargets(final String recipe) {
+        return new ArrayList<>();
+    }
+
+    /**
+     * {@inheritDoc}. Note: This is a placeholder for now.
+     */
+    @Override
+    public List<String> recipePayloads(final String recipe) {
+        return new ArrayList<>();
+    }
+
+    /**
+     * Build the CDS ExecutionServiceInput request from the policy object and the AAI enriched parameters. TO-DO: Avoid
+     * leaking Exceptions to the Kie Session thread. TBD item for Frankfurt release.
+     *
+     * @param onset the event that is reporting the alert for policy to perform an action.
+     * @param operation the control loop operation specifying the actor, operation, target, etc.
+     * @param policy the policy specified from the yaml generated by CLAMP or through Policy API.
+     * @param aaiParams Map of enriched AAI attributes in node.attribute notation.
+     * @return an Optional ExecutionServiceInput instance if valid else an Optional empty object is returned.
+     */
+    public Optional<ExecutionServiceInput> constructRequest(VirtualControlLoopEvent onset,
+        ControlLoopOperation operation, Policy policy, Map<String, String> aaiParams) {
+
+        // For the current operational TOSCA policy model (yaml) CBA name and version are embedded in the payload
+        // section, with the new policy type model being proposed in Frankfurt we will be able to move it out.
+        Map<String, String> payload = policy.getPayload();
+        if (!validateCdsMandatoryParams(policy)) {
+            return Optional.empty();
+        }
+        String cbaName = payload.get(CdsActorConstants.KEY_CBA_NAME);
+        String cbaVersion = payload.get(CdsActorConstants.KEY_CBA_VERSION);
+        String cbaActionName = policy.getRecipe();
+
+        // Embed payload from policy to ConfigDeployRequest object, serialize and inject into grpc request.
+        ConfigDeployRequest request = new ConfigDeployRequest();
+        request.setConfigDeployProperties(payload);
+
+        // Inject AAI properties into payload map. Offer flexibility to the usecase
+        // implementation to inject whatever AAI parameters are of interest to them.
+        // E.g. For vFW usecase El-Alto inject service-instance-id, generic-vnf-id as needed by CDS.
+        request.setAaiProperties(aaiParams);
+
+        Builder struct = Struct.newBuilder();
+        try {
+            String requestStr = request.toString();
+            Preconditions.checkState(!Strings.isNullOrEmpty(requestStr), "Unable to build "
+                + "config-deploy-request from payload parameters: {}", payload);
+            JsonFormat.parser().merge(requestStr, struct);
+        } catch (InvalidProtocolBufferException e) {
+            LOGGER.error("Failed to parse received message. blueprint({}:{}) for action({})", cbaName, cbaVersion,
+                cbaActionName, e);
+            return Optional.empty();
+        }
+
+        // Build CDS gRPC request common-header
+        CommonHeader commonHeader = CommonHeader.newBuilder()
+            .setOriginatorId(CdsActorConstants.ORIGINATOR_ID)
+            .setRequestId(onset.getRequestId().toString())
+            .setSubRequestId(operation.getSubRequestId())
+            .build();
+
+        // Build CDS gRPC request action-identifier
+        ActionIdentifiers actionIdentifiers = ActionIdentifiers.newBuilder()
+            .setBlueprintName(cbaName)
+            .setBlueprintVersion(cbaVersion)
+            .setActionName(cbaActionName)
+            .setMode(CdsActorConstants.CDS_MODE)
+            .build();
+
+        // Finally build the ExecutionServiceInput gRPC request object.
+        ExecutionServiceInput executionServiceInput = ExecutionServiceInput.newBuilder()
+            .setCommonHeader(commonHeader)
+            .setActionIdentifiers(actionIdentifiers)
+            .setPayload(struct.build())
+            .build();
+        return Optional.of(executionServiceInput);
+    }
+
+    private boolean validateCdsMandatoryParams(Policy policy) {
+        if (policy == null || policy.getPayload() == null) {
+            return false;
+        }
+        Map<String, String> payload = policy.getPayload();
+        String cbaName = payload.get(CdsActorConstants.KEY_CBA_NAME);
+        String cbaVersion = payload.get(CdsActorConstants.KEY_CBA_VERSION);
+        String cbaActionName = policy.getRecipe();
+        return !Strings.isNullOrEmpty(cbaName) && !Strings.isNullOrEmpty(cbaVersion) && !Strings
+            .isNullOrEmpty(cbaActionName);
+    }
+
+    class CdsActorServiceManager implements CdsProcessorListener {
+
+        private final AtomicReference<String> cdsResponse = new AtomicReference<>();
+
+        /**
+         * {@inheritDoc}.
+         */
+        @Override
+        public void onMessage(final ExecutionServiceOutput message) {
+            LOGGER.info("Received notification from CDS: {}", message);
+            EventType eventType = message.getStatus().getEventType();
+            switch (eventType) {
+                case EVENT_COMPONENT_FAILURE:
+                    cdsResponse.compareAndSet(null, CdsActorConstants.FAILED);
+                    break;
+                case EVENT_COMPONENT_PROCESSING:
+                    cdsResponse.compareAndSet(null, CdsActorConstants.PROCESSING);
+                    break;
+                case EVENT_COMPONENT_EXECUTED:
+                    cdsResponse.compareAndSet(null, CdsActorConstants.SUCCESS);
+                    break;
+                default:
+                    cdsResponse.compareAndSet(null, CdsActorConstants.FAILED);
+                    break;
+            }
+        }
+
+        /**
+         * {@inheritDoc}.
+         */
+        @Override
+        public void onError(final Throwable throwable) {
+            Status status = Status.fromThrowable(throwable);
+            cdsResponse.compareAndSet(null, CdsActorConstants.ERROR);
+            LOGGER.error("Failed processing blueprint {} {}", status, throwable);
+        }
+
+        /**
+         * Send gRPC request to CDS to execute the blueprint.
+         *
+         * @param cdsClient CDS grpc client object.
+         * @param cdsProps CDS properties.
+         * @param executionServiceInput a valid CDS grpc request object.
+         * @return Status of the CDS request, null if timeout happens or onError is invoked for any reason.
+         */
+        public String sendRequestToCds(CdsProcessorGrpcClient cdsClient, CdsServerProperties cdsProps,
+            ExecutionServiceInput executionServiceInput) {
+            try {
+                LOGGER.trace("Start CdsActorServiceProvider.executeCdsBlueprintProcessor {}.", executionServiceInput);
+                // TO-DO: Handle requests asynchronously once the callback support is added to actors.
+                CountDownLatch countDownLatch = cdsClient.sendRequest(executionServiceInput);
+                boolean status = countDownLatch.await(cdsProps.getTimeout(), TimeUnit.SECONDS);
+                if (!status) {
+                    cdsResponse.compareAndSet(null, CdsActorConstants.TIMED_OUT);
+                }
+                LOGGER.info("CDS response {}", getCdsResponse());
+            } catch (InterruptedException ex) {
+                LOGGER.error("Caught exception in executeCdsBlueprintProcessor in CdsActorServiceProvider: ", ex);
+                cdsResponse.compareAndSet(null, CdsActorConstants.INTERRUPTED);
+                Thread.currentThread().interrupt();
+            }
+            LOGGER.info("Status of the CDS gRPC request is: {}", getCdsResponse());
+            return getCdsResponse();
+        }
+
+        String getCdsResponse() {
+            return cdsResponse.get();
+        }
+    }
+}
diff --git a/models-interactions/model-actors/actor.cds/src/main/java/org/onap/policy/controlloop/actor/cds/beans/ConfigDeployRequest.java b/models-interactions/model-actors/actor.cds/src/main/java/org/onap/policy/controlloop/actor/cds/beans/ConfigDeployRequest.java
new file mode 100644
index 0000000..f33f226
--- /dev/null
+++ b/models-interactions/model-actors/actor.cds/src/main/java/org/onap/policy/controlloop/actor/cds/beans/ConfigDeployRequest.java
@@ -0,0 +1,43 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2019 Bell Canada. 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.cds.beans;
+
+import com.google.gson.Gson;
+import com.google.gson.annotations.SerializedName;
+import java.util.Map;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class ConfigDeployRequest {
+
+    private static final Gson GSON = new Gson();
+
+    @SerializedName("config-deploy-properties")
+    private Map<String, String> configDeployProperties;
+
+    @SerializedName("aai-properties")
+    private Map<String, String> aaiProperties;
+
+    @Override
+    public String toString() {
+        return "{\"config-deploy-request\":" + GSON.toJson(this) + '}';
+    }
+}
diff --git a/models-interactions/model-actors/actor.cds/src/main/java/org/onap/policy/controlloop/actor/cds/constants/CdsActorConstants.java b/models-interactions/model-actors/actor.cds/src/main/java/org/onap/policy/controlloop/actor/cds/constants/CdsActorConstants.java
new file mode 100644
index 0000000..b79aca0
--- /dev/null
+++ b/models-interactions/model-actors/actor.cds/src/main/java/org/onap/policy/controlloop/actor/cds/constants/CdsActorConstants.java
@@ -0,0 +1,39 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2019 Bell Canada. 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.cds.constants;
+
+public class CdsActorConstants {
+
+    public static final String CDS_ACTOR = "CDS";
+
+    // CDS Status
+    public static final String SUCCESS = "Success";
+    public static final String FAILED = "Failed";
+    public static final String PROCESSING = "Processing";
+    public static final String TIMED_OUT = "Timed out";
+    public static final String INTERRUPTED = "Thread interrupted";
+    public static final String ERROR = "Error";
+
+    // CDS blueprint archive parameters
+    public static final String KEY_CBA_NAME = "artifact_name";
+    public static final String KEY_CBA_VERSION = "artifact_version";
+    public static final String ORIGINATOR_ID = "POLICY";
+    // Temporarily set to synchronous mode to support current rules, since callbacks aren't supported yet
+    public static final String CDS_MODE = "sync";
+}
diff --git a/models-interactions/model-actors/actor.cds/src/main/resources/META-INF.services/org.onap.policy.controlloop.actorserviceprovider.spi.Actor b/models-interactions/model-actors/actor.cds/src/main/resources/META-INF.services/org.onap.policy.controlloop.actorserviceprovider.spi.Actor
new file mode 100644
index 0000000..e91d419
--- /dev/null
+++ b/models-interactions/model-actors/actor.cds/src/main/resources/META-INF.services/org.onap.policy.controlloop.actorserviceprovider.spi.Actor
@@ -0,0 +1 @@
+org.onap.policy.controlloop.actor.cds.CdsActorServiceProvider
diff --git a/models-interactions/model-actors/actor.cds/src/test/java/org/onap/policy/controlloop/actor/cds/CdsActorServiceProviderTest.java b/models-interactions/model-actors/actor.cds/src/test/java/org/onap/policy/controlloop/actor/cds/CdsActorServiceProviderTest.java
new file mode 100644
index 0000000..65fd602
--- /dev/null
+++ b/models-interactions/model-actors/actor.cds/src/test/java/org/onap/policy/controlloop/actor/cds/CdsActorServiceProviderTest.java
@@ -0,0 +1,237 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2019 Bell Canada. 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.cds;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.onap.ccsdk.cds.controllerblueprints.common.api.ActionIdentifiers;
+import org.onap.ccsdk.cds.controllerblueprints.common.api.CommonHeader;
+import org.onap.ccsdk.cds.controllerblueprints.common.api.EventType;
+import org.onap.ccsdk.cds.controllerblueprints.common.api.Status;
+import org.onap.ccsdk.cds.controllerblueprints.processing.api.ExecutionServiceInput;
+import org.onap.ccsdk.cds.controllerblueprints.processing.api.ExecutionServiceOutput;
+import org.onap.policy.cds.client.CdsProcessorGrpcClient;
+import org.onap.policy.cds.properties.CdsServerProperties;
+import org.onap.policy.controlloop.ControlLoopOperation;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actor.cds.CdsActorServiceProvider.CdsActorServiceManager;
+import org.onap.policy.controlloop.actor.cds.constants.CdsActorConstants;
+import org.onap.policy.controlloop.policy.Policy;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CdsActorServiceProviderTest {
+
+    private static final String CDS_BLUEPRINT_NAME = "vfw-cds";
+    private static final String CDS_BLUEPRINT_VERSION = "1.0.0";
+    private static final UUID REQUEST_ID = UUID.randomUUID();
+    private static final String SUBREQUEST_ID = "123456";
+
+    @Rule
+    public ExpectedException exceptionRule = ExpectedException.none();
+    @Mock
+    private CdsProcessorGrpcClient cdsClient;
+    private CdsActorServiceProvider cdsActor;
+    private Policy policy;
+    private CdsServerProperties cdsProps;
+    private Map<String, String> aaiParams;
+    private VirtualControlLoopEvent onset;
+    private ControlLoopOperation operation;
+
+    /**
+     * Test setup.
+     */
+    @Before
+    public void setup() {
+        // Setup policy
+        policy = new Policy();
+        Map<String, String> payloadMap = new HashMap<String, String>() {
+            {
+                put(CdsActorConstants.KEY_CBA_NAME, CDS_BLUEPRINT_NAME);
+                put(CdsActorConstants.KEY_CBA_VERSION, CDS_BLUEPRINT_VERSION);
+                put("data", "{\"mapInfo\":{\"key\":\"val\"},\"arrayInfo\":[\"one\",\"two\"],\"paramInfo\":\"val\"}");
+            }
+        };
+        policy.setPayload(payloadMap);
+        policy.setRecipe("CDS");
+
+        // Setup the CDS properties
+        cdsProps = new CdsServerProperties();
+        cdsProps.setHost("10.10.10.10");
+        cdsProps.setPort(2000);
+        cdsProps.setUsername("testUser");
+        cdsProps.setPassword("testPassword");
+        cdsProps.setTimeout(1);
+
+        // Setup aaiParams
+        aaiParams = ImmutableMap.of("service-instance-id", "1234", "generic-vnf-id", "5678");
+
+        // Setup cdsClient
+        when(cdsClient.sendRequest(any(ExecutionServiceInput.class))).thenReturn(mock(CountDownLatch.class));
+
+        // Setup the cdsActor
+        cdsActor = new CdsActorServiceProvider();
+
+        // Setup onset event
+        onset = new VirtualControlLoopEvent();
+        onset.setRequestId(REQUEST_ID);
+
+        // Setup controlloop operation object
+        operation = new ControlLoopOperation();
+        operation.setSubRequestId(SUBREQUEST_ID);
+    }
+
+    @Test
+    public void testActor() {
+        assertEquals(cdsActor.actor(), CdsActorConstants.CDS_ACTOR);
+    }
+
+    @Test
+    public void testConstructRequest() {
+        policy.setPayload(new HashMap<>());
+        Optional<ExecutionServiceInput> cdsRequestOpt = cdsActor
+            .constructRequest(onset, operation, policy, aaiParams);
+
+        assertFalse(cdsRequestOpt.isPresent());
+    }
+
+    @Test
+    public void testConstructRequestWhenMissingCdsParamsInPolicyPayload() {
+        Optional<ExecutionServiceInput> cdsRequestOpt = cdsActor
+            .constructRequest(onset, operation, policy, aaiParams);
+
+        assertTrue(cdsRequestOpt.isPresent());
+        final ExecutionServiceInput cdsRequest = cdsRequestOpt.get();
+
+        assertTrue(cdsRequest.hasCommonHeader());
+        CommonHeader commonHeader = cdsRequest.getCommonHeader();
+        assertEquals(commonHeader.getRequestId(), REQUEST_ID.toString());
+        assertEquals(commonHeader.getSubRequestId(), SUBREQUEST_ID);
+
+        assertTrue(cdsRequest.hasPayload());
+
+        assertTrue(cdsRequest.hasActionIdentifiers());
+        ActionIdentifiers actionIdentifiers = cdsRequest.getActionIdentifiers();
+        assertEquals(actionIdentifiers.getActionName(), CdsActorConstants.CDS_ACTOR);
+        assertEquals(actionIdentifiers.getBlueprintName(), CDS_BLUEPRINT_NAME);
+        assertEquals(actionIdentifiers.getBlueprintVersion(), CDS_BLUEPRINT_VERSION);
+    }
+
+    @Test
+    public void testRecipePayloads() {
+        assertEquals(cdsActor.recipePayloads("").size(), 0);
+    }
+
+    @Test
+    public void testRecipes() {
+        assertEquals(cdsActor.recipes().size(), 0);
+    }
+
+    @Test
+    public void testRecipeTargets() {
+        assertEquals(cdsActor.recipeTargets("").size(), 0);
+    }
+
+    @Test
+    public void testSendRequestToCdsSuccess() {
+        sendRequestToCds();
+        verify(cdsClient).sendRequest(any(ExecutionServiceInput.class));
+    }
+
+    @Test
+    public void testSendRequestToCdsLatchInterrupted() throws InterruptedException {
+        // Reset cdsClient
+        CountDownLatch countDownLatch = mock(CountDownLatch.class);
+        doThrow(new InterruptedException("Test latch interrupted failure")).when(countDownLatch)
+            .await(anyLong(), any(TimeUnit.class));
+        when(cdsClient.sendRequest(any(ExecutionServiceInput.class))).thenReturn(countDownLatch);
+
+        CdsActorServiceProvider.CdsActorServiceManager cdsActorSvcMgr = cdsActor.new CdsActorServiceManager();
+        String response = cdsActorSvcMgr
+            .sendRequestToCds(cdsClient, cdsProps, ExecutionServiceInput.newBuilder().build());
+        assertTrue(Thread.interrupted());
+        assertEquals(response, CdsActorConstants.INTERRUPTED);
+    }
+
+    @Test
+    public void testSendRequestToCdsLatchTimedOut() {
+        CdsActorServiceProvider.CdsActorServiceManager cdsActorSvcMgr = cdsActor.new CdsActorServiceManager();
+        String response = cdsActorSvcMgr
+            .sendRequestToCds(cdsClient, cdsProps, ExecutionServiceInput.newBuilder().build());
+        assertEquals(response, CdsActorConstants.TIMED_OUT);
+    }
+
+    @Test
+    public void testOnMessage() throws InterruptedException {
+        ExecutionServiceOutput message = ExecutionServiceOutput.newBuilder()
+            .setStatus(Status.newBuilder().setEventType(EventType.EVENT_COMPONENT_FAILURE).build()).build();
+
+        // Test "no timeout" scenarios
+        CountDownLatch latch = mock(CountDownLatch.class);
+        when(latch.await(anyLong(), any(TimeUnit.class))).thenReturn(true);
+        when(cdsClient.sendRequest(any(ExecutionServiceInput.class))).thenReturn(latch);
+
+        CdsActorServiceManager cdsActorSvcMgr = sendRequestToCds();
+
+        // #1: Failure test
+        cdsActorSvcMgr.onMessage(message);
+        assertEquals(cdsActorSvcMgr.getCdsResponse(), CdsActorConstants.FAILED);
+
+        // #2: Success test
+        cdsActorSvcMgr = sendRequestToCds();
+        message = ExecutionServiceOutput.newBuilder()
+            .setStatus(Status.newBuilder().setEventType(EventType.EVENT_COMPONENT_EXECUTED).build()).build();
+        cdsActorSvcMgr.onMessage(message);
+        assertEquals(cdsActorSvcMgr.getCdsResponse(), CdsActorConstants.SUCCESS);
+
+        // #3: Processing test
+        cdsActorSvcMgr = sendRequestToCds();
+        message = ExecutionServiceOutput.newBuilder()
+            .setStatus(Status.newBuilder().setEventType(EventType.EVENT_COMPONENT_PROCESSING).build()).build();
+        cdsActorSvcMgr.onMessage(message);
+        assertEquals(cdsActorSvcMgr.getCdsResponse(), CdsActorConstants.PROCESSING);
+    }
+
+    private CdsActorServiceManager sendRequestToCds() {
+        CdsActorServiceManager cdsActorSvcMgr = cdsActor.new CdsActorServiceManager();
+        cdsActorSvcMgr.sendRequestToCds(cdsClient, cdsProps, ExecutionServiceInput.newBuilder().build());
+        return cdsActorSvcMgr;
+    }
+}
diff --git a/models-interactions/model-actors/pom.xml b/models-interactions/model-actors/pom.xml
index 03c6458..0f37e1a 100644
--- a/models-interactions/model-actors/pom.xml
+++ b/models-interactions/model-actors/pom.xml
@@ -38,6 +38,7 @@
     <module>actor.appclcm</module>
     <module>actor.sdnr</module>
     <module>actor.so</module>
+    <module>actor.cds</module>
     <module>actor.test</module>
   </modules>
   <dependencies>