Enhance vid CSIT tests, added for scaleout
Issue-ID: VID-323
Change-Id: I31ad377ee766eaf66c848ad802c33e21ea1dfe76
Signed-off-by: Sandra Koblosz <sandra.koblosz@nokia.com>
diff --git a/tests/vid/healthCheck/test1.robot b/tests/vid/healthCheck/test1.robot
index 8f9448d..4f1aabd 100644
--- a/tests/vid/healthCheck/test1.robot
+++ b/tests/vid/healthCheck/test1.robot
@@ -7,9 +7,9 @@
*** Test Cases ***
Get Requests health check ok
[Tags] get
- CreateSession vid http://localhost:8080
+ CreateSession vid http://${VID_IP}:8080
${headers}= Create Dictionary Accept=application/json Content-Type=application/json
- ${resp}= Get Request vid /vid/healthCheck headers=&{headers}
+ ${resp}= Get Request vid /vid/healthCheck headers=${headers}
Should Be Equal As Strings ${resp.status_code} 200
Log to console statusCode: ${resp.json()['statusCode']}
Should Be Equal As Strings ${resp.json()['statusCode']} 200
diff --git a/tests/vid/https-connection/__init__.robot b/tests/vid/https-connection/__init__.robot
index e69de29..dcb082f 100644
--- a/tests/vid/https-connection/__init__.robot
+++ b/tests/vid/https-connection/__init__.robot
@@ -0,0 +1,2 @@
+*** Settings ***
+Documentation VID - Checking connection to other component using HTTPS
diff --git a/tests/vid/https-connection/keywords.py b/tests/vid/https-connection/keywords.py
deleted file mode 100755
index 85bca10..0000000
--- a/tests/vid/https-connection/keywords.py
+++ /dev/null
@@ -1,125 +0,0 @@
-import ast
-
-import requests
-from assertpy import assert_that
-from robot.api import logger
-from robot.api.deco import keyword
-
-JSESSIONID_COOKIE = "JSESSIONID"
-
-_vid_to_so_request_details = {
- "requestDetails": {
- "cloudConfiguration": {
- "lcpCloudRegionId": "RegionOne",
- "tenantId": "982c540f6e69488eb6be5664255e00c0"
- },
- "modelInfo": {
- "modelInvariantId": "41b3c314-dfab-4501-9c5e-1c9fe5d8e151",
- "modelName": "SoWs1..base_ws..module-0",
- "modelType": "vfModule",
- "modelVersion": "1",
- "modelVersionId": "7ea96ae9-9eac-4eaa-882e-077478a6c44a"
- },
- "relatedInstanceList": [{
- "relatedInstance": {
- "instanceId": "0d8a98d8-d7ca-4c26-b7ab-81d3729e3b6c",
- "modelInfo": {
- "modelInvariantId": "a4413616-cf96-4615-a94e-0dc5a6a65430",
- "modelName": "SC_WS_SW_2",
- "modelType": "service",
- "modelVersion": "3.0",
- "modelVersionId": "0fdaaf44-3c6c-4d81-9c57-b2ce7224dbb9"
- }
- }
- },
- {
- "relatedInstance": {
- "instanceId": "61c19619-2714-46f8-90c9-39734e4f545f",
- "modelInfo": {
- "modelCustomizationName": "SO_WS_1 0",
- "modelInvariantId": "3b2c9dcb-6ef8-4c3c-8d5b-43d5776f7110",
- "modelName": "SO_WS_1",
- "modelType": "vnf",
- "modelVersion": "1.0",
- "modelVersionId": "0fdaaf44-3c6c-4d81-9c57-b2ce7224dbb9"
- }
- }
- }
- ],
- "requestInfo": {
- "source": "VID",
- "suppressRollback": False,
- "requestorId": "az2016",
- "instanceName": "SC_WS_VNF_1_2"
- },
- "requestParameters": {
- "controllerType": "SDNC",
- "userParams": []
- }
-
- }
-}
-
-_expected_so_response = {
- "status": 202,
- "entity": {
- "requestReferences": {
- "instanceId": "fffcbb6c-1983-42df-9ca8-89ae8b3a46c1",
- "requestId": "b2197d7e-3a7d-410e-82ba-7b7e8191bc46"
- }
- }
-}
-
-
-def _extract_cookie_from_headers(headers):
- for i in headers["Set-Cookie"].split(";"):
- if JSESSIONID_COOKIE in i:
- return i
- raise RuntimeError("No cookie when logging in to VID")
-
-
-def _log_request(response):
- logger.console(
- "\n=========\n"
- "Performing request to : {} \nBODY: {}\nHEADERS: {}"
- .format(str(response.request.url), str(response.request.body), str(response.request.headers)))
- logger.console(
- "---------\n"
- "Got response\n BODY: {} \n HEADERS: {}"
- "\n=========\n".format(str(response.headers), str(response.content)))
-
-
-@keyword('Login To VID')
-def login_to_vid():
- headers = {'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0',
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
- 'Accept-Language': 'pl,en-US;q=0.7,en;q=0.3',
- 'Accept-Encoding': 'gzip, deflate', 'Referer': 'http://localhost:8080/vid/login.htm',
- 'Content-Type': 'application/x-www-form-urlencoded',
- 'Content-Length': '36',
- 'Cookie': 'JSESSIONID=1B4AF817AA4BCB87C07BB5B49EFE8526',
- 'Connection': 'keep-alive',
- 'Upgrade-Insecure-Requests': '1'}
- response = requests.post("https://localhost:8443/vid/login_external", data="loginId=demo&password=Kp8bJ4SXszM0WX",
- headers=headers, allow_redirects=False, verify=False)
- logger.console("Performing login")
- _log_request(response)
- return _extract_cookie_from_headers(response.headers)
-
-
-@keyword('Send create VF module instance request to VID')
-def send_create_vfmodule_instance_request_to_vid(jsession_cookie):
- response = requests.post(
- "https://localhost:8443/vid/mso/mso_create_vfmodule_instance/0d8a98d8-d7ca-4c26-b7ab-81d3729e3b6c/vnfs/61c19619-2714-46f8-90c9-39734e4f545f ",
- headers={"Cookie": jsession_cookie}, json=_vid_to_so_request_details, verify=False)
- content = ast.literal_eval(response.content)
- logger.console("Triggering VF module instance creation")
- _log_request(response)
- return content
-
-
-@keyword('Response should contain valid entity')
-def expect_response_from_so_was_correctly_propageted(content):
- logger.console("\nActual entity" + str(content['entity']))
- logger.console("Expected entity" + str(_expected_so_response))
- assert_that(content['entity']).is_equal_to(_expected_so_response)
diff --git a/tests/vid/https-connection/test1.robot b/tests/vid/https-connection/test1.robot
index 2173757..a7f6c4b 100644
--- a/tests/vid/https-connection/test1.robot
+++ b/tests/vid/https-connection/test1.robot
@@ -1,16 +1,29 @@
*** Settings ***
-Library keywords.py
-Library Collections
+Library SeleniumLibrary
+Library RequestsLibrary
+Library OperatingSystem
+Library json
+Resource ../../common.robot
+Resource ../resources/keywords/scaleout_vid_keywords.robot
+
*** Variables ***
+${VID_TEST_ASSET_DIR} %{WORKSPACE}/tests/vid/resources/simulators/test_data_assets
+${EXPECTED_SO_RESPONSES_FILEPATH} ${VID_TEST_ASSET_DIR}/expected_so_responses.json
+${EXPECTED_SO_REQUESTS_FILEPATH} ${VID_TEST_ASSET_DIR}/expected_so_requests.json
+${SO_SIMULATOR_BASE_URL} http://${SO_SIMULATOR_IP}:8443
+${VID_HTTP_BASE_URL} http://${VID_IP}:8080
+${VID_SCALEOUT_ENDPOINT} vid/mso/mso_create_vfmodule_instance/0d8a98d8-d7ca-4c26-b7ab-81d3729e3b6c/vnfs/61c19619-2714-46f8-90c9-39734e4f545f
+${VALID_SCALEOUT_REQ_FILEPATH} ${VID_TEST_ASSET_DIR}/vid_create_vfmodule_request.json
+${VALID_SCALEOUT_RESP_FILEPATH} ${VID_TEST_ASSET_DIR}/so_action_response.json
*** Test Cases ***
-Connection to SO is performed using HTTPS
- ${cookies}= Login To VID
- ${response}= Send create VF module instance request to VID ${cookies}
- Dictionary Should Contain Item ${response} status 200
- Response should contain valid entity ${response}
-
-
-*** Keywords ***
+Triggering create vfmodule operation in SO is performed using HTTPS
+ Setup Expected Data In SO Simulator ${EXPECTED_SO_RESPONSES_FILEPATH} ${SO_SIMULATOR_BASE_URL} setResponse
+ ${jsessionIdCookie}= Login to VID Internally ${VID_HTTP_BASE_URL}/vid/login.htm demo Kp8bJ4SXszM0WX
+ Log to console loginResponse: ${jsessionIdCookie}
+ ${soExpectedJsonResp}= json_from_file ${VALID_SCALEOUT_RESP_FILEPATH}
+ ${soResponse}= Send Post request from VID FE ${VID_HTTP_BASE_URL} ${VID_SCALEOUT_ENDPOINT} ${VALID_SCALEOUT_REQ_FILEPATH} ${VALID_SCALEOUT_RESP_FILEPATH} ${jsessionIdCookie}
+ Dictionaries Should Be Equal ${soExpectedJsonResp} ${soResponse.json()['entity']}
+ [Teardown] Close Browser
\ No newline at end of file
diff --git a/tests/vid/login/test1.robot b/tests/vid/login/test1.robot
index acb6aae..2c39b50 100644
--- a/tests/vid/login/test1.robot
+++ b/tests/vid/login/test1.robot
@@ -40,7 +40,7 @@
Click Button xpath=//input[@id='loginBtn']
Wait Until Page Contains Welcome to VID ${GLOBAL_SELENIUM_BROWSER_WAIT_TIMEOUT}
Log Logged in to ${VID_ENDPOINT}${VID_ENV}
-
+ [Teardown] Close Browser
*** Keywords ***
Setup Browser
diff --git a/tests/vid/resources/docker-compose.yml b/tests/vid/resources/docker-compose.yml
index 4aecb6a..5f2c0fe 100644
--- a/tests/vid/resources/docker-compose.yml
+++ b/tests/vid/resources/docker-compose.yml
@@ -14,7 +14,7 @@
- vid-mariadb:vid-mariadb-docker-instance
vid-mariadb:
- image: mariadb:10
+ image: nexus3.onap.org:10001/library/mariadb:10
environment:
- MYSQL_DATABASE=vid_openecomp_epsdk
- MYSQL_USER=vidadmin
@@ -28,7 +28,7 @@
so-simulator:
build:
context: simulators
- dockerfile: SO-simulator
+ dockerfile: Dockerfile
ports:
- "8444:8443"
container_name: so-simulator
\ No newline at end of file
diff --git a/tests/vid/resources/keywords/scaleout_vid_keywords.robot b/tests/vid/resources/keywords/scaleout_vid_keywords.robot
new file mode 100644
index 0000000..0b96d45
--- /dev/null
+++ b/tests/vid/resources/keywords/scaleout_vid_keywords.robot
@@ -0,0 +1,45 @@
+*** Settings ***
+Documentation Collection of util keywords for managing SO simulator
+Library SeleniumLibrary
+Library RequestsLibrary
+Library OperatingSystem
+Library Collections
+Library json
+Resource ../../../common.robot
+
+
+*** Keywords ***
+Setup Expected Data In SO Simulator
+ [Documentation] Setup data to be returned by simulator
+ [Arguments] ${expectedResponseFilePath} ${simulatorBaseUrl} ${simulatorPutEndpoint}
+ ${expectedDataToReturn}= json_from_file ${expectedResponseFilePath}
+ ${headers}= Create Dictionary Content-Type=application/json
+ ${session}= Create Session so_simulator ${simulatorBaseUrl}
+ ${resp}= Put Request so_simulator uri=/${simulatorPutEndpoint} data=${expectedDataToReturn} headers=${headers}
+ Should Be Equal As Strings ${resp.status_code} 200
+ Log to console Successfully initialized so-simulator: status code ${resp.status_code}
+
+
+Send Post request from VID FE
+ [Documentation] Imitates VID UI. This keyword is designed for imitating calls from VID UI to VID BE
+ [Arguments] ${vidBaseUrl} ${endpoint} ${requestFilePath} ${expectedResponseFilePath} ${cookie}
+ ${vidRequest}= json_from_file ${requestFilePath}
+ ${headers}= Create Dictionary Content-Type=application/json Cookie=${cookie}
+ ${session}= Create Session vid ${vidBaseUrl}
+ ${resp}= Post Request vid uri=/${endpoint} data=${vidRequest} headers=${headers}
+ Should Be Equal As Strings ${resp.status_code} 200
+ Log to console ${resp.content}
+ [Return] ${resp}
+
+
+Login to VID Internally
+ [Arguments] ${url} ${username} ${password}
+ [Documentation] Login using Autn
+ Open browser ${url} chrome
+ Input Text id=loginId ${username}
+ Input Password id=password ${password}
+ Click Element id=loginBtn
+ ${cookie_value} Get Cookie Value JSESSIONID
+ [Return] JSESSIONID=${cookie_value}
+
+
diff --git a/tests/vid/resources/simulators/Dockerfile b/tests/vid/resources/simulators/Dockerfile
new file mode 100644
index 0000000..5aa1392
--- /dev/null
+++ b/tests/vid/resources/simulators/Dockerfile
@@ -0,0 +1,8 @@
+FROM frolvlad/alpine-python3
+
+COPY SO.py /
+ADD ./test_data_assets/ /
+
+EXPOSE 8443
+
+CMD [ "python", "./SO.py", "expected_so_requests.json", "expected_so_responses.json" ]
diff --git a/tests/vid/resources/simulators/SO-simulator b/tests/vid/resources/simulators/SO-simulator
deleted file mode 100644
index 5458766..0000000
--- a/tests/vid/resources/simulators/SO-simulator
+++ /dev/null
@@ -1,8 +0,0 @@
-FROM frolvlad/alpine-python3
-
-ADD SO.py /
-ADD so_post_response.json /
-
-EXPOSE 8443
-
-CMD [ "python", "./SO.py" ]
\ No newline at end of file
diff --git a/tests/vid/resources/simulators/SO.py b/tests/vid/resources/simulators/SO.py
index edc15f6..c119939 100644
--- a/tests/vid/resources/simulators/SO.py
+++ b/tests/vid/resources/simulators/SO.py
@@ -1,45 +1,115 @@
+# ============LICENSE_START=======================================================
+# INTEGRATION CSIT
+# ================================================================================
+# Copyright (C) 2018 Nokia 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=========================================================
+
+import json
import logging
+from functools import partial
+from sys import argv
from http.server import BaseHTTPRequestHandler, HTTPServer
DEFAULT_PORT = 8443
class SOHandler(BaseHTTPRequestHandler):
+ def __init__(self, expected_requests, expected_responses, *args, **kwargs):
- def __init__(self, request, client_address, server):
- self.response_on_get = self._read_on_get_response()
- super().__init__(request, client_address, server)
+ self._expected_requests = expected_requests
+ self._expected_responses = expected_responses
+ super().__init__(*args, **kwargs)
def do_POST(self):
- logging.info('POST called')
+ logging.info(
+ 'POST called. Expected POST REQUEST: ' + json.dumps(
+ self._expected_requests["post"]) + '\nExpected POST response: ' +
+ json.dumps(self._expected_responses["post"]))
self.send_response(200)
self._set_headers()
- self.wfile.write(self.response_on_get.encode("utf-8"))
+ self.wfile.write(json.dumps(self._expected_responses["post"]).encode("utf-8"))
return
def do_GET(self):
- logging.info('GET called')
+ logging.info(
+ 'GET called. Expected GET REQUEST: ' + json.dumps(
+ self._expected_requests["get"]) + '\nExpected GET response: ' +
+ json.dumps(self._expected_responses["get"]))
self.send_response(200)
self._set_headers()
- self.wfile.write(self.response_on_get.encode("utf-8"))
- return
+ self.wfile.write(json.dumps(self._expected_responses["get"]).encode("utf-8"))
+ return self._expected_responses["get"]
+
+ def do_PUT(self):
+ request_body_json = self._get_request_body()
+ if request_body_json is not None:
+ self._apply_expected_data(request_body_json)
+ logging.info("EXPECTED RESPONSES: " + str(self._expected_responses))
+ logging.info("EXPECTED REQUESTS: " + str(self._expected_requests))
+ response_status = 200
+ else:
+ response_status = 400
+ self.send_response(response_status)
+ self._set_headers()
+
+ def _get_request_body(self):
+ content_len = int(self.headers['Content-Length'], 0)
+ parsed_req_body = None
+ if content_len > 0:
+ body = self.rfile.read(content_len)
+ body_decoded = body.decode('utf8')
+ logging.info("BODY: %s type: %s body decoded: %s type: %s", str(body), type(body), str(body_decoded),
+ type(body_decoded))
+ parsed_req_body = json.loads(body_decoded)
+ return parsed_req_body
+
+ def _apply_expected_data(self, request_body_json):
+ if self.path == '/setResponse':
+ logging.info("IN PUT /setResponse: " + str(request_body_json))
+ print("TYPE: %s and text: %s", type(request_body_json), str(request_body_json))
+ self._expected_responses.update(request_body_json)
+ print("TYPE: %s", type(request_body_json))
+ elif self.path == '/setRequest':
+ logging.info("IN PUT /setRequest: " + str(request_body_json))
+ self._expected_requests.update(request_body_json)
def _set_headers(self):
self.send_header('Content-Type', 'application/json')
self.end_headers()
+
+class JsonFileToDictReader(object):
+
@staticmethod
- def _read_on_get_response():
- with open('so_post_response.json', 'r') as file:
- return file.read()
+ def read_expected_test_data(expected_responses_filename):
+ with open(expected_responses_filename, 'r') as file:
+ return json.load(file)
+
+
+def init_so_simulator():
+ expected_so_requests = JsonFileToDictReader.read_expected_test_data(argv[1])
+ expected_so_responses = JsonFileToDictReader.read_expected_test_data(argv[2])
+ logging.basicConfig(filename='output.log', level=logging.INFO)
+ handler = partial(SOHandler, expected_so_requests, expected_so_responses)
+ handler.protocol_version = "HTTP/1.0"
+ httpd = HTTPServer(('', DEFAULT_PORT), handler)
+ logging.info("serving on: " + str(httpd.socket.getsockname()))
+ httpd.serve_forever()
if __name__ == '__main__':
- logging.basicConfig(filename='output.log', level=logging.INFO)
- SOHandler.protocol_version = "HTTP/1.0"
-
- httpd = HTTPServer(('', DEFAULT_PORT), SOHandler)
- logging.info("serving on: " + str(httpd.socket.getsockname()))
- httpd.serve_forever()
+ init_so_simulator()
diff --git a/tests/vid/resources/simulators/test_data_assets/expected_so_requests.json b/tests/vid/resources/simulators/test_data_assets/expected_so_requests.json
new file mode 100644
index 0000000..214fa6f
--- /dev/null
+++ b/tests/vid/resources/simulators/test_data_assets/expected_so_requests.json
@@ -0,0 +1,57 @@
+{
+ "get": {
+ "get_request": "accepted"
+ },
+ "post": {
+ "requestDetails": {
+ "cloudConfiguration": {
+ "lcpCloudRegionId": "RegionOne",
+ "tenantId": "982c540f6e69488eb6be5664255e00c0"
+ },
+ "modelInfo": {
+ "modelInvariantId": "41b3c314-dfab-4501-9c5e-1c9fe5d8e151",
+ "modelName": "SoWs1..base_ws..module-0",
+ "modelType": "vfModule",
+ "modelVersion": "1",
+ "modelVersionId": "7ea96ae9-9eac-4eaa-882e-077478a6c44a"
+ },
+ "relatedInstanceList": [{
+ "relatedInstance": {
+ "instanceId": "0d8a98d8-d7ca-4c26-b7ab-81d3729e3b6c",
+ "modelInfo": {
+ "modelInvariantId": "a4413616-cf96-4615-a94e-0dc5a6a65430",
+ "modelName": "SC_WS_SW_2",
+ "modelType": "service",
+ "modelVersion": "3.0",
+ "modelVersionId": "0fdaaf44-3c6c-4d81-9c57-b2ce7224dbb9"
+ }
+ }
+ },
+ {
+ "relatedInstance": {
+ "instanceId": "61c19619-2714-46f8-90c9-39734e4f545f",
+ "modelInfo": {
+ "modelCustomizationName": "SO_WS_1 0",
+ "modelInvariantId": "3b2c9dcb-6ef8-4c3c-8d5b-43d5776f7110",
+ "modelName": "SO_WS_1",
+ "modelType": "vnf",
+ "modelVersion": "1.0",
+ "modelVersionId": "0fdaaf44-3c6c-4d81-9c57-b2ce7224dbb9"
+ }
+ }
+ }
+ ],
+ "requestInfo": {
+ "source": "VID",
+ "suppressRollback": "false",
+ "requestorId": "az2016",
+ "instanceName": "SC_WS_VNF_1_2"
+ },
+ "requestParameters": {
+ "controllerType": "SDNC",
+ "userParams": []
+ }
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/vid/resources/simulators/test_data_assets/expected_so_responses.json b/tests/vid/resources/simulators/test_data_assets/expected_so_responses.json
new file mode 100644
index 0000000..1b73522
--- /dev/null
+++ b/tests/vid/resources/simulators/test_data_assets/expected_so_responses.json
@@ -0,0 +1,14 @@
+{
+ "get": {
+ "get_response": "accepted"
+ },
+ "post": {
+ "status": 202,
+ "entity": {
+ "requestReferences": {
+ "instanceId": "fffcbb6c-1983-42df-9ca8-89ae8b3a46c1",
+ "requestId": "b2197d7e-3a7d-410e-82ba-7b7e8191bc46"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/vid/resources/simulators/test_data_assets/so_action_response.json b/tests/vid/resources/simulators/test_data_assets/so_action_response.json
new file mode 100644
index 0000000..391231d
--- /dev/null
+++ b/tests/vid/resources/simulators/test_data_assets/so_action_response.json
@@ -0,0 +1,9 @@
+{
+ "status": 202,
+ "entity": {
+ "requestReferences": {
+ "instanceId": "fffcbb6c-1983-42df-9ca8-89ae8b3a46c1",
+ "requestId": "b2197d7e-3a7d-410e-82ba-7b7e8191bc46"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/vid/resources/simulators/test_data_assets/so_response_for_invalid_request.json b/tests/vid/resources/simulators/test_data_assets/so_response_for_invalid_request.json
new file mode 100644
index 0000000..477de14
--- /dev/null
+++ b/tests/vid/resources/simulators/test_data_assets/so_response_for_invalid_request.json
@@ -0,0 +1,11 @@
+{
+ "status": 400,
+ "entity": {
+ "requestError": {
+ "serviceException": {
+ "messageId": "SVC0002",
+ "text": "Error mapping request: Unrecognized field \"configurationParameters\" (class org.onap.so.serviceinstancebeans.ServiceInstancesRequest), not marked as ignorable (8 known properties: \"configurationId\", \"requestDetails\", \"serviceInstanceId\", \"vfModuleInstanceId\", \"correlationId\", \"vnfInstanceId\", \"volumeGroupInstanceId\", \"networkInstanceId\"])\n at [Source: {\"configurationParameters\":[],\"requestDetails\":{\"cloudConfiguration\":{\"lcpCloudRegionId\":\"RegionOne\",\"tenantId\":\"982c540f6e69488eb6be5664255e00c0\"},\"modelInfo\":{\"modelInvariantId\":\"41b3c314-dfab-4501-9c5e-1c9fe5d8e151\",\"modelName\":\"SoWs1..base_ws..module-0\",\"modelType\":\"vfModule\",\"modelVersion\":\"1\",\"modelVersionId\":\"7ea96ae9-9eac-4eaa-882e-077478a6c44a\"},\"relatedInstanceList\":[{\"relatedInstance\":{\"instanceId\":\"0d8a98d8-d7ca-4c26-b7ab-81d3729e3b6c\",\"modelInfo\":{\"modelInvariantId\":\"a4413616-cf96-4615-a94e-0dc5a6a65430\",\"modelName\":\"SC_WS_SW_2\",\"modelType\":\"service\",\"modelVersion\":\"3.0\",\"modelVersionId\":\"00e0bb964-e687-4439-9a9e-de9cd1ff5367\"}}},{\"relatedInstance\":{\"instanceId\":\"61c19619-2714-46f8-90c9-39734e4f545f\",\"modelInfo\":{\"modelCustomizationName\":\"SO_WS_1 0\",\"modelInvariantId\":\"3b2c9dcb-6ef8-4c3c-8d5b-43d5776f7110\",\"modelName\":\"SO_WS_1\",\"modelType\":\"vnf\",\"modelVersion\":\"1.0\",\"modelVersionId\":\"0fdaaf44-3c6c-4d81-9c57-b2ce7224dbb9\"}}}],\"requestInfo\":{\"source\":\"VID\",\"suppressRollback\":false,\"requestorId\":\"az2016\",\"instanceName\":\"SC_WS_VNF_1_2\"},\"requestParameters\":{\"controllerType\":\"SDNC\",\"userParams\":[]}}}; line: 1, column: 29] (through reference chain: org.onap.so.serviceinstancebeans.ServiceInstancesRequest[\"configurationParameters\"])"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/vid/resources/simulators/test_data_assets/vid_create_vfmodule_request.json b/tests/vid/resources/simulators/test_data_assets/vid_create_vfmodule_request.json
new file mode 100644
index 0000000..7f0b6eb
--- /dev/null
+++ b/tests/vid/resources/simulators/test_data_assets/vid_create_vfmodule_request.json
@@ -0,0 +1,52 @@
+{
+ "requestDetails": {
+ "cloudConfiguration": {
+ "lcpCloudRegionId": "RegionOne",
+ "tenantId": "982c540f6e69488eb6be5664255e00c0"
+ },
+ "modelInfo": {
+ "modelInvariantId": "41b3c314-dfab-4501-9c5e-1c9fe5d8e151",
+ "modelName": "SoWs1..base_ws..module-0",
+ "modelType": "vfModule",
+ "modelVersion": "1",
+ "modelVersionId": "7ea96ae9-9eac-4eaa-882e-077478a6c44a"
+ },
+ "relatedInstanceList": [
+ {
+ "relatedInstance": {
+ "instanceId": "0d8a98d8-d7ca-4c26-b7ab-81d3729e3b6c",
+ "modelInfo": {
+ "modelInvariantId": "a4413616-cf96-4615-a94e-0dc5a6a65430",
+ "modelName": "SC_WS_SW_2",
+ "modelType": "service",
+ "modelVersion": "3.0",
+ "modelVersionId": "0fdaaf44-3c6c-4d81-9c57-b2ce7224dbb9"
+ }
+ }
+ },
+ {
+ "relatedInstance": {
+ "instanceId": "61c19619-2714-46f8-90c9-39734e4f545f",
+ "modelInfo": {
+ "modelCustomizationName": "SO_WS_1 0",
+ "modelInvariantId": "3b2c9dcb-6ef8-4c3c-8d5b-43d5776f7110",
+ "modelName": "SO_WS_1",
+ "modelType": "vnf",
+ "modelVersion": "1.0",
+ "modelVersionId": "0fdaaf44-3c6c-4d81-9c57-b2ce7224dbb9"
+ }
+ }
+ }
+ ],
+ "requestInfo": {
+ "source": "VID",
+ "suppressRollback": "false",
+ "requestorId": "az2016",
+ "instanceName": "SC_WS_VNF_1_2"
+ },
+ "requestParameters": {
+ "controllerType": "SDNC",
+ "userParams": []
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/vid/resources/simulators/test_data_assets/vid_scaleout_request.json b/tests/vid/resources/simulators/test_data_assets/vid_scaleout_request.json
new file mode 100644
index 0000000..70761ec
--- /dev/null
+++ b/tests/vid/resources/simulators/test_data_assets/vid_scaleout_request.json
@@ -0,0 +1,23 @@
+{
+ "requestDetails":[
+ {
+ "vnfName":"ws-test-0310-8",
+ "vnfInstanceId":"980fe98e-47f8-4164-862d-4ebb026cec75",
+ "relatedInstanceList":[
+ {
+ "relatedInstance":{
+ "instanceId":"fd84f066-ea75-4b23-acd0-3cf3fce7a99b",
+ "modelInfo":{
+ "modelVersionId":"0e0bb964-e687-4439-9a9e-de9cd1ff5367",
+ "modelName":"ws-service",
+ "modelInvariantId":"734f0952-6678-44e7-8918-f9aa4694b687",
+ "modelType":"service",
+ "modelVersion":"1.0"
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "requestType":"VNF Scale Out"
+}
\ No newline at end of file
diff --git a/tests/vid/scaleOut/__init__.robot b/tests/vid/scaleOut/__init__.robot
new file mode 100644
index 0000000..540b7ca
--- /dev/null
+++ b/tests/vid/scaleOut/__init__.robot
@@ -0,0 +1,2 @@
+*** Settings ***
+Documentation VID - ScaleOut use case
diff --git a/tests/vid/scaleOut/scaleout_workflow_test.robot b/tests/vid/scaleOut/scaleout_workflow_test.robot
new file mode 100644
index 0000000..3b28c58
--- /dev/null
+++ b/tests/vid/scaleOut/scaleout_workflow_test.robot
@@ -0,0 +1,32 @@
+*** Settings ***
+Library RequestsLibrary
+Library OperatingSystem
+Library json
+Resource ../../common.robot
+Resource ../resources/keywords/scaleout_vid_keywords.robot
+
+
+*** Variables ***
+${VID_TEST_ASSET_DIR} %{WORKSPACE}/tests/vid/resources/simulators/test_data_assets
+${EXPECTED_SO_RESPONSES_FILEPATH} ${VID_TEST_ASSET_DIR}/expected_so_responses.json
+${EXPECTED_SO_REQUESTS_FILEPATH} ${VID_TEST_ASSET_DIR}/expected_so_requests.json
+${SO_SIMULATOR_BASE_URL} http://${SO_SIMULATOR_IP}:8443
+${VID_HTTP_BASE_URL} http://${VID_IP}:8080
+${VID_SCALEOUT_ENDPOINT} vid/change-management/workflow/ws-test-0310-8
+${VALID_SCALEOUT_REQ_FILEPATH} ${VID_TEST_ASSET_DIR}/vid_scaleout_request.json
+${VALID_SCALEOUT_RESP_FILEPATH} ${VID_TEST_ASSET_DIR}/so_action_response.json
+
+
+*** Test Cases ***
+Triggering scaleout workflow operation succeeds
+ Setup Expected Data In SO Simulator ${EXPECTED_SO_RESPONSES_FILEPATH} ${SO_SIMULATOR_BASE_URL} setResponse
+ ${soExpectedJsonResp}= json_from_file ${VALID_SCALEOUT_RESP_FILEPATH}
+ ${vidRequest}= json_from_file ${VALID_SCALEOUT_REQ_FILEPATH}
+ ${headers}= Create Dictionary Content-Type=application/json
+ ${session}= Create Session alias=vid url=${VID_HTTP_BASE_URL} headers=${headers}
+ ${resp}= Post Request vid uri=/${VID_SCALEOUT_ENDPOINT} data=${vidRequest} headers=${headers}
+ Should Be Equal As Strings ${resp.status_code} 200
+ Dictionaries Should Be Equal ${soExpectedJsonResp} ${resp.json()['entity']}
+
+
+