diff options
-rw-r--r-- | components/pm-subscription-handler/Dockerfile | 2 | ||||
-rwxr-xr-x | components/pm-subscription-handler/pmsh_service/mod/aai_client.py | 142 | ||||
-rw-r--r-- | components/pm-subscription-handler/pmsh_service/mod/pmsh_logging.py | 12 | ||||
-rwxr-xr-x | components/pm-subscription-handler/pmsh_service/mod/pmsh_utils.py | 170 | ||||
-rwxr-xr-x | components/pm-subscription-handler/pmsh_service/mod/subscription.py | 61 | ||||
-rwxr-xr-x | components/pm-subscription-handler/pmsh_service/pmsh_service.py | 9 | ||||
-rw-r--r-- | components/pm-subscription-handler/tests/data/aai_xnfs.json | 209 | ||||
-rw-r--r-- | components/pm-subscription-handler/tests/data/cbs_data.json | 109 | ||||
-rw-r--r-- | components/pm-subscription-handler/tests/test_aai_service.py | 77 | ||||
-rw-r--r-- | components/pm-subscription-handler/tests/test_pmsh_utils.py | 100 | ||||
-rw-r--r-- | components/pm-subscription-handler/tests/test_subscription.py (renamed from components/pm-subscription-handler/tests/test_logging.py) | 27 | ||||
-rw-r--r-- | components/pm-subscription-handler/tox.ini | 2 |
12 files changed, 899 insertions, 21 deletions
diff --git a/components/pm-subscription-handler/Dockerfile b/components/pm-subscription-handler/Dockerfile index e1dd16fe..4f03a306 100644 --- a/components/pm-subscription-handler/Dockerfile +++ b/components/pm-subscription-handler/Dockerfile @@ -21,8 +21,6 @@ MAINTAINER lego@est.tech ENV PMSHUSER=pmsh \ APPDIR="/opt/app/pmsh" \ - # turn on file based EELF logging - PROD_LOGGING=1 \ # set PATH & PYTHONPATH vars PATH=/usr/local/lib/python3.7/bin:$PATH:$APPDIR/bin \ PYTHONPATH=/usr/local/lib/python3.7/site-packages:./mod:./:$PYTHONPATH:$APPDIR/bin \ diff --git a/components/pm-subscription-handler/pmsh_service/mod/aai_client.py b/components/pm-subscription-handler/pmsh_service/mod/aai_client.py new file mode 100755 index 00000000..8b51a712 --- /dev/null +++ b/components/pm-subscription-handler/pmsh_service/mod/aai_client.py @@ -0,0 +1,142 @@ +# ============LICENSE_START=================================================== +# Copyright (C) 2019-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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END===================================================== +import json +import os +import uuid + +import requests +from requests.auth import HTTPBasicAuth + +import mod.pmsh_logging as logger +from mod.subscription import Subscription, XnfFilter + + +def get_pmsh_subscription_data(cbs_data): + """ + Returns the PMSH subscription data + + Args: + cbs_data: json app config from the Config Binding Service. + + Returns: + Subscription, set(Xnf): `Subscription` <Subscription> object, set of XNFs to be added. + + Raises: + RuntimeError: if AAI data cannot be retrieved. + """ + aai_xnf_data = _get_all_aai_xnf_data() + if aai_xnf_data: + sub = Subscription(**cbs_data['policy']['subscription']) + xnfs = _filter_xnf_data(aai_xnf_data, XnfFilter(**sub.nfFilter)) + else: + raise RuntimeError('Failed to get data from AAI') + return sub, xnfs + + +def _get_all_aai_xnf_data(): + """ + Return queried xnf data from the AAI service. + + Returns: + json: the json response from AAI query, else None. + """ + xnf_data = None + try: + session = requests.Session() + aai_endpoint = f'{_get_aai_service_url()}{"/aai/v16/query"}' + headers = {'accept': 'application/json', + 'content-type': 'application/json', + 'x-fromappid': 'dcae-pmsh', + 'x-transactionid': str(uuid.uuid1())} + json_data = """ + {'start': + ['network/pnfs', + 'network/generic-vnfs'] + }""" + params = {'format': 'simple', 'nodesOnly': 'true'} + response = session.put(aai_endpoint, headers=headers, + auth=HTTPBasicAuth('AAI', 'AAI'), + data=json_data, params=params, verify=False) + response.raise_for_status() + if response.ok: + xnf_data = json.loads(response.text) + except Exception as e: + logger.debug(e) + return xnf_data + + +def _get_aai_service_url(): + """ + Returns the URL of the AAI kubernetes service. + + Returns: + str: the AAI k8s service URL. + + Raises: + KeyError: if AAI env vars not found. + """ + try: + aai_service = os.environ['AAI_SERVICE_HOST'] + aai_ssl_port = os.environ['AAI_SERVICE_PORT_AAI_SSL'] + return f'https://{aai_service}:{aai_ssl_port}' + except KeyError as e: + logger.debug(f'Failed to get AAI env vars: {e}') + raise + + +def _filter_xnf_data(xnf_data, xnf_filter): + """ + Returns a list of filtered xnfs using the xnf_filter . + + Args: + xnf_data: the xnf json data from AAI. + xnf_filter: the `XnfFilter <XnfFilter>` to be applied. + + Returns: + set: a set of filtered xnfs. + + Raises: + KeyError: if AAI data cannot be parsed. + """ + xnf_set = set() + try: + for xnf in xnf_data['results']: + name_identifier = 'pnf-name' if xnf['node-type'] == 'pnf' else 'vnf-name' + if xnf_filter.is_xnf_in_filter(xnf['properties'].get(name_identifier)): + xnf_set.add(Xnf(xnf_name=xnf['properties'].get('name_identifier'), + orchestration_status=xnf['properties'].get('orchestration-status'))) + except KeyError as e: + logger.debug(f'Failed to parse AAI data: {e}') + raise + return xnf_set + + +class Xnf: + def __init__(self, **kwargs): + """ + Object representation of the XNF. + """ + self.xnf_name = kwargs.get('xnf_name') + self.orchestration_status = kwargs.get('orchestration_status') + + @classmethod + def xnf_def(cls): + return cls(xnf_name=None, orchestration_status=None) + + def __str__(self): + return f'xnf-name: {self.xnf_name}, orchestration-status: {self.orchestration_status}' diff --git a/components/pm-subscription-handler/pmsh_service/mod/pmsh_logging.py b/components/pm-subscription-handler/pmsh_service/mod/pmsh_logging.py index 30c8db8e..f88ea137 100644 --- a/components/pm-subscription-handler/pmsh_service/mod/pmsh_logging.py +++ b/components/pm-subscription-handler/pmsh_service/mod/pmsh_logging.py @@ -64,34 +64,34 @@ def get_module_logger(mod_name): return logger -def create_loggers(): +def create_loggers(logs_path="/var/log/ONAP/pmsh/logs"): """ Public method to set the global logger, launched from Run This is *not* launched during unit testing, so unit tests do not create/write log files """ - makedirs("/var/log/ONAP/pmsh/logs", exist_ok=True) + makedirs(logs_path, exist_ok=True) # create the audit log - aud_file = "/var/log/ONAP/pmsh/logs/audit.log" + aud_file = logs_path + "/audit.log" open(aud_file, "a").close() # this is like "touch" global _AUDIT_LOGGER _AUDIT_LOGGER = _create_logger("pmsh_service_audit", aud_file) # create the error log - err_file = "/var/log/ONAP/pmsh/logs/error.log" + err_file = logs_path + "/error.log" open(err_file, "a").close() # this is like "touch" global _ERROR_LOGGER _ERROR_LOGGER = _create_logger("pmsh_service_error", err_file) # create the metrics log - met_file = "/var/log/ONAP/pmsh/logs/metrics.log" + met_file = logs_path + "/metrics.log" open(met_file, "a").close() # this is like "touch" global _METRICS_LOGGER _METRICS_LOGGER = _create_logger("pmsh_service_metrics", met_file) # create the debug log - debug_file = "/var/log/ONAP/pmsh/logs/debug.log" + debug_file = logs_path + "/debug.log" open(debug_file, "a").close() # this is like "touch" global _DEBUG_LOGGER _DEBUG_LOGGER = _create_logger("pmsh_service_debug", debug_file) diff --git a/components/pm-subscription-handler/pmsh_service/mod/pmsh_utils.py b/components/pm-subscription-handler/pmsh_service/mod/pmsh_utils.py new file mode 100755 index 00000000..b665691d --- /dev/null +++ b/components/pm-subscription-handler/pmsh_service/mod/pmsh_utils.py @@ -0,0 +1,170 @@ +# ============LICENSE_START=================================================== +# Copyright (C) 2019-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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END===================================================== +import uuid + +import requests +from requests.auth import HTTPBasicAuth + +import mod.pmsh_logging as logger + + +class AppConfig: + def __init__(self, **kwargs): + self.aaf_creds = {'aaf_id': kwargs.get('aaf_identity'), + 'aaf_pass': kwargs.get('aaf_password')} + self.cert_path = kwargs.get('cert_path') + self.key_path = kwargs.get('key_path') + self.streams_subscribes = kwargs.get('streams_subscribes') + self.streams_publishes = kwargs.get('streams_publishes') + + def get_mr_sub(self, sub_name): + """ + Returns the MrSub object requested. + + Args: + sub_name: the key of the subscriber object. + + Returns: + MrSub: an Instance of an `MrSub` <MrSub> Object. + + Raises: + KeyError: if the sub_name is not found. + """ + try: + return _MrSub(sub_name, self.aaf_creds, **self.streams_subscribes[sub_name]) + except KeyError as e: + logger.debug(e) + raise + + def get_mr_pub(self, pub_name): + """ + Returns the MrPub object requested. + + Args: + pub_name: the key of the publisher object. + + Returns: + MrPub: an Instance of an `MrPub` <MrPub> Object. + + Raises: + KeyError: if the sub_name is not found. + """ + try: + return _MrPub(pub_name, self.aaf_creds, **self.streams_publishes[pub_name]) + except KeyError as e: + logger.debug(e) + raise + + @property + def cert_params(self): + """ + Returns the tls artifact paths. + + Returns: + cert_path, key_path: the path to tls cert and key. + """ + return self.cert_path, self.key_path + + +class _DmaapMrClient: + def __init__(self, aaf_creds, **kwargs): + """ + A DMaaP Message Router utility class. + Sub classes should be invoked via the AppConfig.get_mr_{pub|sub} only. + Args: + aaf_creds: a dict of aaf secure credentials. + **kwargs: a dict of streams_{subscribes|publishes} data. + """ + self.topic_url = kwargs.get('dmaap_info').get('topic_url') + self.aaf_id = aaf_creds.get('aaf_id') + self.aaf_pass = aaf_creds.get('aaf_pass') + + +class _MrPub(_DmaapMrClient): + def __init__(self, pub_name, aaf_creds, **kwargs): + self.pub_name = pub_name + super().__init__(aaf_creds, **kwargs) + + def publish_to_topic(self, event_json): + """ + Publish the event to the DMaaP Message Router topic. + + Args: + event_json: the json data to be published. + + Raises: + Exception: if post request fails. + """ + try: + session = requests.Session() + headers = {'content-type': 'application/json', 'x-transactionId': str(uuid.uuid1())} + response = session.post(self.topic_url, headers=headers, + auth=HTTPBasicAuth(self.aaf_id, self.aaf_pass), json=event_json, + verify=False) + response.raise_for_status() + except Exception as e: + logger.debug(e) + raise + + def publish_subscription_event_data(self, subscription, xnf_name): + """ + Update the Subscription dict with xnf and policy name then publish to DMaaP MR topic. + + Args: + subscription: the `Subscription` <Subscription> object. + xnf_name: the xnf to include in the event. + """ + try: + subscription_event = subscription.prepare_subscription_event(xnf_name) + self.publish_to_topic(subscription_event) + except Exception as e: + logger.debug(f'pmsh_utils.publish_subscription_event_data : {e}') + + +class _MrSub(_DmaapMrClient): + def __init__(self, sub_name, aaf_creds, **kwargs): + self.sub_name = sub_name + super().__init__(aaf_creds, **kwargs) + + def get_from_topic(self, consumer_id, consumer_group='dcae_pmsh_cg', timeout=1000): + """ + Returns the json data from the MrTopic. + + Args: + consumer_id: Within your subscribers group, a name that uniquely + identifies your subscribers process. + consumer_group: A name that uniquely identifies your subscribers. + timeout: The request timeout value in mSec. + + Returns: + list[str]: the json response from DMaaP Message Router topic, else None. + """ + topic_data = None + try: + session = requests.Session() + headers = {'accept': 'application/json', 'content-type': 'application/json'} + response = session.get(f'{self.topic_url}/{consumer_group}/{consumer_id}' + f'?timeout={timeout}', + auth=HTTPBasicAuth(self.aaf_id, self.aaf_pass), headers=headers, + verify=False) + response.raise_for_status() + if response.ok: + topic_data = response.json() + except Exception as e: + logger.debug(e) + return topic_data diff --git a/components/pm-subscription-handler/pmsh_service/mod/subscription.py b/components/pm-subscription-handler/pmsh_service/mod/subscription.py new file mode 100755 index 00000000..aa3318ac --- /dev/null +++ b/components/pm-subscription-handler/pmsh_service/mod/subscription.py @@ -0,0 +1,61 @@ +# ============LICENSE_START=================================================== +# Copyright (C) 2019 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END===================================================== +import re + + +class Subscription: + def __init__(self, **kwargs): + self.subscriptionName = kwargs.get('subscriptionName') + self.administrativeState = kwargs.get('administrativeState') + self.fileBasedGP = kwargs.get('fileBasedGP') + self.fileLocation = kwargs.get('fileLocation') + self.nfTypeModelInvariantId = kwargs.get('nfTypeModelInvariantId') + self.nfFilter = kwargs.get('nfFilter') + self.measurementGroups = kwargs.get('measurementGroups') + + def prepare_subscription_event(self, xnf_name): + """Prepare the sub event for publishing + + Args: + xnf_name: the AAI xnf name. + + Returns: + dict: the Subscription event to be published. + """ + clean_sub = {k: v for k, v in self.__dict__.items() if k != 'nfFilter'} + clean_sub.update({'nfName': xnf_name, 'policyName': f'OP-{self.subscriptionName}'}) + return clean_sub + + +class XnfFilter: + def __init__(self, **kwargs): + self.nf_sw_version = kwargs.get('swVersions') + self.nf_names = kwargs.get('nfNames') + self.regex_matcher = re.compile('|'.join(raw_regex for raw_regex in self.nf_names)) + + def is_xnf_in_filter(self, xnf_name): + """Match the xnf name against regex values in Subscription.nfFilter.nfNames + + Args: + xnf_name: the AAI xnf name. + + Returns: + bool: True if matched, else False. + """ + + return self.regex_matcher.search(xnf_name) diff --git a/components/pm-subscription-handler/pmsh_service/pmsh_service.py b/components/pm-subscription-handler/pmsh_service/pmsh_service.py index 8832f570..d8a593fb 100755 --- a/components/pm-subscription-handler/pmsh_service/pmsh_service.py +++ b/components/pm-subscription-handler/pmsh_service/pmsh_service.py @@ -15,20 +15,17 @@ # # SPDX-License-Identifier: Apache-2.0 # ============LICENSE_END===================================================== - -import os import time import mod.pmsh_logging as logger def main(): - """Entrypoint""" - if "PROD_LOGGING" in os.environ: - logger.create_loggers() + logger.create_loggers() + while True: time.sleep(30) - logger.debug("Ni! Ni! Ni!") + logger.debug("He's not the messiah, he's a very naughty boy!") if __name__ == '__main__': diff --git a/components/pm-subscription-handler/tests/data/aai_xnfs.json b/components/pm-subscription-handler/tests/data/aai_xnfs.json new file mode 100644 index 00000000..78fc6548 --- /dev/null +++ b/components/pm-subscription-handler/tests/data/aai_xnfs.json @@ -0,0 +1,209 @@ +{ + "results": [ + { + "id": "200904", + "node-type": "pnf", + "url": "/aai/v16/network/pnfs/pnf/asd", + "properties": { + "pnf-name": "nokia_123", + "pnf-id": "10cc1039-5098-4b77-9ae9-359d74d7bba6", + "equip-type": "val8", + "equip-vendor": "Nokia", + "equip-model": "val6", + "ipaddress-v4-oam": "10.10.10.25", + "orchestration-status": "Inventoried", + "sw-version": "val7", + "in-maint": false, + "serial-number": "6061ZW3", + "ipaddress-v6-oam": "2001:0db8:0:0:0:0:1428:57ab", + "resource-version": "1572609320819", + "nf-role": "gNB" + } + }, + { + "id": "209096", + "node-type": "pnf", + "url": "/aai/v16/network/pnfs/pnf/pnf201", + "properties": { + "pnf-name": "pnf201", + "pnf-id": "99700432-df00-473d-8ff9-2fb14243417a", + "equip-type": "val8", + "equip-vendor": "Nokia", + "equip-model": "val6", + "ipaddress-v4-oam": "10.10.10.32", + "sw-version": "val7", + "in-maint": false, + "serial-number": "6061ZW3", + "ipaddress-v6-oam": "2001:0db8:0:0:0:0:1428:57ab", + "resource-version": "1573053304574", + "nf-role": "gNB" + } + }, + { + "id": "217288", + "node-type": "pnf", + "url": "/aai/v16/network/pnfs/pnf/pnf202", + "properties": { + "pnf-name": "pnf_33_ericsson", + "pnf-id": "1a9dc493-402c-4d13-b5cc-d555c386860c", + "equip-type": "val8", + "equip-vendor": "Nokia", + "equip-model": "val6", + "ipaddress-v4-oam": "10.10.10.66", + "orchestration-status": "Inventoried", + "sw-version": "val7", + "in-maint": false, + "serial-number": "6061ZW3", + "ipaddress-v6-oam": "2001:0db8:0:0:0:0:1428:57ab", + "resource-version": "1573054784925", + "nf-role": "gNB" + } + }, + { + "id": "262144", + "node-type": "pnf", + "url": "/aai/v16/network/pnfs/pnf/pnf5", + "properties": { + "pnf-name": "pnf5", + "pnf-id": "cd7ee211-2595-49b6-b85c-76eab621d4b4", + "equip-type": "val8", + "equip-vendor": "Nokia", + "equip-model": "val6", + "ipaddress-v4-oam": "10.10.13.26", + "sw-version": "val7", + "in-maint": false, + "serial-number": "6061ZW3", + "ipaddress-v6-oam": "2001:0db8:0:0:0:0:1428:57ab", + "resource-version": "1572543742093", + "nf-role": "gNB" + } + }, + { + "id": "270472", + "node-type": "pnf", + "url": "/aai/v16/network/pnfs/pnf/pnf205", + "properties": { + "pnf-name": "eric_23", + "pnf-id": "d82ca5aa-cf05-407a-bc84-0ebf22cd83be", + "equip-type": "val8", + "equip-vendor": "Ericsson", + "equip-model": "val6", + "ipaddress-v4-oam": "10.40.10.26", + "orchestration-status": "Inventoried", + "sw-version": "val7", + "in-maint": false, + "serial-number": "6061ZW3", + "ipaddress-v6-oam": "2001:0db8:0:0:0:0:1428:57ab", + "resource-version": "1573064215776", + "nf-role": "gNB" + } + }, + { + "id": "278664", + "node-type": "pnf", + "url": "/aai/v16/network/pnfs/pnf/pnf300", + "properties": { + "pnf-name": "huawei_22", + "pnf-id": "eabcfaf7-b7f3-45fb-94e7-e6112fb3e8b8", + "equip-type": "val8", + "equip-vendor": "Huawei", + "equip-model": "val6", + "ipaddress-v4-oam": "30.10.10.26", + "orchestration-status": "Inventoried", + "sw-version": "val7", + "in-maint": false, + "serial-number": "6061ZW3", + "ipaddress-v6-oam": "2001:0db8:0:0:0:0:1428:57ab", + "resource-version": "1573127626866", + "nf-role": "gNB" + } + }, + { + "id": "667696", + "node-type": "pnf", + "url": "/aai/v16/network/pnfs/pnf/pnf303", + "properties": { + "pnf-name": "pnf_1111", + "pnf-id": "5cd35ce5-48a3-47c2-a5ab-787bfa5e3027", + "equip-type": "val8", + "equip-vendor": "Ericsson", + "equip-model": "val6", + "ipaddress-v4-oam": "10.60.10.26", + "sw-version": "val7", + "in-maint": false, + "serial-number": "6061ZW3", + "ipaddress-v6-oam": "2001:0db8:0:0:0:0:1428:57ab", + "resource-version": "1573138552022", + "nf-role": "gNB" + } + }, + { + "id": "671792", + "node-type": "pnf", + "url": "/aai/v16/network/pnfs/pnf/my_pnf_is_good", + "properties": { + "pnf-name": "my_pnf_is_bad", + "pnf-id": "4f043f83-3ddf-49d0-a878-ae6b2f9bd7e5", + "in-maint": false, + "resource-version": "1574701401896" + } + }, + { + "id": "241864", + "node-type": "generic-vnf", + "url": "/aai/v16/network/generic-vnfs/generic-vnf/1083ecbb-f3b3-49da-b5f5-b5962140ee99", + "properties": { + "vnf-id": "1083ecbb-f3b3-49da-b5f5-b5962140ee99", + "vnf-name": "vnf-eric-1", + "vnf-type": "demoVCPEvBNG/vCPE_vbng 5eb5661a-0cb6 0", + "service-id": "2db01c96-4baa-4393-8d79-af8d7bf4698e", + "prov-status": "PREPROV", + "orchestration-status": "Inventoried", + "in-maint": false, + "is-closed-loop-disabled": false, + "resource-version": "1575541662410", + "model-invariant-id": "7129e420-d396-4efb-af02-6b83499b12f8", + "model-version-id": "e80a6ae3-cafd-4d24-850d-e14c084a5ca9", + "model-customization-id": "376dbe87-e9c5-4f2b-80e2-a420b373ee87" + } + }, + { + "id": "241864", + "node-type": "generic-vnf", + "url": "/aai/v16/network/generic-vnfs/generic-vnf/1083ecbb-f3b3-49da-b5f5-b5962140ee99", + "properties": { + "vnf-id": "1083ecbb-f3b3-49da-b5f5-b5962140ee98", + "vnf-name": "vnf-nokia-22", + "vnf-type": "demoVCPEvBNG/vCPE_vbng 5eb5661a-0cb6 0", + "service-id": "2db01c96-4baa-4393-8d79-af8d7bf4698e", + "prov-status": "PREPROV", + "orchestration-status": "Inventoried", + "in-maint": false, + "is-closed-loop-disabled": false, + "resource-version": "1575541662410", + "model-invariant-id": "7129e420-d396-4efb-af02-6b83499b12f8", + "model-version-id": "e80a6ae3-cafd-4d24-850d-e14c084a5ca9", + "model-customization-id": "376dbe87-e9c5-4f2b-80e2-a420b373ee87" + } + }, + { + "id": "303240", + "node-type": "generic-vnf", + "url": "/aai/v16/network/generic-vnfs/generic-vnf/230f339c-3e75-4f6b-8c9c-da4759da8222", + "properties": { + "vnf-id": "230f339c-3e75-4f6b-8c9c-da4759da8222", + "vnf-name": "vcpe_1", + "vnf-type": "demoVCPEInfra/vCPE_infra 0d1234cf-100c 0", + "service-id": "2db01c96-4baa-4393-8d79-af8d7bf4698e", + "prov-status": "PREPROV", + "orchestration-status": "Inventoried", + "in-maint": false, + "is-closed-loop-disabled": false, + "resource-version": "1575542300539", + "model-invariant-id": "c6aef848-e119-4572-9643-0b68da217358", + "model-version-id": "77c1a3d9-422a-4f78-bd8f-f7a357685b25", + "model-customization-id": "1f6a305d-74b6-4b92-b407-cd03284b6432" + } + } + ] +}
\ No newline at end of file diff --git a/components/pm-subscription-handler/tests/data/cbs_data.json b/components/pm-subscription-handler/tests/data/cbs_data.json new file mode 100644 index 00000000..ccc0626d --- /dev/null +++ b/components/pm-subscription-handler/tests/data/cbs_data.json @@ -0,0 +1,109 @@ +{ + "policy":{ + "subscription":{ + "subscriptionName":"ExtraPM-All-gNB-R2B", + "administrativeState":"UNLOCKED", + "fileBasedGP":15, + "fileLocation":"\/pm\/pm.xml", + "nfTypeModelInvariantId":"2829292", + "nfFilter":{ + "swVersions":[ + "1.0.0", + "1.0.1" + ], + "nfNames":[ + "^pnf.*", + "^vnf.*" + ] + }, + "measurementGroups":[ + { + "measurementGroup":{ + "measurementTypes":[ + { + "measurementType":"countera" + }, + { + "measurementType":"counterb" + } + ], + "managedObjectDNsBasic":[ + { + "DN":"dna" + }, + { + "DN":"dnb" + } + ] + } + }, + { + "measurementGroup":{ + "measurementTypes":[ + { + "measurementType":"counterc" + }, + { + "measurementType":"counterd" + } + ], + "managedObjectDNsBasic":[ + { + "DN":"dnc" + }, + { + "DN":"dnd" + } + ] + } + } + ] + } + }, + "config":{ + "aaf_password":"demo123456!", + "aaf_identity":"dcae@dcae.onap.org", + "cert_path":"/opt/app/pm-mapper/etc/certs/cert.pem", + "key_path":"/opt/app/pm-mapper/etc/certs/key.pem", + "streams_subscribes":{ + "aai_subscriber":{ + "type":"message_router", + "dmaap_info":{ + "topic_url":"https://node:30226/events/AAI_EVENT", + "client_role":"org.onap.dcae.aaiSub", + "location":"san-francisco", + "client_id":"1575976809466" + } + }, + "policy_pm_subscriber":{ + "type":"message_router", + "dmaap_info":{ + "topic_url":"https://node:30226/events/org.onap.dmaap.mr.PM_SUBSCRIPTIONS", + "client_role":"org.onap.dcae.pmSubscriber", + "location":"san-francisco", + "client_id":"1575876809456" + } + } + }, + "streams_publishes":{ + "policy_pm_publisher":{ + "type":"message_router", + "dmaap_info":{ + "topic_url":"https://node:30226/events/org.onap.dmaap.mr.PM_SUBSCRIPTIONS", + "client_role":"org.onap.dcae.pmPublisher", + "location":"san-francisco", + "client_id":"1475976809466" + } + }, + "other_publisher":{ + "type":"message_router", + "dmaap_info":{ + "topic_url":"https://node:30226/events/org.onap.dmaap.mr.SOME_OTHER_TOPIC", + "client_role":"org.onap.dcae.pmControlPub", + "location":"san-francisco", + "client_id":"1875976809466" + } + } + } + } +}
\ No newline at end of file diff --git a/components/pm-subscription-handler/tests/test_aai_service.py b/components/pm-subscription-handler/tests/test_aai_service.py new file mode 100644 index 00000000..7b71d3e8 --- /dev/null +++ b/components/pm-subscription-handler/tests/test_aai_service.py @@ -0,0 +1,77 @@ +# ============LICENSE_START=================================================== +# Copyright (C) 2019 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END===================================================== +import json +import os +import unittest +from test.support import EnvironmentVarGuard +from unittest import mock + +import responses +from requests import Session + +import mod.aai_client as aai_client + + +class AaiClientTestCase(unittest.TestCase): + + def setUp(self): + self.env = EnvironmentVarGuard() + self.env.set('AAI_SERVICE_HOST', '1.2.3.4') + self.env.set('AAI_SERVICE_PORT_AAI_SSL', '8443') + with open(os.path.join(os.path.dirname(__file__), 'data/cbs_data.json'), 'r') as data: + self.cbs_data = json.load(data) + with open(os.path.join(os.path.dirname(__file__), 'data/aai_xnfs.json'), 'r') as data: + self.aai_response_data = data.read() + + @mock.patch.object(Session, 'put') + def test_aai_client_get_pm_sub_data_success(self, mock_session): + mock_session.return_value.status_code = 200 + mock_session.return_value.text = self.aai_response_data + sub, xnfs = aai_client.get_pmsh_subscription_data(self.cbs_data) + self.assertEqual(sub.subscriptionName, 'ExtraPM-All-gNB-R2B') + self.assertEqual(sub.administrativeState, 'UNLOCKED') + self.assertEqual(len(xnfs), 6) + + @mock.patch.object(Session, 'put') + def test_aai_client_get_pm_sub_data_fail(self, mock_session): + mock_session.return_value.status_code = 404 + with mock.patch('aai_client._get_all_aai_xnf_data', return_value=None): + with self.assertRaises(RuntimeError): + aai_client.get_pmsh_subscription_data(self.cbs_data) + + @responses.activate + def test_aai_client_get_all_aai_xnf_data_not_found(self): + responses.add(responses.PUT, + 'https://1.2.3.4:8443/aai/v16/query?format=simple&nodesOnly=true', + json={'error': 'not found'}, status=404) + self.assertIsNone(aai_client._get_all_aai_xnf_data()) + + @responses.activate + def test_aai_client_get_all_aai_xnf_data_success(self): + responses.add(responses.PUT, + 'https://1.2.3.4:8443/aai/v16/query?format=simple&nodesOnly=true', + json={'dummy_data': 'blah_blah'}, status=200) + self.assertIsNotNone(aai_client._get_all_aai_xnf_data()) + + def test_aai_client_get_aai_service_url_fail(self): + self.env.clear() + with self.assertRaises(KeyError): + aai_client._get_aai_service_url() + + def test_aai_client_get_aai_service_url_succses(self): + self.assertEqual('https://1.2.3.4:8443', aai_client._get_aai_service_url()) diff --git a/components/pm-subscription-handler/tests/test_pmsh_utils.py b/components/pm-subscription-handler/tests/test_pmsh_utils.py new file mode 100644 index 00000000..ee79d523 --- /dev/null +++ b/components/pm-subscription-handler/tests/test_pmsh_utils.py @@ -0,0 +1,100 @@ +# ============LICENSE_START=================================================== +# Copyright (C) 2019-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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END===================================================== +import json +import os +import unittest +from unittest import mock +from unittest.mock import patch + +import responses +from requests import Session + +from mod.pmsh_utils import AppConfig +from mod.subscription import Subscription + + +class PmshUtilsTestCase(unittest.TestCase): + + def setUp(self): + with open(os.path.join(os.path.dirname(__file__), 'data/cbs_data.json'), 'r') as data: + self.cbs_data = json.load(data) + self.app_conf = AppConfig(**self.cbs_data['config']) + self.sub = Subscription(**self.cbs_data['policy']['subscription']) + + def test_utils_get_mr_sub(self): + mr_policy_sub = self.app_conf.get_mr_sub('policy_pm_subscriber') + self.assertTrue(mr_policy_sub.aaf_id, 'dcae@dcae.onap.org') + + def test_utils_get_mr_sub_fails_with_invalid_name(self): + with self.assertRaises(KeyError): + self.app_conf.get_mr_sub('invalid_sub') + + def test_utils_get_mr_pub(self): + mr_policy_pub = self.app_conf.get_mr_pub('policy_pm_publisher') + self.assertTrue(mr_policy_pub.aaf_pass, 'demo123456!') + + def test_utils_get_mr_pub_fails_with_invalid_name(self): + with self.assertRaises(KeyError): + self.app_conf.get_mr_pub('invalid_pub') + + def test_utils_get_cert_data(self): + self.assertTrue(self.app_conf.cert_params, ('/opt/app/pm-mapper/etc/certs/cert.pem', + '/opt/app/pm-mapper/etc/certs/key.pem')) + + @mock.patch.object(Session, 'post') + def test_mr_pub_publish_to_topic_success(self, mock_session): + mock_session.return_value.status_code = 200 + mr_policy_pub = self.app_conf.get_mr_pub('policy_pm_publisher') + with patch('requests.Session.post') as session_post_call: + mr_policy_pub.publish_to_topic({"dummy_val": "43c4ee19-6b8d-4279-a80f-c507850aae47"}) + session_post_call.assert_called_once() + + @responses.activate + def test_mr_pub_publish_to_topic_fail(self): + responses.add(responses.POST, + 'https://node:30226/events/org.onap.dmaap.mr.PM_SUBSCRIPTIONS', + json={'error': 'Client Error'}, status=400) + mr_policy_pub = self.app_conf.get_mr_pub('policy_pm_publisher') + with self.assertRaises(Exception): + mr_policy_pub.publish_to_topic({"dummy_val": "43c4ee19-6b8d-4279-a80f-c507850aae47"}) + + def test_mr_pub_publish_sub_event_data_success(self): + mr_policy_pub = self.app_conf.get_mr_pub('policy_pm_publisher') + with patch('mod.pmsh_utils._MrPub.publish_to_topic') as pub_to_topic_call: + mr_policy_pub.publish_subscription_event_data(self.sub, 'pnf201') + pub_to_topic_call.assert_called_once() + + @responses.activate + def test_mr_sub_get_from_topic_success(self): + responses.add(responses.GET, + 'https://node:30226/events/org.onap.dmaap.mr.PM_SUBSCRIPTIONS/' + 'dcae_pmsh_cg/1?timeout=1000', + json={"dummy_val": "43c4ee19-6b8d-4279-a80f-c507850aae47"}, status=200) + mr_policy_sub = self.app_conf.get_mr_sub('policy_pm_subscriber') + mr_topic_data = mr_policy_sub.get_from_topic(1) + self.assertIsNotNone(mr_topic_data) + + @responses.activate + def test_mr_sub_get_from_topic_fail(self): + responses.add(responses.GET, + 'https://node:30226/events/org.onap.dmaap.mr.PM_SUBSCRIPTIONS/' + 'dcae_pmsh_cg/1?timeout=1000', + json={"dummy_val": "43c4ee19-6b8d-4279-a80f-c507850aae47"}, status=400) + mr_policy_sub = self.app_conf.get_mr_sub('policy_pm_subscriber') + mr_topic_data = mr_policy_sub.get_from_topic(1) + self.assertIsNone(mr_topic_data) diff --git a/components/pm-subscription-handler/tests/test_logging.py b/components/pm-subscription-handler/tests/test_subscription.py index 790aa40c..cbf930fd 100644 --- a/components/pm-subscription-handler/tests/test_logging.py +++ b/components/pm-subscription-handler/tests/test_subscription.py @@ -15,14 +15,29 @@ # # SPDX-License-Identifier: Apache-2.0 # ============LICENSE_END===================================================== - +import json +import os import unittest +from mod.subscription import Subscription, XnfFilter + + +class SubscriptionTestCase(unittest.TestCase): + + def setUp(self): + with open(os.path.join(os.path.dirname(__file__), 'data/cbs_data.json'), 'r') as data: + self.cbs_data = json.load(data) + self.sub = Subscription(**self.cbs_data['policy']['subscription']) + self.xnf_filter = XnfFilter(**self.sub.nfFilter) + + def test_xnf_filter_true(self): + self.assertTrue(self.xnf_filter.is_xnf_in_filter('pnf1')) -class MyTestCase(unittest.TestCase): - def test_something(self): - self.assertEqual(True, True) + def test_xnf_filter_false(self): + self.assertFalse(self.xnf_filter.is_xnf_in_filter('PNF-33')) + def test_sub_measurement_group(self): + self.assertEqual(len(self.sub.measurementGroups), 2) -if __name__ == '__main__': - unittest.main() + def test_sub_file_location(self): + self.assertEqual(self.sub.fileLocation, '/pm/pm.xml') diff --git a/components/pm-subscription-handler/tox.ini b/components/pm-subscription-handler/tox.ini index 523338d1..6e83d552 100644 --- a/components/pm-subscription-handler/tox.ini +++ b/components/pm-subscription-handler/tox.ini @@ -30,7 +30,7 @@ deps= setenv = PYTHONPATH={toxinidir}/pmsh_service:{toxinidir}/pmsh_service/mod:{toxinidir}/tests commands= - pytest --junitxml xunit-results.xml --cov pmsh_service --cov-report xml --cov-report term tests --verbose --cov-fail-under=0 + pytest --junitxml xunit-results.xml --cov pmsh_service --cov-report xml --cov-report term tests --verbose --cov-fail-under=70 [testenv:flake8] basepython = python3 |