280c93fec04a825d6267d86d20f87fcd52248a7c
2 # -*- coding: utf-8 -*-
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
8 # http://www.apache.org/licenses/LICENSE-2.0
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
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 __author__
= "Pedro de la Cruz Ramos, pedro.delacruzramos@altran.com"
18 __date__
= "2019-11-20"
21 from unittest
import TestCase
22 from unittest
.mock
import Mock
, patch
23 from uuid
import uuid4
24 from http
import HTTPStatus
25 from copy
import deepcopy
27 from osm_common
import dbbase
, fsbase
, msgbase
28 from osm_nbi
import authconn
29 from osm_nbi
.tests
.test_pkg_descriptors
import db_vnfds_text
, db_nsds_text
30 from osm_nbi
.descriptor_topics
import VnfdTopic
, NsdTopic
31 from osm_nbi
.engine
import EngineException
32 from osm_common
.dbbase
import DbException
35 test_name
= "test-user"
36 db_vnfd_content
= yaml
.load(db_vnfds_text
, Loader
=yaml
.Loader
)[0]
37 db_nsd_content
= yaml
.load(db_nsds_text
, Loader
=yaml
.Loader
)[0]
38 test_pid
= db_vnfd_content
["_admin"]["projects_read"][0]
40 "username": test_name
,
41 "project_id": (test_pid
,),
46 "allow_show_user_project_role": True,
51 """Normalize string for checking"""
52 return " ".join(str.strip().split()).lower()
55 def compare_desc(tc
, d1
, d2
, k
):
57 Compare two descriptors
58 We need this function because some methods are adding/removing items to/from the descriptors
59 before they are stored in the database, so the original and stored versions will differ
60 What we check is that COMMON LEAF ITEMS are equal
61 Lists of different length are not compared
62 :param tc: Test Case wich provides context (in particular the assert* methods)
63 :param d1,d2: Descriptors to be compared
64 :param key/item being compared
67 if isinstance(d1
, dict) and isinstance(d2
, dict):
70 compare_desc(tc
, d1
[key
], d2
[key
], k
+ "[{}]".format(key
))
71 elif isinstance(d1
, list) and isinstance(d2
, list) and len(d1
) == len(d2
):
72 for i
in range(len(d1
)):
73 compare_desc(tc
, d1
[i
], d2
[i
], k
+ "[{}]".format(i
))
75 tc
.assertEqual(d1
, d2
, "Wrong descriptor content: {}".format(k
))
78 class Test_VnfdTopic(TestCase
):
81 cls
.test_name
= "test-vnfd-topic"
84 def tearDownClass(cls
):
88 self
.db
= Mock(dbbase
.DbBase())
89 self
.fs
= Mock(fsbase
.FsBase())
90 self
.msg
= Mock(msgbase
.MsgBase())
91 self
.auth
= Mock(authconn
.Authconn(None, None, None))
92 self
.topic
= VnfdTopic(self
.db
, self
.fs
, self
.msg
, self
.auth
)
93 self
.topic
.check_quota
= Mock(return_value
=None) # skip quota
95 @patch("osm_nbi.descriptor_topics.shutil")
96 @patch("osm_nbi.descriptor_topics.os.rename")
97 def test_new_vnfd(self
, mock_rename
, mock_shutil
):
98 did
= db_vnfd_content
["_id"]
100 self
.fs
.get_params
.return_value
= {}
101 self
.fs
.file_exists
.return_value
= False
102 self
.fs
.file_open
.side_effect
= lambda path
, mode
: open(
103 "/tmp/" + str(uuid4()), "a+b"
105 test_vnfd
= deepcopy(db_vnfd_content
)
107 del test_vnfd
["_admin"]
108 with self
.subTest(i
=1, t
="Normal Creation"):
109 self
.db
.create
.return_value
= did
111 did2
, oid
= self
.topic
.new(rollback
, fake_session
, {})
112 db_args
= self
.db
.create
.call_args
[0]
113 msg_args
= self
.msg
.write
.call_args
[0]
114 self
.assertEqual(len(rollback
), 1, "Wrong rollback length")
115 self
.assertEqual(msg_args
[0], self
.topic
.topic_msg
, "Wrong message topic")
116 self
.assertEqual(msg_args
[1], "created", "Wrong message action")
117 self
.assertEqual(msg_args
[2], {"_id": did
}, "Wrong message content")
118 self
.assertEqual(db_args
[0], self
.topic
.topic
, "Wrong DB topic")
119 self
.assertEqual(did2
, did
, "Wrong DB VNFD id")
120 self
.assertIsNotNone(db_args
[1]["_admin"]["created"], "Wrong creation time")
122 db_args
[1]["_admin"]["modified"],
123 db_args
[1]["_admin"]["created"],
124 "Wrong modification time",
127 db_args
[1]["_admin"]["projects_read"],
129 "Wrong read-only project list",
132 db_args
[1]["_admin"]["projects_write"],
134 "Wrong read-write project list",
136 tmp1
= test_vnfd
["vdu"][0]["cloud-init-file"]
137 tmp2
= test_vnfd
["df"][0]["lcm-operations-configuration"][
138 "operate-vnf-op-config"
139 ]["day1-2"][0]["execution-environment-list"][0]["juju"]
140 del test_vnfd
["vdu"][0]["cloud-init-file"]
141 del test_vnfd
["df"][0]["lcm-operations-configuration"][
142 "operate-vnf-op-config"
143 ]["day1-2"][0]["execution-environment-list"][0]["juju"]
145 self
.db
.get_one
.side_effect
= [
146 {"_id": did
, "_admin": deepcopy(db_vnfd_content
["_admin"])},
149 self
.topic
.upload_content(
150 fake_session
, did
, test_vnfd
, {}, {"Content-Type": []}
152 msg_args
= self
.msg
.write
.call_args
[0]
153 test_vnfd
["_id"] = did
155 msg_args
[0], self
.topic
.topic_msg
, "Wrong message topic"
157 self
.assertEqual(msg_args
[1], "edited", "Wrong message action")
158 self
.assertEqual(msg_args
[2], test_vnfd
, "Wrong message content")
159 db_args
= self
.db
.get_one
.mock_calls
[0][1]
160 self
.assertEqual(db_args
[0], self
.topic
.topic
, "Wrong DB topic")
161 self
.assertEqual(db_args
[1]["_id"], did
, "Wrong DB VNFD id")
162 db_args
= self
.db
.replace
.call_args
[0]
163 self
.assertEqual(db_args
[0], self
.topic
.topic
, "Wrong DB topic")
164 self
.assertEqual(db_args
[1], did
, "Wrong DB VNFD id")
165 admin
= db_args
[2]["_admin"]
166 db_admin
= deepcopy(db_vnfd_content
["_admin"])
167 self
.assertEqual(admin
["type"], "vnfd", "Wrong descriptor type")
169 admin
["created"], db_admin
["created"], "Wrong creation time"
172 admin
["modified"], db_admin
["created"], "Wrong modification time"
175 admin
["projects_read"],
176 db_admin
["projects_read"],
177 "Wrong read-only project list",
180 admin
["projects_write"],
181 db_admin
["projects_write"],
182 "Wrong read-write project list",
185 admin
["onboardingState"], "ONBOARDED", "Wrong onboarding state"
188 admin
["operationalState"], "ENABLED", "Wrong operational state"
190 self
.assertEqual(admin
["usageState"], "NOT_IN_USE", "Wrong usage state")
191 storage
= admin
["storage"]
192 self
.assertEqual(storage
["folder"], did
, "Wrong storage folder")
194 storage
["descriptor"], "package", "Wrong storage descriptor"
197 admin
["revision"], 1, "Wrong revision number"
200 compare_desc(self
, test_vnfd
, db_args
[2], "VNFD")
202 test_vnfd
["vdu"][0]["cloud-init-file"] = tmp1
203 test_vnfd
["df"][0]["lcm-operations-configuration"][
204 "operate-vnf-op-config"
205 ]["day1-2"][0]["execution-environment-list"][0]["juju"] = tmp2
206 self
.db
.get_one
.side_effect
= (
207 lambda table
, filter, fail_on_empty
=None, fail_on_more
=None: {
209 "_admin": deepcopy(db_vnfd_content
["_admin"]),
212 with self
.subTest(i
=2, t
="Check Pyangbind Validation: additional properties"):
213 test_vnfd
["extra-property"] = 0
215 with self
.assertRaises(
216 EngineException
, msg
="Accepted VNFD with an additional property"
218 self
.topic
.upload_content(
219 fake_session
, did
, test_vnfd
, {}, {"Content-Type": []}
222 e
.exception
.http_code
,
223 HTTPStatus
.UNPROCESSABLE_ENTITY
,
224 "Wrong HTTP status code",
228 "Error in pyangbind validation: {} ({})".format(
229 "json object contained a key that did not exist",
233 norm(str(e
.exception
)),
234 "Wrong exception text",
236 db_args
= self
.db
.replace
.call_args
[0]
237 admin
= db_args
[2]["_admin"]
239 admin
["revision"], 1, "Wrong revision number"
243 del test_vnfd
["extra-property"]
244 with self
.subTest(i
=3, t
="Check Pyangbind Validation: property types"):
245 tmp
= test_vnfd
["product-name"]
246 test_vnfd
["product-name"] = {"key": 0}
248 with self
.assertRaises(
249 EngineException
, msg
="Accepted VNFD with a wrongly typed property"
251 self
.topic
.upload_content(
252 fake_session
, did
, test_vnfd
, {}, {"Content-Type": []}
255 e
.exception
.http_code
,
256 HTTPStatus
.UNPROCESSABLE_ENTITY
,
257 "Wrong HTTP status code",
261 "Error in pyangbind validation: {} ({})".format(
262 "json object contained a key that did not exist", "key"
265 norm(str(e
.exception
)),
266 "Wrong exception text",
269 test_vnfd
["product-name"] = tmp
270 with self
.subTest(i
=4, t
="Check Input Validation: cloud-init"):
271 with self
.assertRaises(
272 EngineException
, msg
="Accepted non-existent cloud_init file"
274 self
.topic
.upload_content(
275 fake_session
, did
, test_vnfd
, {}, {"Content-Type": []}
278 e
.exception
.http_code
, HTTPStatus
.BAD_REQUEST
, "Wrong HTTP status code"
282 "{} defined in vnf[id={}]:vdu[id={}] but not present in package".format(
283 "cloud-init", test_vnfd
["id"], test_vnfd
["vdu"][0]["id"]
286 norm(str(e
.exception
)),
287 "Wrong exception text",
289 with self
.subTest(i
=5, t
="Check Input Validation: day1-2 configuration[juju]"):
290 del test_vnfd
["vdu"][0]["cloud-init-file"]
291 with self
.assertRaises(
292 EngineException
, msg
="Accepted non-existent charm in VNF configuration"
294 self
.topic
.upload_content(
295 fake_session
, did
, test_vnfd
, {}, {"Content-Type": []}
297 print(str(e
.exception
))
299 e
.exception
.http_code
, HTTPStatus
.BAD_REQUEST
, "Wrong HTTP status code"
303 "{} defined in vnf[id={}] but not present in package".format(
304 "charm", test_vnfd
["id"]
307 norm(str(e
.exception
)),
308 "Wrong exception text",
310 del test_vnfd
["df"][0]["lcm-operations-configuration"][
311 "operate-vnf-op-config"
312 ]["day1-2"][0]["execution-environment-list"][0]["juju"]
313 with self
.subTest(i
=6, t
="Check Input Validation: mgmt-cp"):
314 tmp
= test_vnfd
["mgmt-cp"]
315 del test_vnfd
["mgmt-cp"]
317 with self
.assertRaises(
318 EngineException
, msg
="Accepted VNFD without management interface"
320 self
.topic
.upload_content(
321 fake_session
, did
, test_vnfd
, {}, {"Content-Type": []}
324 e
.exception
.http_code
,
325 HTTPStatus
.UNPROCESSABLE_ENTITY
,
326 "Wrong HTTP status code",
330 "'{}' is a mandatory field and it is not defined".format(
334 norm(str(e
.exception
)),
335 "Wrong exception text",
338 test_vnfd
["mgmt-cp"] = tmp
339 with self
.subTest(i
=7, t
="Check Input Validation: mgmt-cp connection point"):
340 tmp
= test_vnfd
["mgmt-cp"]
341 test_vnfd
["mgmt-cp"] = "wrong-cp"
343 with self
.assertRaises(
344 EngineException
, msg
="Accepted wrong mgmt-cp connection point"
346 self
.topic
.upload_content(
347 fake_session
, did
, test_vnfd
, {}, {"Content-Type": []}
350 e
.exception
.http_code
,
351 HTTPStatus
.UNPROCESSABLE_ENTITY
,
352 "Wrong HTTP status code",
356 "mgmt-cp='{}' must match an existing ext-cpd".format(
360 norm(str(e
.exception
)),
361 "Wrong exception text",
364 test_vnfd
["mgmt-cp"] = tmp
365 with self
.subTest(i
=8, t
="Check Input Validation: vdu int-cpd"):
366 ext_cpd
= test_vnfd
["ext-cpd"][1]
367 tmp
= ext_cpd
["int-cpd"]["cpd"]
368 ext_cpd
["int-cpd"]["cpd"] = "wrong-cpd"
370 with self
.assertRaises(
372 msg
="Accepted wrong ext-cpd internal connection point",
374 self
.topic
.upload_content(
375 fake_session
, did
, test_vnfd
, {}, {"Content-Type": []}
378 e
.exception
.http_code
,
379 HTTPStatus
.UNPROCESSABLE_ENTITY
,
380 "Wrong HTTP status code",
384 "ext-cpd[id='{}']:int-cpd must match an existing vdu int-cpd".format(
388 norm(str(e
.exception
)),
389 "Wrong exception text",
392 ext_cpd
["int-cpd"]["cpd"] = tmp
393 with self
.subTest(i
=9, t
="Check Input Validation: Duplicated VLD"):
394 test_vnfd
["int-virtual-link-desc"].insert(0, {"id": "internal"})
396 with self
.assertRaises(
397 EngineException
, msg
="Accepted duplicated VLD name"
399 self
.topic
.upload_content(
400 fake_session
, did
, test_vnfd
, {}, {"Content-Type": []}
403 e
.exception
.http_code
,
404 HTTPStatus
.UNPROCESSABLE_ENTITY
,
405 "Wrong HTTP status code",
409 "identifier id '{}' is not unique".format(
410 test_vnfd
["int-virtual-link-desc"][0]["id"]
413 norm(str(e
.exception
)),
414 "Wrong exception text",
417 del test_vnfd
["int-virtual-link-desc"][0]
418 with self
.subTest(i
=10, t
="Check Input Validation: vdu int-virtual-link-desc"):
419 vdu
= test_vnfd
["vdu"][0]
420 int_cpd
= vdu
["int-cpd"][1]
421 tmp
= int_cpd
["int-virtual-link-desc"]
422 int_cpd
["int-virtual-link-desc"] = "non-existing-int-virtual-link-desc"
424 with self
.assertRaises(
425 EngineException
, msg
="Accepted int-virtual-link-desc"
427 self
.topic
.upload_content(
428 fake_session
, did
, test_vnfd
, {}, {"Content-Type": []}
431 e
.exception
.http_code
,
432 HTTPStatus
.UNPROCESSABLE_ENTITY
,
433 "Wrong HTTP status code",
437 "vdu[id='{}']:int-cpd[id='{}']:int-virtual-link-desc='{}' must match an existing "
438 "int-virtual-link-desc".format(
439 vdu
["id"], int_cpd
["id"], int_cpd
["int-virtual-link-desc"]
442 norm(str(e
.exception
)),
443 "Wrong exception text",
446 int_cpd
["int-virtual-link-desc"] = tmp
447 with self
.subTest(i
=11, t
="Check Input Validation: virtual-link-profile)"):
448 fake_ivld_profile
= {"id": "fake-profile-ref", "flavour": "fake-flavour"}
449 df
= test_vnfd
["df"][0]
450 df
["virtual-link-profile"] = [fake_ivld_profile
]
452 with self
.assertRaises(
453 EngineException
, msg
="Accepted non-existent Profile Ref"
455 self
.topic
.upload_content(
456 fake_session
, did
, test_vnfd
, {}, {"Content-Type": []}
459 e
.exception
.http_code
,
460 HTTPStatus
.UNPROCESSABLE_ENTITY
,
461 "Wrong HTTP status code",
465 "df[id='{}']:virtual-link-profile='{}' must match an existing "
466 "int-virtual-link-desc".format(
467 df
["id"], fake_ivld_profile
["id"]
470 norm(str(e
.exception
)),
471 "Wrong exception text",
474 del df
["virtual-link-profile"]
476 i
=12, t
="Check Input Validation: scaling-criteria monitoring-param-ref"
478 vdu
= test_vnfd
["vdu"][1]
479 affected_df
= test_vnfd
["df"][0]
480 sa
= affected_df
["scaling-aspect"][0]
481 sp
= sa
["scaling-policy"][0]
482 sc
= sp
["scaling-criteria"][0]
483 tmp
= vdu
.pop("monitoring-parameter")
485 with self
.assertRaises(
487 msg
="Accepted non-existent Scaling Group Policy Criteria",
489 self
.topic
.upload_content(
490 fake_session
, did
, test_vnfd
, {}, {"Content-Type": []}
493 e
.exception
.http_code
,
494 HTTPStatus
.UNPROCESSABLE_ENTITY
,
495 "Wrong HTTP status code",
499 "df[id='{}']:scaling-aspect[id='{}']:scaling-policy"
500 "[name='{}']:scaling-criteria[name='{}']: "
501 "vnf-monitoring-param-ref='{}' not defined in any monitoring-param".format(
506 sc
["vnf-monitoring-param-ref"],
509 norm(str(e
.exception
)),
510 "Wrong exception text",
513 vdu
["monitoring-parameter"] = tmp
515 i
=13, t
="Check Input Validation: scaling-aspect vnf-configuration"
517 df
= test_vnfd
["df"][0]
518 tmp
= test_vnfd
["df"][0]["lcm-operations-configuration"][
519 "operate-vnf-op-config"
522 with self
.assertRaises(
524 msg
="Accepted non-existent Scaling Group VDU ID Reference",
526 self
.topic
.upload_content(
527 fake_session
, did
, test_vnfd
, {}, {"Content-Type": []}
530 e
.exception
.http_code
,
531 HTTPStatus
.UNPROCESSABLE_ENTITY
,
532 "Wrong HTTP status code",
536 "'day1-2 configuration' not defined in the descriptor but it is referenced "
537 "by df[id='{}']:scaling-aspect[id='{}']:scaling-config-action".format(
538 df
["id"], df
["scaling-aspect"][0]["id"]
541 norm(str(e
.exception
)),
542 "Wrong exception text",
545 test_vnfd
["df"][0]["lcm-operations-configuration"][
546 "operate-vnf-op-config"
547 ]["day1-2"].append(tmp
)
548 with self
.subTest(i
=14, t
="Check Input Validation: scaling-config-action"):
549 df
= test_vnfd
["df"][0]
552 .get("lcm-operations-configuration")
553 .get("operate-vnf-op-config")["day1-2"][0]["config-primitive"]
555 test_vnfd
["df"][0].get("lcm-operations-configuration").get(
556 "operate-vnf-op-config"
557 )["day1-2"][0]["config-primitive"] = [{"name": "wrong-primitive"}]
559 with self
.assertRaises(
561 msg
="Accepted non-existent Scaling Group VDU ID Reference",
563 self
.topic
.upload_content(
564 fake_session
, did
, test_vnfd
, {}, {"Content-Type": []}
567 e
.exception
.http_code
,
568 HTTPStatus
.UNPROCESSABLE_ENTITY
,
569 "Wrong HTTP status code",
573 "df[id='{}']:scaling-aspect[id='{}']:scaling-config-action:vnf-"
574 "config-primitive-name-ref='{}' does not match any "
575 "day1-2 configuration:config-primitive:name".format(
577 df
["scaling-aspect"][0]["id"],
578 sa
["scaling-config-action"][0][
579 "vnf-config-primitive-name-ref"
583 norm(str(e
.exception
)),
584 "Wrong exception text",
587 test_vnfd
["df"][0].get("lcm-operations-configuration").get(
588 "operate-vnf-op-config"
589 )["day1-2"][0]["config-primitive"] = tmp
590 with self
.subTest(i
=15, t
="Check Input Validation: everything right"):
591 test_vnfd
["id"] = "fake-vnfd-id"
592 test_vnfd
["df"][0].get("lcm-operations-configuration").get(
593 "operate-vnf-op-config"
594 )["day1-2"][0]["id"] = "fake-vnfd-id"
595 self
.db
.get_one
.side_effect
= [
596 {"_id": did
, "_admin": deepcopy(db_vnfd_content
["_admin"])},
599 rc
= self
.topic
.upload_content(
600 fake_session
, did
, test_vnfd
, {}, {"Content-Type": []}
602 self
.assertTrue(rc
, "Input Validation: Unexpected failure")
605 def test_edit_vnfd(self
):
606 vnfd_content
= deepcopy(db_vnfd_content
)
607 did
= vnfd_content
["_id"]
608 self
.fs
.file_exists
.return_value
= True
609 self
.fs
.dir_ls
.return_value
= True
610 with self
.subTest(i
=1, t
="Normal Edition"):
612 self
.db
.get_one
.side_effect
= [deepcopy(vnfd_content
), None]
613 data
= {"product-name": "new-vnfd-name"}
614 self
.topic
.edit(fake_session
, did
, data
)
615 db_args
= self
.db
.replace
.call_args
[0]
616 msg_args
= self
.msg
.write
.call_args
[0]
618 self
.assertEqual(msg_args
[0], self
.topic
.topic_msg
, "Wrong message topic")
619 self
.assertEqual(msg_args
[1], "edited", "Wrong message action")
620 self
.assertEqual(msg_args
[2], data
, "Wrong message content")
621 self
.assertEqual(db_args
[0], self
.topic
.topic
, "Wrong DB topic")
622 self
.assertEqual(db_args
[1], did
, "Wrong DB ID")
624 db_args
[2]["_admin"]["created"],
625 vnfd_content
["_admin"]["created"],
626 "Wrong creation time",
629 db_args
[2]["_admin"]["modified"], now
, "Wrong modification time"
632 db_args
[2]["_admin"]["projects_read"],
633 vnfd_content
["_admin"]["projects_read"],
634 "Wrong read-only project list",
637 db_args
[2]["_admin"]["projects_write"],
638 vnfd_content
["_admin"]["projects_write"],
639 "Wrong read-write project list",
642 db_args
[2]["product-name"], data
["product-name"], "Wrong VNFD Name"
644 with self
.subTest(i
=2, t
="Conflict on Edit"):
645 data
= {"id": "hackfest3charmed-vnf", "product-name": "new-vnfd-name"}
646 self
.db
.get_one
.side_effect
= [
647 deepcopy(vnfd_content
),
648 {"_id": str(uuid4()), "id": data
["id"]},
650 with self
.assertRaises(
651 EngineException
, msg
="Accepted existing VNFD ID"
653 self
.topic
.edit(fake_session
, did
, data
)
655 e
.exception
.http_code
, HTTPStatus
.CONFLICT
, "Wrong HTTP status code"
659 "{} with id '{}' already exists for this project".format(
663 norm(str(e
.exception
)),
664 "Wrong exception text",
666 with self
.subTest(i
=3, t
="Check Envelope"):
667 data
= {"vnfd": [{"id": "new-vnfd-id-1", "product-name": "new-vnfd-name"}]}
668 with self
.assertRaises(
669 EngineException
, msg
="Accepted VNFD with wrong envelope"
671 self
.topic
.edit(fake_session
, did
, data
, content
=vnfd_content
)
673 e
.exception
.http_code
, HTTPStatus
.BAD_REQUEST
, "Wrong HTTP status code"
676 "'vnfd' must be dict", norm(str(e
.exception
)), "Wrong exception text"
680 def test_delete_vnfd(self
):
681 did
= db_vnfd_content
["_id"]
682 self
.db
.get_one
.return_value
= db_vnfd_content
683 p_id
= db_vnfd_content
["_admin"]["projects_read"][0]
684 with self
.subTest(i
=1, t
="Normal Deletion"):
685 self
.db
.get_list
.return_value
= []
686 self
.db
.del_one
.return_value
= {"deleted": 1}
687 self
.topic
.delete(fake_session
, did
)
688 db_args
= self
.db
.del_one
.call_args
[0]
689 msg_args
= self
.msg
.write
.call_args
[0]
690 self
.assertEqual(msg_args
[0], self
.topic
.topic_msg
, "Wrong message topic")
691 self
.assertEqual(msg_args
[1], "deleted", "Wrong message action")
692 self
.assertEqual(msg_args
[2], {"_id": did
}, "Wrong message content")
693 self
.assertEqual(db_args
[0], self
.topic
.topic
, "Wrong DB topic")
694 self
.assertEqual(db_args
[1]["_id"], did
, "Wrong DB ID")
696 db_args
[1]["_admin.projects_write.cont"],
700 db_g1_args
= self
.db
.get_one
.call_args
[0]
701 self
.assertEqual(db_g1_args
[0], self
.topic
.topic
, "Wrong DB topic")
702 self
.assertEqual(db_g1_args
[1]["_id"], did
, "Wrong DB VNFD ID")
703 db_gl_calls
= self
.db
.get_list
.call_args_list
704 self
.assertEqual(db_gl_calls
[0][0][0], "vnfrs", "Wrong DB topic")
705 # self.assertEqual(db_gl_calls[0][0][1]["vnfd-id"], did, "Wrong DB VNFD ID") # Filter changed after call
706 self
.assertEqual(db_gl_calls
[1][0][0], "nsds", "Wrong DB topic")
708 db_gl_calls
[1][0][1]["vnfd-id"],
709 db_vnfd_content
["id"],
710 "Wrong DB NSD vnfd-id",
713 db_del_args
= self
.db
.del_list
.call_args
[0]
715 self
.db
.del_list
.call_args
[0][0],
716 self
.topic
.topic
+"_revisions",
721 self
.db
.del_list
.call_args
[0][1]['_id']['$regex'],
723 "Wrong ID for rexep delete",
726 self
.db
.set_one
.assert_not_called()
727 fs_del_calls
= self
.fs
.file_delete
.call_args_list
728 self
.assertEqual(fs_del_calls
[0][0][0], did
, "Wrong FS file id")
729 self
.assertEqual(fs_del_calls
[1][0][0], did
+ "_", "Wrong FS folder id")
730 with self
.subTest(i
=2, t
="Conflict on Delete - VNFD in use by VNFR"):
731 self
.db
.get_list
.return_value
= [{"_id": str(uuid4()), "name": "fake-vnfr"}]
732 with self
.assertRaises(
733 EngineException
, msg
="Accepted VNFD in use by VNFR"
735 self
.topic
.delete(fake_session
, did
)
737 e
.exception
.http_code
, HTTPStatus
.CONFLICT
, "Wrong HTTP status code"
740 "there is at least one vnf instance using this descriptor",
741 norm(str(e
.exception
)),
742 "Wrong exception text",
744 with self
.subTest(i
=3, t
="Conflict on Delete - VNFD in use by NSD"):
745 self
.db
.get_list
.side_effect
= [
747 [{"_id": str(uuid4()), "name": "fake-nsd"}],
749 with self
.assertRaises(
750 EngineException
, msg
="Accepted VNFD in use by NSD"
752 self
.topic
.delete(fake_session
, did
)
754 e
.exception
.http_code
, HTTPStatus
.CONFLICT
, "Wrong HTTP status code"
757 "there is at least one ns package referencing this descriptor",
758 norm(str(e
.exception
)),
759 "Wrong exception text",
761 with self
.subTest(i
=4, t
="Non-existent VNFD"):
762 excp_msg
= "Not found any {} with filter='{}'".format("VNFD", {"_id": did
})
763 self
.db
.get_one
.side_effect
= DbException(excp_msg
, HTTPStatus
.NOT_FOUND
)
764 with self
.assertRaises(
765 DbException
, msg
="Accepted non-existent VNFD ID"
767 self
.topic
.delete(fake_session
, did
)
769 e
.exception
.http_code
, HTTPStatus
.NOT_FOUND
, "Wrong HTTP status code"
772 norm(excp_msg
), norm(str(e
.exception
)), "Wrong exception text"
774 with self
.subTest(i
=5, t
="No delete because referenced by other project"):
775 db_vnfd_content
["_admin"]["projects_read"].append("other_project")
776 self
.db
.get_one
= Mock(return_value
=db_vnfd_content
)
777 self
.db
.get_list
= Mock(return_value
=[])
778 self
.msg
.write
.reset_mock()
779 self
.db
.del_one
.reset_mock()
780 self
.fs
.file_delete
.reset_mock()
782 self
.topic
.delete(fake_session
, did
)
783 self
.db
.del_one
.assert_not_called()
784 self
.msg
.write
.assert_not_called()
785 db_g1_args
= self
.db
.get_one
.call_args
[0]
786 self
.assertEqual(db_g1_args
[0], self
.topic
.topic
, "Wrong DB topic")
787 self
.assertEqual(db_g1_args
[1]["_id"], did
, "Wrong DB VNFD ID")
788 db_s1_args
= self
.db
.set_one
.call_args
789 self
.assertEqual(db_s1_args
[0][0], self
.topic
.topic
, "Wrong DB topic")
790 self
.assertEqual(db_s1_args
[0][1]["_id"], did
, "Wrong DB ID")
792 p_id
, db_s1_args
[0][1]["_admin.projects_write.cont"], "Wrong DB filter"
795 db_s1_args
[1]["update_dict"], "Wrong DB update dictionary"
798 db_s1_args
[1]["pull_list"],
799 {"_admin.projects_read": (p_id
,), "_admin.projects_write": (p_id
,)},
800 "Wrong DB pull_list dictionary",
802 self
.fs
.file_delete
.assert_not_called()
805 def test_validate_mgmt_interface_connection_point_on_valid_descriptor(self
):
806 indata
= deepcopy(db_vnfd_content
)
807 self
.topic
.validate_mgmt_interface_connection_point(indata
)
809 def test_validate_mgmt_interface_connection_point_when_missing_connection_point(
812 indata
= deepcopy(db_vnfd_content
)
813 indata
["ext-cpd"] = []
814 with self
.assertRaises(EngineException
) as e
:
815 self
.topic
.validate_mgmt_interface_connection_point(indata
)
817 e
.exception
.http_code
,
818 HTTPStatus
.UNPROCESSABLE_ENTITY
,
819 "Wrong HTTP status code",
823 "mgmt-cp='{}' must match an existing ext-cpd".format(indata
["mgmt-cp"])
825 norm(str(e
.exception
)),
826 "Wrong exception text",
829 def test_validate_mgmt_interface_connection_point_when_missing_mgmt_cp(self
):
830 indata
= deepcopy(db_vnfd_content
)
831 indata
.pop("mgmt-cp")
832 with self
.assertRaises(EngineException
) as e
:
833 self
.topic
.validate_mgmt_interface_connection_point(indata
)
835 e
.exception
.http_code
,
836 HTTPStatus
.UNPROCESSABLE_ENTITY
,
837 "Wrong HTTP status code",
840 norm("'mgmt-cp' is a mandatory field and it is not defined"),
841 norm(str(e
.exception
)),
842 "Wrong exception text",
845 def test_validate_vdu_internal_connection_points_on_valid_descriptor(self
):
846 indata
= db_vnfd_content
847 vdu
= indata
["vdu"][0]
848 self
.topic
.validate_vdu_internal_connection_points(vdu
)
850 def test_validate_external_connection_points_on_valid_descriptor(self
):
851 indata
= db_vnfd_content
852 self
.topic
.validate_external_connection_points(indata
)
854 def test_validate_external_connection_points_when_missing_internal_connection_point(
857 indata
= deepcopy(db_vnfd_content
)
858 vdu
= indata
["vdu"][0]
860 affected_ext_cpd
= indata
["ext-cpd"][0]
861 with self
.assertRaises(EngineException
) as e
:
862 self
.topic
.validate_external_connection_points(indata
)
864 e
.exception
.http_code
,
865 HTTPStatus
.UNPROCESSABLE_ENTITY
,
866 "Wrong HTTP status code",
870 "ext-cpd[id='{}']:int-cpd must match an existing vdu int-cpd".format(
871 affected_ext_cpd
["id"]
874 norm(str(e
.exception
)),
875 "Wrong exception text",
878 def test_validate_vdu_internal_connection_points_on_duplicated_internal_connection_point(
881 indata
= deepcopy(db_vnfd_content
)
882 vdu
= indata
["vdu"][0]
886 "virtual-network-interface-requirement": [{"name": "duplicated"}],
888 vdu
["int-cpd"].insert(0, duplicated_cpd
)
889 with self
.assertRaises(EngineException
) as e
:
890 self
.topic
.validate_vdu_internal_connection_points(vdu
)
892 e
.exception
.http_code
,
893 HTTPStatus
.UNPROCESSABLE_ENTITY
,
894 "Wrong HTTP status code",
898 "vdu[id='{}']:int-cpd[id='{}'] is already used by other int-cpd".format(
899 vdu
["id"], duplicated_cpd
["id"]
902 norm(str(e
.exception
)),
903 "Wrong exception text",
906 def test_validate_external_connection_points_on_duplicated_external_connection_point(
909 indata
= deepcopy(db_vnfd_content
)
911 "id": "vnf-mgmt-ext",
912 "int-cpd": {"vdu-id": "dataVM", "cpd": "vnf-data"},
914 indata
["ext-cpd"].insert(0, duplicated_cpd
)
915 with self
.assertRaises(EngineException
) as e
:
916 self
.topic
.validate_external_connection_points(indata
)
918 e
.exception
.http_code
,
919 HTTPStatus
.UNPROCESSABLE_ENTITY
,
920 "Wrong HTTP status code",
924 "ext-cpd[id='{}'] is already used by other ext-cpd".format(
928 norm(str(e
.exception
)),
929 "Wrong exception text",
932 def test_validate_internal_virtual_links_on_valid_descriptor(self
):
933 indata
= db_vnfd_content
934 self
.topic
.validate_internal_virtual_links(indata
)
936 def test_validate_internal_virtual_links_on_duplicated_ivld(self
):
937 indata
= deepcopy(db_vnfd_content
)
938 duplicated_vld
= {"id": "internal"}
939 indata
["int-virtual-link-desc"].insert(0, duplicated_vld
)
940 with self
.assertRaises(EngineException
) as e
:
941 self
.topic
.validate_internal_virtual_links(indata
)
943 e
.exception
.http_code
,
944 HTTPStatus
.UNPROCESSABLE_ENTITY
,
945 "Wrong HTTP status code",
949 "Duplicated VLD id in int-virtual-link-desc[id={}]".format(
953 norm(str(e
.exception
)),
954 "Wrong exception text",
957 def test_validate_internal_virtual_links_when_missing_ivld_on_connection_point(
960 indata
= deepcopy(db_vnfd_content
)
961 vdu
= indata
["vdu"][0]
962 affected_int_cpd
= vdu
["int-cpd"][0]
963 affected_int_cpd
["int-virtual-link-desc"] = "non-existing-int-virtual-link-desc"
964 with self
.assertRaises(EngineException
) as e
:
965 self
.topic
.validate_internal_virtual_links(indata
)
967 e
.exception
.http_code
,
968 HTTPStatus
.UNPROCESSABLE_ENTITY
,
969 "Wrong HTTP status code",
973 "vdu[id='{}']:int-cpd[id='{}']:int-virtual-link-desc='{}' must match an existing "
974 "int-virtual-link-desc".format(
976 affected_int_cpd
["id"],
977 affected_int_cpd
["int-virtual-link-desc"],
980 norm(str(e
.exception
)),
981 "Wrong exception text",
984 def test_validate_internal_virtual_links_when_missing_ivld_on_profile(self
):
985 indata
= deepcopy(db_vnfd_content
)
986 affected_ivld_profile
= {"id": "non-existing-int-virtual-link-desc"}
988 df
["virtual-link-profile"] = [affected_ivld_profile
]
989 with self
.assertRaises(EngineException
) as e
:
990 self
.topic
.validate_internal_virtual_links(indata
)
992 e
.exception
.http_code
,
993 HTTPStatus
.UNPROCESSABLE_ENTITY
,
994 "Wrong HTTP status code",
998 "df[id='{}']:virtual-link-profile='{}' must match an existing "
999 "int-virtual-link-desc".format(df
["id"], affected_ivld_profile
["id"])
1001 norm(str(e
.exception
)),
1002 "Wrong exception text",
1005 def test_validate_monitoring_params_on_valid_descriptor(self
):
1006 indata
= db_vnfd_content
1007 self
.topic
.validate_monitoring_params(indata
)
1009 def test_validate_monitoring_params_on_duplicated_ivld_monitoring_param(self
):
1010 indata
= deepcopy(db_vnfd_content
)
1011 duplicated_mp
= {"id": "cpu", "name": "cpu", "performance_metric": "cpu"}
1012 affected_ivld
= indata
["int-virtual-link-desc"][0]
1013 affected_ivld
["monitoring-parameters"] = [duplicated_mp
, duplicated_mp
]
1014 with self
.assertRaises(EngineException
) as e
:
1015 self
.topic
.validate_monitoring_params(indata
)
1017 e
.exception
.http_code
,
1018 HTTPStatus
.UNPROCESSABLE_ENTITY
,
1019 "Wrong HTTP status code",
1023 "Duplicated monitoring-parameter id in "
1024 "int-virtual-link-desc[id='{}']:monitoring-parameters[id='{}']".format(
1025 affected_ivld
["id"], duplicated_mp
["id"]
1028 norm(str(e
.exception
)),
1029 "Wrong exception text",
1032 def test_validate_monitoring_params_on_duplicated_vdu_monitoring_param(self
):
1033 indata
= deepcopy(db_vnfd_content
)
1035 "id": "dataVM_cpu_util",
1036 "name": "dataVM_cpu_util",
1037 "performance_metric": "cpu",
1039 affected_vdu
= indata
["vdu"][1]
1040 affected_vdu
["monitoring-parameter"].insert(0, duplicated_mp
)
1041 with self
.assertRaises(EngineException
) as e
:
1042 self
.topic
.validate_monitoring_params(indata
)
1044 e
.exception
.http_code
,
1045 HTTPStatus
.UNPROCESSABLE_ENTITY
,
1046 "Wrong HTTP status code",
1050 "Duplicated monitoring-parameter id in "
1051 "vdu[id='{}']:monitoring-parameter[id='{}']".format(
1052 affected_vdu
["id"], duplicated_mp
["id"]
1055 norm(str(e
.exception
)),
1056 "Wrong exception text",
1059 def test_validate_monitoring_params_on_duplicated_df_monitoring_param(self
):
1060 indata
= deepcopy(db_vnfd_content
)
1064 "performance_metric": "memory",
1066 affected_df
= indata
["df"][0]
1067 affected_df
["monitoring-parameter"] = [duplicated_mp
, duplicated_mp
]
1068 with self
.assertRaises(EngineException
) as e
:
1069 self
.topic
.validate_monitoring_params(indata
)
1071 e
.exception
.http_code
,
1072 HTTPStatus
.UNPROCESSABLE_ENTITY
,
1073 "Wrong HTTP status code",
1077 "Duplicated monitoring-parameter id in "
1078 "df[id='{}']:monitoring-parameter[id='{}']".format(
1079 affected_df
["id"], duplicated_mp
["id"]
1082 norm(str(e
.exception
)),
1083 "Wrong exception text",
1086 def test_validate_scaling_group_descriptor_on_valid_descriptor(self
):
1087 indata
= db_vnfd_content
1088 self
.topic
.validate_scaling_group_descriptor(indata
)
1090 def test_validate_scaling_group_descriptor_when_missing_monitoring_param(self
):
1091 indata
= deepcopy(db_vnfd_content
)
1092 vdu
= indata
["vdu"][1]
1093 affected_df
= indata
["df"][0]
1094 affected_sa
= affected_df
["scaling-aspect"][0]
1095 affected_sp
= affected_sa
["scaling-policy"][0]
1096 affected_sc
= affected_sp
["scaling-criteria"][0]
1097 vdu
.pop("monitoring-parameter")
1098 with self
.assertRaises(EngineException
) as e
:
1099 self
.topic
.validate_scaling_group_descriptor(indata
)
1101 e
.exception
.http_code
,
1102 HTTPStatus
.UNPROCESSABLE_ENTITY
,
1103 "Wrong HTTP status code",
1107 "df[id='{}']:scaling-aspect[id='{}']:scaling-policy"
1108 "[name='{}']:scaling-criteria[name='{}']: "
1109 "vnf-monitoring-param-ref='{}' not defined in any monitoring-param".format(
1112 affected_sp
["name"],
1113 affected_sc
["name"],
1114 affected_sc
["vnf-monitoring-param-ref"],
1117 norm(str(e
.exception
)),
1118 "Wrong exception text",
1121 def test_validate_scaling_group_descriptor_when_missing_vnf_configuration(self
):
1122 indata
= deepcopy(db_vnfd_content
)
1123 df
= indata
["df"][0]
1124 affected_sa
= df
["scaling-aspect"][0]
1125 indata
["df"][0]["lcm-operations-configuration"]["operate-vnf-op-config"][
1128 with self
.assertRaises(EngineException
) as e
:
1129 self
.topic
.validate_scaling_group_descriptor(indata
)
1131 e
.exception
.http_code
,
1132 HTTPStatus
.UNPROCESSABLE_ENTITY
,
1133 "Wrong HTTP status code",
1137 "'day1-2 configuration' not defined in the descriptor but it is referenced "
1138 "by df[id='{}']:scaling-aspect[id='{}']:scaling-config-action".format(
1139 df
["id"], affected_sa
["id"]
1142 norm(str(e
.exception
)),
1143 "Wrong exception text",
1146 def test_validate_scaling_group_descriptor_when_missing_scaling_config_action_primitive(
1149 indata
= deepcopy(db_vnfd_content
)
1150 df
= indata
["df"][0]
1151 affected_sa
= df
["scaling-aspect"][0]
1152 affected_sca_primitive
= affected_sa
["scaling-config-action"][0][
1153 "vnf-config-primitive-name-ref"
1155 df
["lcm-operations-configuration"]["operate-vnf-op-config"]["day1-2"][0][
1158 with self
.assertRaises(EngineException
) as e
:
1159 self
.topic
.validate_scaling_group_descriptor(indata
)
1161 e
.exception
.http_code
,
1162 HTTPStatus
.UNPROCESSABLE_ENTITY
,
1163 "Wrong HTTP status code",
1167 "df[id='{}']:scaling-aspect[id='{}']:scaling-config-action:vnf-"
1168 "config-primitive-name-ref='{}' does not match any "
1169 "day1-2 configuration:config-primitive:name".format(
1170 df
["id"], affected_sa
["id"], affected_sca_primitive
1173 norm(str(e
.exception
)),
1174 "Wrong exception text",
1177 def test_new_vnfd_revision(self
):
1178 did
= db_vnfd_content
["_id"]
1179 self
.fs
.get_params
.return_value
= {}
1180 self
.fs
.file_exists
.return_value
= False
1181 self
.fs
.file_open
.side_effect
= lambda path
, mode
: open(
1182 "/tmp/" + str(uuid4()), "a+b"
1184 test_vnfd
= deepcopy(db_vnfd_content
)
1185 del test_vnfd
["_id"]
1186 del test_vnfd
["_admin"]
1187 self
.db
.create
.return_value
= did
1189 did2
, oid
= self
.topic
.new(rollback
, fake_session
, {})
1190 db_args
= self
.db
.create
.call_args
[0]
1191 self
.assertEqual(db_args
[1]['_admin']['revision'], 0,
1192 "New package should be at revision 0")
1194 @patch("osm_nbi.descriptor_topics.shutil")
1195 @patch("osm_nbi.descriptor_topics.os.rename")
1196 def test_update_vnfd(self
, mock_rename
, mock_shutil
):
1198 did
= db_vnfd_content
["_id"]
1200 self
.fs
.get_params
.return_value
= {}
1201 self
.fs
.file_exists
.return_value
= False
1202 self
.fs
.file_open
.side_effect
= lambda path
, mode
: open(
1203 "/tmp/" + str(uuid4()), "a+b"
1205 new_vnfd
= deepcopy(db_vnfd_content
)
1207 self
.db
.create
.return_value
= did
1209 did2
, oid
= self
.topic
.new(rollback
, fake_session
, {})
1210 del new_vnfd
["vdu"][0]["cloud-init-file"]
1211 del new_vnfd
["df"][0]["lcm-operations-configuration"]["operate-vnf-op-config"][
1212 "day1-2"][0]["execution-environment-list"][0]["juju"]
1217 "_admin": deepcopy(db_vnfd_content
["_admin"])
1219 old_vnfd
["_admin"]["revision"] = old_revision
1220 self
.db
.get_one
.side_effect
= [
1224 self
.topic
.upload_content(
1225 fake_session
, did
, new_vnfd
, {}, {"Content-Type": []}
1228 db_args
= self
.db
.replace
.call_args
[0]
1229 self
.assertEqual(db_args
[2]['_admin']['revision'], old_revision
+ 1,
1230 "Revision should increment")
1233 class Test_NsdTopic(TestCase
):
1235 def setUpClass(cls
):
1236 cls
.test_name
= "test-nsd-topic"
1239 def tearDownClass(cls
):
1243 self
.db
= Mock(dbbase
.DbBase())
1244 self
.fs
= Mock(fsbase
.FsBase())
1245 self
.msg
= Mock(msgbase
.MsgBase())
1246 self
.auth
= Mock(authconn
.Authconn(None, None, None))
1247 self
.topic
= NsdTopic(self
.db
, self
.fs
, self
.msg
, self
.auth
)
1248 self
.topic
.check_quota
= Mock(return_value
=None) # skip quota
1250 @patch("osm_nbi.descriptor_topics.shutil")
1251 @patch("osm_nbi.descriptor_topics.os.rename")
1252 def test_new_nsd(self
, mock_rename
, mock_shutil
):
1253 did
= db_nsd_content
["_id"]
1254 self
.fs
.get_params
.return_value
= {}
1255 self
.fs
.file_exists
.return_value
= False
1256 self
.fs
.file_open
.side_effect
= lambda path
, mode
: open(
1257 "/tmp/" + str(uuid4()), "a+b"
1259 test_nsd
= deepcopy(db_nsd_content
)
1261 del test_nsd
["_admin"]
1262 with self
.subTest(i
=1, t
="Normal Creation"):
1263 self
.db
.create
.return_value
= did
1265 did2
, oid
= self
.topic
.new(rollback
, fake_session
, {})
1266 db_args
= self
.db
.create
.call_args
[0]
1267 msg_args
= self
.msg
.write
.call_args
[0]
1268 self
.assertEqual(len(rollback
), 1, "Wrong rollback length")
1269 self
.assertEqual(msg_args
[0], self
.topic
.topic_msg
, "Wrong message topic")
1270 self
.assertEqual(msg_args
[1], "created", "Wrong message action")
1271 self
.assertEqual(msg_args
[2], {"_id": did
}, "Wrong message content")
1272 self
.assertEqual(db_args
[0], self
.topic
.topic
, "Wrong DB topic")
1273 self
.assertEqual(did2
, did
, "Wrong DB NSD id")
1274 self
.assertIsNotNone(db_args
[1]["_admin"]["created"], "Wrong creation time")
1276 db_args
[1]["_admin"]["modified"],
1277 db_args
[1]["_admin"]["created"],
1278 "Wrong modification time",
1281 db_args
[1]["_admin"]["projects_read"],
1283 "Wrong read-only project list",
1286 db_args
[1]["_admin"]["projects_write"],
1288 "Wrong read-write project list",
1292 self
.db
.get_one
.side_effect
= [
1293 {"_id": did
, "_admin": db_nsd_content
["_admin"]},
1296 self
.db
.get_list
.return_value
= [db_vnfd_content
]
1297 self
.topic
.upload_content(
1298 fake_session
, did
, test_nsd
, {}, {"Content-Type": []}
1300 msg_args
= self
.msg
.write
.call_args
[0]
1301 test_nsd
["_id"] = did
1303 msg_args
[0], self
.topic
.topic_msg
, "Wrong message topic"
1305 self
.assertEqual(msg_args
[1], "edited", "Wrong message action")
1306 self
.assertEqual(msg_args
[2], test_nsd
, "Wrong message content")
1307 db_args
= self
.db
.get_one
.mock_calls
[0][1]
1308 self
.assertEqual(db_args
[0], self
.topic
.topic
, "Wrong DB topic")
1309 self
.assertEqual(db_args
[1]["_id"], did
, "Wrong DB NSD id")
1310 db_args
= self
.db
.replace
.call_args
[0]
1311 self
.assertEqual(db_args
[0], self
.topic
.topic
, "Wrong DB topic")
1312 self
.assertEqual(db_args
[1], did
, "Wrong DB NSD id")
1313 admin
= db_args
[2]["_admin"]
1314 db_admin
= db_nsd_content
["_admin"]
1316 admin
["created"], db_admin
["created"], "Wrong creation time"
1319 admin
["modified"], db_admin
["created"], "Wrong modification time"
1322 admin
["projects_read"],
1323 db_admin
["projects_read"],
1324 "Wrong read-only project list",
1327 admin
["projects_write"],
1328 db_admin
["projects_write"],
1329 "Wrong read-write project list",
1332 admin
["onboardingState"], "ONBOARDED", "Wrong onboarding state"
1335 admin
["operationalState"], "ENABLED", "Wrong operational state"
1337 self
.assertEqual(admin
["usageState"], "NOT_IN_USE", "Wrong usage state")
1338 storage
= admin
["storage"]
1339 self
.assertEqual(storage
["folder"], did
, "Wrong storage folder")
1341 storage
["descriptor"], "package", "Wrong storage descriptor"
1343 compare_desc(self
, test_nsd
, db_args
[2], "NSD")
1344 revision_args
= self
.db
.create
.call_args
[0]
1346 revision_args
[0], self
.topic
.topic
+ "_revisions", "Wrong topic"
1349 revision_args
[1]["id"], db_args
[2]["id"], "Wrong revision id"
1352 revision_args
[1]["_id"],
1353 db_args
[2]["_id"] + ":1",
1354 "Wrong revision _id"
1359 self
.db
.get_one
.side_effect
= (
1360 lambda table
, filter, fail_on_empty
=None, fail_on_more
=None: {
1362 "_admin": db_nsd_content
["_admin"],
1365 with self
.subTest(i
=2, t
="Check Pyangbind Validation: required properties"):
1366 tmp
= test_nsd
["id"]
1369 with self
.assertRaises(
1370 EngineException
, msg
="Accepted NSD with a missing required property"
1372 self
.topic
.upload_content(
1373 fake_session
, did
, test_nsd
, {}, {"Content-Type": []}
1376 e
.exception
.http_code
,
1377 HTTPStatus
.UNPROCESSABLE_ENTITY
,
1378 "Wrong HTTP status code",
1381 norm("Error in pyangbind validation: '{}'".format("id")),
1382 norm(str(e
.exception
)),
1383 "Wrong exception text",
1386 test_nsd
["id"] = tmp
1387 with self
.subTest(i
=3, t
="Check Pyangbind Validation: additional properties"):
1388 test_nsd
["extra-property"] = 0
1390 with self
.assertRaises(
1391 EngineException
, msg
="Accepted NSD with an additional property"
1393 self
.topic
.upload_content(
1394 fake_session
, did
, test_nsd
, {}, {"Content-Type": []}
1397 e
.exception
.http_code
,
1398 HTTPStatus
.UNPROCESSABLE_ENTITY
,
1399 "Wrong HTTP status code",
1403 "Error in pyangbind validation: {} ({})".format(
1404 "json object contained a key that did not exist",
1408 norm(str(e
.exception
)),
1409 "Wrong exception text",
1412 del test_nsd
["extra-property"]
1413 with self
.subTest(i
=4, t
="Check Pyangbind Validation: property types"):
1414 tmp
= test_nsd
["designer"]
1415 test_nsd
["designer"] = {"key": 0}
1417 with self
.assertRaises(
1418 EngineException
, msg
="Accepted NSD with a wrongly typed property"
1420 self
.topic
.upload_content(
1421 fake_session
, did
, test_nsd
, {}, {"Content-Type": []}
1424 e
.exception
.http_code
,
1425 HTTPStatus
.UNPROCESSABLE_ENTITY
,
1426 "Wrong HTTP status code",
1430 "Error in pyangbind validation: {} ({})".format(
1431 "json object contained a key that did not exist", "key"
1434 norm(str(e
.exception
)),
1435 "Wrong exception text",
1438 test_nsd
["designer"] = tmp
1440 i
=5, t
="Check Input Validation: mgmt-network+virtual-link-protocol-data"
1442 df
= test_nsd
["df"][0]
1445 "virtual-link-desc-id": "mgmt",
1446 "virtual-link-protocol-data": {"associated-layer-protocol": "ipv4"},
1448 df
["virtual-link-profile"] = [mgmt_profile
]
1450 with self
.assertRaises(
1451 EngineException
, msg
="Accepted VLD with mgmt-network+ip-profile"
1453 self
.topic
.upload_content(
1454 fake_session
, did
, test_nsd
, {}, {"Content-Type": []}
1457 e
.exception
.http_code
,
1458 HTTPStatus
.UNPROCESSABLE_ENTITY
,
1459 "Wrong HTTP status code",
1463 "Error at df[id='{}']:virtual-link-profile[id='{}']:virtual-link-protocol-data"
1464 " You cannot set a virtual-link-protocol-data when mgmt-network is True".format(
1465 df
["id"], mgmt_profile
["id"]
1468 norm(str(e
.exception
)),
1469 "Wrong exception text",
1472 del df
["virtual-link-profile"]
1473 with self
.subTest(i
=6, t
="Check Descriptor Dependencies: vnfd-id[]"):
1474 self
.db
.get_one
.side_effect
= [
1475 {"_id": did
, "_admin": db_nsd_content
["_admin"]},
1478 self
.db
.get_list
.return_value
= []
1480 with self
.assertRaises(
1481 EngineException
, msg
="Accepted wrong VNFD ID reference"
1483 self
.topic
.upload_content(
1484 fake_session
, did
, test_nsd
, {}, {"Content-Type": []}
1487 e
.exception
.http_code
, HTTPStatus
.CONFLICT
, "Wrong HTTP status code"
1491 "'vnfd-id'='{}' references a non existing vnfd".format(
1492 test_nsd
["vnfd-id"][0]
1495 norm(str(e
.exception
)),
1496 "Wrong exception text",
1502 t
="Check Descriptor Dependencies: "
1503 "vld[vnfd-connection-point-ref][vnfd-connection-point-ref]",
1505 vnfd_descriptor
= deepcopy(db_vnfd_content
)
1506 df
= test_nsd
["df"][0]
1507 affected_vnf_profile
= df
["vnf-profile"][0]
1508 affected_virtual_link
= affected_vnf_profile
["virtual-link-connectivity"][1]
1509 affected_cpd
= vnfd_descriptor
["ext-cpd"].pop()
1510 self
.db
.get_one
.side_effect
= [
1511 {"_id": did
, "_admin": db_nsd_content
["_admin"]},
1514 self
.db
.get_list
.return_value
= [vnfd_descriptor
]
1516 with self
.assertRaises(
1517 EngineException
, msg
="Accepted wrong VLD CP reference"
1519 self
.topic
.upload_content(
1520 fake_session
, did
, test_nsd
, {}, {"Content-Type": []}
1523 e
.exception
.http_code
,
1524 HTTPStatus
.UNPROCESSABLE_ENTITY
,
1525 "Wrong HTTP status code",
1529 "Error at df[id='{}']:vnf-profile[id='{}']:virtual-link-connectivity"
1530 "[virtual-link-profile-id='{}']:constituent-cpd-id='{}' references a "
1531 "non existing ext-cpd:id inside vnfd '{}'".format(
1533 affected_vnf_profile
["id"],
1534 affected_virtual_link
["virtual-link-profile-id"],
1536 vnfd_descriptor
["id"],
1539 norm(str(e
.exception
)),
1540 "Wrong exception text",
1546 def test_edit_nsd(self
):
1547 nsd_content
= deepcopy(db_nsd_content
)
1548 did
= nsd_content
["_id"]
1549 self
.fs
.file_exists
.return_value
= True
1550 self
.fs
.dir_ls
.return_value
= True
1551 with self
.subTest(i
=1, t
="Normal Edition"):
1553 self
.db
.get_one
.side_effect
= [deepcopy(nsd_content
), None]
1554 self
.db
.get_list
.return_value
= [db_vnfd_content
]
1555 data
= {"id": "new-nsd-id", "name": "new-nsd-name"}
1556 self
.topic
.edit(fake_session
, did
, data
)
1557 db_args
= self
.db
.replace
.call_args
[0]
1558 msg_args
= self
.msg
.write
.call_args
[0]
1560 self
.assertEqual(msg_args
[0], self
.topic
.topic_msg
, "Wrong message topic")
1561 self
.assertEqual(msg_args
[1], "edited", "Wrong message action")
1562 self
.assertEqual(msg_args
[2], data
, "Wrong message content")
1563 self
.assertEqual(db_args
[0], self
.topic
.topic
, "Wrong DB topic")
1564 self
.assertEqual(db_args
[1], did
, "Wrong DB ID")
1566 db_args
[2]["_admin"]["created"],
1567 nsd_content
["_admin"]["created"],
1568 "Wrong creation time",
1571 db_args
[2]["_admin"]["modified"], now
, "Wrong modification time"
1574 db_args
[2]["_admin"]["projects_read"],
1575 nsd_content
["_admin"]["projects_read"],
1576 "Wrong read-only project list",
1579 db_args
[2]["_admin"]["projects_write"],
1580 nsd_content
["_admin"]["projects_write"],
1581 "Wrong read-write project list",
1583 self
.assertEqual(db_args
[2]["id"], data
["id"], "Wrong NSD ID")
1584 self
.assertEqual(db_args
[2]["name"], data
["name"], "Wrong NSD Name")
1585 with self
.subTest(i
=2, t
="Conflict on Edit"):
1586 data
= {"id": "fake-nsd-id", "name": "new-nsd-name"}
1587 self
.db
.get_one
.side_effect
= [
1589 {"_id": str(uuid4()), "id": data
["id"]},
1591 with self
.assertRaises(
1592 EngineException
, msg
="Accepted existing NSD ID"
1594 self
.topic
.edit(fake_session
, did
, data
)
1596 e
.exception
.http_code
, HTTPStatus
.CONFLICT
, "Wrong HTTP status code"
1600 "{} with id '{}' already exists for this project".format(
1604 norm(str(e
.exception
)),
1605 "Wrong exception text",
1607 with self
.subTest(i
=3, t
="Check Envelope"):
1608 data
= {"nsd": {"nsd": {"id": "new-nsd-id", "name": "new-nsd-name"}}}
1609 self
.db
.get_one
.side_effect
= [nsd_content
, None]
1610 with self
.assertRaises(
1611 EngineException
, msg
="Accepted NSD with wrong envelope"
1613 self
.topic
.edit(fake_session
, did
, data
, content
=nsd_content
)
1615 e
.exception
.http_code
, HTTPStatus
.BAD_REQUEST
, "Wrong HTTP status code"
1618 "'nsd' must be a list of only one element",
1619 norm(str(e
.exception
)),
1620 "Wrong exception text",
1624 def test_delete_nsd(self
):
1625 did
= db_nsd_content
["_id"]
1626 self
.db
.get_one
.return_value
= db_nsd_content
1627 p_id
= db_nsd_content
["_admin"]["projects_read"][0]
1628 with self
.subTest(i
=1, t
="Normal Deletion"):
1629 self
.db
.get_list
.return_value
= []
1630 self
.db
.del_one
.return_value
= {"deleted": 1}
1631 self
.topic
.delete(fake_session
, did
)
1632 db_args
= self
.db
.del_one
.call_args
[0]
1633 msg_args
= self
.msg
.write
.call_args
[0]
1634 self
.assertEqual(msg_args
[0], self
.topic
.topic_msg
, "Wrong message topic")
1635 self
.assertEqual(msg_args
[1], "deleted", "Wrong message action")
1636 self
.assertEqual(msg_args
[2], {"_id": did
}, "Wrong message content")
1637 self
.assertEqual(db_args
[0], self
.topic
.topic
, "Wrong DB topic")
1638 self
.assertEqual(db_args
[1]["_id"], did
, "Wrong DB ID")
1640 db_args
[1]["_admin.projects_write.cont"],
1644 db_g1_args
= self
.db
.get_one
.call_args
[0]
1645 self
.assertEqual(db_g1_args
[0], self
.topic
.topic
, "Wrong DB topic")
1646 self
.assertEqual(db_g1_args
[1]["_id"], did
, "Wrong DB NSD ID")
1647 db_gl_calls
= self
.db
.get_list
.call_args_list
1648 self
.assertEqual(db_gl_calls
[0][0][0], "nsrs", "Wrong DB topic")
1649 # self.assertEqual(db_gl_calls[0][0][1]["nsd-id"], did, "Wrong DB NSD ID") # Filter changed after call
1650 self
.assertEqual(db_gl_calls
[1][0][0], "nsts", "Wrong DB topic")
1652 db_gl_calls
[1][0][1]["netslice-subnet.ANYINDEX.nsd-ref"],
1653 db_nsd_content
["id"],
1654 "Wrong DB NSD netslice-subnet nsd-ref",
1656 self
.db
.set_one
.assert_not_called()
1657 fs_del_calls
= self
.fs
.file_delete
.call_args_list
1658 self
.assertEqual(fs_del_calls
[0][0][0], did
, "Wrong FS file id")
1659 self
.assertEqual(fs_del_calls
[1][0][0], did
+ "_", "Wrong FS folder id")
1660 with self
.subTest(i
=2, t
="Conflict on Delete - NSD in use by nsr"):
1661 self
.db
.get_list
.return_value
= [{"_id": str(uuid4()), "name": "fake-nsr"}]
1662 with self
.assertRaises(
1663 EngineException
, msg
="Accepted NSD in use by NSR"
1665 self
.topic
.delete(fake_session
, did
)
1667 e
.exception
.http_code
, HTTPStatus
.CONFLICT
, "Wrong HTTP status code"
1670 "there is at least one ns instance using this descriptor",
1671 norm(str(e
.exception
)),
1672 "Wrong exception text",
1674 with self
.subTest(i
=3, t
="Conflict on Delete - NSD in use by NST"):
1675 self
.db
.get_list
.side_effect
= [
1677 [{"_id": str(uuid4()), "name": "fake-nst"}],
1679 with self
.assertRaises(
1680 EngineException
, msg
="Accepted NSD in use by NST"
1682 self
.topic
.delete(fake_session
, did
)
1684 e
.exception
.http_code
, HTTPStatus
.CONFLICT
, "Wrong HTTP status code"
1687 "there is at least one netslice template referencing this descriptor",
1688 norm(str(e
.exception
)),
1689 "Wrong exception text",
1691 with self
.subTest(i
=4, t
="Non-existent NSD"):
1692 excp_msg
= "Not found any {} with filter='{}'".format("NSD", {"_id": did
})
1693 self
.db
.get_one
.side_effect
= DbException(excp_msg
, HTTPStatus
.NOT_FOUND
)
1694 with self
.assertRaises(
1695 DbException
, msg
="Accepted non-existent NSD ID"
1697 self
.topic
.delete(fake_session
, did
)
1699 e
.exception
.http_code
, HTTPStatus
.NOT_FOUND
, "Wrong HTTP status code"
1702 norm(excp_msg
), norm(str(e
.exception
)), "Wrong exception text"
1704 with self
.subTest(i
=5, t
="No delete because referenced by other project"):
1705 db_nsd_content
["_admin"]["projects_read"].append("other_project")
1706 self
.db
.get_one
= Mock(return_value
=db_nsd_content
)
1707 self
.db
.get_list
= Mock(return_value
=[])
1708 self
.msg
.write
.reset_mock()
1709 self
.db
.del_one
.reset_mock()
1710 self
.fs
.file_delete
.reset_mock()
1712 self
.topic
.delete(fake_session
, did
)
1713 self
.db
.del_one
.assert_not_called()
1714 self
.msg
.write
.assert_not_called()
1715 db_g1_args
= self
.db
.get_one
.call_args
[0]
1716 self
.assertEqual(db_g1_args
[0], self
.topic
.topic
, "Wrong DB topic")
1717 self
.assertEqual(db_g1_args
[1]["_id"], did
, "Wrong DB VNFD ID")
1718 db_s1_args
= self
.db
.set_one
.call_args
1719 self
.assertEqual(db_s1_args
[0][0], self
.topic
.topic
, "Wrong DB topic")
1720 self
.assertEqual(db_s1_args
[0][1]["_id"], did
, "Wrong DB ID")
1722 p_id
, db_s1_args
[0][1]["_admin.projects_write.cont"], "Wrong DB filter"
1725 db_s1_args
[1]["update_dict"], "Wrong DB update dictionary"
1728 db_s1_args
[1]["pull_list"],
1729 {"_admin.projects_read": (p_id
,), "_admin.projects_write": (p_id
,)},
1730 "Wrong DB pull_list dictionary",
1732 self
.fs
.file_delete
.assert_not_called()
1735 def test_validate_vld_mgmt_network_with_virtual_link_protocol_data_on_valid_descriptor(
1738 indata
= deepcopy(db_nsd_content
)
1739 vld
= indata
["virtual-link-desc"][0]
1740 self
.topic
.validate_vld_mgmt_network_with_virtual_link_protocol_data(
1744 def test_validate_vld_mgmt_network_with_virtual_link_protocol_data_when_both_defined(
1747 indata
= deepcopy(db_nsd_content
)
1748 vld
= indata
["virtual-link-desc"][0]
1749 df
= indata
["df"][0]
1752 "virtual-link-desc-id": "mgmt",
1753 "virtual-link-protocol-data": {"associated-layer-protocol": "ipv4"},
1755 df
["virtual-link-profile"] = [affected_vlp
]
1756 with self
.assertRaises(EngineException
) as e
:
1757 self
.topic
.validate_vld_mgmt_network_with_virtual_link_protocol_data(
1761 e
.exception
.http_code
,
1762 HTTPStatus
.UNPROCESSABLE_ENTITY
,
1763 "Wrong HTTP status code",
1767 "Error at df[id='{}']:virtual-link-profile[id='{}']:virtual-link-protocol-data"
1768 " You cannot set a virtual-link-protocol-data when mgmt-network is True".format(
1769 df
["id"], affected_vlp
["id"]
1772 norm(str(e
.exception
)),
1773 "Wrong exception text",
1776 def test_validate_vnf_profiles_vnfd_id_on_valid_descriptor(self
):
1777 indata
= deepcopy(db_nsd_content
)
1778 self
.topic
.validate_vnf_profiles_vnfd_id(indata
)
1780 def test_validate_vnf_profiles_vnfd_id_when_missing_vnfd(self
):
1781 indata
= deepcopy(db_nsd_content
)
1782 df
= indata
["df"][0]
1783 affected_vnf_profile
= df
["vnf-profile"][0]
1784 indata
["vnfd-id"] = ["non-existing-vnfd"]
1785 with self
.assertRaises(EngineException
) as e
:
1786 self
.topic
.validate_vnf_profiles_vnfd_id(indata
)
1788 e
.exception
.http_code
,
1789 HTTPStatus
.UNPROCESSABLE_ENTITY
,
1790 "Wrong HTTP status code",
1794 "Error at df[id='{}']:vnf_profile[id='{}']:vnfd-id='{}' "
1795 "does not match any vnfd-id".format(
1797 affected_vnf_profile
["id"],
1798 affected_vnf_profile
["vnfd-id"],
1801 norm(str(e
.exception
)),
1802 "Wrong exception text",
1805 def test_validate_df_vnf_profiles_constituent_connection_points_on_valid_descriptor(
1808 nsd_descriptor
= deepcopy(db_nsd_content
)
1809 vnfd_descriptor
= deepcopy(db_vnfd_content
)
1810 df
= nsd_descriptor
["df"][0]
1811 vnfds_index
= {vnfd_descriptor
["id"]: vnfd_descriptor
}
1812 self
.topic
.validate_df_vnf_profiles_constituent_connection_points(
1816 def test_validate_df_vnf_profiles_constituent_connection_points_when_missing_connection_point(
1819 nsd_descriptor
= deepcopy(db_nsd_content
)
1820 vnfd_descriptor
= deepcopy(db_vnfd_content
)
1821 df
= nsd_descriptor
["df"][0]
1822 affected_vnf_profile
= df
["vnf-profile"][0]
1823 affected_virtual_link
= affected_vnf_profile
["virtual-link-connectivity"][1]
1824 vnfds_index
= {vnfd_descriptor
["id"]: vnfd_descriptor
}
1825 affected_cpd
= vnfd_descriptor
["ext-cpd"].pop()
1826 with self
.assertRaises(EngineException
) as e
:
1827 self
.topic
.validate_df_vnf_profiles_constituent_connection_points(
1831 e
.exception
.http_code
,
1832 HTTPStatus
.UNPROCESSABLE_ENTITY
,
1833 "Wrong HTTP status code",
1837 "Error at df[id='{}']:vnf-profile[id='{}']:virtual-link-connectivity"
1838 "[virtual-link-profile-id='{}']:constituent-cpd-id='{}' references a "
1839 "non existing ext-cpd:id inside vnfd '{}'".format(
1841 affected_vnf_profile
["id"],
1842 affected_virtual_link
["virtual-link-profile-id"],
1844 vnfd_descriptor
["id"],
1847 norm(str(e
.exception
)),
1848 "Wrong exception text",
1851 def test_check_conflict_on_edit_when_missing_constituent_vnfd_id(self
):
1852 nsd_descriptor
= deepcopy(db_nsd_content
)
1853 invalid_vnfd_id
= "invalid-vnfd-id"
1854 nsd_descriptor
["id"] = "invalid-vnfd-id-ns"
1855 nsd_descriptor
["vnfd-id"][0] = invalid_vnfd_id
1856 nsd_descriptor
["df"][0]["vnf-profile"][0]["vnfd-id"] = invalid_vnfd_id
1857 nsd_descriptor
["df"][0]["vnf-profile"][1]["vnfd-id"] = invalid_vnfd_id
1858 with self
.assertRaises(EngineException
) as e
:
1859 self
.db
.get_list
.return_value
= []
1860 nsd_descriptor
= self
.topic
.check_conflict_on_edit(
1861 fake_session
, nsd_descriptor
, [], "id"
1864 e
.exception
.http_code
, HTTPStatus
.CONFLICT
, "Wrong HTTP status code"
1868 "Descriptor error at 'vnfd-id'='{}' references a non "
1869 "existing vnfd".format(invalid_vnfd_id
)
1871 norm(str(e
.exception
)),
1872 "Wrong exception text",
1876 if __name__
== "__main__":