Rapp catalogue enhanced 44/9544/1
authorhalil.cakal <halil.cakal@est.tech>
Mon, 17 Oct 2022 08:20:52 +0000 (09:20 +0100)
committerhalil.cakal <halil.cakal@est.tech>
Wed, 9 Nov 2022 09:00:24 +0000 (09:00 +0000)
Rapp LCM

Issue-ID: NONRTRIC-800
Change-Id: I6fe48b5208baf61d6920a095a2980b34ac539a1a
Signed-off-by: halil.cakal <halil.cakal@est.tech>
32 files changed:
.gitignore
catalogue-enhanced-test/basic_test.sh [new file with mode: 0755]
catalogue-enhanced-test/build_and_start.sh [new file with mode: 0755]
catalogue-enhanced-test/common/compare_json.py [new file with mode: 0644]
catalogue-enhanced-test/common/test_common.sh [new file with mode: 0755]
catalogue-enhanced-test/jsonfiles/rapp1.json [new file with mode: 0644]
catalogue-enhanced-test/jsonfiles/rapp1_invoker_apilist.json [new file with mode: 0644]
catalogue-enhanced-test/jsonfiles/rapp1_provider_apilist.json [new file with mode: 0644]
catalogue-enhanced-test/jsonfiles/rapp2.json [new file with mode: 0644]
catalogue-enhanced-test/jsonfiles/rapp2_invoker_apilist.json [new file with mode: 0644]
catalogue-enhanced-test/jsonfiles/rapp2_provider_apilist.json [new file with mode: 0644]
catalogue-enhanced-test/jsonfiles/tosca_meta.json [new file with mode: 0644]
catalogue-enhanced/.gitignore [new file with mode: 0644]
catalogue-enhanced/Dockerfile [new file with mode: 0644]
catalogue-enhanced/README.md [new file with mode: 0644]
catalogue-enhanced/api/rapp-catalogue-enhanced.yaml [new file with mode: 0644]
catalogue-enhanced/certificate/cert.crt [new file with mode: 0644]
catalogue-enhanced/certificate/generate_cert_and_key.sh [new file with mode: 0755]
catalogue-enhanced/certificate/key.crt [new file with mode: 0644]
catalogue-enhanced/certificate/pass [new file with mode: 0644]
catalogue-enhanced/csar/rapp1/rapp1.csar [new file with mode: 0755]
catalogue-enhanced/nginx.conf [new file with mode: 0644]
catalogue-enhanced/src/catalogue_manager.py [new file with mode: 0644]
catalogue-enhanced/src/main.py [new file with mode: 0644]
catalogue-enhanced/src/maincommon.py [new file with mode: 0644]
catalogue-enhanced/src/payload_logging.py [new file with mode: 0644]
catalogue-enhanced/src/start.sh [new file with mode: 0644]
catalogue-enhanced/src/util.py [new file with mode: 0644]
catalogue-enhanced/src/var_declaration.py [new file with mode: 0644]
catalogue-enhanced/tests/test_catalogue_enhanced.py [new file with mode: 0644]
catalogue-enhanced/tests/unittest_setup.py [new file with mode: 0644]
catalogue-enhanced/tox.ini [new file with mode: 0644]

index be1dfec..2b6a724 100644 (file)
@@ -24,6 +24,7 @@ coverage.*
 .swagger-codegen-ignore
 .swagger-codegen/
 api/README.md
+*.pyc
 
 # Python virtual env
 venv/
diff --git a/catalogue-enhanced-test/basic_test.sh b/catalogue-enhanced-test/basic_test.sh
new file mode 100755 (executable)
index 0000000..8cad83a
--- /dev/null
@@ -0,0 +1,177 @@
+#!/bin/bash
+
+#  ============LICENSE_START===============================================
+#  Copyright (C) 2022 Nordix Foundation. 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=================================================
+#
+
+# Script for basic test of the rapp catalogue enhanced container
+# Run the basic_test with either nonsecure or secure parameter
+
+print_usage() {
+    echo "Usage: ./basic_test.sh nonsecure|secure "
+    exit 1
+}
+
+if [ $# -ne 1 ]; then
+    print_usage
+fi
+if [ "$1" != "nonsecure" ] && [ "$1" != "secure" ]; then
+    print_usage
+fi
+
+if [ $1 == "nonsecure" ]; then
+    #Default http port for the rapp catalogue enhanced
+    PORT=9096
+    # Set http protocol
+    HTTPX="http"
+else
+    #Default https port for the rapp catalogue enhanced
+    PORT=9196
+    # Set https protocol
+    HTTPX="https"
+fi
+
+. ./common/test_common.sh
+
+echo "=== Rapp catalogue enhanced health check ==="
+RESULT="OK"
+do_curl GET '/' 200
+
+echo "=== Reset rapp catalogue enhanced ==="
+RESULT="All rapp definitions deleted"
+do_curl POST '/deleteall' 200
+
+echo "=== API: Query all rapp ids, shall be empty array ==="
+RESULT="json:[]"
+do_curl GET '/rappcatalogue' 200
+
+echo "=== API: Query rapp by rapp id , rapp rapp1 not found ==="
+RESULT="json:{\"title\": \"The rapp does not exist.\", \"status\": 404, \"instance\": \"rapp1\"}"
+do_curl GET '/rappcatalogue/rapp1' 404
+
+echo "=== API: Register an rapp: rapp1 ==="
+res=$(cat jsonfiles/rapp1.json)
+RESULT="json:$res"
+do_curl PUT  '/rappcatalogue/rapp1' 201 jsonfiles/rapp1.json
+
+echo "=== API: Query all rapp ids, shall contain rapp id rapp1 ==="
+RESULT="json:[ \"rapp1\" ]"
+do_curl GET '/rappcatalogue' 200
+
+echo "=== API: Query rapp by rapp id, rapp rapp1 found ==="
+res=$(cat jsonfiles/rapp1.json)
+RESULT="json:$res"
+do_curl GET '/rappcatalogue/rapp1' 200
+
+echo "=== API: Filter api list by service type and rapp id, service type provider ==="
+res=$(cat jsonfiles/rapp1_provider_apilist.json)
+RESULT="json:$res"
+do_curl GET '/rappcatalogue/rapp1/provider' 200
+
+echo "=== API: Filter api list by service type and rapp id, service type invoker ==="
+res=$(cat jsonfiles/rapp1_invoker_apilist.json)
+RESULT="json:$res"
+do_curl GET '/rappcatalogue/rapp1/invoker' 200
+
+echo "=== API: Delete rapp by rapp id, rapp rapp1 deleted successfully ==="
+RESULT=""
+do_curl DELETE '/rappcatalogue/rapp1' 204
+
+echo "=== API: Query all rapp ids, shall be empty array ==="
+RESULT="json:[]"
+do_curl GET '/rappcatalogue' 200
+
+echo "=== API: Query rapp by rapp id , rapp rapp1 not found ==="
+RESULT="json:{\"title\": \"The rapp does not exist.\", \"status\": 404, \"instance\": \"rapp1\"}"
+do_curl GET '/rappcatalogue/rapp1' 404
+
+echo "=== API: Register an rapp: rapp1 ==="
+res=$(cat jsonfiles/rapp1.json)
+RESULT="json:$res"
+do_curl PUT  '/rappcatalogue/rapp1' 201 jsonfiles/rapp1.json
+
+echo "=== API: Query all rapp ids, shall contain rapp id rapp1 ==="
+RESULT="json:[ \"rapp1\" ]"
+do_curl GET '/rappcatalogue' 200
+
+echo "=== API: Query rapp by rapp id, rapp rapp1 found ==="
+res=$(cat jsonfiles/rapp1.json)
+RESULT="json:$res"
+do_curl GET '/rappcatalogue/rapp1' 200
+
+echo "=== API: Query TOSCA.meta file by rapp_id, TOSCA.meta verified and listed ==="
+res=$(cat jsonfiles/tosca_meta.json)
+RESULT="json:$res"
+do_curl GET '/rappcatalogue/csar/rapp1/toscameta' 200
+
+echo "=== API: Query TOSCA.meta file by rapp_id, rapp does not exist ==="
+RESULT="json:{\"title\": \"The rapp does not exist.\", \"status\": 404, \"instance\": \"rapp2\"}"
+do_curl GET '/rappcatalogue/csar/rapp2/toscameta' 404
+
+echo "=== API: Update an rapp: rapp1 ==="
+res=$(cat jsonfiles/rapp1.json)
+RESULT="json:$res"
+do_curl PUT  '/rappcatalogue/rapp1' 200 jsonfiles/rapp1.json
+
+echo "=== API: Query rapp by rapp id, rapp rapp1 found ==="
+res=$(cat jsonfiles/rapp1.json)
+RESULT="json:$res"
+do_curl GET '/rappcatalogue/rapp1' 200
+
+echo "=== API: Register an rapp: rapp2 ==="
+res=$(cat jsonfiles/rapp2.json)
+RESULT="json:$res"
+do_curl PUT  '/rappcatalogue/rapp2' 201 jsonfiles/rapp2.json
+
+echo "=== API: Query rapp by rapp id, rapp rapp2 found ==="
+res=$(cat jsonfiles/rapp2.json)
+RESULT="json:$res"
+do_curl GET '/rappcatalogue/rapp2' 200
+
+echo "=== API: Filter api list by service type and rapp id, service type provider ==="
+res=$(cat jsonfiles/rapp2_provider_apilist.json)
+RESULT="json:$res"
+do_curl GET '/rappcatalogue/rapp2/provider' 200
+
+echo "=== API: Filter api list by service type and rapp id, service type invoker ==="
+res=$(cat jsonfiles/rapp2_invoker_apilist.json)
+RESULT="json:$res"
+do_curl GET '/rappcatalogue/rapp2/invoker' 200
+
+echo "=== API: Query all rapp ids, shall contain rapp id rapp1 and rapp2 ==="
+RESULT="json:[ \"rapp1\", \"rapp2\" ]"
+do_curl GET '/rappcatalogue' 200
+
+echo "=== API: Delete rapp by rapp id, rapp rapp1 deleted successfully ==="
+RESULT=""
+do_curl DELETE '/rappcatalogue/rapp1' 204
+
+echo "=== API: Query rapp by rapp id , rapp rapp1 not found ==="
+RESULT="json:{\"title\": \"The rapp does not exist.\", \"status\": 404, \"instance\": \"rapp1\"}"
+do_curl GET '/rappcatalogue/rapp1' 404
+
+echo "=== API: Query all rapp ids, shall contain rapp id rapp1 and rapp2 ==="
+RESULT="json:[ \"rapp2\" ]"
+do_curl GET '/rappcatalogue' 200
+
+echo "=== API: Query rapp by rapp id, rapp rapp2 found ==="
+res=$(cat jsonfiles/rapp2.json)
+RESULT="json:$res"
+do_curl GET '/rappcatalogue/rapp2' 200
+
+echo "********************"
+echo "*** All tests ok ***"
+echo "********************"
diff --git a/catalogue-enhanced-test/build_and_start.sh b/catalogue-enhanced-test/build_and_start.sh
new file mode 100755 (executable)
index 0000000..351f1df
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+#  ============LICENSE_START===============================================
+#  Copyright (C) 2022 Nordix Foundation. 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=================================================
+#
+
+# Script to build and start the rapp catalogue enhanced container
+# Make sure to run container including args as is this script
+
+print_usage() {
+    echo "Usage: ./build_and_start.sh"
+    exit 1
+}
+
+if [ $# -ge 1 ]; then
+    print_usage
+fi
+
+echo "Building rapp catalogue image..."
+cd ../catalogue-enhanced/
+
+#Build the image
+docker build -t rapp_catalogue_enhanced_image .
+
+docker stop rappcatalogueenhanced > /dev/null 2>&1
+docker rm -f rappcatalogueenhanced > /dev/null 2>&1
+
+echo "Starting rapp catalogue enhanced..."
+echo "PWD path: "$PWD
+
+#Run the container in interactive mode with host networking driver which allows docker to access localhost, unsecure port 9096, secure port 9196
+docker run --network host --rm -it -p 9096:9096 -p 9196:9196 -e ALLOW_HTTP=true --volume "$PWD/certificate:/usr/src/app/cert" --name rappcatalogueenhanced rapp_catalogue_enhanced_image
diff --git a/catalogue-enhanced-test/common/compare_json.py b/catalogue-enhanced-test/common/compare_json.py
new file mode 100644 (file)
index 0000000..cbebd74
--- /dev/null
@@ -0,0 +1,126 @@
+
+#  ============LICENSE_START===============================================
+#  Copyright (C) 2020-2022 Nordix Foundation. 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=================================================
+#
+
+# This script compare two jsons for eqaulity, taken into account that the parameter values
+# marked with '????' are not checked (only the parameter name need to exist)
+# Example of target json with '????'
+# [
+#   {
+#     "callbackUrl": "????",
+#     "keepAliveIntervalSeconds": "????",
+#     "serviceName": "serv2",
+#     "timeSinceLastActivitySeconds": "????"
+#   },
+#   {
+#     "callbackUrl": "????",
+#     "keepAliveIntervalSeconds": "????",
+#     "serviceName": "serv1",
+#     "timeSinceLastActivitySeconds": "????"
+#   }
+#]
+
+import os
+import json
+import sys
+
+# # Helper function to compare two json list.
+# # Returns true for equal, false for not equal
+def compare_json_list(list1, list2):
+    if (list1.__len__() != list2.__len__()):
+        return False
+
+    for l in list1:
+        found = False
+        for m in list2:
+            res = compare_json(l, m)
+            if (res):
+                found = True
+                break
+
+        if (not found):
+            return False
+
+    return True
+
+# Deep compare of two json obects
+# If a parameter value in the target json is set to '????' then the result json value is not checked for the that parameter
+# Return true for equal json, false for not equal json
+def compare_json(obj1, obj2):
+    if isinstance(obj1, list):
+        if (not isinstance(obj2, list)):
+            return False
+        return compare_json_list(obj1, obj2)
+    elif (isinstance(obj1, dict)):
+        if (not isinstance(obj2, dict)):
+            return False
+        exp = set(obj2.keys()) == set(obj1.keys())
+        if (not exp):
+            return False
+        for k in obj1.keys():
+            val1 = obj1.get(k)
+            val2 = obj2.get(k)
+            if isinstance(val1, list):
+                if (not compare_json_list(val1, val2)):
+                    return False
+            elif isinstance(val1, dict):
+                if (not compare_json(val1, val2)):
+                    return False
+            else:
+                #Do not check parameter values marked with '????'
+                if ((val1 != "????") and (val2 != val1)) and ((val2 != "????") and (val2 != val1)):
+                    return False
+    else:
+        return obj1 == obj2
+
+    return True
+
+
+# Compare two json object. Returns true if equal, false if not equal
+# This function is intended to be used from other python scipts using json object (instead of files)
+def compare(target, result):
+    try:
+        res1=compare_json(target, result)
+        res2=compare_json(target, result)
+        if (res1 and res2):
+            return True
+        else:
+            return False
+    except Exception:
+        return False
+
+if __name__ == '__main__':
+    try:
+        #Read the input file and compare the two json (target->result)
+        jsonTarget = json.loads(sys.argv[1])
+        jsonResult = json.loads(sys.argv[2])
+        res1=compare_json(jsonTarget, jsonResult)
+
+        #Read the json again (in case the previous calls has re-arranged the jsons)
+        jsonTarget = json.loads(sys.argv[1])
+        jsonResult = json.loads(sys.argv[2])
+        #Compare the opposite order (result->target) to catch special duplicate json key cases
+        res2=compare_json(jsonResult, jsonTarget)
+
+        if (res1 and res2):
+            print (0)
+        else:
+            print (1)
+
+    except Exception:
+        print (1)
+    sys.exit()
diff --git a/catalogue-enhanced-test/common/test_common.sh b/catalogue-enhanced-test/common/test_common.sh
new file mode 100755 (executable)
index 0000000..69f45e6
--- /dev/null
@@ -0,0 +1,80 @@
+#!/bin/bash
+
+#  ============LICENSE_START===============================================
+#  Copyright (C) 2020-2022 Nordix Foundation. 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=================================================
+#
+
+# Function to execute curl and compare + print result
+
+# Note: Env var PORT must be set to the intended port number
+# Notre Env var HTTPX must be set to either 'http' or 'https'
+
+#args: <http-operation> <url> <response-code> [file]
+#Expects the env $RESULT to contain the expected RESULT.
+#If json, the RESULT shall begin with 'json:'.
+#Any json parameter with unknown value shall be given as "????" to skip checking the value.
+#The requestid parameter is being introduced in the fifth order.
+do_curl() {
+    if [ $# -lt 3 ]; then
+        echo "Need 3 or more parameters, <http-operation> <url> <response-code> [file]: "$@
+        echo "Exiting test script....."
+        exit 1
+    fi
+    curlstr="curl -X "$1" -skw %{http_code} $HTTPX://localhost:"${PORT}${2}" -H accept:*/*"
+    if [ $# -eq 4 ]; then
+        curlstr=$curlstr" -H Content-Type:application/json --data-binary @"$4
+    fi
+    if [ $# -ge 5 ]; then
+        curlstr=$curlstr" -H Content-Type:application/json --data-binary @"$4" -H requestid:"$5
+    fi
+    echo "  CMD (${BASH_LINENO[0]}):"$curlstr
+    res=$($curlstr)
+    status=${res:${#res}-3}
+    body=${res:0:${#res}-3}
+    if [ $status -ne $3 ]; then
+        echo "  Error status :"$status" Expected status: "$3
+        echo "  Body         :"$body
+        echo "Exiting test script....."
+        exit 1
+    else
+        echo "  OK, code           :"$status"     (Expected)"
+        echo "  Body               :"$body
+        if [ "$RESULT" == "*" ]; then
+            echo "  Body contents not checked"
+        elif [[ "$RESULT" == "json:"* ]]; then
+            result=${RESULT:5:${#RESULT}} #Remove 'json:' from the result string
+            res=$(python ./common/compare_json.py "$result" "$body")
+            if [ $res -eq 0 ]; then
+                echo "  Expected json body :"$result
+                echo "  Body as expected"
+            else
+                echo "  Expected json body :"$result
+                echo "Exiting....."
+                exit 1
+            fi
+        else
+            body="$(echo $body | tr -d '\n' )"
+            if [ "$RESULT" == "$body" ]; then
+                echo "  Expected body      :"$RESULT
+                echo "  Body as expected"
+            else
+                echo "  Expected body      :"$RESULT
+                echo "Exiting....."
+                exit 1
+            fi
+        fi
+    fi
+}
diff --git a/catalogue-enhanced-test/jsonfiles/rapp1.json b/catalogue-enhanced-test/jsonfiles/rapp1.json
new file mode 100644 (file)
index 0000000..c37765e
--- /dev/null
@@ -0,0 +1,24 @@
+{
+  "supplierName": "ESY",
+  "rappSchema": {
+    "version": "1.0.0",
+    "display_name": "orufhrecovery",
+    "description": "O-RU Front-Haul Recovery",
+    "csarUrl": "http://localhost:9190/rapp1/csar"
+  },
+  "apiList": [
+    {
+      "serviceType": "invoker",
+      "apiId": "88918c1ccdb75a2da0f284572b66ec2c"
+    },
+    {
+      "serviceType": "provider",
+      "apiId": "84111c1ccdb75a2da0f284572b66e854",
+      "aefProfiles": [
+        {
+          "aefId": "dc76679f940d56c86987f0ba7973df0f"
+        }
+      ]
+    }
+  ]
+}
diff --git a/catalogue-enhanced-test/jsonfiles/rapp1_invoker_apilist.json b/catalogue-enhanced-test/jsonfiles/rapp1_invoker_apilist.json
new file mode 100644 (file)
index 0000000..b666eba
--- /dev/null
@@ -0,0 +1,6 @@
+[
+    {
+        "serviceType": "invoker",
+        "apiId": "88918c1ccdb75a2da0f284572b66ec2c"
+    }
+]
diff --git a/catalogue-enhanced-test/jsonfiles/rapp1_provider_apilist.json b/catalogue-enhanced-test/jsonfiles/rapp1_provider_apilist.json
new file mode 100644 (file)
index 0000000..750501a
--- /dev/null
@@ -0,0 +1,11 @@
+[
+    {
+      "serviceType": "provider",
+      "apiId": "84111c1ccdb75a2da0f284572b66e854",
+      "aefProfiles": [
+        {
+          "aefId": "dc76679f940d56c86987f0ba7973df0f"
+        }
+      ]
+    }
+]
diff --git a/catalogue-enhanced-test/jsonfiles/rapp2.json b/catalogue-enhanced-test/jsonfiles/rapp2.json
new file mode 100644 (file)
index 0000000..25bbb1b
--- /dev/null
@@ -0,0 +1,24 @@
+{
+  "supplierName": "ESY",
+  "rappSchema": {
+    "version": "1.0.0",
+    "display_name": "ransliceassurance",
+    "description": "Re-prioritize a RAN slice’s radio resource allocation priority if sufficient throughput cannot be maintained",
+    "csarUrl": "http://localhost:9190/rapp2/csar"
+  },
+  "apiList": [
+    {
+      "serviceType": "invoker",
+      "apiId": "88918c1ccdb75a2da0f284572b66ec2c"
+    },
+    {
+      "serviceType": "provider",
+      "apiId": "84111c1ccdb75a2da0f284572b66e854",
+      "aefProfiles": [
+        {
+          "aefId": "dc76679f940d56c86987f0ba7973df0f"
+        }
+      ]
+    }
+  ]
+}
diff --git a/catalogue-enhanced-test/jsonfiles/rapp2_invoker_apilist.json b/catalogue-enhanced-test/jsonfiles/rapp2_invoker_apilist.json
new file mode 100644 (file)
index 0000000..b666eba
--- /dev/null
@@ -0,0 +1,6 @@
+[
+    {
+        "serviceType": "invoker",
+        "apiId": "88918c1ccdb75a2da0f284572b66ec2c"
+    }
+]
diff --git a/catalogue-enhanced-test/jsonfiles/rapp2_provider_apilist.json b/catalogue-enhanced-test/jsonfiles/rapp2_provider_apilist.json
new file mode 100644 (file)
index 0000000..750501a
--- /dev/null
@@ -0,0 +1,11 @@
+[
+    {
+      "serviceType": "provider",
+      "apiId": "84111c1ccdb75a2da0f284572b66e854",
+      "aefProfiles": [
+        {
+          "aefId": "dc76679f940d56c86987f0ba7973df0f"
+        }
+      ]
+    }
+]
diff --git a/catalogue-enhanced-test/jsonfiles/tosca_meta.json b/catalogue-enhanced-test/jsonfiles/tosca_meta.json
new file mode 100644 (file)
index 0000000..55bf24a
--- /dev/null
@@ -0,0 +1,11 @@
+[
+    "TOSCA-Meta-File-Version: 1.0",
+    "CSAR-Version: 1.1",
+    "Created-By: Ericsson Software Technology",
+    "Entry-Definitions: Definitions/example_cnf_vnfd.yaml",
+    "ETSI-Entry-Manifest: example_cnf.mf",
+    "ETSI-Entry-Change-Log: Artifacts/ChangeLog.txt",
+    "ETSI-Entry-Tests: Artifacts/Tests/",
+    "ETSI-Entry-Licenses: Artifacts/Licenses",
+    "ETSI-Entry-Certificate: example_cnf.cert"
+]
diff --git a/catalogue-enhanced/.gitignore b/catalogue-enhanced/.gitignore
new file mode 100644 (file)
index 0000000..00f2c95
--- /dev/null
@@ -0,0 +1,16 @@
+# Documentation
+.idea/
+.tox
+docs/_build/
+.DS_STORE
+
+# IDE
+.project
+.vscode
+
+.coverage
+coverage.xml
+htmlcov/
+
+# Python virtual env
+venv/
diff --git a/catalogue-enhanced/Dockerfile b/catalogue-enhanced/Dockerfile
new file mode 100644 (file)
index 0000000..390dcc5
--- /dev/null
@@ -0,0 +1,48 @@
+#  ============LICENSE_START===============================================
+#  Copyright (C) 2022 Nordix Foundation. 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=================================================
+#
+
+FROM python:3.8-slim-buster
+
+RUN pip install connexion[swagger-ui]
+
+#install nginx and curl
+RUN apt-get update && apt-get install -y nginx=1.14.* nginx-extras curl
+
+WORKDIR /usr/src/app
+
+COPY api api
+COPY nginx.conf nginx.conf
+COPY certificate /usr/src/app/cert
+COPY src src
+COPY csar csar
+
+ARG user=nonrtric
+ARG group=nonrtric
+
+RUN groupadd $user && \
+    useradd -r -g $group $user
+RUN chown -R $user:$group /usr/src/app
+RUN chown -R $user:$group /var/log/nginx
+RUN chown -R $user:$group /var/lib/nginx
+RUN chown -R $user:$group /etc/nginx/conf.d
+RUN touch /var/run/nginx.pid
+RUN chown -R $user:$group /var/run/nginx.pid
+
+USER ${user}
+
+RUN chmod +x src/start.sh
+CMD src/start.sh
diff --git a/catalogue-enhanced/README.md b/catalogue-enhanced/README.md
new file mode 100644 (file)
index 0000000..655d164
--- /dev/null
@@ -0,0 +1,96 @@
+## License
+
+Copyright (C) 2022 Nordix Foundation.
+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.
+
+# O-RAN-SC Non-RT RIC rAPP Catalogue Enhanced
+
+The O-RAN Non-RT RIC rApp Catalogue Enhanced provides an OpenApi 3.0 REST API for services to register/unregister themselves and discover other services.
+
+The O-RAN Non-RT RIC rApp Catalogue Enhanced module supports GET, PUT and DELETE operations (version of the OpenAPI yaml file):
+
+| Yaml file                    |     Version         |
+| -----------------------------|-------------------- |
+| rapp-catalogue-enhanced.yaml |      1.0.0          |
+
+The overall folder structure is (relative to the location of this README file):
+
+| Dir              | Description |
+| ---------------- | ----------- |
+|.                 |Dockerfile and README.md |
+|api               |The OpenApi yaml |
+|src               |Python source code |
+|certificate       |A self-signed certificate and a key |
+
+The application is being implemented in Python programming language.
+
+The rApp Catalogue Enhanced module handles the requests that are defined in the OpenAPI yaml file. All these requests are implemented in the catalogue_manager.py module in the src folder. In addition, a number of utility functions are also supported and implemented by the main.py and payload_logging.py in the source folder.
+
+The section below outlines the supported open api rest-based operations as well as the utility operations.
+
+# Ports and certificates
+
+The rApp Catalogue Enhanced module normally opens the port 9096 for http. If a certificate and a key are provided the module will open port 9196 for https instead. The port 9196 is only opened if a valid certificate and key is found.
+The certificate and key shall be placed in the same directory and the directory shall be mounted to /usr/src/app/cert in the container.
+
+| Port     | Protocol |
+| -------- | ----- |
+| 9096     | http  |
+| 9196     | https |
+
+The directory certificate contains a self-signed cert. Use the script generate_cert_and_key.sh to generate a new certificate and key. The password of the certificate must be set 'test'.
+The same urls are availables on both the http port 9096 and the https port 9196. If using curl and https, the flag -k shall be given to make curl ignore checking the certificate.
+
+# Supported operations in Non-RT RIC rAPP Catalogue Enhanced
+
+For the complete yaml specification, see [OpenAPI.yaml](../api/rapp-catalogue-enhanced.yaml)
+
+URIs for server:
+
+| Function              | Path and parameters |
+| --------------------- | ------------------- |
+|  GET, Query all rapp ids | localhost:9096/rappcatalogue |
+|  GET, Query rapp by rapp id | localhost:9096/rappcatalogue/<rapp_id> |
+|  GET, Query API list by rapp id and service type | localhost:9096/rappcatalogue/<rapp_id>/<service_type> |
+|  GET, Validate and query TOSCA.meta file content by rapp id | localhost:9096/rappcatalogue/csar/<rapp_id>/toscameta |
+|  PUT, Register rapp | localhost:9096/rappcatalogue/<rapp_id> |
+|  DELETE, Unregister rapp | localhost:9096/rappcatalogue/<rapp_id> |
+
+
+# Admin functions
+
+| Function              | Path and parameters |
+| --------------------- | ------------------- |
+|  POST, Delete all existing rapp definitions | localhost:9096/deleteall |
+
+
+# Start and test of the Non-RT RIC rAPP Catalogue Enhanced
+
+First, download the plt/rappcatalogue repo on gerrit:
+git clone "https://gerrit.o-ran-sc.org/r/a/nonrtric/plt/rappcatalogue"
+
+Goto the main directory, 'rappcatalogue/catalogue-enhanced-test'.
+This folder contains a script to build and start the rAPP Catalogue Enhanced (as a container in interactive mode), a script for basic testing as well as json files for the test script.
+
+Note that test can be performed both using the nonsecure http port and the secure https port.
+
+Build and start the rApp catalogue enhanced containers:
+
+./build_and_start.sh
+
+This will build and start the container in interactive mode. The built container only resides in the local docker repository.
+Note, the default port is 9096 for http and 9196 for https. When running the rapp catalogue enhanced as a container, the defualt ports can be re-mapped to any port on the localhost.
+
+In a second terminal, go to the same folder and run the basic test script, basic_test.sh nonsecure|secure.
+
+This script runs a number of tests towards the rapp catalogue enhanced to make sure it works properply.
diff --git a/catalogue-enhanced/api/rapp-catalogue-enhanced.yaml b/catalogue-enhanced/api/rapp-catalogue-enhanced.yaml
new file mode 100644 (file)
index 0000000..262bcce
--- /dev/null
@@ -0,0 +1,292 @@
+#  ============LICENSE_START===============================================
+#  Copyright (C) 2022 Nordix Foundation. 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=================================================
+#
+
+openapi: 3.0.0
+info:
+  title: 'Rapp Catalogue API Enhanced'
+  description: 'The Nonrtric Rapp Catalogue defines API specifications to register/unregister/query generic attibutes of Rapp'
+  version: 1.0.0
+servers:
+  - url: /
+paths:
+  '/rappcatalogue':
+    get:
+      operationId: catalogue_manager.query_all_rapp_ids
+      description: 'Query for all rapp identifiers'
+      tags:
+      - All rapp identifiers
+      responses:
+        200:
+          description: 'Array of all rapp identifiers'
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  "$ref": "#/components/schemas/RappId"
+                minItems: 0
+        429:
+          "$ref": "#/components/responses/429-TooManyRequests"
+        503:
+          "$ref": "#/components/responses/503-ServiceUnavailable"
+
+  '/rappcatalogue/{rappid}':
+    parameters:
+      - name: rappid
+        in: path
+        required: true
+        schema:
+          "$ref": "#/components/schemas/RappId"
+    get:
+      operationId: catalogue_manager.query_rapp_by_id
+      description: 'Query for individual rapp definition'
+      tags:
+      - Individual rapp object
+      responses:
+        200:
+          description: 'The rapp definition schemas'
+          content:
+            application/json:
+              schema:
+                "$ref": "#/components/schemas/RappObject"
+        404:
+          "$ref": "#/components/responses/404-NotFound"
+        429:
+          "$ref": "#/components/responses/429-TooManyRequests"
+        503:
+          "$ref": "#/components/responses/503-ServiceUnavailable"
+
+    put:
+      operationId: catalogue_manager.register_rapp
+      description: 'Register, or update, a rapp definition'
+      tags:
+      - Individual rapp object
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              "$ref": "#/components/schemas/RappObject"
+      responses:
+        200:
+          description: 'The rapp has been updated'
+          content:
+            application/json:
+              schema:
+                "$ref": "#/components/schemas/RappObject"
+        201:
+          description: 'The rapp has been registered'
+          content:
+            application/json:
+              schema:
+                "$ref": "#/components/schemas/RappObject"
+        400:
+          "$ref": "#/components/responses/400-BadRequest"
+        408:
+          "$ref": "#/components/responses/408-RequestTimeout"
+        429:
+          "$ref": "#/components/responses/429-TooManyRequests"
+        503:
+          "$ref": "#/components/responses/503-ServiceUnavailable"
+        507:
+          "$ref": "#/components/responses/507-InsufficientStorage"
+
+    delete:
+      operationId: catalogue_manager.unregister_rapp
+      description: 'Unregister a rapp from catalogue'
+      tags:
+      - Individual rapp object
+      responses:
+        204:
+          description: 'The rapp definition has been deleted'
+        404:
+          "$ref": "#/components/responses/404-NotFound"
+        408:
+          "$ref": "#/components/responses/408-RequestTimeout"
+        429:
+          "$ref": "#/components/responses/429-TooManyRequests"
+        503:
+          "$ref": "#/components/responses/503-ServiceUnavailable"
+
+  '/rappcatalogue/{rappid}/{servicetype}':
+    parameters:
+      - name: rappid
+        in: path
+        required: true
+        schema:
+          "$ref": "#/components/schemas/RappId"
+    parameters:
+      - name: servicetype
+        in: path
+        required: true
+        schema:
+          "$ref": "#/components/schemas/ServiceType"
+    get:
+      operationId: catalogue_manager.query_api_list_by_rapp_id_and_service_type
+      description: 'Query for all api list by rapp_id and service_type'
+      tags:
+      - All rapp services
+      responses:
+        200:
+          description: 'Array of all services'
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  "$ref": "#/components/schemas/ServiceTypeObject"
+                minItems: 0
+        429:
+          "$ref": "#/components/responses/429-TooManyRequests"
+        503:
+          "$ref": "#/components/responses/503-ServiceUnavailable"
+
+  '/rappcatalogue/csar/{rappid}/toscameta':
+    parameters:
+      - name: rappid
+        in: path
+        required: true
+        schema:
+          "$ref": "#/components/schemas/RappId"
+
+    get:
+      operationId: catalogue_manager.query_tosca_meta_content_by_rapp_id
+      description: 'Query TOSCA.meta file content by rapp_id'
+      tags:
+      - TOSCA.meta file content
+      responses:
+        200:
+          description: 'TOSCA.meta details'
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  "$ref": "#/components/schemas/ToscaMeta"
+                minItems: 0
+        400:
+          "$ref": "#/components/responses/400-BadRequest"
+        404:
+          "$ref": "#/components/responses/404-NotFound"
+        429:
+          "$ref": "#/components/responses/429-TooManyRequests"
+        503:
+          "$ref": "#/components/responses/503-ServiceUnavailable"
+
+components:
+  schemas:
+
+    RappObject:
+      description: 'A definition of a rapp, i.e. the schemas for a rapp that is being validated'
+      type: object
+      properties:
+        rappSchema:
+          "$ref": "#/components/schemas/JsonSchema"
+      required:
+        - rappSchema
+
+    ProblemDetails:
+      description: 'A problem detail to carry details in a HTTP response according to RFC 7807'
+      type: object
+      properties:
+        type:
+          type: string
+        title:
+          type: string
+        status:
+          type: number
+        detail:
+          type: string
+        instance:
+          type: string
+
+    JsonSchema:
+      description: 'A JSON schema'
+      type: object
+
+    RappId:
+      description: 'Rapp identifier assigned when a rapp is registered'
+      type: string
+
+    ServiceType:
+      description: 'Service identifier differantiate whether service is consumed or produced'
+      type: string
+
+    ServiceTypeObject:
+      description: 'A JSON schema'
+      type: object
+
+    ToscaMeta:
+      description: 'TOSCA.meta file content'
+      type: string
+
+  responses:
+    400-BadRequest:
+      description: 'Object in payload not properly formulated or not related to the method'
+      content:
+        application/problem+json:
+          schema:
+            "$ref": "#/components/schemas/ProblemDetails"
+
+    404-NotFound:
+      description: 'No resource found at the URI'
+      content:
+        application/problem+json:
+          schema:
+            "$ref": "#/components/schemas/ProblemDetails"
+
+    405-MethodNotAllowed:
+      description: 'Method not allowed for the URI'
+      content:
+        application/problem+json:
+          schema:
+            "$ref": "#/components/schemas/ProblemDetails"
+
+    408-RequestTimeout:
+      description: 'Request could not be processed in given amount of time'
+      content:
+        application/problem+json:
+          schema:
+            "$ref": "#/components/schemas/ProblemDetails"
+
+    409-Conflict:
+      description: 'Request could not be processed in the current state of the resource'
+      content:
+        application/problem+json:
+          schema:
+            "$ref": "#/components/schemas/ProblemDetails"
+
+    429-TooManyRequests:
+      description: 'Too many requests have been sent in a given amount of time'
+      content:
+        application/problem+json:
+          schema:
+            "$ref": "#/components/schemas/ProblemDetails"
+
+    503-ServiceUnavailable:
+      description: 'The provider is currently unable to handle the request due to a temporary overload'
+      content:
+        application/problem+json:
+          schema:
+            "$ref": "#/components/schemas/ProblemDetails"
+
+    507-InsufficientStorage:
+      description: 'The method could not be performed on the resource because the provider is unable to store the representation needed to successfully complete the request'
+      content:
+        application/problem+json:
+          schema:
+            "$ref": "#/components/schemas/ProblemDetails"
diff --git a/catalogue-enhanced/certificate/cert.crt b/catalogue-enhanced/certificate/cert.crt
new file mode 100644 (file)
index 0000000..ecdf912
--- /dev/null
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIID/zCCAuegAwIBAgIUORRVFAWtFYuRZ9oXUPVpLioZiSQwDQYJKoZIhvcNAQEL
+BQAwgY0xCzAJBgNVBAYTAklFMREwDwYDVQQIDAhMRUlOU1RFUjESMBAGA1UEBwwJ
+V0VTVE1FQVRIMREwDwYDVQQKDAhFcmljc3NvbjEMMAoGA1UECwwDRVNUMREwDwYD
+VQQDDAhlc3QudGVjaDEjMCEGCSqGSIb3DQEJARYUaGFsaWwuY2FrYWxAZXN0LnRl
+Y2gwIBcNMjIxMDI2MTA1NjUzWhgPMjA1MDAzMTIxMDU2NTNaMIGNMQswCQYDVQQG
+EwJJRTERMA8GA1UECAwITEVJTlNURVIxEjAQBgNVBAcMCVdFU1RNRUFUSDERMA8G
+A1UECgwIRXJpY3Nzb24xDDAKBgNVBAsMA0VTVDERMA8GA1UEAwwIZXN0LnRlY2gx
+IzAhBgkqhkiG9w0BCQEWFGhhbGlsLmNha2FsQGVzdC50ZWNoMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnI+W+Vmzft6ZOOl/ZoImiz8CRsmJbeKhD6f8
+pXoGJC0GbGj3eltvxdYpFAWM57uDfZvbcKPDq4A8jxmBxJniwIAhXaDWoZR3Brz7
+jO0hIsO+vAyDtbhg07EJo1KDf5k68ijF7818lufYcJGZlmsDb11GliAFpCzoKoYi
+ULiZX8UrBybSfFAnTr5M+Akb/zfQWRLTN+YTjirILpBNLHccZOEv+9yMaj0Rl7S8
+r15JQgHuLK1mO9pdN7CQ2wvkCmEZ6WlJeQzENvRtQ5iChbiOwC0YQkjKklW90Y7L
+jS7QFkQ95icme0BUC5iX0yukYiqUPfbLI0/pEKUr6ePB6Xf0dQIDAQABo1MwUTAd
+BgNVHQ4EFgQU9DqxWd9hVVaOaN8QxWNqBgIA5HwwHwYDVR0jBBgwFoAU9DqxWd9h
+VVaOaN8QxWNqBgIA5HwwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC
+AQEAN5J62EhG5Fj0dufcYfwmFYzbRZgiAFLhLiedKGvcU8V1TZrvnDpcjKm31/W3
+3SN+l5IKfQFgW99BO+A6ccqELR7fEt/Uj2yKqpR+cvErkvgdobbbuQAQLduEclyO
+ZrToOavj8gD3S0Zpi+uT3Ftb2FB/fBy/C0RtO8Oaypq0pHPhe2p/KQ3PNDBqs2ES
+P2A36hePvhkKxxc+6Sjfx1Sb8Z3cw57SRYGj7wBM0EVzgaTii/13hyFO1d7QBzWs
+8EDtxLp4xhq0flWSjAjYN8GTm3xZdChSCGLW2/GD2a3aE2EDfx0VWTWUZQrtQJka
+Ze6wYYxKurcS1KcrG02QcJcaOw==
+-----END CERTIFICATE-----
diff --git a/catalogue-enhanced/certificate/generate_cert_and_key.sh b/catalogue-enhanced/certificate/generate_cert_and_key.sh
new file mode 100755 (executable)
index 0000000..b6468df
--- /dev/null
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+#  ============LICENSE_START===============================================
+#  Copyright (C) 2022 Nordix Foundation. 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=================================================
+#
+
+# This will generate a self-signed certificate with password 'test'
+
+SUBJECT="/C=IE/ST=LEINSTER/L=WESTMEATH/O=Ericsson/OU=EST/CN=est.tech/emailAddress=halil.cakal@est.tech"
+PW=test
+echo $PW > pass
+
+openssl req -x509 -passout file:pass -newkey rsa:2048 -keyout key.crt -subj "$SUBJECT" -out cert.crt -days 9999
diff --git a/catalogue-enhanced/certificate/key.crt b/catalogue-enhanced/certificate/key.crt
new file mode 100644 (file)
index 0000000..c5976cf
--- /dev/null
@@ -0,0 +1,30 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIYZlCXDfs28gCAggA
+MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECFDy0yPJFfJeBIIEyMvq3yk4M8kp
+C/mGRY9AEVg49ZOOXmfscfDLfDNm2fF9gtZl9wxRMDjKK5tlAxBfZqw1a5YZfor0
+4S4np6Kq5Rqdq78WVOZlG1a75u6TJ/oSYu6IGT75u5MeypCsWkraCmiO3Ps5Gy3r
+TZcG4s6EoREMT1+yK+WQRfO/AxEPaUT1Xfd1fFwH7nl3cjbJnDsLLhvL2ufvy4im
+JM8JkeeASPWBurl4a61Mg6voO9v4Ai5k7+Cl1+4OvTLbU9RrBAj4wQx667fg1WBV
+hkaelBW7cEn6THK/KkufDmXKyoafkVFb12ErYhyrw/afB0utVYfJrH8Em5HBEys+
+nMkCA5Kcy61ZSugnM12CZcke+uoGOjvkcxwsbMGup4vMmP8E8pIcoFh26KeHU3zz
+V+mI2+PY1SEYc/lYnrU4/Fx7tD1zqq+5iVyhuisy4CgzwH8NTNu8umjC/m2WhLyy
+HoYWyV5qA7JE+e/8+8ss11BMCwh55n2JcwsYj+pWFD3xUwd29CbW0MZyYbX3p4Sr
+i5PReKZ4rmuzq4EPth+cn8q3Uc4gbyS/nXBd0NOHxg2X9f5IuNz83VJcSSZh7UIA
+HmWn3+sxDODO8yLiztT9+UMreW2fA0RAOxD6Ku9e3ByZiNlRDZqWyz8XelZoJPh1
+Y7sUcSi30GsKABq882A+eSsCPDUGs9AVH1/7/BThXq5Kvjn8djtYp2duTl9cOz3F
+Cp33WBL1yE7IBnq3NlYs/C9bJPK5n/uc3Hi0zNWuHrn/AZ222pBuPI+HHA2ooFNM
+LiUehvz0/Xi118hjuxczlgl7zSHjKZrgqB4dM9571q8sA3T/7sfqULmA8rp/xZlM
+1uCDVg3gGmglRWJFr4W4jBfea5W9TLZLc/R98iIMNTY1MLPD9pMdjih0CxcVUAsC
+cDf4lMzaOuluE+snlUIoiDEIdMZ+1aCSYqyVIq2BEyMIMs7hiOEI7zTYGLMVM8wg
+3wtkwVEZ/fNbs2AtpmiOgr+MXa4a1Rjte7FQrqO2hKnNdLyo5d4r1CfbI5ycrJo0
+8Cc7paF12EQ+piMU1XrHew93BkCeeE3t4/fyuFWtKk0SmgqEZHizIxv6H+OrHZfb
+80mvPqA+4do/3EdPx/zKHBP3LYsp2l6ttXo/ji95SAqDPEL0tylD0WAZDzw7pWhA
+v0OoTqg7GQxOqaa2Zty8x2X4nROwqnnu5ZMPg8SA4kxD9kslervOIfUftcDvxgm+
+BEx3RSh+IwXaVfnH4DLNWtQ1OGrSXUUbBZ6wH2zducWggcmbqUHl2xlFAr6/rL2Q
+jr1yPkUooCOSdr8OEYjpiawCZWBH6iMtdFErKppL969b3OcTP4om+vhl/wEWwQ9h
+sgfefcXeejpwOKldHMZVgFMA7PNUy+3vMWz6eL+2zUN3Hy5mLaXnmQXLEGvt9NWJ
+2yugYgR2+L0UhvXBF/xpITc1q5TLKNAbEuIBfj/kP/2+/tfSzN1TPFKMtbVZ0hzW
+xwiv9haAyMRHW5ds9Iu03F3CglHz6Tfax3qyBXkX1Y3Ont5MWabivFkO/kkY9tLg
+Bnx9UPgv5zJVTkUR25Zafe5wKRCEPSkdLtF3v6n3YBC67IUxZo9hNfMsuV/YApvv
+Uqa1+3WrkRiTghW3LL/V6g==
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/catalogue-enhanced/certificate/pass b/catalogue-enhanced/certificate/pass
new file mode 100644 (file)
index 0000000..9daeafb
--- /dev/null
@@ -0,0 +1 @@
+test
diff --git a/catalogue-enhanced/csar/rapp1/rapp1.csar b/catalogue-enhanced/csar/rapp1/rapp1.csar
new file mode 100755 (executable)
index 0000000..54c3be2
Binary files /dev/null and b/catalogue-enhanced/csar/rapp1/rapp1.csar differ
diff --git a/catalogue-enhanced/nginx.conf b/catalogue-enhanced/nginx.conf
new file mode 100644 (file)
index 0000000..408ddec
--- /dev/null
@@ -0,0 +1,110 @@
+#  ============LICENSE_START===============================================
+#  Copyright (C) 2022 Nordix Foundation. 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=================================================
+#
+
+# user www-data;
+worker_processes auto;
+pid /run/nginx.pid;
+include /etc/nginx/modules-enabled/*.conf;
+
+env ALLOW_HTTP;
+
+events {
+    worker_connections 768;
+    # multi_accept on;
+}
+
+http {
+
+    ##
+    # Basic Settings
+    ##
+
+    sendfile on;
+    tcp_nopush on;
+    tcp_nodelay on;
+    keepalive_timeout 65;
+    types_hash_max_size 2048;
+    # server_tokens off;
+
+    # server_names_hash_bucket_size 64;
+    # server_name_in_redirect off;
+
+    include /etc/nginx/mime.types;
+    default_type application/octet-stream;
+
+    perl_set $allow_http 'sub { return $ENV{"ALLOW_HTTP"}; }';
+
+    server { # simple reverse-proxy
+       listen      9096;
+        listen      [::]:9096;
+        server_name  localhost;
+       if ($allow_http != true) {
+           return 444;
+       }
+
+       # serve dynamic requests
+        location / {
+            proxy_set_header   Host                 $host;
+            proxy_set_header   X-Real-IP            $remote_addr;
+            proxy_set_header   X-Forwarded-For      $proxy_add_x_forwarded_for;
+            proxy_pass      http://localhost:9696;
+        }
+    }
+
+    server { # simple reverse-proxy
+        listen      9196 ssl;
+        listen      [::]:9196 ssl;
+        server_name  localhost;
+        ssl_certificate     /usr/src/app/cert/cert.crt;
+        ssl_certificate_key /usr/src/app/cert/key.crt;
+        ssl_password_file   /usr/src/app/cert/pass;
+
+        # serve dynamic requests
+        location / {
+            proxy_set_header   Host                 $host;
+            proxy_set_header   X-Real-IP            $remote_addr;
+            proxy_set_header   X-Forwarded-For      $proxy_add_x_forwarded_for;
+            proxy_pass      http://localhost:9696;
+        }
+    }
+    ##
+    # SSL Settings
+    ##
+
+    ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
+    ssl_prefer_server_ciphers on;
+
+    ##
+    # Logging Settings
+    ##
+
+    access_log /var/log/nginx/access.log;
+    error_log /var/log/nginx/error.log;
+
+    ##
+    # Gzip Settings
+    ##
+
+    gzip on;
+
+    # gzip_vary on;
+    # gzip_proxied any;
+    # gzip_comp_level 6;
+    # gzip_buffers 16 8k;
+    # gzip_http_version 1.1;
+    # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
+}
diff --git a/catalogue-enhanced/src/catalogue_manager.py b/catalogue-enhanced/src/catalogue_manager.py
new file mode 100644 (file)
index 0000000..a318303
--- /dev/null
@@ -0,0 +1,215 @@
+#  ============LICENSE_START===============================================
+#  Copyright (C) 2022 Nordix Foundation. 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 os
+import json
+
+from flask import request, Response
+from jsonschema import validate
+from var_declaration import rapp_registry
+from zipfile import ZipFile
+from io import TextIOWrapper
+from util import ToscametaFormatChecker
+
+# Constsants
+APPL_JSON='application/json'
+TEXT_PLAIN='text/plain'
+APPL_PROB_JSON='application/problem+json'
+
+# API Function: Query for all rapp identifiers
+def query_all_rapp_ids():
+
+  res = list(rapp_registry.keys())
+  return (res, 200)
+
+
+# API Function: Get a rapp definition
+def query_rapp_by_id(rappid):
+
+  rapp_id = str(rappid)
+
+  if (rapp_id not in rapp_registry.keys()):
+    pjson=create_problem_json(None, "The rapp does not exist.", 404, None, rapp_id)
+    return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
+
+  return Response(json.dumps(rapp_registry[rapp_id]), 200, mimetype=APPL_JSON)
+
+
+# API Function: Register, or update, a rapp definition
+def register_rapp(rappid):
+
+  rapp_id=str(rappid)
+
+  try:
+    data = request.data
+    data = json.loads(data)
+  except Exception:
+    pjson=create_problem_json(None, "The rapp definition is corrupt or missing.", 400, None, rapp_id)
+    return Response(json.dumps(pjson), 400, mimetype=APPL_PROB_JSON)
+
+  return_code = 201
+  if rapp_id in rapp_registry.keys():
+    return_code = 200
+
+  # Register or update rapp definition
+  rapp_registry[rapp_id] = data
+
+  return Response(json.dumps(data), return_code, mimetype=APPL_JSON)
+
+
+# API Function: Unregister a rapp from catalogue
+def unregister_rapp(rappid):
+
+  rapp_id = str(rappid)
+
+  if (rapp_id not in rapp_registry.keys()):
+    pjson = create_problem_json(None, "The rapp definition does not exist.", 404, None, rapp_id)
+    return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
+
+  # Delete rapp definition
+  del rapp_registry[rapp_id]
+
+  return Response('', 204, mimetype=APPL_JSON)
+
+# API Function: Query api list by rapp_id and service_type: produced or consumed
+def query_api_list_by_rapp_id_and_service_type(rappid, servicetype):
+
+  rapp_id = str(rappid)
+  service_type = str(servicetype)
+
+  if (rapp_id in rapp_registry.keys()):
+
+    rapp_definition = rapp_registry[rapp_id]
+    try:
+      arr_api_list = rapp_definition['apiList']
+      arr_filtered_api_list = [arr_item for arr_item in arr_api_list if arr_item['serviceType'] == service_type]
+      return (arr_filtered_api_list, 200)
+    except Exception as err:
+      print('An error occured:', err)
+      pjson=create_problem_json(None, "The rapp definition is corrupt or missing.", 400, None, rapp_id)
+      return Response(json.dumps(pjson), 400, mimetype=APPL_PROB_JSON)
+
+  return ([], 200)
+
+# API Function: Validate and return TOSCA.meta file content
+def query_tosca_meta_content_by_rapp_id(rappid):
+
+  rapp_id = str(rappid)
+
+  if (rapp_id not in rapp_registry.keys()):
+    pjson=create_problem_json(None, "The rapp does not exist.", 404, None, rapp_id)
+    return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
+
+  with open_zip_and_filter('/usr/src/app/csar/rapp1/rapp1.csar') as tosca_file:
+    tosca_meta = []
+    while True:
+      line = tosca_file.readline()  # Get next line from file
+      if not line:  # end of file is reached
+        break
+      else:
+        tosca_meta.append(line.strip())
+
+  print('TOSCA.meta content:', tosca_meta)
+  is_valid = validate_tosca_meta_format(tosca_meta)
+
+  if is_valid == True:
+    content = tosca_meta
+    return Response(json.dumps(content), 200, mimetype=APPL_JSON)
+
+  return ([], 200)
+
+# Helper: Open CSAR zip file and returns TOSCA.meta
+def validate_tosca_meta_format(toscameta):
+
+  if len(toscameta) >= 3:
+    file_tag = split_and_strip(toscameta[0])
+    csar_tag = split_and_strip(toscameta[1])
+    crby_tag = split_and_strip(toscameta[2])
+
+    checker = ToscametaFormatChecker(file_tag, csar_tag, crby_tag)  # util.py: validater
+    result = checker.validate()
+
+    if result == True:  # Log: Validated or NOT
+      print('Validated:', checker)
+    else:
+      print('NOT Validated:', checker)
+    return result
+
+  return False
+
+# Helper: Splits given string by colon and strip
+def split_and_strip(string):
+  result = [x.strip() for x in string.split(':')]
+  return result[0]
+
+# Helper: Open CSAR zip file and returns TOSCA.meta file
+def open_zip_and_filter(filename):
+
+  try:
+    with ZipFile(filename, 'r') as zip_object:
+      file_names = zip_object.namelist()
+      for file_name in file_names:
+        if file_name.endswith('TOSCA.meta'):
+          return TextIOWrapper(zip_object.open(file_name))  # TextIOWrapper: provides buffered text stream
+
+      pjson=create_problem_json(None, "TOSCA.meta file is corrupt or missing.", 400, None, rapp_id)
+      return Response(json.dumps(pjson), 400, mimetype=APPL_PROB_JSON)
+  except Exception as err:
+    print('An error occured:', err)
+    pjson=create_problem_json(None, "The CSAR zip content is corrupt or missing.", 400, None, rapp_id)
+    return Response(json.dumps(pjson), 400, mimetype=APPL_PROB_JSON)
+  finally:
+    zip_object.close()
+
+# Helper: Create a problem json object
+def create_problem_json(type_of, title, status, detail, instance):
+
+  error = {}
+  if type_of is not None:
+    error["type"] = type_of
+  if title is not None:
+    error["title"] = title
+  if status is not None:
+    error["status"] = status
+  if detail is not None:
+    error["detail"] = detail
+  if instance is not None:
+    error["instance"] = instance
+  return error
+
+
+# Helper: Create a problem json based on a generic http response code
+def create_error_response(code):
+
+    if code == 400:
+      return(create_problem_json(None, "Bad request", 400, "Object in payload not properly formulated or not related to the method", None))
+    elif code == 404:
+      return(create_problem_json(None, "Not found", 404, "No resource found at the URI", None))
+    elif code == 405:
+      return(create_problem_json(None, "Method not allowed", 405, "Method not allowed for the URI", None))
+    elif code == 408:
+      return(create_problem_json(None, "Request timeout", 408, "Request timeout", None))
+    elif code == 409:
+      return(create_problem_json(None, "Conflict", 409, "Request could not be processed in the current state of the resource", None))
+    elif code == 429:
+      return(create_problem_json(None, "Too many requests", 429, "Too many requests have been sent in a given amount of time", None))
+    elif code == 503:
+      return(create_problem_json(None, "Service unavailable", 503, "The provider is currently unable to handle the request due to a temporary overload", None))
+    elif code == 507:
+      return(create_problem_json(None, "Insufficient storage", 507, "The method could not be performed on the resource because the provider is unable to store the representation needed to successfully complete the request", None))
+    else:
+      return(create_problem_json(None, "Unknown", code, "Not implemented response code", None))
diff --git a/catalogue-enhanced/src/main.py b/catalogue-enhanced/src/main.py
new file mode 100644 (file)
index 0000000..31bde13
--- /dev/null
@@ -0,0 +1,49 @@
+#  ============LICENSE_START===============================================
+#  Copyright (C) 2022 Nordix Foundation. 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 sys
+
+from flask import Response, Flask
+from var_declaration import app, rapp_registry
+
+# app var need to be initialized
+import payload_logging
+
+# Constants
+TEXT_PLAIN='text/plain'
+
+# Check alive function
+@app.route('/', methods=['GET'])
+def test():
+  return Response("OK", 200, mimetype=TEXT_PLAIN)
+
+# Delete all rapp definitions
+@app.route('/deleteall', methods=['POST'])
+def delete_all():
+  rapp_registry.clear()
+
+  return Response("All rapp definitions deleted", 200, mimetype=TEXT_PLAIN)
+
+port_number = 9696
+if len(sys.argv) >= 2 and isinstance(sys.argv[1], int):
+    port_number = sys.argv[1]
+
+#Import base RESTFul API functions from Open API
+app.add_api('rapp-catalogue-enhanced.yaml')
+
+if __name__ == '__main__':
+  app.run(port=port_number, host="127.0.0.1", threaded=False)
diff --git a/catalogue-enhanced/src/maincommon.py b/catalogue-enhanced/src/maincommon.py
new file mode 100644 (file)
index 0000000..20370f1
--- /dev/null
@@ -0,0 +1,28 @@
+#  ============LICENSE_START===============================================
+#  Copyright (C) 2022 Nordix Foundation. 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 os
+import sys
+
+# Must exist
+apipath = os.environ['APIPATH']
+
+# Make sure the api path for the open api yaml file is set, otherwise exit
+def check_apipath():
+    if (apipath is None):
+        print("Env APIPATH not set. Exiting....")
+        sys.exit(1)
diff --git a/catalogue-enhanced/src/payload_logging.py b/catalogue-enhanced/src/payload_logging.py
new file mode 100644 (file)
index 0000000..4450ebc
--- /dev/null
@@ -0,0 +1,60 @@
+#  ============LICENSE_START===============================================
+#  Copyright (C) 2020-2022 Nordix Foundation. 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=================================================
+#
+
+from var_declaration import app
+from flask import Flask, request, Response
+
+#Constants
+TEXT_PLAIN='text/plain'
+
+#Vars
+payload_log=True
+
+#Function to activate/deactivate http header and payload logging
+@app.route('/payload_logging/<state>', methods=['POST', 'PUT'])
+def set_payload_logging(state):
+  global payload_log
+  if (state == "on"):
+    payload_log=True
+  elif (state == "off"):
+    payload_log=False
+  else:
+    return Response("Unknown state: "+state+" - use 'on' or 'off'", 400, mimetype=TEXT_PLAIN)
+
+  return Response("Payload and header logging set to: "+state, 200, mimetype=TEXT_PLAIN)
+
+# Generic function to log http header and payload - called before the request
+@app.app.before_request
+def log_request_info():
+    if (payload_log is True):
+        print('')
+        print('-----Request-----')
+        print('Req Headers: ', request.headers)
+        print('Req Body: ', request.get_data())
+
+# Generic function to log http header and payload - called after the response
+@app.app.after_request
+def log_response_info(response):
+    if (payload_log is True):
+        print('-----Response-----')
+        print('Resp Headers: ', response.headers)
+        print('Resp Body: ', response.get_data())
+    return response
+
+# Helper function to check loggin state
+def is_payload_logging():
+  return payload_log
diff --git a/catalogue-enhanced/src/start.sh b/catalogue-enhanced/src/start.sh
new file mode 100644 (file)
index 0000000..e425208
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+#  ============LICENSE_START===============================================
+#  Copyright (C) 2022 Nordix Foundation. 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=================================================
+#
+
+# Set path to open api
+export APIPATH=$PWD/api
+echo "APIPATH set to: "$APIPATH
+
+cd src
+
+# Start nginx
+nginx -c /usr/src/app/nginx.conf
+
+# Start rapp catalogue enhanced
+echo "Path to main.py: "$PWD
+python -u main.py
diff --git a/catalogue-enhanced/src/util.py b/catalogue-enhanced/src/util.py
new file mode 100644 (file)
index 0000000..31261fd
--- /dev/null
@@ -0,0 +1,47 @@
+#  ============LICENSE_START===============================================
+#  Copyright (C) 2022 Nordix Foundation. 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=================================================
+#
+
+class ToscametaFormatChecker:
+  """
+  A utility class aims to check required fields and their format
+  in TOSCA.meta file in accordance with TOSCA specs.
+  """
+  def __init__(self, file_ver, csar_ver, created_by):
+    self.file_ver = file_ver
+    self.csar_ver = csar_ver
+    self.created_by = created_by
+
+  def validate(self):
+
+    if (self.file_ver == 'TOSCA-Meta-File-Version' and
+        self.csar_ver == 'CSAR-Version' and
+        self.created_by == 'Created-By'):
+
+      return True
+    else:
+      return False
+
+  def __str__(self):  # __str__: returns display string
+    return '[TOSCA.meta: %s, %s, %s]' % (self.file_ver, self.csar_ver, self.created_by)
+
+# Unit tests for class: ToscametaFormatChecker
+if __name__ == '__main__':
+  toscameta_t = ToscametaFormatChecker('TOSCA-Meta-File-Version', 'CSAR-Version', 'Created-By')
+  assert toscameta_t.validate() is True
+
+  toscameta_f = ToscametaFormatChecker('TOSCA-Meta-File-Versn', 'CSAR-Version', 'Created-By')
+  assert toscameta_f.validate() is False
diff --git a/catalogue-enhanced/src/var_declaration.py b/catalogue-enhanced/src/var_declaration.py
new file mode 100644 (file)
index 0000000..e455fe6
--- /dev/null
@@ -0,0 +1,24 @@
+#  ============LICENSE_START===============================================
+#  Copyright (C) 2022 Nordix Foundation. 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=================================================
+#
+
+from maincommon import apipath
+import connexion
+
+#Main app
+app = connexion.App(__name__, specification_dir=apipath)
+
+rapp_registry = {}
diff --git a/catalogue-enhanced/tests/test_catalogue_enhanced.py b/catalogue-enhanced/tests/test_catalogue_enhanced.py
new file mode 100644 (file)
index 0000000..8e9ffaf
--- /dev/null
@@ -0,0 +1,250 @@
+#  ============LICENSE_START===============================================
+#  Copyright (C) 2022 Nordix Foundation. 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=================================================
+#
+
+# This fixture tests the rappcatalogueenhanced module
+import json
+import time
+from unittest_setup import SERVER_URL, setup_env, get_testdata_dir, client
+
+#Setup env and import paths
+setup_env()
+from compare_json import compare
+
+def test_apis(client):
+
+  RESP_TITLE = "The rapp does not exist."
+  RAPP1 = 'rappcatalogue/rapp1'
+  RAPP2 = 'rappcatalogue/rapp2'
+  RAPP1_JSON = 'rapp1.json'
+  RAPP2_JSON = 'rapp2.json'
+
+  testdata = get_testdata_dir()
+
+  # Header for json payload
+  header = {
+    "Content-Type" : "application/json"
+  }
+
+  # rappcatalogueenhanced hello world
+  response = client.get(SERVER_URL)
+  assert response.status_code == 200
+
+  # Reset rapp catalogue enhanced
+  response = client.post(SERVER_URL+'deleteall')
+  assert response.status_code == 200
+  assert response.data ==  b"All rapp definitions deleted"
+
+  # API: Query all rapp ids, shall be empty array
+  data_response = [ ]
+  response = client.get(SERVER_URL+'rappcatalogue')
+  result = json.loads(response.data)
+  res = compare(data_response, result)
+  assert response.status_code == 200
+  assert res == True
+
+  # API: Query rapp by rapp id , rapp rapp1 not found
+  data_response = {"title": RESP_TITLE, "status": 404, "instance": "rapp1"}
+  response=client.get(SERVER_URL+RAPP1)
+  result=json.loads(response.data)
+  res=compare(data_response, result)
+  assert response.status_code == 404
+  assert res == True
+
+  # API: Register an rapp: rapp1
+  with open(testdata+RAPP1_JSON) as json_file:
+    json_payload = json.loads(json_file.read())
+    response = client.put(SERVER_URL+RAPP1, headers=header, data=json.dumps(json_payload))
+    result = json.loads(response.data)
+    res = compare(json_payload, result)
+    assert response.status_code == 201
+    assert res == True
+
+  # API: Query all rapp ids, shall contain rapp id rapp1
+  data_response = ['rapp1']
+  response = client.get(SERVER_URL+'rappcatalogue')
+  result = json.loads(response.data)
+  res = compare(data_response, result)
+  assert response.status_code == 200
+  assert res == True
+
+  # API: Query rapp by rapp id, rapp rapp1 found
+  with open(testdata+RAPP1_JSON) as json_file:
+    data_response = json.load(json_file)
+    response = client.get(SERVER_URL+RAPP1)
+    result = json.loads(response.data)
+    res = compare(data_response, result)
+    assert response.status_code == 200
+    assert res == True
+
+  # API: Filter api list by service type and rapp id, service type provider
+  with open(testdata+'rapp1_provider_apilist.json') as json_file:
+    data_response = json.load(json_file)
+    response = client.get(SERVER_URL+'rappcatalogue/rapp1/provider')
+    result = json.loads(response.data)
+    res = compare(data_response, result)
+    assert response.status_code == 200
+    assert res == True
+
+  # API: Filter api list by service type and rapp id, service type invoker
+  with open(testdata+'rapp1_invoker_apilist.json') as json_file:
+    data_response = json.load(json_file)
+    response = client.get(SERVER_URL+'rappcatalogue/rapp1/invoker')
+    result = json.loads(response.data)
+    res = compare(data_response, result)
+    assert response.status_code == 200
+    assert res == True
+
+  # API: Delete rapp by rapp id, rapp rapp1 deleted successfully
+  data_response = b""
+  response=client.delete(SERVER_URL+RAPP1)
+  assert response.status_code == 204
+  assert data_response == response.data
+
+  # API: Query all rapp ids, shall be empty array
+  data_response = [ ]
+  response = client.get(SERVER_URL+'rappcatalogue')
+  result = json.loads(response.data)
+  res = compare(data_response, result)
+  assert response.status_code == 200
+  assert res == True
+
+  # API: Query rapp by rapp id , rapp rapp1 not found
+  data_response = {"title": RESP_TITLE, "status": 404, "instance": "rapp1"}
+  response=client.get(SERVER_URL+RAPP1)
+  result=json.loads(response.data)
+  res=compare(data_response, result)
+  assert response.status_code == 404
+  assert res == True
+
+  # API: Register an rapp: rapp1
+  with open(testdata+RAPP1_JSON) as json_file:
+    json_payload = json.loads(json_file.read())
+    response = client.put(SERVER_URL+RAPP1, headers=header, data=json.dumps(json_payload))
+    result = json.loads(response.data)
+    res = compare(json_payload, result)
+    assert response.status_code == 201
+    assert res == True
+
+  # API: Query all rapp ids, shall contain rapp id rapp1
+  data_response = ['rapp1']
+  response = client.get(SERVER_URL+'rappcatalogue')
+  result = json.loads(response.data)
+  res = compare(data_response, result)
+  assert response.status_code == 200
+  assert res == True
+
+  # API: Query rapp by rapp id, rapp rapp1 found
+  with open(testdata+RAPP1_JSON) as json_file:
+    data_response = json.load(json_file)
+    response = client.get(SERVER_URL+RAPP1)
+    result = json.loads(response.data)
+    res = compare(data_response, result)
+    assert response.status_code == 200
+    assert res == True
+
+  # API: Update an rapp: rapp1
+  with open(testdata+RAPP1_JSON) as json_file:
+    json_payload = json.loads(json_file.read())
+    response = client.put(SERVER_URL+RAPP1, headers=header, data=json.dumps(json_payload))
+    result = json.loads(response.data)
+    res = compare(json_payload, result)
+    assert response.status_code == 200
+    assert res == True
+
+  # API: Query rapp by rapp id, rapp rapp1 found
+  with open(testdata+RAPP1_JSON) as json_file:
+    data_response = json.load(json_file)
+    response = client.get(SERVER_URL+RAPP1)
+    result = json.loads(response.data)
+    res = compare(data_response, result)
+    assert response.status_code == 200
+    assert res == True
+
+  # API: Register an rapp: rapp2
+  with open(testdata+RAPP2_JSON) as json_file:
+    json_payload = json.loads(json_file.read())
+    response = client.put(SERVER_URL+RAPP2, headers=header, data=json.dumps(json_payload))
+    result = json.loads(response.data)
+    res = compare(json_payload, result)
+    assert response.status_code == 201
+    assert res == True
+
+  # API: Query rapp by rapp id, rapp rapp2 found
+  with open(testdata+RAPP2_JSON) as json_file:
+    data_response = json.load(json_file)
+    response = client.get(SERVER_URL+RAPP2)
+    result = json.loads(response.data)
+    res = compare(data_response, result)
+    assert response.status_code == 200
+    assert res == True
+
+  # API: Filter api list by service type and rapp id, service type provider
+  with open(testdata+'rapp2_provider_apilist.json') as json_file:
+    data_response = json.load(json_file)
+    response = client.get(SERVER_URL+'rappcatalogue/rapp2/provider')
+    result = json.loads(response.data)
+    res = compare(data_response, result)
+    assert response.status_code == 200
+    assert res == True
+
+  # API: Filter api list by service type and rapp id, service type invoker
+  with open(testdata+'rapp2_invoker_apilist.json') as json_file:
+    data_response = json.load(json_file)
+    response = client.get(SERVER_URL+'rappcatalogue/rapp2/invoker')
+    result = json.loads(response.data)
+    res = compare(data_response, result)
+    assert response.status_code == 200
+    assert res == True
+
+  # API: Query all rapp ids, shall contain rapp id rapp1 and rapp2
+  data_response = ['rapp1', 'rapp2']
+  response = client.get(SERVER_URL+'rappcatalogue')
+  result = json.loads(response.data)
+  res = compare(data_response, result)
+  assert response.status_code == 200
+  assert res == True
+
+  # API: Delete rapp by rapp id, rapp rapp1 deleted successfully
+  data_response = b""
+  response=client.delete(SERVER_URL+RAPP1)
+  assert response.status_code == 204
+  assert data_response == response.data
+
+  # API: Query rapp by rapp id , rapp rapp1 not found
+  data_response = {"title": RESP_TITLE, "status": 404, "instance": "rapp1"}
+  response=client.get(SERVER_URL+RAPP1)
+  result=json.loads(response.data)
+  res=compare(data_response, result)
+  assert response.status_code == 404
+  assert res == True
+
+  # API: Query all rapp ids, shall contain rapp id rapp2
+  data_response = ['rapp2']
+  response = client.get(SERVER_URL+'rappcatalogue')
+  result = json.loads(response.data)
+  res = compare(data_response, result)
+  assert response.status_code == 200
+  assert res == True
+
+  # API: Query rapp by rapp id, rapp rapp2 found
+  with open(testdata+RAPP2_JSON) as json_file:
+    data_response = json.load(json_file)
+    response = client.get(SERVER_URL+RAPP2)
+    result = json.loads(response.data)
+    res = compare(data_response, result)
+    assert response.status_code == 200
+    assert res == True
diff --git a/catalogue-enhanced/tests/unittest_setup.py b/catalogue-enhanced/tests/unittest_setup.py
new file mode 100644 (file)
index 0000000..e14d3ae
--- /dev/null
@@ -0,0 +1,57 @@
+#  ============LICENSE_START===============================================
+#  Copyright (C) 2022 Nordix Foundation. 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=================================================
+#
+
+# Setting up dir and env for unit test of rappcatalogueenhanced
+import sys
+import os
+import pytest
+
+#Server port and base path
+PORT_NUMBER="9096"
+HOST_IP="localhost"
+SERVER_URL="http://"+HOST_IP+":"+PORT_NUMBER+"/"
+
+#Dir for json test data files
+testdata=""
+
+def setup_env():
+  global testdata
+  cwd=os.getcwd()+"/"
+
+  if 'TESTS_BASE_PATH' in os.environ:
+    cwd = os.environ['TESTS_BASE_PATH']+"/"
+
+  testdata = cwd+"../../catalogue-enhanced-test/jsonfiles/"
+
+  #Env var to setup version and host logging
+  os.environ['APIPATH'] = cwd+"../api/"
+  os.environ['REMOTE_HOSTS_LOGGING'] = "ON"
+
+  sys.path.append(os.path.abspath(cwd+'../src'))  # include: src
+  sys.path.append(os.path.abspath(cwd+'../../catalogue-enhanced-test/common'))    # include: commons
+
+  os.chdir(cwd+"../src")
+
+def get_testdata_dir():
+  return testdata
+
+#Test client for rest calls
+@pytest.fixture
+def client():
+  from main import app
+  with app.app.test_client() as c:
+    yield c
diff --git a/catalogue-enhanced/tox.ini b/catalogue-enhanced/tox.ini
new file mode 100644 (file)
index 0000000..e21b9b5
--- /dev/null
@@ -0,0 +1,34 @@
+# ==================================================================================
+#       Copyright (c) 2022 Nordix
+#
+#   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.
+# ==================================================================================
+#
+
+[tox]
+envlist = code
+minversion = 2.0
+skipsdist = true
+
+# basic test and coverage job
+[testenv:code]
+basepython = python3.8
+deps=
+  pytest
+  coverage
+  pytest-cov
+  connexion
+
+setenv = TESTS_BASE_PATH = {toxinidir}/tests
+commands =
+  pytest --cov-append --cov {toxinidir} --cov-report xml --cov-report term-missing --cov-report html