AETHER-2846 Support all parameters which lists in Aether Docs

AETHER-2847 Integrating with Prometheus and record eNB information
AETHER-2848 Move SAS configuration as enodeb base, not plugin in driver code
AETHER-2879 add gps information in prometheus
AETHER-2880 add ip and port as configurable parameter in enodebd
AETHER-2897 Firmware update feature over CWMP
AETHER-3022 Integrate firmware upgrade state into configuration workflow
AETHER-3120 Develop ACS state machine with firmware upgrade feature

Change-Id: I0bcbf2229ba3c1638f2a997f3c651f8d6240145d
diff --git a/state_machines/enb_acs_states.py b/state_machines/enb_acs_states.py
index a9b84a5..d9e2ed9 100644
--- a/state_machines/enb_acs_states.py
+++ b/state_machines/enb_acs_states.py
@@ -10,10 +10,15 @@
 See the License for the specific language governing permissions and
 limitations under the License.
 """
+
+import time
 from abc import ABC, abstractmethod
 from collections import namedtuple
 from typing import Any, Optional
 
+import metrics
+
+from configuration.service_configs import load_service_config
 from data_models.data_model import InvalidTrParamPath
 from data_models.data_model_parameters import ParameterName
 from device_config.configuration_init import build_desired_config
@@ -964,6 +969,14 @@
                         'Status=%d' % message.Status,
                     )
             self._mark_as_configured()
+
+            metrics.STAT_ENODEB_LAST_CONFIGURED.labels(
+                serial_number=self.acs.device_cfg.get_parameter("Serial number"),
+                ip_address=self.acs.device_cfg.get_parameter("ip_address"),
+                gps_lat=self.acs.device_cfg.get_parameter("GPS lat"),
+                gps_lon=self.acs.device_cfg.get_parameter("GPS long")
+            ).set(int(time.time()))
+
             if not self.acs.are_invasive_changes_applied:
                 return AcsReadMsgResult(True, self.apply_invasive_transition)
             return AcsReadMsgResult(True, self.done_transition)
@@ -1257,6 +1270,115 @@
         return 'Waiting after eNB reboot to prevent race conditions'
 
 
+class DownloadState(EnodebAcsState):
+    """
+    The eNB handler will enter this state when firmware version is older than desired version.
+    """
+
+    def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
+        super().__init__()
+        self.acs = acs
+        self.done_transition = when_done
+
+    def get_msg(self, message: Any) -> AcsMsgAndTransition:
+
+        print("ACS Device CFG")
+        print(self.acs.device_cfg._param_to_value)
+
+        request = models.Download()
+        request.CommandKey = "20220206215200"
+        request.FileType = "1 Firmware Upgrade Image"
+        request.URL = "http://18.116.99.179/firmware/Qproject_TEST3918_2102241222.ffw"
+        request.Username = ""
+        request.Password = ""
+        request.FileSize = 57208579
+        request.TargetFileName = "Qproject_TEST3918_2102241222.ffw"
+        request.DelaySeconds = 0
+        request.SuccessURL = ""
+        request.FailureURL = ""
+        return AcsMsgAndTransition(request, self.done_transition)
+
+    def state_description(self) -> str:
+        return 'Upgrade the firmware the desired version'
+
+class WaitDownloadResponseState(EnodebAcsState):
+    """
+    The eNB handler will enter this state after the Download command sent.
+    """
+
+    def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
+        super().__init__()
+        self.acs = acs
+        self.done_transition = when_done
+        
+    def read_msg(self, message: Any) -> AcsReadMsgResult:
+        if not isinstance(message, models.DownloadResponse):
+            return AcsReadMsgResult(False, None)
+        return AcsReadMsgResult(True, None)
+
+    def get_msg(self, message: Any) -> AcsMsgAndTransition:
+        """ Reply with empty message """
+        logger.info("Received Download Response from eNodeB")
+        return AcsMsgAndTransition(models.DummyInput(), self.done_transition)
+
+    def state_description(self) -> str:
+        return "Wait DownloadResponse message"
+
+class WaitInformTransferCompleteState(EnodebAcsState):
+    """
+    The eNB handler will enter this state after firmware upgraded and rebooted
+    """
+
+    REBOOT_TIMEOUT = 300 # In seconds
+    INFORM_EVENT_CODE = "7 TRANSFER COMPLETE"
+    PREIODIC_EVENT_CODE = "2 PERIODIC"
+
+    def __init__(self, acs: EnodebAcsStateMachine, when_done: str, when_periodic: str, when_timeout: str):
+        super().__init__()
+        self.acs = acs
+        self.done_transition = when_done
+        self.periodic_update_transition = when_periodic
+        self.timeout_transition = when_timeout
+        self.timeout_timer = None
+        self.timer_handle = None
+    
+    def enter(self):
+        print("Get into the TransferComplete State")
+        self.timeout_timer = StateMachineTimer(self.REBOOT_TIMEOUT)
+
+        def check_timer() -> None:
+            if self.timeout_timer.is_done():
+                self.acs.transition(self.timeout_transition)
+                raise Tr069Error("Didn't receive Inform response after rebooting")
+
+        self.timer_handle = self.acs.event_loop.call_later(
+            self.REBOOT_TIMEOUT,
+            check_timer,
+        )
+
+    def exit(self):
+        self.timer_handle.cancel()
+        self.timeout_timer = None
+
+    def get_msg(self, message: Any) -> AcsMsgAndTransition:
+        return AcsMsgAndTransition(models.DummyInput(), None)
+
+    def read_msg(self, message: Any) -> AcsReadMsgResult:
+        if not isinstance(message, models.Inform):
+            return AcsReadMsgResult(False, None)
+        if does_inform_have_event(message, self.PREIODIC_EVENT_CODE):
+            logger.info("Receive Periodic update from enodeb")
+            return AcsReadMsgResult(True, self.periodic_update_transition)
+        if does_inform_have_event(message, self.INFORM_EVENT_CODE):
+            logger.info("Receive Transfer complete")
+            return AcsReadMsgResult(True, self.done_transition)
+
+        # Unhandled situation
+        return AcsReadMsgResult(False, None)
+
+    def state_description(self) -> str:
+        return "Wait DownloadResponse message"
+
 class ErrorState(EnodebAcsState):
     """
     The eNB handler will enter this state when an unhandled Fault is received.
@@ -1290,4 +1412,4 @@
 
     def state_description(self) -> str:
         return 'Error state - awaiting manual restart of enodebd service or ' \
-               'an Inform to be received from the eNB'
+               'an Inform to be received from the eNB'
\ No newline at end of file