From: halil.cakal Date: Mon, 13 Jun 2022 08:02:24 +0000 (+0100) Subject: Callout hooks towards external server for create and delete operations X-Git-Tag: 2.3.0^2 X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?p=sim%2Fa1-interface.git;a=commitdiff_plain;h=e220bfc2cb1727996da2fe120b676a9454cf982e Callout hooks towards external server for create and delete operations Patch set 2: Commit message clean-up. Issue-ID: NONRTRIC-756 Change-Id: I73bf9dd8788aeae2ca63ac0143a49bde943788e5 Signed-off-by: halil.cakal --- diff --git a/near-rt-ric-simulator/src/STD_2.0.0/a1.py b/near-rt-ric-simulator/src/STD_2.0.0/a1.py index 2dc7b55..28eccc6 100644 --- a/near-rt-ric-simulator/src/STD_2.0.0/a1.py +++ b/near-rt-ric-simulator/src/STD_2.0.0/a1.py @@ -15,12 +15,14 @@ # ============LICENSE_END================================================= # +import os import copy import datetime import json import logging import collections import time +import requests from connexion import NoContent from flask import Flask, escape, request, Response, make_response @@ -33,6 +35,9 @@ from maincommon import check_apipath, apipath, get_supported_interfaces_response APPL_JSON='application/json' APPL_PROB_JSON='application/problem+json' +EXT_SRV_URL=os.getenv('EXT_SRV_URL') + + # API Function: Get all policy type ids def get_all_policy_types(): @@ -127,6 +132,14 @@ def put_policy(policyTypeId, policyId): pjson=create_problem_json(None, "Duplicate, the policy json already exists.", 400, None, policy_id) return Response(json.dumps(pjson), 400, mimetype=APPL_PROB_JSON) + #Callout hooks for external server + #When it fails, break and return 419 HTTP status code + if (EXT_SRV_URL is not None): + resp = callout_external_server(policy_id, data, 'PUT') + if (resp != retcode): + pjson=create_error_response(resp) + return Response(json.dumps(pjson), 500, mimetype=APPL_PROB_JSON) + if (fp_previous is not None): del policy_fingerprint[fp_previous] @@ -171,7 +184,6 @@ def get_policy(policyTypeId, policyId): return Response(json.dumps(policy_instances[policy_type_id][policy_id]), 200, mimetype=APPL_JSON) - # API Function: Delete a policy def delete_policy(policyTypeId, policyId): @@ -191,6 +203,14 @@ def delete_policy(policyTypeId, policyId): pjson=create_problem_json(None, "The requested policy does not exist.", 404, None, policy_id) return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON) + #Callout hooks for external server + #When it fails, break and return 419 HTTP status code + if (EXT_SRV_URL is not None): + resp = callout_external_server(policy_id, None, 'DELETE') + if (resp != 204): + pjson=create_error_response(resp) + return Response(json.dumps(pjson), 500, mimetype=APPL_PROB_JSON) + if (is_duplicate_check()): fp_previous=calcFingerprint(policy_instances[policy_type_id][policy_id], policy_type_id) else: @@ -202,7 +222,6 @@ def delete_policy(policyTypeId, policyId): callbacks.pop(policy_id) return Response('', 204, mimetype=APPL_JSON) - # API Function: Get status for a policy def get_policy_status(policyTypeId, policyId): @@ -224,16 +243,37 @@ def get_policy_status(policyTypeId, policyId): return Response(json.dumps(policy_status[policy_id]), status=200, mimetype=APPL_JSON) +# Helper: Callout external server to notify it for policy operations +# Returns 200, 201 and 204 for the success callout hooks, for the others returns 419 +def callout_external_server(policy_id, payload, operation): + + target_url=EXT_SRV_URL + policy_id + try: + if (operation == 'PUT'): + #Suppress error when self-signed certificate is being used with verify flag + resp=requests.put(target_url, json=payload, timeout=10, verify=False) + return resp.status_code + elif (operation == 'DELETE'): + resp=requests.delete(target_url, timeout=10, verify=False) + return resp.status_code + except Exception: + #Return a generic unassigned HTTP status code as per iana, for all exceptions (419:Callout failed) + #https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml + return 419 + # Helper: Create a response object if forced http response code is set def get_forced_response(): + if (forced_settings['code'] is not None): - pjson=create_error_response(forced_settings['code']) + value=forced_settings['code'] + pjson=create_error_response(int(value)) forced_settings['code']=None return Response(json.dumps(pjson), pjson['status'], mimetype=APPL_PROB_JSON) return None # Helper: Delay if delayed response code is set def do_delay(): + if (forced_settings['delay'] is not None): try: val=int(forced_settings['delay']) @@ -243,6 +283,7 @@ def do_delay(): # Helper: Check if response shall be delayed or a forced response shall be sent def check_modified_response(): + do_delay() return get_forced_response() @@ -265,19 +306,21 @@ def create_problem_json(type_of, title, status, detail, instance): # Helper: Create a problem json based on a generic http response code def create_error_response(code): - if code == '400': + 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': + elif (code == 404): return(create_problem_json(None, "Not found", 404, "No resource found at the URI", None)) - elif code == '405': + elif (code == 405): return(create_problem_json(None, "Method not allowed", 405, "Method not allowed for the URI", None)) - elif code == '409': + 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': + elif (code == 419): + return(create_problem_json(None, "Callout failed", 419, "Callout hooks could not be processed on the external server", 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 == '507': + 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)) - elif code == '503': + 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)) else: return(create_problem_json(None, "Unknown", code, "Not implemented response code", None)) diff --git a/near-rt-ric-simulator/test/EXT_SRV/nginx.conf b/near-rt-ric-simulator/test/EXT_SRV/nginx.conf old mode 100644 new mode 100755 index 7b3e620..0a3bd65 --- a/near-rt-ric-simulator/test/EXT_SRV/nginx.conf +++ b/near-rt-ric-simulator/test/EXT_SRV/nginx.conf @@ -32,8 +32,8 @@ http { perl_set $allow_http 'sub { return $ENV{"ALLOW_HTTP"}; }'; server { # simple reverse-proxy - listen 8085; - listen [::]:8085; + listen 9095; + listen [::]:9095; server_name localhost; if ($allow_http != true) { return 444; @@ -44,13 +44,13 @@ http { 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:2222; + proxy_pass http://localhost:3333; } } server { # simple reverse-proxy - listen 8185 ssl; - listen [::]:8185 ssl; + listen 9195 ssl; + listen [::]:9195 ssl; server_name localhost; ssl_certificate /usr/src/app/cert/cert.crt; ssl_certificate_key /usr/src/app/cert/key.crt; @@ -61,7 +61,7 @@ http { 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:2222; + proxy_pass http://localhost:3333; } } ## @@ -90,4 +90,4 @@ http { # 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; -} \ No newline at end of file +} diff --git a/near-rt-ric-simulator/test/EXT_SRV/src/main.py b/near-rt-ric-simulator/test/EXT_SRV/src/main.py old mode 100644 new mode 100755 index 4323d10..9587308 --- a/near-rt-ric-simulator/test/EXT_SRV/src/main.py +++ b/near-rt-ric-simulator/test/EXT_SRV/src/main.py @@ -70,7 +70,7 @@ def forcedelay(): else: return Response("Force delay: " + str(forced_settings['delay']) + " sec set for all external server responses until it is resetted ", 200, mimetype=TEXT_PLAIN) -port_number = 2222 +port_number = 3333 if len(sys.argv) >= 2: if isinstance(sys.argv[1], int): port_number = sys.argv[1] diff --git a/near-rt-ric-simulator/test/EXT_SRV_TEST/basic_test.sh b/near-rt-ric-simulator/test/EXT_SRV_TEST/basic_test.sh old mode 100644 new mode 100755 index 14490be..2deec7f --- a/near-rt-ric-simulator/test/EXT_SRV_TEST/basic_test.sh +++ b/near-rt-ric-simulator/test/EXT_SRV_TEST/basic_test.sh @@ -34,12 +34,12 @@ fi if [ $1 == "nonsecure" ]; then #Default http port for the simulator - PORT=8085 + PORT=9095 # Set http protocol HTTPX="http" else #Default https port for the simulator - PORT=8185 + PORT=9195 # Set https protocol HTTPX="https" fi @@ -47,11 +47,11 @@ fi . ../common/test_common.sh . ../common/elapse_time_curl.sh -echo "=== Simulator hello world ===" +echo "=== External server hello world ===" RESULT="OK" do_curl GET / 200 -echo "=== Reset simulator a1policy instances ===" +echo "=== Reset external server a1policy instances ===" RESULT="All a1 policy instances deleted" do_curl POST /serveradmin/deleteinstances 200 diff --git a/near-rt-ric-simulator/test/EXT_SRV_TEST/build_and_start.sh b/near-rt-ric-simulator/test/EXT_SRV_TEST/build_and_start.sh old mode 100644 new mode 100755 index 4deea51..9e2e987 --- a/near-rt-ric-simulator/test/EXT_SRV_TEST/build_and_start.sh +++ b/near-rt-ric-simulator/test/EXT_SRV_TEST/build_and_start.sh @@ -42,4 +42,4 @@ echo "Starting external server for A1 simulator callouts..." echo "PWD path: "$PWD #Run the container in interactive mode, unsecure port 8085, secure port 8185 -docker run --rm -it -p 8085:8085 -p 8185:8185 -e ALLOW_HTTP=true --volume "$PWD/certificate:/usr/src/app/cert" --name externalserversimulator external_server +docker run --rm -it -p 9095:9095 -p 9195:9195 -e ALLOW_HTTP=true --volume "$PWD/certificate:/usr/src/app/cert" --name externalserversimulator external_server diff --git a/near-rt-ric-simulator/test/STD_2.0.0/basic_test.sh b/near-rt-ric-simulator/test/STD_2.0.0/basic_test.sh index 084a368..fd49caa 100755 --- a/near-rt-ric-simulator/test/STD_2.0.0/basic_test.sh +++ b/near-rt-ric-simulator/test/STD_2.0.0/basic_test.sh @@ -21,24 +21,27 @@ # Run the build_and_start with the same arg, except arg 'nonsecure|secure', as this script print_usage() { - echo "Usage: ./basic_test.sh nonsecure|secure duplicate-check|ignore-duplicate " + echo "Usage: ./basic_test.sh nonsecure|secure duplicate-check|ignore-duplicate ext-srv|ext-srv-secure|ignore-ext-srv" exit 1 } -if [ $# -ne 2 ]; then +if [ $# -ne 3 ]; then print_usage fi -if [ "$1" != "nonsecure" ] && [ "$1" != "secure" ]; then + +if [ $1 != "nonsecure" ] && [ $1 != "secure" ]; then print_usage fi -if [ "$2" == "duplicate-check" ]; then - DUP_CHECK=1 -elif [ "$2" == "ignore-duplicate" ]; then - DUP_CHECK=0 -else + +if [ $2 != "duplicate-check" ] && [ $2 != "ignore-duplicate" ]; then print_usage fi +if [ $3 != "ext-srv" ] && [ $3 != "ext-srv-secure" ] && [ $3 != "ignore-ext-srv" ]; then + print_usage +fi + + if [ $1 == "nonsecure" ]; then #Default http port for the simulator PORT=8085 @@ -51,6 +54,28 @@ else HTTPX="https" fi +if [ $2 == "duplicate-check" ]; then + DUP_CHECK=1 +else + DUP_CHECK=0 +fi + +if [ $3 == "ext-srv" ]; then + #Default http port for the external server + PORT_EXT_SRV=9095 + # Set http protocol for external server + HTTPX_EXT_SRV="http" + EXT_SRV_EXIST=1 +elif [ $3 == "ext-srv-secure" ]; then + #Default https port for the external server + PORT_EXT_SRV=9195 + # Set https protocol for external server + HTTPX_EXT_SRV="https" + EXT_SRV_EXIST=1 +else + EXT_SRV_EXIST=0 +fi + . ../common/test_common.sh @@ -70,6 +95,21 @@ echo "=== Reset simulator, all ===" RESULT="All policy instances and types deleted" do_curl POST /deleteall 200 +#Test all admin functions in the external server +if [ $EXT_SRV_EXIST == 1 ]; then + echo "=== External server, hello world ===" + RESULT="OK" + do_curl_ext_srv GET / 200 + + echo "=== External server, reset all ===" + RESULT="All a1 policy instances deleted" + do_curl_ext_srv POST /serveradmin/deleteinstances 200 + + echo "=== External server, reset force delay ===" + RESULT="Force delay has been resetted for all external server responses" + do_curl_ext_srv POST /serveradmin/forcedelay 200 +fi + echo "=== Get counter: interface ===" RESULT="STD_2.0.0" do_curl GET /counter/interface 200 @@ -144,6 +184,13 @@ res=$(cat jsonfiles/pi1.json) RESULT="json:$res" do_curl PUT /A1-P/v2/policytypes/STD_1/policies/pi1 201 jsonfiles/pi1.json +if [ $EXT_SRV_EXIST == 1 ]; then + echo "=== External server, get a pi1 policy: pi1 ===" + res=$(cat jsonfiles/pi1.json) + RESULT="json:$res" + do_curl_ext_srv GET /a1policy/pi1 200 +fi + echo "=== API: Get policy instance pi1 of type: STD_1 ===" res=$(cat jsonfiles/pi1.json) RESULT="json:$res" @@ -154,11 +201,25 @@ res=$(cat jsonfiles/pi1.json) RESULT="json:$res" do_curl PUT /A1-P/v2/policytypes/STD_1/policies/pi1 200 jsonfiles/pi1.json +if [ $EXT_SRV_EXIST == 1 ]; then + echo "=== External server, get a pi1 policy: pi1 ===" + res=$(cat jsonfiles/pi1.json) + RESULT="json:$res" + do_curl_ext_srv GET /a1policy/pi1 200 +fi + echo "=== API: Update policy instance pi1 of type: STD_1===" res=$(cat jsonfiles/pi1_updated.json) RESULT="json:$res" do_curl PUT /A1-P/v2/policytypes/STD_1/policies/pi1 200 jsonfiles/pi1_updated.json +if [ $EXT_SRV_EXIST == 1 ]; then + echo "=== External server, get a pi1 policy: pi1 ===" + res=$(cat jsonfiles/pi1_updated.json) + RESULT="json:$res" + do_curl_ext_srv GET /a1policy/pi1 200 +fi + echo "=== API: Duplicate policy instance json, pi2 of type: STD_1===" res=$(cat jsonfiles/pi1_updated.json) if [ $DUP_CHECK == 1 ]; then @@ -171,20 +232,38 @@ else RESULT="json:$res" do_curl PUT /A1-P/v2/policytypes/STD_1/policies/pi2 201 jsonfiles/pi1_updated.json + if [ $EXT_SRV_EXIST == 1 ]; then + echo "=== External server, get a pi2 policy: pi2 ===" + res=$(cat jsonfiles/pi1_updated.json) + RESULT="json:$res" + do_curl_ext_srv GET /a1policy/pi2 200 + fi + echo "=== API: DELETE policy instance pi2 ===" RESULT="" do_curl DELETE /A1-P/v2/policytypes/STD_1/policies/pi2 204 + + if [ $EXT_SRV_EXIST == 1 ]; then + echo "=== External server, get a pi2 policy: policy instance not found ===" + RESULT="json:{\"title\": \"The A1 policy requested does not exist.\", \"status\": 404, \"instance\": \"pi2\"}" + do_curl_ext_srv GET /a1policy/pi2 404 + fi fi echo "=== API: Get policy instances, shall contain pi1==" RESULT="json:[ \"pi1\" ]" do_curl GET /A1-P/v2/policytypes/STD_1/policies 200 +if [ $EXT_SRV_EXIST == 1 ]; then + echo "=== External server, get policy instances, shall contain pi1==" + RESULT="json:[ \"pi1\" ]" + do_curl_ext_srv GET /a1policies 200 +fi + echo "=== Put a policy type: STD_2 ===" RESULT="Policy type STD_2 is OK." do_curl PUT '/policytype?id=STD_2' 201 jsonfiles/std_2.json - echo "=== API: Duplicate policy instance id pi1 of type: STD_2===" res=$(cat jsonfiles/pi1_updated.json) RESULT="json:{\"title\": \"The policy id already exist for other policy type.\", \"status\": 400, \"instance\": \"pi1\"}" @@ -202,7 +281,6 @@ echo "=== Get counter: intstance ===" RESULT="1" do_curl GET /counter/num_instances 200 - echo "=== Set force response code 409. ===" RESULT="*" do_curl POST '/forceresponse?code=409' 200 @@ -263,10 +341,16 @@ echo "=== API: DELETE policy instance pi1 ===" RESULT="" do_curl DELETE /A1-P/v2/policytypes/STD_1/policies/pi1 204 -echo "=== API: Get policy instances, shall contain pi1 and pi2==" +echo "=== API: Get policy instances, shall contain pi2==" RESULT="json:[ \"pi2\" ]" do_curl GET /A1-P/v2/policytypes/STD_1/policies 200 +if [ $EXT_SRV_EXIST == 1 ]; then + echo "=== External server, get policy instances, shall contain pi2==" + RESULT="json:[ \"pi2\" ]" + do_curl_ext_srv GET /a1policies 200 +fi + echo "=== API: Get policy status ===" RESULT="json:{\"enforceStatus\": \"\", \"enforceReason\": \"\"}" do_curl GET /A1-P/v2/policytypes/STD_1/policies/pi2/status 200 diff --git a/near-rt-ric-simulator/test/STD_2.0.0/build_and_start.sh b/near-rt-ric-simulator/test/STD_2.0.0/build_and_start.sh index a58004a..e903cd5 100755 --- a/near-rt-ric-simulator/test/STD_2.0.0/build_and_start.sh +++ b/near-rt-ric-simulator/test/STD_2.0.0/build_and_start.sh @@ -1,7 +1,7 @@ #!/bin/bash # ============LICENSE_START=============================================== -# Copyright (C) 2021 Nordix Foundation. All rights reserved. +# Copyright (C) 2021-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. @@ -21,13 +21,14 @@ # Make sure to run the simulator with the same arg as this script print_usage() { - echo "Usage: ./build_and_start.sh duplicate-check|ignore-duplicate " + echo "Usage: ./build_and_start.sh duplicate-check|ignore-duplicate ext-srv|ext-srv-secure|ignore-ext-srv" exit 1 } -if [ $# -ne 1 ]; then +if [ $# -ne 2 ]; then print_usage fi + if [ $1 == "duplicate-check" ]; then DUP_CHECK=1 elif [ $1 == "ignore-duplicate" ]; then @@ -36,15 +37,60 @@ else print_usage fi -echo "Building image" -cd ../../ +if [ $2 == "ext-srv" ]; then + URL="http://localhost:9095/a1policy/" +elif [ $2 == "ext-srv-secure" ]; then + URL="https://localhost:9195/a1policy/" +elif [ $2 == "ignore-ext-srv" ]; then + URL="" +else + print_usage +fi -#Build the image -docker build -t a1test . +URL_FLAG="" +if [ ! -z "$URL" ]; then + URL_FLAG="-e EXT_SRV_URL=$URL" +fi + +# Stop and remove container images if they run +echo "Stopping A1 simulator image..." docker stop a1StdSimulator > /dev/null 2>&1 docker rm -f a1StdSimulator > /dev/null 2>&1 -echo "Starting ric-sim" -#Run the container in interactive mode, unsecure port 8085, secure port 8185 -docker run --rm -it -p 8085:8085 -p 8185:8185 -e A1_VERSION=STD_2.0.0 -e ALLOW_HTTP=true -e REMOTE_HOSTS_LOGGING=1 -e DUPLICATE_CHECK=$DUP_CHECK --volume "$PWD/certificate:/usr/src/app/cert" --name a1StdSimulator a1test +echo "Stopping external server image..." +docker stop externalserversimulator > /dev/null 2>&1 +docker rm -f externalserversimulator > /dev/null 2>&1 + +# Initialize path variables for certificate and build operations + +dirstd2=$PWD + +cd ../../ +dirnrtsim=$PWD + +cd test/EXT_SRV/ +dirextsrv=$PWD + +# Build containers + +cd $dirnrtsim +echo "Building A1 simulator image..." +docker build -t a1test . + +if [ ! -z "$URL" ]; then + cd $dirextsrv + echo "Building external server image..." + docker build -t external_server . +fi + +# Run containers + +# Runs external_server in detached mode +# In order to tail logs use:: docker logs -f externalserversimulator +if [ ! -z "$URL" ]; then + docker run -d --network host --rm -it -p 9095:9095 -p 9195:9195 -e ALLOW_HTTP=true --volume "$dirextsrv/certificate:/usr/src/app/cert" --name externalserversimulator external_server +fi + +# Runs A1 simulator +docker run --network host --rm -it -p 8085:8085 -p 8185:8185 -e A1_VERSION=STD_2.0.0 -e ALLOW_HTTP=true -e REMOTE_HOSTS_LOGGING=1 -e DUPLICATE_CHECK=$DUP_CHECK $URL_FLAG --volume "$dirnrtsim/certificate:/usr/src/app/cert" --name a1StdSimulator a1test diff --git a/near-rt-ric-simulator/test/common/test_common.sh b/near-rt-ric-simulator/test/common/test_common.sh index a45a6ff..6d2f80e 100755 --- a/near-rt-ric-simulator/test/common/test_common.sh +++ b/near-rt-ric-simulator/test/common/test_common.sh @@ -73,4 +73,53 @@ do_curl() { fi fi fi -} \ No newline at end of file +} + +do_curl_ext_srv() { + 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_EXT_SRV://localhost:"${PORT_EXT_SRV}${2}" -H accept:*/*" + if [ $# -gt 3 ]; then + curlstr=$curlstr" -H Content-Type:application/json --data-binary @"$4 + 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 +}