From 9e8b7846f408094cd37c5530005e8ff8d5c1c1f5 Mon Sep 17 00:00:00 2001 From: elinuxhenrik Date: Fri, 5 Jun 2020 10:23:27 +0200 Subject: [PATCH] Add monitor web page to Health Check test Also add documentation. Change-Id: I4732a65d21a458711672f6f49d56d186b15bd8c8 Issue-ID: NONRTRIC-200 Signed-off-by: elinuxhenrik --- .gitignore | 4 +- docs/images/healtcheck.png | Bin 0 -> 30199 bytes docs/index.rst | 1 + docs/use-cases.rst | 19 ++++ test/usecases/healthcheck/README.md | 39 +++++++ test/usecases/healthcheck/src/main.py | 145 +++++++++++++++++++++---- test/usecases/healthcheck/src/requirements.txt | 3 + 7 files changed, 186 insertions(+), 25 deletions(-) create mode 100644 docs/images/healtcheck.png create mode 100644 docs/use-cases.rst create mode 100644 test/usecases/healthcheck/README.md create mode 100644 test/usecases/healthcheck/src/requirements.txt diff --git a/.gitignore b/.gitignore index 6136fcbc..e5a2f721 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,6 @@ docs/_build/ .project .settings .pydevproject -infer-out/ \ No newline at end of file +infer-out/ + +.vscode \ No newline at end of file diff --git a/docs/images/healtcheck.png b/docs/images/healtcheck.png new file mode 100644 index 0000000000000000000000000000000000000000..fc56e38eea87bf14834da94ba8412c8e137abb97 GIT binary patch literal 30199 zcmdSBcTkhv_b-YKE8uGZ0VyH~f)uGzqXJT-S82gQ?;S!QQBhE7B2A=skP>xT=-C@9=Dy!;j7bL{P^kaTRx+p8eSpox2)<{YiB-u3i|QZQRf<`T9sVOK=46) z(X`w35$rrHr{#Ws)0m}HwS#1QPo9?I+R+i^D|&`MK1Ig$PJF5BSkuZIS2 zbMbG%!0NQvYp#%fVn{6QZ;hAc@d(kchwlG0{4Frls?VMLTl#NzfiGH9P*AexGLn)^ zTi_WTrXBFmRH-=H5t2ICeyBlP<>_QO$_7Yh?0PR{Qiq|!NF4D<#VXT3$9|;huYlVb z`&SwUUbihT=Z6pjq4m&$3@RB)!{Gy|edxpOxwd|i1>h-lmwq??D&&`bh4HEta?ffR z)q+JWB~!lG@FKU@}@I zL5K87TKHDz0SnRh0bu(q^&x6wL5>oR=0&cu9p%)Hqg8Lz92C=q>WZZf0xbLb`?0Htp_f!1YvD6*{BvZ%P^NbSjVF3hA3Lz`xJXzR8| zBJgy3N(612=fe0A7>dIsqNyk-TzPCC3#08JJ!0Pnz(uPkb?B93PkbbFM|>aFlFs

@Rp}rTU%}ekDX_n83bbcatZ6; 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 z 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 literal 0 HcmV?d00001 diff --git a/docs/index.rst b/docs/index.rst index d20c1ba5..21469e74 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -15,6 +15,7 @@ Non-RT RIC ./developer-guide.rst ./installation-guide.rst ./overview.rst + ./use-cases.rst ./release-notes.rst * :ref:`search` diff --git a/docs/use-cases.rst b/docs/use-cases.rst new file mode 100644 index 00000000..a6d9cc5c --- /dev/null +++ b/docs/use-cases.rst @@ -0,0 +1,19 @@ +.. This work is licensed under a Creative Commons Attribution 4.0 International License. +.. http://creativecommons.org/licenses/by/4.0 +.. Copyright (C) 2020 Nordix + +Use Cases +========= + +To support the use cases defined for the Non-RT RIC, there are implementations provided in the nonrtric repo, see: + nonrtric/test/usecases + +Health Check +------------ +The Health Check use case for the Non-RT RIC is a python script that regularly creates, reads, updates, and deletes a +policy in all Near-RT RICs that support the type used by the script. A self refreshing web page provides a view of +statistics for these regular checks. + +For more information about it, see the README file in the use case's folder. + +.. image:: ./images/healtcheck.png diff --git a/test/usecases/healthcheck/README.md b/test/usecases/healthcheck/README.md new file mode 100644 index 00000000..a699e844 --- /dev/null +++ b/test/usecases/healthcheck/README.md @@ -0,0 +1,39 @@ +# Use case Health Check test +# General + +The Health Check use case test provides a python script that regularly creates, reads, updates, and deletes a policy +in all Near-RT RICs that support the type used by the script. A self refreshing web page provides a view of statistics +for these regular checks. + +# Prerequisits +To run this script Python3 needs to be installed. To install the script's dependencies, run the following command from +the `src` folder: `pip install -r requirements.txt` + +# How to run +Go to the `src/` folder and run `python3 main.py`. The script will start and run until stopped. Use the `-h` option to +see the options available for the script. + +As default, the script uses the "Hello World" policy type with ID "2". To create the instances it uses the body file +`nonrtric/test/autotest/testdata/OSC/pihw_template.json`. The body file contains the string "XXX" as a parameter value. +This string vill be replaced with dynamic data during creation. It is possible to provide a custom policy type and +body file to the script at startup. + +To see the web page, navigate to `localhost:9990/stats`. The page refreshes itself with the same interval as the script +uses. + +## License + +Copyright (C) 2020 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. + +For more information about license please see the [LICENSE](LICENSE.txt) file for details. diff --git a/test/usecases/healthcheck/src/main.py b/test/usecases/healthcheck/src/main.py index d3fed3ad..fdf6d422 100644 --- a/test/usecases/healthcheck/src/main.py +++ b/test/usecases/healthcheck/src/main.py @@ -17,6 +17,10 @@ import argparse from datetime import datetime +from jinja2 import Template +from flask import Flask, request +import os.path +from os import path from pygments.util import xrange from requests import ConnectionError import requests @@ -32,6 +36,58 @@ TIME_BETWEEN_CHECKS = 60 type_to_use = '' policy_body = '' +app = Flask(__name__) + +# Server info +HOST_IP = "::" +HOST_PORT = 9990 +APP_URL = "/stats" + +stat_page_template = """ + + + + + Non-RealTime RIC Health Check + + +

General

+ + Policy type ID:...............................{{policyTypeId}}
+ Policy body path:.............................{{policyBodyPath}}
+ Time of last check:...........................{{time}}
+ Duration of check:............................{{duration}}
+ Number of checks:.............................{{noOfChecks}}
+
+

Near-RT RICs

+ + Number of unavailable Near-RT RICS:...........{{noOfUnavailableRics}}
+ Number of Near-RT RICS not supporting type....{{noOfNotSupportingRics}}
+ Number of Near-RT RICS supporting type:.......{{noOfSupportingRics}}
+
+

Policies

+ + Number of created policies:...................{{noOfCreatedPolicies}}
+ Number of read policies:......................{{noOfReadPolicies}}
+ Number of updated policies:...................{{noOfUpdatedPolicies}}
+ Number of deleted policies:...................{{noOfDeletedPolicies}}
+
+ + +""" +type_to_use = "2" +test_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) +policy_body_path = os.path.join(test_dir, 'auto-test','testdata','OSC','pihw_template.json') + +duration = 0 +no_of_checks = 0 +no_of_unavailable_rics = 0 +no_of_rics_not_supporting_type = 0 +no_of_rics_supporting_type = 0 +no_of_created_policies = 0 +no_of_read_policies = 0 +no_of_updated_policies = 0 +no_of_deleted_policies = 0 class Ric: @@ -79,6 +135,27 @@ class PolicyCheckThread (threading.Thread): self.ric.no_of_deleted_policies += 1 +class MonitorServer (threading.Thread): + def __init__(self): + threading.Thread.__init__(self) + + def run(self): + verboseprint('Staring monitor server') + app.run(port=HOST_PORT, host=HOST_IP) + + +@app.route(APP_URL, + methods=['GET']) +def produceStatsPage(): + t = Template(stat_page_template) + page = t.render(refreshTime=TIME_BETWEEN_CHECKS, policyTypeId=type_to_use, policyBodyPath=policy_body_path, + time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), duration=duration, noOfChecks=no_of_checks, + noOfUnavailableRics=no_of_unavailable_rics, noOfNotSupportingRics=no_of_rics_not_supporting_type, + noOfSupportingRics=no_of_rics_supporting_type, noOfCreatedPolicies=no_of_created_policies, + noOfReadPolicies=no_of_read_policies, noOfUpdatedPolicies=no_of_updated_policies, + noOfDeletedPolicies=no_of_deleted_policies) + return page,200 + def get_rics_from_agent(): resp = requests.get(BASE_URL + '/rics') if not resp.ok: @@ -146,14 +223,21 @@ def delete_policy(thread_id): return True -def statistics(duration): +def statistics(): + global duration + global no_of_checks + global no_of_unavailable_rics + global no_of_rics_not_supporting_type + global no_of_rics_supporting_type + global no_of_created_policies + global no_of_read_policies + global no_of_updated_policies + global no_of_deleted_policies + + # Clear ric data between checks as it may have changed since last check. no_of_unavailable_rics = 0 no_of_rics_not_supporting_type = 0 no_of_rics_supporting_type = 0 - no_of_created_policies = 0 - no_of_read_policies = 0 - no_of_updated_policies = 0 - no_of_deleted_policies = 0 for ric in rics.values(): if not (ric.state == 'AVAILABLE' or ric.state == 'CONSISTENCY_CHECK'): @@ -167,17 +251,17 @@ def statistics(duration): else: no_of_rics_not_supporting_type += 1 - print(f'*********** Statistics {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} ***********') - print(f'Duration of check: {duration.total_seconds()} seconds') - print(f'Number of checks: {no_of_checks}') - print(f'Number of unavailable rics: {no_of_unavailable_rics}') - print(f'Number of rics not supporting type: {no_of_rics_not_supporting_type}') - print(f'Number of rics supporting type: {no_of_rics_supporting_type}') - print(f'Number of created policies: {no_of_created_policies}') - print(f'Number of read policies: {no_of_read_policies}') - print(f'Number of updated policies: {no_of_updated_policies}') - print(f'Number of deleted policies: {no_of_deleted_policies}') - print('******************************************************') + print(f'*********** Statistics {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} *******************') + print(f'Duration of check: {duration.total_seconds()} seconds') + print(f'Number of checks: {no_of_checks}') + print(f'Number of unavailable Near-RT RICS: {no_of_unavailable_rics}') + print(f'Number of Near-RT RICS not supporting type: {no_of_rics_not_supporting_type}') + print(f'Number of Near-RT RICS supporting type: {no_of_rics_supporting_type}') + print(f'Number of created policies: {no_of_created_policies}') + print(f'Number of read policies: {no_of_read_policies}') + print(f'Number of updated policies: {no_of_updated_policies}') + print(f'Number of deleted policies: {no_of_deleted_policies}') + print('**************************************************************') def run_check_threads(rics): @@ -197,6 +281,9 @@ def run_check_threads(rics): def split_rics_equally(chunks): # prep with empty dicts return_list = [dict() for _ in xrange(chunks)] + if len(rics) < RIC_CHUNK_SIZE: + return [rics] + idx = 0 for k,v in rics.items(): return_list[idx][k] = v @@ -214,8 +301,8 @@ def get_no_of_chunks(size_of_chunks, size_to_chunk): if __name__ == '__main__': parser = argparse.ArgumentParser(prog='PROG') - parser.add_argument('policyTypeId', help='The ID of the policy type to use') - parser.add_argument('policyBodyPath', help='The path to the JSON body of the policy to create') + parser.add_argument('--policyTypeId', help='The ID of the policy type to use') + parser.add_argument('--policyBodyPath', help='The path to the JSON body of the policy to create') parser.add_argument('-v', '--verbose', action='store_true', help='Turn on verbose printing') parser.add_argument('--version', action='version', version='%(prog)s 1.0') args = vars(parser.parse_args()) @@ -226,11 +313,19 @@ if __name__ == '__main__': else: verboseprint = lambda *a, **k: None # do-nothing function - verboseprint(f'Using policy type {args["policyTypeId"]}') - verboseprint(f'Using policy file {args["policyBodyPath"]}') + if args["policyTypeId"]: + type_to_use = args["policyTypeId"] + + if args["policyBodyPath"]: + policy_body_path = args["policyBodyPath"] + if not os.path.exists(policy_body_path): + print(f'Policy body {policy_body_path} does not exist.') + sys.exit(1) - type_to_use = args["policyTypeId"] - with open(args["policyBodyPath"]) as json_file: + verboseprint(f'Using policy type {type_to_use}') + verboseprint(f'Using policy file {policy_body_path}') + + with open(policy_body_path) as json_file: policy_body = json_file.read() verboseprint(f'Policy body: {policy_body}') @@ -242,7 +337,9 @@ if __name__ == '__main__': rics = create_ric_dict(rics_from_agent) - no_of_checks = 0 + monitor_server = MonitorServer() + monitor_server.start() + while True: start_time = datetime.now() chunked_rics = split_rics_equally(get_no_of_chunks(RIC_CHUNK_SIZE, rics.__len__())) @@ -252,7 +349,7 @@ if __name__ == '__main__': no_of_checks += 1 finish_time = datetime.now() duration = finish_time - start_time - statistics(duration) + statistics() sleep_time = TIME_BETWEEN_CHECKS - duration.total_seconds() verboseprint(f'Sleeping {sleep_time} seconds') time.sleep(sleep_time) diff --git a/test/usecases/healthcheck/src/requirements.txt b/test/usecases/healthcheck/src/requirements.txt new file mode 100644 index 00000000..7018123f --- /dev/null +++ b/test/usecases/healthcheck/src/requirements.txt @@ -0,0 +1,3 @@ +Flask==1.1.1 +jinja2==2.11.2 +pygments==2.2.0 -- 2.16.6