blob: 24e24e700bad7250ba8fa301dcc20fe79b645c48 [file] [log] [blame]
Wei-Yu Chen49950b92021-11-08 19:19:18 +08001"""
2Copyright 2020 The Magma Authors.
3
4This source code is licensed under the BSD-style license found in the
5LICENSE file in the root directory of this source tree.
6
7Unless required by applicable law or agreed to in writing, software
8distributed under the License is distributed on an "AS IS" BASIS,
9WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10See the License for the specific language governing permissions and
11limitations under the License.
12"""
13from abc import ABC, abstractmethod
14from asyncio import BaseEventLoop
15from time import time
16from typing import Any, Type
17
18from common.service import MagmaService
19from data_models.data_model import DataModel
20from data_models.data_model_parameters import ParameterName
21from device_config.enodeb_config_postprocessor import (
22 EnodebConfigurationPostProcessor,
23)
24from device_config.enodeb_configuration import EnodebConfiguration
25from devices.device_utils import EnodebDeviceName
26from state_machines.acs_state_utils import are_tr069_params_equal
27
28
29class EnodebAcsStateMachine(ABC):
30 """
31 Handles all TR-069 messages.
32 Acts as the Auto Configuration Server (ACS), as specified by TR-069.
33 A device/version specific ACS message handler.
34 Different devices have various idiosyncrasies.
35 Subclass BasicEnodebAcsStateMachine for a specific device/version
36 implementation.
37
38 This ACS class can only handle a single connected eNodeB device.
39 Multiple connected eNodeB devices will lead to undefined behavior.
40
41 This ABC is more of an interface definition.
42 """
43
44 def __init__(self, use_param_key: bool = False) -> None:
45 self._service = None
46 self._desired_cfg = None
47 self._device_cfg = None
48 self._data_model = None
49 self._are_invasive_changes_applied = True
50 # Flag to preseve backwards compatibility
51 self._use_param_key = use_param_key
52 self._param_version_key = None
53
54 def has_parameter(self, param: ParameterName) -> bool:
55 """
56 Return True if the data model has the parameter
57
58 Raise KeyError if the parameter is optional and we do not know yet
59 if this eNodeB has the parameter
60 """
61 return self.data_model.is_parameter_present(param)
62
63 def get_parameter(self, param: ParameterName) -> Any:
64 """
65 Return the value of the parameter
66 """
67 return self.device_cfg.get_parameter(param)
68
69 def set_parameter_asap(self, param: ParameterName, value: Any) -> None:
70 """
71 Set the parameter to the suggested value ASAP
72 """
73 self.desired_cfg.set_parameter(param, value)
74
75 def is_enodeb_configured(self) -> bool:
76 """
77 True if the desired configuration matches the device configuration
78 """
79 if self.desired_cfg is None:
80 return False
81 if not self.data_model.are_param_presences_known():
82 return False
83 desired = self.desired_cfg.get_parameter_names()
84
85 for name in desired:
86 val1 = self.desired_cfg.get_parameter(name)
87 val2 = self.device_cfg.get_parameter(name)
88 type_ = self.data_model.get_parameter(name).type
89 if not are_tr069_params_equal(val1, val2, type_):
90 return False
91
92 for obj_name in self.desired_cfg.get_object_names():
93 params = self.desired_cfg.get_parameter_names_for_object(obj_name)
94 for name in params:
95 val1 = self.device_cfg.get_parameter_for_object(name, obj_name)
96 val2 = self.desired_cfg.get_parameter_for_object(
97 name,
98 obj_name,
99 )
100 type_ = self.data_model.get_parameter(name).type
101 if not are_tr069_params_equal(val1, val2, type_):
102 return False
103 return True
104
105 @abstractmethod
106 def get_state(self) -> str:
107 """
108 Get info about the state of the ACS
109 """
110 pass
111
112 @abstractmethod
113 def handle_tr069_message(self, message: Any) -> Any:
114 """
115 Given a TR-069 message sent from the hardware, return an
116 appropriate response
117 """
118 pass
119
120 @abstractmethod
121 def transition(self, next_state: str) -> None:
122 pass
123
124 @property
125 def service(self) -> MagmaService:
126 return self._service
127
128 @service.setter
129 def service(self, service: MagmaService) -> None:
130 self._service = service
131
132 @property
133 def event_loop(self) -> BaseEventLoop:
134 return self._service.loop
135
136 @property
137 def mconfig(self) -> Any:
138 return self._service.mconfig
139
140 @property
141 def service_config(self) -> Any:
142 return self._service.config
143
144 @property
145 def desired_cfg(self) -> EnodebConfiguration:
146 return self._desired_cfg
147
148 @desired_cfg.setter
149 def desired_cfg(self, val: EnodebConfiguration) -> None:
150 if self.has_version_key:
151 self.parameter_version_inc()
152 self._desired_cfg = val
153
154 @property
155 def device_cfg(self) -> EnodebConfiguration:
156 return self._device_cfg
157
158 @device_cfg.setter
159 def device_cfg(self, val: EnodebConfiguration) -> None:
160 self._device_cfg = val
161
162 @property
163 def data_model(self) -> DataModel:
164 return self._data_model
165
166 @property
167 def has_version_key(self) -> bool:
168 """ Return if the ACS supports param version key """
169 return self._use_param_key
170
171 @property
172 def parameter_version_key(self) -> int:
173 """ Return the param version key """
174 return self._param_version_key
175
176 def parameter_version_inc(self):
177 """ Set the internal version key to the timestamp """
178 self._param_version_key = time()
179
180 @data_model.setter
181 def data_model(self, data_model) -> None:
182 self._data_model = data_model
183
184 @property
185 def are_invasive_changes_applied(self) -> bool:
186 return self._are_invasive_changes_applied
187
188 @are_invasive_changes_applied.setter
189 def are_invasive_changes_applied(self, is_applied: bool) -> None:
190 self._are_invasive_changes_applied = is_applied
191
192 @property
193 @abstractmethod
194 def data_model_class(self) -> Type[DataModel]:
195 pass
196
197 @property
198 @abstractmethod
199 def device_name(self) -> EnodebDeviceName:
200 pass
201
202 @property
203 @abstractmethod
204 def config_postprocessor(self) -> EnodebConfigurationPostProcessor:
205 pass
206
207 @abstractmethod
208 def reboot_asap(self) -> None:
209 """
210 Send a request to reboot the eNodeB ASAP
211 """
212 pass
213
214 @abstractmethod
215 def is_enodeb_connected(self) -> bool:
216 pass
217
218 @abstractmethod
219 def stop_state_machine(self) -> None:
220 pass