| """ |
| Copyright 2020 The Magma Authors. |
| |
| This source code is licensed under the BSD-style license found in the |
| LICENSE file in the root directory of this source tree. |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| """ |
| |
| # pylint: disable=protected-access |
| from unittest import TestCase |
| |
| from state_machines.enb_acs_manager import StateMachineManager |
| from tests.test_utils.enb_acs_builder import ( |
| EnodebAcsStateMachineBuilder, |
| ) |
| from tests.test_utils.spyne_builder import ( |
| get_spyne_context_with_ip, |
| ) |
| from tests.test_utils.tr069_msg_builder import Tr069MessageBuilder |
| from tr069 import models |
| |
| |
| class StateMachineManagerTests(TestCase): |
| def test_handle_one_ip(self): |
| manager = self._get_manager() |
| |
| # Send in an Inform message, and we should get an InformResponse |
| ctx = get_spyne_context_with_ip() |
| inform = Tr069MessageBuilder.get_inform() |
| req = manager.handle_tr069_message(ctx, inform) |
| self.assertTrue( |
| isinstance(req, models.InformResponse), |
| 'State machine handler should reply with an ' |
| 'InformResponse', |
| ) |
| |
| def test_serial_not_found(self): |
| """ |
| Test that the SM manager doesn't crash if serial number is not found |
| in an Inform message under any expected param path. |
| """ |
| manager = self._get_manager() |
| ctx = get_spyne_context_with_ip("192.168.60.145") |
| inform_msg = models.Inform( |
| DeviceId=models.DeviceIdStruct( |
| Manufacturer='Unused', |
| OUI='48BF74', |
| ProductClass='Unused', |
| ), |
| Event=models.EventList(EventStruct=[]), |
| ParameterList=models.ParameterValueList( |
| ParameterValueStruct=[ |
| Tr069MessageBuilder.get_parameter_value_struct( |
| name='Device.DeviceInfo.HardwareVersion', |
| val_type='string', |
| data='VER.C', |
| ), |
| Tr069MessageBuilder.get_parameter_value_struct( |
| name='Device.DeviceInfo.ManufacturerOUI', |
| val_type='string', |
| data='48BF74', |
| ), |
| Tr069MessageBuilder.get_parameter_value_struct( |
| name='Device.DeviceInfo.SoftwareVersion', |
| val_type='string', |
| data='BaiBS_RTS_3.1.6', |
| ), |
| ], |
| ), |
| ) |
| |
| # No exception should be thrown, and we should return an empty response |
| resp = manager.handle_tr069_message(ctx, inform_msg) |
| self.assertTrue(isinstance(resp, models.DummyInput)) |
| |
| def test_handle_two_ips(self): |
| manager = self._get_manager() |
| ctx1 = get_spyne_context_with_ip("192.168.60.145") |
| ctx2 = get_spyne_context_with_ip("192.168.60.99") |
| |
| ##### Start session for the first IP ##### |
| # Send an Inform message, wait for an InformResponse |
| inform_msg = Tr069MessageBuilder.get_inform( |
| '48BF74', |
| 'BaiBS_RTS_3.1.6', |
| '120200002618AGP0001', |
| ) |
| resp1 = manager.handle_tr069_message(ctx1, inform_msg) |
| self.assertTrue( |
| isinstance(resp1, models.InformResponse), |
| 'Should respond with an InformResponse', |
| ) |
| |
| # Send an empty http request to kick off the rest of provisioning |
| req1 = models.DummyInput() |
| resp1 = manager.handle_tr069_message(ctx1, req1) |
| |
| # Expect a request for an optional parameter, three times |
| self.assertTrue( |
| isinstance(resp1, models.GetParameterValues), |
| 'State machine should be requesting param values', |
| ) |
| req1 = Tr069MessageBuilder.get_fault() |
| resp1 = manager.handle_tr069_message(ctx1, req1) |
| self.assertTrue( |
| isinstance(resp1, models.GetParameterValues), |
| 'State machine should be requesting param values', |
| ) |
| |
| ##### Start session for the second IP ##### |
| # Send an Inform message, wait for an InformResponse |
| inform_msg = Tr069MessageBuilder.get_inform( |
| '48BF74', |
| 'BaiBS_RTS_3.1.6', |
| '120200002618AGP0002', |
| ) |
| resp2 = manager.handle_tr069_message(ctx2, inform_msg) |
| self.assertTrue( |
| isinstance(resp2, models.InformResponse), |
| 'Should respond with an InformResponse', |
| ) |
| |
| ##### Continue session for the first IP ##### |
| req1 = Tr069MessageBuilder.get_fault() |
| resp1 = manager.handle_tr069_message(ctx1, req1) |
| self.assertTrue( |
| isinstance(resp1, models.GetParameterValues), |
| 'State machine should be requesting param values', |
| ) |
| req1 = Tr069MessageBuilder.get_fault() |
| resp1 = manager.handle_tr069_message(ctx1, req1) |
| # Expect a request for read-only params |
| self.assertTrue( |
| isinstance(resp1, models.GetParameterValues), |
| 'State machine should be requesting param values', |
| ) |
| |
| ##### Continue session for the second IP ##### |
| # Send an empty http request to kick off the rest of provisioning |
| req2 = models.DummyInput() |
| resp2 = manager.handle_tr069_message(ctx2, req2) |
| # Expect a request for an optional parameter, three times |
| self.assertTrue( |
| isinstance(resp2, models.GetParameterValues), |
| 'State machine should be requesting param values', |
| ) |
| req2 = Tr069MessageBuilder.get_fault() |
| resp2 = manager.handle_tr069_message(ctx2, req2) |
| self.assertTrue( |
| isinstance(resp2, models.GetParameterValues), |
| 'State machine should be requesting param values', |
| ) |
| req2 = Tr069MessageBuilder.get_fault() |
| resp2 = manager.handle_tr069_message(ctx2, req2) |
| self.assertTrue( |
| isinstance(resp2, models.GetParameterValues), |
| 'State machine should be requesting param values', |
| ) |
| req2 = Tr069MessageBuilder.get_fault() |
| resp2 = manager.handle_tr069_message(ctx2, req2) |
| # Expect a request for read-only params |
| self.assertTrue( |
| isinstance(resp2, models.GetParameterValues), |
| 'State machine should be requesting param values', |
| ) |
| |
| def test_handle_registered_enb(self): |
| """ |
| When we have a config with eNB registered per serial, we should accept |
| TR-069 sessions from any registered eNB, and ereject from unregistered |
| eNB devices. |
| """ |
| manager = self._get_manager_multi_enb() |
| ip1 = "192.168.60.145" |
| ctx1 = get_spyne_context_with_ip(ip1) |
| inform_msg = Tr069MessageBuilder.get_inform( |
| '48BF74', |
| 'BaiBS_RTS_3.1.6', |
| '120200002618AGP0003', |
| ) |
| resp1 = manager.handle_tr069_message(ctx1, inform_msg) |
| self.assertTrue( |
| isinstance(resp1, models.InformResponse), |
| 'Should respond with an InformResponse', |
| ) |
| |
| ip2 = "192.168.60.146" |
| ctx2 = get_spyne_context_with_ip(ip2) |
| inform_msg = Tr069MessageBuilder.get_inform( |
| '48BF74', |
| 'BaiBS_RTS_3.1.6', |
| 'unregistered_serial', |
| ) |
| |
| resp2 = manager.handle_tr069_message(ctx2, inform_msg) |
| self.assertTrue( |
| isinstance(resp2, models.DummyInput), |
| 'Should respond with an empty HTTP response', |
| ) |
| |
| def test_ip_change(self) -> None: |
| manager = self._get_manager() |
| |
| # Send an Inform |
| ip1 = "192.168.60.145" |
| ctx1 = get_spyne_context_with_ip(ip1) |
| inform_msg = Tr069MessageBuilder.get_inform( |
| '48BF74', |
| 'BaiBS_RTS_3.1.6', |
| '120200002618AGP0003', |
| ) |
| resp1 = manager.handle_tr069_message(ctx1, inform_msg) |
| self.assertTrue( |
| isinstance(resp1, models.InformResponse), |
| 'Should respond with an InformResponse', |
| ) |
| handler1 = manager.get_handler_by_ip(ip1) |
| |
| # Send an Inform from the same serial, but different IP |
| ip2 = "192.168.60.99" |
| ctx2 = get_spyne_context_with_ip(ip2) |
| inform_msg = Tr069MessageBuilder.get_inform( |
| '48BF74', |
| 'BaiBS_RTS_3.1.6', |
| '120200002618AGP0003', |
| ) |
| resp2 = manager.handle_tr069_message(ctx2, inform_msg) |
| self.assertTrue( |
| isinstance(resp2, models.InformResponse), |
| 'Should respond with an InformResponse', |
| ) |
| handler2 = manager.get_handler_by_ip(ip2) |
| |
| # Now check that the serial is associated with the second ip |
| self.assertTrue( |
| (handler1 is handler2), |
| 'After an IP switch, the manager should have moved ' |
| 'the handler to a new IP', |
| ) |
| |
| def test_serial_change(self) -> None: |
| manager = self._get_manager() |
| ip = "192.168.60.145" |
| |
| # Send an Inform |
| ctx1 = get_spyne_context_with_ip(ip) |
| inform_msg = Tr069MessageBuilder.get_inform( |
| '48BF74', |
| 'BaiBS_RTS_3.1.6', |
| '120200002618AGP0001', |
| ) |
| resp1 = manager.handle_tr069_message(ctx1, inform_msg) |
| self.assertTrue( |
| isinstance(resp1, models.InformResponse), |
| 'Should respond with an InformResponse', |
| ) |
| handler1 = manager.get_handler_by_ip(ip) |
| |
| # Send an Inform from the same serial, but different IP |
| ctx2 = get_spyne_context_with_ip(ip) |
| inform_msg = Tr069MessageBuilder.get_inform( |
| '48BF74', |
| 'BaiBS_RTS_3.1.6', |
| '120200002618AGP0002', |
| ) |
| resp2 = manager.handle_tr069_message(ctx2, inform_msg) |
| self.assertTrue( |
| isinstance(resp2, models.InformResponse), |
| 'Should respond with an InformResponse', |
| ) |
| handler2 = manager.get_handler_by_ip(ip) |
| |
| # Now check that the serial is associated with the second ip |
| self.assertTrue( |
| (handler1 is not handler2), |
| 'After an IP switch, the manager should have moved ' |
| 'the handler to a new IP', |
| ) |
| |
| def test_inform_from_baicells_qafb(self) -> None: |
| manager = self._get_manager() |
| ip = "192.168.60.145" |
| |
| # Send an Inform |
| ctx1 = get_spyne_context_with_ip(ip) |
| inform_msg = Tr069MessageBuilder.get_qafb_inform( |
| '48BF74', |
| 'BaiBS_QAFB_v1234', |
| '120200002618AGP0001', |
| ) |
| resp1 = manager.handle_tr069_message(ctx1, inform_msg) |
| self.assertTrue( |
| isinstance(resp1, models.InformResponse), |
| 'Should respond with an InformResponse', |
| ) |
| |
| def test_inform_from_unrecognized(self) -> None: |
| manager = self._get_manager() |
| ip = "192.168.60.145" |
| |
| # Send an Inform |
| ctx1 = get_spyne_context_with_ip(ip) |
| inform_msg = Tr069MessageBuilder.get_qafb_inform( |
| '48BF74', |
| 'Unrecognized device', |
| '120200002618AGP0001', |
| ) |
| resp1 = manager.handle_tr069_message(ctx1, inform_msg) |
| self.assertTrue( |
| isinstance(resp1, models.DummyInput), |
| 'Should end provisioninng session with empty response', |
| ) |
| |
| def _get_manager(self) -> StateMachineManager: |
| service = EnodebAcsStateMachineBuilder.build_magma_service() |
| return StateMachineManager(service) |
| |
| def _get_manager_multi_enb(self) -> StateMachineManager: |
| service = EnodebAcsStateMachineBuilder.build_multi_enb_magma_service() |
| return StateMachineManager(service) |