.swagger-codegen-ignore
.swagger-codegen/
api/README.md
+*.pyc
# Python virtual env
venv/
--- /dev/null
+#!/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 "********************"
--- /dev/null
+#!/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
--- /dev/null
+
+# ============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()
--- /dev/null
+#!/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
+}
--- /dev/null
+{
+ "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"
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+[
+ {
+ "serviceType": "invoker",
+ "apiId": "88918c1ccdb75a2da0f284572b66ec2c"
+ }
+]
--- /dev/null
+[
+ {
+ "serviceType": "provider",
+ "apiId": "84111c1ccdb75a2da0f284572b66e854",
+ "aefProfiles": [
+ {
+ "aefId": "dc76679f940d56c86987f0ba7973df0f"
+ }
+ ]
+ }
+]
--- /dev/null
+{
+ "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"
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+[
+ {
+ "serviceType": "invoker",
+ "apiId": "88918c1ccdb75a2da0f284572b66ec2c"
+ }
+]
--- /dev/null
+[
+ {
+ "serviceType": "provider",
+ "apiId": "84111c1ccdb75a2da0f284572b66e854",
+ "aefProfiles": [
+ {
+ "aefId": "dc76679f940d56c86987f0ba7973df0f"
+ }
+ ]
+ }
+]
--- /dev/null
+[
+ "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"
+]
--- /dev/null
+# Documentation
+.idea/
+.tox
+docs/_build/
+.DS_STORE
+
+# IDE
+.project
+.vscode
+
+.coverage
+coverage.xml
+htmlcov/
+
+# Python virtual env
+venv/
--- /dev/null
+# ============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
--- /dev/null
+## 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.
--- /dev/null
+# ============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"
--- /dev/null
+-----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-----
--- /dev/null
+#!/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
--- /dev/null
+-----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-----
--- /dev/null
+# ============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;
+}
--- /dev/null
+# ============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))
--- /dev/null
+# ============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)
--- /dev/null
+# ============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)
--- /dev/null
+# ============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
--- /dev/null
+#!/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
--- /dev/null
+# ============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
--- /dev/null
+# ============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 = {}
--- /dev/null
+# ============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
--- /dev/null
+# ============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
--- /dev/null
+# ==================================================================================
+# 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