diff --git a/MEC011/SRV/APPSAQ/PlatAppServices.robot b/MEC011/SRV/APPSAQ/PlatAppServices.robot index 2daddf98d7724f8f4cee85bdff92a02ed6d13822..77813400d2356413c3c765be4244f5d49a18793f 100644 --- a/MEC011/SRV/APPSAQ/PlatAppServices.robot +++ b/MEC011/SRV/APPSAQ/PlatAppServices.robot @@ -4,8 +4,10 @@ Documentation ... A test suite for validating Application Service Availability Query (APPSAQ) operations. Resource ../../../GenericKeywords.robot -Resource environment/variables.txt +#Resource environment/variables.txt +Resource environment/variables_sandbox.txt Library REST ${SCHEMA}://${HOST}:${PORT} ssl_verify=false +Library libraries/Server.py Default Tags TC_MEC_SRV_APPSAQ @@ -22,6 +24,7 @@ TC_MEC_MEC011_SRV_APPSAQ_001_OK ... ETSI GS MEC 011 3.2.1, clause 8.2.6.3.1 [Tags] PIC_MEC_PLAT PIC_SERVICES [Setup] Create new service ServiceInfo ${APP_INSTANCE_ID} + Set Suite Variable ${SERVICE_ID} ${response['body']['serInstanceId']} Get a list of mecService of an application instance ${APP_INSTANCE_ID} Check HTTP Response Status Code Is 200 Check HTTP Response Body Json Schema Is ServiceInfoList @@ -33,7 +36,7 @@ TC_MEC_MEC011_SRV_APPSAQ_001_BR ... Check that the IUT responds with an error when ... a request with incorrect parameters is sent by a MEC Application ... - ... Reference ETSI GS MEC 011 3.2.1, clause 5.2.5, + ... Reference ETSI G3.2.1 011 3.2.1, clause 5.2.5, ... ETSI GS MEC 011 3.2.1, clause 8.1.2.2, ... ETSI GS MEC 011 3.2.1, clause 8.2.6.3.1 [Tags] PIC_MEC_PLAT PIC_SERVICES @@ -51,13 +54,16 @@ TC_MEC_MEC011_SRV_APPSAQ_002_OK ... ETSI GS MEC 011 3.2.1, clause 8.1.2.2, ... ETSI GS MEC 011 3.2.1, clause 8.2.6.3.4 [Tags] PIC_MEC_PLAT PIC_SERVICES + [Setup] Create new service ServiceInfo ${APP_INSTANCE_ID} Check HTTP Response Status Code Is 201 Check HTTP Response Body Json Schema Is ServiceInfo Check HTTP Response Header Contains Location Check Response Contains ${response['body']} serName ${NEW_SERVICE_NAME} - ##TODO add notification server - + Spawn Notification Server SerAvailabilityNotificationSubscription + Validate Json SerAvailabilityNotificationSubscription.schema.json ${payload_notification} + [TearDown] Remove individual service ${APP_INSTANCE_ID} ${SERVICE_ID} + TC_MEC_MEC011_SRV_APPSAQ_002_BR [Documentation] @@ -96,6 +102,7 @@ TC_MEC_MEC011_SRV_APPSAQ_003_OK ... ETSI GS MEC 011 3.2.1, clause 8.2.7.3.1 [Tags] PIC_MEC_PLAT PIC_SERVICES [Setup] Create new service ServiceInfo ${APP_INSTANCE_ID} + Set Suite Variable ${SERVICE_ID} ${response['body']['serInstanceId']} Get individual service ${APP_INSTANCE_ID} ${SERVICE_ID} Check HTTP Response Status Code Is 200 Check HTTP Response Body Json Schema Is ServiceInfo @@ -127,6 +134,7 @@ TC_MEC_MEC011_SRV_APPSAQ_004_OK ... ETSI GS MEC 011 3.2.1, clause 8.2.7.3.2 [Tags] PIC_MEC_PLAT PIC_SERVICES [Setup] Create new service ServiceInfo ${APP_INSTANCE_ID} + Set Suite Variable ${SERVICE_ID} ${response['body']['serInstanceId']} Update service ${APP_INSTANCE_ID} ${SERVICE_ID} ServiceInfoUpdated Check HTTP Response Status Code Is 200 Check HTTP Response Body Json Schema Is ServiceInfo @@ -143,7 +151,8 @@ TC_MEC_MEC011_SRV_APPSAQ_004_BR ... ETSI GS MEC 011 3.2.1, clause 8.1.2.2, ... ETSI GS MEC 011 3.2.1, clause 8.2.7.3.2 [Tags] PIC_MEC_PLAT PIC_SERVICES - [Setup] Create new service ServiceInfo ${APP_INSTANCE_ID} + [Setup] Create new service ServiceInfo ${APP_INSTANCE_ID} + Set Suite Variable ${SERVICE_ID} ${response['body']['serInstanceId']} Update service ${APP_INSTANCE_ID} ${SERVICE_ID} ServiceInfoUpdatedError Check HTTP Response Status Code Is 400 [TearDown] Remove individual service ${APP_INSTANCE_ID} ${SERVICE_ID} @@ -173,6 +182,7 @@ TC_MEC_MEC011_SRV_APPSAQ_004_PF ... ETSI GS MEC 011 3.2.1, clause 8.2.7.3.2 [Tags] PIC_MEC_PLAT PIC_SERVICES [Setup] Create new service ServiceInfo ${APP_INSTANCE_ID} + Set Suite Variable ${SERVICE_ID} ${response['body']['serInstanceId']} Update service with etag ${APP_INSTANCE_ID} ${SERVICE_ID} ServiceInfoUpdated ${INVALID_ETAG} Check HTTP Response Status Code Is 412 [TearDown] Remove individual service ${APP_INSTANCE_ID} ${SERVICE_ID} @@ -186,6 +196,7 @@ TC_MEC_MEC011_SRV_APPSAQ_005_OK ... Reference ETSI GS MEC 011 3.2.1, clause 8.2.7.3.5 [Tags] PIC_MEC_PLAT PIC_SERVICES [Setup] Create new service ServiceInfo ${APP_INSTANCE_ID} + Set Suite Variable ${SERVICE_ID} ${response['body']['serInstanceId']} Remove individual service ${APP_INSTANCE_ID} ${SERVICE_ID} Check HTTP Response Status Code Is 204 @@ -207,7 +218,7 @@ TC_MEC_MEC011_SRV_APPSAQ_005_NF Get a list of mecService of an application instance with parameters [Arguments] ${appInstanceId} ${key}=None ${value}=None Set Headers {"Accept":"application/json"} - Set Headers {"Authorization":"${TOKEN}"} + #Set Headers {"Authorization":"${TOKEN}"} Set Headers {"Content-Type":"*/*"} Get ${apiRoot}/${apiName}/${apiVersion}/applications/${appInstanceId}/services?${key}=${value} ${output}= Output response @@ -217,7 +228,7 @@ Get a list of mecService of an application instance [Arguments] ${appInstanceId} Set Headers {"Accept":"application/json"} Set Headers {"Content-Type":"*/*"} - Set Headers {"Authorization":"${TOKEN}"} + #Set Headers {"Authorization":"${TOKEN}"} Get ${apiRoot}/${apiName}/${apiVersion}/applications/${appInstanceId}/services ${output}= Output response Set Suite Variable ${response} ${output} @@ -228,11 +239,11 @@ Create new service Set Headers {"Accept":"application/json"} Set Headers {"Content-Type":"application/json"} #Set Headers {"Content-Type":"*/*"} - Set Headers {"Authorization":"${TOKEN}"} + #Set Headers {"Authorization":"${TOKEN}"} ${file}= Catenate SEPARATOR= jsons/ ${content} .json ${body}= Get File ${file} - Log ${appInstanceId} Post ${apiRoot}/${apiName}/${apiVersion}/applications/${appInstanceId}/services ${body} + Log ${apiRoot}/${apiName}/${apiVersion}/applications/${appInstanceId}/services ${output}= Output response Set Suite Variable ${response} ${output} @@ -240,7 +251,7 @@ Create new service Get individual service [Arguments] ${appInstanceId} ${serviceName} Set Headers {"Accept":"application/json"} - Set Headers {"Authorization":"${TOKEN}"} + #Set Headers {"Authorization":"${TOKEN}"} Set Headers {"Content-Type":"*/*"} Get ${apiRoot}/${apiName}/${apiVersion}/applications/${appInstanceId}/services/${serviceName} ${output}= Output response @@ -250,7 +261,7 @@ Update service [Arguments] ${appInstanceId} ${serviceId} ${content} Set Headers {"Accept":"application/json"} Set Headers {"Content-Type":"application/json"} - Set Headers {"Authorization":"${TOKEN}"} + #Set Headers {"Authorization":"${TOKEN}"} #Set Headers {"Content-Type":"*/*"} ${file}= Catenate SEPARATOR= jsons/ ${content} .json ${body}= Get File ${file} @@ -262,7 +273,7 @@ Update service with etag [Arguments] ${appInstanceId} ${serviceId} ${content} ${etag} Set Headers {"Accept":"application/json"} Set Headers {"Content-Type":"application/json"} - Set Headers {"Authorization":"${TOKEN}"} + #Set Headers {"Authorization":"${TOKEN}"} Set Headers {"If-Match":"${etag}"} ${file}= Catenate SEPARATOR= jsons/ ${content} .json ${body}= Get File ${file} @@ -274,31 +285,29 @@ Update service with etag Remove individual service [Arguments] ${appInstanceId} ${serviceName} Set Headers {"Accept":"application/json"} - Set Headers {"Authorization":"${TOKEN}"} + #Set Headers {"Authorization":"${TOKEN}"} Set Headers {"Content-Type":"*/*"} Delete ${apiRoot}/${apiName}/${apiVersion}/applications/${appInstanceId}/services/${serviceName} ${output}= Output response Set Suite Variable ${response} ${output} - - -# Check Plaform IUT notifies the MEC Application instances - # [Documentation] - # ... + - # [Arguments] ${instance_id} ${content} +Create a new subscription + [Arguments] ${appInstanceId} ${content} + Set Headers {"Accept":"application/json"} + Set Headers {"Content-Type":"application/json"} + Set Headers {"Authorization":"${TOKEN}"} + ${file}= Catenate SEPARATOR= jsons/ ${content} .json + ${body}= Get File ${file} + Post ${apiRoot}/${apiName}/${apiVersion}/applications/${appInstanceId}/subscriptions ${body} + ${output}= Output response + Set Suite Variable ${response} ${output} + - # TODO check how to send the message (isn't defined). Does it need to be tested as it's not defined? +Spawn Notification Server + [Arguments] ${payload_notification} - # // MEC 011, clause 6.4.2 - # the IUT entity sends a notification_message containing - # body containing - # notificationType set to "SerAvailabilityNotification", - # services containing - # serName set to SERVICE_NAME - # _links containing - # subscription set to MP1_SUBSCRIPTION_A - # ; - # ; - # ; - # ; - # to the MEC_APP_Subscriber entity + ${output} Spawn Web Server ${NOTIFICATION_SERVER_IP} ${NOTIFICATION_SERVER_PORT} ${NOTIFICATION_SERVER_TIMEOUT} ${NOTIFICATION_SERVER_HTTP_METHOD} ${NOTIFICATION_SERVER_URI} ${payload_notification} + ${length} = Get Length ${output} + Set Suite Variable ${payload_notification} ${output} + Run Keyword If ${length} == 0 Skip \ No newline at end of file diff --git a/MEC011/SRV/APPSAQ/environment/variables.txt b/MEC011/SRV/APPSAQ/environment/variables.txt index 2a6d2075460a82e3438b3378ae90e32e6e05aff8..198d3b08b7d76b57fa5896f7be7e7cbc9c7d98d6 100644 --- a/MEC011/SRV/APPSAQ/environment/variables.txt +++ b/MEC011/SRV/APPSAQ/environment/variables.txt @@ -1,16 +1,26 @@ *** Variables *** # Generic variables + +${response} {} +#########Env variable for validating TCs locally with Mockoon ${SCHEMA} http ${HOST} 127.0.0.1 -${PORT} 8081 -${response} {} +${PORT} 8082 ${TOKEN} Basic YWxhZGRpbjpvcGVuc2VzYW1l ${apiRoot} ${apiName} mec_service_mgmt ${apiVersion} v1 +##Notification Server variables +${NOTIFICATION_SERVER_IP} 127.0.0.1 +${NOTIFICATION_SERVER_PORT} 8888 +${NOTIFICATION_SERVER_HTTP_METHOD} POST +${NOTIFICATION_SERVER_URI} /callback_url +${NOTIFICATION_SERVER_TIMEOUT} 5 + + # Specific variables -${APP_INSTANCE_ID} 5abe4782-2c70-4e47-9a4e-0ee3a1a0fd1f +${APP_INSTANCE_ID} f1e4d448-e277-496b-bf63-98391cfd20fb ${INSTANCE_ID} instance_id ${FAKE_INSTANCE_ID_VALUE} 5 ${NON_EXISTENT_APP_INSTANCE_ID} NON_EXISTENT_APP_INSTANCE_ID diff --git a/MEC011/SRV/APPSAQ/environment/variables_sandbox.txt b/MEC011/SRV/APPSAQ/environment/variables_sandbox.txt new file mode 100644 index 0000000000000000000000000000000000000000..3005fa36b8b6e5ca44714f87f83e21a367139826 --- /dev/null +++ b/MEC011/SRV/APPSAQ/environment/variables_sandbox.txt @@ -0,0 +1,28 @@ +*** Variables *** +# Generic variables + +${response} {} +####Env variable for the ETSI MEC Sandbox +${SCHEMA} https +${HOST} try-mec.etsi.org +${PORT} 443 +#${TOKEN} Basic YWxhZGRpbjpvcGVuc2VzYW1l +${apiRoot} /<replace_with_what_provided_by_sandbox>/mep1 +${apiName} mec_service_mgmt +${apiVersion} v1 + + +# Specific variables +${APP_INSTANCE_ID} f1e4d448-e277-496b-bf63-98391cfd20fb +${INSTANCE_ID} instance_id +${FAKE_INSTANCE_ID_VALUE} 5 +${NON_EXISTENT_APP_INSTANCE_ID} NON_EXISTENT_APP_INSTANCE_ID +${SERVICE_ID} e0deee2b-6e50-4f33-ab09-8bf0585025d3 +${NON_EXISTENT_SERVICE_ID} NON_EXISTENT_SERVICE_ID + +## this parameter should be the same as in jsons/ServiceInfo.json on param serName +${NEW_SERVICE_NAME} NEW_SERVICE_NAME + +## this parameter should be the same as in jsons/ServiceInfoUpdated.json on param sversion +${SVC_NEW_VERSION} v2.0 +${INVALID_ETAG} INVALID_ETAG \ No newline at end of file diff --git a/MEC011/SRV/APPSAQ/jsons/SerAvailabilityNotification.json b/MEC011/SRV/APPSAQ/jsons/SerAvailabilityNotification.json new file mode 100644 index 0000000000000000000000000000000000000000..fbdcc2369c286c23e761856db51bc2fb6dcdc6c7 --- /dev/null +++ b/MEC011/SRV/APPSAQ/jsons/SerAvailabilityNotification.json @@ -0,0 +1,9 @@ +{ + "subscriptionType": "SerAvailabilityNotificationSubscription", + "callbackReference": "http://127.0.0.1:8888/someendpoint", + "_links": { + "self": { + "href": "http://127.0.0.1:8888/someendpoint" + } + } +} \ No newline at end of file diff --git a/MEC011/SRV/APPSAQ/jsons/ServiceInfo.json b/MEC011/SRV/APPSAQ/jsons/ServiceInfo.json index 1cc5adf0fdf9a457da473ac49f1422925478a5c0..74c67709373109ccba28b36f857f5ce6e06de5e3 100644 --- a/MEC011/SRV/APPSAQ/jsons/ServiceInfo.json +++ b/MEC011/SRV/APPSAQ/jsons/ServiceInfo.json @@ -3,6 +3,5 @@ "version": "0.1", "state": "INACTIVE", "serializer": "XML", - "serInstanceId": "3F897E85-ABCD-FFFF-A957-FCF0CCE649FD", "transportId": "transportId" } \ No newline at end of file diff --git a/MEC011/SRV/APPSAQ/libraries/Server.py b/MEC011/SRV/APPSAQ/libraries/Server.py new file mode 100644 index 0000000000000000000000000000000000000000..fb646702b03e99229758efb85e78ebb9114bcbc5 --- /dev/null +++ b/MEC011/SRV/APPSAQ/libraries/Server.py @@ -0,0 +1,148 @@ +#!/usr/bin/python3 + +from http.server import BaseHTTPRequestHandler, HTTPServer +import json, os +import logging + +# Library version +__version__ = '0.0.1' + +def import_notification_json(subscription_type): + notification_type = subscription_type.split("Subscription")[0] + file_path = "./jsons/"+notification_type+".json" + logging.info(file_path) + logging.info(os.listdir()) + try: + with open(file_path, 'r') as json_file: + # Load the JSON data + data = json.load(json_file) + logging.info(data) + return data + except FileNotFoundError: + logging.error(f"Error: File not found at {file_path}") + + +class Server ( object ): + + ROBOT_LIBRARY_VERSION = '0.0.1' + + def spawn_web_server (self, host="127.0.0.1", port=8080, timeout=15, method="POST", endpoint="/callback_url", resp_body=None): + + class GET_Server(BaseHTTPRequestHandler): + + def __call__(self, *args, **kwargs): + """Handle a request.""" + super().__init__(*args, **kwargs) + + def __init__(self, endpoint, resp_body): + self.resp_body = resp_body + self.endpoint = endpoint + + def do_GET(self): + self.send_response(200) + self.send_header('Content-Type', 'application/json') + self.end_headers() + if self.path == self.endpoint: + self.wfile.write(json.dumps(self.resp_body).encode(encoding='utf_8')) + else: + self.wfile.write(json.dumps("wrong endpoint").encode(encoding='utf_8')) + + class POST_Server(BaseHTTPRequestHandler): + + def __call__(self, *args, **kwargs): + """Handle a request.""" + super().__init__(*args, **kwargs) + + def __init__(self, endpoint, resp_body): + self.resp_body = resp_body + self.endpoint = endpoint + self.req_body = None + + + def do_POST(self): + self.send_response(200) + self.send_header('Content-Type', 'application/json') + self.end_headers() + + #if self.path == self.endpoint: + # self.wfile.write(json.dumps(self.resp_body).encode(encoding='utf_8')) + #else: + # self.wfile.write(json.dumps("wrong endpoint").encode(encoding='utf_8')) + + content_len = int(self.headers.get('Content-Length')) + post_body = self.rfile.read(content_len) + self.req_body=post_body + + def get_req_body(self): + return self.req_body + + def get_resp_body(self): + return self.resp_body + + + class PUT_Server(BaseHTTPRequestHandler): + + def __call__(self, *args, **kwargs): + """Handle a request.""" + super().__init__(*args, **kwargs) + + def __init__(self, endpoint, resp_body): + self.resp_body = resp_body + self.endpoint = endpoint + + def do_PUT(self): + self.send_response(200) + self.send_header('Content-Type', 'application/json') + self.end_headers() + if self.path == self.endpoint: + self.wfile.write(json.dumps(self.resp_body).encode(encoding='utf_8')) + else: + self.wfile.write(json.dumps("wrong endpoint").encode(encoding='utf_8')) + + class DELETE_Server(BaseHTTPRequestHandler): + + def __call__(self, *args, **kwargs): + """Handle a request.""" + super().__init__(*args, **kwargs) + + def __init__(self, endpoint, resp_body): + self.resp_body = resp_body + self.endpoint = endpoint + + def do_DELETE(self): + self.send_response(200) + self.send_header('Content-Type', 'application/json') + self.end_headers() + if self.path == self.endpoint: + self.wfile.write(json.dumps(self.resp_body).encode(encoding='utf_8')) + else: + self.wfile.write(json.dumps("wrong endpoint").encode(encoding='utf_8')) + + if method == "GET": + self.handler = GET_Server(endpoint, resp_body) + elif method == "POST": + self.handler = POST_Server(endpoint, resp_body) + elif method == "PUT": + self.handler = PUT_Server(endpoint, resp_body) + elif method == "DELETE": + self.handler = DELETE_Server(endpoint, resp_body) + else: + logging.info("Error, unknown endpoint") + exit(1) + + self.app = HTTPServer((host, int(port)), self.handler) + self.app.timeout = int(timeout) + + + self.app.handle_request() + self.app.server_close() + logging.info(self.handler.get_resp_body()) + ## If a notification is received, then is returned. Otherwise an empty dictionary. + if(self.handler.get_req_body()!=None): + return json.loads(self.handler.get_req_body().decode("windows-1252")) + + ##OLD mechanism commented: if no response is received, then read the Notification JSON file and return it. + notification_json= import_notification_json(self.handler.get_resp_body()) + #return notification_json ##Decomment if you want to use the notification read from file + return {} + diff --git a/MEC011/SRV/APPSAQ/schemas/SerAvailabilityNotificationSubscription.schema.json b/MEC011/SRV/APPSAQ/schemas/SerAvailabilityNotificationSubscription.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..f779cccfe3793dd7649027fe8f2cfd05a75bf67f --- /dev/null +++ b/MEC011/SRV/APPSAQ/schemas/SerAvailabilityNotificationSubscription.schema.json @@ -0,0 +1,136 @@ +{ + "title": "SerAvailabilityNotificationSubscription", + "required": [ + "subscriptionType", + "callbackReference", + "_links" + ], + "type": "object", + "properties": { + "subscriptionType": { + "type": "string", + "description": "Shall be set to SerAvailabilityNotificationSubscription.", + "examples": [ + "SerAvailabilityNotificationSubscription" + ] + }, + "callbackReference": { + "type": "string", + "description": "URI selected by the MEC application instance to receive notifications on the subscribed MEC service availability information. This shall be included in both the request and the response." + }, + "_links": { + "title": "Self", + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "title": "LinkType", + "type": "object", + "properties": { + "href": { + "type": "string", + "description": "URI referring to a resource", + "examples": [ + "/mecSerMgmtApi/example" + ] + } + }, + "description": "This type represents a type of link and may be referenced from data structures" + } + }, + "description": "Self-referring URI." + }, + "filteringCriteria": { + "title": "SerAvailabilityNotificationSubscription.FilteringCriteria", + "type": "object", + "properties": { + "serInstanceIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Identifiers of service instances about which to report events." + }, + "serNames": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of services about which to report events." + }, + "serCategories": { + "type": "array", + "items": { + "title": "CategoryRef", + "required": [ + "href", + "id", + "name", + "version" + ], + "type": "object", + "properties": { + "href": { + "type": "string", + "description": "Reference of the catalogue", + "examples": [ + "/example/catalogue1" + ] + }, + "id": { + "type": "string", + "description": "Unique identifier of the category", + "examples": [ + "id12345" + ] + }, + "name": { + "type": "string", + "description": "Name of the category, example values include RNI, Location & Bandwidth Management", + "examples": [ + "RNI" + ] + }, + "version": { + "type": "string", + "description": "Category version", + "examples": [ + "version1" + ] + } + }, + "description": "This type represents the category reference" + }, + "description": "Categories of services about which to report events." + }, + "states": { + "type": "array", + "items": { + "title": "ServiceState", + "enum": [ + "ACTIVE", + "INACTIVE", + "SUSPENDED" + ], + "type": "string", + "description": "This enumeration defines the possible states of a service.", + "examples": [ + "ACTIVE" + ] + }, + "description": "States of the services about which to report events. If the event is a state change, this filter represents the state after the change." + }, + "isLocal": { + "type": "boolean", + "description": "Indicate whether the service is located in the same locality (as defined by scopeOfLocality) as the consuming MEC application.", + "examples": [ + true + ] + } + }, + "description": "Filtering criteria to match services for which events are requested to be reported. If absent, matches all services. All child attributes are combined with the logical \"AND\" operation." + } + } + } \ No newline at end of file