Code Coverage

Cobertura Coverage Report > osm_nbi.tests >

test_descriptor_topics.py

Trend

Classes100%
 
Lines99%
   
Conditionals100%
 

File Coverage summary

NameClassesLinesConditionals
test_descriptor_topics.py
100%
1/1
99%
983/988
100%
0/0

Coverage Breakdown by Class

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