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