From: delacruzramo Date: Fri, 15 Nov 2019 13:45:32 +0000 (+0100) Subject: Unit tests for descriptor_topics X-Git-Tag: v7.0.0rc1~12 X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FNBI.git;a=commitdiff_plain;h=4b52a15a0c28878d9abe38297d228461d65d7d78;ds=sidebyside Unit tests for descriptor_topics Change-Id: Ie9bec8368f30ed023ed58bb324c38c77b9b61b00 Signed-off-by: delacruzramo --- diff --git a/osm_nbi/tests/test_descriptor_topics.py b/osm_nbi/tests/test_descriptor_topics.py new file mode 100755 index 0000000..95f871c --- /dev/null +++ b/osm_nbi/tests/test_descriptor_topics.py @@ -0,0 +1,502 @@ +#! /usr/bin/python3 +# -*- coding: utf-8 -*- + +# 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. + +__author__ = "Pedro de la Cruz Ramos, pedro.delacruzramos@altran.com" +__date__ = "2019-11-20" + +import unittest +from unittest import TestCase +from unittest.mock import Mock +from uuid import uuid4 +from http import HTTPStatus +from copy import deepcopy +from time import time +from osm_common import dbbase, fsbase, msgbase +from osm_nbi import authconn +from osm_nbi.tests.test_pkg_descriptors import db_vnfds_text, db_nsds_text +from osm_nbi.descriptor_topics import VnfdTopic +from osm_nbi.engine import EngineException +from osm_common.dbbase import DbException +import yaml + + +test_pid = str(uuid4()) +test_name = "test-user" +fake_session = {"username": test_name, "project_id": (test_pid,), "method": None, + "admin": True, "force": False, "public": False, "allow_show_user_project_role": True} + +db_vnfd_content = yaml.load(db_vnfds_text, Loader=yaml.Loader)[0] +db_nsd_content = yaml.load(db_nsds_text, Loader=yaml.Loader)[0] + + +def norm(str): + """Normalize string for checking""" + return ' '.join(str.strip().split()).lower() + + +def compare_desc(tc, d1, d2, k): + """ + Compare two descriptors + We need this function because some methods are adding/removing items to/from the descriptors + before they are stored in the database, so the original and stored versions will differ + What we check is that COMMON LEAF ITEMS are equal + Lists of different length are not compared + :param tc: Test Case wich provides context (in particular the assert* methods) + :param d1,d2: Descriptors to be compared + :param key/item being compared + :return: Nothing + """ + if isinstance(d1, dict) and isinstance(d2, dict): + for key in d1.keys(): + if key in d2: + compare_desc(tc, d1[key], d2[key], k+"[{}]".format(key)) + elif isinstance(d1, list) and isinstance(d2, list) and len(d1) == len(d2): + for i in range(len(d1)): + compare_desc(tc, d1[i], d2[i], k+"[{}]".format(i)) + else: + tc.assertEqual(d1, d2, "Wrong descriptor content: {}".format(k)) + + +class Test_VnfdTopic(TestCase): + + @classmethod + def setUpClass(cls): + cls.test_name = "test-vnfd-topic" + + @classmethod + def tearDownClass(cls): + pass + + def setUp(self): + self.db = Mock(dbbase.DbBase()) + self.fs = Mock(fsbase.FsBase()) + self.msg = Mock(msgbase.MsgBase()) + self.auth = Mock(authconn.Authconn(None, None, None)) + self.topic = VnfdTopic(self.db, self.fs, self.msg, self.auth) + + def test_new_vnfd(self): + did = db_vnfd_content["_id"] + self.fs.get_params.return_value = {} + self.fs.file_exists.return_value = False + self.fs.file_open.side_effect = lambda path, mode: open("/tmp/" + str(uuid4()), "a+b") + test_vnfd = deepcopy(db_vnfd_content) + del test_vnfd["_id"] + del test_vnfd["_admin"] + with self.subTest(i=1, t='Normal Creation'): + self.db.create.return_value = did + rollback = [] + did2, oid = self.topic.new(rollback, fake_session, {}) + db_args = self.db.create.call_args[0] + msg_args = self.msg.write.call_args[0] + self.assertEqual(len(rollback), 1, "Wrong rollback length") + self.assertEqual(msg_args[0], self.topic.topic_msg, "Wrong message topic") + self.assertEqual(msg_args[1], "created", "Wrong message action") + self.assertEqual(msg_args[2], {"_id": did}, "Wrong message content") + self.assertEqual(db_args[0], self.topic.topic, "Wrong DB topic") + self.assertEqual(did2, did, "Wrong DB VNFD id") + self.assertIsNotNone(db_args[1]["_admin"]["created"], "Wrong creation time") + self.assertEqual(db_args[1]["_admin"]["modified"], db_args[1]["_admin"]["created"], + "Wrong modification time") + self.assertEqual(db_args[1]["_admin"]["projects_read"], [test_pid], "Wrong read-only project list") + self.assertEqual(db_args[1]["_admin"]["projects_write"], [test_pid], "Wrong read-write project list") + tmp1 = test_vnfd["vdu"][0]["cloud-init-file"] + tmp2 = test_vnfd["vnf-configuration"]["juju"] + del test_vnfd["vdu"][0]["cloud-init-file"] + del test_vnfd["vnf-configuration"]["juju"] + try: + self.db.get_one.side_effect = [{"_id": did, "_admin": db_vnfd_content["_admin"]}, None] + self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []}) + msg_args = self.msg.write.call_args[0] + test_vnfd["_id"] = did + self.assertEqual(msg_args[0], self.topic.topic_msg, "Wrong message topic") + self.assertEqual(msg_args[1], "edited", "Wrong message action") + self.assertEqual(msg_args[2], test_vnfd, "Wrong message content") + db_args = self.db.get_one.mock_calls[0][1] + self.assertEqual(db_args[0], self.topic.topic, "Wrong DB topic") + self.assertEqual(db_args[1]["_id"], did, "Wrong DB VNFD id") + db_args = self.db.replace.call_args[0] + self.assertEqual(db_args[0], self.topic.topic, "Wrong DB topic") + self.assertEqual(db_args[1], did, "Wrong DB VNFD id") + admin = db_args[2]["_admin"] + db_admin = db_vnfd_content["_admin"] + self.assertEqual(admin["type"], "vnfd", "Wrong descriptor type") + self.assertEqual(admin["created"], db_admin["created"], "Wrong creation time") + self.assertGreater(admin["modified"], db_admin["created"], "Wrong modification time") + self.assertEqual(admin["projects_read"], db_admin["projects_read"], "Wrong read-only project list") + self.assertEqual(admin["projects_write"], db_admin["projects_write"], "Wrong read-write project list") + self.assertEqual(admin["onboardingState"], "ONBOARDED", "Wrong onboarding state") + self.assertEqual(admin["operationalState"], "ENABLED", "Wrong operational state") + self.assertEqual(admin["usageState"], "NOT_IN_USE", "Wrong usage state") + storage = admin["storage"] + self.assertEqual(storage["folder"], did, "Wrong storage folder") + self.assertEqual(storage["descriptor"], "package", "Wrong storage descriptor") + compare_desc(self, test_vnfd, db_args[2], "VNFD") + finally: + test_vnfd["vdu"][0]["cloud-init-file"] = tmp1 + test_vnfd["vnf-configuration"]["juju"] = tmp2 + self.db.get_one.side_effect = lambda table, filter, fail_on_empty=None, fail_on_more=None:\ + {"_id": did, "_admin": db_vnfd_content["_admin"]} + with self.subTest(i=2, t='Check Pyangbind Validation: required properties'): + tmp = test_vnfd["id"] + del test_vnfd["id"] + try: + with self.assertRaises(EngineException, msg="Accepted VNFD with a missing required property") as e: + self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []}) + self.assertEqual(e.exception.http_code, HTTPStatus.UNPROCESSABLE_ENTITY, "Wrong HTTP status code") + self.assertIn(norm("Error in pyangbind validation: '{}'".format("id")), + norm(str(e.exception)), "Wrong exception text") + finally: + test_vnfd["id"] = tmp + with self.subTest(i=3, t='Check Pyangbind Validation: additional properties'): + test_vnfd["extra-property"] = 0 + try: + with self.assertRaises(EngineException, msg="Accepted VNFD with an additional property") as e: + self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []}) + self.assertEqual(e.exception.http_code, HTTPStatus.UNPROCESSABLE_ENTITY, "Wrong HTTP status code") + self.assertIn(norm("Error in pyangbind validation: {} ({})" + .format("json object contained a key that did not exist", "extra-property")), + norm(str(e.exception)), "Wrong exception text") + finally: + del test_vnfd["extra-property"] + with self.subTest(i=4, t='Check Pyangbind Validation: property types'): + tmp = test_vnfd["short-name"] + test_vnfd["short-name"] = {"key": 0} + try: + with self.assertRaises(EngineException, msg="Accepted VNFD with a wrongly typed property") as e: + self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []}) + self.assertEqual(e.exception.http_code, HTTPStatus.UNPROCESSABLE_ENTITY, "Wrong HTTP status code") + self.assertIn(norm("Error in pyangbind validation: {} ({})" + .format("json object contained a key that did not exist", "key")), + norm(str(e.exception)), "Wrong exception text") + finally: + test_vnfd["short-name"] = tmp + with self.subTest(i=5, t='Check Input Validation: cloud-init'): + with self.assertRaises(EngineException, msg="Accepted non-existent cloud_init file") as e: + self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []}) + self.assertEqual(e.exception.http_code, HTTPStatus.BAD_REQUEST, "Wrong HTTP status code") + self.assertIn(norm("{} defined in vnf[id={}]:vdu[id={}] but not present in package" + .format("cloud-init", test_vnfd["id"], test_vnfd["vdu"][0]["id"])), + norm(str(e.exception)), "Wrong exception text") + with self.subTest(i=6, t='Check Input Validation: vnf-configuration[juju]'): + del test_vnfd["vdu"][0]["cloud-init-file"] + with self.assertRaises(EngineException, msg="Accepted non-existent charm in VNF configuration") as e: + self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []}) + self.assertEqual(e.exception.http_code, HTTPStatus.BAD_REQUEST, "Wrong HTTP status code") + self.assertIn(norm("{} defined in vnf[id={}] but not present in package".format("charm", test_vnfd["id"])), + norm(str(e.exception)), "Wrong exception text") + with self.subTest(i=7, t='Check Input Validation: mgmt-interface'): + del test_vnfd["vnf-configuration"]["juju"] + tmp = test_vnfd["mgmt-interface"] + del test_vnfd["mgmt-interface"] + try: + with self.assertRaises(EngineException, msg="Accepted VNFD without management interface") as e: + self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []}) + self.assertEqual(e.exception.http_code, HTTPStatus.UNPROCESSABLE_ENTITY, "Wrong HTTP status code") + self.assertIn(norm("'{}' is a mandatory field and it is not defined".format("mgmt-interface")), + norm(str(e.exception)), "Wrong exception text") + finally: + test_vnfd["mgmt-interface"] = tmp + with self.subTest(i=8, t='Check Input Validation: mgmt-interface[cp]'): + tmp = test_vnfd["mgmt-interface"]["cp"] + test_vnfd["mgmt-interface"]["cp"] = "wrong-cp" + try: + with self.assertRaises(EngineException, + msg="Accepted wrong management interface connection point") as e: + self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []}) + self.assertEqual(e.exception.http_code, HTTPStatus.UNPROCESSABLE_ENTITY, "Wrong HTTP status code") + self.assertIn(norm("mgmt-interface:cp='{}' must match an existing connection-point" + .format(test_vnfd["mgmt-interface"]["cp"])), + norm(str(e.exception)), "Wrong exception text") + finally: + test_vnfd["mgmt-interface"]["cp"] = tmp + with self.subTest(i=9, t='Check Input Validation: vdu[interface][external-connection-point-ref]'): + tmp = test_vnfd["vdu"][0]["interface"][0]["external-connection-point-ref"] + test_vnfd["vdu"][0]["interface"][0]["external-connection-point-ref"] = "wrong-cp" + try: + with self.assertRaises(EngineException, + msg="Accepted wrong VDU interface external connection point reference") as e: + self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []}) + self.assertEqual(e.exception.http_code, HTTPStatus.UNPROCESSABLE_ENTITY, "Wrong HTTP status code") + self.assertIn(norm("vdu[id='{}']:interface[name='{}']:external-connection-point-ref='{}'" + " must match an existing connection-point" + .format(test_vnfd["vdu"][0]["id"], test_vnfd["vdu"][0]["interface"][0]["name"], + test_vnfd["vdu"][0]["interface"][0]["external-connection-point-ref"])), + norm(str(e.exception)), "Wrong exception text") + finally: + test_vnfd["vdu"][0]["interface"][0]["external-connection-point-ref"] = tmp + with self.subTest(i=10, t='Check Input Validation: vdu[interface][internal-connection-point-ref]'): + tmp = test_vnfd["vdu"][1]["interface"][0]["internal-connection-point-ref"] + test_vnfd["vdu"][1]["interface"][0]["internal-connection-point-ref"] = "wrong-cp" + try: + with self.assertRaises(EngineException, + msg="Accepted wrong VDU interface internal connection point reference") as e: + self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []}) + self.assertEqual(e.exception.http_code, HTTPStatus.UNPROCESSABLE_ENTITY, "Wrong HTTP status code") + self.assertIn(norm("vdu[id='{}']:interface[name='{}']:internal-connection-point-ref='{}'" + " must match an existing vdu:internal-connection-point" + .format(test_vnfd["vdu"][1]["id"], test_vnfd["vdu"][1]["interface"][0]["name"], + test_vnfd["vdu"][1]["interface"][0]["internal-connection-point-ref"])), + norm(str(e.exception)), "Wrong exception text") + finally: + test_vnfd["vdu"][1]["interface"][0]["internal-connection-point-ref"] = tmp + with self.subTest(i=11, t='Check Input Validation: vdu[vdu-configuration][juju]'): + test_vnfd["vdu"][0]["vdu-configuration"] = {"juju": {"charm": "wrong-charm"}} + try: + with self.assertRaises(EngineException, msg="Accepted non-existent charm in VDU configuration") as e: + self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []}) + self.assertEqual(e.exception.http_code, HTTPStatus.BAD_REQUEST, "Wrong HTTP status code") + self.assertIn(norm("{} defined in vnf[id={}]:vdu[id={}] but not present in package" + .format("charm", test_vnfd["id"], test_vnfd["vdu"][0]["id"])), + norm(str(e.exception)), "Wrong exception text") + finally: + del test_vnfd["vdu"][0]["vdu-configuration"] + with self.subTest(i=12, t='Check Input Validation: Duplicated VLD name'): + test_vnfd["internal-vld"].append(deepcopy(test_vnfd["internal-vld"][0])) + test_vnfd["internal-vld"][1]["id"] = "wrong-internal-vld" + try: + with self.assertRaises(EngineException, msg="Accepted duplicated VLD name") as e: + self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []}) + self.assertEqual(e.exception.http_code, HTTPStatus.UNPROCESSABLE_ENTITY, "Wrong HTTP status code") + self.assertIn(norm("Duplicated VLD name '{}' in vnfd[id={}]:internal-vld[id={}]" + .format(test_vnfd["internal-vld"][1]["name"], test_vnfd["id"], + test_vnfd["internal-vld"][1]["id"])), + norm(str(e.exception)), "Wrong exception text") + finally: + del test_vnfd["internal-vld"][1] + with self.subTest(i=13, t='Check Input Validation: internal-vld[internal-connection-point][id-ref])'): + tmp = test_vnfd["internal-vld"][0]["internal-connection-point"][0]["id-ref"] + test_vnfd["internal-vld"][0]["internal-connection-point"][0]["id-ref"] = "wrong-icp-id-ref" + try: + with self.assertRaises(EngineException, msg="Accepted non-existent internal VLD ICP id-ref") as e: + self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []}) + self.assertEqual(e.exception.http_code, HTTPStatus.UNPROCESSABLE_ENTITY, "Wrong HTTP status code") + self.assertIn(norm("internal-vld[id='{}']:internal-connection-point='{}' must match an existing " + "vdu:internal-connection-point" + .format(test_vnfd["internal-vld"][0]["id"], + test_vnfd["internal-vld"][0]["internal-connection-point"][0]["id-ref"])), + norm(str(e.exception)), "Wrong exception text") + finally: + test_vnfd["internal-vld"][0]["internal-connection-point"][0]["id-ref"] = tmp + with self.subTest(i=14, t='Check Input Validation: internal-vld[ip-profile-ref])'): + test_vnfd["ip-profiles"] = [{"name": "fake-ip-profile-ref"}] + test_vnfd["internal-vld"][0]["ip-profile-ref"] = "wrong-ip-profile-ref" + try: + with self.assertRaises(EngineException, msg="Accepted non-existent IP Profile Ref") as e: + self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []}) + self.assertEqual(e.exception.http_code, HTTPStatus.UNPROCESSABLE_ENTITY, "Wrong HTTP status code") + self.assertIn(norm("internal-vld[id='{}']:ip-profile-ref='{}' does not exist" + .format(test_vnfd["internal-vld"][0]["id"], + test_vnfd["internal-vld"][0]["ip-profile-ref"])), + norm(str(e.exception)), "Wrong exception text") + finally: + del test_vnfd["ip-profiles"] + del test_vnfd["internal-vld"][0]["ip-profile-ref"] + with self.subTest(i=15, t='Check Input Validation: vdu[monitoring-param])'): + test_vnfd["monitoring-param"] = [{"id": "fake-mp-id", "vdu-monitoring-param": { + "vdu-monitoring-param-ref": "fake-vdu-mp-ref", "vdu-ref": "fake-vdu-ref"}}] + test_vnfd["vdu"][0]["monitoring-param"] = [{"id": "wrong-vdu-mp-id"}] + try: + with self.assertRaises(EngineException, msg="Accepted non-existent VDU Monitorimg Param") as e: + self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []}) + self.assertEqual(e.exception.http_code, HTTPStatus.UNPROCESSABLE_ENTITY, "Wrong HTTP status code") + mp = test_vnfd["monitoring-param"][0]["vdu-monitoring-param"] + self.assertIn(norm("monitoring-param:vdu-monitoring-param:vdu-monitoring-param-ref='{}' not defined" + " at vdu[id='{}'] or vdu does not exist" + .format(mp["vdu-monitoring-param-ref"], mp["vdu-ref"])), + norm(str(e.exception)), "Wrong exception text") + finally: + del test_vnfd["monitoring-param"] + del test_vnfd["vdu"][0]["monitoring-param"] + with self.subTest(i=16, t='Check Input Validation: vdu[vdu-configuration][metrics]'): + test_vnfd["monitoring-param"] = [{"id": "fake-mp-id", "vdu-metric": { + "vdu-metric-name-ref": "fake-vdu-mp-ref", "vdu-ref": "fake-vdu-ref"}}] + test_vnfd["vdu"][0]["vdu-configuration"] = {"metrics": [{"name": "wrong-vdu-mp-id"}]} + try: + with self.assertRaises(EngineException, msg="Accepted non-existent VDU Configuration Metric") as e: + self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []}) + self.assertEqual(e.exception.http_code, HTTPStatus.UNPROCESSABLE_ENTITY, "Wrong HTTP status code") + mp = test_vnfd["monitoring-param"][0]["vdu-metric"] + self.assertIn(norm("monitoring-param:vdu-metric:vdu-metric-name-ref='{}' not defined" + " at vdu[id='{}'] or vdu does not exist" + .format(mp["vdu-metric-name-ref"], mp["vdu-ref"])), + norm(str(e.exception)), "Wrong exception text") + finally: + del test_vnfd["monitoring-param"] + del test_vnfd["vdu"][0]["vdu-configuration"] + with self.subTest(i=17, t='Check Input Validation: scaling-group-descriptor[scaling-policy][scaling-criteria]'): + test_vnfd["monitoring-param"] = [{"id": "fake-mp-id"}] + test_vnfd["scaling-group-descriptor"] = [{ + "name": "fake-vnf-sg-name", + "vdu": [{"vdu-id-ref": "wrong-vdu-id-ref"}], + "scaling-policy": [{"name": "fake-vnf-sp-name", "scaling-criteria": [{ + "name": "fake-vnf-sc-name", "vnf-monitoring-param-ref": "wrong-vnf-mp-id"}]}]}] + with self.assertRaises(EngineException, msg="Accepted non-existent Scaling Group Policy Criteria") as e: + self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []}) + self.assertEqual(e.exception.http_code, HTTPStatus.UNPROCESSABLE_ENTITY, "Wrong HTTP status code") + sg = test_vnfd["scaling-group-descriptor"][0] + sc = sg["scaling-policy"][0]["scaling-criteria"][0] + self.assertIn(norm("scaling-group-descriptor[name='{}']:scaling-criteria[name='{}']:" + "vnf-monitoring-param-ref='{}' not defined in any monitoring-param" + .format(sg["name"], sc["name"], sc["vnf-monitoring-param-ref"])), + norm(str(e.exception)), "Wrong exception text") + with self.subTest(i=18, t='Check Input Validation: scaling-group-descriptor[vdu][vdu-id-ref]'): + sc["vnf-monitoring-param-ref"] = "fake-mp-id" + with self.assertRaises(EngineException, msg="Accepted non-existent Scaling Group VDU ID Reference") as e: + self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []}) + self.assertEqual(e.exception.http_code, HTTPStatus.UNPROCESSABLE_ENTITY, "Wrong HTTP status code") + self.assertIn(norm("scaling-group-descriptor[name='{}']:vdu-id-ref={} does not match any vdu" + .format(sg["name"], sg["vdu"][0]["vdu-id-ref"])), + norm(str(e.exception)), "Wrong exception text") + with self.subTest(i=19, t='Check Input Validation: scaling-group-descriptor[scaling-config-action]'): + tmp = test_vnfd["vnf-configuration"] + del test_vnfd["vnf-configuration"] + sg["vdu"][0]["vdu-id-ref"] = test_vnfd["vdu"][0]["id"] + sg["scaling-config-action"] = [{"trigger": "pre-scale-in"}] + try: + with self.assertRaises(EngineException, msg="Accepted non-existent Scaling Group VDU ID Reference")\ + as e: + self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []}) + self.assertEqual(e.exception.http_code, HTTPStatus.UNPROCESSABLE_ENTITY, "Wrong HTTP status code") + self.assertIn(norm("'vnf-configuration' not defined in the descriptor but it is referenced" + " by scaling-group-descriptor[name='{}']:scaling-config-action" + .format(sg["name"])), + norm(str(e.exception)), "Wrong exception text") + finally: + test_vnfd["vnf-configuration"] = tmp + with self.subTest(i=20, t='Check Input Validation: scaling-group-descriptor[scaling-config-action]' + '[vnf-config-primitive-name-ref]'): + sg["scaling-config-action"][0]["vnf-config-primitive-name-ref"] = "wrong-sca-prim-name" + with self.assertRaises(EngineException, msg="Accepted non-existent Scaling Group VDU ID Reference") as e: + self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []}) + self.assertEqual(e.exception.http_code, HTTPStatus.UNPROCESSABLE_ENTITY, "Wrong HTTP status code") + self.assertIn(norm("scaling-group-descriptor[name='{}']:scaling-config-action:" + "vnf-config-primitive-name-ref='{}' does not match" + " any vnf-configuration:config-primitive:name" + .format(sg["name"], sg["scaling-config-action"][0]["vnf-config-primitive-name-ref"])), + norm(str(e.exception)), "Wrong exception text") + # del test_vnfd["monitoring-param"] + # del test_vnfd["scaling-group-descriptor"] + with self.subTest(i=21, t='Check Input Validation: everything right'): + sg["scaling-config-action"][0]["vnf-config-primitive-name-ref"] = "touch" + test_vnfd["id"] = "fake-vnfd-id" + self.db.get_one.side_effect = [{"_id": did, "_admin": db_vnfd_content["_admin"]}, None] + rc = self.topic.upload_content(fake_session, did, test_vnfd, {}, {"Content-Type": []}) + self.assertTrue(rc, "Input Validation: Unexpected failure") + return + + def test_edit_vnfd(self): + did = db_vnfd_content["_id"] + self.fs.file_exists.return_value = True + self.fs.dir_ls.return_value = True + with self.subTest(i=1, t='Normal Edition'): + now = time() + self.db.get_one.side_effect = [db_vnfd_content, None] + data = {"id": "new-vnfd-id", "name": "new-vnfd-name"} + self.topic.edit(fake_session, did, data) + db_args = self.db.replace.call_args[0] + msg_args = self.msg.write.call_args[0] + data["_id"] = did + self.assertEqual(msg_args[0], self.topic.topic_msg, "Wrong message topic") + self.assertEqual(msg_args[1], "edited", "Wrong message action") + self.assertEqual(msg_args[2], data, "Wrong message content") + self.assertEqual(db_args[0], self.topic.topic, "Wrong DB topic") + self.assertEqual(db_args[1], did, "Wrong DB ID") + self.assertEqual(db_args[2]["_admin"]["created"], db_vnfd_content["_admin"]["created"], + "Wrong creation time") + self.assertGreater(db_args[2]["_admin"]["modified"], now, + "Wrong modification time") + self.assertEqual(db_args[2]["_admin"]["projects_read"], db_vnfd_content["_admin"]["projects_read"], + "Wrong read-only project list") + self.assertEqual(db_args[2]["_admin"]["projects_write"], db_vnfd_content["_admin"]["projects_write"], + "Wrong read-write project list") + self.assertEqual(db_args[2]["id"], data["id"], "Wrong VNFD ID") + self.assertEqual(db_args[2]["name"], data["name"], "Wrong VNFD Name") + with self.subTest(i=2, t='Conflict on Edit'): + data = {"id": "fake-vnfd-id", "name": "new-vnfd-name"} + self.db.get_one.side_effect = [db_vnfd_content, {"_id": str(uuid4()), "id": data["id"]}] + with self.assertRaises(EngineException, msg="Accepted existing VNFD ID") as e: + self.topic.edit(fake_session, did, data) + self.assertEqual(e.exception.http_code, HTTPStatus.CONFLICT, "Wrong HTTP status code") + self.assertIn(norm("{} with id '{}' already exists for this project".format("vnfd", data["id"])), + norm(str(e.exception)), "Wrong exception text") + with self.subTest(i=3, t='Check Envelope'): + data = {"vnfd": {"id": "new-vnfd-id-1", "name": "new-vnfd-name"}} + with self.assertRaises(EngineException, msg="Accepted VNFD with wrong envelope") as e: + self.topic.edit(fake_session, did, data) + self.assertEqual(e.exception.http_code, HTTPStatus.BAD_REQUEST, "Wrong HTTP status code") + self.assertIn("'vnfd' must be a list of only one element", norm(str(e.exception)), "Wrong exception text") + return + + def test_delete_vnfd(self): + did = db_vnfd_content["_id"] + self.db.get_one.return_value = db_vnfd_content + with self.subTest(i=1, t='Normal Deletion'): + self.db.get_list.return_value = [] + self.db.del_one.return_value = {"deleted": 1} + self.topic.delete(fake_session, did) + db_args = self.db.del_one.call_args[0] + msg_args = self.msg.write.call_args[0] + self.assertEqual(msg_args[0], self.topic.topic_msg, "Wrong message topic") + self.assertEqual(msg_args[1], "deleted", "Wrong message action") + self.assertEqual(msg_args[2], {"_id": did}, "Wrong message content") + self.assertEqual(db_args[0], self.topic.topic, "Wrong DB topic") + self.assertEqual(db_args[1]["_id"], did, "Wrong DB ID") + self.assertEqual(db_args[1]["_admin.projects_read"], [[], ['ANY']], "Wrong DB filter") + db_g1_args = self.db.get_one.call_args[0] + self.assertEqual(db_g1_args[0], self.topic.topic, "Wrong DB topic") + self.assertEqual(db_g1_args[1]["_id"], did, "Wrong DB VNFD ID") + db_gl_calls = self.db.get_list.call_args_list + self.assertEqual(db_gl_calls[0][0][0], "vnfrs", "Wrong DB topic") + # self.assertEqual(db_gl_calls[0][0][1]["vnfd-id"], did, "Wrong DB VNFD ID") # Filter changed after call + self.assertEqual(db_gl_calls[1][0][0], "nsds", "Wrong DB topic") + self.assertEqual(db_gl_calls[1][0][1]["constituent-vnfd.ANYINDEX.vnfd-id-ref"], db_vnfd_content["id"], + "Wrong DB NSD constituent-vnfd id-ref") + db_s1_args = self.db.set_one.call_args + self.assertEqual(db_s1_args[0][0], self.topic.topic, "Wrong DB topic") + self.assertEqual(db_s1_args[0][1]["_id"], did, "Wrong DB ID") + self.assertIn(test_pid, db_s1_args[0][1]["_admin.projects_write.cont"], "Wrong DB filter") + self.assertIsNone(db_s1_args[1]["update_dict"], "Wrong DB update dictionary") + self.assertEqual(db_s1_args[1]["pull"]["_admin.projects_read"]["$in"], fake_session["project_id"], + "Wrong DB pull dictionary") + fs_del_calls = self.fs.file_delete.call_args_list + self.assertEqual(fs_del_calls[0][0][0], did, "Wrong FS file id") + self.assertEqual(fs_del_calls[1][0][0], did+'_', "Wrong FS folder id") + with self.subTest(i=2, t='Conflict on Delete - VNFD in use by VNFR'): + self.db.get_list.return_value = [{"_id": str(uuid4()), "name": "fake-vnfr"}] + with self.assertRaises(EngineException, msg="Accepted VNFD in use by VNFR") as e: + self.topic.delete(fake_session, did) + self.assertEqual(e.exception.http_code, HTTPStatus.CONFLICT, "Wrong HTTP status code") + self.assertIn("there is at least one vnf using this descriptor", norm(str(e.exception)), + "Wrong exception text") + with self.subTest(i=3, t='Conflict on Delete - VNFD in use by NSD'): + self.db.get_list.side_effect = [[], [{"_id": str(uuid4()), "name": "fake-nsd"}]] + with self.assertRaises(EngineException, msg="Accepted VNFD in use by NSD") as e: + self.topic.delete(fake_session, did) + self.assertEqual(e.exception.http_code, HTTPStatus.CONFLICT, "Wrong HTTP status code") + self.assertIn("there is at least one nsd referencing this descriptor", norm(str(e.exception)), + "Wrong exception text") + with self.subTest(i=4, t='Non-existent VNFD'): + excp_msg = "Not found any {} with filter='{}'".format("VNFD", {"_id": did}) + self.db.get_one.side_effect = DbException(excp_msg, HTTPStatus.NOT_FOUND) + with self.assertRaises(DbException, msg="Accepted non-existent VNFD ID") as e: + self.topic.delete(fake_session, did) + self.assertEqual(e.exception.http_code, HTTPStatus.NOT_FOUND, "Wrong HTTP status code") + self.assertIn(norm(excp_msg), norm(str(e.exception)), "Wrong exception text") + return + + +if __name__ == '__main__': + unittest.main() diff --git a/osm_nbi/tests/test_pkg_descriptors.py b/osm_nbi/tests/test_pkg_descriptors.py new file mode 100644 index 0000000..3c6e28c --- /dev/null +++ b/osm_nbi/tests/test_pkg_descriptors.py @@ -0,0 +1,242 @@ +# +# 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 those usages not covered by the Apache License, Version 2.0 please +# contact: esousa@whitestack.com or alfonso.tiernosepulveda@telefonica.com +## + +"""Contains database content needed for tests""" + +__author__ = "Pedro de la Cruz Ramos, pedro.delacruzramos@altran.com" +__date__ = "2019-11-20" + +db_vnfds_text = """ +--- +- _admin: + created: 1566823352.7154346 + modified: 1566823353.9295402 + onboardingState: ONBOARDED + operationalState: ENABLED + projects_read: + - 25b5aebf-3da1-49ed-99de-1d2b4a86d6e4 + projects_write: + - 25b5aebf-3da1-49ed-99de-1d2b4a86d6e4 + storage: + descriptor: hackfest_3charmed_vnfd/hackfest_3charmed_vnfd.yaml + folder: 7637bcf8-cf14-42dc-ad70-c66fcf1e6e77 + fs: local + path: /app/storage/ + pkg-dir: hackfest_3charmed_vnfd + zipfile: package.tar.gz + type: vnfd + usageState: NOT_IN_USE + userDefinedData: {} + _id: 7637bcf8-cf14-42dc-ad70-c66fcf1e6e77 + connection-point: + - id: vnf-mgmt + name: vnf-mgmt + short-name: vnf-mgmt + type: VPORT + - id: vnf-data + name: vnf-data + short-name: vnf-data + type: VPORT + description: A VNF consisting of 2 VDUs connected to an internal VL, and one VDU + with cloud-init + id: hackfest3charmed-vnf + internal-vld: + - id: internal + internal-connection-point: + - id-ref: mgmtVM-internal + - id-ref: dataVM-internal + name: internal + short-name: internal + type: ELAN + logo: osm.png + mgmt-interface: + cp: vnf-mgmt + monitoring-param: + - aggregation-type: AVERAGE + id: monitor1 + name: monitor1 + vdu-monitoring-param: + vdu-monitoring-param-ref: dataVM_cpu_util + vdu-ref: dataVM + name: hackfest3charmed-vnf + scaling-group-descriptor: + - max-instance-count: 10 + name: scale_dataVM + scaling-config-action: + - trigger: post-scale-out + vnf-config-primitive-name-ref: touch + - trigger: pre-scale-in + vnf-config-primitive-name-ref: touch + scaling-policy: + - cooldown-time: 60 + name: auto_cpu_util_above_threshold + scaling-criteria: + - name: cpu_util_above_threshold + scale-in-relational-operation: LE + scale-in-threshold: '15.0000000000' + scale-out-relational-operation: GE + scale-out-threshold: '60.0000000000' + vnf-monitoring-param-ref: monitor1 + scaling-type: automatic + threshold-time: 0 + vdu: + - count: 1 + vdu-id-ref: dataVM + short-name: hackfest3charmed-vnf + vdu: + - count: '1' + cloud-init-file: cloud-config.txt + id: mgmtVM + image: hackfest3-mgmt + interface: + - external-connection-point-ref: vnf-mgmt + name: mgmtVM-eth0 + position: 1 + type: EXTERNAL + virtual-interface: + type: VIRTIO + - internal-connection-point-ref: mgmtVM-internal + name: mgmtVM-eth1 + position: 2 + type: INTERNAL + virtual-interface: + type: VIRTIO + internal-connection-point: + - id: mgmtVM-internal + name: mgmtVM-internal + short-name: mgmtVM-internal + type: VPORT + name: mgmtVM + vm-flavor: + memory-mb: '1024' + storage-gb: '10' + vcpu-count: 1 + - count: '1' + id: dataVM + image: hackfest3-mgmt + interface: + - internal-connection-point-ref: dataVM-internal + name: dataVM-eth0 + position: 1 + type: INTERNAL + virtual-interface: + type: VIRTIO + - external-connection-point-ref: vnf-data + name: dataVM-xe0 + position: 2 + type: EXTERNAL + virtual-interface: + type: VIRTIO + internal-connection-point: + - id: dataVM-internal + name: dataVM-internal + short-name: dataVM-internal + type: VPORT + monitoring-param: + - id: dataVM_cpu_util + nfvi-metric: cpu_utilization + name: dataVM + vm-flavor: + memory-mb: '1024' + storage-gb: '10' + vcpu-count: 1 + version: '1.0' + vnf-configuration: + config-primitive: + - name: touch + parameter: + - data-type: STRING + default-value: + name: filename + initial-config-primitive: + - name: config + parameter: + - name: ssh-hostname + value: + - name: ssh-username + value: ubuntu + - name: ssh-password + value: osm4u + seq: '1' + - name: touch + parameter: + - name: filename + value: + seq: '2' + juju: + charm: simple +""" + +db_nsds_text = """ +--- +- _admin: + created: 1566823353.971486 + modified: 1566823353.971486 + onboardingState: ONBOARDED + operationalState: ENABLED + projects_read: + - 25b5aebf-3da1-49ed-99de-1d2b4a86d6e4 + projects_write: + - 25b5aebf-3da1-49ed-99de-1d2b4a86d6e4 + storage: + descriptor: hackfest_3charmed_nsd/hackfest_3charmed_nsd.yaml + folder: 8c2f8b95-bb1b-47ee-8001-36dc090678da + fs: local + path: /app/storage/ + pkg-dir: hackfest_3charmed_nsd + zipfile: package.tar.gz + usageState: NOT_IN_USE + userDefinedData: {} + _id: 8c2f8b95-bb1b-47ee-8001-36dc090678da + constituent-vnfd: + - member-vnf-index: '1' + vnfd-id-ref: hackfest3charmed-vnf + - member-vnf-index: '2' + vnfd-id-ref: hackfest3charmed-vnf + description: NS with 2 VNFs hackfest3charmed-vnf connected by datanet and mgmtnet + VLs + id: hackfest3charmed-ns + logo: osm.png + name: hackfest3charmed-ns + short-name: hackfest3charmed-ns + version: '1.0' + vld: + - id: mgmt + mgmt-network: true + name: mgmt + short-name: mgmt + type: ELAN + vim-network-name: mgmt + vnfd-connection-point-ref: + - member-vnf-index-ref: '1' + vnfd-connection-point-ref: vnf-mgmt + vnfd-id-ref: hackfest3charmed-vnf + - member-vnf-index-ref: '2' + vnfd-connection-point-ref: vnf-mgmt + vnfd-id-ref: hackfest3charmed-vnf + - id: datanet + name: datanet + short-name: datanet + type: ELAN + vnfd-connection-point-ref: + - member-vnf-index-ref: '1' + vnfd-connection-point-ref: vnf-data + vnfd-id-ref: hackfest3charmed-vnf + - member-vnf-index-ref: '2' + vnfd-connection-point-ref: vnf-data + vnfd-id-ref: hackfest3charmed-vnf +"""