Adapts PLA to new SOL006 NSD descriptors format
[osm/PLA.git] / osm_pla / test / test_server.py
1 # Copyright 2020 ArctosLabs Scandinavia AB
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12 # implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 import asyncio
16 import copy
17 import os
18 import sys
19 from unittest import TestCase, mock
20 from unittest.mock import Mock
21
22 import yaml
23
24 from osm_pla.placement.mznplacement import NsPlacementDataFactory, MznPlacementConductor
25 from pathlib import Path
26
27 # need to Mock the imports from osm_common made in Server and Config beforehand
28 sys.modules['osm_common'] = Mock()
29 from osm_pla.server.server import Server # noqa: E402
30 from osm_pla.config.config import Config # noqa: E402
31
32 nslcmop_record_wo_pinning = {'statusEnteredTime': 1574625718.8280587, 'startTime': 1574625718.8280587,
33 '_admin': {'created': 1574625718.8286533,
34 'projects_write': ['61e4bbab-9659-4abc-a01d-ba3a307becf9'],
35 'worker': 'e5121e773e8b', 'modified': 1574625718.8286533,
36 'projects_read': ['61e4bbab-9659-4abc-a01d-ba3a307becf9']},
37 'operationState': 'PROCESSING', 'nsInstanceId': '45f588bd-5bf4-4181-b13b-f16a55a23be4',
38 'lcmOperationType': 'instantiate', 'isCancelPending': False,
39 'id': 'a571b1de-19e5-48bd-b252-ba0ad7d540c9',
40 '_id': 'a571b1de-19e5-48bd-b252-ba0ad7d540c9',
41 'isAutomaticInvocation': False,
42 'links': {'nsInstance': '/osm/nslcm/v1/ns_instances/45f588bd-5bf4-4181-b13b-f16a55a23be4',
43 'self': '/osm/nslcm/v1/ns_lcm_op_occs/a571b1de-19e5-48bd-b252-ba0ad7d540c9'},
44 'operationParams': {'vimAccountId': 'eb553051-5b6c-4ad6-939b-2ad23bd82e57',
45 'lcmOperationType': 'instantiate', 'nsDescription': 'just a test',
46 'nsdId': '0f4e658f-62a6-4f73-8623-270e8f0a18bc',
47 'nsName': 'ThreeNsd plain placement', 'ssh_keys': [],
48 'validVimAccounts': ['eb553051-5b6c-4ad6-939b-2ad23bd82e57',
49 '576bbe0a-b95d-4ced-a63e-f387f8e6e2ce',
50 '3d1ffc5d-b36d-4f69-8356-7f59c740ca2f',
51 'db54dcd4-9fc4-441c-8820-17bce0aef2c3'],
52 'nsr_id': '45f588bd-5bf4-4181-b13b-f16a55a23be4',
53 'placement-engine': 'PLA',
54 'nsInstanceId': '45f588bd-5bf4-4181-b13b-f16a55a23be4'}}
55
56 nslcmop_record_w_pinning = {'statusEnteredTime': 1574627411.420499, 'startTime': 1574627411.420499,
57 '_admin': {'created': 1574627411.4209971,
58 'projects_write': ['61e4bbab-9659-4abc-a01d-ba3a307becf9'],
59 'worker': 'e5121e773e8b', 'modified': 1574627411.4209971,
60 'projects_read': ['61e4bbab-9659-4abc-a01d-ba3a307becf9']},
61 'operationState': 'PROCESSING',
62 'nsInstanceId': '61587478-ea25-44eb-9f13-7005046ddb08', 'lcmOperationType': 'instantiate',
63 'isCancelPending': False, 'id': '80f95a17-6fa7-408d-930f-40aa4589d074',
64 '_id': '80f95a17-6fa7-408d-930f-40aa4589d074',
65 'isAutomaticInvocation': False,
66 'links': {
67 'nsInstance': '/osm/nslcm/v1/ns_instances/61587478-ea25-44eb-9f13-7005046ddb08',
68 'self': '/osm/nslcm/v1/ns_lcm_op_occs/80f95a17-6fa7-408d-930f-40aa4589d074'},
69 'operationParams': {
70 'vimAccountId': '576bbe0a-b95d-4ced-a63e-f387f8e6e2ce',
71 'nsr_id': '61587478-ea25-44eb-9f13-7005046ddb08',
72 'nsDescription': 'default description', 'nsdId': '0f4e658f-62a6-4f73-8623-270e8f0a18bc',
73 'validVimAccounts': [
74 'eb553051-5b6c-4ad6-939b-2ad23bd82e57', '576bbe0a-b95d-4ced-a63e-f387f8e6e2ce',
75 '3d1ffc5d-b36d-4f69-8356-7f59c740ca2f',
76 'db54dcd4-9fc4-441c-8820-17bce0aef2c3'], 'nsName': 'ThreeVnfTest2',
77 'wimAccountId': False, 'vnf': [
78 {'vimAccountId': '3d1ffc5d-b36d-4f69-8356-7f59c740ca2f', 'member-vnf-index': '1'}],
79 'placementEngine': 'PLA',
80 'nsInstanceId': '61587478-ea25-44eb-9f13-7005046ddb08',
81 'lcmOperationType': 'instantiate'}}
82
83 nslcmop_record_w_pinning_and_order_constraints = {
84 'links': {'nsInstance': '/osm/nslcm/v1/ns_instances/7c4c3d94-ebb2-44e8-b236-d876b118434e',
85 'self': '/osm/nslcm/v1/ns_lcm_op_occs/fd7c9e15-38aa-4fc5-913c-417b26859fb0'},
86 'id': 'fd7c9e15-38aa-4fc5-913c-417b26859fb0', 'operationState': 'PROCESSING', 'isAutomaticInvocation': False,
87 'nsInstanceId': '7c4c3d94-ebb2-44e8-b236-d876b118434e', '_id': 'fd7c9e15-38aa-4fc5-913c-417b26859fb0',
88 'isCancelPending': False, 'startTime': 1574772631.6932728, 'statusEnteredTime': 1574772631.6932728,
89 'lcmOperationType': 'instantiate',
90 'operationParams': {'placementEngine': 'PLA',
91 'placement-constraints': {
92 'vld-constraints': [{
93 'id': 'three_vnf_constrained_vld_1',
94 'link-constraints': {
95 'latency': 120,
96 'jitter': 20}},
97 {
98 'link_constraints': {
99 'latency': 120,
100 'jitter': 20},
101 'id': 'three_vnf_constrained_nsd_vld_2'}]},
102 'nsName': 'ThreeVnfTest2',
103 'nsDescription': 'default description',
104 'nsr_id': '7c4c3d94-ebb2-44e8-b236-d876b118434e',
105 'nsdId': '0f4e658f-62a6-4f73-8623-270e8f0a18bc',
106 'validVimAccounts': ['eb553051-5b6c-4ad6-939b-2ad23bd82e57',
107 '576bbe0a-b95d-4ced-a63e-f387f8e6e2ce',
108 '3d1ffc5d-b36d-4f69-8356-7f59c740ca2f',
109 'db54dcd4-9fc4-441c-8820-17bce0aef2c3'],
110 'wimAccountId': False,
111 'vnf': [{'member-vnf-index': '3', 'vimAccountId': '3d1ffc5d-b36d-4f69-8356-7f59c740ca2f'}],
112 'nsInstanceId': '7c4c3d94-ebb2-44e8-b236-d876b118434e',
113 'lcmOperationType': 'instantiate',
114 'vimAccountId': '576bbe0a-b95d-4ced-a63e-f387f8e6e2ce'},
115 '_admin': {'projects_read': ['61e4bbab-9659-4abc-a01d-ba3a307becf9'], 'modified': 1574772631.693885,
116 'projects_write': ['61e4bbab-9659-4abc-a01d-ba3a307becf9'], 'created': 1574772631.693885,
117 'worker': 'e5121e773e8b'}}
118
119 list_of_vims = [{"_id": "73cd1a1b-333e-4e29-8db2-00d23bd9b644", "vim_user": "admin", "name": "OpenStack1",
120 "vim_url": "http://10.234.12.47:5000/v3", "vim_type": "openstack", "vim_tenant_name": "osm_demo",
121 "vim_password": "O/mHomfXPmCrTvUbYXVoyg==", "schema_version": "1.1",
122 "_admin": {"modified": 1565597984.3155663,
123 "deployed": {"RO": "f0c1b516-bcd9-11e9-bb73-02420aff0030",
124 "RO-account": "f0d45496-bcd9-11e9-bb73-02420aff0030"},
125 "projects_write": ["admin"], "operationalState": "ENABLED", "detailed-status": "Done",
126 "created": 1565597984.3155663, "projects_read": ["admin"]},
127 "config": {}},
128 {"_id": "684165ea-2cf9-4fbd-ac22-8464ca07d1d8", "vim_user": "admin",
129 "name": "OpenStack2", "vim_url": "http://10.234.12.44:5000/v3",
130 "vim_tenant_name": "osm_demo", "vim_password": "Rw7gln9liP4ClMyHd5OFsw==",
131 "description": "Openstack on NUC", "vim_type": "openstack",
132 "admin": {"modified": 1566474766.7288046,
133 "deployed": {"RO": "5bc59656-c4d3-11e9-b1e5-02420aff0006",
134 "RO-account": "5bd772e0-c4d3-11e9-b1e5-02420aff0006"},
135 "projects_write": ["admin"], "operationalState": "ENABLED",
136 "detailed-status": "Done", "created": 1566474766.7288046,
137 "projects_read": ["admin"]},
138 "config": {}, "schema_version": "1.1"},
139 {"_id": "8460b670-31cf-4fae-9f3e-d0dd6c57b61e", "vim_user": "admin", "name": "OpenStack1",
140 "vim_url": "http://10.234.12.47:5000/v3", "vim_type": "openstack",
141 "vim_tenant_name": "osm_demo", "vim_password": "NsgJJDlCdKreX30FQFNz7A==",
142 "description": "Openstack on Dell",
143 "_admin": {"modified": 1566992449.5942867,
144 "deployed": {"RO": "aed94f86-c988-11e9-bb38-02420aff0088",
145 "RO-account": "aee72fac-c988-11e9-bb38-02420aff0088"},
146 "projects_write": ["0a5d0c5b-7e08-48a1-a686-642a038bbd70"],
147 "operationalState": "ENABLED", "detailed-status": "Done", "created": 1566992449.5942867,
148 "projects_read": ["0a5d0c5b-7e08-48a1-a686-642a038bbd70"]}, "config": {},
149 "schema_version": "1.1"},
150 {"_id": "9b8b5268-acb7-4893-b494-a77656b418f2",
151 "vim_user": "admin", "name": "OpenStack2",
152 "vim_url": "http://10.234.12.44:5000/v3",
153 "vim_type": "openstack", "vim_tenant_name": "osm_demo",
154 "vim_password": "AnAV3xtoiwwdnAfv0KahSw==",
155 "description": "Openstack on NUC",
156 "_admin": {"modified": 1566992484.9190753,
157 "deployed": {"RO": "c3d61158-c988-11e9-bb38-02420aff0088",
158 "RO-account": "c3ec973e-c988-11e9-bb38-02420aff0088"},
159 "projects_write": ["0a5d0c5b-7e08-48a1-a686-642a038bbd70"],
160 "operationalState": "ENABLED", "detailed-status": "Done",
161 "created": 1566992484.9190753,
162 "projects_read": ["0a5d0c5b-7e08-48a1-a686-642a038bbd70"]},
163 "config": {}, "schema_version": "1.1"},
164 {"_id": "3645f215-f32d-4355-b5ab-df0a2e2233c3", "vim_user": "admin", "name": "OpenStack3",
165 "vim_url": "http://10.234.12.46:5000/v3", "vim_tenant_name": "osm_demo",
166 "vim_password": "XkG2w8e8/DiuohCFNp0+lQ==", "description": "Openstack on NUC",
167 "vim_type": "openstack",
168 "_admin": {"modified": 1567421247.7016313,
169 "deployed": {"RO": "0e80f6a2-cd6f-11e9-bb50-02420aff00b6",
170 "RO-account": "0e974524-cd6f-11e9-bb50-02420aff00b6"},
171 "projects_write": ["0a5d0c5b-7e08-48a1-a686-642a038bbd70"],
172 "operationalState": "ENABLED", "detailed-status": "Done",
173 "created": 1567421247.7016313,
174 "projects_read": ["0a5d0c5b-7e08-48a1-a686-642a038bbd70"]},
175 "schema_version": "1.1", "config": {}},
176 {"_id": "53f8f2bb-88b5-4bf9-babf-556698b5261f",
177 "vim_user": "admin", "name": "OpenStack4",
178 "vim_url": "http://10.234.12.43:5000/v3",
179 "vim_tenant_name": "osm_demo",
180 "vim_password": "GLrgVn8fMVneXMZq1r4yVA==",
181 "description": "Openstack on NUC",
182 "vim_type": "openstack",
183 "_admin": {"modified": 1567421296.1576457,
184 "deployed": {
185 "RO": "2b43c756-cd6f-11e9-bb50-02420aff00b6",
186 "RO-account": "2b535aea-cd6f-11e9-bb50-02420aff00b6"},
187 "projects_write": [
188 "0a5d0c5b-7e08-48a1-a686-642a038bbd70"],
189 "operationalState": "ENABLED",
190 "detailed-status": "Done",
191 "created": 1567421296.1576457,
192 "projects_read": [
193 "0a5d0c5b-7e08-48a1-a686-642a038bbd70"]},
194 "schema_version": "1.1", "config": {}}]
195
196 # FIXME this is not correct re mgmt-network setting.
197 nsd_from_db = {"_id": "15fc1941-f095-4cd8-af2d-1000bd6d9eaa",
198 "_admin": {"modified": 1567672251.7531693,
199 "storage": {"pkg-dir": "ns_constrained_nsd", "fs": "local",
200 "descriptor": "ns_constrained_nsd/ns_constrained_nsd.yaml",
201 "zipfile": "package.tar.gz",
202 "folder": "15fc1941-f095-4cd8-af2d-1000bd6d9eaa", "path": "/app/storage/"},
203 "onboardingState": "ONBOARDED", "usageState": "NOT_IN_USE",
204 "projects_write": ["0a5d0c5b-7e08-48a1-a686-642a038bbd70"], "operationalState": "ENABLED",
205 "userDefinedData": {}, "created": 1567672251.7531693,
206 "projects_read": ["0a5d0c5b-7e08-48a1-a686-642a038bbd70"]},
207 'id': 'three_vnf_constrained_nsd_low',
208 'name': 'three_vnf_constrained_nsd_low',
209 'description': 'Placement constraints NSD',
210 'designer': 'ArctosLabs',
211 'version': '1.0',
212 'vnfd-id': ['cirros_vnfd_v2'],
213 'df': [{
214 'id': 'default-df',
215 'vnf-profile': [{
216 'id': 'one',
217 'vnfd-id': 'cirros_vnfd_v2',
218 'virtual-link-connectivity': [{
219 'virtual-link-profile-id': 'three_vnf_constrained_nsd_low_vld1',
220 'constituent-cpd-id': [{
221 'constituent-base-element-id': 'one',
222 'constituent-cpd-id': 'vnf-cp0-ext'
223 }]
224 }]
225 }, {
226 'id': 'two',
227 'vnfd-id': 'cirros_vnfd_v2',
228 'virtual-link-connectivity': [{
229 'virtual-link-profile-id': 'three_vnf_constrained_nsd_low_vld1',
230 'constituent-cpd-id': [{
231 'constituent-base-element-id': 'two',
232 'constituent-cpd-id': 'vnf-cp0-ext'
233 }]
234 }, {
235 'virtual-link-profile-id': 'three_vnf_constrained_nsd_low_vld2',
236 'constituent-cpd-id': [{
237 'constituent-base-element-id': 'two',
238 'constituent-cpd-id': 'vnf-cp0-ext'
239 }]
240 }]
241 }, {
242 'id': 'three',
243 'vnfd-id': 'cirros_vnfd_v2',
244 'virtual-link-connectivity': [{
245 'virtual-link-profile-id': 'three_vnf_constrained_nsd_low_vld2',
246 'constituent-cpd-id': [{
247 'constituent-base-element-id': 'three',
248 'constituent-cpd-id': 'vnf-cp0-ext'
249 }]
250 }]
251 }]
252 }],
253 'virtual-link-desc': [{
254 'id': 'three_vnf_constrained_nsd_low_vld1',
255 'mgmt-network': True,
256 'vim-network-name': 'external'
257 }, {
258 'id': 'three_vnf_constrained_nsd_low_vld2',
259 'mgmt-network': True,
260 'vim-network-name': 'lanretxe'
261 }],
262 }
263
264
265 ######################################################
266 # These are helper functions to handle unittest of asyncio.
267 # Inspired by: https://blog.miguelgrinberg.com/post/unit-testing-asyncio-code
268 def _run(co_routine):
269 return asyncio.get_event_loop().run_until_complete(co_routine)
270
271
272 def _async_mock(*args, **kwargs):
273 m = mock.MagicMock(*args, **kwargs)
274
275 async def mock_coro(*args, **kwargs):
276 return m(*args, **kwargs)
277
278 mock_coro.mock = m
279 return mock_coro
280
281
282 ######################################################
283
284 class TestServer(TestCase):
285
286 def _produce_ut_vim_accounts_info(self, list_of_vims):
287 """
288 FIXME temporary, we will need more control over vim_urls and _id for test purpose - make a generator
289 :return: vim_url and _id as dict, i.e. extract these from vim_accounts data
290 """
291 return {_['name']: _['_id'] for _ in list_of_vims}
292
293 def _produce_ut_vnf_price_list(self):
294 price_list_file = "vnf_price_list.yaml"
295 with open(str(Path(price_list_file))) as pl_fd:
296 price_list_data = yaml.safe_load_all(pl_fd)
297 return {i['vnfd']: {i1['vim_name']: i1['price'] for i1 in i['prices']} for i in next(price_list_data)}
298
299 def _populate_pil_info(self, file):
300 """
301 FIXME we need more control over content in pil information - more files or generator and data
302 Note str(Path()) is a 3.5 thing
303 """
304 with open(str(Path(file))) as pp_fd:
305 test_data = yaml.safe_load_all(pp_fd)
306 return next(test_data)
307
308 @mock.patch.object(Config, '_read_config_file')
309 @mock.patch.object(Config, 'get', side_effect=['doesnotmatter', 'memory', 'memory', 'local', 'doesnotmatter'])
310 def serverSetup(self, mock_get, mock__read_config_file):
311 """
312 Helper that returns a Server object
313 :return:
314 """
315 cfg = Config(None)
316 return Server(cfg)
317
318 def _adjust_path(self, file):
319 """In case we are not running from test directory,
320 then assume we are in top level directory (e.g. running from tox) and adjust file path accordingly"""
321 path_component = '/osm_pla/test/'
322 real_path = os.path.realpath(file)
323 if path_component not in real_path:
324 return os.path.dirname(real_path) + path_component + os.path.basename(real_path)
325 else:
326 return real_path
327
328 def test__get_nslcmop(self):
329 server = self.serverSetup()
330 server.db = Mock()
331 _ = server._get_nslcmop(nslcmop_record_wo_pinning["id"])
332 server.db.get_one.assert_called_with("nslcmops", {'_id': nslcmop_record_wo_pinning["id"]})
333
334 def test__get_nsd(self): # OK
335 server = self.serverSetup()
336 server.db = Mock()
337 _ = server._get_nsd(nslcmop_record_wo_pinning['operationParams']['nsdId'])
338 server.db.get_one.assert_called_with("nsds", {'_id': nslcmop_record_wo_pinning['operationParams']['nsdId']})
339
340 def test__create_vnf_id_maps(self):
341 server = self.serverSetup()
342 server.db = Mock()
343 expected_mvi2mzn = {'one': 'VNF0', 'two': 'VNF1', 'three': 'VNF2'}
344 expected_mzn2mvi = {'VNF0': 'one', 'VNF1': 'two', 'VNF2': 'three'}
345
346 nsd_for_test = copy.deepcopy(nsd_from_db)
347 mvi2mzn, mzn2mvi = server._create_vnf_id_maps(nsd_for_test)
348
349 self.assertDictEqual(expected_mvi2mzn, mvi2mzn, 'Faulty mzn2member-vnf-index mapping')
350 self.assertDictEqual(expected_mzn2mvi, mzn2mvi, 'Faulty mzn2member-vnf-index mapping')
351
352 def test__get_vim_accounts(self): # OK
353 server = self.serverSetup()
354 server.db = Mock()
355 _ = server._get_vim_accounts(nslcmop_record_wo_pinning['operationParams']['validVimAccounts'])
356 server.db.get_list.assert_called_with('vim_accounts',
357 {'_id': nslcmop_record_wo_pinning['operationParams']['validVimAccounts']})
358
359 def test__get_vnf_price_list(self):
360 server = self.serverSetup()
361 pl1 = server._get_vnf_price_list(Path(self._adjust_path('./vnf_price_list.yaml')))
362 self.assertIs(type(pl1), dict, "price list not a dictionary")
363 for k, v in pl1.items():
364 self.assertIs(type(v), dict, "price list values not a dict")
365
366 pl2 = server._get_vnf_price_list(Path(self._adjust_path('./vnf_price_list_keys.yaml')), 'hackfest_project_a')
367 self.assertIs(type(pl2), dict, "price list not a dictionary")
368 for k, v in pl2.items():
369 self.assertIs(type(v), dict, "price list values not a dict")
370 self.assertEqual(pl1, pl2, "non-project and project price lists differ")
371
372 def test__get_pil_info(self):
373 server = self.serverSetup()
374 ppi = server._get_pil_info(Path(self._adjust_path('./pil_price_list.yaml')))
375 self.assertIs(type(ppi), dict, "pil is not a dict")
376 self.assertIn('pil', ppi.keys(), "pil has no pil key")
377 self.assertIs(type(ppi['pil']), list, "pil does not contain a list")
378 # check for expected keys
379 expected_keys = {'pil_description', 'pil_price', 'pil_latency', 'pil_jitter', 'pil_endpoints'}
380 self.assertEqual(expected_keys, ppi['pil'][0].keys(), 'expected keys not found')
381
382 def test_handle_kafka_command(self): # OK
383 server = self.serverSetup()
384 server.loop.create_task = Mock()
385 server.handle_kafka_command('pli', 'get_placement', {})
386 server.loop.create_task.assert_not_called()
387 server.loop.create_task.reset_mock()
388 server.handle_kafka_command('pla', 'get_placement', {'nslcmopId': nslcmop_record_wo_pinning["id"]})
389 self.assertTrue(server.loop.create_task.called, 'create_task not called')
390 args, kwargs = server.loop.create_task.call_args
391 self.assertIn('Server.get_placement', str(args[0]), 'get_placement not called')
392
393 @mock.patch.object(NsPlacementDataFactory, '__init__', lambda x0, x1, x2, x3, x4, x5, x6: None)
394 @mock.patch.object(MznPlacementConductor, 'do_placement_computation')
395 @mock.patch.object(NsPlacementDataFactory, 'create_ns_placement_data')
396 @mock.patch.object(Server, '_get_vim_accounts')
397 @mock.patch.object(Server, '_get_nsd')
398 @mock.patch.object(Server, '_get_nslcmop')
399 @mock.patch.object(Server, '_get_vnf_price_list')
400 @mock.patch.object(Server, '_get_pil_info')
401 @mock.patch.object(Server, '_get_projects')
402 def test_get_placement(self, mock_get_projects, mock_get_pil_info, mock_get_vnf_price_list, mock__get_nslcmop,
403 mock__get_nsd,
404 mock__get_vim_accounts,
405 mock_create_ns_placement_data,
406 mock_do_placement_computation):
407 """
408 run _get_placement and check that things get called as expected
409 :return:
410 """
411 placement_ret_val = [{'vimAccountId': 'bbbbbbbb-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': 'VNF0'},
412 {'vimAccountId': 'aaaaaaaa-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': 'VNF1'},
413 {'vimAccountId': 'aaaaaaaa-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': 'VNF2'}]
414 server = self.serverSetup()
415
416 server.msgBus.aiowrite = _async_mock()
417 nsd_for_test = copy.deepcopy(nsd_from_db)
418 mock__get_nsd.return_value = nsd_for_test
419 mock__get_vim_accounts.return_value = list_of_vims
420
421 # FIXME need update to match nslcmop, not for test but for consistency
422 mock_do_placement_computation.return_value = placement_ret_val
423 _run(server.get_placement(nslcmop_record_wo_pinning['id']))
424
425 self.assertTrue(mock_get_projects.called, '_get_projects not called as expected')
426 self.assertTrue(mock_get_vnf_price_list.called, '_get_vnf_price_list not called as expected')
427 self.assertTrue(mock_get_pil_info.called, '_get_pil_info not called as expected')
428 self.assertTrue(mock__get_nslcmop.called, '_get_nslcmop not called as expected')
429 # mock_get_nsd.assert_called_once() assert_called_once() for python > 3.5
430 self.assertTrue(mock__get_nsd.called, 'get_nsd not called as expected')
431 # mock_get_enabled_vims.assert_called_once() assert_called_once() for python > 3.5
432 self.assertTrue(mock__get_vim_accounts.called, 'get_vim_accounts not called as expected')
433 # mock_create_ns_placement_data.assert_called_once() assert_called_once() for python > 3.5
434 self.assertTrue(mock_create_ns_placement_data.called, 'create_ns_placement_data not called as expected')
435 # mock_do_placement_computation.assert_called_once() assert_called_once() for python > 3.5
436 self.assertTrue(mock_do_placement_computation.called, 'do_placement_computation not called as expected')
437 self.assertTrue(server.msgBus.aiowrite.mock.called)
438
439 args, kwargs = server.msgBus.aiowrite.mock.call_args
440 self.assertTrue(len(args) == 3, 'invalid format')
441 self.assertEqual('pla', args[0], 'topic invalid')
442 self.assertEqual('placement', args[1], 'message invalid')
443 # extract placement result and check content
444 rsp_payload = args[2]
445
446 expected_rsp_keys = {'placement'}
447 self.assertEqual(expected_rsp_keys, set(rsp_payload.keys()), "placement response missing keys")
448 self.assertIs(type(rsp_payload['placement']), dict, 'placement not a dict')
449
450 expected_placement_keys = {'vnf', 'nslcmopId'}
451 self.assertEqual(expected_placement_keys, set(rsp_payload['placement']), "placement keys invalid")
452
453 vim_account_candidates = [e['vimAccountId'] for e in placement_ret_val]
454
455 self.assertEqual(nslcmop_record_wo_pinning['id'], rsp_payload['placement']['nslcmopId'], "nslcmopId invalid")
456
457 self.assertIs(type(rsp_payload['placement']['vnf']), list, 'vnf not a list')
458 expected_vnf_keys = {'vimAccountId', 'member-vnf-index'}
459 self.assertEqual(expected_vnf_keys, set(rsp_payload['placement']['vnf'][0]), "placement['vnf'] missing keys")
460 self.assertIn(rsp_payload['placement']['vnf'][0]['vimAccountId'], vim_account_candidates,
461 "vimAccountId invalid")
462
463 @mock.patch.object(NsPlacementDataFactory, '__init__', lambda x0, x1, x2, x3, x4, x5, x6: None)
464 @mock.patch.object(MznPlacementConductor, 'do_placement_computation')
465 @mock.patch.object(NsPlacementDataFactory, 'create_ns_placement_data')
466 @mock.patch.object(Server, '_get_vim_accounts')
467 @mock.patch.object(Server, '_get_nsd')
468 @mock.patch.object(Server, '_get_nslcmop')
469 @mock.patch.object(Server, '_get_vnf_price_list')
470 @mock.patch.object(Server, '_get_pil_info')
471 @mock.patch.object(Server, '_get_projects')
472 def test_get_placement_with_pinning(self, mock_get_projects, mock_get_pil_info, mock_get_vnf_price_list,
473 mock__get_nslcmop,
474 mock__get_nsd, mock__get_vim_accounts,
475 mock_create_ns_placement_data,
476 mock_do_placement_computation):
477 """
478 run _get_placement and check that things get called as expected
479 :return:
480 """
481 placement_ret_val = [{'vimAccountId': 'bbbbbbbb-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': 'VNF0'},
482 {'vimAccountId': 'aaaaaaaa-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': 'VNF1'},
483 {'vimAccountId': 'aaaaaaaa-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': 'VNF2'}]
484 server = self.serverSetup()
485
486 server.msgBus.aiowrite = _async_mock()
487 nsd_for_test = copy.deepcopy(nsd_from_db)
488 mock__get_nsd.return_value = nsd_for_test
489 mock__get_vim_accounts.return_value = list_of_vims
490
491 # FIXME need update to match nslcmop, not for test but for consistency
492 mock_do_placement_computation.return_value = placement_ret_val
493 _run(server.get_placement(nslcmop_record_w_pinning['id']))
494
495 self.assertTrue((mock_get_projects.called, '_get_projects not called as expected'))
496 self.assertTrue(mock_get_vnf_price_list.called, '_get_vnf_price_list not called as expected')
497 self.assertTrue(mock_get_pil_info.called, '_get_pil_info not called as expected')
498 self.assertTrue(mock__get_nslcmop.called, '_get_nslcmop not called as expected')
499 # mock_get_nsd.assert_called_once() assert_called_once() for python > 3.5
500 self.assertTrue(mock__get_nsd.called, 'get_nsd not called as expected')
501 # mock_get_enabled_vims.assert_called_once() assert_called_once() for python > 3.5
502 self.assertTrue(mock__get_vim_accounts.called, 'get_vim_accounts not called as expected')
503 # mock_create_ns_placement_data.assert_called_once() assert_called_once() for python > 3.5
504 self.assertTrue(mock_create_ns_placement_data.called, 'create_ns_placement_data not called as expected')
505 # mock_do_placement_computation.assert_called_once() assert_called_once() for python > 3.5
506 self.assertTrue(mock_do_placement_computation.called, 'do_placement_computation not called as expected')
507 self.assertTrue(server.msgBus.aiowrite.mock.called)
508
509 args, kwargs = server.msgBus.aiowrite.mock.call_args
510 self.assertTrue(len(args) == 3, 'invalid format')
511 self.assertEqual('pla', args[0], 'topic invalid')
512 self.assertEqual('placement', args[1], 'message invalid')
513 # extract placement result and check content
514 rsp_payload = args[2]
515
516 expected_rsp_keys = {'placement'}
517 self.assertEqual(expected_rsp_keys, set(rsp_payload.keys()), "placement response missing keys")
518 self.assertIs(type(rsp_payload['placement']), dict, 'placement not a dict')
519
520 expected_placement_keys = {'vnf', 'nslcmopId'}
521 self.assertEqual(expected_placement_keys, set(rsp_payload['placement']), "placement keys invalid")
522
523 vim_account_candidates = [e['vimAccountId'] for e in placement_ret_val]
524
525 self.assertEqual(nslcmop_record_w_pinning['id'], rsp_payload['placement']['nslcmopId'], "nslcmopId invalid")
526
527 self.assertIs(type(rsp_payload['placement']['vnf']), list, 'vnf not a list')
528 expected_vnf_keys = {'vimAccountId', 'member-vnf-index'}
529 self.assertEqual(expected_vnf_keys, set(rsp_payload['placement']['vnf'][0]), "placement['vnf'] missing keys")
530 self.assertIn(rsp_payload['placement']['vnf'][0]['vimAccountId'], vim_account_candidates,
531 "vimAccountId invalid")
532
533 # Note: does not mock reading of price list and pil_info
534 @mock.patch.object(NsPlacementDataFactory, '__init__', lambda x0, x1, x2, x3, x4, x5: None)
535 @mock.patch.object(MznPlacementConductor, 'do_placement_computation')
536 @mock.patch.object(NsPlacementDataFactory, 'create_ns_placement_data')
537 @mock.patch.object(Server, '_get_vim_accounts')
538 @mock.patch.object(Server, '_get_nsd')
539 @mock.patch.object(Server, '_get_nslcmop')
540 def test_get_placement_w_exception(self, mock__get_nslcmop,
541 mock__get_nsd,
542 mock__get_vim_accounts,
543 mock_create_ns_placement_data,
544 mock_do_placement_computation):
545 """
546 check that raised exceptions are handled and response provided accordingly
547 """
548 server = self.serverSetup()
549
550 server.msgBus.aiowrite = _async_mock()
551 nsd_for_test = copy.deepcopy(nsd_from_db)
552 mock__get_nsd.return_value = nsd_for_test
553 mock__get_nsd.side_effect = RuntimeError('kaboom!')
554 mock__get_vim_accounts.return_value = list_of_vims
555 mock_do_placement_computation.return_value = \
556 [{'vimAccountId': 'bbbbbbbb-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '1'},
557 {'vimAccountId': 'aaaaaaaa-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '2'},
558 {'vimAccountId': 'aaaaaaaa-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '3'}]
559
560 _run(server.get_placement(nslcmop_record_w_pinning['id']))
561 self.assertTrue(server.msgBus.aiowrite.mock.called)
562 args, kwargs = server.msgBus.aiowrite.mock.call_args
563 rsp_payload = args[2]
564 expected_keys = {'placement'}
565 self.assertEqual(expected_keys, set(rsp_payload.keys()), "placement response missing keys")
566 self.assertIs(type(rsp_payload['placement']['vnf']), list, 'vnf not a list')
567 self.assertEqual([], rsp_payload['placement']['vnf'], 'vnf list not empty')
568 self.assertEqual(nslcmop_record_w_pinning['id'], rsp_payload['placement']['nslcmopId'], "nslcmopId invalid")