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