blob: 24e24e700bad7250ba8fa301dcc20fe79b645c48 [file] [log] [blame]
"""
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.
"""
from abc import ABC, abstractmethod
from asyncio import BaseEventLoop
from time import time
from typing import Any, Type
from common.service import MagmaService
from data_models.data_model import DataModel
from data_models.data_model_parameters import ParameterName
from device_config.enodeb_config_postprocessor import (
EnodebConfigurationPostProcessor,
)
from device_config.enodeb_configuration import EnodebConfiguration
from devices.device_utils import EnodebDeviceName
from state_machines.acs_state_utils import are_tr069_params_equal
class EnodebAcsStateMachine(ABC):
"""
Handles all TR-069 messages.
Acts as the Auto Configuration Server (ACS), as specified by TR-069.
A device/version specific ACS message handler.
Different devices have various idiosyncrasies.
Subclass BasicEnodebAcsStateMachine for a specific device/version
implementation.
This ACS class can only handle a single connected eNodeB device.
Multiple connected eNodeB devices will lead to undefined behavior.
This ABC is more of an interface definition.
"""
def __init__(self, use_param_key: bool = False) -> None:
self._service = None
self._desired_cfg = None
self._device_cfg = None
self._data_model = None
self._are_invasive_changes_applied = True
# Flag to preseve backwards compatibility
self._use_param_key = use_param_key
self._param_version_key = None
def has_parameter(self, param: ParameterName) -> bool:
"""
Return True if the data model has the parameter
Raise KeyError if the parameter is optional and we do not know yet
if this eNodeB has the parameter
"""
return self.data_model.is_parameter_present(param)
def get_parameter(self, param: ParameterName) -> Any:
"""
Return the value of the parameter
"""
return self.device_cfg.get_parameter(param)
def set_parameter_asap(self, param: ParameterName, value: Any) -> None:
"""
Set the parameter to the suggested value ASAP
"""
self.desired_cfg.set_parameter(param, value)
def is_enodeb_configured(self) -> bool:
"""
True if the desired configuration matches the device configuration
"""
if self.desired_cfg is None:
return False
if not self.data_model.are_param_presences_known():
return False
desired = self.desired_cfg.get_parameter_names()
for name in desired:
val1 = self.desired_cfg.get_parameter(name)
val2 = self.device_cfg.get_parameter(name)
type_ = self.data_model.get_parameter(name).type
if not are_tr069_params_equal(val1, val2, type_):
return False
for obj_name in self.desired_cfg.get_object_names():
params = self.desired_cfg.get_parameter_names_for_object(obj_name)
for name in params:
val1 = self.device_cfg.get_parameter_for_object(name, obj_name)
val2 = self.desired_cfg.get_parameter_for_object(
name,
obj_name,
)
type_ = self.data_model.get_parameter(name).type
if not are_tr069_params_equal(val1, val2, type_):
return False
return True
@abstractmethod
def get_state(self) -> str:
"""
Get info about the state of the ACS
"""
pass
@abstractmethod
def handle_tr069_message(self, message: Any) -> Any:
"""
Given a TR-069 message sent from the hardware, return an
appropriate response
"""
pass
@abstractmethod
def transition(self, next_state: str) -> None:
pass
@property
def service(self) -> MagmaService:
return self._service
@service.setter
def service(self, service: MagmaService) -> None:
self._service = service
@property
def event_loop(self) -> BaseEventLoop:
return self._service.loop
@property
def mconfig(self) -> Any:
return self._service.mconfig
@property
def service_config(self) -> Any:
return self._service.config
@property
def desired_cfg(self) -> EnodebConfiguration:
return self._desired_cfg
@desired_cfg.setter
def desired_cfg(self, val: EnodebConfiguration) -> None:
if self.has_version_key:
self.parameter_version_inc()
self._desired_cfg = val
@property
def device_cfg(self) -> EnodebConfiguration:
return self._device_cfg
@device_cfg.setter
def device_cfg(self, val: EnodebConfiguration) -> None:
self._device_cfg = val
@property
def data_model(self) -> DataModel:
return self._data_model
@property
def has_version_key(self) -> bool:
""" Return if the ACS supports param version key """
return self._use_param_key
@property
def parameter_version_key(self) -> int:
""" Return the param version key """
return self._param_version_key
def parameter_version_inc(self):
""" Set the internal version key to the timestamp """
self._param_version_key = time()
@data_model.setter
def data_model(self, data_model) -> None:
self._data_model = data_model
@property
def are_invasive_changes_applied(self) -> bool:
return self._are_invasive_changes_applied
@are_invasive_changes_applied.setter
def are_invasive_changes_applied(self, is_applied: bool) -> None:
self._are_invasive_changes_applied = is_applied
@property
@abstractmethod
def data_model_class(self) -> Type[DataModel]:
pass
@property
@abstractmethod
def device_name(self) -> EnodebDeviceName:
pass
@property
@abstractmethod
def config_postprocessor(self) -> EnodebConfigurationPostProcessor:
pass
@abstractmethod
def reboot_asap(self) -> None:
"""
Send a request to reboot the eNodeB ASAP
"""
pass
@abstractmethod
def is_enodeb_connected(self) -> bool:
pass
@abstractmethod
def stop_state_machine(self) -> None:
pass