From bf46d88d2741eda6ce0d9c851ff1ef074101d792 Mon Sep 17 00:00:00 2001 From: Luis Gomez Date: Wed, 12 Aug 2020 20:23:03 -0700 Subject: [PATCH] Add RESTCONF RFC8040 SSE test The new test is executed when using RFC8040. And it is available in Aluminium onwards. Change-Id: Iaf74ff1d119a93d8c87227e9fc0a9c25c1fd7a04 Signed-off-by: Luis Gomez --- .../notifications/notifications_basic.robot | 115 +++++++----------- ...netconf-userfeatures-rfc8040-magnesium.txt | 8 ++ .../netconf-userfeatures-rfc8040-sodium.txt | 8 ++ tools/wstools/ssereceiver.py | 77 ++++++++++++ 4 files changed, 139 insertions(+), 69 deletions(-) create mode 100644 csit/testplans/netconf-userfeatures-rfc8040-magnesium.txt create mode 100644 csit/testplans/netconf-userfeatures-rfc8040-sodium.txt create mode 100644 tools/wstools/ssereceiver.py diff --git a/csit/suites/netconf/notifications/notifications_basic.robot b/csit/suites/netconf/notifications/notifications_basic.robot index 0a1371641d..f6929c5ff3 100644 --- a/csit/suites/netconf/notifications/notifications_basic.robot +++ b/csit/suites/netconf/notifications/notifications_basic.robot @@ -46,79 +46,64 @@ Resource ${CURDIR}/../../../variables/Variables.robot *** Variables *** ${TEMPLATE_FOLDER} ${CURDIR}/templates -${RESTCONF_SUBSCRIBE_URI} restconf/operations/sal-remote:create-data-change-event-subscription -${RESTCONF_SUBSCRIBE_DATA} subscribe.xml +${DRAFT_STREAMS_URI} restconf/streams +${RFC8040_STREAMS_URI} rests/data/ietf-restconf-monitoring:restconf-state/streams ${NODES_STREAM_PATH} opendaylight-inventory:nodes/datastore=CONFIGURATION/scope=BASE -${RESTCONF_GET_SUBSCRIPTION_URI} restconf/streams/stream/data-change-event-subscription/${NODES_STREAM_PATH} -${RFC8040_NOTIFICATIONS_STREAMS_URI} rests/data/ietf-restconf-monitoring:restconf-state/streams -${RFC8040_GET_SUBSCRIPTION_URI} ${RFC8040_NOTIFICATIONS_STREAMS_URI}/stream/data-change-event-subscription/${NODES_STREAM_PATH} +${DRAFT_DCN_STREAM_URI} ${DRAFT_STREAMS_URI}/stream/data-change-event-subscription/${NODES_STREAM_PATH} +${RFC8040_DCN_STREAM_URI} ${RFC8040_STREAMS_URI}/stream/data-change-event-subscription/${NODES_STREAM_PATH} +${RESTCONF_SUBSCRIBE_DATA} subscribe.xml ${RESTCONF_CONFIG_DATA} config_data.xml -${RECEIVER_LOG_FILE} wsreceiver.log +${RECEIVER_LOG_FILE} receiver.log ${RECEIVER_OPTIONS} ${EMPTY} ${CONTROLLER_LOG_LEVEL} INFO *** Test Cases *** -Clean_Config - [Documentation] Make sure config inventory is empty. +Create_DCN_Stream + [Documentation] Create DCN stream. [Tags] critical - ${uri} = Restconf.Generate URI opendaylight-inventory:nodes config - TemplatedRequests.Delete_From_Uri uri=${uri} additional_allowed_status_codes=${DELETED_STATUS_CODES} - # TODO: Rework also other test cases to use TemplatedRequests. - -Create_Subscription - [Documentation] Subscribe for notifications. - [Tags] critical - # check get streams url passes prior to creating a subscription - ${resp} = RequestsLibrary.Get_Request restconf ${RFC8040_NOTIFICATIONS_STREAMS_URI} headers=${SEND_ACCEPT_XML_HEADERS} - Log_Response ${resp} - BuiltIn.Should_Contain ${ALLOWED_STATUS_CODES} ${resp.status_code} + Comment Create DCN subscription ${body} = OperatingSystem.Get_File ${TEMPLATE_FOLDER}/${RESTCONF_SUBSCRIBE_DATA} - BuiltIn.Log ${RESTCONF_SUBSCRIBE_URI} - BuiltIn.Log ${body} ${uri} = Restconf.Generate URI sal-remote:create-data-change-event-subscription rpc ${resp} = RequestsLibrary.Post_Request restconf ${uri} headers=${SEND_ACCEPT_XML_HEADERS} data=${body} Log_Response ${resp} BuiltIn.Should_Contain ${ALLOWED_STATUS_CODES} ${resp.status_code} -Check_Notification_Stream - [Documentation] Check any notification stream via RESTCONF is accessible +Subscribe_To_DCN_Stream + [Documentation] Subscribe to DCN streams. [Tags] critical - ${resp} = RequestsLibrary.Get_Request restconf ${RFC8040_NOTIFICATIONS_STREAMS_URI} headers=${SEND_ACCEPT_XML_HEADERS} - Log_Response ${resp} - BuiltIn.Should_Contain ${ALLOWED_STATUS_CODES} ${resp.status_code} - ${root}= XML.Parse XML ${resp.content} - ${name}= Get Elements Texts ${root} stream/name - BuiltIn.Log ${name[0]} - ${resp} = RequestsLibrary.Get_Request restconf ${RFC8040_NOTIFICATIONS_STREAMS_URI}/stream=${name[0]}/access=JSON/location headers=${SEND_ACCEPT_XML_HEADERS} - Log_Response ${resp} - BuiltIn.Should_Contain ${ALLOWED_STATUS_CODES} ${resp.status_code} - -Check_Subscription - [Documentation] Get & check subscription ... - [Tags] critical - ${uri} = Set Variable If "${USE_RFC8040}" == "False" ${RESTCONF_GET_SUBSCRIPTION_URI} ${RFC8040_GET_SUBSCRIPTION_URI} + ${uri} = Set Variable If "${USE_RFC8040}" == "False" ${DRAFT_DCN_STREAM_URI} ${RFC8040_DCN_STREAM_URI} ${resp} = RequestsLibrary.Get_Request restconf ${uri} headers=${SEND_ACCEPT_XML_HEADERS} Log_Response ${resp} BuiltIn.Should_Contain ${ALLOWED_STATUS_CODES} ${resp.status_code} - ${location} = XML.Get Element Text ${resp.content} + ${location} = XML.Get_Element_Text ${resp.content} BuiltIn.Log ${location} BuiltIn.Log ${resp.headers["Location"]} Should Contain ${location} ${resp.headers["Location"]} BuiltIn.Set_Suite_Variable ${location} +List_DCN_Streams + [Documentation] List DCN streams. + [Tags] critical + ${uri} = BuiltIn.Set_Variable_If "${USE_RFC8040}" == "False" ${DRAFT_STREAMS_URI} ${RFC8040_STREAMS_URI} + ${resp} = RequestsLibrary.Get_Request restconf ${uri} headers=${SEND_ACCEPT_XML_HEADERS} + Log_Response ${resp} + BuiltIn.Should_Contain ${ALLOWED_STATUS_CODES} ${resp.status_code} + Comment Stream only shows in RFC URL. + BuiltIn.Run_Keyword_If "${USE_RFC8040}" == "True" BuiltIn.Should_Contain ${resp.text} ${NODES_STREAM_PATH} + Start_Receiver [Documentation] Start the websocket listener - ${output} = SSHLibrary.Write python wsreceiver.py --uri ${location} --count 2 --logfile ${RECEIVER_LOG_FILE} ${RECEIVER_OPTIONS} + ${output} = BuiltIn.Run_Keyword_If "${USE_RFC8040}" == "False" SSHLibrary.Write python wsreceiver.py --uri ${location} --count 2 --logfile ${RECEIVER_LOG_FILE} ${RECEIVER_OPTIONS} + ... ELSE SSHLibrary.Write python3 ssereceiver.py --controller ${ODL_SYSTEM_IP} --logfile ${RECEIVER_LOG_FILE} BuiltIn.Log ${output} ${output} = SSHLibrary.Read delay=2s BuiltIn.Log ${output} -Change_Config - [Documentation] Make a change in configuration. +Change_DS_Config + [Documentation] Make a change in DS configuration. [Tags] critical ${body} = OperatingSystem.Get_File ${TEMPLATE_FOLDER}/${RESTCONF_CONFIG_DATA} - ${uri} = Set Variable If "${USE_RFC8040}" == "False" ${CONFIG_API} rests/data - BuiltIn.Log ${body} + ${uri} = BuiltIn.Set_Variable_If "${USE_RFC8040}" == "False" /restconf/config /rests/data ${resp} = RequestsLibrary.Post_Request restconf ${uri} headers=${SEND_ACCEPT_XML_HEADERS} data=${body} Log_Response ${resp} BuiltIn.Should_Contain ${ALLOWED_STATUS_CODES} ${resp.status_code} @@ -127,8 +112,8 @@ Change_Config Log_Response ${resp} BuiltIn.Should_Contain ${ALLOWED_STATUS_CODES} ${resp.status_code} -Check_Create_Notification - [Documentation] Check the websocket listener log for a change notification. +Check_Notification + [Documentation] Check the WSS/SSE listener log for a change notification. [Tags] critical ${notification} = SSHLibrary.Execute_Command cat ${RECEIVER_LOG_FILE} BuiltIn.Log ${notification} @@ -141,46 +126,38 @@ Check_Create_Notification BuiltIn.Should_Contain ${notification} BuiltIn.Should_Contain ${notification} +Check_Delete_Notification + [Documentation] Check the websocket listener log for a delete notification. + [Tags] critical + BuiltIn.Should_Contain ${notification} deleted + Check_Bug_3934 [Documentation] Check the websocket listener log for the bug correction. [Tags] critical ${data} = OperatingSystem.Get_File ${TEMPLATE_FOLDER}/${RESTCONF_CONFIG_DATA} BuiltIn.Log ${data} BuiltIn.Log ${notification} - ${packed_data} = String.Remove_String ${data} ${SPACE} - ${packed_notification} = String.Remove_String ${notification} ${SPACE} + ${packed_data} = String.Remove_String ${data} \n + ${packed_data} = String.Remove_String ${packed_data} ${SPACE} + ${packed_notification} = String.Remove_String ${notification} \n + ${packed_notification} = String.Remove_String ${packed_notification} \\n + ${packed_notification} = String.Remove_String ${packed_notification} ${SPACE} BuiltIn.Should_Contain ${packed_notification} ${packed_data} [Teardown] Report_Failure_Due_To_Bug 3934 -Check_Delete_Notification - [Documentation] Check the websocket listener log for a delete notification. - [Tags] critical - BuiltIn.Should_Contain ${notification} deleted - *** Keywords *** Setup_Everything [Documentation] SSH-login to mininet machine, create HTTP session, ... prepare directories for responses, put Python tool to mininet machine, setup imported resources. SetupUtils.Setup_Utils_For_Setup_And_Teardown - Disable SSE On Controller ${CONTROLLER} - ClusterManagement.Stop_Members_From_List_Or_All - ClusterManagement.Start_Members_From_List_Or_All KarafKeywords.Open_Controller_Karaf_Console_On_Background - TemplatedRequests.Create_Default_Session - SSHLibrary.Set_Default_Configuration prompt=${TOOLS_SYSTEM_PROMPT} - SSHLibrary.Open_Connection ${TOOLS_SYSTEM_IP} alias=receiver - SSHKeywords.Flexible_Mininet_Login + SSHKeywords.Open_Connection_To_Tools_System SSHLibrary.Put_File ${CURDIR}/../../../../tools/wstools/wsreceiver.py - ${output_log} ${error_log} = SSHLibrary.Execute Command sudo apt-get install -y python-pip return_stdout=True return_stderr=True - BuiltIn.Log ${output_log} - BuiltIn.Log ${error_log} - ${output_log} = SSHLibrary.Execute_Command sudo pip install websocket-client - BuiltIn.Log ${output_log} - ${output_log} = SSHLibrary.Execute_Command python -c "help('modules')" - BuiltIn.Log ${output_log} - Should Contain ${output_log} websocket + SSHLibrary.Put_File ${CURDIR}/../../../../tools/wstools/ssereceiver.py + SSHLibrary.Execute Command sudo apt-get install -y python-pip return_stdout=True return_stderr=True + SSHLibrary.Execute_Command sudo pip install websocket-client return_stdout=True return_stderr=True + SSHLibrary.Execute Command sudo python3 -m pip install asyncio aiohttp aiohttp-sse-client coroutine return_stdout=True return_stderr=True RequestsLibrary.Create Session restconf http://${ODL_SYSTEM_IP}:${RESTCONFPORT} auth=${AUTH} - BuiltIn.Log http://${ODL_SYSTEM_IP}:${RESTCONFPORT} KarafKeywords.Execute_Controller_Karaf_Command_On_Background log:set ${CONTROLLER_LOG_LEVEL} Teardown_Everything @@ -194,4 +171,4 @@ Log_Response [Documentation] Log response. BuiltIn.Log ${resp} BuiltIn.Log ${resp.headers} - BuiltIn.Log ${resp.content} + BuiltIn.Log ${resp.text} diff --git a/csit/testplans/netconf-userfeatures-rfc8040-magnesium.txt b/csit/testplans/netconf-userfeatures-rfc8040-magnesium.txt new file mode 100644 index 0000000000..3f532503bd --- /dev/null +++ b/csit/testplans/netconf-userfeatures-rfc8040-magnesium.txt @@ -0,0 +1,8 @@ +# Place the suites in run order: +integration/test/csit/suites/netconf/ready +integration/test/csit/suites/netconf/apidocs +integration/test/csit/suites/netconf/MDSAL +integration/test/csit/suites/netconf/CRUD +integration/test/csit/suites/netconf/CRUD-ACTION +integration/test/csit/suites/netconf/KeyAuth + diff --git a/csit/testplans/netconf-userfeatures-rfc8040-sodium.txt b/csit/testplans/netconf-userfeatures-rfc8040-sodium.txt new file mode 100644 index 0000000000..3f532503bd --- /dev/null +++ b/csit/testplans/netconf-userfeatures-rfc8040-sodium.txt @@ -0,0 +1,8 @@ +# Place the suites in run order: +integration/test/csit/suites/netconf/ready +integration/test/csit/suites/netconf/apidocs +integration/test/csit/suites/netconf/MDSAL +integration/test/csit/suites/netconf/CRUD +integration/test/csit/suites/netconf/CRUD-ACTION +integration/test/csit/suites/netconf/KeyAuth + diff --git a/tools/wstools/ssereceiver.py b/tools/wstools/ssereceiver.py new file mode 100644 index 0000000000..72752cd63d --- /dev/null +++ b/tools/wstools/ssereceiver.py @@ -0,0 +1,77 @@ +"""Server_Sent event messages receiver. + +The tool receives and logs data from specified URI via SSE""" + +import argparse +import logging +import asyncio +import aiohttp +from aiohttp_sse_client import client as sse_client + + +async def get_events(): + conn = aiohttp.TCPConnector() + auth = aiohttp.BasicAuth(args.user, args.password) + client = aiohttp.ClientSession(connector=conn, auth=auth) + async with sse_client.EventSource( + "http://" + args.controller + ":8181" + args.uri, session=client + ) as event_source: + try: + async for event in event_source: + logger.info(event) + except ConnectionError: + pass + + +def parse_arguments(): + """Use argparse to get arguments, + + Returns: + :return: args object + """ + parser = argparse.ArgumentParser() + parser.add_argument("--controller", default="127.0.0.1", help="Controller IP") + parser.add_argument( + "--uri", + default="/rests/notif/data-change-event-subscription/opendaylight-inventory:nodes/datastore=CONFIGURATION/scope=BASE", + help="URI endpoint to connect", + ) + parser.add_argument( + "--count", type=int, default=1, help="Number of messages to receive" + ) + parser.add_argument("--user", default="admin", help="Controller User") + parser.add_argument("--password", default="admin", help="Controller Password") + parser.add_argument("--logfile", default="ssereceiver.log", help="Log file name") + parser.add_argument( + "--debug", + dest="loglevel", + action="store_const", + const=logging.DEBUG, + default=logging.INFO, + help="Log level", + ) + args = parser.parse_args() + return args + + +def main(): + loop = asyncio.get_event_loop() + loop.run_until_complete(get_events()) + loop.run_until_complete(asyncio.sleep(0)) + loop.close() + + +if __name__ == "__main__": + args = parse_arguments() + logger = logging.getLogger("logger") + log_formatter = logging.Formatter("%(asctime)s %(levelname)s: %(message)s") + console_handler = logging.StreamHandler() + file_handler = logging.FileHandler(args.logfile, mode="w") + console_handler.setFormatter(log_formatter) + file_handler.setFormatter(log_formatter) + logger.addHandler(console_handler) + logger.addHandler(file_handler) + logger.setLevel(args.loglevel) + logger.info("Starting to receive server-sent event messages") + logger.info(args) + main() -- 2.36.6