2 # Licensed under the Apache License, Version 2.0 (the "License"); you may
3 # not use this file except in compliance with the License. You may obtain
4 # a copy of the License at
6 # http://www.apache.org/licenses/LICENSE-2.0
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11 # License for the specific language governing permissions and limitations
14 # For those usages not covered by the Apache License, Version 2.0 please
15 # contact: esousa@whitestack.com or alfonso.tiernosepulveda@telefonica.com
20 from unittest
.mock
import Mock
, mock_open
# patch, MagicMock
21 from osm_common
.dbbase
import DbException
22 from osm_nbi
.engine
import EngineException
23 from osm_common
.dbmemory
import DbMemory
24 from osm_common
.fsbase
import FsBase
25 from osm_common
.msgbase
import MsgBase
26 from osm_common
import dbbase
27 from http
import HTTPStatus
28 from osm_nbi
.instance_topics
import NsLcmOpTopic
, NsrTopic
29 from osm_nbi
.tests
.test_db_descriptors
import (
36 from copy
import deepcopy
40 class TestNsLcmOpTopic(unittest
.TestCase
):
43 self
.fs
= Mock(FsBase())
44 self
.fs
.get_params
.return_value
= {"./fake/folder"}
45 self
.fs
.file_open
= mock_open()
46 self
.msg
= Mock(MsgBase())
48 self
.nslcmop_topic
= NsLcmOpTopic(self
.db
, self
.fs
, self
.msg
, None)
49 self
.nslcmop_topic
.check_quota
= Mock(return_value
=None) # skip quota
52 "vim_accounts", yaml
.load(db_vim_accounts_text
, Loader
=yaml
.Loader
)
54 self
.db
.create_list("nsds", yaml
.load(db_nsds_text
, Loader
=yaml
.Loader
))
55 self
.db
.create_list("vnfds", yaml
.load(db_vnfds_text
, Loader
=yaml
.Loader
))
56 self
.db
.create_list("vnfrs", yaml
.load(db_vnfrs_text
, Loader
=yaml
.Loader
))
57 self
.db
.create_list("nsrs", yaml
.load(db_nsrs_text
, Loader
=yaml
.Loader
))
58 self
.db
.create
= Mock(return_value
="created_id")
59 self
.db
.set_one
= Mock(return_value
={"updated": 1})
60 self
.nsd
= self
.db
.get_list("nsds")[0]
61 self
.nsd_id
= self
.nsd
["_id"]
62 self
.nsr
= self
.db
.get_list("nsrs")[0]
63 self
.nsr_id
= self
.nsr
["_id"]
64 self
.nsr_project
= self
.nsr
["_admin"]["projects_read"][0]
66 self
.vim
= self
.db
.get_list("vim_accounts")[0]
67 self
.vim_id
= self
.vim
["_id"]
69 def test_create_instantiate(self
):
74 "project_id": [self
.nsr_project
],
79 "nsInstanceId": self
.nsr_id
,
81 "vimAccountId": self
.vim_id
,
82 "additionalParamsForVnf": [
84 "member-vnf-index": "1",
85 "additionalParams": {"touch_filename": "file"},
88 "member-vnf-index": "2",
89 "additionalParams": {"touch_filename": "file"},
94 "member-vnf-index": "1",
100 "name": "dataVM-eth0",
101 "ip-address": "10.11.12.13",
102 "floating-ip-required": True,
108 {"name": "internal", "vim-network-id": "vim-net-id"}
112 "lcmOperationType": "instantiate",
117 nslcmop_id
, _
= self
.nslcmop_topic
.new(
118 rollback
, session
, indata
=deepcopy(indata
), kwargs
=None, headers
=headers
121 # check nslcmop is created at database
123 self
.db
.create
.call_count
,
125 "database create not called, or called more than once",
127 _call
= self
.db
.create
.call_args_list
[0]
129 _call
[0][0], "nslcmops", "must be create a nslcmops entry at database"
132 created_nslcmop
= _call
[0][1]
135 created_nslcmop
["_id"],
136 "mismatch between return id and database '_id'",
140 created_nslcmop
["nsInstanceId"],
141 "bad reference id from nslcmop to nsr",
144 created_nslcmop
["_admin"].get("projects_read"),
145 "Database record must contain '_amdin.projects_read'",
149 created_nslcmop
["_admin"],
150 "Database record must contain '_admin.created'",
153 created_nslcmop
["lcmOperationType"] == "instantiate",
154 "Database record must contain 'lcmOperationType=instantiate'",
159 len(self
.db
.set_one
.call_args_list
) + 1,
160 "rollback mismatch with created/set items at database",
163 # test parameters with error
164 bad_id
= "88d90b0c-faff-4b9f-bccd-aaaaaaaaaaaa"
168 {"nsInstanceId": bad_id
},
170 HTTPStatus
.NOT_FOUND
,
171 ("not found", bad_id
),
174 # ({"vimAccountId": bad_id}, DbException, HTTPStatus.NOT_FOUND, ("not found", bad_id)), # TODO add "vim"
176 "bad member-vnf-index",
177 {"vnf.0.member-vnf-index": "k"},
179 HTTPStatus
.BAD_REQUEST
,
183 for message
, kwargs_
, expect_exc
, expect_code
, expect_text_list
in test_set
:
184 with self
.assertRaises(expect_exc
, msg
=message
) as e
:
185 self
.nslcmop_topic
.new(
188 indata
=deepcopy(indata
),
193 self
.assertTrue(e
.exception
.http_code
== expect_code
)
195 for expect_text
in expect_text_list
:
198 str(e
.exception
).lower(),
199 "Expected '{}' at exception text".format(expect_text
),
202 def test_check_ns_operation_action(self
):
203 nsrs
= self
.db
.get_list("nsrs")[0]
207 "member_vnf_index": "1",
209 "primitive": "touch",
210 "primitive_params": {"filename": "file"},
213 self
.nslcmop_topic
._check
_ns
_operation
(session
, nsrs
, "action", indata
)
215 indata_copy
= indata
.copy()
216 if k
== "primitive_params":
218 indata_copy
[k
] = "non_existing"
219 with self
.assertRaises(EngineException
) as exc_manager
:
220 self
.nslcmop_topic
._check
_ns
_operation
(
221 session
, nsrs
, "action", indata_copy
223 exc
= exc_manager
.exception
226 HTTPStatus
.BAD_REQUEST
,
227 "Engine exception bad http_code with {}".format(indata_copy
),
231 class TestNsLcmOpTopicWithMock(unittest
.TestCase
):
233 self
.db
= Mock(dbbase
.DbBase())
234 self
.fs
= Mock(FsBase())
235 self
.fs
.get_params
.return_value
= {"./fake/folder"}
236 self
.fs
.file_open
= mock_open()
237 self
.msg
= Mock(MsgBase())
239 self
.nslcmop_topic
= NsLcmOpTopic(self
.db
, self
.fs
, self
.msg
, None)
241 def test_get_vnfd_from_vnf_member_revision(self
):
242 test_vnfr
= yaml
.load(db_vnfrs_text
, Loader
=yaml
.Loader
)[0]
243 test_vnfd
= yaml
.load(db_vnfds_text
, Loader
=yaml
.Loader
)
244 self
.db
.get_one
.side_effect
= [test_vnfr
, test_vnfd
]
245 vnfr
= self
.nslcmop_topic
._get
_vnfd
_from
_vnf
_member
_index
("1", test_vnfr
['_id'])
246 self
.assertEqual(self
.db
.get_one
.call_args_list
[0][0][0], 'vnfrs', "Incorrect first DB lookup")
247 self
.assertEqual(self
.db
.get_one
.call_args_list
[1][0][0], 'vnfds', "Incorrect second DB lookup")
249 def test_get_vnfd_from_vnf_member_no_revision(self
):
250 test_vnfr
= yaml
.load(db_vnfrs_text
, Loader
=yaml
.Loader
)[0]
251 test_vnfr
['revision'] = 3
252 test_vnfd
= yaml
.load(db_vnfds_text
, Loader
=yaml
.Loader
)
253 self
.db
.get_one
.side_effect
= [test_vnfr
, test_vnfd
]
254 vnfr
= self
.nslcmop_topic
._get
_vnfd
_from
_vnf
_member
_index
("1", test_vnfr
['_id'])
255 self
.assertEqual(self
.db
.get_one
.call_args_list
[0][0][0], 'vnfrs', "Incorrect first DB lookup")
256 self
.assertEqual(self
.db
.get_one
.call_args_list
[1][0][0], 'vnfds_revisions', "Incorrect second DB lookup")
259 class TestNsrTopic(unittest
.TestCase
):
262 self
.fs
= Mock(FsBase())
263 self
.fs
.get_params
.return_value
= {"./fake/folder"}
264 self
.fs
.file_open
= mock_open()
265 self
.msg
= Mock(MsgBase())
267 self
.nsr_topic
= NsrTopic(self
.db
, self
.fs
, self
.msg
, None)
268 self
.nsr_topic
.check_quota
= Mock(return_value
=None) # skip quota
271 "vim_accounts", yaml
.load(db_vim_accounts_text
, Loader
=yaml
.Loader
)
273 self
.db
.create_list("nsds", yaml
.load(db_nsds_text
, Loader
=yaml
.Loader
))
274 self
.db
.create_list("vnfds", yaml
.load(db_vnfds_text
, Loader
=yaml
.Loader
))
275 self
.db
.create
= Mock(return_value
="created_id")
276 self
.nsd
= self
.db
.get_list("nsds")[0]
277 self
.nsd_id
= self
.nsd
["_id"]
278 self
.nsd_project
= self
.nsd
["_admin"]["projects_read"][0]
280 self
.vim
= self
.db
.get_list("vim_accounts")[0]
281 self
.vim_id
= self
.vim
["_id"]
283 def test_create(self
):
288 "project_id": [self
.nsd_project
],
292 "nsdId": self
.nsd_id
,
294 "vimAccountId": self
.vim_id
,
295 "additionalParamsForVnf": [
297 "member-vnf-index": "hackfest_vnf1",
298 "additionalParams": {"touch_filename": "file"},
301 "member-vnf-index": "hackfest_vnf2",
302 "additionalParams": {"touch_filename": "file"},
310 rollback
, session
, indata
=indata
, kwargs
=None, headers
=headers
313 # check vnfrs and nsrs created in whatever order
317 for _call
in self
.db
.create
.call_args_list
:
318 assert len(_call
[0]) >= 2, "called db.create with few parameters"
319 created_item
= _call
[0][1]
320 if _call
[0][0] == "vnfrs":
321 created_vnfrs
.append(created_item
)
323 "member-vnf-index-ref",
325 "Created item must contain member-vnf-index-ref section",
330 created_item
["nsr-id-ref"],
331 "bad reference id from vnfr to nsr",
334 nsr_id
= created_item
["nsr-id-ref"]
336 elif _call
[0][0] == "nsrs":
337 created_nsrs
.append(created_item
)
340 nsr_id
, created_item
["_id"], "bad reference id from vnfr to nsr"
343 nsr_id
= created_item
["_id"]
345 assert True, "created an unknown record {} at database".format(
350 created_item
["_admin"].get("projects_read"),
351 "Database record must contain '_amdin.projects_read'",
355 created_item
["_admin"],
356 "Database record must contain '_admin.created'",
359 created_item
["_admin"]["nsState"] == "NOT_INSTANTIATED",
360 "Database record must contain '_admin.nstate=NOT INSTANTIATE'",
364 len(created_vnfrs
), 2, "created a mismatch number of vnfr at database"
367 len(created_nsrs
), 1, "Only one nsrs must be created at database"
371 len(created_vnfrs
) + 1,
372 "rollback mismatch with created items at database",
375 # test parameters with error
376 bad_id
= "88d90b0c-faff-4b9f-bccd-aaaaaaaaaaaa"
383 HTTPStatus
.NOT_FOUND
,
384 ("not found", bad_id
),
386 # ({"vimAccountId": bad_id}, DbException, HTTPStatus.NOT_FOUND, ("not found", bad_id)), # TODO add "vim"
388 "additional params not supply",
389 {"additionalParamsForVnf.0.member-vnf-index": "k"},
391 HTTPStatus
.BAD_REQUEST
,
395 for message
, kwargs_
, expect_exc
, expect_code
, expect_text_list
in test_set
:
396 with self
.assertRaises(expect_exc
, msg
=message
) as e
:
400 indata
=deepcopy(indata
),
405 self
.assertTrue(e
.exception
.http_code
== expect_code
)
407 for expect_text
in expect_text_list
:
408 self
.assertIn(expect_text
, str(e
.exception
).lower(),
409 "Expected '{}' at exception text".format(expect_text
))
411 def test_show_instance(self
):
412 session
= {"force": False, "admin": False, "public": False, "project_id": [self
.nsd_project
], "method": "write"}
414 for refresh_status
in ("true", "false"):
415 self
.db
.create_list("nsrs", yaml
.load(db_nsrs_text
, Loader
=yaml
.Loader
))
416 actual_nsr
= self
.db
.get_list("nsrs")[0]
417 nsr_id
= actual_nsr
["_id"]
418 filter_q
['vcaStatus-refresh'] = refresh_status
419 expected_nsr
= self
.nsr_topic
.show(session
, nsr_id
, filter_q
=filter_q
)
420 self
.nsr_topic
.delete(session
, nsr_id
)
421 actual_nsr
.pop("_admin")
422 expected_nsr
.pop("_admin")
423 self
.assertEqual(expected_nsr
, actual_nsr
, "Database nsr and show() nsr do not match.")
425 def test_vca_status_refresh(self
):
426 session
= {"force": False, "admin": False, "public": False, "project_id": [self
.nsd_project
], "method": "write"}
427 filter_q
= {'vcaStatus-refresh': 'true'}
429 self
.db
.create_list("nsrs", yaml
.load(db_nsrs_text
, Loader
=yaml
.Loader
))
430 nsr
= self
.db
.get_list("nsrs")[0]
432 # When vcaStatus-refresh is true
433 filter_q
['vcaStatus-refresh'] = "true"
434 self
.nsr_topic
.vca_status_refresh(session
, nsr
, filter_q
)
435 msg_args
= self
.msg
.write
.call_args
[0]
436 self
.assertEqual(msg_args
[1], "vca_status_refresh", "Wrong message action")
437 self
.assertGreater(nsr
["_admin"]["modified"], time() - time_delta
)
439 # When vcaStatus-refresh is false but modified time is within threshold
440 filter_q
['vcaStatus-refresh'] = "false"
442 nsr
["_admin"]["modified"] = time_now
443 self
.nsr_topic
.vca_status_refresh(session
, nsr
, filter_q
)
444 msg_args
= self
.msg
.write
.call_args
[1]
445 self
.assertEqual(msg_args
, {}, "Message should not be sent.")
446 self
.assertEqual(nsr
["_admin"]["modified"], time_now
, "Modified time should not be changed.")
448 # When vcaStatus-refresh is false but modified time is less than threshold
449 filter_q
['vcaStatus-refresh'] = "false"
450 nsr
["_admin"]["modified"] = time() - (2*time_delta
)
451 self
.nsr_topic
.vca_status_refresh(session
, nsr
, filter_q
)
452 msg_args
= self
.msg
.write
.call_args
[0]
453 self
.assertEqual(msg_args
[1], "vca_status_refresh", "Wrong message action")
454 self
.nsr_topic
.delete(session
, nsr
["_id"])
455 self
.assertGreater(nsr
["_admin"]["modified"], time() - time_delta
, "Modified time is not changed.")
457 def test_delete_ns(self
):
458 self
.db
.create_list("nsrs", yaml
.load(db_nsrs_text
, Loader
=yaml
.Loader
))
459 self
.nsr
= self
.db
.get_list("nsrs")[0]
460 self
.nsr_id
= self
.nsr
["_id"]
461 self
.db_set_one
= self
.db
.set_one
462 p_id
= self
.nsd_project
469 "project_id": [p_id
],
476 "project_id": [p_other
],
486 with self
.subTest(i
=1, t
="Normal Deletion"):
487 self
.db
.del_one
= Mock()
488 self
.db
.set_one
= Mock()
489 self
.nsr_topic
.delete(session
, self
.nsr_id
)
491 db_args_ro_nsrs
= self
.db
.del_one
.call_args_list
[1][0]
492 db_args
= self
.db
.del_one
.call_args_list
[0][0]
493 msg_args
= self
.msg
.write
.call_args
[0]
495 msg_args
[0], self
.nsr_topic
.topic_msg
, "Wrong message topic"
497 self
.assertEqual(msg_args
[1], "deleted", "Wrong message action")
498 self
.assertEqual(msg_args
[2], {"_id": self
.nsr_id
}, "Wrong message content")
499 self
.assertEqual(db_args_ro_nsrs
[0], "ro_nsrs", "Wrong DB topic")
500 self
.assertEqual(db_args
[0], self
.nsr_topic
.topic
, "Wrong DB topic")
501 self
.assertEqual(db_args
[1]["_id"], self
.nsr_id
, "Wrong DB ID")
503 db_args
[1]["_admin.projects_read.cont"], [p_id
], "Wrong DB filter"
505 self
.db
.set_one
.assert_not_called()
506 fs_del_calls
= self
.fs
.file_delete
.call_args_list
507 self
.assertEqual(fs_del_calls
[0][0][0], self
.nsr_id
, "Wrong FS file id")
508 with self
.subTest(i
=2, t
="No delete because referenced by other project"):
511 {"_id": self
.nsr_id
},
514 "_admin.projects_read": p_other
,
515 "_admin.projects_write": p_other
,
518 self
.db
.del_one
.reset_mock()
519 self
.db
.set_one
.reset_mock()
520 self
.msg
.write
.reset_mock()
521 self
.fs
.file_delete
.reset_mock()
523 self
.nsr_topic
.delete(session2
, self
.nsr_id
)
524 self
.db
.del_one
.assert_not_called()
525 self
.msg
.write
.assert_not_called()
526 db_s1_args
= self
.db
.set_one
.call_args
527 self
.assertEqual(db_s1_args
[0][0], self
.nsr_topic
.topic
, "Wrong DB topic")
528 self
.assertEqual(db_s1_args
[0][1]["_id"], self
.nsr_id
, "Wrong DB ID")
530 db_s1_args
[1]["update_dict"], "Wrong DB update dictionary"
533 db_s1_args
[1]["pull_list"],
534 {"_admin.projects_read": [p_other
], "_admin.projects_write": [p_other
]},
535 "Wrong DB pull_list dictionary",
537 self
.fs
.file_delete
.assert_not_called()
538 with self
.subTest(i
=4, t
="Delete with force and admin"):
539 self
.db
.del_one
.reset_mock()
540 self
.db
.set_one
.reset_mock()
541 self
.msg
.write
.reset_mock()
542 self
.fs
.file_delete
.reset_mock()
543 self
.nsr_topic
.delete(session_force
, self
.nsr_id
)
545 db_args_ro_nsrs
= self
.db
.del_one
.call_args_list
[1][0]
546 db_args
= self
.db
.del_one
.call_args_list
[0][0]
547 msg_args
= self
.msg
.write
.call_args
[0]
549 msg_args
[0], self
.nsr_topic
.topic_msg
, "Wrong message topic"
551 self
.assertEqual(msg_args
[1], "deleted", "Wrong message action")
552 self
.assertEqual(msg_args
[2], {"_id": self
.nsr_id
}, "Wrong message content")
553 self
.assertEqual(db_args_ro_nsrs
[0], "ro_nsrs", "Wrong DB topic")
554 self
.assertEqual(db_args
[0], self
.nsr_topic
.topic
, "Wrong DB topic")
555 self
.assertEqual(db_args
[1]["_id"], self
.nsr_id
, "Wrong DB ID")
556 self
.db
.set_one
.assert_not_called()
557 fs_del_calls
= self
.fs
.file_delete
.call_args_list
558 self
.assertEqual(fs_del_calls
[0][0][0], self
.nsr_id
, "Wrong FS file id")
559 with self
.subTest(i
=3, t
="Conflict on Delete - NS in INSTANTIATED state"):
562 {"_id": self
.nsr_id
},
563 {"_admin.nsState": "INSTANTIATED"},
565 "_admin.projects_read": p_other
,
566 "_admin.projects_write": p_other
,
569 self
.db
.del_one
.reset_mock()
570 self
.db
.set_one
.reset_mock()
571 self
.msg
.write
.reset_mock()
572 self
.fs
.file_delete
.reset_mock()
574 with self
.assertRaises(
575 EngineException
, msg
="Accepted NSR with nsState INSTANTIATED"
577 self
.nsr_topic
.delete(session
, self
.nsr_id
)
579 e
.exception
.http_code
, HTTPStatus
.CONFLICT
, "Wrong HTTP status code"
581 self
.assertIn("INSTANTIATED", str(e
.exception
), "Wrong exception text")
582 # TODOD with self.subTest(i=3, t='Conflict on Delete - NS in use by NSI'):
584 with self
.subTest(i
=4, t
="Non-existent NS"):
585 self
.db
.del_one
.reset_mock()
586 self
.db
.set_one
.reset_mock()
587 self
.msg
.write
.reset_mock()
588 self
.fs
.file_delete
.reset_mock()
589 excp_msg
= "Not found"
590 with self
.assertRaises(
591 DbException
, msg
="Accepted non-existent NSD ID"
593 self
.nsr_topic
.delete(session2
, "other_id")
595 e
.exception
.http_code
, HTTPStatus
.NOT_FOUND
, "Wrong HTTP status code"
597 self
.assertIn(excp_msg
, str(e
.exception
), "Wrong exception text")
598 self
.assertIn("other_id", str(e
.exception
), "Wrong exception text")