From 6a61ad84e1df33bec201df43dc0b217d892f91b9 Mon Sep 17 00:00:00 2001 From: rajendrajaiswal Date: Mon, 16 Dec 2019 14:24:02 +0000 Subject: PNF Simulator to support Control Loop subscription model Change-Id: I9919edb32f3f68f86fad28c908f808fcee3fc548 Issue-ID: INT-1312 Signed-off-by: rajendrajaiswal --- .../docker-compose/FileReadyEvent.json | 1 + .../pmsh-pnf-sim/docker-compose/docker-compose.yml | 20 ++++ .../docker-compose/pnf-subscriptions.yang | 47 ++++++++++ test/mocks/pmsh-pnf-sim/docker-compose/pnf.py | 103 +++++++++++++++++++++ .../mocks/pmsh-pnf-sim/docker-compose/pnfconfig.py | 3 + .../pmsh-pnf-sim/docker-compose/schedulepmjob.py | 14 +++ test/mocks/pmsh-pnf-sim/docker-compose/sftp/pm.xml | 41 ++++++++ test/mocks/pmsh-pnf-sim/docker-compose/startup.xml | 26 ++++++ .../pmsh-pnf-sim/docker-compose/subscriber.py | 78 ++++++++++++++++ 9 files changed, 333 insertions(+) create mode 100644 test/mocks/pmsh-pnf-sim/docker-compose/FileReadyEvent.json create mode 100644 test/mocks/pmsh-pnf-sim/docker-compose/docker-compose.yml create mode 100644 test/mocks/pmsh-pnf-sim/docker-compose/pnf-subscriptions.yang create mode 100644 test/mocks/pmsh-pnf-sim/docker-compose/pnf.py create mode 100644 test/mocks/pmsh-pnf-sim/docker-compose/pnfconfig.py create mode 100644 test/mocks/pmsh-pnf-sim/docker-compose/schedulepmjob.py create mode 100644 test/mocks/pmsh-pnf-sim/docker-compose/sftp/pm.xml create mode 100644 test/mocks/pmsh-pnf-sim/docker-compose/startup.xml create mode 100644 test/mocks/pmsh-pnf-sim/docker-compose/subscriber.py diff --git a/test/mocks/pmsh-pnf-sim/docker-compose/FileReadyEvent.json b/test/mocks/pmsh-pnf-sim/docker-compose/FileReadyEvent.json new file mode 100644 index 000000000..0f8df3dc0 --- /dev/null +++ b/test/mocks/pmsh-pnf-sim/docker-compose/FileReadyEvent.json @@ -0,0 +1 @@ +{"event":{"commonEventHeader":{"version":"4.0.1","vesEventListenerVersion":"7.0.1","domain":"notification","eventName":"Noti_RnNode-Ericsson_FileReady","eventId":"FileReady_1797490e-10ae-4d48-9ea7-3d7d790b25e1","lastEpochMicrosec":8745745764578,"priority":"Normal","reportingEntityName":"otenb5309","sequence":0,"sourceName":"oteNB5309","startEpochMicrosec":8745745764578,"timeZoneOffset":"UTC+05.30"},"notificationFields":{"changeIdentifier":"PM_MEAS_FILES","changeType":"FileReady","notificationFieldsVersion":"2.0","arrayOfNamedHashMap":[{"name":"Apmfilename.xml.gz","hashMap":{"location":"sftp://bulkpm:bulkpm@sftpserver:22/upload/Apmfilename.xml.gz","compression":"gzip","fileFormatType":"org.3GPP.32.435#measCollec","fileFormatVersion":"V10"}}]}}} \ No newline at end of file diff --git a/test/mocks/pmsh-pnf-sim/docker-compose/docker-compose.yml b/test/mocks/pmsh-pnf-sim/docker-compose/docker-compose.yml new file mode 100644 index 000000000..419e54bb2 --- /dev/null +++ b/test/mocks/pmsh-pnf-sim/docker-compose/docker-compose.yml @@ -0,0 +1,20 @@ +version: '3' + +services: + netopeer2: + image: registry.gitlab.com/blue-onap/docker/sysrepo-netopeer2:v0.7-r2-5 + container_name: netopeer2 + restart: always + ports: + - "830:830" + - "6513:6513" + volumes: + - ./:/config/models/pnf-subscriptions + sftp: + container_name: sftpserver + image: atmoz/sftp + ports: + - "2222:22" + volumes: + - /host/upload:/home/admin + command: admin:admin:1001 \ No newline at end of file diff --git a/test/mocks/pmsh-pnf-sim/docker-compose/pnf-subscriptions.yang b/test/mocks/pmsh-pnf-sim/docker-compose/pnf-subscriptions.yang new file mode 100644 index 000000000..6adce57cc --- /dev/null +++ b/test/mocks/pmsh-pnf-sim/docker-compose/pnf-subscriptions.yang @@ -0,0 +1,47 @@ +module pnf-subscriptions { + namespace "http://onap.org/pnf-subscriptions"; + prefix subscriptions; + + revision "2019-11-22" { + description + "initial version"; + } + container subscriptions { + list configuration{ + key "subscriptionName"; + leaf subscriptionName { + type string; + } + leaf administrativeState { + type string; + } + leaf fileBasedGP { + type int16; + } + leaf fileLocation { + type string; + } + list measurementGroups { + key "id"; + leaf id{ + type int16; + } + container measurementGroup { + list measurementTypes { + key "measurementType"; + leaf measurementType { + type string; + } + } + list managedObjectDNsBasic { + key "DN"; + leaf DN { + type string; + } + } + } + + } + } + } +} \ No newline at end of file diff --git a/test/mocks/pmsh-pnf-sim/docker-compose/pnf.py b/test/mocks/pmsh-pnf-sim/docker-compose/pnf.py new file mode 100644 index 000000000..05b09ba17 --- /dev/null +++ b/test/mocks/pmsh-pnf-sim/docker-compose/pnf.py @@ -0,0 +1,103 @@ +import gzip +import json +import os +import shutil +import time +import xml.etree.ElementTree as ET +from random import randint +import requests +import pnfconfig + + +class PNF: + """ Handle update on xml and send file ready event to ves collector """ + def __init__(self): + pass + + @staticmethod + def create_job_id(jobid, change_list): + """ + create new measinfo tag and add new sub element in existing xml. + :param jobid: create unique job id within xml sub element. + :param change_list: list to create sub elements itmes. + """ + try: + measurement_type = [] + meas_object_dn = [] + for items in range(len(change_list)): + if "/measurementType =" in change_list[items]: + measurement_type.append(((change_list[items].rsplit('/', 1))[1].rsplit('=', 1))[1].strip()) + if "/DN =" in change_list[items]: + meas_object_dn.append(((change_list[items].rsplit('/', 1))[1].rsplit('=', 1))[1].strip()) + script_dir = os.path.dirname(__file__) + pm_rel_file_path = "sftp/" + pm_location = os.path.join(script_dir, pm_rel_file_path) + ET.register_namespace('', "http://www.3gpp.org/ftp/specs/archive/32_series/32.435#measCollec") + tree = ET.parse(pm_location + "pm.xml") + root = tree.getroot() + attrib = {} + measinfo = ET.SubElement(root[1], 'measInfo', attrib) + attrib = {'jobId': jobid} + ET.SubElement(measinfo, 'job', attrib) + ET.SubElement(measinfo, 'granPeriod', {'duration': 'PT900S', 'endTime': '2000-03-01T14:14:30+02:00'}) + ET.SubElement(measinfo, 'repPeriod', {'duration': 'PT1800S'}) + for items in range(len(measurement_type)): + meastype = ET.SubElement(measinfo, 'measType', {'p': (items + 1).__str__()}) + meastype.text = measurement_type[items] + for items in range(len(meas_object_dn)): + measvalue = ET.SubElement(measinfo, 'measValue', {'measObjLdn': meas_object_dn[items]}) + for item in range(len(measurement_type)): + value = ET.SubElement(measvalue, 'r', {'p': (item + 1).__str__()}) + value.text = randint(100, 900).__str__() + tree.write(pm_location + "pm.xml", encoding="utf-8", xml_declaration=True) + except Exception as error: + print(error) + + @staticmethod + def delete_job_id(jobid): + """ + delete measinfo tag from existing xml pm file based on jobid. + :param jobid: element within measinfo tag. + """ + try: + script_dir = os.path.dirname(__file__) + pm_rel_file_path = "sftp/" + pm_location = os.path.join(script_dir, pm_rel_file_path) + ET.register_namespace('', "http://www.3gpp.org/ftp/specs/archive/32_series/32.435#measCollec") + tree = ET.parse(pm_location + "pm.xml") + root = tree.getroot() + for measinfo in root[1].findall( + '{http://www.3gpp.org/ftp/specs/archive/32_series/32.435#measCollec}measInfo'): + xml_id = measinfo.find('{http://www.3gpp.org/ftp/specs/archive/32_series/32.435#measCollec}job').attrib + if xml_id["jobId"] == jobid: + root[1].remove(measinfo) + tree.write(pm_location + "pm.xml", encoding="utf-8", xml_declaration=True) + except Exception as error: + print(error) + + @staticmethod + def pm_job(): + """ + create timestemp based gunzip xml file and send file ready event to ves collector. + """ + try: + script_dir = os.path.dirname(__file__) + timestemp = time.time() + pm_rel_file_path = "sftp/" + pm_location = os.path.join(script_dir, pm_rel_file_path) + shutil.copy(pm_location + "pm.xml", pm_location + "A{}.xml".format(timestemp)) + with open(pm_location + "A{}.xml".format(timestemp), 'rb') as f_in: + with gzip.open(pm_location + "A{}.xml.gz".format(timestemp), 'wb') as f_out: + shutil.copyfileobj(f_in, f_out) + os.remove(pm_location + "A{}.xml".format(timestemp)) + rel_path = "FileReadyEvent.json" + file_ready_event_path = os.path.join(script_dir, rel_path) + with open(file_ready_event_path) as json_file: + data = json_file.read().replace("pmfilename", str(timestemp)) + eventdata = json.loads(data) + url = "http://{}:{}/eventListener/v7".format(pnfconfig.VES_IP, pnfconfig.VES_PORT) + print("Sending File Ready Event to VES Collector " + url + " -- data @" + data) + headers = {'content-type': 'application/json'} + requests.post(url, json=eventdata, headers=headers) + except Exception as error: + print(error) diff --git a/test/mocks/pmsh-pnf-sim/docker-compose/pnfconfig.py b/test/mocks/pmsh-pnf-sim/docker-compose/pnfconfig.py new file mode 100644 index 000000000..ca58cea7e --- /dev/null +++ b/test/mocks/pmsh-pnf-sim/docker-compose/pnfconfig.py @@ -0,0 +1,3 @@ +VES_IP = "10.209.57.227" +VES_PORT = "30235" +ROP = 300 # in seconds diff --git a/test/mocks/pmsh-pnf-sim/docker-compose/schedulepmjob.py b/test/mocks/pmsh-pnf-sim/docker-compose/schedulepmjob.py new file mode 100644 index 000000000..ecbd74417 --- /dev/null +++ b/test/mocks/pmsh-pnf-sim/docker-compose/schedulepmjob.py @@ -0,0 +1,14 @@ +import time +import schedule +from pnf import PNF +import pnfconfig + +if __name__ == "__main__": + try: + pnf = PNF() + schedule.every(pnfconfig.rop).seconds.do(lambda: pnf.pm_job(pnfconfig.VES_IP, pnfconfig.VES_PORT)) + while True: + schedule.run_pending() + time.sleep(1) + except Exception as error: + print(error) diff --git a/test/mocks/pmsh-pnf-sim/docker-compose/sftp/pm.xml b/test/mocks/pmsh-pnf-sim/docker-compose/sftp/pm.xml new file mode 100644 index 000000000..375bbbda0 --- /dev/null +++ b/test/mocks/pmsh-pnf-sim/docker-compose/sftp/pm.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + attTCHSeizures + succTCHSeizures + attImmediateAssignProcs + succImmediateAssignProcs + + 234 + 345 + 567 + 789 + + + 890 + 901 + 123 + 234 + + + 456 + 567 + 678 + 789 + true + + + + + + + \ No newline at end of file diff --git a/test/mocks/pmsh-pnf-sim/docker-compose/startup.xml b/test/mocks/pmsh-pnf-sim/docker-compose/startup.xml new file mode 100644 index 000000000..7bd895093 --- /dev/null +++ b/test/mocks/pmsh-pnf-sim/docker-compose/startup.xml @@ -0,0 +1,26 @@ + + + + sub0 + UNLOCKED + 15 + c://PM + + 1 + + + EutranCellRelation.pmCounter1 + + + EutranCellRelation.pmCounter2 + + + ManagedElement=1,ENodeBFunction=1,EUtranCell=CityCenter1 + + + ManagedElement=1,ENodeBFunction=1,EUtranCell=CityCenter1, EUtranCellRelation=CityCenter2 + + + + + \ No newline at end of file diff --git a/test/mocks/pmsh-pnf-sim/docker-compose/subscriber.py b/test/mocks/pmsh-pnf-sim/docker-compose/subscriber.py new file mode 100644 index 000000000..44109a12d --- /dev/null +++ b/test/mocks/pmsh-pnf-sim/docker-compose/subscriber.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +import re +import sysrepo as sr +from pnf import PNF + + +def module_change_cb(sess, module_name, event, private_ctx): + """ Handle event change based on yang operation. """ + try: + change_path = "/" + module_name + ":*" + iterate = sess.get_changes_iter(change_path) + change = sess.get_change_next(iterate) + changelist = [] + operation = change.oper() + pnf = PNF() + if event == sr.SR_EV_APPLY: + print("------------------> Start Handle Change <------------------") + if operation == sr.SR_OP_CREATED: + while True: + change = sess.get_change_next(iterate) + if change is None: + break + changelist.append(change.new_val().to_string()) + result = re.findall(r'\'(.*?)\'', changelist[0]) + jobid = result[0] + print("Subscription Created : " + changelist[0]) + pnf.create_job_id(jobid, changelist) + pnf.pm_job() + elif operation == sr.SR_OP_DELETED: + changelist.append(change.old_val().to_string()) + result = re.findall(r'\'(.*?)\'', changelist[0]) + jobid = result[0] + print("Subscription Deleted : " + changelist[0]) + pnf.delete_job_id(jobid) + pnf.pm_job() + elif operation == sr.SR_OP_MODIFIED: + changelist.append(change.new_val().to_string()) + element = changelist[0] + print("Subscription Modified :" + element) + result = re.findall(r'\'(.*?)\'', changelist[0]) + jobid = result[0] + administrative_state = ((element.rsplit('/', 1)[1]).split('=', 1))[1].strip() + if administrative_state == "LOCKED": + pnf.delete_job_id(jobid) + pnf.pm_job() + elif administrative_state == "UNLOCKED": + select_xpath = "/" + module_name + ":*//*" + values = sess.get_items(select_xpath) + if values is not None: + for i in range(values.val_cnt()): + if jobid in values.val(i).to_string(): + changelist.append(values.val(i).to_string()) + pnf.create_job_id(jobid, changelist) + pnf.pm_job() + else: + print("Unknown Operation") + print("------------------> End Handle Change <------------------") + except Exception as error: + print(error) + return sr.SR_ERR_OK + + +def start(): + """ main function to create connection based on moudule name. """ + try: + module_name = "pnf-subscriptions" + conn = sr.Connection(module_name) + sess = sr.Session(conn) + subscribe = sr.Subscribe(sess) + subscribe.module_change_subscribe(module_name, module_change_cb) + sr.global_loop() + print("Application exit requested, exiting.") + except Exception as error: + print(error) + + +if __name__ == '__main__': + start() -- cgit 1.2.3-korg