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