blob: c656c1d27f5af6c199e33d3784ca7673ef49eca2 [file] [log] [blame]
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -08001# Copyright 2017-present Open Networking Foundation
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 implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Scott Baker22b46c52018-11-15 15:15:29 -080015from requests import ConnectionError
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -080016import unittest
Matteo Scandolo2ed64b92018-06-18 10:32:56 -070017import functools
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -080018from mock import patch, call, Mock, PropertyMock
19import requests_mock
20
21import os, sys
22
23# Hack to load synchronizer framework
24test_path=os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
25xos_dir=os.path.join(test_path, "../../..")
26if not os.path.exists(os.path.join(test_path, "new_base")):
27 xos_dir=os.path.join(test_path, "../../../../../../orchestration/xos/xos")
Matteo Scandoloce27e9c2018-04-06 10:06:53 -070028 services_dir = os.path.join(xos_dir, "../../xos_services")
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -080029sys.path.append(xos_dir)
30sys.path.append(os.path.join(xos_dir, 'synchronizers', 'new_base'))
Luca Preteca974c82018-05-01 18:06:16 -070031# END of hack to load synchronizer framework
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -080032
Luca Preteca974c82018-05-01 18:06:16 -070033# Generate model from xproto
Matteo Scandoloce27e9c2018-04-06 10:06:53 -070034def get_models_fn(service_name, xproto_name):
35 name = os.path.join(service_name, "xos", xproto_name)
36 if os.path.exists(os.path.join(services_dir, name)):
37 return name
38 else:
39 name = os.path.join(service_name, "xos", "synchronizer", "models", xproto_name)
40 if os.path.exists(os.path.join(services_dir, name)):
41 return name
42 raise Exception("Unable to find service=%s xproto=%s" % (service_name, xproto_name))
43# END generate model from xproto
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -080044
Matteo Scandolo2ed64b92018-06-18 10:32:56 -070045def match_json(desired, req):
46 if desired!=req.json():
47 raise Exception("Got request %s, but body is not matching" % req.url)
48 return False
49 return True
50
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -080051class TestSyncOLTDevice(unittest.TestCase):
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -080052 def setUp(self):
Matteo Scandolo6be6ee92018-05-24 15:07:51 -070053 global DeferredException
Matteo Scandoloce27e9c2018-04-06 10:06:53 -070054 self.sys_path_save = sys.path
55 sys.path.append(xos_dir)
56 sys.path.append(os.path.join(xos_dir, 'synchronizers', 'new_base'))
57
58 # Setting up the config module
59 from xosconfig import Config
Matteo Scandolof7ebb112018-09-18 16:17:22 -070060 config = os.path.join(test_path, "../test_config.yaml")
Matteo Scandoloce27e9c2018-04-06 10:06:53 -070061 Config.clear()
62 Config.init(config, "synchronizer-config-schema.yaml")
Luca Preteca974c82018-05-01 18:06:16 -070063 # END setting up the config module
Matteo Scandoloce27e9c2018-04-06 10:06:53 -070064
65 from synchronizers.new_base.mock_modelaccessor_build import build_mock_modelaccessor
Matteo Scandolo19466a02018-05-16 17:43:39 -070066 # build_mock_modelaccessor(xos_dir, services_dir, [get_models_fn("olt-service", "volt.xproto")])
67
68 # FIXME this is to get jenkins to pass the tests, somehow it is running tests in a different order
69 # and apparently it is not overriding the generated model accessor
70 build_mock_modelaccessor(xos_dir, services_dir, [get_models_fn("olt-service", "volt.xproto"),
71 get_models_fn("vsg", "vsg.xproto"),
72 get_models_fn("../profiles/rcord", "rcord.xproto")])
73
Matteo Scandoloce27e9c2018-04-06 10:06:53 -070074 import synchronizers.new_base.modelaccessor
Matteo Scandolo6be6ee92018-05-24 15:07:51 -070075 from sync_olt_device import SyncOLTDevice, DeferredException
Matteo Scandoloce27e9c2018-04-06 10:06:53 -070076 self.sync_step = SyncOLTDevice
77
Matteo Scandolob8621cd2018-04-04 17:12:37 -070078 pon_port = Mock()
79 pon_port.port_id = "00ff00"
Matteo Scandolob8621cd2018-04-04 17:12:37 -070080
Matteo Scandolo2ed64b92018-06-18 10:32:56 -070081 # Create a mock OLTDevice
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -080082 o = Mock()
83 o.volt_service.voltha_url = "voltha_url"
Luca Preteca974c82018-05-01 18:06:16 -070084 o.volt_service.voltha_port = 1234
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -080085 o.volt_service.voltha_user = "voltha_user"
86 o.volt_service.voltha_pass = "voltha_pass"
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -080087
Matteo Scandoloa79395f2018-10-08 13:34:49 -070088 o.volt_service.onos_voltha_port = 4321
89 o.volt_service.onos_voltha_url = "onos"
90 o.volt_service.onos_voltha_user = "karaf"
91 o.volt_service.onos_voltha_pass = "karaf"
92
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -080093 o.device_type = "ponsim_olt"
94 o.host = "172.17.0.1"
95 o.port = "50060"
Luca Prete244e6ec2018-07-02 14:30:24 +020096 o.driver = "voltha"
Matteo Scandoloa79395f2018-10-08 13:34:49 -070097 o.name = "Test Device"
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -080098
Matteo Scandolob8621cd2018-04-04 17:12:37 -070099 # feedback state
100 o.device_id = None
101 o.admin_state = None
102 o.oper_status = None
103 o.of_id = None
Matteo Scandolo096a3cf2018-06-20 13:56:13 -0700104 o.id = 1
Matteo Scandolob8621cd2018-04-04 17:12:37 -0700105
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -0800106 o.tologdict.return_value = {'name': "Mock VOLTServiceInstance"}
107
108 o.save.return_value = "Saved"
109
Matteo Scandolo6be6ee92018-05-24 15:07:51 -0700110 o.pon_ports.all.return_value = [pon_port]
Matteo Scandolob8621cd2018-04-04 17:12:37 -0700111
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -0800112 self.o = o
113
Matteo Scandolod6fce512018-10-16 10:35:29 -0700114 self.voltha_devices_response = {"id": "123", "serial_number": "foobar"}
115
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -0800116 def tearDown(self):
117 self.o = None
Matteo Scandoloce27e9c2018-04-06 10:06:53 -0700118 sys.path = self.sys_path_save
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -0800119
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -0800120 @requests_mock.Mocker()
121 def test_get_of_id_from_device(self, m):
122 logical_devices = {
123 "items": [
Matteo Scandolof6337eb2018-04-05 15:58:37 -0700124 {"root_device_id": "123", "id": "0001000ce2314000", "datapath_id": "55334486016"},
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -0800125 {"root_device_id": "0001cc4974a62b87", "id": "0001000000000001"}
126 ]
127 }
Luca Preteca974c82018-05-01 18:06:16 -0700128 m.get("http://voltha_url:1234/api/v1/logical_devices", status_code=200, json=logical_devices)
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -0800129 self.o.device_id = "123"
Matteo Scandolof6337eb2018-04-05 15:58:37 -0700130 self.o = self.sync_step.get_ids_from_logical_device(self.o)
131 self.assertEqual(self.o.of_id, "0001000ce2314000")
132 self.assertEqual(self.o.dp_id, "of:0000000ce2314000")
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -0800133
134 with self.assertRaises(Exception) as e:
135 self.o.device_id = "idonotexist"
Matteo Scandolof6337eb2018-04-05 15:58:37 -0700136 self.sync_step.get_ids_from_logical_device(self.o)
Matteo Scandolo2c144932018-05-04 14:06:24 -0700137 self.assertEqual(e.exception.message, "Can't find a logical_device for OLT device id: idonotexist")
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -0800138
139 @requests_mock.Mocker()
140 def test_sync_record_fail_add(self, m):
141 """
142 Should print an error if we can't add the device in VOLTHA
143 """
Luca Preteca974c82018-05-01 18:06:16 -0700144 m.post("http://voltha_url:1234/api/v1/devices", status_code=500, text="MockError")
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -0800145
146 with self.assertRaises(Exception) as e:
Matteo Scandoloce27e9c2018-04-06 10:06:53 -0700147 self.sync_step().sync_record(self.o)
Matteo Scandolo2c144932018-05-04 14:06:24 -0700148 self.assertEqual(e.exception.message, "Failed to add OLT device: MockError")
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -0800149
150 @requests_mock.Mocker()
151 def test_sync_record_fail_no_id(self, m):
152 """
153 Should print an error if VOLTHA does not return the device id
154 """
Luca Preteca974c82018-05-01 18:06:16 -0700155 m.post("http://voltha_url:1234/api/v1/devices", status_code=200, json={"id": ""})
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -0800156
157 with self.assertRaises(Exception) as e:
Matteo Scandoloce27e9c2018-04-06 10:06:53 -0700158 self.sync_step().sync_record(self.o)
Matteo Scandolo2c144932018-05-04 14:06:24 -0700159 self.assertEqual(e.exception.message, "VOLTHA Device Id is empty. This probably means that the OLT device is already provisioned in VOLTHA")
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -0800160
161 @requests_mock.Mocker()
162 def test_sync_record_fail_enable(self, m):
163 """
164 Should print an error if device.enable fails
165 """
Matteo Scandolod6fce512018-10-16 10:35:29 -0700166 m.post("http://voltha_url:1234/api/v1/devices", status_code=200, json=self.voltha_devices_response)
Luca Preteca974c82018-05-01 18:06:16 -0700167 m.post("http://voltha_url:1234/api/v1/devices/123/enable", status_code=500, text="EnableError")
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -0800168
169 with self.assertRaises(Exception) as e:
Matteo Scandoloce27e9c2018-04-06 10:06:53 -0700170 self.sync_step().sync_record(self.o)
Matteo Scandolo2c144932018-05-04 14:06:24 -0700171
172 self.assertEqual(e.exception.message, "Failed to enable OLT device: EnableError")
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -0800173
174 @requests_mock.Mocker()
175 def test_sync_record_success(self, m):
176 """
177 If device.enable succed should fetch the state, retrieve the of_id and push it to ONOS
178 """
Matteo Scandolo2ed64b92018-06-18 10:32:56 -0700179
180 expected_conf = {
181 "type": self.o.device_type,
182 "host_and_port": "%s:%s" % (self.o.host, self.o.port)
183 }
184
Matteo Scandolod6fce512018-10-16 10:35:29 -0700185 m.post("http://voltha_url:1234/api/v1/devices", status_code=200, json=self.voltha_devices_response, additional_matcher=functools.partial(match_json, expected_conf))
Luca Preteca974c82018-05-01 18:06:16 -0700186 m.post("http://voltha_url:1234/api/v1/devices/123/enable", status_code=200)
Matteo Scandolo45876652018-10-16 16:03:17 -0700187 m.get("http://voltha_url:1234/api/v1/devices/123", json={"oper_status": "ACTIVE", "admin_state": "ENABLED", "serial_number": "foobar"})
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -0800188 logical_devices = {
189 "items": [
Matteo Scandolof6337eb2018-04-05 15:58:37 -0700190 {"root_device_id": "123", "id": "0001000ce2314000", "datapath_id": "55334486016"},
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -0800191 {"root_device_id": "0001cc4974a62b87", "id": "0001000000000001"}
192 ]
193 }
Luca Preteca974c82018-05-01 18:06:16 -0700194 m.get("http://voltha_url:1234/api/v1/logical_devices", status_code=200, json=logical_devices)
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -0800195
Matteo Scandoloa79395f2018-10-08 13:34:49 -0700196 onos_expected_conf = {
197 "devices": {
198 "of:0000000ce2314000": {
199 "basic": {
200 "name": self.o.name
201 }
202 }
203 }
204 }
205 m.post("http://onos:4321/onos/v1/network/configuration/", status_code=200, json=onos_expected_conf,
206 additional_matcher=functools.partial(match_json, onos_expected_conf))
207
Matteo Scandoloce27e9c2018-04-06 10:06:53 -0700208 self.sync_step().sync_record(self.o)
Matteo Scandolo096a3cf2018-06-20 13:56:13 -0700209 self.assertEqual(self.o.admin_state, "ENABLED")
210 self.assertEqual(self.o.oper_status, "ACTIVE")
Matteo Scandolo45876652018-10-16 16:03:17 -0700211 self.assertEqual(self.o.serial_number, "foobar")
Matteo Scandolof6337eb2018-04-05 15:58:37 -0700212 self.assertEqual(self.o.of_id, "0001000ce2314000")
Matteo Scandolo096a3cf2018-06-20 13:56:13 -0700213 self.assertEqual(self.o.save.call_count, 2) # we're updating the backend_status when activating and then adding logical device ids
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -0800214
215 @requests_mock.Mocker()
Matteo Scandolo2ed64b92018-06-18 10:32:56 -0700216 def test_sync_record_success_mac_address(self, m):
217 """
218 A device should be pre-provisioned via mac_address, the the process is the same
219 """
220
221 del self.o.host
222 del self.o.port
223 self.o.mac_address = "00:0c:e2:31:40:00"
224
225 expected_conf = {
226 "type": self.o.device_type,
227 "mac_address": self.o.mac_address
228 }
229
Matteo Scandoloa79395f2018-10-08 13:34:49 -0700230 onos_expected_conf = {
231 "devices": {
232 "of:0000000ce2314000": {
233 "basic": {
234 "name": self.o.name
235 }
236 }
237 }
238 }
239 m.post("http://onos:4321/onos/v1/network/configuration/", status_code=200, json=onos_expected_conf,
240 additional_matcher=functools.partial(match_json, onos_expected_conf))
241
Matteo Scandolod6fce512018-10-16 10:35:29 -0700242 m.post("http://voltha_url:1234/api/v1/devices", status_code=200, json=self.voltha_devices_response,
Matteo Scandolo2ed64b92018-06-18 10:32:56 -0700243 additional_matcher=functools.partial(match_json, expected_conf))
244 m.post("http://voltha_url:1234/api/v1/devices/123/enable", status_code=200)
Matteo Scandolo45876652018-10-16 16:03:17 -0700245 m.get("http://voltha_url:1234/api/v1/devices/123", json={"oper_status": "ACTIVE", "admin_state": "ENABLED", "serial_number": "foobar"})
Matteo Scandolo2ed64b92018-06-18 10:32:56 -0700246 logical_devices = {
247 "items": [
248 {"root_device_id": "123", "id": "0001000ce2314000", "datapath_id": "55334486016"},
249 {"root_device_id": "0001cc4974a62b87", "id": "0001000000000001"}
250 ]
251 }
252 m.get("http://voltha_url:1234/api/v1/logical_devices", status_code=200, json=logical_devices)
253
Matteo Scandolo2ed64b92018-06-18 10:32:56 -0700254 self.sync_step().sync_record(self.o)
Matteo Scandolo096a3cf2018-06-20 13:56:13 -0700255 self.assertEqual(self.o.admin_state, "ENABLED")
256 self.assertEqual(self.o.oper_status, "ACTIVE")
Matteo Scandolo2ed64b92018-06-18 10:32:56 -0700257 self.assertEqual(self.o.of_id, "0001000ce2314000")
Matteo Scandolo096a3cf2018-06-20 13:56:13 -0700258 self.assertEqual(self.o.save.call_count, 2)
259
260 @requests_mock.Mocker()
261 def test_sync_record_enable_timeout(self, m):
262 """
263 If device.enable fails we need to tell the suer
264 """
265
266 expected_conf = {
267 "type": self.o.device_type,
268 "host_and_port": "%s:%s" % (self.o.host, self.o.port)
269 }
270
Matteo Scandolod6fce512018-10-16 10:35:29 -0700271 m.post("http://voltha_url:1234/api/v1/devices", status_code=200, json=self.voltha_devices_response,
Matteo Scandolo096a3cf2018-06-20 13:56:13 -0700272 additional_matcher=functools.partial(match_json, expected_conf))
273 m.post("http://voltha_url:1234/api/v1/devices/123/enable", status_code=200)
274 m.get("http://voltha_url:1234/api/v1/devices/123", [
Matteo Scandolo45876652018-10-16 16:03:17 -0700275 {"json": {"oper_status": "ACTIVATING", "admin_state": "ENABLED", "serial_number": "foobar"}, "status_code": 200},
276 {"json": {"oper_status": "ERROR", "admin_state": "FAILED", "serial_number": "foobar"}, "status_code": 200}
Matteo Scandolo096a3cf2018-06-20 13:56:13 -0700277 ])
278
279 logical_devices = {
280 "items": [
281 {"root_device_id": "123", "id": "0001000ce2314000", "datapath_id": "55334486016"},
282 {"root_device_id": "0001cc4974a62b87", "id": "0001000000000001"}
283 ]
284 }
285 m.get("http://voltha_url:1234/api/v1/logical_devices", status_code=200, json=logical_devices)
286
Matteo Scandolo096a3cf2018-06-20 13:56:13 -0700287 with self.assertRaises(Exception) as e:
288 self.sync_step().sync_record(self.o)
289
290 self.assertEqual(e.exception.message, "It was not possible to activate OLTDevice with id 1")
291 self.assertEqual(self.o.oper_status, "ERROR")
292 self.assertEqual(self.o.admin_state, "FAILED")
293 self.assertEqual(self.o.save.call_count, 1)
Matteo Scandolo2ed64b92018-06-18 10:32:56 -0700294
295 @requests_mock.Mocker()
Matteo Scandolob8621cd2018-04-04 17:12:37 -0700296 def test_sync_record_already_existing_in_voltha(self, m):
Matteo Scandolob8621cd2018-04-04 17:12:37 -0700297 # mock device feedback state
298 self.o.device_id = "123"
Matteo Scandolo096a3cf2018-06-20 13:56:13 -0700299 self.o.admin_state = "ENABLED"
300 self.o.oper_status = "ACTIVE"
Matteo Scandolof6337eb2018-04-05 15:58:37 -0700301 self.o.dp_id = "of:0000000ce2314000"
Matteo Scandolo2c144932018-05-04 14:06:24 -0700302 self.o.of_id = "0001000ce2314000"
Matteo Scandolob8621cd2018-04-04 17:12:37 -0700303
Matteo Scandoloa79395f2018-10-08 13:34:49 -0700304 expected_conf = {
305 "devices": {
306 self.o.dp_id: {
307 "basic": {
308 "name": self.o.name
309 }
310 }
311 }
312 }
313 m.post("http://onos:4321/onos/v1/network/configuration/", status_code=200, json=expected_conf,
314 additional_matcher=functools.partial(match_json, expected_conf))
315
Matteo Scandolob8621cd2018-04-04 17:12:37 -0700316 self.sync_step().sync_record(self.o)
317 self.o.save.assert_not_called()
318
Matteo Scandolob8621cd2018-04-04 17:12:37 -0700319 @requests_mock.Mocker()
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -0800320 def test_delete_record(self, m):
Matteo Scandolof6337eb2018-04-05 15:58:37 -0700321 self.o.of_id = "0001000ce2314000"
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -0800322 self.o.device_id = "123"
323
Luca Preteca974c82018-05-01 18:06:16 -0700324 m.post("http://voltha_url:1234/api/v1/devices/123/disable", status_code=200)
325 m.delete("http://voltha_url:1234/api/v1/devices/123/delete", status_code=200)
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -0800326
Matteo Scandoloce27e9c2018-04-06 10:06:53 -0700327 self.sync_step().delete_record(self.o)
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -0800328
Matteo Scandolo563891c2018-08-21 11:56:32 -0700329 self.assertEqual(m.call_count, 2)
330
Scott Baker22b46c52018-11-15 15:15:29 -0800331 @patch('requests.post')
332 def test_delete_record_connectionerror(self, m):
333 self.o.of_id = "0001000ce2314000"
334 self.o.device_id = "123"
335
336 m.side_effect = ConnectionError()
337
338 self.sync_step().delete_record(self.o)
339
340 # No exception thrown, as ConnectionError will be caught
341
342
Matteo Scandolo563891c2018-08-21 11:56:32 -0700343 @requests_mock.Mocker()
344 def test_delete_unsynced_record(self, m):
345
346 self.sync_step().delete_record(self.o)
347
348 self.assertEqual(m.call_count, 0)
Matteo Scandolo4a8b4d62018-03-06 17:18:46 -0800349
350if __name__ == "__main__":
Luca Preteca974c82018-05-01 18:06:16 -0700351 unittest.main()