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 |
1 |
from contextlib import contextmanager |
19 |
1 |
import unittest |
20 |
1 |
from time import time |
21 |
1 |
from unittest.mock import Mock, mock_open # patch, MagicMock |
22 |
1 |
from osm_common.dbbase import DbException |
23 |
1 |
from osm_nbi.engine import EngineException |
24 |
1 |
from osm_common.dbmemory import DbMemory |
25 |
1 |
from osm_common.fsbase import FsBase |
26 |
1 |
from osm_common.msgbase import MsgBase |
27 |
1 |
from osm_common import dbbase |
28 |
1 |
from http import HTTPStatus |
29 |
1 |
from osm_nbi.instance_topics import NsLcmOpTopic, NsrTopic |
30 |
1 |
from osm_nbi.tests.test_db_descriptors import ( |
31 |
|
db_vim_accounts_text, |
32 |
|
db_nsds_text, |
33 |
|
db_vnfds_text, |
34 |
|
db_nsrs_text, |
35 |
|
db_vnfrs_text, |
36 |
|
) |
37 |
1 |
from copy import deepcopy |
38 |
1 |
import yaml |
39 |
|
|
40 |
|
|
41 |
1 |
class TestNsLcmOpTopic(unittest.TestCase): |
42 |
1 |
def setUp(self): |
43 |
1 |
self.db = DbMemory() |
44 |
1 |
self.fs = Mock(FsBase()) |
45 |
1 |
self.fs.get_params.return_value = {"./fake/folder"} |
46 |
1 |
self.fs.file_open = mock_open() |
47 |
1 |
self.msg = Mock(MsgBase()) |
48 |
|
# create class |
49 |
1 |
self.nslcmop_topic = NsLcmOpTopic(self.db, self.fs, self.msg, None) |
50 |
1 |
self.nslcmop_topic.check_quota = Mock(return_value=None) # skip quota |
51 |
|
|
52 |
1 |
self.db.create_list("vim_accounts", yaml.safe_load(db_vim_accounts_text)) |
53 |
1 |
self.db.create_list("nsds", yaml.safe_load(db_nsds_text)) |
54 |
1 |
self.db.create_list("vnfds", yaml.safe_load(db_vnfds_text)) |
55 |
1 |
self.db.create_list("vnfrs", yaml.safe_load(db_vnfrs_text)) |
56 |
1 |
self.db.create_list("nsrs", yaml.safe_load(db_nsrs_text)) |
57 |
1 |
self.db.create = Mock(return_value="created_id") |
58 |
1 |
self.nsd = self.db.get_list("nsds")[0] |
59 |
1 |
self.nsd_id = self.nsd["_id"] |
60 |
1 |
self.nsr = self.db.get_list("nsrs")[0] |
61 |
1 |
self.nsr_id = self.nsr["_id"] |
62 |
1 |
self.nsr_project = self.nsr["_admin"]["projects_read"][0] |
63 |
|
|
64 |
1 |
self.vim = self.db.get_list("vim_accounts")[0] |
65 |
1 |
self.vim_id = self.vim["_id"] |
66 |
|
|
67 |
1 |
def test_create_instantiate(self): |
68 |
1 |
self.db.set_one = Mock(return_value={"updated": 1}) |
69 |
1 |
session = { |
70 |
|
"force": False, |
71 |
|
"admin": False, |
72 |
|
"public": False, |
73 |
|
"project_id": [self.nsr_project], |
74 |
|
"method": "write", |
75 |
|
} |
76 |
1 |
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 |
1 |
rollback = [] |
114 |
1 |
headers = {} |
115 |
|
|
116 |
1 |
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 |
1 |
self.assertEqual( |
122 |
|
self.db.create.call_count, |
123 |
|
1, |
124 |
|
"database create not called, or called more than once", |
125 |
|
) |
126 |
1 |
_call = self.db.create.call_args_list[0] |
127 |
1 |
self.assertEqual( |
128 |
|
_call[0][0], "nslcmops", "must be create a nslcmops entry at database" |
129 |
|
) |
130 |
|
|
131 |
1 |
created_nslcmop = _call[0][1] |
132 |
1 |
self.assertEqual( |
133 |
|
nslcmop_id, |
134 |
|
created_nslcmop["_id"], |
135 |
|
"mismatch between return id and database '_id'", |
136 |
|
) |
137 |
1 |
self.assertEqual( |
138 |
|
self.nsr_id, |
139 |
|
created_nslcmop["nsInstanceId"], |
140 |
|
"bad reference id from nslcmop to nsr", |
141 |
|
) |
142 |
1 |
self.assertTrue( |
143 |
|
created_nslcmop["_admin"].get("projects_read"), |
144 |
|
"Database record must contain '_amdin.projects_read'", |
145 |
|
) |
146 |
1 |
self.assertIn( |
147 |
|
"created", |
148 |
|
created_nslcmop["_admin"], |
149 |
|
"Database record must contain '_admin.created'", |
150 |
|
) |
151 |
1 |
self.assertTrue( |
152 |
|
created_nslcmop["lcmOperationType"] == "instantiate", |
153 |
|
"Database record must contain 'lcmOperationType=instantiate'", |
154 |
|
) |
155 |
|
|
156 |
1 |
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 |
1 |
bad_id = "88d90b0c-faff-4b9f-bccd-aaaaaaaaaaaa" |
164 |
1 |
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 |
1 |
for message, kwargs_, expect_exc, expect_code, expect_text_list in test_set: |
183 |
1 |
with self.assertRaises(expect_exc, msg=message) as e: |
184 |
1 |
self.nslcmop_topic.new( |
185 |
|
rollback, |
186 |
|
session, |
187 |
|
indata=deepcopy(indata), |
188 |
|
kwargs=kwargs_, |
189 |
|
headers=headers, |
190 |
|
) |
191 |
1 |
if expect_code: |
192 |
1 |
self.assertTrue(e.exception.http_code == expect_code) |
193 |
1 |
if expect_text_list: |
194 |
1 |
for expect_text in expect_text_list: |
195 |
1 |
self.assertIn( |
196 |
|
expect_text, |
197 |
|
str(e.exception).lower(), |
198 |
|
"Expected '{}' at exception text".format(expect_text), |
199 |
|
) |
200 |
|
|
201 |
1 |
def test_check_ns_operation_action(self): |
202 |
1 |
nsrs = self.db.get_list("nsrs")[0] |
203 |
1 |
session = {} |
204 |
|
|
205 |
1 |
indata = { |
206 |
|
"member_vnf_index": "1", |
207 |
|
"vdu_id": None, |
208 |
|
"primitive": "touch", |
209 |
|
"primitive_params": {"filename": "file"}, |
210 |
|
} |
211 |
|
|
212 |
1 |
self.nslcmop_topic._check_ns_operation(session, nsrs, "action", indata) |
213 |
1 |
for k in indata: |
214 |
1 |
indata_copy = indata.copy() |
215 |
1 |
if k == "primitive_params": |
216 |
1 |
continue |
217 |
1 |
indata_copy[k] = "non_existing" |
218 |
1 |
with self.assertRaises(EngineException) as exc_manager: |
219 |
1 |
self.nslcmop_topic._check_ns_operation( |
220 |
|
session, nsrs, "action", indata_copy |
221 |
|
) |
222 |
1 |
exc = exc_manager.exception |
223 |
1 |
self.assertEqual( |
224 |
|
exc.http_code, |
225 |
|
HTTPStatus.BAD_REQUEST, |
226 |
|
"Engine exception bad http_code with {}".format(indata_copy), |
227 |
|
) |
228 |
|
|
229 |
1 |
def test_update_remove_vnf(self): |
230 |
1 |
vnfr_id = self.db.get_list("vnfrs")[0]["_id"] |
231 |
1 |
session = {} |
232 |
1 |
self.db.set_one( |
233 |
|
"nsrs", |
234 |
|
{"_id": self.nsr_id}, |
235 |
|
{"_admin.nsState": "INSTANTIATED"}, |
236 |
|
) |
237 |
1 |
indata = { |
238 |
|
"lcmOperationType": "update", |
239 |
|
"updateType": "REMOVE_VNF", |
240 |
|
"nsInstanceId": self.nsr_id, |
241 |
|
"removeVnfInstanceId": vnfr_id, |
242 |
|
} |
243 |
|
|
244 |
1 |
session = { |
245 |
|
"force": False, |
246 |
|
"admin": False, |
247 |
|
"public": False, |
248 |
|
"project_id": [self.nsr_project], |
249 |
|
"method": "write", |
250 |
|
} |
251 |
1 |
rollback = [] |
252 |
1 |
headers = {} |
253 |
|
|
254 |
1 |
nslcmop_id, _ = self.nslcmop_topic.new( |
255 |
|
rollback, session, indata, kwargs=None, headers=headers |
256 |
|
) |
257 |
|
|
258 |
1 |
self.assertEqual( |
259 |
|
self.db.create.call_count, |
260 |
|
1, |
261 |
|
"database create not called, or called more than once", |
262 |
|
) |
263 |
1 |
_call = self.db.create.call_args_list[0] |
264 |
1 |
self.assertEqual( |
265 |
|
_call[0][0], "nslcmops", "nslcmops entry must be created at database" |
266 |
|
) |
267 |
1 |
created_nslcmop = _call[0][1] |
268 |
1 |
self.assertEqual( |
269 |
|
self.nsr_id, |
270 |
|
created_nslcmop["nsInstanceId"], |
271 |
|
"mismatch between nsId '_id' in created nslcmop and database nsr", |
272 |
|
) |
273 |
1 |
self.assertTrue( |
274 |
|
created_nslcmop["lcmOperationType"] == "update", |
275 |
|
"Database record must contain 'lcmOperationType=update'", |
276 |
|
) |
277 |
1 |
self.assertTrue( |
278 |
|
created_nslcmop["operationParams"]["updateType"] == "REMOVE_VNF", |
279 |
|
"Database record must contain 'updateType=REMOVE_VNF'", |
280 |
|
) |
281 |
|
|
282 |
1 |
def test_migrate(self): |
283 |
1 |
_ = self.db.get_list("vnfrs")[0]["_id"] |
284 |
1 |
session = {} |
285 |
1 |
self.db.set_one( |
286 |
|
"nsrs", |
287 |
|
{"_id": self.nsr_id}, |
288 |
|
{"_admin.nsState": "INSTANTIATED"}, |
289 |
|
) |
290 |
1 |
session = { |
291 |
|
"force": False, |
292 |
|
"admin": False, |
293 |
|
"public": False, |
294 |
|
"project_id": [self.nsr_project], |
295 |
|
"method": "write", |
296 |
|
} |
297 |
1 |
rollback = [] |
298 |
1 |
headers = {} |
299 |
|
|
300 |
1 |
with self.subTest(i=1, t="Migration for Specific VM"): |
301 |
1 |
indata = { |
302 |
|
"lcmOperationType": "migrate", |
303 |
|
"nsInstanceId": self.nsr_id, |
304 |
|
"migrateToHost": "sample02", |
305 |
|
"vdu": {"vduCountIndex": 0, "vduId": "mgmtVM"}, |
306 |
|
"vnfInstanceId": "9e8006df-cdfa-4f63-bf6a-fce860d71c1f", |
307 |
|
} |
308 |
1 |
nslcmop_id, _ = self.nslcmop_topic.new( |
309 |
|
rollback, session, indata, kwargs=None, headers=headers |
310 |
|
) |
311 |
|
|
312 |
1 |
self.assertEqual( |
313 |
|
self.db.create.call_count, |
314 |
|
1, |
315 |
|
"database create not called, or called more than once", |
316 |
|
) |
317 |
1 |
_call = self.db.create.call_args_list[0] |
318 |
1 |
self.assertEqual( |
319 |
|
_call[0][0], "nslcmops", "nslcmops entry must be created at database" |
320 |
|
) |
321 |
1 |
created_nslcmop = _call[0][1] |
322 |
1 |
self.assertEqual( |
323 |
|
self.nsr_id, |
324 |
|
created_nslcmop["nsInstanceId"], |
325 |
|
"mismatch between nsId '_id' in created nslcmop and database nsr", |
326 |
|
) |
327 |
1 |
self.assertTrue( |
328 |
|
created_nslcmop["lcmOperationType"] == "migrate", |
329 |
|
"Database record must contain 'lcmOperationType=migrate'", |
330 |
|
) |
331 |
1 |
with self.subTest(i=2, t="Migration of all VDUs in a VNF"): |
332 |
1 |
indata = { |
333 |
|
"lcmOperationType": "migrate", |
334 |
|
"nsInstanceId": self.nsr_id, |
335 |
|
"vnfInstanceId": "9e8006df-cdfa-4f63-bf6a-fce860d71c1f", |
336 |
|
} |
337 |
1 |
nslcmop_id, _ = self.nslcmop_topic.new( |
338 |
|
rollback, session, indata, kwargs=None, headers=headers |
339 |
|
) |
340 |
|
|
341 |
1 |
self.assertEqual( |
342 |
|
self.db.create.call_count, |
343 |
|
2, |
344 |
|
"database create not called, or called more than once", |
345 |
|
) |
346 |
1 |
_call = self.db.create.call_args_list[0] |
347 |
1 |
self.assertEqual( |
348 |
|
_call[0][0], "nslcmops", "nslcmops entry must be created at database" |
349 |
|
) |
350 |
1 |
created_nslcmop = _call[0][1] |
351 |
1 |
self.assertEqual( |
352 |
|
self.nsr_id, |
353 |
|
created_nslcmop["nsInstanceId"], |
354 |
|
"mismatch between nsId '_id' in created nslcmop and database nsr", |
355 |
|
) |
356 |
1 |
self.assertTrue( |
357 |
|
created_nslcmop["lcmOperationType"] == "migrate", |
358 |
|
"Database record must contain 'lcmOperationType=migrate'", |
359 |
|
) |
360 |
1 |
with self.subTest(i=3, t="Migration failure - vduId not provided in vdu "): |
361 |
1 |
indata = { |
362 |
|
"lcmOperationType": "migrate", |
363 |
|
"nsInstanceId": self.nsr_id, |
364 |
|
"migrateToHost": "sample02", |
365 |
|
"vdu": {"vduCountIndex": 0}, |
366 |
|
"vnfInstanceId": "9e8006df-cdfa-4f63-bf6a-fce860d71c1f", |
367 |
|
} |
368 |
|
|
369 |
1 |
with self.assertRaises(Exception) as e: |
370 |
1 |
nslcmop_id, _ = self.nslcmop_topic.new( |
371 |
|
rollback, session, indata, kwargs=None, headers=headers |
372 |
|
) |
373 |
1 |
self.assertTrue( |
374 |
|
"Format error at 'vdu' ''vduId' is a required property'" |
375 |
|
in str(e.exception) |
376 |
|
) |
377 |
|
|
378 |
|
|
379 |
1 |
class TestNsLcmOpTopicWithMock(unittest.TestCase): |
380 |
1 |
def setUp(self): |
381 |
1 |
self.db = Mock(dbbase.DbBase()) |
382 |
1 |
self.fs = Mock(FsBase()) |
383 |
1 |
self.fs.get_params.return_value = {"./fake/folder"} |
384 |
1 |
self.fs.file_open = mock_open() |
385 |
1 |
self.msg = Mock(MsgBase()) |
386 |
|
# create class |
387 |
1 |
self.nslcmop_topic = NsLcmOpTopic(self.db, self.fs, self.msg, None) |
388 |
|
|
389 |
1 |
def test_get_vnfd_from_vnf_member_revision(self): |
390 |
1 |
test_vnfr = yaml.safe_load(db_vnfrs_text)[0] |
391 |
1 |
test_vnfd = yaml.safe_load(db_vnfds_text) |
392 |
1 |
self.db.get_one.side_effect = [test_vnfr, test_vnfd] |
393 |
1 |
_ = self.nslcmop_topic._get_vnfd_from_vnf_member_index("1", test_vnfr["_id"]) |
394 |
1 |
self.assertEqual( |
395 |
|
self.db.get_one.call_args_list[0][0][0], |
396 |
|
"vnfrs", |
397 |
|
"Incorrect first DB lookup", |
398 |
|
) |
399 |
1 |
self.assertEqual( |
400 |
|
self.db.get_one.call_args_list[1][0][0], |
401 |
|
"vnfds", |
402 |
|
"Incorrect second DB lookup", |
403 |
|
) |
404 |
|
|
405 |
1 |
def test_get_vnfd_from_vnf_member_no_revision(self): |
406 |
1 |
test_vnfr = yaml.safe_load(db_vnfrs_text)[0] |
407 |
1 |
test_vnfr["revision"] = 3 |
408 |
1 |
test_vnfd = yaml.safe_load(db_vnfds_text) |
409 |
1 |
self.db.get_one.side_effect = [test_vnfr, test_vnfd] |
410 |
1 |
_ = self.nslcmop_topic._get_vnfd_from_vnf_member_index("1", test_vnfr["_id"]) |
411 |
1 |
self.assertEqual( |
412 |
|
self.db.get_one.call_args_list[0][0][0], |
413 |
|
"vnfrs", |
414 |
|
"Incorrect first DB lookup", |
415 |
|
) |
416 |
1 |
self.assertEqual( |
417 |
|
self.db.get_one.call_args_list[1][0][0], |
418 |
|
"vnfds_revisions", |
419 |
|
"Incorrect second DB lookup", |
420 |
|
) |
421 |
|
|
422 |
1 |
@contextmanager |
423 |
1 |
def assertNotRaises(self, exception_type): |
424 |
1 |
try: |
425 |
1 |
yield None |
426 |
0 |
except exception_type: |
427 |
0 |
raise self.failureException("{} raised".format(exception_type.__name__)) |
428 |
|
|
429 |
1 |
def test_check_ns_update_operation(self): |
430 |
1 |
self.db = DbMemory() |
431 |
1 |
self.nslcmop_topic = NsLcmOpTopic(self.db, self.fs, self.msg, None) |
432 |
1 |
session = {} |
433 |
|
|
434 |
1 |
with self.subTest(i=1, t="VNF instance does not belong to NS"): |
435 |
1 |
test_vnfr = yaml.safe_load(db_vnfrs_text) |
436 |
1 |
test_vnfr[0]["revision"] = 2 |
437 |
1 |
test_nsr = yaml.safe_load(db_nsrs_text) |
438 |
1 |
test_nsr[0]["constituent-vnfr-ref"][ |
439 |
|
0 |
440 |
|
] = "99d90b0c-faff-4b9f-bccd-017f33985984" |
441 |
1 |
self.db.create_list("vnfrs", test_vnfr) |
442 |
1 |
self.db.create_list("nsrs", test_nsr) |
443 |
1 |
nsrs = self.db.get_list("nsrs")[0] |
444 |
1 |
indata = { |
445 |
|
"updateType": "CHANGE_VNFPKG", |
446 |
|
"changeVnfPackageData": { |
447 |
|
"vnfInstanceId": "88d90b0c-faff-4b9f-bccd-017f33985984", |
448 |
|
"vnfdId": "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77", |
449 |
|
}, |
450 |
|
"nsInstanceId": "f48163a6-c807-47bc-9682-f72caef5af85", |
451 |
|
} |
452 |
1 |
with self.assertRaises(EngineException) as expected_exception: |
453 |
1 |
self.nslcmop_topic._check_ns_operation(session, nsrs, "update", indata) |
454 |
1 |
self.assertEqual( |
455 |
|
str(expected_exception.exception), |
456 |
|
"Error in validating ns-update request: vnf 88d90b0c-faff-4b9f-bccd-017f33985984" |
457 |
|
" does not belong to NS f48163a6-c807-47bc-9682-f72caef5af85", |
458 |
|
) |
459 |
|
|
460 |
1 |
with self.subTest(i=2, t="Ns update request validated with no exception"): |
461 |
1 |
test_vnfr = yaml.safe_load(db_vnfrs_text) |
462 |
1 |
test_vnfr[0]["revision"] = 2 |
463 |
1 |
test_nsr = yaml.safe_load(db_nsrs_text) |
464 |
1 |
self.db.create_list("vnfrs", test_vnfr) |
465 |
1 |
self.db.create_list("nsrs", test_nsr) |
466 |
1 |
nsrs = self.db.get_list("nsrs")[1] |
467 |
1 |
indata = { |
468 |
|
"updateType": "CHANGE_VNFPKG", |
469 |
|
"changeVnfPackageData": { |
470 |
|
"vnfInstanceId": "88d90b0c-faff-4b9f-bccd-017f33985984", |
471 |
|
"vnfdId": "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77", |
472 |
|
}, |
473 |
|
"nsInstanceId": "f48163a6-c807-47bc-9682-f72caef5af85", |
474 |
|
} |
475 |
1 |
with self.assertNotRaises(EngineException): |
476 |
1 |
self.nslcmop_topic._check_ns_operation(session, nsrs, "update", indata) |
477 |
|
|
478 |
1 |
with self.subTest( |
479 |
|
i=3, t="Ns update request rejected because of too small timeout" |
480 |
|
): |
481 |
1 |
indata = { |
482 |
|
"updateType": "CHANGE_VNFPKG", |
483 |
|
"changeVnfPackageData": { |
484 |
|
"vnfInstanceId": "88d90b0c-faff-4b9f-bccd-017f33985984", |
485 |
|
"vnfdId": "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77", |
486 |
|
}, |
487 |
|
"nsInstanceId": "f48163a6-c807-47bc-9682-f72caef5af85", |
488 |
|
"timeout_ns_update": 50, |
489 |
|
} |
490 |
1 |
with self.assertRaises(EngineException) as expected_exception: |
491 |
1 |
self.nslcmop_topic._check_ns_operation(session, nsrs, "update", indata) |
492 |
1 |
self.assertEqual( |
493 |
|
str(expected_exception.exception), |
494 |
|
"Error in validating ns-update request: 50 second is not enough " |
495 |
|
"to upgrade the VNF instance: 88d90b0c-faff-4b9f-bccd-017f33985984", |
496 |
|
) |
497 |
|
|
498 |
1 |
with self.subTest(i=4, t="wrong vnfdid is given as an update parameter"): |
499 |
1 |
test_vnfr = yaml.safe_load(db_vnfrs_text) |
500 |
1 |
test_vnfr[0]["revision"] = 2 |
501 |
1 |
test_nsr = yaml.safe_load(db_nsrs_text) |
502 |
1 |
self.db.create_list("vnfrs", test_vnfr) |
503 |
1 |
self.db.create_list("nsrs", test_nsr) |
504 |
1 |
nsrs = self.db.get_list("nsrs")[2] |
505 |
1 |
indata = { |
506 |
|
"updateType": "CHANGE_VNFPKG", |
507 |
|
"changeVnfPackageData": { |
508 |
|
"vnfInstanceId": "88d90b0c-faff-4b9f-bccd-017f33985984", |
509 |
|
"vnfdId": "9637bcf8-cf14-42dc-ad70-c66fcf1e6e77", |
510 |
|
}, |
511 |
|
"nsInstanceId": "f48163a6-c807-47bc-9682-f72caef5af85", |
512 |
|
} |
513 |
1 |
with self.assertRaises(EngineException) as expected_exception: |
514 |
1 |
self.nslcmop_topic._check_ns_operation(session, nsrs, "update", indata) |
515 |
1 |
self.assertEqual( |
516 |
|
str(expected_exception.exception), |
517 |
|
"Error in validating ns-update request: vnfd-id 9637bcf8-cf14-42dc-ad70-c66fcf1e6e77 does not " |
518 |
|
"match with the vnfd-id: 7637bcf8-cf14-42dc-ad70-c66fcf1e6e77 of " |
519 |
|
"VNF instance: 88d90b0c-faff-4b9f-bccd-017f33985984", |
520 |
|
) |
521 |
|
|
522 |
1 |
with self.subTest( |
523 |
|
i=5, t="Ns update REMOVE_VNF request validated with no exception" |
524 |
|
): |
525 |
1 |
test_vnfr = yaml.safe_load(db_vnfrs_text) |
526 |
1 |
test_vnfr[0]["revision"] = 2 |
527 |
1 |
test_nsr = yaml.safe_load(db_nsrs_text) |
528 |
1 |
self.db.create_list("vnfrs", test_vnfr) |
529 |
1 |
self.db.create_list("nsrs", test_nsr) |
530 |
1 |
nsrs = self.db.get_list("nsrs")[1] |
531 |
1 |
indata = { |
532 |
|
"updateType": "REMOVE_VNF", |
533 |
|
"removeVnfInstanceId": "88d90b0c-faff-4b9f-bccd-017f33985984", |
534 |
|
"nsInstanceId": "f48163a6-c807-47bc-9682-f72caef5af85", |
535 |
|
} |
536 |
1 |
with self.assertNotRaises(EngineException): |
537 |
1 |
self.nslcmop_topic._check_ns_operation(session, nsrs, "update", indata) |
538 |
|
|
539 |
|
|
540 |
1 |
class TestNsrTopic(unittest.TestCase): |
541 |
1 |
def setUp(self): |
542 |
1 |
self.db = DbMemory() |
543 |
1 |
self.fs = Mock(FsBase()) |
544 |
1 |
self.fs.get_params.return_value = {"./fake/folder"} |
545 |
1 |
self.fs.file_open = mock_open() |
546 |
1 |
self.msg = Mock(MsgBase()) |
547 |
|
# create class |
548 |
1 |
self.nsr_topic = NsrTopic(self.db, self.fs, self.msg, None) |
549 |
1 |
self.nsr_topic.check_quota = Mock(return_value=None) # skip quota |
550 |
|
|
551 |
1 |
self.db.create_list("vim_accounts", yaml.safe_load(db_vim_accounts_text)) |
552 |
1 |
self.db.create_list("nsds", yaml.safe_load(db_nsds_text)) |
553 |
1 |
self.db.create_list("vnfds", yaml.safe_load(db_vnfds_text)) |
554 |
1 |
self.db.create = Mock(return_value="created_id") |
555 |
1 |
self.nsd = self.db.get_list("nsds")[0] |
556 |
1 |
self.nsd_id = self.nsd["_id"] |
557 |
1 |
self.nsd_project = self.nsd["_admin"]["projects_read"][0] |
558 |
|
|
559 |
1 |
self.vim = self.db.get_list("vim_accounts")[0] |
560 |
1 |
self.vim_id = self.vim["_id"] |
561 |
|
|
562 |
1 |
def test_create(self): |
563 |
1 |
session = { |
564 |
|
"force": False, |
565 |
|
"admin": False, |
566 |
|
"public": False, |
567 |
|
"project_id": [self.nsd_project], |
568 |
|
"method": "write", |
569 |
|
} |
570 |
1 |
indata = { |
571 |
|
"nsdId": self.nsd_id, |
572 |
|
"nsName": "name", |
573 |
|
"vimAccountId": self.vim_id, |
574 |
|
"additionalParamsForVnf": [ |
575 |
|
{ |
576 |
|
"member-vnf-index": "hackfest_vnf1", |
577 |
|
"additionalParams": {"touch_filename": "file"}, |
578 |
|
}, |
579 |
|
{ |
580 |
|
"member-vnf-index": "hackfest_vnf2", |
581 |
|
"additionalParams": {"touch_filename": "file"}, |
582 |
|
}, |
583 |
|
], |
584 |
|
} |
585 |
1 |
rollback = [] |
586 |
1 |
headers = {} |
587 |
|
|
588 |
1 |
self.nsr_topic.new( |
589 |
|
rollback, session, indata=indata, kwargs=None, headers=headers |
590 |
|
) |
591 |
|
|
592 |
|
# check vnfrs and nsrs created in whatever order |
593 |
1 |
created_vnfrs = [] |
594 |
1 |
created_nsrs = [] |
595 |
1 |
nsr_id = None |
596 |
1 |
for _call in self.db.create.call_args_list: |
597 |
1 |
assert len(_call[0]) >= 2, "called db.create with few parameters" |
598 |
1 |
created_item = _call[0][1] |
599 |
1 |
if _call[0][0] == "vnfrs": |
600 |
1 |
created_vnfrs.append(created_item) |
601 |
1 |
self.assertIn( |
602 |
|
"member-vnf-index-ref", |
603 |
|
created_item, |
604 |
|
"Created item must contain member-vnf-index-ref section", |
605 |
|
) |
606 |
1 |
if nsr_id: |
607 |
1 |
self.assertEqual( |
608 |
|
nsr_id, |
609 |
|
created_item["nsr-id-ref"], |
610 |
|
"bad reference id from vnfr to nsr", |
611 |
|
) |
612 |
|
else: |
613 |
1 |
nsr_id = created_item["nsr-id-ref"] |
614 |
|
|
615 |
1 |
elif _call[0][0] == "nsrs": |
616 |
1 |
created_nsrs.append(created_item) |
617 |
1 |
if nsr_id: |
618 |
1 |
self.assertEqual( |
619 |
|
nsr_id, created_item["_id"], "bad reference id from vnfr to nsr" |
620 |
|
) |
621 |
|
else: |
622 |
0 |
nsr_id = created_item["_id"] |
623 |
|
else: |
624 |
0 |
assert True, "created an unknown record {} at database".format( |
625 |
|
_call[0][0] |
626 |
|
) |
627 |
|
|
628 |
1 |
self.assertTrue( |
629 |
|
created_item["_admin"].get("projects_read"), |
630 |
|
"Database record must contain '_amdin.projects_read'", |
631 |
|
) |
632 |
1 |
self.assertIn( |
633 |
|
"created", |
634 |
|
created_item["_admin"], |
635 |
|
"Database record must contain '_admin.created'", |
636 |
|
) |
637 |
1 |
self.assertTrue( |
638 |
|
created_item["_admin"]["nsState"] == "NOT_INSTANTIATED", |
639 |
|
"Database record must contain '_admin.nstate=NOT INSTANTIATE'", |
640 |
|
) |
641 |
|
|
642 |
1 |
self.assertEqual( |
643 |
|
len(created_vnfrs), 2, "created a mismatch number of vnfr at database" |
644 |
|
) |
645 |
|
|
646 |
1 |
self.assertEqual( |
647 |
|
created_vnfrs[0]["vdur"][0]["interfaces"][0]["position"], |
648 |
|
1, |
649 |
|
"vdur first interface position does not match", |
650 |
|
) |
651 |
|
|
652 |
1 |
self.assertEqual( |
653 |
|
created_vnfrs[0]["vdur"][0]["interfaces"][1]["position"], |
654 |
|
2, |
655 |
|
"vdur second interface position does not match", |
656 |
|
) |
657 |
|
|
658 |
1 |
self.assertEqual( |
659 |
|
len(created_nsrs), 1, "Only one nsrs must be created at database" |
660 |
|
) |
661 |
1 |
self.assertEqual( |
662 |
|
len(rollback), |
663 |
|
len(created_vnfrs) + 1, |
664 |
|
"rollback mismatch with created items at database", |
665 |
|
) |
666 |
|
|
667 |
|
# test parameters with error |
668 |
1 |
bad_id = "88d90b0c-faff-4b9f-bccd-aaaaaaaaaaaa" |
669 |
1 |
test_set = ( |
670 |
|
# TODO add "nsd" |
671 |
|
( |
672 |
|
"nsd not found", |
673 |
|
{"nsdId": bad_id}, |
674 |
|
DbException, |
675 |
|
HTTPStatus.NOT_FOUND, |
676 |
|
("not found", bad_id), |
677 |
|
), |
678 |
|
# ({"vimAccountId": bad_id}, DbException, HTTPStatus.NOT_FOUND, ("not found", bad_id)), # TODO add "vim" |
679 |
|
( |
680 |
|
"additional params not supply", |
681 |
|
{"additionalParamsForVnf.0.member-vnf-index": "k"}, |
682 |
|
EngineException, |
683 |
|
HTTPStatus.BAD_REQUEST, |
684 |
|
None, |
685 |
|
), |
686 |
|
) |
687 |
1 |
for message, kwargs_, expect_exc, expect_code, expect_text_list in test_set: |
688 |
1 |
with self.assertRaises(expect_exc, msg=message) as e: |
689 |
1 |
self.nsr_topic.new( |
690 |
|
rollback, |
691 |
|
session, |
692 |
|
indata=deepcopy(indata), |
693 |
|
kwargs=kwargs_, |
694 |
|
headers=headers, |
695 |
|
) |
696 |
1 |
if expect_code: |
697 |
1 |
self.assertTrue(e.exception.http_code == expect_code) |
698 |
1 |
if expect_text_list: |
699 |
1 |
for expect_text in expect_text_list: |
700 |
1 |
self.assertIn( |
701 |
|
expect_text, |
702 |
|
str(e.exception).lower(), |
703 |
|
"Expected '{}' at exception text".format(expect_text), |
704 |
|
) |
705 |
|
|
706 |
1 |
def test_show_instance(self): |
707 |
1 |
session = { |
708 |
|
"force": False, |
709 |
|
"admin": False, |
710 |
|
"public": False, |
711 |
|
"project_id": [self.nsd_project], |
712 |
|
"method": "write", |
713 |
|
} |
714 |
1 |
filter_q = {} |
715 |
1 |
for refresh_status in ("true", "false"): |
716 |
1 |
self.db.create_list("nsrs", yaml.safe_load(db_nsrs_text)) |
717 |
1 |
actual_nsr = self.db.get_list("nsrs")[0] |
718 |
1 |
nsr_id = actual_nsr["_id"] |
719 |
1 |
filter_q["vcaStatus-refresh"] = refresh_status |
720 |
1 |
expected_nsr = self.nsr_topic.show(session, nsr_id, filter_q=filter_q) |
721 |
1 |
self.nsr_topic.delete(session, nsr_id) |
722 |
1 |
actual_nsr.pop("_admin") |
723 |
1 |
expected_nsr.pop("_admin") |
724 |
1 |
self.assertEqual( |
725 |
|
expected_nsr, actual_nsr, "Database nsr and show() nsr do not match." |
726 |
|
) |
727 |
|
|
728 |
1 |
def test_vca_status_refresh(self): |
729 |
1 |
session = { |
730 |
|
"force": False, |
731 |
|
"admin": False, |
732 |
|
"public": False, |
733 |
|
"project_id": [self.nsd_project], |
734 |
|
"method": "write", |
735 |
|
} |
736 |
1 |
filter_q = {"vcaStatus-refresh": "true"} |
737 |
1 |
time_delta = 120 |
738 |
1 |
self.db.create_list("nsrs", yaml.safe_load(db_nsrs_text)) |
739 |
1 |
nsr = self.db.get_list("nsrs")[0] |
740 |
|
|
741 |
|
# When vcaStatus-refresh is true |
742 |
1 |
filter_q["vcaStatus-refresh"] = "true" |
743 |
1 |
self.nsr_topic.vca_status_refresh(session, nsr, filter_q) |
744 |
1 |
msg_args = self.msg.write.call_args[0] |
745 |
1 |
self.assertEqual(msg_args[1], "vca_status_refresh", "Wrong message action") |
746 |
1 |
self.assertGreater(nsr["_admin"]["modified"], time() - time_delta) |
747 |
|
|
748 |
|
# When vcaStatus-refresh is false but modified time is within threshold |
749 |
1 |
filter_q["vcaStatus-refresh"] = "false" |
750 |
1 |
time_now = time() |
751 |
1 |
nsr["_admin"]["modified"] = time_now |
752 |
1 |
self.nsr_topic.vca_status_refresh(session, nsr, filter_q) |
753 |
1 |
msg_args = self.msg.write.call_args[1] |
754 |
1 |
self.assertEqual(msg_args, {}, "Message should not be sent.") |
755 |
1 |
self.assertEqual( |
756 |
|
nsr["_admin"]["modified"], time_now, "Modified time should not be changed." |
757 |
|
) |
758 |
|
|
759 |
|
# When vcaStatus-refresh is false but modified time is less than threshold |
760 |
1 |
filter_q["vcaStatus-refresh"] = "false" |
761 |
1 |
nsr["_admin"]["modified"] = time() - (2 * time_delta) |
762 |
1 |
self.nsr_topic.vca_status_refresh(session, nsr, filter_q) |
763 |
1 |
msg_args = self.msg.write.call_args[0] |
764 |
1 |
self.assertEqual(msg_args[1], "vca_status_refresh", "Wrong message action") |
765 |
1 |
self.nsr_topic.delete(session, nsr["_id"]) |
766 |
1 |
self.assertGreater( |
767 |
|
nsr["_admin"]["modified"], |
768 |
|
time() - time_delta, |
769 |
|
"Modified time is not changed.", |
770 |
|
) |
771 |
|
|
772 |
1 |
def test_delete_ns(self): |
773 |
1 |
self.db.create_list("nsrs", yaml.safe_load(db_nsrs_text)) |
774 |
1 |
self.nsr = self.db.get_list("nsrs")[0] |
775 |
1 |
self.nsr_id = self.nsr["_id"] |
776 |
1 |
self.db_set_one = self.db.set_one |
777 |
1 |
p_id = self.nsd_project |
778 |
1 |
p_other = "other_p" |
779 |
|
|
780 |
1 |
session = { |
781 |
|
"force": False, |
782 |
|
"admin": False, |
783 |
|
"public": None, |
784 |
|
"project_id": [p_id], |
785 |
|
"method": "delete", |
786 |
|
} |
787 |
1 |
session2 = { |
788 |
|
"force": False, |
789 |
|
"admin": False, |
790 |
|
"public": None, |
791 |
|
"project_id": [p_other], |
792 |
|
"method": "delete", |
793 |
|
} |
794 |
1 |
session_force = { |
795 |
|
"force": True, |
796 |
|
"admin": True, |
797 |
|
"public": None, |
798 |
|
"project_id": [], |
799 |
|
"method": "delete", |
800 |
|
} |
801 |
1 |
with self.subTest(i=1, t="Normal Deletion"): |
802 |
1 |
self.db.del_one = Mock() |
803 |
1 |
self.db.set_one = Mock() |
804 |
1 |
self.nsr_topic.delete(session, self.nsr_id) |
805 |
|
|
806 |
1 |
db_args_ro_nsrs = self.db.del_one.call_args_list[1][0] |
807 |
1 |
db_args = self.db.del_one.call_args_list[0][0] |
808 |
1 |
msg_args = self.msg.write.call_args[0] |
809 |
1 |
self.assertEqual( |
810 |
|
msg_args[0], self.nsr_topic.topic_msg, "Wrong message topic" |
811 |
|
) |
812 |
1 |
self.assertEqual(msg_args[1], "deleted", "Wrong message action") |
813 |
1 |
self.assertEqual(msg_args[2], {"_id": self.nsr_id}, "Wrong message content") |
814 |
1 |
self.assertEqual(db_args_ro_nsrs[0], "ro_nsrs", "Wrong DB topic") |
815 |
1 |
self.assertEqual(db_args[0], self.nsr_topic.topic, "Wrong DB topic") |
816 |
1 |
self.assertEqual(db_args[1]["_id"], self.nsr_id, "Wrong DB ID") |
817 |
1 |
self.assertEqual( |
818 |
|
db_args[1]["_admin.projects_read.cont"], [p_id], "Wrong DB filter" |
819 |
|
) |
820 |
1 |
self.db.set_one.assert_not_called() |
821 |
1 |
fs_del_calls = self.fs.file_delete.call_args_list |
822 |
1 |
self.assertEqual(fs_del_calls[0][0][0], self.nsr_id, "Wrong FS file id") |
823 |
1 |
with self.subTest(i=2, t="No delete because referenced by other project"): |
824 |
1 |
self.db_set_one( |
825 |
|
"nsrs", |
826 |
|
{"_id": self.nsr_id}, |
827 |
|
update_dict=None, |
828 |
|
push={ |
829 |
|
"_admin.projects_read": p_other, |
830 |
|
"_admin.projects_write": p_other, |
831 |
|
}, |
832 |
|
) |
833 |
1 |
self.db.del_one.reset_mock() |
834 |
1 |
self.db.set_one.reset_mock() |
835 |
1 |
self.msg.write.reset_mock() |
836 |
1 |
self.fs.file_delete.reset_mock() |
837 |
|
|
838 |
1 |
self.nsr_topic.delete(session2, self.nsr_id) |
839 |
1 |
self.db.del_one.assert_not_called() |
840 |
1 |
self.msg.write.assert_not_called() |
841 |
1 |
db_s1_args = self.db.set_one.call_args |
842 |
1 |
self.assertEqual(db_s1_args[0][0], self.nsr_topic.topic, "Wrong DB topic") |
843 |
1 |
self.assertEqual(db_s1_args[0][1]["_id"], self.nsr_id, "Wrong DB ID") |
844 |
1 |
self.assertIsNone( |
845 |
|
db_s1_args[1]["update_dict"], "Wrong DB update dictionary" |
846 |
|
) |
847 |
1 |
self.assertEqual( |
848 |
|
db_s1_args[1]["pull_list"], |
849 |
|
{"_admin.projects_read": [p_other], "_admin.projects_write": [p_other]}, |
850 |
|
"Wrong DB pull_list dictionary", |
851 |
|
) |
852 |
1 |
self.fs.file_delete.assert_not_called() |
853 |
1 |
with self.subTest(i=4, t="Delete with force and admin"): |
854 |
1 |
self.db.del_one.reset_mock() |
855 |
1 |
self.db.set_one.reset_mock() |
856 |
1 |
self.msg.write.reset_mock() |
857 |
1 |
self.fs.file_delete.reset_mock() |
858 |
1 |
self.nsr_topic.delete(session_force, self.nsr_id) |
859 |
|
|
860 |
1 |
db_args_ro_nsrs = self.db.del_one.call_args_list[1][0] |
861 |
1 |
db_args = self.db.del_one.call_args_list[0][0] |
862 |
1 |
msg_args = self.msg.write.call_args[0] |
863 |
1 |
self.assertEqual( |
864 |
|
msg_args[0], self.nsr_topic.topic_msg, "Wrong message topic" |
865 |
|
) |
866 |
1 |
self.assertEqual(msg_args[1], "deleted", "Wrong message action") |
867 |
1 |
self.assertEqual(msg_args[2], {"_id": self.nsr_id}, "Wrong message content") |
868 |
1 |
self.assertEqual(db_args_ro_nsrs[0], "ro_nsrs", "Wrong DB topic") |
869 |
1 |
self.assertEqual(db_args[0], self.nsr_topic.topic, "Wrong DB topic") |
870 |
1 |
self.assertEqual(db_args[1]["_id"], self.nsr_id, "Wrong DB ID") |
871 |
1 |
self.db.set_one.assert_not_called() |
872 |
1 |
fs_del_calls = self.fs.file_delete.call_args_list |
873 |
1 |
self.assertEqual(fs_del_calls[0][0][0], self.nsr_id, "Wrong FS file id") |
874 |
1 |
with self.subTest(i=3, t="Conflict on Delete - NS in INSTANTIATED state"): |
875 |
1 |
self.db_set_one( |
876 |
|
"nsrs", |
877 |
|
{"_id": self.nsr_id}, |
878 |
|
{"_admin.nsState": "INSTANTIATED"}, |
879 |
|
pull={ |
880 |
|
"_admin.projects_read": p_other, |
881 |
|
"_admin.projects_write": p_other, |
882 |
|
}, |
883 |
|
) |
884 |
1 |
self.db.del_one.reset_mock() |
885 |
1 |
self.db.set_one.reset_mock() |
886 |
1 |
self.msg.write.reset_mock() |
887 |
1 |
self.fs.file_delete.reset_mock() |
888 |
|
|
889 |
1 |
with self.assertRaises( |
890 |
|
EngineException, msg="Accepted NSR with nsState INSTANTIATED" |
891 |
|
) as e: |
892 |
1 |
self.nsr_topic.delete(session, self.nsr_id) |
893 |
1 |
self.assertEqual( |
894 |
|
e.exception.http_code, HTTPStatus.CONFLICT, "Wrong HTTP status code" |
895 |
|
) |
896 |
1 |
self.assertIn("INSTANTIATED", str(e.exception), "Wrong exception text") |
897 |
|
# TODOD with self.subTest(i=3, t='Conflict on Delete - NS in use by NSI'): |
898 |
|
|
899 |
1 |
with self.subTest(i=4, t="Non-existent NS"): |
900 |
1 |
self.db.del_one.reset_mock() |
901 |
1 |
self.db.set_one.reset_mock() |
902 |
1 |
self.msg.write.reset_mock() |
903 |
1 |
self.fs.file_delete.reset_mock() |
904 |
1 |
excp_msg = "Not found" |
905 |
1 |
with self.assertRaises( |
906 |
|
DbException, msg="Accepted non-existent NSD ID" |
907 |
|
) as e: |
908 |
1 |
self.nsr_topic.delete(session2, "other_id") |
909 |
1 |
self.assertEqual( |
910 |
|
e.exception.http_code, HTTPStatus.NOT_FOUND, "Wrong HTTP status code" |
911 |
|
) |
912 |
1 |
self.assertIn(excp_msg, str(e.exception), "Wrong exception text") |
913 |
1 |
self.assertIn("other_id", str(e.exception), "Wrong exception text") |
914 |
1 |
return |