Init commit for standalone enodebd

Change-Id: I88eeef5135dd7ba8551ddd9fb6a0695f5325337b
diff --git a/tests/baicells_old_tests.py b/tests/baicells_old_tests.py
new file mode 100644
index 0000000..e693790
--- /dev/null
+++ b/tests/baicells_old_tests.py
@@ -0,0 +1,581 @@
+"""
+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 devices.device_utils import EnodebDeviceName
+from tests.test_utils.enb_acs_builder import (
+    EnodebAcsStateMachineBuilder,
+)
+from tests.test_utils.enodeb_handler import EnodebHandlerTestCase
+from tests.test_utils.tr069_msg_builder import Tr069MessageBuilder
+from tr069 import models
+
+
+class BaicellsOldHandlerTests(EnodebHandlerTestCase):
+    def test_initial_enb_bootup(self) -> None:
+        """
+        Baicells does not support configuration during initial bootup of
+        eNB device. This is because it is in a REM process, and we just need
+        to wait for this process to finish, ~10 minutes. Attempting to
+        configure the device during this period will cause undefined
+        behavior.
+        As a result of this, end any provisoning sessions, which we can do
+        by just sending empty HTTP responses, not even using an
+        InformResponse.
+        """
+        acs_state_machine = \
+            EnodebAcsStateMachineBuilder \
+                .build_acs_state_machine(EnodebDeviceName.BAICELLS_OLD)
+
+        # Send an Inform message
+        inform_msg = \
+            Tr069MessageBuilder.get_inform(
+                '48BF74',
+                'BaiStation_V100R001C00B110SPC002',
+                '120200002618AGP0003', ['1 BOOT'],
+            )
+        resp = acs_state_machine.handle_tr069_message(inform_msg)
+
+        self.assertTrue(
+            isinstance(resp, models.DummyInput),
+            'Should respond with an InformResponse',
+        )
+
+    def test_manual_reboot(self) -> None:
+        """
+        Test a scenario where a Magma user goes through the enodebd CLI to
+        reboot the Baicells eNodeB.
+
+        This checks the scenario where the command is not sent in the middle
+        of a TR-069 provisioning session.
+        """
+        acs_state_machine = \
+            EnodebAcsStateMachineBuilder \
+                .build_acs_state_machine(EnodebDeviceName.BAICELLS_OLD)
+
+        # User uses the CLI tool to get eNodeB to reboot
+        acs_state_machine.reboot_asap()
+
+        # And now the Inform message arrives from the eNodeB
+        inform_msg = \
+            Tr069MessageBuilder.get_inform(
+                '48BF74',
+                'BaiStation_V100R001C00B110SPC002',
+                '120200002618AGP0003',
+                ['2 PERIODIC'],
+            )
+        resp = acs_state_machine.handle_tr069_message(inform_msg)
+        self.assertTrue(
+            isinstance(resp, models.InformResponse),
+            'In reboot sequence, state machine should still '
+            'respond to an Inform with InformResponse.',
+        )
+        req = models.DummyInput()
+        resp = acs_state_machine.handle_tr069_message(req)
+        self.assertTrue(
+            isinstance(resp, models.Reboot),
+            'In reboot sequence, state machine should send a '
+            'Reboot message.',
+        )
+        req = Tr069MessageBuilder.get_reboot_response()
+        resp = acs_state_machine.handle_tr069_message(req)
+        self.assertTrue(
+            isinstance(resp, models.DummyInput),
+            'State machine should end TR-069 session after '
+            'receiving a RebootResponse',
+        )
+
+    def test_manual_reboot_during_provisioning(self) -> None:
+        """
+        Test a scenario where a Magma user goes through the enodebd CLI to
+        reboot the Baicells eNodeB.
+
+        This checks the scenario where the command is sent in the middle
+        of a TR-069 provisioning session.
+        """
+        acs_state_machine = \
+            EnodebAcsStateMachineBuilder \
+                .build_acs_state_machine(EnodebDeviceName.BAICELLS_OLD)
+
+        # Send an Inform message, wait for an InformResponse
+        inform_msg = \
+            Tr069MessageBuilder.get_inform(
+                '48BF74',
+                'BaiStation_V100R001C00B110SPC002',
+                '120200002618AGP0003',
+                ['2 PERIODIC'],
+            )
+        resp = acs_state_machine.handle_tr069_message(inform_msg)
+        self.assertTrue(
+            isinstance(resp, models.InformResponse),
+            'Should respond with an InformResponse',
+        )
+
+        # Send an empty http request to kick off the rest of provisioning
+        req = models.DummyInput()
+        resp = acs_state_machine.handle_tr069_message(req)
+
+        # Expect a request for an optional parameter, three times
+        self.assertTrue(
+            isinstance(resp, models.GetParameterValues),
+            'State machine should be requesting param values',
+        )
+        req = Tr069MessageBuilder.get_fault()
+
+        # User uses the CLI tool to get eNodeB to reboot
+        acs_state_machine.reboot_asap()
+
+        resp = acs_state_machine.handle_tr069_message(req)
+        self.assertTrue(
+            isinstance(resp, models.Reboot),
+            'In reboot sequence, state machine should send a '
+            'Reboot message.',
+        )
+        req = Tr069MessageBuilder.get_reboot_response()
+        resp = acs_state_machine.handle_tr069_message(req)
+        self.assertTrue(
+            isinstance(resp, models.DummyInput),
+            'State machine should end TR-069 session after '
+            'receiving a RebootResponse',
+        )
+
+    def test_provision_without_invasive_changes(self) -> None:
+        """
+        Test the scenario where:
+        - eNodeB has already been powered for 10 minutes without configuration
+        - Setting parameters which are 'non-invasive' on the eNodeB
+
+        'Invasive' parameters are those which require special behavior to apply
+        the changes for the eNodeB.
+        """
+        acs_state_machine = \
+            EnodebAcsStateMachineBuilder \
+                .build_acs_state_machine(EnodebDeviceName.BAICELLS_OLD)
+
+        # Send an Inform message, wait for an InformResponse
+        inform_msg = \
+            Tr069MessageBuilder.get_inform(
+                '48BF74',
+                'BaiStation_V100R001C00B110SPC001',
+                '120200002618AGP0003',
+                ['2 PERIODIC'],
+            )
+        resp = acs_state_machine.handle_tr069_message(inform_msg)
+        self.assertTrue(
+            isinstance(resp, models.InformResponse),
+            'Should respond with an InformResponse',
+        )
+
+        # Send an empty http request to kick off the rest of provisioning
+        req = models.DummyInput()
+        resp = acs_state_machine.handle_tr069_message(req)
+
+        # Expect a request for an optional parameter, five times
+        self.assertTrue(
+            isinstance(resp, models.GetParameterValues),
+            'State machine should be requesting param values',
+        )
+        req = Tr069MessageBuilder.get_fault()
+        resp = acs_state_machine.handle_tr069_message(req)
+        self.assertTrue(
+            isinstance(resp, models.GetParameterValues),
+            'State machine should be requesting param values',
+        )
+        req = Tr069MessageBuilder.get_fault()
+        resp = acs_state_machine.handle_tr069_message(req)
+        self.assertTrue(
+            isinstance(resp, models.GetParameterValues),
+            'State machine should be requesting param values',
+        )
+        req = Tr069MessageBuilder.get_fault()
+        resp = acs_state_machine.handle_tr069_message(req)
+        self.assertTrue(
+            isinstance(resp, models.GetParameterValues),
+            'State machine should be requesting param values',
+        )
+        req = Tr069MessageBuilder.get_fault()
+        resp = acs_state_machine.handle_tr069_message(req)
+        self.assertTrue(
+            isinstance(resp, models.GetParameterValues),
+            'State machine should be requesting param values',
+        )
+        req = Tr069MessageBuilder.get_fault()
+        resp = acs_state_machine.handle_tr069_message(req)
+
+        # Expect a request for read-only params
+        self.assertTrue(
+            isinstance(resp, models.GetParameterValues),
+            'State machine should be requesting param values',
+        )
+        req = Tr069MessageBuilder.get_read_only_param_values_response()
+
+        # Send back some typical values
+        # And then SM should request regular parameter values
+        resp = acs_state_machine.handle_tr069_message(req)
+        self.assertTrue(
+            isinstance(resp, models.GetParameterValues),
+            'State machine should be requesting param values',
+        )
+
+        # Send back typical values for the regular parameters
+        req = Tr069MessageBuilder.\
+            get_regular_param_values_response(
+                admin_state=False,
+                earfcndl=39150,
+            )
+        resp = acs_state_machine.handle_tr069_message(req)
+
+        # SM will be requesting object parameter values
+        self.assertTrue(
+            isinstance(resp, models.GetParameterValues),
+            'State machine should be requesting object param vals',
+        )
+
+        # Send back some typical values for object parameters
+        req = Tr069MessageBuilder.get_object_param_values_response()
+        resp = acs_state_machine.handle_tr069_message(req)
+
+        # In this scenario, the ACS and thus state machine will not need
+        # to delete or add objects to the eNB configuration.
+        # SM should then just be attempting to set parameter values
+        self.assertTrue(
+            isinstance(resp, models.SetParameterValues),
+            'State machine should be setting param values',
+        )
+
+        # Send back confirmation that the parameters were successfully set
+        req = models.SetParameterValuesResponse()
+        req.Status = 0
+        resp = acs_state_machine.handle_tr069_message(req)
+
+        # Expect a request for read-only params
+        self.assertTrue(
+            isinstance(resp, models.GetParameterValues),
+            'State machine should be requesting param values',
+        )
+        req = Tr069MessageBuilder.get_read_only_param_values_response()
+
+        # Send back some typical values
+        # And then SM should continue polling the read-only params
+        resp = acs_state_machine.handle_tr069_message(req)
+        self.assertTrue(
+            isinstance(resp, models.DummyInput),
+            'State machine should be ending session',
+        )
+
+        # If a different eNB is suddenly plugged in, or the same eNB sends a
+        # new Inform, enodebd should be able to handle it.
+        # Send an Inform message, wait for an InformResponse
+        inform_msg = \
+            Tr069MessageBuilder.get_inform(
+                '48BF74',
+                'BaiStation_V100R001C00B110SPC002',
+                '120200002618AGP0003',
+                ['2 PERIODIC'],
+            )
+        resp = acs_state_machine.handle_tr069_message(inform_msg)
+        self.assertTrue(
+            isinstance(resp, models.InformResponse),
+            'Should respond with an InformResponse',
+        )
+
+        # Send an empty http request to kick off the rest of provisioning
+        req = models.DummyInput()
+        resp = acs_state_machine.handle_tr069_message(req)
+
+        # Expect a request for an optional parameter, three times
+        self.assertTrue(
+            isinstance(resp, models.GetParameterValues),
+            'State machine should be requesting param values',
+        )
+
+    def test_reboot_after_invasive_changes(self) -> None:
+        """
+        Test the scenario where:
+        - eNodeB has already been powered for 10 minutes without configuration
+        - Setting parameters which are 'invasive' on the eNodeB
+        - Simulate the scenario up until reboot, and test that enodebd does
+          not try to complete configuration after reboot, because it is
+          waiting for REM process to finish running
+        - This test does not wait the ten minutes to simulate REM process
+          finishing on the Baicells eNodeB
+
+        'Invasive' parameters are those which require special behavior to apply
+        the changes for the eNodeB.
+
+        In the case of the Baicells eNodeB, properly applying changes to
+        invasive parameters requires rebooting the device.
+        """
+        acs_state_machine = \
+            EnodebAcsStateMachineBuilder\
+            .build_acs_state_machine(EnodebDeviceName.BAICELLS_OLD)
+
+        # Send an Inform message, wait for an InformResponse
+        inform_msg = \
+            Tr069MessageBuilder.get_inform(
+                '48BF74',
+                'BaiStation_V100R001C00B110SPC002',
+                '120200002618AGP0003',
+                ['2 PERIODIC'],
+            )
+        resp = acs_state_machine.handle_tr069_message(inform_msg)
+        self.assertTrue(
+            isinstance(resp, models.InformResponse),
+            'Should respond with an InformResponse',
+        )
+
+        # Send an empty http request to kick off the rest of provisioning
+        req = models.DummyInput()
+        resp = acs_state_machine.handle_tr069_message(req)
+
+        # Expect a request for an optional parameter, five times
+        self.assertTrue(
+            isinstance(resp, models.GetParameterValues),
+            'State machine should be requesting param values',
+        )
+        req = Tr069MessageBuilder.get_fault()
+        resp = acs_state_machine.handle_tr069_message(req)
+        self.assertTrue(
+            isinstance(resp, models.GetParameterValues),
+            'State machine should be requesting param values',
+        )
+        req = Tr069MessageBuilder.get_fault()
+        resp = acs_state_machine.handle_tr069_message(req)
+        self.assertTrue(
+            isinstance(resp, models.GetParameterValues),
+            'State machine should be requesting param values',
+        )
+        req = Tr069MessageBuilder.get_fault()
+        resp = acs_state_machine.handle_tr069_message(req)
+        self.assertTrue(
+            isinstance(resp, models.GetParameterValues),
+            'State machine should be requesting param values',
+        )
+        req = Tr069MessageBuilder.get_fault()
+        resp = acs_state_machine.handle_tr069_message(req)
+        self.assertTrue(
+            isinstance(resp, models.GetParameterValues),
+            'State machine should be requesting param values',
+        )
+        req = Tr069MessageBuilder.get_fault()
+        resp = acs_state_machine.handle_tr069_message(req)
+
+        # Expect a request for read-only params
+        self.assertTrue(
+            isinstance(resp, models.GetParameterValues),
+            'State machine should be requesting param values',
+        )
+        req = Tr069MessageBuilder.get_read_only_param_values_response()
+
+        # Send back some typical values
+        # And then SM should request regular parameter values
+        resp = acs_state_machine.handle_tr069_message(req)
+        self.assertTrue(
+            isinstance(resp, models.GetParameterValues),
+            'State machine should be requesting param values',
+        )
+
+        # Send back typical values for the regular parameters
+        req = Tr069MessageBuilder.get_regular_param_values_response()
+        resp = acs_state_machine.handle_tr069_message(req)
+
+        # SM will be requesting object parameter values
+        self.assertTrue(
+            isinstance(resp, models.GetParameterValues),
+            'State machine should be requesting object param vals',
+        )
+
+        # Send back some typical values for object parameters
+        req = Tr069MessageBuilder.get_object_param_values_response()
+        resp = acs_state_machine.handle_tr069_message(req)
+
+        # In this scenario, the ACS and thus state machine will not need
+        # to delete or add objects to the eNB configuration.
+        # SM should then just be attempting to set parameter values
+        self.assertTrue(
+            isinstance(resp, models.SetParameterValues),
+            'State machine should be setting param values',
+        )
+
+        # Send back confirmation that the parameters were successfully set
+        req = models.SetParameterValuesResponse()
+        req.Status = 0
+        resp = acs_state_machine.handle_tr069_message(req)
+
+        # Since invasive parameters have been set, then to apply the changes
+        # to the Baicells eNodeB, we need to reboot the device
+        self.assertTrue(isinstance(resp, models.Reboot))
+        req = Tr069MessageBuilder.get_reboot_response()
+        resp = acs_state_machine.handle_tr069_message(req)
+
+        # After the reboot has been received, enodebd should end the
+        # provisioning session
+        self.assertTrue(
+            isinstance(resp, models.DummyInput),
+            'After sending command to reboot the Baicells eNodeB, '
+            'enodeb should end the TR-069 session.',
+        )
+
+        # At this point, sometime after the eNodeB reboots, we expect it to
+        # send an Inform indicating reboot. Since it should be in REM process,
+        # we hold off on finishing configuration, and end TR-069 sessions.
+        req = \
+            Tr069MessageBuilder.get_inform(
+                '48BF74',
+                'BaiStation_V100R001C00B110SPC002',
+                '120200002618AGP0003',
+                ['1 BOOT', 'M Reboot'],
+            )
+        resp = acs_state_machine.handle_tr069_message(req)
+        self.assertTrue(
+            isinstance(resp, models.DummyInput),
+            'After receiving a post-reboot Inform, enodebd '
+            'should end TR-069 sessions for 10 minutes to wait '
+            'for REM process to finish.',
+        )
+
+        # Pretend that we have waited, and now we are in normal operation again
+        acs_state_machine.transition('wait_inform_post_reboot')
+        req = \
+            Tr069MessageBuilder.get_inform(
+                '48BF74',
+                'BaiStation_V100R001C00B110SPC002',
+                '120200002618AGP0003',
+                ['2 PERIODIC'],
+            )
+        resp = acs_state_machine.handle_tr069_message(req)
+        self.assertTrue(
+            isinstance(resp, models.InformResponse),
+            'After receiving a post-reboot Inform, enodebd '
+            'should end TR-069 sessions for 10 minutes to wait '
+            'for REM process to finish.',
+        )
+
+        req = models.DummyInput()
+        resp = acs_state_machine.handle_tr069_message(req)
+        self.assertTrue(
+            isinstance(resp, models.GetParameterValues),
+            'enodebd should be requesting params',
+        )
+        self.assertTrue(
+            len(resp.ParameterNames.string) > 1,
+            'Should be requesting transient params.',
+        )
+
+    def test_reboot_without_getting_optional(self) -> None:
+        """
+        The state machine should not skip figuring out which optional
+        parameters are present.
+        """
+        acs_state_machine = \
+            EnodebAcsStateMachineBuilder \
+                .build_acs_state_machine(EnodebDeviceName.BAICELLS_OLD)
+
+        # Send an Inform message, wait for an InformResponse
+        inform_msg = \
+            Tr069MessageBuilder.get_inform(
+                '48BF74',
+                'BaiStation_V100R001C00B110SPC002',
+                '120200002618AGP0003',
+                ['2 PERIODIC'],
+            )
+        resp = acs_state_machine.handle_tr069_message(inform_msg)
+        self.assertTrue(
+            isinstance(resp, models.InformResponse),
+            'Should respond with an InformResponse',
+        )
+
+        # And now reboot the eNodeB
+        acs_state_machine.transition('reboot')
+        req = models.DummyInput()
+        resp = acs_state_machine.handle_tr069_message(req)
+        self.assertTrue(isinstance(resp, models.Reboot))
+        req = Tr069MessageBuilder.get_reboot_response()
+        resp = acs_state_machine.handle_tr069_message(req)
+
+        # After the reboot has been received, enodebd should end the
+        # provisioning session
+        self.assertTrue(
+            isinstance(resp, models.DummyInput),
+            'After sending command to reboot the Baicells eNodeB, '
+            'enodeb should end the TR-069 session.',
+        )
+
+        # At this point, sometime after the eNodeB reboots, we expect it to
+        # send an Inform indicating reboot. Since it should be in REM process,
+        # we hold off on finishing configuration, and end TR-069 sessions.
+        req = \
+            Tr069MessageBuilder.get_inform(
+                '48BF74',
+                'BaiStation_V100R001C00B110SPC002',
+                '120200002618AGP0003',
+                ['1 BOOT', 'M Reboot'],
+            )
+        resp = acs_state_machine.handle_tr069_message(req)
+        self.assertTrue(
+            isinstance(resp, models.DummyInput),
+            'After receiving a post-reboot Inform, enodebd '
+            'should end TR-069 sessions for 10 minutes to wait '
+            'for REM process to finish.',
+        )
+
+        # Pretend that we have waited, and now we are in normal operation again
+        acs_state_machine.transition('wait_inform_post_reboot')
+        req = \
+            Tr069MessageBuilder.get_inform(
+                '48BF74',
+                'BaiStation_V100R001C00B110SPC002',
+                '120200002618AGP0003',
+                ['2 PERIODIC'],
+            )
+        resp = acs_state_machine.handle_tr069_message(req)
+        self.assertTrue(
+            isinstance(resp, models.InformResponse),
+            'After receiving a post-reboot Inform, enodebd '
+            'should end TR-069 sessions for 10 minutes to wait '
+            'for REM process to finish.',
+        )
+
+        # Since we haven't figured out the presence of optional parameters, the
+        # state machine should be requesting them now. There are three for the
+        # Baicells state machine.
+        req = models.DummyInput()
+        resp = acs_state_machine.handle_tr069_message(req)
+        self.assertTrue(
+            isinstance(resp, models.GetParameterValues),
+            'enodebd should be requesting params',
+        )
+        self.assertTrue(
+            len(resp.ParameterNames.string) == 1,
+            'Should be requesting optional params.',
+        )
+        req = Tr069MessageBuilder.get_fault()
+        resp = acs_state_machine.handle_tr069_message(req)
+        self.assertTrue(
+            isinstance(resp, models.GetParameterValues),
+            'State machine should be requesting param',
+        )
+        self.assertTrue(
+            len(resp.ParameterNames.string) == 1,
+            'Should be requesting optional params.',
+        )
+        req = Tr069MessageBuilder.get_fault()
+        resp = acs_state_machine.handle_tr069_message(req)
+        self.assertTrue(
+            isinstance(resp, models.GetParameterValues),
+            'State machine should be requesting param',
+        )
+        self.assertTrue(
+            len(resp.ParameterNames.string) == 1,
+            'Should be requesting optional params.',
+        )