Seed code 00/8000/2
authorelinuxhenrik <henrik.b.andersson@est.tech>
Wed, 30 Mar 2022 07:53:51 +0000 (09:53 +0200)
committerelinuxhenrik <henrik.b.andersson@est.tech>
Fri, 1 Apr 2022 08:54:49 +0000 (10:54 +0200)
Issue-ID: NONRTRIC-709
Signed-off-by: elinuxhenrik <henrik.b.andersson@est.tech>
Change-Id: I23c2eda3e852cb70b87c7f30eca349aa28a64546

Dockerfile [new file with mode: 0644]
LICENSE.txt [new file with mode: 0644]
README.md [new file with mode: 0644]
container-tag.yaml [new file with mode: 0644]
src/main.py [new file with mode: 0644]
src/pihw_template.json [new file with mode: 0644]
src/requirements.txt [new file with mode: 0644]

diff --git a/Dockerfile b/Dockerfile
new file mode 100644 (file)
index 0000000..541a0e9
--- /dev/null
@@ -0,0 +1,14 @@
+# syntax=docker/dockerfile:1
+
+FROM python:3.8-slim-buster
+
+WORKDIR /src
+
+COPY src/requirements.txt requirements.txt
+
+RUN pip3 install -r requirements.txt
+
+COPY ./src .
+
+CMD [ "python3", "main.py"]
+
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644 (file)
index 0000000..e8798fc
--- /dev/null
@@ -0,0 +1,18 @@
+LICENSE.txt
+
+The content of this sub-directory is based on ONAP SDNC project,
+with appropriate licenses specified in the individual files.
+The modifications are covered by the following license:
+
+Unless otherwise specified, all software contained herein is licensed
+under the Apache License, Version 2.0 (the "Software License");
+you may not use this software except in compliance with the Software
+License. You may obtain a copy of the Software License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the Software License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the Software License for the specific language governing permissions
+and limitations under the Software License.
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..5daa371
--- /dev/null
+++ b/README.md
@@ -0,0 +1,42 @@
+# 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/container-tag.yaml b/container-tag.yaml
new file mode 100644 (file)
index 0000000..6b1c9db
--- /dev/null
@@ -0,0 +1,5 @@
+# The Jenkins job requires a tag to build the Docker image.
+# By default this file is in the docker build directory,
+# but the location can configured in the JJB template.
+---
+tag: 1.0.0
diff --git a/src/main.py b/src/main.py
new file mode 100644 (file)
index 0000000..330c43d
--- /dev/null
@@ -0,0 +1,357 @@
+#  ============LICENSE_START===============================================
+#  Copyright (C) 2020 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.
+#  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.
+#  ============LICENSE_END=================================================
+#
+
+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
+import sys
+import threading
+import time
+
+SERVICE_NAME = 'HealthCheck'
+BASE_URL = 'http://localhost:8081'
+RIC_CHUNK_SIZE = 10
+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 = """
+<!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"
+policy_body_path = '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:
+
+    def __init__(self, name, supported_types, state):
+        self.name = name
+        self.supports_type_to_use = self.policy_type_supported(supported_types)
+        self.state = state
+        self.no_of_created_policies = 0
+        self.no_of_read_policies = 0
+        self.no_of_updated_policies = 0
+        self.no_of_deleted_policies = 0
+
+    def update_supported_types(self, supported_types):
+        self.supports_type_to_use = self.policy_type_supported(supported_types)
+
+    def policy_type_supported(self, supported_policy_types):
+        for supported_type in supported_policy_types:
+            if type_to_use == supported_type:
+                return True
+
+        return False
+
+
+class PolicyCheckThread (threading.Thread):
+
+    def __init__(self, thread_id, ric):
+        threading.Thread.__init__(self)
+        self.thread_id = thread_id
+        self.ric = ric
+
+    def run(self):
+        verboseprint(f'Checking ric: {self.ric.name}')
+        if put_policy(self.thread_id, self.ric.name):
+            verboseprint(f'Created policy: {self.thread_id} in ric: {self.ric.name}')
+            self.ric.no_of_created_policies += 1
+            time.sleep(0.5)
+            if get_policy(self.thread_id):
+                verboseprint(f'Read policy: {self.thread_id} from ric: {self.ric.name}')
+                self.ric.no_of_read_policies += 1
+            if put_policy(self.thread_id, self.ric.name, update_value=1):
+                verboseprint(f'Updated policy: {self.thread_id} in ric: {self.ric.name}')
+                self.ric.no_of_updated_policies += 1
+            if delete_policy(self.thread_id):
+                verboseprint(f'Deleted policy: {self.thread_id} from ric: {self.ric.name}')
+                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:
+        verboseprint(f'Unable to get Rics {resp.status_code}')
+        return {}
+    return resp.json()
+
+
+def create_ric_dict(rics_as_json):
+    rics = {}
+    for ric_info in rics_as_json:
+        rics[ric_info["ricName"]] = (Ric(ric_info["ricName"], ric_info["policyTypes"], ric_info['state']))
+        verboseprint(f'Adding ric: {rics[ric_info["ricName"]]}')
+
+    return rics
+
+
+def update_rics():
+    added_rics = {}
+    for ric_info in get_rics_from_agent():
+        if ric_info["ricName"] in rics:
+            rics[ric_info["ricName"]].update_supported_types(ric_info["policyTypes"])
+            rics[ric_info["ricName"]].state = ric_info['state']
+        else:
+            added_rics[ric_info["ricName"]] = (Ric(ric_info["ricName"], ric_info["policyTypes"]))
+            verboseprint(f'Adding ric: {rics[ric_info["ricName"]]}')
+
+    rics.update(added_rics)
+
+
+def put_policy(thread_id, ric_name, update_value=0):
+    policy_id = f'thread_{thread_id}'
+    complete_url = f'{BASE_URL}/policy?type={type_to_use}&id={policy_id}&ric={ric_name}&service={SERVICE_NAME}'
+    headers = {'content-type': 'application/json'}
+    resp = requests.put(complete_url, policy_body.replace('XXX', str(thread_id + update_value)), headers=headers, verify=False)
+
+    if not resp.ok:
+        verboseprint(f'Unable to create policy {resp}')
+        return False
+    else:
+        return True
+
+
+def get_policy(thread_id):
+    policy_id = f'thread_{thread_id}'
+    complete_url = f'{BASE_URL}/policy?id={policy_id}'
+    resp = requests.get(complete_url)
+
+    if not resp.ok:
+        verboseprint(f'Unable to get policy {resp}')
+        return False
+    else:
+        return True
+
+
+def delete_policy(thread_id):
+    policy_id = f'thread_{thread_id}'
+    complete_url = f'{BASE_URL}/policy?id={policy_id}'
+    resp = requests.delete(complete_url)
+
+    if not resp.ok:
+        verboseprint(f'Unable to delete policy for policy ID {policy_id}')
+        return False
+
+    return True
+
+
+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
+
+    for ric in rics.values():
+        if not (ric.state == 'AVAILABLE' or ric.state == 'CONSISTENCY_CHECK'):
+            no_of_unavailable_rics += 1
+        elif ric.supports_type_to_use:
+            no_of_rics_supporting_type += 1
+            no_of_created_policies += ric.no_of_created_policies
+            no_of_read_policies += ric.no_of_read_policies
+            no_of_updated_policies += ric.no_of_updated_policies
+            no_of_deleted_policies += ric.no_of_deleted_policies
+        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 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):
+    thread_id = 1
+    threads = []
+    for ric in rics.values():
+        if ric.supports_type_to_use and (ric.state == 'AVAILABLE' or ric.state == 'CONSISTENCY_CHECK'): #or ric.name == 'ric_not_working':
+            policy_checker = PolicyCheckThread(thread_id, ric)
+            policy_checker.start()
+            thread_id += 1
+            threads.append(policy_checker)
+
+    for checker in threads:
+        checker.join()
+
+
+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
+        if idx < chunks-1:  # indexes start at 0
+            idx += 1
+        else:
+            idx = 0
+    return return_list
+
+
+def get_no_of_chunks(size_of_chunks, size_to_chunk):
+    (q, _) = divmod(size_to_chunk, size_of_chunks)
+    return q
+
+
+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('-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())
+
+    if args['verbose']:
+        def verboseprint(*args, **kwargs):
+            print(*args, **kwargs)
+    else:
+        verboseprint = lambda *a, **k: None # do-nothing function
+
+    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)
+
+    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}')
+
+    try:
+        rics_from_agent = get_rics_from_agent()
+    except ConnectionError:
+        print(f'A1PMS is not answering on {BASE_URL}, cannot start!')
+        sys.exit(1)
+
+    rics = create_ric_dict(rics_from_agent)
+
+    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__()))
+        for ric_chunk in chunked_rics:
+            run_check_threads(ric_chunk)
+
+        no_of_checks += 1
+        finish_time = datetime.now()
+        duration = finish_time - start_time
+        statistics()
+        sleep_time = TIME_BETWEEN_CHECKS - duration.total_seconds()
+        verboseprint(f'Sleeping {sleep_time} seconds')
+        time.sleep(sleep_time)
+        update_rics()
+
+    verboseprint('Exiting main')
diff --git a/src/pihw_template.json b/src/pihw_template.json
new file mode 100644 (file)
index 0000000..7574995
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "threshold": XXX
+}
\ No newline at end of file
diff --git a/src/requirements.txt b/src/requirements.txt
new file mode 100644 (file)
index 0000000..7189704
--- /dev/null
@@ -0,0 +1,5 @@
+Flask==2.1.1
+Jinja2>=3.0
+Pygments==2.3.1
+requests==2.22.0
+wincertstore==0.2