blob: 2a0a91119f0c5a1e0b7a1c975618e2b2a59b610d [file] [log] [blame]
Wei-Yu Chenad55cb82022-02-15 20:07:01 +08001# SPDX-FileCopyrightText: 2020 The Magma Authors.
2# SPDX-FileCopyrightText: 2022 Open Networking Foundation <support@opennetworking.org>
3#
4# SPDX-License-Identifier: BSD-3-Clause
Wei-Yu Chen678f0a52021-12-21 13:50:52 +08005
Wei-Yu Chen31ebdb52022-06-27 16:53:11 +08006import datetime
Wei-Yu Chen678f0a52021-12-21 13:50:52 +08007import time
Wei-Yu Chen49950b92021-11-08 19:19:18 +08008from abc import ABC, abstractmethod
9from collections import namedtuple
10from typing import Any, Optional
11
Wei-Yu Chen678f0a52021-12-21 13:50:52 +080012import metrics
13
14from configuration.service_configs import load_service_config
Wei-Yu Chen49950b92021-11-08 19:19:18 +080015from data_models.data_model import InvalidTrParamPath
16from data_models.data_model_parameters import ParameterName
17from device_config.configuration_init import build_desired_config
18from exceptions import ConfigurationError, Tr069Error
19from logger import EnodebdLogger as logger
20from state_machines.acs_state_utils import (
21 does_inform_have_event,
22 get_all_objects_to_add,
23 get_all_objects_to_delete,
24 get_all_param_values_to_set,
25 get_obj_param_values_to_set,
26 get_object_params_to_get,
27 get_optional_param_to_check,
28 get_param_values_to_set,
29 get_params_to_get,
30 parse_get_parameter_values_response,
31 process_inform_message,
32)
33from state_machines.enb_acs import EnodebAcsStateMachine
34from state_machines.timer import StateMachineTimer
35from tr069 import models
36
37AcsMsgAndTransition = namedtuple(
38 'AcsMsgAndTransition', ['msg', 'next_state'],
39)
40
41AcsReadMsgResult = namedtuple(
42 'AcsReadMsgResult', ['msg_handled', 'next_state'],
43)
44
45
46class EnodebAcsState(ABC):
47 """
48 State class for the Enodeb state machine
49
50 States can transition after reading a message from the eNB, sending a
51 message out to the eNB, or when a timer completes. As such, some states
52 are only responsible for message sending, and others are only responsible
53 for reading incoming messages.
54
55 In the constructor, set up state transitions.
56 """
57
58 def __init__(self):
59 self._acs = None
60
61 def enter(self) -> None:
62 """
63 Set up your timers here. Call transition(..) on the ACS when the timer
64 completes or throw an error
65 """
66 pass
67
68 def exit(self) -> None:
69 """Destroy timers here"""
70 pass
71
72 def read_msg(self, message: Any) -> AcsReadMsgResult:
73 """
74 Args: message: tr069 message
75 Returns: name of the next state, if transition required
76 """
77 raise ConfigurationError(
78 '%s should implement read_msg() if it '
79 'needs to handle message reading' % self.__class__.__name__,
80 )
81
82 def get_msg(self, message: Any) -> AcsMsgAndTransition:
83 """
84 Produce a message to send back to the eNB.
85
86 Args:
87 message: TR-069 message which was already processed by read_msg
88
89 Returns: Message and possible transition
90 """
91 raise ConfigurationError(
92 '%s should implement get_msg() if it '
93 'needs to produce messages' % self.__class__.__name__,
94 )
95
96 @property
97 def acs(self) -> EnodebAcsStateMachine:
98 return self._acs
99
100 @acs.setter
101 def acs(self, val: EnodebAcsStateMachine) -> None:
102 self._acs = val
103
104 @abstractmethod
105 def state_description(self) -> str:
106 """ Provide a few words about what the state represents """
107 pass
108
109
110class WaitInformState(EnodebAcsState):
111 """
112 This state indicates that no Inform message has been received yet, or
113 that no Inform message has been received for a long time.
114
115 This state is used to handle an Inform message that arrived when enodebd
116 already believes that the eNB is connected. As such, it is unclear to
117 enodebd whether the eNB is just sending another Inform, or if a different
118 eNB was plugged into the same interface.
119 """
120
121 def __init__(
122 self,
123 acs: EnodebAcsStateMachine,
124 when_done: str,
125 when_boot: Optional[str] = None,
126 ):
127 super().__init__()
128 self.acs = acs
129 self.done_transition = when_done
130 self.boot_transition = when_boot
131 self.has_enb_just_booted = False
132
133 def read_msg(self, message: Any) -> AcsReadMsgResult:
134 """
135 Args:
136 message: models.Inform Tr069 Inform message
137 """
138 if not isinstance(message, models.Inform):
139 return AcsReadMsgResult(False, None)
Wei-Yu Chenb91af852022-03-15 22:24:49 +0800140
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800141 process_inform_message(
142 message, self.acs.data_model,
143 self.acs.device_cfg,
144 )
Wei-Yu Chenb91af852022-03-15 22:24:49 +0800145
146 # Switch enodeb status to connected
147 metrics.set_enb_status(
148 self.acs.device_cfg.get_parameter("Serial number"),
149 status="connected"
150 )
151
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800152 if does_inform_have_event(message, '1 BOOT'):
153 return AcsReadMsgResult(True, self.boot_transition)
154 return AcsReadMsgResult(True, None)
155
156 def get_msg(self, message: Any) -> AcsMsgAndTransition:
157 """ Reply with InformResponse """
Wei-Yu Chenb91af852022-03-15 22:24:49 +0800158
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800159 response = models.InformResponse()
160 # Set maxEnvelopes to 1, as per TR-069 spec
161 response.MaxEnvelopes = 1
162 return AcsMsgAndTransition(response, self.done_transition)
163
164 def state_description(self) -> str:
165 return 'Waiting for an Inform'
166
167
168class GetRPCMethodsState(EnodebAcsState):
169 """
170 After the first Inform message from boot, it is expected that the eNB
171 will try to learn the RPC methods of the ACS.
172 """
173
174 def __init__(self, acs: EnodebAcsStateMachine, when_done: str, when_skip: str):
175 super().__init__()
176 self.acs = acs
177 self.done_transition = when_done
178 self.skip_transition = when_skip
179
180 def read_msg(self, message: Any) -> AcsReadMsgResult:
181 # If this is a regular Inform, not after a reboot we'll get an empty
182 if isinstance(message, models.DummyInput):
183 return AcsReadMsgResult(True, self.skip_transition)
184 if not isinstance(message, models.GetRPCMethods):
185 return AcsReadMsgResult(False, self.done_transition)
186 return AcsReadMsgResult(True, None)
187
188 def get_msg(self, message: Any) -> AcsMsgAndTransition:
189 resp = models.GetRPCMethodsResponse()
190 resp.MethodList = models.MethodList()
191 RPC_METHODS = ['Inform', 'GetRPCMethods', 'TransferComplete']
192 resp.MethodList.arrayType = 'xsd:string[%d]' \
193 % len(RPC_METHODS)
194 resp.MethodList.string = RPC_METHODS
195 return AcsMsgAndTransition(resp, self.done_transition)
196
197 def state_description(self) -> str:
198 return 'Waiting for incoming GetRPC Methods after boot'
199
200
201class BaicellsRemWaitState(EnodebAcsState):
202 """
203 We've already received an Inform message. This state is to handle a
204 Baicells eNodeB issue.
205
206 After eNodeB is rebooted, hold off configuring it for some time to give
207 time for REM to run. This is a BaiCells eNodeB issue that doesn't support
208 enabling the eNodeB during initial REM.
209
210 In this state, just hang at responding to Inform, and then ending the
211 TR-069 session.
212 """
213
214 CONFIG_DELAY_AFTER_BOOT = 600
215
216 def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
217 super().__init__()
218 self.acs = acs
219 self.done_transition = when_done
220 self.rem_timer = None
221
222 def enter(self):
223 self.rem_timer = StateMachineTimer(self.CONFIG_DELAY_AFTER_BOOT)
224 logger.info(
225 'Holding off of eNB configuration for %s seconds. '
226 'Will resume after eNB REM process has finished. ',
227 self.CONFIG_DELAY_AFTER_BOOT,
228 )
229
230 def exit(self):
231 self.rem_timer = None
232
233 def read_msg(self, message: Any) -> AcsReadMsgResult:
234 if not isinstance(message, models.Inform):
235 return AcsReadMsgResult(False, None)
236 process_inform_message(
237 message, self.acs.data_model,
238 self.acs.device_cfg,
239 )
240 return AcsReadMsgResult(True, None)
241
242 def get_msg(self, message: Any) -> AcsMsgAndTransition:
243 if self.rem_timer.is_done():
244 return AcsMsgAndTransition(
245 models.DummyInput(),
246 self.done_transition,
247 )
248 return AcsMsgAndTransition(models.DummyInput(), None)
249
250 def state_description(self) -> str:
251 remaining = self.rem_timer.seconds_remaining()
252 return 'Waiting for eNB REM to run for %d more seconds before ' \
253 'resuming with configuration.' % remaining
254
255
256class WaitEmptyMessageState(EnodebAcsState):
257 def __init__(
258 self,
259 acs: EnodebAcsStateMachine,
260 when_done: str,
261 when_missing: Optional[str] = None,
262 ):
263 super().__init__()
264 self.acs = acs
265 self.done_transition = when_done
266 self.unknown_param_transition = when_missing
267
268 def read_msg(self, message: Any) -> AcsReadMsgResult:
269 """
270 It's expected that we transition into this state right after receiving
271 an Inform message and replying with an InformResponse. At that point,
272 the eNB sends an empty HTTP request (aka DummyInput) to initiate the
273 rest of the provisioning process
274 """
275 if not isinstance(message, models.DummyInput):
276 logger.debug("Ignoring message %s", str(type(message)))
277 return AcsReadMsgResult(msg_handled=False, next_state=None)
278 if self.unknown_param_transition:
279 if get_optional_param_to_check(self.acs.data_model):
280 return AcsReadMsgResult(
281 msg_handled=True,
282 next_state=self.unknown_param_transition,
283 )
284 return AcsReadMsgResult(
285 msg_handled=True,
286 next_state=self.done_transition,
287 )
288
289 def get_msg(self, message: Any) -> AcsReadMsgResult:
290 """
291 Return a dummy message waiting for the empty message from CPE
292 """
293 request = models.DummyInput()
294 return AcsMsgAndTransition(msg=request, next_state=None)
295
296 def state_description(self) -> str:
297 return 'Waiting for empty message from eNodeB'
298
299
300class CheckOptionalParamsState(EnodebAcsState):
301 def __init__(
302 self,
303 acs: EnodebAcsStateMachine,
304 when_done: str,
305 ):
306 super().__init__()
307 self.acs = acs
308 self.done_transition = when_done
309 self.optional_param = None
310
311 def get_msg(self, message: Any) -> AcsMsgAndTransition:
312 self.optional_param = get_optional_param_to_check(self.acs.data_model)
313 if self.optional_param is None:
314 raise Tr069Error('Invalid State')
315 # Generate the request
316 request = models.GetParameterValues()
317 request.ParameterNames = models.ParameterNames()
318 request.ParameterNames.arrayType = 'xsd:string[1]'
319 request.ParameterNames.string = []
320 path = self.acs.data_model.get_parameter(self.optional_param).path
321 request.ParameterNames.string.append(path)
322 return AcsMsgAndTransition(request, None)
323
324 def read_msg(self, message: Any) -> AcsReadMsgResult:
325 """ Process either GetParameterValuesResponse or a Fault """
326 if type(message) == models.Fault:
327 self.acs.data_model.set_parameter_presence(
328 self.optional_param,
329 False,
330 )
331 elif type(message) == models.GetParameterValuesResponse:
332 name_to_val = parse_get_parameter_values_response(
333 self.acs.data_model,
334 message,
335 )
336 logger.debug(
337 'Received CPE parameter values: %s',
338 str(name_to_val),
339 )
340 for name, val in name_to_val.items():
341 self.acs.data_model.set_parameter_presence(
342 self.optional_param,
343 True,
344 )
345 magma_val = self.acs.data_model.transform_for_magma(name, val)
346 self.acs.device_cfg.set_parameter(name, magma_val)
347 else:
348 return AcsReadMsgResult(False, None)
349
350 if get_optional_param_to_check(self.acs.data_model) is not None:
351 return AcsReadMsgResult(True, None)
352 return AcsReadMsgResult(True, self.done_transition)
353
354 def state_description(self) -> str:
355 return 'Checking if some optional parameters exist in data model'
356
357
358class SendGetTransientParametersState(EnodebAcsState):
359 """
360 Periodically read eNodeB status. Note: keep frequency low to avoid
361 backing up large numbers of read operations if enodebd is busy.
362 Some eNB parameters are read only and updated by the eNB itself.
363 """
364 PARAMETERS = [
365 ParameterName.OP_STATE,
366 ParameterName.RF_TX_STATUS,
367 ParameterName.GPS_STATUS,
368 ParameterName.PTP_STATUS,
369 ParameterName.MME_STATUS,
370 ParameterName.GPS_LAT,
371 ParameterName.GPS_LONG,
372 ]
373
374 def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
375 super().__init__()
376 self.acs = acs
377 self.done_transition = when_done
378
379 def read_msg(self, message: Any) -> AcsReadMsgResult:
380 if not isinstance(message, models.DummyInput):
381 return AcsReadMsgResult(False, None)
382 return AcsReadMsgResult(True, None)
383
384 def get_msg(self, message: Any) -> AcsMsgAndTransition:
385 request = models.GetParameterValues()
386 request.ParameterNames = models.ParameterNames()
387 request.ParameterNames.string = []
388 for name in self.PARAMETERS:
389 # Not all data models have these parameters
390 if self.acs.data_model.is_parameter_present(name):
391 path = self.acs.data_model.get_parameter(name).path
392 request.ParameterNames.string.append(path)
393 request.ParameterNames.arrayType = \
394 'xsd:string[%d]' % len(request.ParameterNames.string)
395
396 return AcsMsgAndTransition(request, self.done_transition)
397
398 def state_description(self) -> str:
399 return 'Getting transient read-only parameters'
400
401
402class WaitGetTransientParametersState(EnodebAcsState):
403 """
404 Periodically read eNodeB status. Note: keep frequency low to avoid
405 backing up large numbers of read operations if enodebd is busy
406 """
407
408 def __init__(
409 self,
410 acs: EnodebAcsStateMachine,
411 when_get: str,
412 when_get_obj_params: str,
413 when_delete: str,
414 when_add: str,
415 when_set: str,
416 when_skip: str,
417 ):
418 super().__init__()
419 self.acs = acs
420 self.done_transition = when_get
421 self.get_obj_params_transition = when_get_obj_params
422 self.rm_obj_transition = when_delete
423 self.add_obj_transition = when_add
424 self.set_transition = when_set
425 self.skip_transition = when_skip
426
427 def read_msg(self, message: Any) -> AcsReadMsgResult:
428 if not isinstance(message, models.GetParameterValuesResponse):
429 return AcsReadMsgResult(False, None)
430 # Current values of the fetched parameters
431 name_to_val = parse_get_parameter_values_response(
432 self.acs.data_model,
433 message,
434 )
435 logger.debug('Fetched Transient Params: %s', str(name_to_val))
436
437 # Update device configuration
438 for name in name_to_val:
439 magma_val = \
440 self.acs.data_model.transform_for_magma(
441 name,
442 name_to_val[name],
443 )
444 self.acs.device_cfg.set_parameter(name, magma_val)
445
446 return AcsReadMsgResult(True, self.get_next_state())
447
448 def get_next_state(self) -> str:
449 should_get_params = \
450 len(
451 get_params_to_get(
452 self.acs.device_cfg,
453 self.acs.data_model,
454 ),
455 ) > 0
456 if should_get_params:
457 return self.done_transition
458 should_get_obj_params = \
459 len(
460 get_object_params_to_get(
461 self.acs.desired_cfg,
462 self.acs.device_cfg,
463 self.acs.data_model,
464 ),
465 ) > 0
466 if should_get_obj_params:
467 return self.get_obj_params_transition
468 elif len(
469 get_all_objects_to_delete(
470 self.acs.desired_cfg,
471 self.acs.device_cfg,
472 ),
473 ) > 0:
474 return self.rm_obj_transition
475 elif len(
476 get_all_objects_to_add(
477 self.acs.desired_cfg,
478 self.acs.device_cfg,
479 ),
480 ) > 0:
481 return self.add_obj_transition
482 return self.skip_transition
483
484 def state_description(self) -> str:
485 return 'Getting transient read-only parameters'
486
487
488class GetParametersState(EnodebAcsState):
489 """
490 Get the value of most parameters of the eNB that are defined in the data
491 model. Object parameters are excluded.
492 """
493
494 def __init__(
495 self,
496 acs: EnodebAcsStateMachine,
497 when_done: str,
498 request_all_params: bool = False,
499 ):
500 super().__init__()
501 self.acs = acs
502 self.done_transition = when_done
503 # Set to True if we want to request values of all parameters, even if
504 # the ACS state machine already has recorded values of them.
505 self.request_all_params = request_all_params
506
507 def read_msg(self, message: Any) -> AcsReadMsgResult:
508 """
509 It's expected that we transition into this state right after receiving
510 an Inform message and replying with an InformResponse. At that point,
511 the eNB sends an empty HTTP request (aka DummyInput) to initiate the
512 rest of the provisioning process
513 """
514 if not isinstance(message, models.DummyInput):
515 return AcsReadMsgResult(False, None)
516 return AcsReadMsgResult(True, None)
517
518 def get_msg(self, message: Any) -> AcsMsgAndTransition:
519 """
520 Respond with GetParameterValuesRequest
521
522 Get the values of all parameters defined in the data model.
523 Also check which addable objects are present, and what the values of
524 parameters for those objects are.
525 """
526
527 # Get the names of regular parameters
528 names = get_params_to_get(
529 self.acs.device_cfg, self.acs.data_model,
530 self.request_all_params,
531 )
532
533 # Generate the request
534 request = models.GetParameterValues()
535 request.ParameterNames = models.ParameterNames()
536 request.ParameterNames.arrayType = 'xsd:string[%d]' \
537 % len(names)
538 request.ParameterNames.string = []
539 for name in names:
540 path = self.acs.data_model.get_parameter(name).path
541 if path is not InvalidTrParamPath:
542 # Only get data elements backed by tr69 path
543 request.ParameterNames.string.append(path)
544
545 return AcsMsgAndTransition(request, self.done_transition)
546
547 def state_description(self) -> str:
548 return 'Getting non-object parameters'
549
550
551class WaitGetParametersState(EnodebAcsState):
552 def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
553 super().__init__()
554 self.acs = acs
555 self.done_transition = when_done
556
557 def read_msg(self, message: Any) -> AcsReadMsgResult:
558 """ Process GetParameterValuesResponse """
559 if not isinstance(message, models.GetParameterValuesResponse):
560 return AcsReadMsgResult(False, None)
561 name_to_val = parse_get_parameter_values_response(
562 self.acs.data_model,
563 message,
564 )
565 logger.debug('Received CPE parameter values: %s', str(name_to_val))
566 for name, val in name_to_val.items():
567 magma_val = self.acs.data_model.transform_for_magma(name, val)
568 self.acs.device_cfg.set_parameter(name, magma_val)
569 return AcsReadMsgResult(True, self.done_transition)
570
571 def state_description(self) -> str:
572 return 'Getting non-object parameters'
573
574
575class GetObjectParametersState(EnodebAcsState):
576 def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
577 super().__init__()
578 self.acs = acs
579 self.done_transition = when_done
580
581 def get_msg(self, message: Any) -> AcsMsgAndTransition:
582 """ Respond with GetParameterValuesRequest """
583 names = get_object_params_to_get(
584 self.acs.desired_cfg,
585 self.acs.device_cfg,
586 self.acs.data_model,
587 )
588
589 # Generate the request
590 request = models.GetParameterValues()
591 request.ParameterNames = models.ParameterNames()
592 request.ParameterNames.arrayType = 'xsd:string[%d]' \
593 % len(names)
594 request.ParameterNames.string = []
595 for name in names:
596 path = self.acs.data_model.get_parameter(name).path
597 request.ParameterNames.string.append(path)
598
599 return AcsMsgAndTransition(request, self.done_transition)
600
601 def state_description(self) -> str:
602 return 'Getting object parameters'
603
604
605class WaitGetObjectParametersState(EnodebAcsState):
606 def __init__(
607 self,
608 acs: EnodebAcsStateMachine,
609 when_delete: str,
610 when_add: str,
611 when_set: str,
612 when_skip: str,
613 ):
614 super().__init__()
615 self.acs = acs
616 self.rm_obj_transition = when_delete
617 self.add_obj_transition = when_add
618 self.set_params_transition = when_set
619 self.skip_transition = when_skip
620
621 def read_msg(self, message: Any) -> AcsReadMsgResult:
622 """ Process GetParameterValuesResponse """
623 if not isinstance(message, models.GetParameterValuesResponse):
624 return AcsReadMsgResult(False, None)
625
626 path_to_val = {}
627 if hasattr(message.ParameterList, 'ParameterValueStruct') and \
628 message.ParameterList.ParameterValueStruct is not None:
629 for param_value_struct in message.ParameterList.ParameterValueStruct:
630 path_to_val[param_value_struct.Name] = \
631 param_value_struct.Value.Data
632 logger.debug('Received object parameters: %s', str(path_to_val))
633
634 # Number of PLMN objects reported can be incorrect. Let's count them
635 num_plmns = 0
636 obj_to_params = self.acs.data_model.get_numbered_param_names()
637 while True:
638 obj_name = ParameterName.PLMN_N % (num_plmns + 1)
639 if obj_name not in obj_to_params or len(obj_to_params[obj_name]) == 0:
640 logger.warning(
641 "eNB has PLMN %s but not defined in model",
642 obj_name,
643 )
644 break
645 param_name_list = obj_to_params[obj_name]
646 obj_path = self.acs.data_model.get_parameter(param_name_list[0]).path
647 if obj_path not in path_to_val:
648 break
649 if not self.acs.device_cfg.has_object(obj_name):
650 self.acs.device_cfg.add_object(obj_name)
651 num_plmns += 1
652 for name in param_name_list:
653 path = self.acs.data_model.get_parameter(name).path
654 value = path_to_val[path]
655 magma_val = \
656 self.acs.data_model.transform_for_magma(name, value)
657 self.acs.device_cfg.set_parameter_for_object(
658 name, magma_val,
659 obj_name,
660 )
661 num_plmns_reported = \
662 int(self.acs.device_cfg.get_parameter(ParameterName.NUM_PLMNS))
663 if num_plmns != num_plmns_reported:
664 logger.warning(
665 "eNB reported %d PLMNs but found %d",
666 num_plmns_reported, num_plmns,
667 )
668 self.acs.device_cfg.set_parameter(
669 ParameterName.NUM_PLMNS,
670 num_plmns,
671 )
672
673 # Now we can have the desired state
674 if self.acs.desired_cfg is None:
675 self.acs.desired_cfg = build_desired_config(
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800676 self.acs.device_cfg,
677 self.acs.data_model,
678 self.acs.config_postprocessor,
679 )
680
681 if len(
682 get_all_objects_to_delete(
683 self.acs.desired_cfg,
684 self.acs.device_cfg,
685 ),
686 ) > 0:
687 return AcsReadMsgResult(True, self.rm_obj_transition)
688 elif len(
689 get_all_objects_to_add(
690 self.acs.desired_cfg,
691 self.acs.device_cfg,
692 ),
693 ) > 0:
694 return AcsReadMsgResult(True, self.add_obj_transition)
695 elif len(
696 get_all_param_values_to_set(
697 self.acs.desired_cfg,
698 self.acs.device_cfg,
699 self.acs.data_model,
700 ),
701 ) > 0:
702 return AcsReadMsgResult(True, self.set_params_transition)
703 return AcsReadMsgResult(True, self.skip_transition)
704
705 def state_description(self) -> str:
706 return 'Getting object parameters'
707
708
709class DeleteObjectsState(EnodebAcsState):
710 def __init__(
711 self,
712 acs: EnodebAcsStateMachine,
713 when_add: str,
714 when_skip: str,
715 ):
716 super().__init__()
717 self.acs = acs
718 self.deleted_param = None
719 self.add_obj_transition = when_add
720 self.skip_transition = when_skip
721
722 def get_msg(self, message: Any) -> AcsMsgAndTransition:
723 """
724 Send DeleteObject message to TR-069 and poll for response(s).
725 Input:
726 - Object name (string)
727 """
728 request = models.DeleteObject()
729 self.deleted_param = get_all_objects_to_delete(
730 self.acs.desired_cfg,
731 self.acs.device_cfg,
732 )[0]
733 request.ObjectName = \
734 self.acs.data_model.get_parameter(self.deleted_param).path
735 return AcsMsgAndTransition(request, None)
736
737 def read_msg(self, message: Any) -> AcsReadMsgResult:
738 """
739 Send DeleteObject message to TR-069 and poll for response(s).
740 Input:
741 - Object name (string)
742 """
743 if type(message) == models.DeleteObjectResponse:
744 if message.Status != 0:
745 raise Tr069Error(
746 'Received DeleteObjectResponse with '
747 'Status=%d' % message.Status,
748 )
749 elif type(message) == models.Fault:
750 raise Tr069Error(
751 'Received Fault in response to DeleteObject '
752 '(faultstring = %s)' % message.FaultString,
753 )
754 else:
755 return AcsReadMsgResult(False, None)
756
757 self.acs.device_cfg.delete_object(self.deleted_param)
758 obj_list_to_delete = get_all_objects_to_delete(
759 self.acs.desired_cfg,
760 self.acs.device_cfg,
761 )
762 if len(obj_list_to_delete) > 0:
763 return AcsReadMsgResult(True, None)
764 if len(
765 get_all_objects_to_add(
766 self.acs.desired_cfg,
767 self.acs.device_cfg,
768 ),
769 ) == 0:
770 return AcsReadMsgResult(True, self.skip_transition)
771 return AcsReadMsgResult(True, self.add_obj_transition)
772
773 def state_description(self) -> str:
774 return 'Deleting objects'
775
776
777class AddObjectsState(EnodebAcsState):
778 def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
779 super().__init__()
780 self.acs = acs
781 self.done_transition = when_done
782 self.added_param = None
783
784 def get_msg(self, message: Any) -> AcsMsgAndTransition:
785 request = models.AddObject()
786 self.added_param = get_all_objects_to_add(
787 self.acs.desired_cfg,
788 self.acs.device_cfg,
789 )[0]
790 desired_param = self.acs.data_model.get_parameter(self.added_param)
791 desired_path = desired_param.path
792 path_parts = desired_path.split('.')
793 # If adding enumerated object, ie. XX.N. we should add it to the
794 # parent object XX. so strip the index
795 if len(path_parts) > 2 and \
796 path_parts[-1] == '' and path_parts[-2].isnumeric():
797 logger.debug('Stripping index from path=%s', desired_path)
798 desired_path = '.'.join(path_parts[:-2]) + '.'
799 request.ObjectName = desired_path
800 return AcsMsgAndTransition(request, None)
801
802 def read_msg(self, message: Any) -> AcsReadMsgResult:
803 if type(message) == models.AddObjectResponse:
804 if message.Status != 0:
805 raise Tr069Error(
806 'Received AddObjectResponse with '
807 'Status=%d' % message.Status,
808 )
809 elif type(message) == models.Fault:
810 raise Tr069Error(
811 'Received Fault in response to AddObject '
812 '(faultstring = %s)' % message.FaultString,
813 )
814 else:
815 return AcsReadMsgResult(False, None)
816 instance_n = message.InstanceNumber
817 self.acs.device_cfg.add_object(self.added_param % instance_n)
818 obj_list_to_add = get_all_objects_to_add(
819 self.acs.desired_cfg,
820 self.acs.device_cfg,
821 )
822 if len(obj_list_to_add) > 0:
823 return AcsReadMsgResult(True, None)
824 return AcsReadMsgResult(True, self.done_transition)
825
826 def state_description(self) -> str:
827 return 'Adding objects'
828
829
830class SetParameterValuesState(EnodebAcsState):
831 def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
832 super().__init__()
833 self.acs = acs
834 self.done_transition = when_done
835
836 def get_msg(self, message: Any) -> AcsMsgAndTransition:
837 request = models.SetParameterValues()
838 request.ParameterList = models.ParameterValueList()
839 param_values = get_all_param_values_to_set(
840 self.acs.desired_cfg,
841 self.acs.device_cfg,
842 self.acs.data_model,
843 )
844 request.ParameterList.arrayType = 'cwmp:ParameterValueStruct[%d]' \
845 % len(param_values)
846 request.ParameterList.ParameterValueStruct = []
847 logger.debug(
848 'Sending TR069 request to set CPE parameter values: %s',
849 str(param_values),
850 )
851 # TODO: Match key response when we support having multiple outstanding
852 # calls.
853 if self.acs.has_version_key:
854 request.ParameterKey = models.ParameterKeyType()
855 request.ParameterKey.Data =\
856 "SetParameter-{:10.0f}".format(self.acs.parameter_version_key)
857 request.ParameterKey.type = 'xsd:string'
858
859 for name, value in param_values.items():
860 param_info = self.acs.data_model.get_parameter(name)
861 type_ = param_info.type
862 name_value = models.ParameterValueStruct()
863 name_value.Value = models.anySimpleType()
864 name_value.Name = param_info.path
865 enb_value = self.acs.data_model.transform_for_enb(name, value)
866 if type_ in ('int', 'unsignedInt'):
867 name_value.Value.type = 'xsd:%s' % type_
868 name_value.Value.Data = str(enb_value)
869 elif type_ == 'boolean':
870 # Boolean values have integral representations in spec
871 name_value.Value.type = 'xsd:boolean'
872 name_value.Value.Data = str(int(enb_value))
873 elif type_ == 'string':
874 name_value.Value.type = 'xsd:string'
875 name_value.Value.Data = str(enb_value)
876 else:
877 raise Tr069Error(
878 'Unsupported type for %s: %s' %
879 (name, type_),
880 )
881 if param_info.is_invasive:
882 self.acs.are_invasive_changes_applied = False
883 request.ParameterList.ParameterValueStruct.append(name_value)
884
885 return AcsMsgAndTransition(request, self.done_transition)
886
887 def state_description(self) -> str:
888 return 'Setting parameter values'
889
890
891class SetParameterValuesNotAdminState(EnodebAcsState):
892 def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
893 super().__init__()
894 self.acs = acs
895 self.done_transition = when_done
896
897 def get_msg(self, message: Any) -> AcsMsgAndTransition:
898 request = models.SetParameterValues()
899 request.ParameterList = models.ParameterValueList()
900 param_values = get_all_param_values_to_set(
901 self.acs.desired_cfg,
902 self.acs.device_cfg,
903 self.acs.data_model,
904 exclude_admin=True,
905 )
906 request.ParameterList.arrayType = 'cwmp:ParameterValueStruct[%d]' \
907 % len(param_values)
908 request.ParameterList.ParameterValueStruct = []
909 logger.debug(
910 'Sending TR069 request to set CPE parameter values: %s',
911 str(param_values),
912 )
913 for name, value in param_values.items():
914 param_info = self.acs.data_model.get_parameter(name)
915 type_ = param_info.type
916 name_value = models.ParameterValueStruct()
917 name_value.Value = models.anySimpleType()
918 name_value.Name = param_info.path
919 enb_value = self.acs.data_model.transform_for_enb(name, value)
920 if type_ in ('int', 'unsignedInt'):
921 name_value.Value.type = 'xsd:%s' % type_
922 name_value.Value.Data = str(enb_value)
923 elif type_ == 'boolean':
924 # Boolean values have integral representations in spec
925 name_value.Value.type = 'xsd:boolean'
926 name_value.Value.Data = str(int(enb_value))
927 elif type_ == 'string':
928 name_value.Value.type = 'xsd:string'
929 name_value.Value.Data = str(enb_value)
930 else:
931 raise Tr069Error(
932 'Unsupported type for %s: %s' %
933 (name, type_),
934 )
935 if param_info.is_invasive:
936 self.acs.are_invasive_changes_applied = False
937 request.ParameterList.ParameterValueStruct.append(name_value)
938
939 return AcsMsgAndTransition(request, self.done_transition)
940
941 def state_description(self) -> str:
942 return 'Setting parameter values excluding Admin Enable'
943
944
945class WaitSetParameterValuesState(EnodebAcsState):
946 def __init__(
947 self,
948 acs: EnodebAcsStateMachine,
949 when_done: str,
950 when_apply_invasive: str,
951 status_non_zero_allowed: bool = False,
952 ):
953 super().__init__()
954 self.acs = acs
955 self.done_transition = when_done
956 self.apply_invasive_transition = when_apply_invasive
957 # Set Params can legally return zero and non zero status
958 # Per tr-196, if there are errors the method should return a fault.
959 # Make flag optional to compensate for existing radios returning non
960 # zero on error.
961 self.status_non_zero_allowed = status_non_zero_allowed
962
963 def read_msg(self, message: Any) -> AcsReadMsgResult:
964 if type(message) == models.SetParameterValuesResponse:
965 if not self.status_non_zero_allowed:
966 if message.Status != 0:
967 raise Tr069Error(
968 'Received SetParameterValuesResponse with '
969 'Status=%d' % message.Status,
970 )
971 self._mark_as_configured()
Wei-Yu Chen678f0a52021-12-21 13:50:52 +0800972
Wei-Yu Chenb91af852022-03-15 22:24:49 +0800973 metrics.set_enb_last_configured_time(
974 self.acs.device_cfg.get_parameter("Serial number"),
975 self.acs.device_cfg.get_parameter("ip_address"),
976 int(time.time())
977 )
978
979 # Switch enodeb status to configured
980 metrics.set_enb_status(
981 self.acs.device_cfg.get_parameter("Serial number"),
982 status="configured"
983 )
Wei-Yu Chen678f0a52021-12-21 13:50:52 +0800984
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800985 if not self.acs.are_invasive_changes_applied:
986 return AcsReadMsgResult(True, self.apply_invasive_transition)
Wei-Yu Chenb91af852022-03-15 22:24:49 +0800987
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800988 return AcsReadMsgResult(True, self.done_transition)
Wei-Yu Chenb91af852022-03-15 22:24:49 +0800989
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800990 elif type(message) == models.Fault:
991 logger.error(
992 'Received Fault in response to SetParameterValues, '
993 'Code (%s), Message (%s)', message.FaultCode,
994 message.FaultString,
995 )
996 if message.SetParameterValuesFault is not None:
997 for fault in message.SetParameterValuesFault:
998 logger.error(
999 'SetParameterValuesFault Param: %s, '
1000 'Code: %s, String: %s', fault.ParameterName,
1001 fault.FaultCode, fault.FaultString,
1002 )
1003 return AcsReadMsgResult(False, None)
1004
1005 def _mark_as_configured(self) -> None:
1006 """
1007 A successful attempt at setting parameter values means that we need to
1008 update what we think the eNB's configuration is to match what we just
1009 set the parameter values to.
1010 """
1011 # Values of parameters
1012 name_to_val = get_param_values_to_set(
1013 self.acs.desired_cfg,
1014 self.acs.device_cfg,
1015 self.acs.data_model,
1016 )
1017 for name, val in name_to_val.items():
1018 magma_val = self.acs.data_model.transform_for_magma(name, val)
1019 self.acs.device_cfg.set_parameter(name, magma_val)
1020
1021 # Values of object parameters
1022 obj_to_name_to_val = get_obj_param_values_to_set(
1023 self.acs.desired_cfg,
1024 self.acs.device_cfg,
1025 self.acs.data_model,
1026 )
1027 for obj_name, name_to_val in obj_to_name_to_val.items():
1028 for name, val in name_to_val.items():
1029 logger.debug(
1030 'Set obj: %s, name: %s, val: %s', str(obj_name),
1031 str(name), str(val),
1032 )
1033 magma_val = self.acs.data_model.transform_for_magma(name, val)
1034 self.acs.device_cfg.set_parameter_for_object(
1035 name, magma_val,
1036 obj_name,
1037 )
1038 logger.info('Successfully configured CPE parameters!')
1039
1040 def state_description(self) -> str:
1041 return 'Setting parameter values'
1042
1043
1044class EndSessionState(EnodebAcsState):
1045 """ To end a TR-069 session, send an empty HTTP response """
1046
1047 def __init__(self, acs: EnodebAcsStateMachine):
1048 super().__init__()
1049 self.acs = acs
1050
1051 def read_msg(self, message: Any) -> AcsReadMsgResult:
1052 """
1053 No message is expected after enodebd sends the eNodeB
1054 an empty HTTP response.
1055
1056 If a device sends an empty HTTP request, we can just
1057 ignore it and send another empty response.
1058 """
1059 if isinstance(message, models.DummyInput):
1060 return AcsReadMsgResult(True, None)
1061 return AcsReadMsgResult(False, None)
1062
1063 def get_msg(self, message: Any) -> AcsMsgAndTransition:
Wei-Yu Chenb91af852022-03-15 22:24:49 +08001064 # Switch enodeb status to disconnected
1065 metrics.set_enb_status(
1066 self.acs.device_cfg.get_parameter("Serial number"),
1067 status="disconnected"
1068 )
1069
Wei-Yu Chen49950b92021-11-08 19:19:18 +08001070 request = models.DummyInput()
1071 return AcsMsgAndTransition(request, None)
1072
1073 def state_description(self) -> str:
1074 return 'Completed provisioning eNB. Awaiting new Inform.'
1075
1076
1077class EnbSendRebootState(EnodebAcsState):
1078 def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
1079 super().__init__()
1080 self.acs = acs
1081 self.done_transition = when_done
1082 self.prev_msg_was_inform = False
1083
1084 def read_msg(self, message: Any) -> AcsReadMsgResult:
1085 """
1086 This state can be transitioned into through user command.
1087 All messages received by enodebd will be ignored in this state.
1088 """
1089 if self.prev_msg_was_inform \
1090 and not isinstance(message, models.DummyInput):
1091 return AcsReadMsgResult(False, None)
1092 elif isinstance(message, models.Inform):
1093 self.prev_msg_was_inform = True
1094 process_inform_message(
1095 message, self.acs.data_model,
1096 self.acs.device_cfg,
1097 )
1098 return AcsReadMsgResult(True, None)
1099 self.prev_msg_was_inform = False
1100 return AcsReadMsgResult(True, None)
1101
1102 def get_msg(self, message: Any) -> AcsMsgAndTransition:
1103 if self.prev_msg_was_inform:
1104 response = models.InformResponse()
1105 # Set maxEnvelopes to 1, as per TR-069 spec
1106 response.MaxEnvelopes = 1
1107 return AcsMsgAndTransition(response, None)
1108 logger.info('Sending reboot request to eNB')
1109 request = models.Reboot()
1110 request.CommandKey = ''
1111 self.acs.are_invasive_changes_applied = True
1112 return AcsMsgAndTransition(request, self.done_transition)
1113
1114 def state_description(self) -> str:
1115 return 'Rebooting eNB'
1116
1117
1118class SendRebootState(EnodebAcsState):
1119 def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
1120 super().__init__()
1121 self.acs = acs
1122 self.done_transition = when_done
1123 self.prev_msg_was_inform = False
1124
1125 def read_msg(self, message: Any) -> AcsReadMsgResult:
1126 """
1127 This state can be transitioned into through user command.
1128 All messages received by enodebd will be ignored in this state.
1129 """
1130 if self.prev_msg_was_inform \
1131 and not isinstance(message, models.DummyInput):
1132 return AcsReadMsgResult(False, None)
1133 elif isinstance(message, models.Inform):
1134 self.prev_msg_was_inform = True
1135 process_inform_message(
1136 message, self.acs.data_model,
1137 self.acs.device_cfg,
1138 )
1139 return AcsReadMsgResult(True, None)
1140 self.prev_msg_was_inform = False
1141 return AcsReadMsgResult(True, None)
1142
1143 def get_msg(self, message: Any) -> AcsMsgAndTransition:
1144 if self.prev_msg_was_inform:
1145 response = models.InformResponse()
1146 # Set maxEnvelopes to 1, as per TR-069 spec
1147 response.MaxEnvelopes = 1
1148 return AcsMsgAndTransition(response, None)
1149 logger.info('Sending reboot request to eNB')
1150 request = models.Reboot()
1151 request.CommandKey = ''
1152 return AcsMsgAndTransition(request, self.done_transition)
1153
1154 def state_description(self) -> str:
1155 return 'Rebooting eNB'
1156
1157
1158class WaitRebootResponseState(EnodebAcsState):
1159 def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
1160 super().__init__()
1161 self.acs = acs
1162 self.done_transition = when_done
1163
1164 def read_msg(self, message: Any) -> AcsReadMsgResult:
1165 if not isinstance(message, models.RebootResponse):
1166 return AcsReadMsgResult(False, None)
1167 return AcsReadMsgResult(True, None)
1168
1169 def get_msg(self, message: Any) -> AcsMsgAndTransition:
1170 """ Reply with empty message """
1171 return AcsMsgAndTransition(models.DummyInput(), self.done_transition)
1172
1173 def state_description(self) -> str:
1174 return 'Rebooting eNB'
1175
1176
1177class WaitInformMRebootState(EnodebAcsState):
1178 """
1179 After sending a reboot request, we expect an Inform request with a
1180 specific 'inform event code'
1181 """
1182
1183 # Time to wait for eNodeB reboot. The measured time
1184 # (on BaiCells indoor eNodeB)
1185 # is ~110secs, so add healthy padding on top of this.
1186 REBOOT_TIMEOUT = 300 # In seconds
1187 # We expect that the Inform we receive tells us the eNB has rebooted
1188 INFORM_EVENT_CODE = 'M Reboot'
1189
1190 def __init__(
1191 self,
1192 acs: EnodebAcsStateMachine,
1193 when_done: str,
1194 when_timeout: str,
1195 ):
1196 super().__init__()
1197 self.acs = acs
1198 self.done_transition = when_done
1199 self.timeout_transition = when_timeout
1200 self.timeout_timer = None
1201 self.timer_handle = None
1202
1203 def enter(self):
1204 self.timeout_timer = StateMachineTimer(self.REBOOT_TIMEOUT)
1205
1206 def check_timer() -> None:
1207 if self.timeout_timer.is_done():
1208 self.acs.transition(self.timeout_transition)
1209 raise Tr069Error(
1210 'Did not receive Inform response after '
1211 'rebooting',
1212 )
1213
1214 self.timer_handle = \
1215 self.acs.event_loop.call_later(
1216 self.REBOOT_TIMEOUT,
1217 check_timer,
1218 )
1219
1220 def exit(self):
1221 self.timer_handle.cancel()
1222 self.timeout_timer = None
1223
1224 def read_msg(self, message: Any) -> AcsReadMsgResult:
1225 if not isinstance(message, models.Inform):
1226 return AcsReadMsgResult(False, None)
1227 if not does_inform_have_event(message, self.INFORM_EVENT_CODE):
1228 raise Tr069Error(
1229 'Did not receive M Reboot event code in '
1230 'Inform',
1231 )
1232 process_inform_message(
1233 message, self.acs.data_model,
1234 self.acs.device_cfg,
1235 )
1236 return AcsReadMsgResult(True, self.done_transition)
1237
1238 def state_description(self) -> str:
1239 return 'Waiting for M Reboot code from Inform'
1240
1241
1242class WaitRebootDelayState(EnodebAcsState):
1243 """
1244 After receiving the Inform notifying us that the eNodeB has successfully
1245 rebooted, wait a short duration to prevent unspecified race conditions
1246 that may occur w.r.t reboot
1247 """
1248
1249 # Short delay timer to prevent race conditions w.r.t. reboot
1250 SHORT_CONFIG_DELAY = 10
1251
1252 def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
1253 super().__init__()
1254 self.acs = acs
1255 self.done_transition = when_done
1256 self.config_timer = None
1257 self.timer_handle = None
1258
1259 def enter(self):
1260 self.config_timer = StateMachineTimer(self.SHORT_CONFIG_DELAY)
1261
1262 def check_timer() -> None:
1263 if self.config_timer.is_done():
1264 self.acs.transition(self.done_transition)
1265
1266 self.timer_handle = \
1267 self.acs.event_loop.call_later(
1268 self.SHORT_CONFIG_DELAY,
1269 check_timer,
1270 )
1271
1272 def exit(self):
1273 self.timer_handle.cancel()
1274 self.config_timer = None
1275
1276 def read_msg(self, message: Any) -> AcsReadMsgResult:
1277 return AcsReadMsgResult(True, None)
1278
1279 def get_msg(self, message: Any) -> AcsMsgAndTransition:
1280 return AcsMsgAndTransition(models.DummyInput(), None)
1281
1282 def state_description(self) -> str:
1283 return 'Waiting after eNB reboot to prevent race conditions'
1284
1285
Wei-Yu Chen678f0a52021-12-21 13:50:52 +08001286class DownloadState(EnodebAcsState):
1287 """
1288 The eNB handler will enter this state when firmware version is older than desired version.
1289 """
1290
1291 def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
1292 super().__init__()
1293 self.acs = acs
1294 self.done_transition = when_done
1295
1296 def get_msg(self, message: Any) -> AcsMsgAndTransition:
1297
Wei-Yu Chen31ebdb52022-06-27 16:53:11 +08001298 enodebd_cfg = load_service_config("enodebd")
1299
Wei-Yu Chenb91af852022-03-15 22:24:49 +08001300 # Switch enodeb status to firmware upgrading
1301 metrics.set_enb_status(
1302 self.acs.device_cfg.get_parameter("Serial number"),
1303 status="firmware_upgrading"
1304 )
Wei-Yu Chen678f0a52021-12-21 13:50:52 +08001305
1306 request = models.Download()
Wei-Yu Chen31ebdb52022-06-27 16:53:11 +08001307 request.CommandKey = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
Wei-Yu Chen678f0a52021-12-21 13:50:52 +08001308 request.FileType = "1 Firmware Upgrade Image"
Wei-Yu Chen31ebdb52022-06-27 16:53:11 +08001309 request.URL = enodebd_cfg["firmware"]["url"]
1310 request.Username = enodebd_cfg["firmware"]["username"]
1311 request.Password = enodebd_cfg["firmware"]["password"]
1312 request.FileSize = enodebd_cfg["firmware"]["filesize"]
1313 request.TargetFileName = enodebd_cfg["firmware"]["filename"]
Wei-Yu Chen678f0a52021-12-21 13:50:52 +08001314 request.DelaySeconds = 0
1315 request.SuccessURL = ""
1316 request.FailureURL = ""
Wei-Yu Chenb91af852022-03-15 22:24:49 +08001317
Wei-Yu Chen678f0a52021-12-21 13:50:52 +08001318 return AcsMsgAndTransition(request, self.done_transition)
1319
1320 def state_description(self) -> str:
1321 return 'Upgrade the firmware the desired version'
1322
1323class WaitDownloadResponseState(EnodebAcsState):
1324 """
1325 The eNB handler will enter this state after the Download command sent.
1326 """
1327
1328 def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
1329 super().__init__()
1330 self.acs = acs
1331 self.done_transition = when_done
1332
1333 def read_msg(self, message: Any) -> AcsReadMsgResult:
1334 if not isinstance(message, models.DownloadResponse):
1335 return AcsReadMsgResult(False, None)
1336 return AcsReadMsgResult(True, None)
1337
1338 def get_msg(self, message: Any) -> AcsMsgAndTransition:
1339 """ Reply with empty message """
1340 logger.info("Received Download Response from eNodeB")
1341 return AcsMsgAndTransition(models.DummyInput(), self.done_transition)
1342
1343 def state_description(self) -> str:
1344 return "Wait DownloadResponse message"
1345
1346class WaitInformTransferCompleteState(EnodebAcsState):
1347 """
1348 The eNB handler will enter this state after firmware upgraded and rebooted
1349 """
1350
1351 REBOOT_TIMEOUT = 300 # In seconds
1352 INFORM_EVENT_CODE = "7 TRANSFER COMPLETE"
1353 PREIODIC_EVENT_CODE = "2 PERIODIC"
1354
1355 def __init__(self, acs: EnodebAcsStateMachine, when_done: str, when_periodic: str, when_timeout: str):
1356 super().__init__()
1357 self.acs = acs
1358 self.done_transition = when_done
1359 self.periodic_update_transition = when_periodic
1360 self.timeout_transition = when_timeout
1361 self.timeout_timer = None
1362 self.timer_handle = None
1363
1364 def enter(self):
1365 print("Get into the TransferComplete State")
1366 self.timeout_timer = StateMachineTimer(self.REBOOT_TIMEOUT)
1367
1368 def check_timer() -> None:
1369 if self.timeout_timer.is_done():
1370 self.acs.transition(self.timeout_transition)
1371 raise Tr069Error("Didn't receive Inform response after rebooting")
1372
1373 self.timer_handle = self.acs.event_loop.call_later(
1374 self.REBOOT_TIMEOUT,
1375 check_timer,
1376 )
1377
1378 def exit(self):
1379 self.timer_handle.cancel()
1380 self.timeout_timer = None
1381
1382 def get_msg(self, message: Any) -> AcsMsgAndTransition:
1383 return AcsMsgAndTransition(models.DummyInput(), None)
1384
1385 def read_msg(self, message: Any) -> AcsReadMsgResult:
1386 if not isinstance(message, models.Inform):
1387 return AcsReadMsgResult(False, None)
1388 if does_inform_have_event(message, self.PREIODIC_EVENT_CODE):
1389 logger.info("Receive Periodic update from enodeb")
1390 return AcsReadMsgResult(True, self.periodic_update_transition)
1391 if does_inform_have_event(message, self.INFORM_EVENT_CODE):
1392 logger.info("Receive Transfer complete")
1393 return AcsReadMsgResult(True, self.done_transition)
1394
1395 # Unhandled situation
1396 return AcsReadMsgResult(False, None)
1397
1398 def state_description(self) -> str:
1399 return "Wait DownloadResponse message"
1400
Wei-Yu Chenb91af852022-03-15 22:24:49 +08001401class CheckStatusState(EnodebAcsState):
1402 """
1403 Sent a request to enodeb to get the basic status from device
1404 """
1405
1406 def __init__(
1407 self,
1408 acs: EnodebAcsStateMachine,
1409 when_done: str,
1410 ):
1411 super().__init__()
1412 self.acs = acs
1413 self.done_transition = when_done
1414
1415 def get_msg(self, message: Any) -> AcsMsgAndTransition:
1416 """
1417 Send with GetParameterValuesRequest
1418 """
1419
1420 self.PARAMETERS = [
1421 ParameterName.RF_TX_STATUS,
1422 ParameterName.GPS_STATUS,
1423 ParameterName.GPS_LAT,
1424 ParameterName.GPS_LONG,
1425 ]
1426
1427 request = models.GetParameterValues()
1428 request.ParameterNames = models.ParameterNames()
1429 request.ParameterNames.arrayType = 'xsd:string[1]'
1430 request.ParameterNames.string = []
1431
1432 for name in self.PARAMETERS:
1433 if self.acs.data_model.is_parameter_present(name):
1434 path = self.acs.data_model.get_parameter(name).path
1435 request.ParameterNames.string.append(path)
1436
1437 request.ParameterNames.arrayType = \
1438 'xsd:string[%d]' % len(request.ParameterNames.string)
1439
1440 return AcsMsgAndTransition(request, self.done_transition)
1441
1442 def read_msg(self, message: Any) -> AcsReadMsgResult:
1443
1444 if not isinstance(message, models.GetParameterValuesResponse):
Wei-Yu Chencb8ebb82022-06-09 21:01:00 +08001445 return AcsReadMsgResult(msg_handled=False, next_state=self.done_transition)
Wei-Yu Chenb91af852022-03-15 22:24:49 +08001446
1447 name_to_val = parse_get_parameter_values_response(self.acs.data_model, message, )
1448 logger.info("CheckStatusState: %s", str(name_to_val))
1449
1450 # Call set_enb_gps_status to update the parameter in prometheus api
1451 metrics.set_enb_gps_status(
1452 self.acs.device_cfg.get_parameter("Serial number"),
1453 name_to_val["GPS lat"], name_to_val["GPS long"],
1454 name_to_val["gps_status"]
1455 )
1456
1457 # Call set_enb_op_status to update the parameter in prometheus api
1458 metrics.set_enb_op_status(
1459 self.acs.device_cfg.get_parameter("Serial number"),
1460 name_to_val["Opstate"]
1461 )
1462
1463 # Sleep 1 minute and check status again
1464 time.sleep(60)
1465
1466 return AcsReadMsgResult(msg_handled=True, next_state=self.done_transition)
1467
1468 def state_description(self) -> str:
1469 return 'Getting'
1470
1471
Wei-Yu Chen49950b92021-11-08 19:19:18 +08001472class ErrorState(EnodebAcsState):
1473 """
1474 The eNB handler will enter this state when an unhandled Fault is received.
1475
1476 If the inform_transition_target constructor parameter is non-null, this
1477 state will attempt to autoremediate by transitioning to the specified
1478 target state when an Inform is received.
1479 """
1480
1481 def __init__(
1482 self, acs: EnodebAcsStateMachine,
1483 inform_transition_target: Optional[str] = None,
1484 ):
1485 super().__init__()
1486 self.acs = acs
1487 self.inform_transition_target = inform_transition_target
1488
1489 def read_msg(self, message: Any) -> AcsReadMsgResult:
1490 return AcsReadMsgResult(True, None)
1491
1492 def get_msg(self, message: Any) -> AcsMsgAndTransition:
1493 if not self.inform_transition_target:
1494 return AcsMsgAndTransition(models.DummyInput(), None)
1495
1496 if isinstance(message, models.Inform):
1497 return AcsMsgAndTransition(
1498 models.DummyInput(),
1499 self.inform_transition_target,
1500 )
1501 return AcsMsgAndTransition(models.DummyInput(), None)
1502
1503 def state_description(self) -> str:
1504 return 'Error state - awaiting manual restart of enodebd service or ' \
Wei-Yu Chen678f0a52021-12-21 13:50:52 +08001505 'an Inform to be received from the eNB'