From: halil.cakal Date: Mon, 17 Oct 2022 08:20:52 +0000 (+0100) Subject: Rapp catalogue enhanced X-Git-Tag: 1.0.0~2 X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=commitdiff_plain;h=b2dee20acbe763855bed3899192fab27284faa46;p=nonrtric%2Fplt%2Frappcatalogue.git Rapp catalogue enhanced Rapp LCM Issue-ID: NONRTRIC-800 Change-Id: I6fe48b5208baf61d6920a095a2980b34ac539a1a Signed-off-by: halil.cakal --- diff --git a/.gitignore b/.gitignore index be1dfec..2b6a724 100644 --- a/.gitignore +++ b/.gitignore @@ -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 index 0000000..8cad83a --- /dev/null +++ b/catalogue-enhanced-test/basic_test.sh @@ -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 index 0000000..351f1df --- /dev/null +++ b/catalogue-enhanced-test/build_and_start.sh @@ -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 index 0000000..cbebd74 --- /dev/null +++ b/catalogue-enhanced-test/common/compare_json.py @@ -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 index 0000000..69f45e6 --- /dev/null +++ b/catalogue-enhanced-test/common/test_common.sh @@ -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: [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, [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 index 0000000..c37765e --- /dev/null +++ b/catalogue-enhanced-test/jsonfiles/rapp1.json @@ -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 index 0000000..b666eba --- /dev/null +++ b/catalogue-enhanced-test/jsonfiles/rapp1_invoker_apilist.json @@ -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 index 0000000..750501a --- /dev/null +++ b/catalogue-enhanced-test/jsonfiles/rapp1_provider_apilist.json @@ -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 index 0000000..25bbb1b --- /dev/null +++ b/catalogue-enhanced-test/jsonfiles/rapp2.json @@ -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 index 0000000..b666eba --- /dev/null +++ b/catalogue-enhanced-test/jsonfiles/rapp2_invoker_apilist.json @@ -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 index 0000000..750501a --- /dev/null +++ b/catalogue-enhanced-test/jsonfiles/rapp2_provider_apilist.json @@ -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 index 0000000..55bf24a --- /dev/null +++ b/catalogue-enhanced-test/jsonfiles/tosca_meta.json @@ -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 index 0000000..00f2c95 --- /dev/null +++ b/catalogue-enhanced/.gitignore @@ -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 index 0000000..390dcc5 --- /dev/null +++ b/catalogue-enhanced/Dockerfile @@ -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 index 0000000..655d164 --- /dev/null +++ b/catalogue-enhanced/README.md @@ -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/ | +| GET, Query API list by rapp id and service type | localhost:9096/rappcatalogue// | +| GET, Validate and query TOSCA.meta file content by rapp id | localhost:9096/rappcatalogue/csar//toscameta | +| PUT, Register rapp | localhost:9096/rappcatalogue/ | +| DELETE, Unregister rapp | localhost:9096/rappcatalogue/ | + + +# 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 index 0000000..262bcce --- /dev/null +++ b/catalogue-enhanced/api/rapp-catalogue-enhanced.yaml @@ -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 index 0000000..ecdf912 --- /dev/null +++ b/catalogue-enhanced/certificate/cert.crt @@ -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 index 0000000..b6468df --- /dev/null +++ b/catalogue-enhanced/certificate/generate_cert_and_key.sh @@ -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 index 0000000..c5976cf --- /dev/null +++ b/catalogue-enhanced/certificate/key.crt @@ -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 index 0000000..9daeafb --- /dev/null +++ b/catalogue-enhanced/certificate/pass @@ -0,0 +1 @@ +test diff --git a/catalogue-enhanced/csar/rapp1/rapp1.csar b/catalogue-enhanced/csar/rapp1/rapp1.csar new file mode 100755 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 index 0000000..408ddec --- /dev/null +++ b/catalogue-enhanced/nginx.conf @@ -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 index 0000000..a318303 --- /dev/null +++ b/catalogue-enhanced/src/catalogue_manager.py @@ -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 index 0000000..31bde13 --- /dev/null +++ b/catalogue-enhanced/src/main.py @@ -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 index 0000000..20370f1 --- /dev/null +++ b/catalogue-enhanced/src/maincommon.py @@ -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 index 0000000..4450ebc --- /dev/null +++ b/catalogue-enhanced/src/payload_logging.py @@ -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/', 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 index 0000000..e425208 --- /dev/null +++ b/catalogue-enhanced/src/start.sh @@ -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 index 0000000..31261fd --- /dev/null +++ b/catalogue-enhanced/src/util.py @@ -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 index 0000000..e455fe6 --- /dev/null +++ b/catalogue-enhanced/src/var_declaration.py @@ -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 index 0000000..8e9ffaf --- /dev/null +++ b/catalogue-enhanced/tests/test_catalogue_enhanced.py @@ -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 index 0000000..e14d3ae --- /dev/null +++ b/catalogue-enhanced/tests/unittest_setup.py @@ -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 index 0000000..e21b9b5 --- /dev/null +++ b/catalogue-enhanced/tox.ini @@ -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