PNF Simulator to support Control Loop subscription model
Change-Id: I9919edb32f3f68f86fad28c908f808fcee3fc548
Issue-ID: INT-1312
Signed-off-by: rajendrajaiswal <rajendra.jaiswal@ericsson.com>
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 0000000..0f8df3d
--- /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 0000000..419e54b
--- /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 0000000..6adce57
--- /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 0000000..05b09ba
--- /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 0000000..ca58cea
--- /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 0000000..ecbd744
--- /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 0000000..375bbbd
--- /dev/null
+++ b/test/mocks/pmsh-pnf-sim/docker-compose/sftp/pm.xml
@@ -0,0 +1,41 @@
+<?xml version='1.0' encoding='utf-8'?>
+<measCollecFile xmlns="http://www.3gpp.org/ftp/specs/archive/32_series/32.435#measCollec" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.3gpp.org/ftp/specs/archive/32_series/32.435#measCollec http://www.3gpp.org/ftp/specs/archive/32_series/32.435#measCollec">
+ <fileHeader fileFormatVersion="32.435 V7.0" vendorName="Company NN" dnPrefix="DC=a1.companyNN.com,SubNetwork=1,IRPAgent=1">
+ <fileSender localDn="SubNetwork=CountryNN,MeContext=MEC-Gbg-1,ManagedElement=RNC-Gbg-1" elementType="RNC" />
+ <measCollec beginTime="2000-03-01T14:00:00+02:00" />
+ </fileHeader>
+ <measData>
+ <managedElement localDn="SubNetwork=CountryNN,MeContext=MEC-Gbg-1,ManagedElement=RNC-Gbg-1" userLabel="RNC Telecomville" />
+ <measInfo>
+ <job jobId="sub0" />
+ <granPeriod duration="PT900S" endTime="2000-03-01T14:14:30+02:00" />
+ <repPeriod duration="PT1800S" />
+ <measType p="1">attTCHSeizures</measType>
+ <measType p="2">succTCHSeizures</measType>
+ <measType p="3">attImmediateAssignProcs</measType>
+ <measType p="4">succImmediateAssignProcs</measType>
+ <measValue measObjLdn="RncFunction=RF-1,UtranCell=Gbg-997">
+ <r p="1">234</r>
+ <r p="2">345</r>
+ <r p="3">567</r>
+ <r p="4">789</r>
+ </measValue>
+ <measValue measObjLdn="RncFunction=RF-1,UtranCell=Gbg-998">
+ <r p="1">890</r>
+ <r p="2">901</r>
+ <r p="3">123</r>
+ <r p="4">234</r>
+ </measValue>
+ <measValue measObjLdn="RncFunction=RF-1,UtranCell=Gbg-999">
+ <r p="1">456</r>
+ <r p="2">567</r>
+ <r p="3">678</r>
+ <r p="4">789</r>
+ <suspect>true</suspect>
+ </measValue>
+ </measInfo>
+ </measData>
+ <fileFooter>
+ <measCollec endTime="2000-03-01T14:15:00+02:00" />
+ </fileFooter>
+</measCollecFile>
\ 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 0000000..7bd8950
--- /dev/null
+++ b/test/mocks/pmsh-pnf-sim/docker-compose/startup.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<subscriptions xmlns="http://onap.org/pnf-subscriptions">
+ <configuration>
+ <subscriptionName>sub0</subscriptionName>
+ <administrativeState>UNLOCKED</administrativeState>
+ <fileBasedGP>15</fileBasedGP>
+ <fileLocation>c://PM</fileLocation>
+ <measurementGroups>
+ <id>1</id>
+ <measurementGroup>
+ <measurementTypes>
+ <measurementType>EutranCellRelation.pmCounter1</measurementType>
+ </measurementTypes>
+ <measurementTypes>
+ <measurementType>EutranCellRelation.pmCounter2</measurementType>
+ </measurementTypes>
+ <managedObjectDNsBasic>
+ <DN>ManagedElement=1,ENodeBFunction=1,EUtranCell=CityCenter1</DN>
+ </managedObjectDNsBasic>
+ <managedObjectDNsBasic>
+ <DN>ManagedElement=1,ENodeBFunction=1,EUtranCell=CityCenter1, EUtranCellRelation=CityCenter2</DN>
+ </managedObjectDNsBasic>
+ </measurementGroup>
+ </measurementGroups>
+ </configuration>
+</subscriptions>
\ 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 0000000..44109a1
--- /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()