Code Coverage

Cobertura Coverage Report > osm_nbi.tests >

test_descriptor_topics.py

Trend

File Coverage summary

NameClassesLinesConditionals
test_descriptor_topics.py
100%
1/1
99%
1075/1080
100%
0/0

Coverage Breakdown by Class

NameLinesConditionals
test_descriptor_topics.py
99%
1075/1080
N/A

Source

osm_nbi/tests/test_descriptor_topics.py
1 #! /usr/bin/python3
2 # -*- coding: utf-8 -*-
3
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 #    http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13 # implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 1 __author__ = "Pedro de la Cruz Ramos, pedro.delacruzramos@altran.com"
18 1 __date__ = "2019-11-20"
19
20 1 from contextlib import contextmanager
21 1 import unittest
22 1 from unittest import TestCase
23 1 from unittest.mock import Mock, patch
24 1 from uuid import uuid4
25 1 from http import HTTPStatus
26 1 from copy import deepcopy
27 1 from time import time
28 1 from osm_common import dbbase, fsbase, msgbase
29 1 from osm_nbi import authconn
30 1 from osm_nbi.tests.test_pkg_descriptors import (
31     db_vnfds_text,
32     db_nsds_text,
33     vnfd_exploit_text,
34     vnfd_exploit_fixed_text,
35 )
36 1 from osm_nbi.descriptor_topics import VnfdTopic, NsdTopic
37 1 from osm_nbi.engine import EngineException
38 1 from osm_common.dbbase import DbException
39 1 import yaml
40 1 import tempfile
41 1 import collections
42 1 import collections.abc
43
44 1 collections.MutableSequence = collections.abc.MutableSequence
45
46 1 test_name = "test-user"
47 1 db_vnfd_content = yaml.safe_load(db_vnfds_text)[0]
48 1 db_nsd_content = yaml.safe_load(db_nsds_text)[0]
49 1 test_pid = db_vnfd_content["_admin"]["projects_read"][0]
50 1 fake_session = {
51     "username": test_name,
52     "project_id": (test_pid,),
53     "method": None,
54     "admin": True,
55     "force": False,
56     "public": False,
57     "allow_show_user_project_role": True,
58 }
59 1 UUID = "00000000-0000-0000-0000-000000000000"
60
61
62 1 def admin_value():
63 1     return {"projects_read": []}
64
65
66 1 def setup_mock_fs(fs):
67 1     fs.path = ""
68 1     fs.get_params.return_value = {}
69 1     fs.file_exists.return_value = False
70 1     fs.file_open.side_effect = lambda path, mode: tempfile.TemporaryFile(mode="a+b")
71
72
73 1 def norm(s: str):
74     """Normalize string for checking"""
75 1     return " ".join(s.strip().split()).lower()
76
77
78 1 def compare_desc(tc, d1, d2, k):
79     """
80     Compare two descriptors
81     We need this function because some methods are adding/removing items to/from the descriptors
82     before they are stored in the database, so the original and stored versions will differ
83     What we check is that COMMON LEAF ITEMS are equal
84     Lists of different length are not compared
85     :param tc: Test Case wich provides context (in particular the assert* methods)
86     :param d1,d2: Descriptors to be compared
87     :param k: key/item being compared
88     :return: Nothing
89     """
90 1     if isinstance(d1, dict) and isinstance(d2, dict):
91 1         for key in d1.keys():
92 1             if key in d2:
93 1                 compare_desc(tc, d1[key], d2[key], k + "[{}]".format(key))
94 1     elif isinstance(d1, list) and isinstance(d2, list) and len(d1) == len(d2):
95 1         for i in range(len(d1)):
96 1             compare_desc(tc, d1[i], d2[i], k + "[{}]".format(i))
97     else:
98 1         tc.assertEqual(d1, d2, "Wrong descriptor content: {}".format(k))
99
100
101 1 class Test_VnfdTopic(TestCase):
102 1     @classmethod
103 1     def setUpClass(cls):
104 1         cls.test_name = "test-vnfd-topic"
105
106 1     @classmethod
107 1     def tearDownClass(cls):
108 1         pass
109
110 1     def setUp(self):
111 1         self.db = Mock(dbbase.DbBase())
112 1         self.fs = Mock(fsbase.FsBase())
113 1         self.msg = Mock(msgbase.MsgBase())
114 1         self.auth = Mock(authconn.Authconn(None, None, None))
115 1         self.topic = VnfdTopic(self.db, self.fs, self.msg, self.auth)
116 1         self.topic.check_quota = Mock(return_value=None)  # skip quota
117
118 1     @contextmanager
119 1     def assertNotRaises(self, exception_type=Exception):
120 1         try:
121 1             yield None
122 0         except exception_type:
123 0             raise self.failureException("{} raised".format(exception_type.__name__))
124
125 1     def create_desc_temp(self, template):
126 1         old_desc = deepcopy(template)
127 1         new_desc = deepcopy(template)
128 1         return old_desc, new_desc
129
130 1     def prepare_vnfd_creation(self):
131 1         setup_mock_fs(self.fs)
132 1         test_vnfd = deepcopy(db_vnfd_content)
133 1         did = db_vnfd_content["_id"]
134 1         self.db.create.return_value = did
135 1         self.db.get_one.side_effect = [
136             {"_id": did, "_admin": deepcopy(db_vnfd_content["_admin"])},
137             None,
138         ]
139 1         return did, test_vnfd
140
141 1     def prepare_vnfd(self, vnfd_text):
142 1         setup_mock_fs(self.fs)
143 1         test_vnfd = yaml.safe_load(vnfd_text)
144 1         self.db.create.return_value = UUID
145 1         self.db.get_one.side_effect = [
146             {"_id": UUID, "_admin": admin_value()},
147             None,
148         ]
149 1         return UUID, test_vnfd
150
151 1     def prepare_test_vnfd(self, test_vnfd):
152 1         del test_vnfd["_id"]
153 1         del test_vnfd["_admin"]
154 1         del test_vnfd["vdu"][0]["cloud-init-file"]
155 1         del test_vnfd["df"][0]["lcm-operations-configuration"]["operate-vnf-op-config"][
156             "day1-2"
157         ][0]["execution-environment-list"][0]["juju"]
158 1         return test_vnfd
159
160 1     @patch("osm_nbi.descriptor_topics.shutil")
161 1     @patch("osm_nbi.descriptor_topics.os.rename")
162 1     def test_new_vnfd_normal_creation(self, mock_rename, mock_shutil):
163 1         did, test_vnfd = self.prepare_vnfd_creation()
164 1         test_vnfd = self.prepare_test_vnfd(test_vnfd)
165 1         rollback = []
166 1         did2, oid = self.topic.new(rollback, fake_session, {})
167 1         db_args = self.db.create.call_args[0]
168 1         msg_args = self.msg.write.call_args[0]
169
170 1         self.assertEqual(len(rollback), 1, "Wrong rollback length")
171 1         self.assertEqual(msg_args[0], self.topic.topic_msg, "Wrong message topic")
172 1         self.assertEqual(msg_args[1], "created", "Wrong message action")
173 1         self.assertEqual(msg_args[2], {"_id": did}, "Wrong message content")
174 1         self.assertEqual(db_args[0], self.topic.topic, "Wrong DB topic")
175 1         self.assertEqual(did2, did, "Wrong DB VNFD id")
176 1         self.assertIsNotNone(db_args[1]["_admin"]["created"], "Wrong creation time")
177 1         self.assertEqual(
178             db_args[1]["_admin"]["modified"],
179             db_args[1]["_admin"]["created"],
180             "Wrong modification time",
181         )
182 1         self.assertEqual(
183             db_args[1]["_admin"]["projects_read"],
184             [test_pid],
185             "Wrong read-only project list",
186         )
187 1         self.assertEqual(
188             db_args[1]["_admin"]["projects_write"],
189             [test_pid],
190             "Wrong read-write project list",
191         )
192
193 1         self.db.get_one.side_effect = [
194             {"_id": did, "_admin": deepcopy(db_vnfd_content["_admin"])},
195             None,
196         ]
197
198 1         self.topic.upload_content(
199             fake_session, did, test_vnfd, {}, {"Content-Type": []}
200         )
201 1         msg_args = self.msg.write.call_args[0]
202 1         test_vnfd["_id"] = did
203 1         self.assertEqual(msg_args[0], self.topic.topic_msg, "Wrong message topic")
204 1         self.assertEqual(msg_args[1], "edited", "Wrong message action")
205 1         self.assertEqual(msg_args[2], test_vnfd, "Wrong message content")
206
207 1         db_args = self.db.get_one.mock_calls[0][1]
208 1         self.assertEqual(db_args[0], self.topic.topic, "Wrong DB topic")
209 1         self.assertEqual(db_args[1]["_id"], did, "Wrong DB VNFD id")
210
211 1         db_args = self.db.replace.call_args[0]
212 1         self.assertEqual(db_args[0], self.topic.topic, "Wrong DB topic")
213 1         self.assertEqual(db_args[1], did, "Wrong DB VNFD id")
214
215 1         admin = db_args[2]["_admin"]
216 1         db_admin = deepcopy(db_vnfd_content["_admin"])
217 1         self.assertEqual(admin["type"], "vnfd", "Wrong descriptor type")
218 1         self.assertEqual(admin["created"], db_admin["created"], "Wrong creation time")
219 1         self.assertGreater(
220             admin["modified"], db_admin["created"], "Wrong modification time"
221         )
222 1         self.assertEqual(
223             admin["projects_read"],
224             db_admin["projects_read"],
225             "Wrong read-only project list",
226         )
227 1         self.assertEqual(
228             admin["projects_write"],
229             db_admin["projects_write"],
230             "Wrong read-write project list",
231         )
232 1         self.assertEqual(
233             admin["onboardingState"], "ONBOARDED", "Wrong onboarding state"
234         )
235 1         self.assertEqual(
236             admin["operationalState"], "ENABLED", "Wrong operational state"
237         )
238 1         self.assertEqual(admin["usageState"], "NOT_IN_USE", "Wrong usage state")
239
240 1         storage = admin["storage"]
241 1         self.assertEqual(storage["folder"], did + ":1", "Wrong storage folder")
242 1         self.assertEqual(storage["descriptor"], "package", "Wrong storage descriptor")
243 1         self.assertEqual(admin["revision"], 1, "Wrong revision number")
244 1         compare_desc(self, test_vnfd, db_args[2], "VNFD")
245
246 1     @patch("osm_nbi.descriptor_topics.shutil")
247 1     @patch("osm_nbi.descriptor_topics.os.rename")
248 1     def test_new_vnfd_exploit(self, mock_rename, mock_shutil):
249 1         id, test_vnfd = self.prepare_vnfd(vnfd_exploit_text)
250
251 1         with self.assertRaises(EngineException):
252 1             self.topic.upload_content(
253                 fake_session, id, test_vnfd, {}, {"Content-Type": []}
254             )
255
256 1     @patch("osm_nbi.descriptor_topics.shutil")
257 1     @patch("osm_nbi.descriptor_topics.os.rename")
258 1     def test_new_vnfd_valid_helm_chart(self, mock_rename, mock_shutil):
259 1         id, test_vnfd = self.prepare_vnfd(vnfd_exploit_fixed_text)
260
261 1         with self.assertNotRaises():
262 1             self.topic.upload_content(
263                 fake_session, id, test_vnfd, {}, {"Content-Type": []}
264             )
265
266 1     @patch("osm_nbi.descriptor_topics.shutil")
267 1     @patch("osm_nbi.descriptor_topics.os.rename")
268 1     def test_new_vnfd_check_pyangbind_validation_additional_properties(
269         self, mock_rename, mock_shutil
270     ):
271 1         did, test_vnfd = self.prepare_vnfd_creation()
272 1         test_vnfd = self.prepare_test_vnfd(test_vnfd)
273 1         self.topic.upload_content(
274             fake_session, did, test_vnfd, {}, {"Content-Type": []}
275         )
276 1         test_vnfd["_id"] = did
277 1         test_vnfd["extra-property"] = 0
278 1         self.db.get_one.side_effect = (
279             lambda table, filter, fail_on_empty=None, fail_on_more=None: {
280                 "_id": did,
281                 "_admin": deepcopy(db_vnfd_content["_admin"]),
282             }
283         )
284
285 1         with self.assertRaises(
286             EngineException, msg="Accepted VNFD with an additional property"
287         ) as e:
288 1             self.topic.upload_content(
289                 fake_session, did, test_vnfd, {}, {"Content-Type": []}
290             )
291 1         self.assertEqual(
292             e.exception.http_code,
293             HTTPStatus.UNPROCESSABLE_ENTITY,
294             "Wrong HTTP status code",
295         )
296 1         self.assertIn(
297             norm(
298                 "Error in pyangbind validation: {} ({})".format(
299                     "json object contained a key that did not exist", "extra-property"
300                 )
301             ),
302             norm(str(e.exception)),
303             "Wrong exception text",
304         )
305 1         db_args = self.db.replace.call_args[0]
306 1         admin = db_args[2]["_admin"]
307 1         self.assertEqual(admin["revision"], 1, "Wrong revision number")
308
309 1     @patch("osm_nbi.descriptor_topics.shutil")
310 1     @patch("osm_nbi.descriptor_topics.os.rename")
311 1     def test_new_vnfd_check_pyangbind_validation_property_types(
312         self, mock_rename, mock_shutil
313     ):
314 1         did, test_vnfd = self.prepare_vnfd_creation()
315 1         test_vnfd = self.prepare_test_vnfd(test_vnfd)
316 1         test_vnfd["_id"] = did
317 1         test_vnfd["product-name"] = {"key": 0}
318
319 1         with self.assertRaises(
320             EngineException, msg="Accepted VNFD with a wrongly typed property"
321         ) as e:
322 1             self.topic.upload_content(
323                 fake_session, did, test_vnfd, {}, {"Content-Type": []}
324             )
325 1         self.assertEqual(
326             e.exception.http_code,
327             HTTPStatus.UNPROCESSABLE_ENTITY,
328             "Wrong HTTP status code",
329         )
330 1         self.assertIn(
331             norm(
332                 "Error in pyangbind validation: {} ({})".format(
333                     "json object contained a key that did not exist", "key"
334                 )
335             ),
336             norm(str(e.exception)),
337             "Wrong exception text",
338         )
339
340 1     @patch("osm_nbi.descriptor_topics.shutil")
341 1     @patch("osm_nbi.descriptor_topics.os.rename")
342 1     def test_new_vnfd_check_input_validation_cloud_init(self, mock_rename, mock_shutil):
343 1         did, test_vnfd = self.prepare_vnfd_creation()
344 1         del test_vnfd["df"][0]["lcm-operations-configuration"]["operate-vnf-op-config"][
345             "day1-2"
346         ][0]["execution-environment-list"][0]["juju"]
347
348 1         with self.assertRaises(
349             EngineException, msg="Accepted non-existent cloud_init file"
350         ) as e:
351 1             self.topic.upload_content(
352                 fake_session, did, test_vnfd, {}, {"Content-Type": []}
353             )
354 1         self.assertEqual(
355             e.exception.http_code, HTTPStatus.BAD_REQUEST, "Wrong HTTP status code"
356         )
357 1         self.assertIn(
358             norm(
359                 "{} defined in vnf[id={}]:vdu[id={}] but not present in package".format(
360                     "cloud-init", test_vnfd["id"], test_vnfd["vdu"][0]["id"]
361                 )
362             ),
363             norm(str(e.exception)),
364             "Wrong exception text",
365         )
366
367 1     @patch("osm_nbi.descriptor_topics.shutil")
368 1     @patch("osm_nbi.descriptor_topics.os.rename")
369 1     def test_new_vnfd_check_input_validation_day12_configuration(
370         self, mock_rename, mock_shutil
371     ):
372 1         did, test_vnfd = self.prepare_vnfd_creation()
373 1         del test_vnfd["vdu"][0]["cloud-init-file"]
374
375 1         with self.assertRaises(
376             EngineException, msg="Accepted non-existent charm in VNF configuration"
377         ) as e:
378 1             self.topic.upload_content(
379                 fake_session, did, test_vnfd, {}, {"Content-Type": []}
380             )
381 1         self.assertEqual(
382             e.exception.http_code, HTTPStatus.BAD_REQUEST, "Wrong HTTP status code"
383         )
384 1         self.assertIn(
385             norm(
386                 "{} defined in vnf[id={}] but not present in package".format(
387                     "charm", test_vnfd["id"]
388                 )
389             ),
390             norm(str(e.exception)),
391             "Wrong exception text",
392         )
393
394 1     @patch("osm_nbi.descriptor_topics.shutil")
395 1     @patch("osm_nbi.descriptor_topics.os.rename")
396 1     def test_new_vnfd_check_input_validation_mgmt_cp(self, mock_rename, mock_shutil):
397 1         did, test_vnfd = self.prepare_vnfd_creation()
398 1         test_vnfd = self.prepare_test_vnfd(test_vnfd)
399 1         del test_vnfd["mgmt-cp"]
400
401 1         with self.assertRaises(
402             EngineException, msg="Accepted VNFD without management interface"
403         ) as e:
404 1             self.topic.upload_content(
405                 fake_session, did, test_vnfd, {}, {"Content-Type": []}
406             )
407 1         self.assertEqual(
408             e.exception.http_code,
409             HTTPStatus.UNPROCESSABLE_ENTITY,
410             "Wrong HTTP status code",
411         )
412 1         self.assertIn(
413             norm("'{}' is a mandatory field and it is not defined".format("mgmt-cp")),
414             norm(str(e.exception)),
415             "Wrong exception text",
416         )
417
418 1     @patch("osm_nbi.descriptor_topics.shutil")
419 1     @patch("osm_nbi.descriptor_topics.os.rename")
420 1     def test_new_vnfd_check_input_validation_mgmt_cp_connection_point(
421         self, mock_rename, mock_shutil
422     ):
423 1         did, test_vnfd = self.prepare_vnfd_creation()
424 1         test_vnfd = self.prepare_test_vnfd(test_vnfd)
425 1         test_vnfd["mgmt-cp"] = "wrong-cp"
426
427 1         with self.assertRaises(
428             EngineException, msg="Accepted wrong mgmt-cp connection point"
429         ) as e:
430 1             self.topic.upload_content(
431                 fake_session, did, test_vnfd, {}, {"Content-Type": []}
432             )
433 1         self.assertEqual(
434             e.exception.http_code,
435             HTTPStatus.UNPROCESSABLE_ENTITY,
436             "Wrong HTTP status code",
437         )
438 1         self.assertIn(
439             norm(
440                 "mgmt-cp='{}' must match an existing ext-cpd".format(
441                     test_vnfd["mgmt-cp"]
442                 )
443             ),
444             norm(str(e.exception)),
445             "Wrong exception text",
446         )
447
448 1     @patch("osm_nbi.descriptor_topics.shutil")
449 1     @patch("osm_nbi.descriptor_topics.os.rename")
450 1     def test_new_vnfd_check_input_validation_vdu_int_cpd(
451         self, mock_rename, mock_shutil
452     ):
453         """Testing input validation during new vnfd creation
454         for vdu internal connection point"""
455 1         did, test_vnfd = self.prepare_vnfd_creation()
456 1         test_vnfd = self.prepare_test_vnfd(test_vnfd)
457 1         ext_cpd = test_vnfd["ext-cpd"][1]
458 1         ext_cpd["int-cpd"]["cpd"] = "wrong-cpd"
459
460 1         with self.assertRaises(
461             EngineException, msg="Accepted wrong ext-cpd internal connection point"
462         ) as e:
463 1             self.topic.upload_content(
464                 fake_session, did, test_vnfd, {}, {"Content-Type": []}
465             )
466 1         self.assertEqual(
467             e.exception.http_code,
468             HTTPStatus.UNPROCESSABLE_ENTITY,
469             "Wrong HTTP status code",
470         )
471 1         self.assertIn(
472             norm(
473                 "ext-cpd[id='{}']:int-cpd must match an existing vdu int-cpd".format(
474                     ext_cpd["id"]
475                 )
476             ),
477             norm(str(e.exception)),
478             "Wrong exception text",
479         )
480
481 1     @patch("osm_nbi.descriptor_topics.shutil")
482 1     @patch("osm_nbi.descriptor_topics.os.rename")
483 1     def test_new_vnfd_check_input_validation_duplicated_vld(
484         self, mock_rename, mock_shutil
485     ):
486         """Testing input validation during new vnfd creation
487         for dublicated virtual link description"""
488 1         did, test_vnfd = self.prepare_vnfd_creation()
489 1         test_vnfd = self.prepare_test_vnfd(test_vnfd)
490 1         test_vnfd["int-virtual-link-desc"].insert(0, {"id": "internal"})
491
492 1         with self.assertRaises(
493             EngineException, msg="Accepted duplicated VLD name"
494         ) as e:
495 1             self.topic.upload_content(
496                 fake_session, did, test_vnfd, {}, {"Content-Type": []}
497             )
498 1         self.assertEqual(
499             e.exception.http_code,
500             HTTPStatus.UNPROCESSABLE_ENTITY,
501             "Wrong HTTP status code",
502         )
503 1         self.assertIn(
504             norm(
505                 "identifier id '{}' is not unique".format(
506                     test_vnfd["int-virtual-link-desc"][0]["id"]
507                 )
508             ),
509             norm(str(e.exception)),
510             "Wrong exception text",
511         )
512
513 1     @patch("osm_nbi.descriptor_topics.shutil")
514 1     @patch("osm_nbi.descriptor_topics.os.rename")
515 1     def test_new_vnfd_check_input_validation_vdu_int_virtual_link_desc(
516         self, mock_rename, mock_shutil
517     ):
518         """Testing input validation during new vnfd creation
519         for vdu internal virtual link description"""
520 1         did, test_vnfd = self.prepare_vnfd_creation()
521 1         test_vnfd = self.prepare_test_vnfd(test_vnfd)
522 1         vdu = test_vnfd["vdu"][0]
523 1         int_cpd = vdu["int-cpd"][1]
524 1         int_cpd["int-virtual-link-desc"] = "non-existing-int-virtual-link-desc"
525
526 1         with self.assertRaises(
527             EngineException, msg="Accepted int-virtual-link-desc"
528         ) as e:
529 1             self.topic.upload_content(
530                 fake_session, did, test_vnfd, {}, {"Content-Type": []}
531             )
532 1         self.assertEqual(
533             e.exception.http_code,
534             HTTPStatus.UNPROCESSABLE_ENTITY,
535             "Wrong HTTP status code",
536         )
537 1         self.assertIn(
538             norm(
539                 "vdu[id='{}']:int-cpd[id='{}']:int-virtual-link-desc='{}' must match an existing "
540                 "int-virtual-link-desc".format(
541                     vdu["id"], int_cpd["id"], int_cpd["int-virtual-link-desc"]
542                 )
543             ),
544             norm(str(e.exception)),
545             "Wrong exception text",
546         )
547
548 1     @patch("osm_nbi.descriptor_topics.shutil")
549 1     @patch("osm_nbi.descriptor_topics.os.rename")
550 1     def test_new_vnfd_check_input_validation_virtual_link_profile(
551         self, mock_rename, mock_shutil
552     ):
553         """Testing input validation during new vnfd creation
554         for virtual link profile"""
555 1         did, test_vnfd = self.prepare_vnfd_creation()
556 1         test_vnfd = self.prepare_test_vnfd(test_vnfd)
557 1         fake_ivld_profile = {"id": "fake-profile-ref", "flavour": "fake-flavour"}
558 1         df = test_vnfd["df"][0]
559 1         df["virtual-link-profile"] = [fake_ivld_profile]
560
561 1         with self.assertRaises(
562             EngineException, msg="Accepted non-existent Profile Ref"
563         ) as e:
564 1             self.topic.upload_content(
565                 fake_session, did, test_vnfd, {}, {"Content-Type": []}
566             )
567 1         self.assertEqual(
568             e.exception.http_code,
569             HTTPStatus.UNPROCESSABLE_ENTITY,
570             "Wrong HTTP status code",
571         )
572 1         self.assertIn(
573             norm(
574                 "df[id='{}']:virtual-link-profile='{}' must match an existing "
575                 "int-virtual-link-desc".format(df["id"], fake_ivld_profile["id"])
576             ),
577             norm(str(e.exception)),
578             "Wrong exception text",
579         )
580
581 1     @patch("osm_nbi.descriptor_topics.shutil")
582 1     @patch("osm_nbi.descriptor_topics.os.rename")
583 1     def test_new_vnfd_check_input_validation_scaling_criteria_vdu_id(
584         self, mock_rename, mock_shutil
585     ):
586         """Testing input validation during new vnfd creation
587         for scaling criteria with invalid vdu-id"""
588 1         did, test_vnfd = self.prepare_vnfd_creation()
589 1         test_vnfd = self.prepare_test_vnfd(test_vnfd)
590 1         test_vnfd["df"][0]["scaling-aspect"][0]["aspect-delta-details"]["deltas"][0][
591             "vdu-delta"
592         ][0]["id"] = "vdudelta1"
593 1         affected_df = test_vnfd["df"][0]
594 1         sa = affected_df["scaling-aspect"][0]
595 1         delta = sa["aspect-delta-details"]["deltas"][0]
596 1         vdu_delta = delta["vdu-delta"][0]
597
598 1         with self.assertRaises(
599             EngineException, msg="Accepted invalid Scaling Group Policy Criteria"
600         ) as e:
601 1             self.topic.upload_content(
602                 fake_session, did, test_vnfd, {}, {"Content-Type": []}
603             )
604 1         self.assertEqual(
605             e.exception.http_code,
606             HTTPStatus.UNPROCESSABLE_ENTITY,
607             "Wrong HTTP status code",
608         )
609 1         self.assertIn(
610             norm(
611                 "df[id='{}']:scaling-aspect[id='{}']:aspect-delta-details"
612                 "[delta='{}']: "
613                 "vdu-id='{}' not defined in vdu".format(
614                     affected_df["id"],
615                     sa["id"],
616                     delta["id"],
617                     vdu_delta["id"],
618                 )
619             ),
620             norm(str(e.exception)),
621             "Wrong exception text",
622         )
623
624 1     @patch("osm_nbi.descriptor_topics.shutil")
625 1     @patch("osm_nbi.descriptor_topics.os.rename")
626 1     def test_new_vnfd_check_input_validation_scaling_criteria_monitoring_param_ref(
627         self, mock_rename, mock_shutil
628     ):
629         """Testing input validation during new vnfd creation
630         for scaling criteria without monitoring parameter"""
631 1         did, test_vnfd = self.prepare_vnfd_creation()
632 1         test_vnfd = self.prepare_test_vnfd(test_vnfd)
633 1         vdu = test_vnfd["vdu"][1]
634 1         affected_df = test_vnfd["df"][0]
635 1         sa = affected_df["scaling-aspect"][0]
636 1         sp = sa["scaling-policy"][0]
637 1         sc = sp["scaling-criteria"][0]
638 1         vdu.pop("monitoring-parameter")
639
640 1         with self.assertRaises(
641             EngineException, msg="Accepted non-existent Scaling Group Policy Criteria"
642         ) as e:
643 1             self.topic.upload_content(
644                 fake_session, did, test_vnfd, {}, {"Content-Type": []}
645             )
646 1         self.assertEqual(
647             e.exception.http_code,
648             HTTPStatus.UNPROCESSABLE_ENTITY,
649             "Wrong HTTP status code",
650         )
651 1         self.assertIn(
652             norm(
653                 "df[id='{}']:scaling-aspect[id='{}']:scaling-policy"
654                 "[name='{}']:scaling-criteria[name='{}']: "
655                 "vnf-monitoring-param-ref='{}' not defined in any monitoring-param".format(
656                     affected_df["id"],
657                     sa["id"],
658                     sp["name"],
659                     sc["name"],
660                     sc["vnf-monitoring-param-ref"],
661                 )
662             ),
663             norm(str(e.exception)),
664             "Wrong exception text",
665         )
666
667 1     @patch("osm_nbi.descriptor_topics.shutil")
668 1     @patch("osm_nbi.descriptor_topics.os.rename")
669 1     def test_new_vnfd_check_input_validation_scaling_aspect_vnf_configuration(
670         self, mock_rename, mock_shutil
671     ):
672         """Testing input validation during new vnfd creation
673         for scaling criteria without day12 configuration"""
674 1         did, test_vnfd = self.prepare_vnfd_creation()
675 1         test_vnfd = self.prepare_test_vnfd(test_vnfd)
676 1         test_vnfd["df"][0]["lcm-operations-configuration"]["operate-vnf-op-config"][
677             "day1-2"
678         ].pop()
679 1         df = test_vnfd["df"][0]
680
681 1         with self.assertRaises(
682             EngineException, msg="Accepted non-existent Scaling Group VDU ID Reference"
683         ) as e:
684 1             self.topic.upload_content(
685                 fake_session, did, test_vnfd, {}, {"Content-Type": []}
686             )
687 1         self.assertEqual(
688             e.exception.http_code,
689             HTTPStatus.UNPROCESSABLE_ENTITY,
690             "Wrong HTTP status code",
691         )
692 1         self.assertIn(
693             norm(
694                 "'day1-2 configuration' not defined in the descriptor but it is referenced "
695                 "by df[id='{}']:scaling-aspect[id='{}']:scaling-config-action".format(
696                     df["id"], df["scaling-aspect"][0]["id"]
697                 )
698             ),
699             norm(str(e.exception)),
700             "Wrong exception text",
701         )
702
703 1     @patch("osm_nbi.descriptor_topics.shutil")
704 1     @patch("osm_nbi.descriptor_topics.os.rename")
705 1     def test_new_vnfd_check_input_validation_scaling_config_action(
706         self, mock_rename, mock_shutil
707     ):
708         """Testing input validation during new vnfd creation
709         for scaling criteria wrong config primitive"""
710 1         did, test_vnfd = self.prepare_vnfd_creation()
711 1         test_vnfd = self.prepare_test_vnfd(test_vnfd)
712 1         df = test_vnfd["df"][0]
713 1         affected_df = test_vnfd["df"][0]
714 1         sa = affected_df["scaling-aspect"][0]
715 1         test_vnfd["df"][0].get("lcm-operations-configuration").get(
716             "operate-vnf-op-config"
717         )["day1-2"][0]["config-primitive"] = [{"name": "wrong-primitive"}]
718
719 1         with self.assertRaises(
720             EngineException, msg="Accepted non-existent Scaling Group VDU ID Reference"
721         ) as e:
722 1             self.topic.upload_content(
723                 fake_session, did, test_vnfd, {}, {"Content-Type": []}
724             )
725 1         self.assertEqual(
726             e.exception.http_code,
727             HTTPStatus.UNPROCESSABLE_ENTITY,
728             "Wrong HTTP status code",
729         )
730 1         self.assertIn(
731             norm(
732                 "df[id='{}']:scaling-aspect[id='{}']:scaling-config-action:vnf-"
733                 "config-primitive-name-ref='{}' does not match any "
734                 "day1-2 configuration:config-primitive:name".format(
735                     df["id"],
736                     df["scaling-aspect"][0]["id"],
737                     sa["scaling-config-action"][0]["vnf-config-primitive-name-ref"],
738                 )
739             ),
740             norm(str(e.exception)),
741             "Wrong exception text",
742         )
743
744 1     @patch("osm_nbi.descriptor_topics.shutil")
745 1     @patch("osm_nbi.descriptor_topics.os.rename")
746 1     def test_new_vnfd_check_input_validation_healing_criteria_vdu_id(
747         self, mock_rename, mock_shutil
748     ):
749         """Testing input validation during new vnfd creation
750         for healing criteria with invalid vdu-id"""
751 1         did, test_vnfd = self.prepare_vnfd_creation()
752 1         test_vnfd = self.prepare_test_vnfd(test_vnfd)
753 1         test_vnfd["df"][0]["healing-aspect"][0]["healing-policy"][0][
754             "vdu-id"
755         ] = "vduid1"
756 1         affected_df = test_vnfd["df"][0]
757 1         ha = affected_df["healing-aspect"][0]
758 1         hp = ha["healing-policy"][0]
759 1         hp_vdu_id = hp["vdu-id"]
760
761 1         with self.assertRaises(
762             EngineException, msg="Accepted invalid Healing Group Policy Criteria"
763         ) as e:
764 1             self.topic.upload_content(
765                 fake_session, did, test_vnfd, {}, {"Content-Type": []}
766             )
767 1         self.assertEqual(
768             e.exception.http_code,
769             HTTPStatus.UNPROCESSABLE_ENTITY,
770             "Wrong HTTP status code",
771         )
772 1         self.assertIn(
773             norm(
774                 "df[id='{}']:healing-aspect[id='{}']:healing-policy"
775                 "[name='{}']: "
776                 "vdu-id='{}' not defined in vdu".format(
777                     affected_df["id"],
778                     ha["id"],
779                     hp["event-name"],
780                     hp_vdu_id,
781                 )
782             ),
783             norm(str(e.exception)),
784             "Wrong exception text",
785         )
786
787 1     @patch("osm_nbi.descriptor_topics.shutil")
788 1     @patch("osm_nbi.descriptor_topics.os.rename")
789 1     def test_new_vnfd_check_input_validation_alarm_criteria_monitoring_param_ref(
790         self, mock_rename, mock_shutil
791     ):
792         """Testing input validation during new vnfd creation
793         for alarm with invalid monitoring parameter reference"""
794 1         did, test_vnfd = self.prepare_vnfd_creation()
795 1         test_vnfd = self.prepare_test_vnfd(test_vnfd)
796 1         test_vnfd["vdu"][1]["alarm"][0]["vnf-monitoring-param-ref"] = "unit_test_alarm"
797 1         vdu = test_vnfd["vdu"][1]
798 1         alarm = vdu["alarm"][0]
799 1         alarm_monitoring_param = alarm["vnf-monitoring-param-ref"]
800
801 1         with self.assertRaises(
802             EngineException, msg="Accepted invalid Alarm Criteria"
803         ) as e:
804 1             self.topic.upload_content(
805                 fake_session, did, test_vnfd, {}, {"Content-Type": []}
806             )
807 1         self.assertEqual(
808             e.exception.http_code,
809             HTTPStatus.UNPROCESSABLE_ENTITY,
810             "Wrong HTTP status code",
811         )
812 1         self.assertIn(
813             norm(
814                 "vdu[id='{}']:alarm[id='{}']:"
815                 "vnf-monitoring-param-ref='{}' not defined in any monitoring-param".format(
816                     vdu["id"],
817                     alarm["alarm-id"],
818                     alarm_monitoring_param,
819                 )
820             ),
821             norm(str(e.exception)),
822             "Wrong exception text",
823         )
824
825 1     @patch("osm_nbi.descriptor_topics.shutil")
826 1     @patch("osm_nbi.descriptor_topics.os.rename")
827 1     def test_new_vnfd_check_input_validation_storage_reference_criteria(
828         self, mock_rename, mock_shutil
829     ):
830         """Testing input validation during new vnfd creation
831         for invalid virtual-storge-desc reference"""
832 1         did, test_vnfd = self.prepare_vnfd_creation()
833 1         test_vnfd = self.prepare_test_vnfd(test_vnfd)
834 1         test_vnfd["vdu"][1]["virtual-storage-desc"] = "unit_test_storage"
835 1         vdu = test_vnfd["vdu"][1]
836 1         vsd_ref = vdu["virtual-storage-desc"]
837
838 1         with self.assertRaises(
839             EngineException, msg="Accepted invalid virtual-storage-desc"
840         ) as e:
841 1             self.topic.upload_content(
842                 fake_session, did, test_vnfd, {}, {"Content-Type": []}
843             )
844 1         self.assertEqual(
845             e.exception.http_code,
846             HTTPStatus.UNPROCESSABLE_ENTITY,
847             "Wrong HTTP status code",
848         )
849 1         self.assertIn(
850             norm(
851                 "vdu[virtual-storage-desc='{}']"
852                 "not defined in vnfd".format(
853                     vsd_ref,
854                 )
855             ),
856             norm(str(e.exception)),
857             "Wrong exception text",
858         )
859
860 1     @patch("osm_nbi.descriptor_topics.shutil")
861 1     @patch("osm_nbi.descriptor_topics.os.rename")
862 1     def test_new_vnfd_check_input_validation_compute_reference_criteria(
863         self, mock_rename, mock_shutil
864     ):
865         """Testing input validation during new vnfd creation
866         for invalid virtual-compute-desc reference"""
867 1         did, test_vnfd = self.prepare_vnfd_creation()
868 1         test_vnfd = self.prepare_test_vnfd(test_vnfd)
869 1         test_vnfd["vdu"][1]["virtual-compute-desc"] = "unit_test_compute"
870 1         vdu = test_vnfd["vdu"][1]
871 1         vcd_ref = vdu["virtual-compute-desc"]
872
873 1         with self.assertRaises(
874             EngineException, msg="Accepted invalid virtual-compute-desc"
875         ) as e:
876 1             self.topic.upload_content(
877                 fake_session, did, test_vnfd, {}, {"Content-Type": []}
878             )
879 1         self.assertEqual(
880             e.exception.http_code,
881             HTTPStatus.UNPROCESSABLE_ENTITY,
882             "Wrong HTTP status code",
883         )
884 1         self.assertIn(
885             norm(
886                 "vdu[virtual-compute-desc='{}']"
887                 "not defined in vnfd".format(
888                     vcd_ref,
889                 )
890             ),
891             norm(str(e.exception)),
892             "Wrong exception text",
893         )
894
895 1     @patch("osm_nbi.descriptor_topics.shutil")
896 1     @patch("osm_nbi.descriptor_topics.os.rename")
897 1     def test_new_vnfd_check_input_validation_everything_right(
898         self, mock_rename, mock_shutil
899     ):
900         """Testing input validation during new vnfd creation
901         everything correct"""
902 1         did, test_vnfd = self.prepare_vnfd_creation()
903 1         test_vnfd = self.prepare_test_vnfd(test_vnfd)
904 1         test_vnfd["id"] = "fake-vnfd-id"
905 1         test_vnfd["df"][0].get("lcm-operations-configuration").get(
906             "operate-vnf-op-config"
907         )["day1-2"][0]["id"] = "fake-vnfd-id"
908 1         self.db.get_one.side_effect = [
909             {"_id": did, "_admin": deepcopy(db_vnfd_content["_admin"])},
910             None,
911         ]
912 1         rc = self.topic.upload_content(
913             fake_session, did, test_vnfd, {}, {"Content-Type": []}
914         )
915 1         self.assertTrue(rc, "Input Validation: Unexpected failure")
916
917 1     def test_edit_vnfd(self):
918 1         vnfd_content = deepcopy(db_vnfd_content)
919 1         did = vnfd_content["_id"]
920 1         self.fs.file_exists.return_value = True
921 1         self.fs.dir_ls.return_value = True
922 1         with self.subTest(i=1, t="Normal Edition"):
923 1             now = time()
924 1             self.db.get_one.side_effect = [deepcopy(vnfd_content), None]
925 1             data = {"product-name": "new-vnfd-name"}
926 1             self.topic.edit(fake_session, did, data)
927 1             db_args = self.db.replace.call_args[0]
928 1             msg_args = self.msg.write.call_args[0]
929 1             data["_id"] = did
930 1             self.assertEqual(msg_args[0], self.topic.topic_msg, "Wrong message topic")
931 1             self.assertEqual(msg_args[1], "edited", "Wrong message action")
932 1             self.assertEqual(msg_args[2], data, "Wrong message content")
933 1             self.assertEqual(db_args[0], self.topic.topic, "Wrong DB topic")
934 1             self.assertEqual(db_args[1], did, "Wrong DB ID")
935 1             self.assertEqual(
936                 db_args[2]["_admin"]["created"],
937                 vnfd_content["_admin"]["created"],
938                 "Wrong creation time",
939             )
940 1             self.assertGreater(
941                 db_args[2]["_admin"]["modified"], now, "Wrong modification time"
942             )
943 1             self.assertEqual(
944                 db_args[2]["_admin"]["projects_read"],
945                 vnfd_content["_admin"]["projects_read"],
946                 "Wrong read-only project list",
947             )
948 1             self.assertEqual(
949                 db_args[2]["_admin"]["projects_write"],
950                 vnfd_content["_admin"]["projects_write"],
951                 "Wrong read-write project list",
952             )
953 1             self.assertEqual(
954                 db_args[2]["product-name"], data["product-name"], "Wrong VNFD Name"
955             )
956 1         with self.subTest(i=2, t="Conflict on Edit"):
957 1             data = {"id": "hackfest3charmed-vnf", "product-name": "new-vnfd-name"}
958 1             self.db.get_one.side_effect = [
959                 deepcopy(vnfd_content),
960                 {"_id": str(uuid4()), "id": data["id"]},
961             ]
962 1             with self.assertRaises(
963                 EngineException, msg="Accepted existing VNFD ID"
964             ) as e:
965 1                 self.topic.edit(fake_session, did, data)
966 1             self.assertEqual(
967                 e.exception.http_code, HTTPStatus.CONFLICT, "Wrong HTTP status code"
968             )
969 1             self.assertIn(
970                 norm(
971                     "{} with id '{}' already exists for this project".format(
972                         "vnfd", data["id"]
973                     )
974                 ),
975                 norm(str(e.exception)),
976                 "Wrong exception text",
977             )
978 1         with self.subTest(i=3, t="Check Envelope"):
979 1             data = {"vnfd": [{"id": "new-vnfd-id-1", "product-name": "new-vnfd-name"}]}
980 1             with self.assertRaises(
981                 EngineException, msg="Accepted VNFD with wrong envelope"
982             ) as e:
983 1                 self.topic.edit(fake_session, did, data, content=vnfd_content)
984 1             self.assertEqual(
985                 e.exception.http_code, HTTPStatus.BAD_REQUEST, "Wrong HTTP status code"
986             )
987 1             self.assertIn(
988                 "'vnfd' must be dict", norm(str(e.exception)), "Wrong exception text"
989             )
990 1         return
991
992 1     def test_delete_vnfd(self):
993 1         did = db_vnfd_content["_id"]
994 1         self.db.get_one.return_value = db_vnfd_content
995 1         p_id = db_vnfd_content["_admin"]["projects_read"][0]
996 1         with self.subTest(i=1, t="Normal Deletion"):
997 1             self.db.get_list.return_value = []
998 1             self.db.del_one.return_value = {"deleted": 1}
999 1             self.topic.delete(fake_session, did)
1000 1             db_args = self.db.del_one.call_args[0]
1001 1             msg_args = self.msg.write.call_args[0]
1002 1             self.assertEqual(msg_args[0], self.topic.topic_msg, "Wrong message topic")
1003 1             self.assertEqual(msg_args[1], "deleted", "Wrong message action")
1004 1             self.assertEqual(msg_args[2], {"_id": did}, "Wrong message content")
1005 1             self.assertEqual(db_args[0], self.topic.topic, "Wrong DB topic")
1006 1             self.assertEqual(db_args[1]["_id"], did, "Wrong DB ID")
1007 1             self.assertEqual(
1008                 db_args[1]["_admin.projects_write.cont"],
1009                 [p_id, "ANY"],
1010                 "Wrong DB filter",
1011             )
1012 1             db_g1_args = self.db.get_one.call_args[0]
1013 1             self.assertEqual(db_g1_args[0], self.topic.topic, "Wrong DB topic")
1014 1             self.assertEqual(db_g1_args[1]["_id"], did, "Wrong DB VNFD ID")
1015 1             db_gl_calls = self.db.get_list.call_args_list
1016 1             self.assertEqual(db_gl_calls[0][0][0], "vnfrs", "Wrong DB topic")
1017             # self.assertEqual(db_gl_calls[0][0][1]["vnfd-id"], did, "Wrong DB VNFD ID")   # Filter changed after call
1018 1             self.assertEqual(db_gl_calls[1][0][0], "nsds", "Wrong DB topic")
1019 1             self.assertEqual(
1020                 db_gl_calls[1][0][1]["vnfd-id"],
1021                 db_vnfd_content["id"],
1022                 "Wrong DB NSD vnfd-id",
1023             )
1024
1025 1             self.assertEqual(
1026                 self.db.del_list.call_args[0][0],
1027                 self.topic.topic + "_revisions",
1028                 "Wrong DB topic",
1029             )
1030
1031 1             self.assertEqual(
1032                 self.db.del_list.call_args[0][1]["_id"]["$regex"],
1033                 did,
1034                 "Wrong ID for rexep delete",
1035             )
1036
1037 1             self.db.set_one.assert_not_called()
1038 1             fs_del_calls = self.fs.file_delete.call_args_list
1039 1             self.assertEqual(fs_del_calls[0][0][0], did, "Wrong FS file id")
1040 1             self.assertEqual(fs_del_calls[1][0][0], did + "_", "Wrong FS folder id")
1041 1         with self.subTest(i=2, t="Conflict on Delete - VNFD in use by VNFR"):
1042 1             self.db.get_list.return_value = [{"_id": str(uuid4()), "name": "fake-vnfr"}]
1043 1             with self.assertRaises(
1044                 EngineException, msg="Accepted VNFD in use by VNFR"
1045             ) as e:
1046 1                 self.topic.delete(fake_session, did)
1047 1             self.assertEqual(
1048                 e.exception.http_code, HTTPStatus.CONFLICT, "Wrong HTTP status code"
1049             )
1050 1             self.assertIn(
1051                 "there is at least one vnf instance using this descriptor",
1052                 norm(str(e.exception)),
1053                 "Wrong exception text",
1054             )
1055 1         with self.subTest(i=3, t="Conflict on Delete - VNFD in use by NSD"):
1056 1             self.db.get_list.side_effect = [
1057                 [],
1058                 [{"_id": str(uuid4()), "name": "fake-nsd"}],
1059             ]
1060 1             with self.assertRaises(
1061                 EngineException, msg="Accepted VNFD in use by NSD"
1062             ) as e:
1063 1                 self.topic.delete(fake_session, did)
1064 1             self.assertEqual(
1065                 e.exception.http_code, HTTPStatus.CONFLICT, "Wrong HTTP status code"
1066             )
1067 1             self.assertIn(
1068                 "there is at least one ns package referencing this descriptor",
1069                 norm(str(e.exception)),
1070                 "Wrong exception text",
1071             )
1072 1         with self.subTest(i=4, t="Non-existent VNFD"):
1073 1             excp_msg = "Not found any {} with filter='{}'".format("VNFD", {"_id": did})
1074 1             self.db.get_one.side_effect = DbException(excp_msg, HTTPStatus.NOT_FOUND)
1075 1             with self.assertRaises(
1076                 DbException, msg="Accepted non-existent VNFD ID"
1077             ) as e:
1078 1                 self.topic.delete(fake_session, did)
1079 1             self.assertEqual(
1080                 e.exception.http_code, HTTPStatus.NOT_FOUND, "Wrong HTTP status code"
1081             )
1082 1             self.assertIn(
1083                 norm(excp_msg), norm(str(e.exception)), "Wrong exception text"
1084             )
1085 1         with self.subTest(i=5, t="No delete because referenced by other project"):
1086 1             db_vnfd_content["_admin"]["projects_read"].append("other_project")
1087 1             self.db.get_one = Mock(return_value=db_vnfd_content)
1088 1             self.db.get_list = Mock(return_value=[])
1089 1             self.msg.write.reset_mock()
1090 1             self.db.del_one.reset_mock()
1091 1             self.fs.file_delete.reset_mock()
1092
1093 1             self.topic.delete(fake_session, did)
1094 1             self.db.del_one.assert_not_called()
1095 1             self.msg.write.assert_not_called()
1096 1             db_g1_args = self.db.get_one.call_args[0]
1097 1             self.assertEqual(db_g1_args[0], self.topic.topic, "Wrong DB topic")
1098 1             self.assertEqual(db_g1_args[1]["_id"], did, "Wrong DB VNFD ID")
1099 1             db_s1_args = self.db.set_one.call_args
1100 1             self.assertEqual(db_s1_args[0][0], self.topic.topic, "Wrong DB topic")
1101 1             self.assertEqual(db_s1_args[0][1]["_id"], did, "Wrong DB ID")
1102 1             self.assertIn(
1103                 p_id, db_s1_args[0][1]["_admin.projects_write.cont"], "Wrong DB filter"
1104             )
1105 1             self.assertIsNone(
1106                 db_s1_args[1]["update_dict"], "Wrong DB update dictionary"
1107             )
1108 1             self.assertEqual(
1109                 db_s1_args[1]["pull_list"],
1110                 {"_admin.projects_read": (p_id,), "_admin.projects_write": (p_id,)},
1111                 "Wrong DB pull_list dictionary",
1112             )
1113 1             self.fs.file_delete.assert_not_called()
1114 1         return
1115
1116 1     def prepare_vnfd_validation(self):
1117 1         descriptor_name = "test_descriptor"
1118 1         self.fs.file_open.side_effect = lambda path, mode: open(
1119             "/tmp/" + str(uuid4()), "a+b"
1120         )
1121 1         old_vnfd, new_vnfd = self.create_desc_temp(db_vnfd_content)
1122 1         return descriptor_name, old_vnfd, new_vnfd
1123
1124 1     @patch("osm_nbi.descriptor_topics.detect_descriptor_usage")
1125 1     @patch("osm_nbi.descriptor_topics.yaml.safe_load")
1126 1     def test_validate_vnfd_changes_day12_config_primitive_changed(
1127         self, mock_safe_load, mock_detect_usage
1128     ):
1129         """Validating VNFD for VNFD updates, day1-2 config primitive has changed"""
1130 1         descriptor_name, old_vnfd, new_vnfd = self.prepare_vnfd_validation()
1131 1         did = old_vnfd["_id"]
1132 1         new_vnfd["df"][0]["lcm-operations-configuration"]["operate-vnf-op-config"][
1133             "day1-2"
1134         ][0]["config-primitive"][0]["name"] = "new_action"
1135 1         mock_safe_load.side_effect = [old_vnfd, new_vnfd]
1136 1         mock_detect_usage.return_value = True
1137 1         self.db.get_one.return_value = old_vnfd
1138
1139 1         with self.assertNotRaises(EngineException):
1140 1             self.topic._validate_descriptor_changes(
1141                 did, descriptor_name, "/tmp/", "/tmp:1/"
1142             )
1143 1         self.db.get_one.assert_called_once()
1144 1         mock_detect_usage.assert_called_once()
1145 1         self.assertEqual(mock_safe_load.call_count, 2)
1146
1147 1     @patch("osm_nbi.descriptor_topics.detect_descriptor_usage")
1148 1     @patch("osm_nbi.descriptor_topics.yaml.safe_load")
1149 1     def test_validate_vnfd_changes_sw_version_changed(
1150         self, mock_safe_load, mock_detect_usage
1151     ):
1152         """Validating VNFD for updates, software version has changed"""
1153         # old vnfd uses the default software version: 1.0
1154 1         descriptor_name, old_vnfd, new_vnfd = self.prepare_vnfd_validation()
1155 1         did = old_vnfd["_id"]
1156 1         new_vnfd["software-version"] = "1.3"
1157 1         new_vnfd["sw-image-desc"][0]["name"] = "new-image"
1158 1         mock_safe_load.side_effect = [old_vnfd, new_vnfd]
1159 1         mock_detect_usage.return_value = True
1160 1         self.db.get_one.return_value = old_vnfd
1161
1162 1         with self.assertNotRaises(EngineException):
1163 1             self.topic._validate_descriptor_changes(
1164                 did, descriptor_name, "/tmp/", "/tmp:1/"
1165             )
1166 1         self.db.get_one.assert_called_once()
1167 1         mock_detect_usage.assert_called_once()
1168 1         self.assertEqual(mock_safe_load.call_count, 2)
1169
1170 1     @patch("osm_nbi.descriptor_topics.detect_descriptor_usage")
1171 1     @patch("osm_nbi.descriptor_topics.yaml.safe_load")
1172 1     def test_validate_vnfd_changes_sw_version_not_changed_mgm_cp_changed(
1173         self, mock_safe_load, mock_detect_usage
1174     ):
1175         """Validating VNFD for updates, software version has not
1176         changed, mgmt-cp has changed."""
1177 1         descriptor_name, old_vnfd, new_vnfd = self.prepare_vnfd_validation()
1178 1         new_vnfd["mgmt-cp"] = "new-mgmt-cp"
1179 1         mock_safe_load.side_effect = [old_vnfd, new_vnfd]
1180 1         did = old_vnfd["_id"]
1181 1         mock_detect_usage.return_value = True
1182 1         self.db.get_one.return_value = old_vnfd
1183
1184 1         with self.assertRaises(
1185             EngineException, msg="there are disallowed changes in the vnf descriptor"
1186         ) as e:
1187 1             self.topic._validate_descriptor_changes(
1188                 did, descriptor_name, "/tmp/", "/tmp:1/"
1189             )
1190
1191 1         self.assertEqual(
1192             e.exception.http_code,
1193             HTTPStatus.UNPROCESSABLE_ENTITY,
1194             "Wrong HTTP status code",
1195         )
1196 1         self.assertIn(
1197             norm("there are disallowed changes in the vnf descriptor"),
1198             norm(str(e.exception)),
1199             "Wrong exception text",
1200         )
1201 1         self.db.get_one.assert_called_once()
1202 1         mock_detect_usage.assert_called_once()
1203 1         self.assertEqual(mock_safe_load.call_count, 2)
1204
1205 1     @patch("osm_nbi.descriptor_topics.detect_descriptor_usage")
1206 1     @patch("osm_nbi.descriptor_topics.yaml.safe_load")
1207 1     def test_validate_vnfd_changes_sw_version_not_changed_mgm_cp_changed_vnfd_not_in_use(
1208         self, mock_safe_load, mock_detect_usage
1209     ):
1210         """Validating VNFD for updates, software version has not
1211         changed, mgmt-cp has changed, vnfd is not in use."""
1212 1         descriptor_name, old_vnfd, new_vnfd = self.prepare_vnfd_validation()
1213 1         new_vnfd["mgmt-cp"] = "new-mgmt-cp"
1214 1         mock_safe_load.side_effect = [old_vnfd, new_vnfd]
1215 1         did = old_vnfd["_id"]
1216 1         mock_detect_usage.return_value = None
1217 1         self.db.get_one.return_value = old_vnfd
1218
1219 1         with self.assertNotRaises(EngineException):
1220 1             self.topic._validate_descriptor_changes(
1221                 did, descriptor_name, "/tmp/", "/tmp:1/"
1222             )
1223
1224 1         self.db.get_one.assert_called_once()
1225 1         mock_detect_usage.assert_called_once()
1226 1         mock_safe_load.assert_not_called()
1227
1228 1     def test_validate_mgmt_interface_connection_point_on_valid_descriptor(self):
1229 1         indata = deepcopy(db_vnfd_content)
1230 1         self.topic.validate_mgmt_interface_connection_point(indata)
1231
1232 1     def test_validate_mgmt_interface_connection_point_when_missing_connection_point(
1233         self,
1234     ):
1235 1         indata = deepcopy(db_vnfd_content)
1236 1         indata["ext-cpd"] = []
1237 1         with self.assertRaises(EngineException) as e:
1238 1             self.topic.validate_mgmt_interface_connection_point(indata)
1239 1         self.assertEqual(
1240             e.exception.http_code,
1241             HTTPStatus.UNPROCESSABLE_ENTITY,
1242             "Wrong HTTP status code",
1243         )
1244 1         self.assertIn(
1245             norm(
1246                 "mgmt-cp='{}' must match an existing ext-cpd".format(indata["mgmt-cp"])
1247             ),
1248             norm(str(e.exception)),
1249             "Wrong exception text",
1250         )
1251
1252 1     def test_validate_mgmt_interface_connection_point_when_missing_mgmt_cp(self):
1253 1         indata = deepcopy(db_vnfd_content)
1254 1         indata.pop("mgmt-cp")
1255 1         with self.assertRaises(EngineException) as e:
1256 1             self.topic.validate_mgmt_interface_connection_point(indata)
1257 1         self.assertEqual(
1258             e.exception.http_code,
1259             HTTPStatus.UNPROCESSABLE_ENTITY,
1260             "Wrong HTTP status code",
1261         )
1262 1         self.assertIn(
1263             norm("'mgmt-cp' is a mandatory field and it is not defined"),
1264             norm(str(e.exception)),
1265             "Wrong exception text",
1266         )
1267
1268 1     def test_validate_vdu_internal_connection_points_on_valid_descriptor(self):
1269 1         indata = db_vnfd_content
1270 1         vdu = indata["vdu"][0]
1271 1         self.topic.validate_vdu_internal_connection_points(vdu)
1272
1273 1     def test_validate_external_connection_points_on_valid_descriptor(self):
1274 1         indata = db_vnfd_content
1275 1         self.topic.validate_external_connection_points(indata)
1276
1277 1     def test_validate_external_connection_points_when_missing_internal_connection_point(
1278         self,
1279     ):
1280 1         indata = deepcopy(db_vnfd_content)
1281 1         vdu = indata["vdu"][0]
1282 1         vdu.pop("int-cpd")
1283 1         affected_ext_cpd = indata["ext-cpd"][0]
1284 1         with self.assertRaises(EngineException) as e:
1285 1             self.topic.validate_external_connection_points(indata)
1286 1         self.assertEqual(
1287             e.exception.http_code,
1288             HTTPStatus.UNPROCESSABLE_ENTITY,
1289             "Wrong HTTP status code",
1290         )
1291 1         self.assertIn(
1292             norm(
1293                 "ext-cpd[id='{}']:int-cpd must match an existing vdu int-cpd".format(
1294                     affected_ext_cpd["id"]
1295                 )
1296             ),
1297             norm(str(e.exception)),
1298             "Wrong exception text",
1299         )
1300
1301 1     def test_validate_vdu_internal_connection_points_on_duplicated_internal_connection_point(
1302         self,
1303     ):
1304 1         indata = deepcopy(db_vnfd_content)
1305 1         vdu = indata["vdu"][0]
1306 1         duplicated_cpd = {
1307             "id": "vnf-mgmt",
1308             "order": 3,
1309             "virtual-network-interface-requirement": [{"name": "duplicated"}],
1310         }
1311 1         vdu["int-cpd"].insert(0, duplicated_cpd)
1312 1         with self.assertRaises(EngineException) as e:
1313 1             self.topic.validate_vdu_internal_connection_points(vdu)
1314 1         self.assertEqual(
1315             e.exception.http_code,
1316             HTTPStatus.UNPROCESSABLE_ENTITY,
1317             "Wrong HTTP status code",
1318         )
1319 1         self.assertIn(
1320             norm(
1321                 "vdu[id='{}']:int-cpd[id='{}'] is already used by other int-cpd".format(
1322                     vdu["id"], duplicated_cpd["id"]
1323                 )
1324             ),
1325             norm(str(e.exception)),
1326             "Wrong exception text",
1327         )
1328
1329 1     def test_validate_external_connection_points_on_duplicated_external_connection_point(
1330         self,
1331     ):
1332 1         indata = deepcopy(db_vnfd_content)
1333 1         duplicated_cpd = {
1334             "id": "vnf-mgmt-ext",
1335             "int-cpd": {"vdu-id": "dataVM", "cpd": "vnf-data"},
1336         }
1337 1         indata["ext-cpd"].insert(0, duplicated_cpd)
1338 1         with self.assertRaises(EngineException) as e:
1339 1             self.topic.validate_external_connection_points(indata)
1340 1         self.assertEqual(
1341             e.exception.http_code,
1342             HTTPStatus.UNPROCESSABLE_ENTITY,
1343             "Wrong HTTP status code",
1344         )
1345 1         self.assertIn(
1346             norm(
1347                 "ext-cpd[id='{}'] is already used by other ext-cpd".format(
1348                     duplicated_cpd["id"]
1349                 )
1350             ),
1351             norm(str(e.exception)),
1352             "Wrong exception text",
1353         )
1354
1355 1     def test_validate_internal_virtual_links_on_valid_descriptor(self):
1356 1         indata = db_vnfd_content
1357 1         self.topic.validate_internal_virtual_links(indata)
1358
1359 1     def test_validate_internal_virtual_links_on_duplicated_ivld(self):
1360 1         indata = deepcopy(db_vnfd_content)
1361 1         duplicated_vld = {"id": "internal"}
1362 1         indata["int-virtual-link-desc"].insert(0, duplicated_vld)
1363 1         with self.assertRaises(EngineException) as e:
1364 1             self.topic.validate_internal_virtual_links(indata)
1365 1         self.assertEqual(
1366             e.exception.http_code,
1367             HTTPStatus.UNPROCESSABLE_ENTITY,
1368             "Wrong HTTP status code",
1369         )
1370 1         self.assertIn(
1371             norm(
1372                 "Duplicated VLD id in int-virtual-link-desc[id={}]".format(
1373                     duplicated_vld["id"]
1374                 )
1375             ),
1376             norm(str(e.exception)),
1377             "Wrong exception text",
1378         )
1379
1380 1     def test_validate_internal_virtual_links_when_missing_ivld_on_connection_point(
1381         self,
1382     ):
1383 1         indata = deepcopy(db_vnfd_content)
1384 1         vdu = indata["vdu"][0]
1385 1         affected_int_cpd = vdu["int-cpd"][0]
1386 1         affected_int_cpd["int-virtual-link-desc"] = "non-existing-int-virtual-link-desc"
1387 1         with self.assertRaises(EngineException) as e:
1388 1             self.topic.validate_internal_virtual_links(indata)
1389 1         self.assertEqual(
1390             e.exception.http_code,
1391             HTTPStatus.UNPROCESSABLE_ENTITY,
1392             "Wrong HTTP status code",
1393         )
1394 1         self.assertIn(
1395             norm(
1396                 "vdu[id='{}']:int-cpd[id='{}']:int-virtual-link-desc='{}' must match an existing "
1397                 "int-virtual-link-desc".format(
1398                     vdu["id"],
1399                     affected_int_cpd["id"],
1400                     affected_int_cpd["int-virtual-link-desc"],
1401                 )
1402             ),
1403             norm(str(e.exception)),
1404             "Wrong exception text",
1405         )
1406
1407 1     def test_validate_internal_virtual_links_when_missing_ivld_on_profile(self):
1408 1         indata = deepcopy(db_vnfd_content)
1409 1         affected_ivld_profile = {"id": "non-existing-int-virtual-link-desc"}
1410 1         df = indata["df"][0]
1411 1         df["virtual-link-profile"] = [affected_ivld_profile]
1412 1         with self.assertRaises(EngineException) as e:
1413 1             self.topic.validate_internal_virtual_links(indata)
1414 1         self.assertEqual(
1415             e.exception.http_code,
1416             HTTPStatus.UNPROCESSABLE_ENTITY,
1417             "Wrong HTTP status code",
1418         )
1419 1         self.assertIn(
1420             norm(
1421                 "df[id='{}']:virtual-link-profile='{}' must match an existing "
1422                 "int-virtual-link-desc".format(df["id"], affected_ivld_profile["id"])
1423             ),
1424             norm(str(e.exception)),
1425             "Wrong exception text",
1426         )
1427
1428 1     def test_validate_monitoring_params_on_valid_descriptor(self):
1429 1         indata = db_vnfd_content
1430 1         self.topic.validate_monitoring_params(indata)
1431
1432 1     def test_validate_monitoring_params_on_duplicated_ivld_monitoring_param(self):
1433 1         indata = deepcopy(db_vnfd_content)
1434 1         duplicated_mp = {"id": "cpu", "name": "cpu", "performance_metric": "cpu"}
1435 1         affected_ivld = indata["int-virtual-link-desc"][0]
1436 1         affected_ivld["monitoring-parameters"] = [duplicated_mp, duplicated_mp]
1437 1         with self.assertRaises(EngineException) as e:
1438 1             self.topic.validate_monitoring_params(indata)
1439 1         self.assertEqual(
1440             e.exception.http_code,
1441             HTTPStatus.UNPROCESSABLE_ENTITY,
1442             "Wrong HTTP status code",
1443         )
1444 1         self.assertIn(
1445             norm(
1446                 "Duplicated monitoring-parameter id in "
1447                 "int-virtual-link-desc[id='{}']:monitoring-parameters[id='{}']".format(
1448                     affected_ivld["id"], duplicated_mp["id"]
1449                 )
1450             ),
1451             norm(str(e.exception)),
1452             "Wrong exception text",
1453         )
1454
1455 1     def test_validate_monitoring_params_on_duplicated_vdu_monitoring_param(self):
1456 1         indata = deepcopy(db_vnfd_content)
1457 1         duplicated_mp = {
1458             "id": "dataVM_cpu_util",
1459             "name": "dataVM_cpu_util",
1460             "performance_metric": "cpu",
1461         }
1462 1         affected_vdu = indata["vdu"][1]
1463 1         affected_vdu["monitoring-parameter"].insert(0, duplicated_mp)
1464 1         with self.assertRaises(EngineException) as e:
1465 1             self.topic.validate_monitoring_params(indata)
1466 1         self.assertEqual(
1467             e.exception.http_code,
1468             HTTPStatus.UNPROCESSABLE_ENTITY,
1469             "Wrong HTTP status code",
1470         )
1471 1         self.assertIn(
1472             norm(
1473                 "Duplicated monitoring-parameter id in "
1474                 "vdu[id='{}']:monitoring-parameter[id='{}']".format(
1475                     affected_vdu["id"], duplicated_mp["id"]
1476                 )
1477             ),
1478             norm(str(e.exception)),
1479             "Wrong exception text",
1480         )
1481
1482 1     def test_validate_monitoring_params_on_duplicated_df_monitoring_param(self):
1483 1         indata = deepcopy(db_vnfd_content)
1484 1         duplicated_mp = {
1485             "id": "memory",
1486             "name": "memory",
1487             "performance_metric": "memory",
1488         }
1489 1         affected_df = indata["df"][0]
1490 1         affected_df["monitoring-parameter"] = [duplicated_mp, duplicated_mp]
1491 1         with self.assertRaises(EngineException) as e:
1492 1             self.topic.validate_monitoring_params(indata)
1493 1         self.assertEqual(
1494             e.exception.http_code,
1495             HTTPStatus.UNPROCESSABLE_ENTITY,
1496             "Wrong HTTP status code",
1497         )
1498 1         self.assertIn(
1499             norm(
1500                 "Duplicated monitoring-parameter id in "
1501                 "df[id='{}']:monitoring-parameter[id='{}']".format(
1502                     affected_df["id"], duplicated_mp["id"]
1503                 )
1504             ),
1505             norm(str(e.exception)),
1506             "Wrong exception text",
1507         )
1508
1509 1     def test_validate_scaling_group_descriptor_on_valid_descriptor(self):
1510 1         indata = db_vnfd_content
1511 1         self.topic.validate_scaling_group_descriptor(indata)
1512
1513 1     def test_validate_scaling_group_descriptor_when_missing_monitoring_param(self):
1514 1         indata = deepcopy(db_vnfd_content)
1515 1         vdu = indata["vdu"][1]
1516 1         affected_df = indata["df"][0]
1517 1         affected_sa = affected_df["scaling-aspect"][0]
1518 1         affected_sp = affected_sa["scaling-policy"][0]
1519 1         affected_sc = affected_sp["scaling-criteria"][0]
1520 1         vdu.pop("monitoring-parameter")
1521 1         with self.assertRaises(EngineException) as e:
1522 1             self.topic.validate_scaling_group_descriptor(indata)
1523 1         self.assertEqual(
1524             e.exception.http_code,
1525             HTTPStatus.UNPROCESSABLE_ENTITY,
1526             "Wrong HTTP status code",
1527         )
1528 1         self.assertIn(
1529             norm(
1530                 "df[id='{}']:scaling-aspect[id='{}']:scaling-policy"
1531                 "[name='{}']:scaling-criteria[name='{}']: "
1532                 "vnf-monitoring-param-ref='{}' not defined in any monitoring-param".format(
1533                     affected_df["id"],
1534                     affected_sa["id"],
1535                     affected_sp["name"],
1536                     affected_sc["name"],
1537                     affected_sc["vnf-monitoring-param-ref"],
1538                 )
1539             ),
1540             norm(str(e.exception)),
1541             "Wrong exception text",
1542         )
1543
1544 1     def test_validate_scaling_group_descriptor_when_missing_vnf_configuration(self):
1545 1         indata = deepcopy(db_vnfd_content)
1546 1         df = indata["df"][0]
1547 1         affected_sa = df["scaling-aspect"][0]
1548 1         indata["df"][0]["lcm-operations-configuration"]["operate-vnf-op-config"][
1549             "day1-2"
1550         ].pop()
1551 1         with self.assertRaises(EngineException) as e:
1552 1             self.topic.validate_scaling_group_descriptor(indata)
1553 1         self.assertEqual(
1554             e.exception.http_code,
1555             HTTPStatus.UNPROCESSABLE_ENTITY,
1556             "Wrong HTTP status code",
1557         )
1558 1         self.assertIn(
1559             norm(
1560                 "'day1-2 configuration' not defined in the descriptor but it is referenced "
1561                 "by df[id='{}']:scaling-aspect[id='{}']:scaling-config-action".format(
1562                     df["id"], affected_sa["id"]
1563                 )
1564             ),
1565             norm(str(e.exception)),
1566             "Wrong exception text",
1567         )
1568
1569 1     def test_validate_scaling_group_descriptor_when_missing_scaling_config_action_primitive(
1570         self,
1571     ):
1572 1         indata = deepcopy(db_vnfd_content)
1573 1         df = indata["df"][0]
1574 1         affected_sa = df["scaling-aspect"][0]
1575 1         affected_sca_primitive = affected_sa["scaling-config-action"][0][
1576             "vnf-config-primitive-name-ref"
1577         ]
1578 1         df["lcm-operations-configuration"]["operate-vnf-op-config"]["day1-2"][0][
1579             "config-primitive"
1580         ] = []
1581 1         with self.assertRaises(EngineException) as e:
1582 1             self.topic.validate_scaling_group_descriptor(indata)
1583 1         self.assertEqual(
1584             e.exception.http_code,
1585             HTTPStatus.UNPROCESSABLE_ENTITY,
1586             "Wrong HTTP status code",
1587         )
1588 1         self.assertIn(
1589             norm(
1590                 "df[id='{}']:scaling-aspect[id='{}']:scaling-config-action:vnf-"
1591                 "config-primitive-name-ref='{}' does not match any "
1592                 "day1-2 configuration:config-primitive:name".format(
1593                     df["id"], affected_sa["id"], affected_sca_primitive
1594                 )
1595             ),
1596             norm(str(e.exception)),
1597             "Wrong exception text",
1598         )
1599
1600 1     def test_new_vnfd_revision(self):
1601 1         did = db_vnfd_content["_id"]
1602 1         self.fs.get_params.return_value = {}
1603 1         self.fs.file_exists.return_value = False
1604 1         self.fs.file_open.side_effect = lambda path, mode: open(
1605             "/tmp/" + str(uuid4()), "a+b"
1606         )
1607 1         test_vnfd = deepcopy(db_vnfd_content)
1608 1         del test_vnfd["_id"]
1609 1         del test_vnfd["_admin"]
1610 1         self.db.create.return_value = did
1611 1         rollback = []
1612 1         did2, oid = self.topic.new(rollback, fake_session, {})
1613 1         db_args = self.db.create.call_args[0]
1614 1         self.assertEqual(
1615             db_args[1]["_admin"]["revision"], 0, "New package should be at revision 0"
1616         )
1617
1618 1     @patch("osm_nbi.descriptor_topics.shutil")
1619 1     @patch("osm_nbi.descriptor_topics.os.rename")
1620 1     def test_update_vnfd(self, mock_rename, mock_shutil):
1621 1         old_revision = 5
1622 1         did = db_vnfd_content["_id"]
1623 1         self.fs.path = ""
1624 1         self.fs.get_params.return_value = {}
1625 1         self.fs.file_exists.return_value = False
1626 1         self.fs.file_open.side_effect = lambda path, mode: open(
1627             "/tmp/" + str(uuid4()), "a+b"
1628         )
1629 1         new_vnfd = deepcopy(db_vnfd_content)
1630 1         del new_vnfd["_id"]
1631 1         self.db.create.return_value = did
1632 1         rollback = []
1633 1         did2, oid = self.topic.new(rollback, fake_session, {})
1634 1         del new_vnfd["vdu"][0]["cloud-init-file"]
1635 1         del new_vnfd["df"][0]["lcm-operations-configuration"]["operate-vnf-op-config"][
1636             "day1-2"
1637         ][0]["execution-environment-list"][0]["juju"]
1638
1639 1         old_vnfd = {"_id": did, "_admin": deepcopy(db_vnfd_content["_admin"])}
1640 1         old_vnfd["_admin"]["revision"] = old_revision
1641
1642 1         self.db.get_one.side_effect = [old_vnfd, old_vnfd, None]
1643 1         self.topic.upload_content(fake_session, did, new_vnfd, {}, {"Content-Type": []})
1644
1645 1         db_args = self.db.replace.call_args[0]
1646 1         self.assertEqual(
1647             db_args[2]["_admin"]["revision"],
1648             old_revision + 1,
1649             "Revision should increment",
1650         )
1651
1652
1653 1 class Test_NsdTopic(TestCase):
1654 1     @classmethod
1655 1     def setUpClass(cls):
1656 1         cls.test_name = "test-nsd-topic"
1657
1658 1     @classmethod
1659 1     def tearDownClass(cls):
1660 1         pass
1661
1662 1     def setUp(self):
1663 1         self.db = Mock(dbbase.DbBase())
1664 1         self.fs = Mock(fsbase.FsBase())
1665 1         self.msg = Mock(msgbase.MsgBase())
1666 1         self.auth = Mock(authconn.Authconn(None, None, None))
1667 1         self.topic = NsdTopic(self.db, self.fs, self.msg, self.auth)
1668 1         self.topic.check_quota = Mock(return_value=None)  # skip quota
1669
1670 1     @contextmanager
1671 1     def assertNotRaises(self, exception_type):
1672 1         try:
1673 1             yield None
1674 0         except exception_type:
1675 0             raise self.failureException("{} raised".format(exception_type.__name__))
1676
1677 1     def create_desc_temp(self, template):
1678 1         old_desc = deepcopy(template)
1679 1         new_desc = deepcopy(template)
1680 1         return old_desc, new_desc
1681
1682 1     def prepare_nsd_creation(self):
1683 1         self.fs.path = ""
1684 1         did = db_nsd_content["_id"]
1685 1         self.fs.get_params.return_value = {}
1686 1         self.fs.file_exists.return_value = False
1687 1         self.fs.file_open.side_effect = lambda path, mode: tempfile.TemporaryFile(
1688             mode="a+b"
1689         )
1690 1         self.db.get_one.side_effect = [
1691             {"_id": did, "_admin": deepcopy(db_nsd_content["_admin"])},
1692             None,
1693         ]
1694 1         test_nsd = deepcopy(db_nsd_content)
1695 1         del test_nsd["_id"]
1696 1         del test_nsd["_admin"]
1697 1         return did, test_nsd
1698
1699 1     @patch("osm_nbi.descriptor_topics.shutil")
1700 1     @patch("osm_nbi.descriptor_topics.os.rename")
1701 1     def test_new_nsd_normal_creation(self, mock_rename, mock_shutil):
1702 1         did, test_nsd = self.prepare_nsd_creation()
1703 1         self.db.create.return_value = did
1704 1         rollback = []
1705
1706 1         did2, oid = self.topic.new(rollback, fake_session, {})
1707 1         db_args = self.db.create.call_args[0]
1708 1         msg_args = self.msg.write.call_args[0]
1709 1         self.assertEqual(len(rollback), 1, "Wrong rollback length")
1710 1         self.assertEqual(msg_args[0], self.topic.topic_msg, "Wrong message topic")
1711 1         self.assertEqual(msg_args[1], "created", "Wrong message action")
1712 1         self.assertEqual(msg_args[2], {"_id": did}, "Wrong message content")
1713 1         self.assertEqual(db_args[0], self.topic.topic, "Wrong DB topic")
1714 1         self.assertEqual(did2, did, "Wrong DB NSD id")
1715 1         self.assertIsNotNone(db_args[1]["_admin"]["created"], "Wrong creation time")
1716 1         self.assertEqual(
1717             db_args[1]["_admin"]["modified"],
1718             db_args[1]["_admin"]["created"],
1719             "Wrong modification time",
1720         )
1721 1         self.assertEqual(
1722             db_args[1]["_admin"]["projects_read"],
1723             [test_pid],
1724             "Wrong read-only project list",
1725         )
1726 1         self.assertEqual(
1727             db_args[1]["_admin"]["projects_write"],
1728             [test_pid],
1729             "Wrong read-write project list",
1730         )
1731
1732 1         self.db.get_list.return_value = [db_vnfd_content]
1733
1734 1         self.topic.upload_content(fake_session, did, test_nsd, {}, {"Content-Type": []})
1735 1         msg_args = self.msg.write.call_args[0]
1736 1         test_nsd["_id"] = did
1737 1         self.assertEqual(msg_args[0], self.topic.topic_msg, "Wrong message topic")
1738 1         self.assertEqual(msg_args[1], "edited", "Wrong message action")
1739 1         self.assertEqual(msg_args[2], test_nsd, "Wrong message content")
1740
1741 1         db_args = self.db.get_one.mock_calls[0][1]
1742 1         self.assertEqual(db_args[0], self.topic.topic, "Wrong DB topic")
1743 1         self.assertEqual(db_args[1]["_id"], did, "Wrong DB NSD id")
1744
1745 1         db_args = self.db.replace.call_args[0]
1746 1         self.assertEqual(db_args[0], self.topic.topic, "Wrong DB topic")
1747 1         self.assertEqual(db_args[1], did, "Wrong DB NSD id")
1748
1749 1         admin = db_args[2]["_admin"]
1750 1         db_admin = db_nsd_content["_admin"]
1751 1         self.assertEqual(admin["created"], db_admin["created"], "Wrong creation time")
1752 1         self.assertGreater(
1753             admin["modified"], db_admin["created"], "Wrong modification time"
1754         )
1755 1         self.assertEqual(
1756             admin["projects_read"],
1757             db_admin["projects_read"],
1758             "Wrong read-only project list",
1759         )
1760 1         self.assertEqual(
1761             admin["projects_write"],
1762             db_admin["projects_write"],
1763             "Wrong read-write project list",
1764         )
1765 1         self.assertEqual(
1766             admin["onboardingState"], "ONBOARDED", "Wrong onboarding state"
1767         )
1768 1         self.assertEqual(
1769             admin["operationalState"], "ENABLED", "Wrong operational state"
1770         )
1771 1         self.assertEqual(admin["usageState"], "NOT_IN_USE", "Wrong usage state")
1772
1773 1         storage = admin["storage"]
1774 1         self.assertEqual(storage["folder"], did + ":1", "Wrong storage folder")
1775 1         self.assertEqual(storage["descriptor"], "package", "Wrong storage descriptor")
1776
1777 1         compare_desc(self, test_nsd, db_args[2], "NSD")
1778 1         revision_args = self.db.create.call_args[0]
1779 1         self.assertEqual(
1780             revision_args[0], self.topic.topic + "_revisions", "Wrong topic"
1781         )
1782 1         self.assertEqual(revision_args[1]["id"], db_args[2]["id"], "Wrong revision id")
1783 1         self.assertEqual(
1784             revision_args[1]["_id"], db_args[2]["_id"] + ":1", "Wrong revision _id"
1785         )
1786
1787 1     @patch("osm_nbi.descriptor_topics.shutil")
1788 1     @patch("osm_nbi.descriptor_topics.os.rename")
1789 1     def test_new_nsd_check_pyangbind_validation_required_properties(
1790         self, mock_rename, mock_shutil
1791     ):
1792 1         did, test_nsd = self.prepare_nsd_creation()
1793 1         del test_nsd["id"]
1794
1795 1         with self.assertRaises(
1796             EngineException, msg="Accepted NSD with a missing required property"
1797         ) as e:
1798 1             self.topic.upload_content(
1799                 fake_session, did, test_nsd, {}, {"Content-Type": []}
1800             )
1801 1         self.assertEqual(
1802             e.exception.http_code,
1803             HTTPStatus.UNPROCESSABLE_ENTITY,
1804             "Wrong HTTP status code",
1805         )
1806 1         self.assertIn(
1807             norm("Error in pyangbind validation: '{}'".format("id")),
1808             norm(str(e.exception)),
1809             "Wrong exception text",
1810         )
1811
1812 1     @patch("osm_nbi.descriptor_topics.shutil")
1813 1     @patch("osm_nbi.descriptor_topics.os.rename")
1814 1     def test_new_nsd_check_pyangbind_validation_additional_properties(
1815         self, mock_rename, mock_shutil
1816     ):
1817 1         did, test_nsd = self.prepare_nsd_creation()
1818 1         test_nsd["extra-property"] = 0
1819
1820 1         with self.assertRaises(
1821             EngineException, msg="Accepted NSD with an additional property"
1822         ) as e:
1823 1             self.topic.upload_content(
1824                 fake_session, did, test_nsd, {}, {"Content-Type": []}
1825             )
1826 1         self.assertEqual(
1827             e.exception.http_code,
1828             HTTPStatus.UNPROCESSABLE_ENTITY,
1829             "Wrong HTTP status code",
1830         )
1831 1         self.assertIn(
1832             norm(
1833                 "Error in pyangbind validation: {} ({})".format(
1834                     "json object contained a key that did not exist", "extra-property"
1835                 )
1836             ),
1837             norm(str(e.exception)),
1838             "Wrong exception text",
1839         )
1840
1841 1     @patch("osm_nbi.descriptor_topics.shutil")
1842 1     @patch("osm_nbi.descriptor_topics.os.rename")
1843 1     def test_new_nsd_check_pyangbind_validation_property_types(
1844         self, mock_rename, mock_shutil
1845     ):
1846 1         did, test_nsd = self.prepare_nsd_creation()
1847 1         test_nsd["designer"] = {"key": 0}
1848
1849 1         with self.assertRaises(
1850             EngineException, msg="Accepted NSD with a wrongly typed property"
1851         ) as e:
1852 1             self.topic.upload_content(
1853                 fake_session, did, test_nsd, {}, {"Content-Type": []}
1854             )
1855 1         self.assertEqual(
1856             e.exception.http_code,
1857             HTTPStatus.UNPROCESSABLE_ENTITY,
1858             "Wrong HTTP status code",
1859         )
1860 1         self.assertIn(
1861             norm(
1862                 "Error in pyangbind validation: {} ({})".format(
1863                     "json object contained a key that did not exist", "key"
1864                 )
1865             ),
1866             norm(str(e.exception)),
1867             "Wrong exception text",
1868         )
1869
1870 1     @patch("osm_nbi.descriptor_topics.shutil")
1871 1     @patch("osm_nbi.descriptor_topics.os.rename")
1872 1     def test_new_nsd_check_input_validation_mgmt_network_virtual_link_protocol_data(
1873         self, mock_rename, mock_shutil
1874     ):
1875 1         did, test_nsd = self.prepare_nsd_creation()
1876 1         df = test_nsd["df"][0]
1877 1         mgmt_profile = {
1878             "id": "id",
1879             "virtual-link-desc-id": "mgmt",
1880             "virtual-link-protocol-data": {"associated-layer-protocol": "ipv4"},
1881         }
1882 1         df["virtual-link-profile"] = [mgmt_profile]
1883
1884 1         with self.assertRaises(
1885             EngineException, msg="Accepted VLD with mgmt-network+ip-profile"
1886         ) as e:
1887 1             self.topic.upload_content(
1888                 fake_session, did, test_nsd, {}, {"Content-Type": []}
1889             )
1890 1         self.assertEqual(
1891             e.exception.http_code,
1892             HTTPStatus.UNPROCESSABLE_ENTITY,
1893             "Wrong HTTP status code",
1894         )
1895 1         self.assertIn(
1896             norm(
1897                 "Error at df[id='{}']:virtual-link-profile[id='{}']:virtual-link-protocol-data"
1898                 " You cannot set a virtual-link-protocol-data when mgmt-network is True".format(
1899                     df["id"], mgmt_profile["id"]
1900                 )
1901             ),
1902             norm(str(e.exception)),
1903             "Wrong exception text",
1904         )
1905
1906 1     @patch("osm_nbi.descriptor_topics.shutil")
1907 1     @patch("osm_nbi.descriptor_topics.os.rename")
1908 1     def test_new_nsd_check_descriptor_dependencies_vnfd_id(
1909         self, mock_rename, mock_shutil
1910     ):
1911 1         did, test_nsd = self.prepare_nsd_creation()
1912 1         self.db.get_list.return_value = []
1913
1914 1         with self.assertRaises(
1915             EngineException, msg="Accepted wrong VNFD ID reference"
1916         ) as e:
1917 1             self.topic.upload_content(
1918                 fake_session, did, test_nsd, {}, {"Content-Type": []}
1919             )
1920 1         self.assertEqual(
1921             e.exception.http_code, HTTPStatus.CONFLICT, "Wrong HTTP status code"
1922         )
1923 1         self.assertIn(
1924             norm(
1925                 "'vnfd-id'='{}' references a non existing vnfd".format(
1926                     test_nsd["vnfd-id"][0]
1927                 )
1928             ),
1929             norm(str(e.exception)),
1930             "Wrong exception text",
1931         )
1932
1933 1     @patch("osm_nbi.descriptor_topics.shutil")
1934 1     @patch("osm_nbi.descriptor_topics.os.rename")
1935 1     def test_new_nsd_check_descriptor_dependencies_vld_vnfd_connection_point_ref(
1936         self, mock_rename, mock_shutil
1937     ):
1938         # Check Descriptor Dependencies: "vld[vnfd-connection-point-ref][vnfd-connection-point-ref]
1939 1         did, test_nsd = self.prepare_nsd_creation()
1940 1         vnfd_descriptor = deepcopy(db_vnfd_content)
1941 1         df = test_nsd["df"][0]
1942 1         affected_vnf_profile = df["vnf-profile"][0]
1943 1         affected_virtual_link = affected_vnf_profile["virtual-link-connectivity"][1]
1944 1         affected_cpd = vnfd_descriptor["ext-cpd"].pop()
1945 1         self.db.get_list.return_value = [vnfd_descriptor]
1946
1947 1         with self.assertRaises(
1948             EngineException, msg="Accepted wrong VLD CP reference"
1949         ) as e:
1950 1             self.topic.upload_content(
1951                 fake_session, did, test_nsd, {}, {"Content-Type": []}
1952             )
1953 1         self.assertEqual(
1954             e.exception.http_code,
1955             HTTPStatus.UNPROCESSABLE_ENTITY,
1956             "Wrong HTTP status code",
1957         )
1958 1         self.assertIn(
1959             norm(
1960                 "Error at df[id='{}']:vnf-profile[id='{}']:virtual-link-connectivity"
1961                 "[virtual-link-profile-id='{}']:constituent-cpd-id='{}' references a "
1962                 "non existing ext-cpd:id inside vnfd '{}'".format(
1963                     df["id"],
1964                     affected_vnf_profile["id"],
1965                     affected_virtual_link["virtual-link-profile-id"],
1966                     affected_cpd["id"],
1967                     vnfd_descriptor["id"],
1968                 )
1969             ),
1970             norm(str(e.exception)),
1971             "Wrong exception text",
1972         )
1973
1974 1     def test_edit_nsd(self):
1975 1         nsd_content = deepcopy(db_nsd_content)
1976 1         did = nsd_content["_id"]
1977 1         self.fs.file_exists.return_value = True
1978 1         self.fs.dir_ls.return_value = True
1979 1         with self.subTest(i=1, t="Normal Edition"):
1980 1             now = time()
1981 1             self.db.get_one.side_effect = [deepcopy(nsd_content), None]
1982 1             self.db.get_list.return_value = [db_vnfd_content]
1983 1             data = {"id": "new-nsd-id", "name": "new-nsd-name"}
1984 1             self.topic.edit(fake_session, did, data)
1985 1             db_args = self.db.replace.call_args[0]
1986 1             msg_args = self.msg.write.call_args[0]
1987 1             data["_id"] = did
1988 1             self.assertEqual(msg_args[0], self.topic.topic_msg, "Wrong message topic")
1989 1             self.assertEqual(msg_args[1], "edited", "Wrong message action")
1990 1             self.assertEqual(msg_args[2], data, "Wrong message content")
1991 1             self.assertEqual(db_args[0], self.topic.topic, "Wrong DB topic")
1992 1             self.assertEqual(db_args[1], did, "Wrong DB ID")
1993 1             self.assertEqual(
1994                 db_args[2]["_admin"]["created"],
1995                 nsd_content["_admin"]["created"],
1996                 "Wrong creation time",
1997             )
1998 1             self.assertGreater(
1999                 db_args[2]["_admin"]["modified"], now, "Wrong modification time"
2000             )
2001 1             self.assertEqual(
2002                 db_args[2]["_admin"]["projects_read"],
2003                 nsd_content["_admin"]["projects_read"],
2004                 "Wrong read-only project list",
2005             )
2006 1             self.assertEqual(
2007                 db_args[2]["_admin"]["projects_write"],
2008                 nsd_content["_admin"]["projects_write"],
2009                 "Wrong read-write project list",
2010             )
2011 1             self.assertEqual(db_args[2]["id"], data["id"], "Wrong NSD ID")
2012 1             self.assertEqual(db_args[2]["name"], data["name"], "Wrong NSD Name")
2013 1         with self.subTest(i=2, t="Conflict on Edit"):
2014 1             data = {"id": "fake-nsd-id", "name": "new-nsd-name"}
2015 1             self.db.get_one.side_effect = [
2016                 nsd_content,
2017                 {"_id": str(uuid4()), "id": data["id"]},
2018             ]
2019 1             with self.assertRaises(
2020                 EngineException, msg="Accepted existing NSD ID"
2021             ) as e:
2022 1                 self.topic.edit(fake_session, did, data)
2023 1             self.assertEqual(
2024                 e.exception.http_code, HTTPStatus.CONFLICT, "Wrong HTTP status code"
2025             )
2026 1             self.assertIn(
2027                 norm(
2028                     "{} with id '{}' already exists for this project".format(
2029                         "nsd", data["id"]
2030                     )
2031                 ),
2032                 norm(str(e.exception)),
2033                 "Wrong exception text",
2034             )
2035 1         with self.subTest(i=3, t="Check Envelope"):
2036 1             data = {"nsd": {"nsd": {"id": "new-nsd-id", "name": "new-nsd-name"}}}
2037 1             self.db.get_one.side_effect = [nsd_content, None]
2038 1             with self.assertRaises(
2039                 EngineException, msg="Accepted NSD with wrong envelope"
2040             ) as e:
2041 1                 self.topic.edit(fake_session, did, data, content=nsd_content)
2042 1             self.assertEqual(
2043                 e.exception.http_code, HTTPStatus.BAD_REQUEST, "Wrong HTTP status code"
2044             )
2045 1             self.assertIn(
2046                 "'nsd' must be a list of only one element",
2047                 norm(str(e.exception)),
2048                 "Wrong exception text",
2049             )
2050 1         self.db.reset_mock()
2051 1         return
2052
2053 1     def test_delete_nsd(self):
2054 1         did = db_nsd_content["_id"]
2055 1         self.db.get_one.return_value = db_nsd_content
2056 1         p_id = db_nsd_content["_admin"]["projects_read"][0]
2057 1         with self.subTest(i=1, t="Normal Deletion"):
2058 1             self.db.get_list.return_value = []
2059 1             self.db.del_one.return_value = {"deleted": 1}
2060 1             self.topic.delete(fake_session, did)
2061 1             db_args = self.db.del_one.call_args[0]
2062 1             msg_args = self.msg.write.call_args[0]
2063 1             self.assertEqual(msg_args[0], self.topic.topic_msg, "Wrong message topic")
2064 1             self.assertEqual(msg_args[1], "deleted", "Wrong message action")
2065 1             self.assertEqual(msg_args[2], {"_id": did}, "Wrong message content")
2066 1             self.assertEqual(db_args[0], self.topic.topic, "Wrong DB topic")
2067 1             self.assertEqual(db_args[1]["_id"], did, "Wrong DB ID")
2068 1             self.assertEqual(
2069                 db_args[1]["_admin.projects_write.cont"],
2070                 [p_id, "ANY"],
2071                 "Wrong DB filter",
2072             )
2073 1             db_g1_args = self.db.get_one.call_args[0]
2074 1             self.assertEqual(db_g1_args[0], self.topic.topic, "Wrong DB topic")
2075 1             self.assertEqual(db_g1_args[1]["_id"], did, "Wrong DB NSD ID")
2076 1             db_gl_calls = self.db.get_list.call_args_list
2077 1             self.assertEqual(db_gl_calls[0][0][0], "nsrs", "Wrong DB topic")
2078             # self.assertEqual(db_gl_calls[0][0][1]["nsd-id"], did, "Wrong DB NSD ID")   # Filter changed after call
2079 1             self.assertEqual(db_gl_calls[1][0][0], "nsts", "Wrong DB topic")
2080 1             self.assertEqual(
2081                 db_gl_calls[1][0][1]["netslice-subnet.ANYINDEX.nsd-ref"],
2082                 db_nsd_content["id"],
2083                 "Wrong DB NSD netslice-subnet nsd-ref",
2084             )
2085 1             self.db.set_one.assert_not_called()
2086 1             fs_del_calls = self.fs.file_delete.call_args_list
2087 1             self.assertEqual(fs_del_calls[0][0][0], did, "Wrong FS file id")
2088 1             self.assertEqual(fs_del_calls[1][0][0], did + "_", "Wrong FS folder id")
2089 1         with self.subTest(i=2, t="Conflict on Delete - NSD in use by nsr"):
2090 1             self.db.get_list.return_value = [{"_id": str(uuid4()), "name": "fake-nsr"}]
2091 1             with self.assertRaises(
2092                 EngineException, msg="Accepted NSD in use by NSR"
2093             ) as e:
2094 1                 self.topic.delete(fake_session, did)
2095 1             self.assertEqual(
2096                 e.exception.http_code, HTTPStatus.CONFLICT, "Wrong HTTP status code"
2097             )
2098 1             self.assertIn(
2099                 "there is at least one ns instance using this descriptor",
2100                 norm(str(e.exception)),
2101                 "Wrong exception text",
2102             )
2103 1         with self.subTest(i=3, t="Conflict on Delete - NSD in use by NST"):
2104 1             self.db.get_list.side_effect = [
2105                 [],
2106                 [{"_id": str(uuid4()), "name": "fake-nst"}],
2107             ]
2108 1             with self.assertRaises(
2109                 EngineException, msg="Accepted NSD in use by NST"
2110             ) as e:
2111 1                 self.topic.delete(fake_session, did)
2112 1             self.assertEqual(
2113                 e.exception.http_code, HTTPStatus.CONFLICT, "Wrong HTTP status code"
2114             )
2115 1             self.assertIn(
2116                 "there is at least one netslice template referencing this descriptor",
2117                 norm(str(e.exception)),
2118                 "Wrong exception text",
2119             )
2120 1         with self.subTest(i=4, t="Non-existent NSD"):
2121 1             excp_msg = "Not found any {} with filter='{}'".format("NSD", {"_id": did})
2122 1             self.db.get_one.side_effect = DbException(excp_msg, HTTPStatus.NOT_FOUND)
2123 1             with self.assertRaises(
2124                 DbException, msg="Accepted non-existent NSD ID"
2125             ) as e:
2126 1                 self.topic.delete(fake_session, did)
2127 1             self.assertEqual(
2128                 e.exception.http_code, HTTPStatus.NOT_FOUND, "Wrong HTTP status code"
2129             )
2130 1             self.assertIn(
2131                 norm(excp_msg), norm(str(e.exception)), "Wrong exception text"
2132             )
2133 1         with self.subTest(i=5, t="No delete because referenced by other project"):
2134 1             db_nsd_content["_admin"]["projects_read"].append("other_project")
2135 1             self.db.get_one = Mock(return_value=db_nsd_content)
2136 1             self.db.get_list = Mock(return_value=[])
2137 1             self.msg.write.reset_mock()
2138 1             self.db.del_one.reset_mock()
2139 1             self.fs.file_delete.reset_mock()
2140
2141 1             self.topic.delete(fake_session, did)
2142 1             self.db.del_one.assert_not_called()
2143 1             self.msg.write.assert_not_called()
2144 1             db_g1_args = self.db.get_one.call_args[0]
2145 1             self.assertEqual(db_g1_args[0], self.topic.topic, "Wrong DB topic")
2146 1             self.assertEqual(db_g1_args[1]["_id"], did, "Wrong DB VNFD ID")
2147 1             db_s1_args = self.db.set_one.call_args
2148 1             self.assertEqual(db_s1_args[0][0], self.topic.topic, "Wrong DB topic")
2149 1             self.assertEqual(db_s1_args[0][1]["_id"], did, "Wrong DB ID")
2150 1             self.assertIn(
2151                 p_id, db_s1_args[0][1]["_admin.projects_write.cont"], "Wrong DB filter"
2152             )
2153 1             self.assertIsNone(
2154                 db_s1_args[1]["update_dict"], "Wrong DB update dictionary"
2155             )
2156 1             self.assertEqual(
2157                 db_s1_args[1]["pull_list"],
2158                 {"_admin.projects_read": (p_id,), "_admin.projects_write": (p_id,)},
2159                 "Wrong DB pull_list dictionary",
2160             )
2161 1             self.fs.file_delete.assert_not_called()
2162 1         self.db.reset_mock()
2163 1         return
2164
2165 1     def prepare_nsd_validation(self):
2166 1         descriptor_name = "test_ns_descriptor"
2167 1         self.fs.file_open.side_effect = lambda path, mode: open(
2168             "/tmp/" + str(uuid4()), "a+b"
2169         )
2170 1         old_nsd, new_nsd = self.create_desc_temp(db_nsd_content)
2171 1         return descriptor_name, old_nsd, new_nsd
2172
2173 1     @patch("osm_nbi.descriptor_topics.detect_descriptor_usage")
2174 1     @patch("osm_nbi.descriptor_topics.yaml.safe_load")
2175 1     def test_validate_descriptor_ns_configuration_changed(
2176         self, mock_safe_load, mock_detect_usage
2177     ):
2178         """Validating NSD and NSD has changes in ns-configuration:config-primitive"""
2179 1         descriptor_name, old_nsd, new_nsd = self.prepare_nsd_validation()
2180 1         mock_safe_load.side_effect = [old_nsd, new_nsd]
2181 1         mock_detect_usage.return_value = True
2182 1         self.db.get_one.return_value = old_nsd
2183 1         old_nsd.update(
2184             {"ns-configuration": {"config-primitive": [{"name": "add-user"}]}}
2185         )
2186 1         new_nsd.update(
2187             {"ns-configuration": {"config-primitive": [{"name": "del-user"}]}}
2188         )
2189
2190 1         with self.assertNotRaises(EngineException):
2191 1             self.topic._validate_descriptor_changes(
2192                 old_nsd["_id"], descriptor_name, "/tmp", "/tmp:1"
2193             )
2194 1         self.db.get_one.assert_called_once()
2195 1         mock_detect_usage.assert_called_once()
2196 1         self.assertEqual(mock_safe_load.call_count, 2)
2197
2198 1     @patch("osm_nbi.descriptor_topics.detect_descriptor_usage")
2199 1     @patch("osm_nbi.descriptor_topics.yaml.safe_load")
2200 1     def test_validate_descriptor_nsd_name_changed(
2201         self, mock_safe_load, mock_detect_usage
2202     ):
2203         """Validating NSD, NSD name has changed."""
2204 1         descriptor_name, old_nsd, new_nsd = self.prepare_nsd_validation()
2205 1         did = old_nsd["_id"]
2206 1         new_nsd["name"] = "nscharm-ns2"
2207 1         mock_safe_load.side_effect = [old_nsd, new_nsd]
2208 1         mock_detect_usage.return_value = True
2209 1         self.db.get_one.return_value = old_nsd
2210
2211 1         with self.assertRaises(
2212             EngineException, msg="there are disallowed changes in the ns descriptor"
2213         ) as e:
2214 1             self.topic._validate_descriptor_changes(
2215                 did, descriptor_name, "/tmp", "/tmp:1"
2216             )
2217 1         self.assertEqual(
2218             e.exception.http_code,
2219             HTTPStatus.UNPROCESSABLE_ENTITY,
2220             "Wrong HTTP status code",
2221         )
2222 1         self.assertIn(
2223             norm("there are disallowed changes in the ns descriptor"),
2224             norm(str(e.exception)),
2225             "Wrong exception text",
2226         )
2227
2228 1         self.db.get_one.assert_called_once()
2229 1         mock_detect_usage.assert_called_once()
2230 1         self.assertEqual(mock_safe_load.call_count, 2)
2231
2232 1     @patch("osm_nbi.descriptor_topics.detect_descriptor_usage")
2233 1     @patch("osm_nbi.descriptor_topics.yaml.safe_load")
2234 1     def test_validate_descriptor_nsd_name_changed_nsd_not_in_use(
2235         self, mock_safe_load, mock_detect_usage
2236     ):
2237         """Validating NSD, NSD name has changed, NSD is not in use."""
2238 1         descriptor_name, old_nsd, new_nsd = self.prepare_nsd_validation()
2239 1         did = old_nsd["_id"]
2240 1         new_nsd["name"] = "nscharm-ns2"
2241 1         mock_safe_load.side_effect = [old_nsd, new_nsd]
2242 1         mock_detect_usage.return_value = None
2243 1         self.db.get_one.return_value = old_nsd
2244
2245 1         with self.assertNotRaises(Exception):
2246 1             self.topic._validate_descriptor_changes(
2247                 did, descriptor_name, "/tmp", "/tmp:1"
2248             )
2249
2250 1         self.db.get_one.assert_called_once()
2251 1         mock_detect_usage.assert_called_once()
2252 1         mock_safe_load.assert_not_called()
2253
2254 1     def test_validate_vld_mgmt_network_with_virtual_link_protocol_data_on_valid_descriptor(
2255         self,
2256     ):
2257 1         indata = deepcopy(db_nsd_content)
2258 1         vld = indata["virtual-link-desc"][0]
2259 1         self.topic.validate_vld_mgmt_network_with_virtual_link_protocol_data(
2260             vld, indata
2261         )
2262
2263 1     def test_validate_vld_mgmt_network_with_virtual_link_protocol_data_when_both_defined(
2264         self,
2265     ):
2266 1         indata = deepcopy(db_nsd_content)
2267 1         vld = indata["virtual-link-desc"][0]
2268 1         df = indata["df"][0]
2269 1         affected_vlp = {
2270             "id": "id",
2271             "virtual-link-desc-id": "mgmt",
2272             "virtual-link-protocol-data": {"associated-layer-protocol": "ipv4"},
2273         }
2274 1         df["virtual-link-profile"] = [affected_vlp]
2275 1         with self.assertRaises(EngineException) as e:
2276 1             self.topic.validate_vld_mgmt_network_with_virtual_link_protocol_data(
2277                 vld, indata
2278             )
2279 1         self.assertEqual(
2280             e.exception.http_code,
2281             HTTPStatus.UNPROCESSABLE_ENTITY,
2282             "Wrong HTTP status code",
2283         )
2284 1         self.assertIn(
2285             norm(
2286                 "Error at df[id='{}']:virtual-link-profile[id='{}']:virtual-link-protocol-data"
2287                 " You cannot set a virtual-link-protocol-data when mgmt-network is True".format(
2288                     df["id"], affected_vlp["id"]
2289                 )
2290             ),
2291             norm(str(e.exception)),
2292             "Wrong exception text",
2293         )
2294
2295 1     def test_validate_vnf_profiles_vnfd_id_on_valid_descriptor(self):
2296 1         indata = deepcopy(db_nsd_content)
2297 1         self.topic.validate_vnf_profiles_vnfd_id(indata)
2298
2299 1     def test_validate_vnf_profiles_vnfd_id_when_missing_vnfd(self):
2300 1         indata = deepcopy(db_nsd_content)
2301 1         df = indata["df"][0]
2302 1         affected_vnf_profile = df["vnf-profile"][0]
2303 1         indata["vnfd-id"] = ["non-existing-vnfd"]
2304 1         with self.assertRaises(EngineException) as e:
2305 1             self.topic.validate_vnf_profiles_vnfd_id(indata)
2306 1         self.assertEqual(
2307             e.exception.http_code,
2308             HTTPStatus.UNPROCESSABLE_ENTITY,
2309             "Wrong HTTP status code",
2310         )
2311 1         self.assertIn(
2312             norm(
2313                 "Error at df[id='{}']:vnf_profile[id='{}']:vnfd-id='{}' "
2314                 "does not match any vnfd-id".format(
2315                     df["id"],
2316                     affected_vnf_profile["id"],
2317                     affected_vnf_profile["vnfd-id"],
2318                 )
2319             ),
2320             norm(str(e.exception)),
2321             "Wrong exception text",
2322         )
2323
2324 1     def test_validate_df_vnf_profiles_constituent_connection_points_on_valid_descriptor(
2325         self,
2326     ):
2327 1         nsd_descriptor = deepcopy(db_nsd_content)
2328 1         vnfd_descriptor = deepcopy(db_vnfd_content)
2329 1         df = nsd_descriptor["df"][0]
2330 1         vnfds_index = {vnfd_descriptor["id"]: vnfd_descriptor}
2331 1         self.topic.validate_df_vnf_profiles_constituent_connection_points(
2332             df, vnfds_index
2333         )
2334
2335 1     def test_validate_df_vnf_profiles_constituent_connection_points_when_missing_connection_point(
2336         self,
2337     ):
2338 1         nsd_descriptor = deepcopy(db_nsd_content)
2339 1         vnfd_descriptor = deepcopy(db_vnfd_content)
2340 1         df = nsd_descriptor["df"][0]
2341 1         affected_vnf_profile = df["vnf-profile"][0]
2342 1         affected_virtual_link = affected_vnf_profile["virtual-link-connectivity"][1]
2343 1         vnfds_index = {vnfd_descriptor["id"]: vnfd_descriptor}
2344 1         affected_cpd = vnfd_descriptor["ext-cpd"].pop()
2345 1         with self.assertRaises(EngineException) as e:
2346 1             self.topic.validate_df_vnf_profiles_constituent_connection_points(
2347                 df, vnfds_index
2348             )
2349 1         self.assertEqual(
2350             e.exception.http_code,
2351             HTTPStatus.UNPROCESSABLE_ENTITY,
2352             "Wrong HTTP status code",
2353         )
2354 1         self.assertIn(
2355             norm(
2356                 "Error at df[id='{}']:vnf-profile[id='{}']:virtual-link-connectivity"
2357                 "[virtual-link-profile-id='{}']:constituent-cpd-id='{}' references a "
2358                 "non existing ext-cpd:id inside vnfd '{}'".format(
2359                     df["id"],
2360                     affected_vnf_profile["id"],
2361                     affected_virtual_link["virtual-link-profile-id"],
2362                     affected_cpd["id"],
2363                     vnfd_descriptor["id"],
2364                 )
2365             ),
2366             norm(str(e.exception)),
2367             "Wrong exception text",
2368         )
2369
2370 1     def test_check_conflict_on_edit_when_missing_constituent_vnfd_id(self):
2371 1         nsd_descriptor = deepcopy(db_nsd_content)
2372 1         invalid_vnfd_id = "invalid-vnfd-id"
2373 1         nsd_descriptor["id"] = "invalid-vnfd-id-ns"
2374 1         nsd_descriptor["vnfd-id"][0] = invalid_vnfd_id
2375 1         nsd_descriptor["df"][0]["vnf-profile"][0]["vnfd-id"] = invalid_vnfd_id
2376 1         nsd_descriptor["df"][0]["vnf-profile"][1]["vnfd-id"] = invalid_vnfd_id
2377 1         with self.assertRaises(EngineException) as e:
2378 1             self.db.get_list.return_value = []
2379 1             nsd_descriptor = self.topic.check_conflict_on_edit(
2380                 fake_session, nsd_descriptor, [], "id"
2381             )
2382 1         self.assertEqual(
2383             e.exception.http_code, HTTPStatus.CONFLICT, "Wrong HTTP status code"
2384         )
2385 1         self.assertIn(
2386             norm(
2387                 "Descriptor error at 'vnfd-id'='{}' references a non "
2388                 "existing vnfd".format(invalid_vnfd_id)
2389             ),
2390             norm(str(e.exception)),
2391             "Wrong exception text",
2392         )
2393
2394
2395 1 if __name__ == "__main__":
2396 0     unittest.main()