Feature-9904: Enhancing NG-UI to enable Juju operational view dashboard
[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 http import HTTPStatus
27 from osm_nbi.instance_topics import NsLcmOpTopic, NsrTopic
28 from osm_nbi.tests.test_db_descriptors import (
29 db_vim_accounts_text,
30 db_nsds_text,
31 db_vnfds_text,
32 db_nsrs_text,
33 db_vnfrs_text,
34 )
35 from copy import deepcopy
36 import yaml
37
38
39 class TestNsLcmOpTopic(unittest.TestCase):
40 def setUp(self):
41 self.db = DbMemory()
42 self.fs = Mock(FsBase())
43 self.fs.get_params.return_value = {"./fake/folder"}
44 self.fs.file_open = mock_open()
45 self.msg = Mock(MsgBase())
46 # create class
47 self.nslcmop_topic = NsLcmOpTopic(self.db, self.fs, self.msg, None)
48 self.nslcmop_topic.check_quota = Mock(return_value=None) # skip quota
49
50 self.db.create_list(
51 "vim_accounts", yaml.load(db_vim_accounts_text, Loader=yaml.Loader)
52 )
53 self.db.create_list("nsds", yaml.load(db_nsds_text, Loader=yaml.Loader))
54 self.db.create_list("vnfds", yaml.load(db_vnfds_text, Loader=yaml.Loader))
55 self.db.create_list("vnfrs", yaml.load(db_vnfrs_text, Loader=yaml.Loader))
56 self.db.create_list("nsrs", yaml.load(db_nsrs_text, Loader=yaml.Loader))
57 self.db.create = Mock(return_value="created_id")
58 self.db.set_one = Mock(return_value={"updated": 1})
59 self.nsd = self.db.get_list("nsds")[0]
60 self.nsd_id = self.nsd["_id"]
61 self.nsr = self.db.get_list("nsrs")[0]
62 self.nsr_id = self.nsr["_id"]
63 self.nsr_project = self.nsr["_admin"]["projects_read"][0]
64
65 self.vim = self.db.get_list("vim_accounts")[0]
66 self.vim_id = self.vim["_id"]
67
68 def test_create_instantiate(self):
69 session = {
70 "force": False,
71 "admin": False,
72 "public": False,
73 "project_id": [self.nsr_project],
74 "method": "write",
75 }
76 indata = {
77 "nsdId": self.nsd_id,
78 "nsInstanceId": self.nsr_id,
79 "nsName": "name",
80 "vimAccountId": self.vim_id,
81 "additionalParamsForVnf": [
82 {
83 "member-vnf-index": "1",
84 "additionalParams": {"touch_filename": "file"},
85 },
86 {
87 "member-vnf-index": "2",
88 "additionalParams": {"touch_filename": "file"},
89 },
90 ],
91 "vnf": [
92 {
93 "member-vnf-index": "1",
94 "vdu": [
95 {
96 "id": "dataVM",
97 "interface": [
98 {
99 "name": "dataVM-eth0",
100 "ip-address": "10.11.12.13",
101 "floating-ip-required": True,
102 }
103 ],
104 }
105 ],
106 "internal-vld": [
107 {"name": "internal", "vim-network-id": "vim-net-id"}
108 ],
109 }
110 ],
111 "lcmOperationType": "instantiate",
112 }
113 rollback = []
114 headers = {}
115
116 nslcmop_id, _ = self.nslcmop_topic.new(
117 rollback, session, indata=deepcopy(indata), kwargs=None, headers=headers
118 )
119
120 # check nslcmop is created at database
121 self.assertEqual(
122 self.db.create.call_count,
123 1,
124 "database create not called, or called more than once",
125 )
126 _call = self.db.create.call_args_list[0]
127 self.assertEqual(
128 _call[0][0], "nslcmops", "must be create a nslcmops entry at database"
129 )
130
131 created_nslcmop = _call[0][1]
132 self.assertEqual(
133 nslcmop_id,
134 created_nslcmop["_id"],
135 "mismatch between return id and database '_id'",
136 )
137 self.assertEqual(
138 self.nsr_id,
139 created_nslcmop["nsInstanceId"],
140 "bad reference id from nslcmop to nsr",
141 )
142 self.assertTrue(
143 created_nslcmop["_admin"].get("projects_read"),
144 "Database record must contain '_amdin.projects_read'",
145 )
146 self.assertIn(
147 "created",
148 created_nslcmop["_admin"],
149 "Database record must contain '_admin.created'",
150 )
151 self.assertTrue(
152 created_nslcmop["lcmOperationType"] == "instantiate",
153 "Database record must contain 'lcmOperationType=instantiate'",
154 )
155
156 self.assertEqual(
157 len(rollback),
158 len(self.db.set_one.call_args_list) + 1,
159 "rollback mismatch with created/set items at database",
160 )
161
162 # test parameters with error
163 bad_id = "88d90b0c-faff-4b9f-bccd-aaaaaaaaaaaa"
164 test_set = (
165 (
166 "nsr not found",
167 {"nsInstanceId": bad_id},
168 DbException,
169 HTTPStatus.NOT_FOUND,
170 ("not found", bad_id),
171 ),
172 # TODO add "nsd"
173 # ({"vimAccountId": bad_id}, DbException, HTTPStatus.NOT_FOUND, ("not found", bad_id)), # TODO add "vim"
174 (
175 "bad member-vnf-index",
176 {"vnf.0.member-vnf-index": "k"},
177 EngineException,
178 HTTPStatus.BAD_REQUEST,
179 ("k",),
180 ),
181 )
182 for message, kwargs_, expect_exc, expect_code, expect_text_list in test_set:
183 with self.assertRaises(expect_exc, msg=message) as e:
184 self.nslcmop_topic.new(
185 rollback,
186 session,
187 indata=deepcopy(indata),
188 kwargs=kwargs_,
189 headers=headers,
190 )
191 if expect_code:
192 self.assertTrue(e.exception.http_code == expect_code)
193 if expect_text_list:
194 for expect_text in expect_text_list:
195 self.assertIn(
196 expect_text,
197 str(e.exception).lower(),
198 "Expected '{}' at exception text".format(expect_text),
199 )
200
201 def test_check_ns_operation_action(self):
202 nsrs = self.db.get_list("nsrs")[0]
203 session = {}
204
205 indata = {
206 "member_vnf_index": "1",
207 "vdu_id": None,
208 "primitive": "touch",
209 "primitive_params": {"filename": "file"},
210 }
211
212 self.nslcmop_topic._check_ns_operation(session, nsrs, "action", indata)
213 for k in indata:
214 indata_copy = indata.copy()
215 if k == "primitive_params":
216 continue
217 indata_copy[k] = "non_existing"
218 with self.assertRaises(EngineException) as exc_manager:
219 self.nslcmop_topic._check_ns_operation(
220 session, nsrs, "action", indata_copy
221 )
222 exc = exc_manager.exception
223 self.assertEqual(
224 exc.http_code,
225 HTTPStatus.BAD_REQUEST,
226 "Engine exception bad http_code with {}".format(indata_copy),
227 )
228
229
230 class TestNsrTopic(unittest.TestCase):
231 def setUp(self):
232 self.db = DbMemory()
233 self.fs = Mock(FsBase())
234 self.fs.get_params.return_value = {"./fake/folder"}
235 self.fs.file_open = mock_open()
236 self.msg = Mock(MsgBase())
237 # create class
238 self.nsr_topic = NsrTopic(self.db, self.fs, self.msg, None)
239 self.nsr_topic.check_quota = Mock(return_value=None) # skip quota
240
241 self.db.create_list(
242 "vim_accounts", yaml.load(db_vim_accounts_text, Loader=yaml.Loader)
243 )
244 self.db.create_list("nsds", yaml.load(db_nsds_text, Loader=yaml.Loader))
245 self.db.create_list("vnfds", yaml.load(db_vnfds_text, Loader=yaml.Loader))
246 self.db.create = Mock(return_value="created_id")
247 self.nsd = self.db.get_list("nsds")[0]
248 self.nsd_id = self.nsd["_id"]
249 self.nsd_project = self.nsd["_admin"]["projects_read"][0]
250
251 self.vim = self.db.get_list("vim_accounts")[0]
252 self.vim_id = self.vim["_id"]
253
254 def test_create(self):
255 session = {
256 "force": False,
257 "admin": False,
258 "public": False,
259 "project_id": [self.nsd_project],
260 "method": "write",
261 }
262 indata = {
263 "nsdId": self.nsd_id,
264 "nsName": "name",
265 "vimAccountId": self.vim_id,
266 "additionalParamsForVnf": [
267 {
268 "member-vnf-index": "hackfest_vnf1",
269 "additionalParams": {"touch_filename": "file"},
270 },
271 {
272 "member-vnf-index": "hackfest_vnf2",
273 "additionalParams": {"touch_filename": "file"},
274 },
275 ],
276 }
277 rollback = []
278 headers = {}
279
280 self.nsr_topic.new(
281 rollback, session, indata=indata, kwargs=None, headers=headers
282 )
283
284 # check vnfrs and nsrs created in whatever order
285 created_vnfrs = []
286 created_nsrs = []
287 nsr_id = None
288 for _call in self.db.create.call_args_list:
289 assert len(_call[0]) >= 2, "called db.create with few parameters"
290 created_item = _call[0][1]
291 if _call[0][0] == "vnfrs":
292 created_vnfrs.append(created_item)
293 self.assertIn(
294 "member-vnf-index-ref",
295 created_item,
296 "Created item must contain member-vnf-index-ref section",
297 )
298 if nsr_id:
299 self.assertEqual(
300 nsr_id,
301 created_item["nsr-id-ref"],
302 "bad reference id from vnfr to nsr",
303 )
304 else:
305 nsr_id = created_item["nsr-id-ref"]
306
307 elif _call[0][0] == "nsrs":
308 created_nsrs.append(created_item)
309 if nsr_id:
310 self.assertEqual(
311 nsr_id, created_item["_id"], "bad reference id from vnfr to nsr"
312 )
313 else:
314 nsr_id = created_item["_id"]
315 else:
316 assert True, "created an unknown record {} at database".format(
317 _call[0][0]
318 )
319
320 self.assertTrue(
321 created_item["_admin"].get("projects_read"),
322 "Database record must contain '_amdin.projects_read'",
323 )
324 self.assertIn(
325 "created",
326 created_item["_admin"],
327 "Database record must contain '_admin.created'",
328 )
329 self.assertTrue(
330 created_item["_admin"]["nsState"] == "NOT_INSTANTIATED",
331 "Database record must contain '_admin.nstate=NOT INSTANTIATE'",
332 )
333
334 self.assertEqual(
335 len(created_vnfrs), 2, "created a mismatch number of vnfr at database"
336 )
337 self.assertEqual(
338 len(created_nsrs), 1, "Only one nsrs must be created at database"
339 )
340 self.assertEqual(
341 len(rollback),
342 len(created_vnfrs) + 1,
343 "rollback mismatch with created items at database",
344 )
345
346 # test parameters with error
347 bad_id = "88d90b0c-faff-4b9f-bccd-aaaaaaaaaaaa"
348 test_set = (
349 # TODO add "nsd"
350 (
351 "nsd not found",
352 {"nsdId": bad_id},
353 DbException,
354 HTTPStatus.NOT_FOUND,
355 ("not found", bad_id),
356 ),
357 # ({"vimAccountId": bad_id}, DbException, HTTPStatus.NOT_FOUND, ("not found", bad_id)), # TODO add "vim"
358 (
359 "additional params not supply",
360 {"additionalParamsForVnf.0.member-vnf-index": "k"},
361 EngineException,
362 HTTPStatus.BAD_REQUEST,
363 None,
364 ),
365 )
366 for message, kwargs_, expect_exc, expect_code, expect_text_list in test_set:
367 with self.assertRaises(expect_exc, msg=message) as e:
368 self.nsr_topic.new(
369 rollback,
370 session,
371 indata=deepcopy(indata),
372 kwargs=kwargs_,
373 headers=headers,
374 )
375 if expect_code:
376 self.assertTrue(e.exception.http_code == expect_code)
377 if expect_text_list:
378 for expect_text in expect_text_list:
379 self.assertIn(expect_text, str(e.exception).lower(),
380 "Expected '{}' at exception text".format(expect_text))
381
382 def test_show_instance(self):
383 session = {"force": False, "admin": False, "public": False, "project_id": [self.nsd_project], "method": "write"}
384 filter_q = {}
385 for refresh_status in ("true", "false"):
386 self.db.create_list("nsrs", yaml.load(db_nsrs_text, Loader=yaml.Loader))
387 actual_nsr = self.db.get_list("nsrs")[0]
388 nsr_id = actual_nsr["_id"]
389 filter_q['vcaStatus-refresh'] = refresh_status
390 expected_nsr = self.nsr_topic.show(session, nsr_id, filter_q=filter_q)
391 self.nsr_topic.delete(session, nsr_id)
392 actual_nsr.pop("_admin")
393 expected_nsr.pop("_admin")
394 self.assertEqual(expected_nsr, actual_nsr, "Database nsr and show() nsr do not match.")
395
396 def test_vca_status_refresh(self):
397 session = {"force": False, "admin": False, "public": False, "project_id": [self.nsd_project], "method": "write"}
398 filter_q = {'vcaStatus-refresh': 'true'}
399 time_delta = 120
400 self.db.create_list("nsrs", yaml.load(db_nsrs_text, Loader=yaml.Loader))
401 nsr = self.db.get_list("nsrs")[0]
402
403 # When vcaStatus-refresh is true
404 filter_q['vcaStatus-refresh'] = "true"
405 self.nsr_topic.vca_status_refresh(session, nsr, filter_q)
406 msg_args = self.msg.write.call_args[0]
407 self.assertEqual(msg_args[1], "vca_status_refresh", "Wrong message action")
408 self.assertGreater(nsr["_admin"]["modified"], time() - time_delta)
409
410 # When vcaStatus-refresh is false but modified time is within threshold
411 filter_q['vcaStatus-refresh'] = "false"
412 time_now = time()
413 nsr["_admin"]["modified"] = time_now
414 self.nsr_topic.vca_status_refresh(session, nsr, filter_q)
415 msg_args = self.msg.write.call_args[1]
416 self.assertEqual(msg_args, {}, "Message should not be sent.")
417 self.assertEqual(nsr["_admin"]["modified"], time_now, "Modified time should not be changed.")
418
419 # When vcaStatus-refresh is false but modified time is less than threshold
420 filter_q['vcaStatus-refresh'] = "false"
421 nsr["_admin"]["modified"] = time() - (2*time_delta)
422 self.nsr_topic.vca_status_refresh(session, nsr, filter_q)
423 msg_args = self.msg.write.call_args[0]
424 self.assertEqual(msg_args[1], "vca_status_refresh", "Wrong message action")
425 self.nsr_topic.delete(session, nsr["_id"])
426 self.assertGreater(nsr["_admin"]["modified"], time() - time_delta, "Modified time is not changed.")
427
428 def test_delete_ns(self):
429 self.db.create_list("nsrs", yaml.load(db_nsrs_text, Loader=yaml.Loader))
430 self.nsr = self.db.get_list("nsrs")[0]
431 self.nsr_id = self.nsr["_id"]
432 self.db_set_one = self.db.set_one
433 p_id = self.nsd_project
434 p_other = "other_p"
435
436 session = {
437 "force": False,
438 "admin": False,
439 "public": None,
440 "project_id": [p_id],
441 "method": "delete",
442 }
443 session2 = {
444 "force": False,
445 "admin": False,
446 "public": None,
447 "project_id": [p_other],
448 "method": "delete",
449 }
450 session_force = {
451 "force": True,
452 "admin": True,
453 "public": None,
454 "project_id": [],
455 "method": "delete",
456 }
457 with self.subTest(i=1, t="Normal Deletion"):
458 self.db.del_one = Mock()
459 self.db.set_one = Mock()
460 self.nsr_topic.delete(session, self.nsr_id)
461
462 db_args_ro_nsrs = self.db.del_one.call_args_list[1][0]
463 db_args = self.db.del_one.call_args_list[0][0]
464 msg_args = self.msg.write.call_args[0]
465 self.assertEqual(
466 msg_args[0], self.nsr_topic.topic_msg, "Wrong message topic"
467 )
468 self.assertEqual(msg_args[1], "deleted", "Wrong message action")
469 self.assertEqual(msg_args[2], {"_id": self.nsr_id}, "Wrong message content")
470 self.assertEqual(db_args_ro_nsrs[0], "ro_nsrs", "Wrong DB topic")
471 self.assertEqual(db_args[0], self.nsr_topic.topic, "Wrong DB topic")
472 self.assertEqual(db_args[1]["_id"], self.nsr_id, "Wrong DB ID")
473 self.assertEqual(
474 db_args[1]["_admin.projects_read.cont"], [p_id], "Wrong DB filter"
475 )
476 self.db.set_one.assert_not_called()
477 fs_del_calls = self.fs.file_delete.call_args_list
478 self.assertEqual(fs_del_calls[0][0][0], self.nsr_id, "Wrong FS file id")
479 with self.subTest(i=2, t="No delete because referenced by other project"):
480 self.db_set_one(
481 "nsrs",
482 {"_id": self.nsr_id},
483 update_dict=None,
484 push={
485 "_admin.projects_read": p_other,
486 "_admin.projects_write": p_other,
487 },
488 )
489 self.db.del_one.reset_mock()
490 self.db.set_one.reset_mock()
491 self.msg.write.reset_mock()
492 self.fs.file_delete.reset_mock()
493
494 self.nsr_topic.delete(session2, self.nsr_id)
495 self.db.del_one.assert_not_called()
496 self.msg.write.assert_not_called()
497 db_s1_args = self.db.set_one.call_args
498 self.assertEqual(db_s1_args[0][0], self.nsr_topic.topic, "Wrong DB topic")
499 self.assertEqual(db_s1_args[0][1]["_id"], self.nsr_id, "Wrong DB ID")
500 self.assertIsNone(
501 db_s1_args[1]["update_dict"], "Wrong DB update dictionary"
502 )
503 self.assertEqual(
504 db_s1_args[1]["pull_list"],
505 {"_admin.projects_read": [p_other], "_admin.projects_write": [p_other]},
506 "Wrong DB pull_list dictionary",
507 )
508 self.fs.file_delete.assert_not_called()
509 with self.subTest(i=4, t="Delete with force and admin"):
510 self.db.del_one.reset_mock()
511 self.db.set_one.reset_mock()
512 self.msg.write.reset_mock()
513 self.fs.file_delete.reset_mock()
514 self.nsr_topic.delete(session_force, self.nsr_id)
515
516 db_args_ro_nsrs = self.db.del_one.call_args_list[1][0]
517 db_args = self.db.del_one.call_args_list[0][0]
518 msg_args = self.msg.write.call_args[0]
519 self.assertEqual(
520 msg_args[0], self.nsr_topic.topic_msg, "Wrong message topic"
521 )
522 self.assertEqual(msg_args[1], "deleted", "Wrong message action")
523 self.assertEqual(msg_args[2], {"_id": self.nsr_id}, "Wrong message content")
524 self.assertEqual(db_args_ro_nsrs[0], "ro_nsrs", "Wrong DB topic")
525 self.assertEqual(db_args[0], self.nsr_topic.topic, "Wrong DB topic")
526 self.assertEqual(db_args[1]["_id"], self.nsr_id, "Wrong DB ID")
527 self.db.set_one.assert_not_called()
528 fs_del_calls = self.fs.file_delete.call_args_list
529 self.assertEqual(fs_del_calls[0][0][0], self.nsr_id, "Wrong FS file id")
530 with self.subTest(i=3, t="Conflict on Delete - NS in INSTANTIATED state"):
531 self.db_set_one(
532 "nsrs",
533 {"_id": self.nsr_id},
534 {"_admin.nsState": "INSTANTIATED"},
535 pull={
536 "_admin.projects_read": p_other,
537 "_admin.projects_write": p_other,
538 },
539 )
540 self.db.del_one.reset_mock()
541 self.db.set_one.reset_mock()
542 self.msg.write.reset_mock()
543 self.fs.file_delete.reset_mock()
544
545 with self.assertRaises(
546 EngineException, msg="Accepted NSR with nsState INSTANTIATED"
547 ) as e:
548 self.nsr_topic.delete(session, self.nsr_id)
549 self.assertEqual(
550 e.exception.http_code, HTTPStatus.CONFLICT, "Wrong HTTP status code"
551 )
552 self.assertIn("INSTANTIATED", str(e.exception), "Wrong exception text")
553 # TODOD with self.subTest(i=3, t='Conflict on Delete - NS in use by NSI'):
554
555 with self.subTest(i=4, t="Non-existent NS"):
556 self.db.del_one.reset_mock()
557 self.db.set_one.reset_mock()
558 self.msg.write.reset_mock()
559 self.fs.file_delete.reset_mock()
560 excp_msg = "Not found"
561 with self.assertRaises(
562 DbException, msg="Accepted non-existent NSD ID"
563 ) as e:
564 self.nsr_topic.delete(session2, "other_id")
565 self.assertEqual(
566 e.exception.http_code, HTTPStatus.NOT_FOUND, "Wrong HTTP status code"
567 )
568 self.assertIn(excp_msg, str(e.exception), "Wrong exception text")
569 self.assertIn("other_id", str(e.exception), "Wrong exception text")
570 return