Bug 1212
[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", "short-name": "three_vnf_constrained_nsd_low",
198 "name": "three_vnf_constrained_nsd_low", "version": "1.0",
199 "description": "Placement constraints NSD",
200 "_admin": {"modified": 1567672251.7531693,
201 "storage": {"pkg-dir": "ns_constrained_nsd", "fs": "local",
202 "descriptor": "ns_constrained_nsd/ns_constrained_nsd.yaml",
203 "zipfile": "package.tar.gz",
204 "folder": "15fc1941-f095-4cd8-af2d-1000bd6d9eaa", "path": "/app/storage/"},
205 "onboardingState": "ONBOARDED", "usageState": "NOT_IN_USE",
206 "projects_write": ["0a5d0c5b-7e08-48a1-a686-642a038bbd70"], "operationalState": "ENABLED",
207 "userDefinedData": {}, "created": 1567672251.7531693,
208 "projects_read": ["0a5d0c5b-7e08-48a1-a686-642a038bbd70"]},
209 "constituent-vnfd": [{"vnfd-id-ref": "cirros_vnfd_v2", "member-vnf-index": "one"},
210 {"vnfd-id-ref": "cirros_vnfd_v2", "member-vnf-index": "two"},
211 {"vnfd-id-ref": "cirros_vnfd_v2", "member-vnf-index": "three"}],
212 "id": "three_vnf_constrained_nsd_low", "vendor": "ArctosLabs",
213 "vld": [{"type": "ELAN", "short-name": "ns_constrained_nsd_low_vld1",
214 "link-constraint": [{"constraint-type": "LATENCY", "value": "100"},
215 {"constraint-type": "JITTER", "value": "30"}],
216 "vim-network-name": "external", "mgmt-network": True,
217 "id": "three_vnf_constrained_nsd_low_vld1",
218 "vnfd-connection-point-ref": [
219 {"vnfd-id-ref": "cirros_vnfd_v2", "member-vnf-index-ref": "one",
220 "vnfd-connection-point-ref": "vnf-cp0"},
221 {"vnfd-id-ref": "cirros_vnfd_v2",
222 "member-vnf-index-ref": "two",
223 "vnfd-connection-point-ref": "vnf-cp0"}],
224 "name": "ns_constrained_nsd_vld1"},
225 {"type": "ELAN", "short-name": "ns_constrained_nsd_low_vld2",
226 "link-constraint": [{"constraint-type": "LATENCY", "value": "50"},
227 {"constraint-type": "JITTER", "value": "30"}],
228 "vim-network-name": "lanretxe", "mgmt-network": True,
229 "id": "three_vnf_constrained_nsd_low_vld2",
230 "vnfd-connection-point-ref": [
231 {"vnfd-id-ref": "cirros_vnfd_v2", "member-vnf-index-ref": "two",
232 "vnfd-connection-point-ref": "vnf-cp0"},
233 {"vnfd-id-ref": "cirros_vnfd_v2", "member-vnf-index-ref": "three",
234 "vnfd-connection-point-ref": "vnf-cp0"}],
235 "name": "ns_constrained_nsd_vld2"}]}
236
237
238 ######################################################
239 # These are helper functions to handle unittest of asyncio.
240 # Inspired by: https://blog.miguelgrinberg.com/post/unit-testing-asyncio-code
241 def _run(co_routine):
242 return asyncio.get_event_loop().run_until_complete(co_routine)
243
244
245 def _async_mock(*args, **kwargs):
246 m = mock.MagicMock(*args, **kwargs)
247
248 async def mock_coro(*args, **kwargs):
249 return m(*args, **kwargs)
250
251 mock_coro.mock = m
252 return mock_coro
253
254
255 ######################################################
256
257 class TestServer(TestCase):
258
259 def _produce_ut_vim_accounts_info(self, list_of_vims):
260 """
261 FIXME temporary, we will need more control over vim_urls and _id for test purpose - make a generator
262 :return: vim_url and _id as dict, i.e. extract these from vim_accounts data
263 """
264 return {_['name']: _['_id'] for _ in list_of_vims}
265
266 def _produce_ut_vnf_price_list(self):
267 price_list_file = "vnf_price_list.yaml"
268 with open(str(Path(price_list_file))) as pl_fd:
269 price_list_data = yaml.safe_load_all(pl_fd)
270 return {i['vnfd']: {i1['vim_name']: i1['price'] for i1 in i['prices']} for i in next(price_list_data)}
271
272 def _populate_pil_info(self, file):
273 """
274 FIXME we need more control over content in pil information - more files or generator and data
275 Note str(Path()) is a 3.5 thing
276 """
277 with open(str(Path(file))) as pp_fd:
278 test_data = yaml.safe_load_all(pp_fd)
279 return next(test_data)
280
281 @mock.patch.object(Config, '_read_config_file')
282 @mock.patch.object(Config, 'get', side_effect=['doesnotmatter', 'memory', 'memory', 'local', 'doesnotmatter'])
283 def serverSetup(self, mock_get, mock__read_config_file):
284 """
285 Helper that returns a Server object
286 :return:
287 """
288 cfg = Config(None)
289 return Server(cfg)
290
291 def _adjust_path(self, file):
292 """In case we are not running from test directory,
293 then assume we are in top level directory (e.g. running from tox) and adjust file path accordingly"""
294 path_component = '/osm_pla/test/'
295 real_path = os.path.realpath(file)
296 if path_component not in real_path:
297 return os.path.dirname(real_path) + path_component + os.path.basename(real_path)
298 else:
299 return real_path
300
301 def test__get_nslcmop(self):
302 server = self.serverSetup()
303 server.db = Mock()
304 _ = server._get_nslcmop(nslcmop_record_wo_pinning["id"])
305 server.db.get_one.assert_called_with("nslcmops", {'_id': nslcmop_record_wo_pinning["id"]})
306
307 def test__get_nsd(self): # OK
308 server = self.serverSetup()
309 server.db = Mock()
310 _ = server._get_nsd(nslcmop_record_wo_pinning['operationParams']['nsdId'])
311 server.db.get_one.assert_called_with("nsds", {'_id': nslcmop_record_wo_pinning['operationParams']['nsdId']})
312
313 def test__create_vnf_id_maps(self):
314 server = self.serverSetup()
315 server.db = Mock()
316 expected_mvi2mzn = {'one': 'VNF0', 'two': 'VNF1', 'three': 'VNF2'}
317 expected_mzn2mvi = {'VNF0': 'one', 'VNF1': 'two', 'VNF2': 'three'}
318
319 nsd_for_test = copy.deepcopy(nsd_from_db)
320 mvi2mzn, mzn2mvi = server._create_vnf_id_maps(nsd_for_test)
321
322 self.assertDictEqual(expected_mvi2mzn, mvi2mzn, 'Faulty mzn2member-vnf-index mapping')
323 self.assertDictEqual(expected_mzn2mvi, mzn2mvi, 'Faulty mzn2member-vnf-index mapping')
324
325 def test__get_vim_accounts(self): # OK
326 server = self.serverSetup()
327 server.db = Mock()
328 _ = server._get_vim_accounts(nslcmop_record_wo_pinning['operationParams']['validVimAccounts'])
329 server.db.get_list.assert_called_with('vim_accounts',
330 {'_id': nslcmop_record_wo_pinning['operationParams']['validVimAccounts']})
331
332 def test__get_vnf_price_list(self):
333 server = self.serverSetup()
334 pl1 = server._get_vnf_price_list(Path(self._adjust_path('./vnf_price_list.yaml')))
335 self.assertIs(type(pl1), dict, "price list not a dictionary")
336 for k, v in pl1.items():
337 self.assertIs(type(v), dict, "price list values not a dict")
338
339 pl2 = server._get_vnf_price_list(Path(self._adjust_path('./vnf_price_list_keys.yaml')), 'hackfest_project_a')
340 self.assertIs(type(pl2), dict, "price list not a dictionary")
341 for k, v in pl2.items():
342 self.assertIs(type(v), dict, "price list values not a dict")
343 self.assertEqual(pl1, pl2, "non-project and project price lists differ")
344
345 def test__get_pil_info(self):
346 server = self.serverSetup()
347 ppi = server._get_pil_info(Path(self._adjust_path('./pil_price_list.yaml')))
348 self.assertIs(type(ppi), dict, "pil is not a dict")
349 self.assertIn('pil', ppi.keys(), "pil has no pil key")
350 self.assertIs(type(ppi['pil']), list, "pil does not contain a list")
351 # check for expected keys
352 expected_keys = {'pil_description', 'pil_price', 'pil_latency', 'pil_jitter', 'pil_endpoints'}
353 self.assertEqual(expected_keys, ppi['pil'][0].keys(), 'expected keys not found')
354
355 def test_handle_kafka_command(self): # OK
356 server = self.serverSetup()
357 server.loop.create_task = Mock()
358 server.handle_kafka_command('pli', 'get_placement', {})
359 server.loop.create_task.assert_not_called()
360 server.loop.create_task.reset_mock()
361 server.handle_kafka_command('pla', 'get_placement', {'nslcmopId': nslcmop_record_wo_pinning["id"]})
362 self.assertTrue(server.loop.create_task.called, 'create_task not called')
363 args, kwargs = server.loop.create_task.call_args
364 self.assertIn('Server.get_placement', str(args[0]), 'get_placement not called')
365
366 @mock.patch.object(NsPlacementDataFactory, '__init__', lambda x0, x1, x2, x3, x4, x5, x6: None)
367 @mock.patch.object(MznPlacementConductor, 'do_placement_computation')
368 @mock.patch.object(NsPlacementDataFactory, 'create_ns_placement_data')
369 @mock.patch.object(Server, '_get_vim_accounts')
370 @mock.patch.object(Server, '_get_nsd')
371 @mock.patch.object(Server, '_get_nslcmop')
372 @mock.patch.object(Server, '_get_vnf_price_list')
373 @mock.patch.object(Server, '_get_pil_info')
374 @mock.patch.object(Server, '_get_projects')
375 def test_get_placement(self, mock_get_projects, mock_get_pil_info, mock_get_vnf_price_list, mock__get_nslcmop,
376 mock__get_nsd,
377 mock__get_vim_accounts,
378 mock_create_ns_placement_data,
379 mock_do_placement_computation):
380 """
381 run _get_placement and check that things get called as expected
382 :return:
383 """
384 placement_ret_val = [{'vimAccountId': 'bbbbbbbb-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': 'VNF0'},
385 {'vimAccountId': 'aaaaaaaa-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': 'VNF1'},
386 {'vimAccountId': 'aaaaaaaa-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': 'VNF2'}]
387 server = self.serverSetup()
388
389 server.msgBus.aiowrite = _async_mock()
390 nsd_for_test = copy.deepcopy(nsd_from_db)
391 mock__get_nsd.return_value = nsd_for_test
392 mock__get_vim_accounts.return_value = list_of_vims
393
394 # FIXME need update to match nslcmop, not for test but for consistency
395 mock_do_placement_computation.return_value = placement_ret_val
396 _run(server.get_placement(nslcmop_record_wo_pinning['id']))
397
398 self.assertTrue(mock_get_projects.called, '_get_projects not called as expected')
399 self.assertTrue(mock_get_vnf_price_list.called, '_get_vnf_price_list not called as expected')
400 self.assertTrue(mock_get_pil_info.called, '_get_pil_info not called as expected')
401 self.assertTrue(mock__get_nslcmop.called, '_get_nslcmop not called as expected')
402 # mock_get_nsd.assert_called_once() assert_called_once() for python > 3.5
403 self.assertTrue(mock__get_nsd.called, 'get_nsd not called as expected')
404 # mock_get_enabled_vims.assert_called_once() assert_called_once() for python > 3.5
405 self.assertTrue(mock__get_vim_accounts.called, 'get_vim_accounts not called as expected')
406 # mock_create_ns_placement_data.assert_called_once() assert_called_once() for python > 3.5
407 self.assertTrue(mock_create_ns_placement_data.called, 'create_ns_placement_data not called as expected')
408 # mock_do_placement_computation.assert_called_once() assert_called_once() for python > 3.5
409 self.assertTrue(mock_do_placement_computation.called, 'do_placement_computation not called as expected')
410 self.assertTrue(server.msgBus.aiowrite.mock.called)
411
412 args, kwargs = server.msgBus.aiowrite.mock.call_args
413 self.assertTrue(len(args) == 3, 'invalid format')
414 self.assertEqual('pla', args[0], 'topic invalid')
415 self.assertEqual('placement', args[1], 'message invalid')
416 # extract placement result and check content
417 rsp_payload = args[2]
418
419 expected_rsp_keys = {'placement'}
420 self.assertEqual(expected_rsp_keys, set(rsp_payload.keys()), "placement response missing keys")
421 self.assertIs(type(rsp_payload['placement']), dict, 'placement not a dict')
422
423 expected_placement_keys = {'vnf', 'nslcmopId'}
424 self.assertEqual(expected_placement_keys, set(rsp_payload['placement']), "placement keys invalid")
425
426 vim_account_candidates = [e['vimAccountId'] for e in placement_ret_val]
427
428 self.assertEqual(nslcmop_record_wo_pinning['id'], rsp_payload['placement']['nslcmopId'], "nslcmopId invalid")
429
430 self.assertIs(type(rsp_payload['placement']['vnf']), list, 'vnf not a list')
431 expected_vnf_keys = {'vimAccountId', 'member-vnf-index'}
432 self.assertEqual(expected_vnf_keys, set(rsp_payload['placement']['vnf'][0]), "placement['vnf'] missing keys")
433 self.assertIn(rsp_payload['placement']['vnf'][0]['vimAccountId'], vim_account_candidates,
434 "vimAccountId invalid")
435
436 @mock.patch.object(NsPlacementDataFactory, '__init__', lambda x0, x1, x2, x3, x4, x5, x6: None)
437 @mock.patch.object(MznPlacementConductor, 'do_placement_computation')
438 @mock.patch.object(NsPlacementDataFactory, 'create_ns_placement_data')
439 @mock.patch.object(Server, '_get_vim_accounts')
440 @mock.patch.object(Server, '_get_nsd')
441 @mock.patch.object(Server, '_get_nslcmop')
442 @mock.patch.object(Server, '_get_vnf_price_list')
443 @mock.patch.object(Server, '_get_pil_info')
444 @mock.patch.object(Server, '_get_projects')
445 def test_get_placement_with_pinning(self, mock_get_projects, mock_get_pil_info, mock_get_vnf_price_list,
446 mock__get_nslcmop,
447 mock__get_nsd, mock__get_vim_accounts,
448 mock_create_ns_placement_data,
449 mock_do_placement_computation):
450 """
451 run _get_placement and check that things get called as expected
452 :return:
453 """
454 placement_ret_val = [{'vimAccountId': 'bbbbbbbb-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': 'VNF0'},
455 {'vimAccountId': 'aaaaaaaa-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': 'VNF1'},
456 {'vimAccountId': 'aaaaaaaa-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': 'VNF2'}]
457 server = self.serverSetup()
458
459 server.msgBus.aiowrite = _async_mock()
460 nsd_for_test = copy.deepcopy(nsd_from_db)
461 mock__get_nsd.return_value = nsd_for_test
462 mock__get_vim_accounts.return_value = list_of_vims
463
464 # FIXME need update to match nslcmop, not for test but for consistency
465 mock_do_placement_computation.return_value = placement_ret_val
466 _run(server.get_placement(nslcmop_record_w_pinning['id']))
467
468 self.assertTrue((mock_get_projects.called, '_get_projects not called as expected'))
469 self.assertTrue(mock_get_vnf_price_list.called, '_get_vnf_price_list not called as expected')
470 self.assertTrue(mock_get_pil_info.called, '_get_pil_info not called as expected')
471 self.assertTrue(mock__get_nslcmop.called, '_get_nslcmop not called as expected')
472 # mock_get_nsd.assert_called_once() assert_called_once() for python > 3.5
473 self.assertTrue(mock__get_nsd.called, 'get_nsd not called as expected')
474 # mock_get_enabled_vims.assert_called_once() assert_called_once() for python > 3.5
475 self.assertTrue(mock__get_vim_accounts.called, 'get_vim_accounts not called as expected')
476 # mock_create_ns_placement_data.assert_called_once() assert_called_once() for python > 3.5
477 self.assertTrue(mock_create_ns_placement_data.called, 'create_ns_placement_data not called as expected')
478 # mock_do_placement_computation.assert_called_once() assert_called_once() for python > 3.5
479 self.assertTrue(mock_do_placement_computation.called, 'do_placement_computation not called as expected')
480 self.assertTrue(server.msgBus.aiowrite.mock.called)
481
482 args, kwargs = server.msgBus.aiowrite.mock.call_args
483 self.assertTrue(len(args) == 3, 'invalid format')
484 self.assertEqual('pla', args[0], 'topic invalid')
485 self.assertEqual('placement', args[1], 'message invalid')
486 # extract placement result and check content
487 rsp_payload = args[2]
488
489 expected_rsp_keys = {'placement'}
490 self.assertEqual(expected_rsp_keys, set(rsp_payload.keys()), "placement response missing keys")
491 self.assertIs(type(rsp_payload['placement']), dict, 'placement not a dict')
492
493 expected_placement_keys = {'vnf', 'nslcmopId'}
494 self.assertEqual(expected_placement_keys, set(rsp_payload['placement']), "placement keys invalid")
495
496 vim_account_candidates = [e['vimAccountId'] for e in placement_ret_val]
497
498 self.assertEqual(nslcmop_record_w_pinning['id'], rsp_payload['placement']['nslcmopId'], "nslcmopId invalid")
499
500 self.assertIs(type(rsp_payload['placement']['vnf']), list, 'vnf not a list')
501 expected_vnf_keys = {'vimAccountId', 'member-vnf-index'}
502 self.assertEqual(expected_vnf_keys, set(rsp_payload['placement']['vnf'][0]), "placement['vnf'] missing keys")
503 self.assertIn(rsp_payload['placement']['vnf'][0]['vimAccountId'], vim_account_candidates,
504 "vimAccountId invalid")
505
506 # Note: does not mock reading of price list and pil_info
507 @mock.patch.object(NsPlacementDataFactory, '__init__', lambda x0, x1, x2, x3, x4, x5: None)
508 @mock.patch.object(MznPlacementConductor, 'do_placement_computation')
509 @mock.patch.object(NsPlacementDataFactory, 'create_ns_placement_data')
510 @mock.patch.object(Server, '_get_vim_accounts')
511 @mock.patch.object(Server, '_get_nsd')
512 @mock.patch.object(Server, '_get_nslcmop')
513 def test_get_placement_w_exception(self, mock__get_nslcmop,
514 mock__get_nsd,
515 mock__get_vim_accounts,
516 mock_create_ns_placement_data,
517 mock_do_placement_computation):
518 """
519 check that raised exceptions are handled and response provided accordingly
520 """
521 server = self.serverSetup()
522
523 server.msgBus.aiowrite = _async_mock()
524 nsd_for_test = copy.deepcopy(nsd_from_db)
525 mock__get_nsd.return_value = nsd_for_test
526 mock__get_nsd.side_effect = RuntimeError('kaboom!')
527 mock__get_vim_accounts.return_value = list_of_vims
528 mock_do_placement_computation.return_value = \
529 [{'vimAccountId': 'bbbbbbbb-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '1'},
530 {'vimAccountId': 'aaaaaaaa-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '2'},
531 {'vimAccountId': 'aaaaaaaa-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '3'}]
532
533 _run(server.get_placement(nslcmop_record_w_pinning['id']))
534 self.assertTrue(server.msgBus.aiowrite.mock.called)
535 args, kwargs = server.msgBus.aiowrite.mock.call_args
536 rsp_payload = args[2]
537 expected_keys = {'placement'}
538 self.assertEqual(expected_keys, set(rsp_payload.keys()), "placement response missing keys")
539 self.assertIs(type(rsp_payload['placement']['vnf']), list, 'vnf not a list')
540 self.assertEqual([], rsp_payload['placement']['vnf'], 'vnf list not empty')
541 self.assertEqual(nslcmop_record_w_pinning['id'], rsp_payload['placement']['nslcmopId'], "nslcmopId invalid")