cbb80ef7b965886e9e1159b441575e11de65b363
[osm/NBI.git] / osm_nbi / tests / test_instance_topics.py
1 #
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
5 #
6 # http://www.apache.org/licenses/LICENSE-2.0
7 #
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
12 # under the License.
13 #
14 # For those usages not covered by the Apache License, Version 2.0 please
15 # contact: esousa@whitestack.com or alfonso.tiernosepulveda@telefonica.com
16 ##
17
18 import unittest
19 from time import time
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 (
30 db_vim_accounts_text,
31 db_nsds_text,
32 db_vnfds_text,
33 db_nsrs_text,
34 db_vnfrs_text,
35 )
36 from copy import deepcopy
37 import yaml
38
39
40 class TestNsLcmOpTopic(unittest.TestCase):
41 def setUp(self):
42 self.db = DbMemory()
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())
47 # create class
48 self.nslcmop_topic = NsLcmOpTopic(self.db, self.fs, self.msg, None)
49 self.nslcmop_topic.check_quota = Mock(return_value=None) # skip quota
50
51 self.db.create_list(
52 "vim_accounts", yaml.load(db_vim_accounts_text, Loader=yaml.Loader)
53 )
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]
65
66 self.vim = self.db.get_list("vim_accounts")[0]
67 self.vim_id = self.vim["_id"]
68
69 def test_create_instantiate(self):
70 session = {
71 "force": False,
72 "admin": False,
73 "public": False,
74 "project_id": [self.nsr_project],
75 "method": "write",
76 }
77 indata = {
78 "nsdId": self.nsd_id,
79 "nsInstanceId": self.nsr_id,
80 "nsName": "name",
81 "vimAccountId": self.vim_id,
82 "additionalParamsForVnf": [
83 {
84 "member-vnf-index": "1",
85 "additionalParams": {"touch_filename": "file"},
86 },
87 {
88 "member-vnf-index": "2",
89 "additionalParams": {"touch_filename": "file"},
90 },
91 ],
92 "vnf": [
93 {
94 "member-vnf-index": "1",
95 "vdu": [
96 {
97 "id": "dataVM",
98 "interface": [
99 {
100 "name": "dataVM-eth0",
101 "ip-address": "10.11.12.13",
102 "floating-ip-required": True,
103 }
104 ],
105 }
106 ],
107 "internal-vld": [
108 {"name": "internal", "vim-network-id": "vim-net-id"}
109 ],
110 }
111 ],
112 "lcmOperationType": "instantiate",
113 }
114 rollback = []
115 headers = {}
116
117 nslcmop_id, _ = self.nslcmop_topic.new(
118 rollback, session, indata=deepcopy(indata), kwargs=None, headers=headers
119 )
120
121 # check nslcmop is created at database
122 self.assertEqual(
123 self.db.create.call_count,
124 1,
125 "database create not called, or called more than once",
126 )
127 _call = self.db.create.call_args_list[0]
128 self.assertEqual(
129 _call[0][0], "nslcmops", "must be create a nslcmops entry at database"
130 )
131
132 created_nslcmop = _call[0][1]
133 self.assertEqual(
134 nslcmop_id,
135 created_nslcmop["_id"],
136 "mismatch between return id and database '_id'",
137 )
138 self.assertEqual(
139 self.nsr_id,
140 created_nslcmop["nsInstanceId"],
141 "bad reference id from nslcmop to nsr",
142 )
143 self.assertTrue(
144 created_nslcmop["_admin"].get("projects_read"),
145 "Database record must contain '_amdin.projects_read'",
146 )
147 self.assertIn(
148 "created",
149 created_nslcmop["_admin"],
150 "Database record must contain '_admin.created'",
151 )
152 self.assertTrue(
153 created_nslcmop["lcmOperationType"] == "instantiate",
154 "Database record must contain 'lcmOperationType=instantiate'",
155 )
156
157 self.assertEqual(
158 len(rollback),
159 len(self.db.set_one.call_args_list) + 1,
160 "rollback mismatch with created/set items at database",
161 )
162
163 # test parameters with error
164 bad_id = "88d90b0c-faff-4b9f-bccd-aaaaaaaaaaaa"
165 test_set = (
166 (
167 "nsr not found",
168 {"nsInstanceId": bad_id},
169 DbException,
170 HTTPStatus.NOT_FOUND,
171 ("not found", bad_id),
172 ),
173 # TODO add "nsd"
174 # ({"vimAccountId": bad_id}, DbException, HTTPStatus.NOT_FOUND, ("not found", bad_id)), # TODO add "vim"
175 (
176 "bad member-vnf-index",
177 {"vnf.0.member-vnf-index": "k"},
178 EngineException,
179 HTTPStatus.BAD_REQUEST,
180 ("k",),
181 ),
182 )
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(
186 rollback,
187 session,
188 indata=deepcopy(indata),
189 kwargs=kwargs_,
190 headers=headers,
191 )
192 if expect_code:
193 self.assertTrue(e.exception.http_code == expect_code)
194 if expect_text_list:
195 for expect_text in expect_text_list:
196 self.assertIn(
197 expect_text,
198 str(e.exception).lower(),
199 "Expected '{}' at exception text".format(expect_text),
200 )
201
202 def test_check_ns_operation_action(self):
203 nsrs = self.db.get_list("nsrs")[0]
204 session = {}
205
206 indata = {
207 "member_vnf_index": "1",
208 "vdu_id": None,
209 "primitive": "touch",
210 "primitive_params": {"filename": "file"},
211 }
212
213 self.nslcmop_topic._check_ns_operation(session, nsrs, "action", indata)
214 for k in indata:
215 indata_copy = indata.copy()
216 if k == "primitive_params":
217 continue
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
222 )
223 exc = exc_manager.exception
224 self.assertEqual(
225 exc.http_code,
226 HTTPStatus.BAD_REQUEST,
227 "Engine exception bad http_code with {}".format(indata_copy),
228 )
229
230
231 class TestNsLcmOpTopicWithMock(unittest.TestCase):
232 def setUp(self):
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())
238 # create class
239 self.nslcmop_topic = NsLcmOpTopic(self.db, self.fs, self.msg, None)
240
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")
248
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")
257
258
259 class TestNsrTopic(unittest.TestCase):
260 def setUp(self):
261 self.db = DbMemory()
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())
266 # create class
267 self.nsr_topic = NsrTopic(self.db, self.fs, self.msg, None)
268 self.nsr_topic.check_quota = Mock(return_value=None) # skip quota
269
270 self.db.create_list(
271 "vim_accounts", yaml.load(db_vim_accounts_text, Loader=yaml.Loader)
272 )
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]
279
280 self.vim = self.db.get_list("vim_accounts")[0]
281 self.vim_id = self.vim["_id"]
282
283 def test_create(self):
284 session = {
285 "force": False,
286 "admin": False,
287 "public": False,
288 "project_id": [self.nsd_project],
289 "method": "write",
290 }
291 indata = {
292 "nsdId": self.nsd_id,
293 "nsName": "name",
294 "vimAccountId": self.vim_id,
295 "additionalParamsForVnf": [
296 {
297 "member-vnf-index": "hackfest_vnf1",
298 "additionalParams": {"touch_filename": "file"},
299 },
300 {
301 "member-vnf-index": "hackfest_vnf2",
302 "additionalParams": {"touch_filename": "file"},
303 },
304 ],
305 }
306 rollback = []
307 headers = {}
308
309 self.nsr_topic.new(
310 rollback, session, indata=indata, kwargs=None, headers=headers
311 )
312
313 # check vnfrs and nsrs created in whatever order
314 created_vnfrs = []
315 created_nsrs = []
316 nsr_id = None
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)
322 self.assertIn(
323 "member-vnf-index-ref",
324 created_item,
325 "Created item must contain member-vnf-index-ref section",
326 )
327 if nsr_id:
328 self.assertEqual(
329 nsr_id,
330 created_item["nsr-id-ref"],
331 "bad reference id from vnfr to nsr",
332 )
333 else:
334 nsr_id = created_item["nsr-id-ref"]
335
336 elif _call[0][0] == "nsrs":
337 created_nsrs.append(created_item)
338 if nsr_id:
339 self.assertEqual(
340 nsr_id, created_item["_id"], "bad reference id from vnfr to nsr"
341 )
342 else:
343 nsr_id = created_item["_id"]
344 else:
345 assert True, "created an unknown record {} at database".format(
346 _call[0][0]
347 )
348
349 self.assertTrue(
350 created_item["_admin"].get("projects_read"),
351 "Database record must contain '_amdin.projects_read'",
352 )
353 self.assertIn(
354 "created",
355 created_item["_admin"],
356 "Database record must contain '_admin.created'",
357 )
358 self.assertTrue(
359 created_item["_admin"]["nsState"] == "NOT_INSTANTIATED",
360 "Database record must contain '_admin.nstate=NOT INSTANTIATE'",
361 )
362
363 self.assertEqual(
364 len(created_vnfrs), 2, "created a mismatch number of vnfr at database"
365 )
366 self.assertEqual(
367 len(created_nsrs), 1, "Only one nsrs must be created at database"
368 )
369 self.assertEqual(
370 len(rollback),
371 len(created_vnfrs) + 1,
372 "rollback mismatch with created items at database",
373 )
374
375 # test parameters with error
376 bad_id = "88d90b0c-faff-4b9f-bccd-aaaaaaaaaaaa"
377 test_set = (
378 # TODO add "nsd"
379 (
380 "nsd not found",
381 {"nsdId": bad_id},
382 DbException,
383 HTTPStatus.NOT_FOUND,
384 ("not found", bad_id),
385 ),
386 # ({"vimAccountId": bad_id}, DbException, HTTPStatus.NOT_FOUND, ("not found", bad_id)), # TODO add "vim"
387 (
388 "additional params not supply",
389 {"additionalParamsForVnf.0.member-vnf-index": "k"},
390 EngineException,
391 HTTPStatus.BAD_REQUEST,
392 None,
393 ),
394 )
395 for message, kwargs_, expect_exc, expect_code, expect_text_list in test_set:
396 with self.assertRaises(expect_exc, msg=message) as e:
397 self.nsr_topic.new(
398 rollback,
399 session,
400 indata=deepcopy(indata),
401 kwargs=kwargs_,
402 headers=headers,
403 )
404 if expect_code:
405 self.assertTrue(e.exception.http_code == expect_code)
406 if expect_text_list:
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))
410
411 def test_show_instance(self):
412 session = {"force": False, "admin": False, "public": False, "project_id": [self.nsd_project], "method": "write"}
413 filter_q = {}
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.")
424
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'}
428 time_delta = 120
429 self.db.create_list("nsrs", yaml.load(db_nsrs_text, Loader=yaml.Loader))
430 nsr = self.db.get_list("nsrs")[0]
431
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)
438
439 # When vcaStatus-refresh is false but modified time is within threshold
440 filter_q['vcaStatus-refresh'] = "false"
441 time_now = time()
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.")
447
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.")
456
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
463 p_other = "other_p"
464
465 session = {
466 "force": False,
467 "admin": False,
468 "public": None,
469 "project_id": [p_id],
470 "method": "delete",
471 }
472 session2 = {
473 "force": False,
474 "admin": False,
475 "public": None,
476 "project_id": [p_other],
477 "method": "delete",
478 }
479 session_force = {
480 "force": True,
481 "admin": True,
482 "public": None,
483 "project_id": [],
484 "method": "delete",
485 }
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)
490
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]
494 self.assertEqual(
495 msg_args[0], self.nsr_topic.topic_msg, "Wrong message topic"
496 )
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")
502 self.assertEqual(
503 db_args[1]["_admin.projects_read.cont"], [p_id], "Wrong DB filter"
504 )
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"):
509 self.db_set_one(
510 "nsrs",
511 {"_id": self.nsr_id},
512 update_dict=None,
513 push={
514 "_admin.projects_read": p_other,
515 "_admin.projects_write": p_other,
516 },
517 )
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()
522
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")
529 self.assertIsNone(
530 db_s1_args[1]["update_dict"], "Wrong DB update dictionary"
531 )
532 self.assertEqual(
533 db_s1_args[1]["pull_list"],
534 {"_admin.projects_read": [p_other], "_admin.projects_write": [p_other]},
535 "Wrong DB pull_list dictionary",
536 )
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)
544
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]
548 self.assertEqual(
549 msg_args[0], self.nsr_topic.topic_msg, "Wrong message topic"
550 )
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"):
560 self.db_set_one(
561 "nsrs",
562 {"_id": self.nsr_id},
563 {"_admin.nsState": "INSTANTIATED"},
564 pull={
565 "_admin.projects_read": p_other,
566 "_admin.projects_write": p_other,
567 },
568 )
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()
573
574 with self.assertRaises(
575 EngineException, msg="Accepted NSR with nsState INSTANTIATED"
576 ) as e:
577 self.nsr_topic.delete(session, self.nsr_id)
578 self.assertEqual(
579 e.exception.http_code, HTTPStatus.CONFLICT, "Wrong HTTP status code"
580 )
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'):
583
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"
592 ) as e:
593 self.nsr_topic.delete(session2, "other_id")
594 self.assertEqual(
595 e.exception.http_code, HTTPStatus.NOT_FOUND, "Wrong HTTP status code"
596 )
597 self.assertIn(excp_msg, str(e.exception), "Wrong exception text")
598 self.assertIn("other_id", str(e.exception), "Wrong exception text")
599 return