Add monitor web page to Health Check test

Also add documentation.

Change-Id: I4732a65d21a458711672f6f49d56d186b15bd8c8
Issue-ID: NONRTRIC-200
Signed-off-by: elinuxhenrik <henrik.b.andersson@est.tech>
diff --git a/test/usecases/healthcheck/README.md b/test/usecases/healthcheck/README.md
new file mode 100644
index 0000000..a699e84
--- /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 d3fed3a..fdf6d42 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 @@
 type_to_use = ''
 policy_body = ''
 
+app = Flask(__name__)
+
+# Server info
+HOST_IP = "::"
+HOST_PORT = 9990
+APP_URL = "/stats"
+
+stat_page_template = """
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv=\"refresh\" content=\"{{refreshTime}}\">
+        <title>Non-RealTime RIC Health Check</title>
+    </head>
+    <body>
+        <h3>General</h3>
+        <font face=\"monospace\">
+            Policy type ID:...............................{{policyTypeId}}<br>
+            Policy body path:.............................{{policyBodyPath}}<br>
+            Time of last check:...........................{{time}}<br>
+            Duration of check:............................{{duration}}<br>
+            Number of checks:.............................{{noOfChecks}}<br>
+        </font>
+        <h3>Near-RT RICs</h3>
+        <font face=\"monospace\">
+            Number of unavailable Near-RT RICS:...........{{noOfUnavailableRics}}<br>
+            Number of Near-RT RICS not supporting type....{{noOfNotSupportingRics}}<br>
+            Number of Near-RT RICS supporting type:.......{{noOfSupportingRics}}<br>
+        </font>
+        <h3>Policies</h3>
+        <font face=\"monospace\">
+            Number of created policies:...................{{noOfCreatedPolicies}}<br>
+            Number of read policies:......................{{noOfReadPolicies}}<br>
+            Number of updated policies:...................{{noOfUpdatedPolicies}}<br>
+            Number of deleted policies:...................{{noOfDeletedPolicies}}<br>
+        </font>
+    </body>
+</html>
+"""
+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 @@
                 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 @@
     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 @@
         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 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 @@
 
 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 @@
     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"]
 
-    type_to_use = args["policyTypeId"]
-    with open(args["policyBodyPath"]) as json_file:
+    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)
+
+    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 @@
 
     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 @@
         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 0000000..7018123
--- /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