blob: 4e7238885fdf65c2a759424a668b1acbd259238d [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(
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800675 self.acs.device_cfg,
676 self.acs.data_model,
677 self.acs.config_postprocessor,
678 )
679
680 if len(
681 get_all_objects_to_delete(
682 self.acs.desired_cfg,
683 self.acs.device_cfg,
684 ),
685 ) > 0:
686 return AcsReadMsgResult(True, self.rm_obj_transition)
687 elif len(
688 get_all_objects_to_add(
689 self.acs.desired_cfg,
690 self.acs.device_cfg,
691 ),
692 ) > 0:
693 return AcsReadMsgResult(True, self.add_obj_transition)
694 elif len(
695 get_all_param_values_to_set(
696 self.acs.desired_cfg,
697 self.acs.device_cfg,
698 self.acs.data_model,
699 ),
700 ) > 0:
701 return AcsReadMsgResult(True, self.set_params_transition)
702 return AcsReadMsgResult(True, self.skip_transition)
703
704 def state_description(self) -> str:
705 return 'Getting object parameters'
706
707
708class DeleteObjectsState(EnodebAcsState):
709 def __init__(
710 self,
711 acs: EnodebAcsStateMachine,
712 when_add: str,
713 when_skip: str,
714 ):
715 super().__init__()
716 self.acs = acs
717 self.deleted_param = None
718 self.add_obj_transition = when_add
719 self.skip_transition = when_skip
720
721 def get_msg(self, message: Any) -> AcsMsgAndTransition:
722 """
723 Send DeleteObject message to TR-069 and poll for response(s).
724 Input:
725 - Object name (string)
726 """
727 request = models.DeleteObject()
728 self.deleted_param = get_all_objects_to_delete(
729 self.acs.desired_cfg,
730 self.acs.device_cfg,
731 )[0]
732 request.ObjectName = \
733 self.acs.data_model.get_parameter(self.deleted_param).path
734 return AcsMsgAndTransition(request, None)
735
736 def read_msg(self, message: Any) -> AcsReadMsgResult:
737 """
738 Send DeleteObject message to TR-069 and poll for response(s).
739 Input:
740 - Object name (string)
741 """
742 if type(message) == models.DeleteObjectResponse:
743 if message.Status != 0:
744 raise Tr069Error(
745 'Received DeleteObjectResponse with '
746 'Status=%d' % message.Status,
747 )
748 elif type(message) == models.Fault:
749 raise Tr069Error(
750 'Received Fault in response to DeleteObject '
751 '(faultstring = %s)' % message.FaultString,
752 )
753 else:
754 return AcsReadMsgResult(False, None)
755
756 self.acs.device_cfg.delete_object(self.deleted_param)
757 obj_list_to_delete = get_all_objects_to_delete(
758 self.acs.desired_cfg,
759 self.acs.device_cfg,
760 )
761 if len(obj_list_to_delete) > 0:
762 return AcsReadMsgResult(True, None)
763 if len(
764 get_all_objects_to_add(
765 self.acs.desired_cfg,
766 self.acs.device_cfg,
767 ),
768 ) == 0:
769 return AcsReadMsgResult(True, self.skip_transition)
770 return AcsReadMsgResult(True, self.add_obj_transition)
771
772 def state_description(self) -> str:
773 return 'Deleting objects'
774
775
776class AddObjectsState(EnodebAcsState):
777 def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
778 super().__init__()
779 self.acs = acs
780 self.done_transition = when_done
781 self.added_param = None
782
783 def get_msg(self, message: Any) -> AcsMsgAndTransition:
784 request = models.AddObject()
785 self.added_param = get_all_objects_to_add(
786 self.acs.desired_cfg,
787 self.acs.device_cfg,
788 )[0]
789 desired_param = self.acs.data_model.get_parameter(self.added_param)
790 desired_path = desired_param.path
791 path_parts = desired_path.split('.')
792 # If adding enumerated object, ie. XX.N. we should add it to the
793 # parent object XX. so strip the index
794 if len(path_parts) > 2 and \
795 path_parts[-1] == '' and path_parts[-2].isnumeric():
796 logger.debug('Stripping index from path=%s', desired_path)
797 desired_path = '.'.join(path_parts[:-2]) + '.'
798 request.ObjectName = desired_path
799 return AcsMsgAndTransition(request, None)
800
801 def read_msg(self, message: Any) -> AcsReadMsgResult:
802 if type(message) == models.AddObjectResponse:
803 if message.Status != 0:
804 raise Tr069Error(
805 'Received AddObjectResponse with '
806 'Status=%d' % message.Status,
807 )
808 elif type(message) == models.Fault:
809 raise Tr069Error(
810 'Received Fault in response to AddObject '
811 '(faultstring = %s)' % message.FaultString,
812 )
813 else:
814 return AcsReadMsgResult(False, None)
815 instance_n = message.InstanceNumber
816 self.acs.device_cfg.add_object(self.added_param % instance_n)
817 obj_list_to_add = get_all_objects_to_add(
818 self.acs.desired_cfg,
819 self.acs.device_cfg,
820 )
821 if len(obj_list_to_add) > 0:
822 return AcsReadMsgResult(True, None)
823 return AcsReadMsgResult(True, self.done_transition)
824
825 def state_description(self) -> str:
826 return 'Adding objects'
827
828
829class SetParameterValuesState(EnodebAcsState):
830 def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
831 super().__init__()
832 self.acs = acs
833 self.done_transition = when_done
834
835 def get_msg(self, message: Any) -> AcsMsgAndTransition:
836 request = models.SetParameterValues()
837 request.ParameterList = models.ParameterValueList()
838 param_values = get_all_param_values_to_set(
839 self.acs.desired_cfg,
840 self.acs.device_cfg,
841 self.acs.data_model,
842 )
843 request.ParameterList.arrayType = 'cwmp:ParameterValueStruct[%d]' \
844 % len(param_values)
845 request.ParameterList.ParameterValueStruct = []
846 logger.debug(
847 'Sending TR069 request to set CPE parameter values: %s',
848 str(param_values),
849 )
850 # TODO: Match key response when we support having multiple outstanding
851 # calls.
852 if self.acs.has_version_key:
853 request.ParameterKey = models.ParameterKeyType()
854 request.ParameterKey.Data =\
855 "SetParameter-{:10.0f}".format(self.acs.parameter_version_key)
856 request.ParameterKey.type = 'xsd:string'
857
858 for name, value in param_values.items():
859 param_info = self.acs.data_model.get_parameter(name)
860 type_ = param_info.type
861 name_value = models.ParameterValueStruct()
862 name_value.Value = models.anySimpleType()
863 name_value.Name = param_info.path
864 enb_value = self.acs.data_model.transform_for_enb(name, value)
865 if type_ in ('int', 'unsignedInt'):
866 name_value.Value.type = 'xsd:%s' % type_
867 name_value.Value.Data = str(enb_value)
868 elif type_ == 'boolean':
869 # Boolean values have integral representations in spec
870 name_value.Value.type = 'xsd:boolean'
871 name_value.Value.Data = str(int(enb_value))
872 elif type_ == 'string':
873 name_value.Value.type = 'xsd:string'
874 name_value.Value.Data = str(enb_value)
875 else:
876 raise Tr069Error(
877 'Unsupported type for %s: %s' %
878 (name, type_),
879 )
880 if param_info.is_invasive:
881 self.acs.are_invasive_changes_applied = False
882 request.ParameterList.ParameterValueStruct.append(name_value)
883
884 return AcsMsgAndTransition(request, self.done_transition)
885
886 def state_description(self) -> str:
887 return 'Setting parameter values'
888
889
890class SetParameterValuesNotAdminState(EnodebAcsState):
891 def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
892 super().__init__()
893 self.acs = acs
894 self.done_transition = when_done
895
896 def get_msg(self, message: Any) -> AcsMsgAndTransition:
897 request = models.SetParameterValues()
898 request.ParameterList = models.ParameterValueList()
899 param_values = get_all_param_values_to_set(
900 self.acs.desired_cfg,
901 self.acs.device_cfg,
902 self.acs.data_model,
903 exclude_admin=True,
904 )
905 request.ParameterList.arrayType = 'cwmp:ParameterValueStruct[%d]' \
906 % len(param_values)
907 request.ParameterList.ParameterValueStruct = []
908 logger.debug(
909 'Sending TR069 request to set CPE parameter values: %s',
910 str(param_values),
911 )
912 for name, value in param_values.items():
913 param_info = self.acs.data_model.get_parameter(name)
914 type_ = param_info.type
915 name_value = models.ParameterValueStruct()
916 name_value.Value = models.anySimpleType()
917 name_value.Name = param_info.path
918 enb_value = self.acs.data_model.transform_for_enb(name, value)
919 if type_ in ('int', 'unsignedInt'):
920 name_value.Value.type = 'xsd:%s' % type_
921 name_value.Value.Data = str(enb_value)
922 elif type_ == 'boolean':
923 # Boolean values have integral representations in spec
924 name_value.Value.type = 'xsd:boolean'
925 name_value.Value.Data = str(int(enb_value))
926 elif type_ == 'string':
927 name_value.Value.type = 'xsd:string'
928 name_value.Value.Data = str(enb_value)
929 else:
930 raise Tr069Error(
931 'Unsupported type for %s: %s' %
932 (name, type_),
933 )
934 if param_info.is_invasive:
935 self.acs.are_invasive_changes_applied = False
936 request.ParameterList.ParameterValueStruct.append(name_value)
937
938 return AcsMsgAndTransition(request, self.done_transition)
939
940 def state_description(self) -> str:
941 return 'Setting parameter values excluding Admin Enable'
942
943
944class WaitSetParameterValuesState(EnodebAcsState):
945 def __init__(
946 self,
947 acs: EnodebAcsStateMachine,
948 when_done: str,
949 when_apply_invasive: str,
950 status_non_zero_allowed: bool = False,
951 ):
952 super().__init__()
953 self.acs = acs
954 self.done_transition = when_done
955 self.apply_invasive_transition = when_apply_invasive
956 # Set Params can legally return zero and non zero status
957 # Per tr-196, if there are errors the method should return a fault.
958 # Make flag optional to compensate for existing radios returning non
959 # zero on error.
960 self.status_non_zero_allowed = status_non_zero_allowed
961
962 def read_msg(self, message: Any) -> AcsReadMsgResult:
963 if type(message) == models.SetParameterValuesResponse:
964 if not self.status_non_zero_allowed:
965 if message.Status != 0:
966 raise Tr069Error(
967 'Received SetParameterValuesResponse with '
968 'Status=%d' % message.Status,
969 )
970 self._mark_as_configured()
Wei-Yu Chen678f0a52021-12-21 13:50:52 +0800971
Wei-Yu Chenb91af852022-03-15 22:24:49 +0800972 metrics.set_enb_last_configured_time(
973 self.acs.device_cfg.get_parameter("Serial number"),
974 self.acs.device_cfg.get_parameter("ip_address"),
975 int(time.time())
976 )
977
978 # Switch enodeb status to configured
979 metrics.set_enb_status(
980 self.acs.device_cfg.get_parameter("Serial number"),
981 status="configured"
982 )
Wei-Yu Chen678f0a52021-12-21 13:50:52 +0800983
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800984 if not self.acs.are_invasive_changes_applied:
985 return AcsReadMsgResult(True, self.apply_invasive_transition)
Wei-Yu Chenb91af852022-03-15 22:24:49 +0800986
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800987 return AcsReadMsgResult(True, self.done_transition)
Wei-Yu Chenb91af852022-03-15 22:24:49 +0800988
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800989 elif type(message) == models.Fault:
990 logger.error(
991 'Received Fault in response to SetParameterValues, '
992 'Code (%s), Message (%s)', message.FaultCode,
993 message.FaultString,
994 )
995 if message.SetParameterValuesFault is not None:
996 for fault in message.SetParameterValuesFault:
997 logger.error(
998 'SetParameterValuesFault Param: %s, '
999 'Code: %s, String: %s', fault.ParameterName,
1000 fault.FaultCode, fault.FaultString,
1001 )
1002 return AcsReadMsgResult(False, None)
1003
1004 def _mark_as_configured(self) -> None:
1005 """
1006 A successful attempt at setting parameter values means that we need to
1007 update what we think the eNB's configuration is to match what we just
1008 set the parameter values to.
1009 """
1010 # Values of parameters
1011 name_to_val = get_param_values_to_set(
1012 self.acs.desired_cfg,
1013 self.acs.device_cfg,
1014 self.acs.data_model,
1015 )
1016 for name, val in name_to_val.items():
1017 magma_val = self.acs.data_model.transform_for_magma(name, val)
1018 self.acs.device_cfg.set_parameter(name, magma_val)
1019
1020 # Values of object parameters
1021 obj_to_name_to_val = get_obj_param_values_to_set(
1022 self.acs.desired_cfg,
1023 self.acs.device_cfg,
1024 self.acs.data_model,
1025 )
1026 for obj_name, name_to_val in obj_to_name_to_val.items():
1027 for name, val in name_to_val.items():
1028 logger.debug(
1029 'Set obj: %s, name: %s, val: %s', str(obj_name),
1030 str(name), str(val),
1031 )
1032 magma_val = self.acs.data_model.transform_for_magma(name, val)
1033 self.acs.device_cfg.set_parameter_for_object(
1034 name, magma_val,
1035 obj_name,
1036 )
1037 logger.info('Successfully configured CPE parameters!')
1038
1039 def state_description(self) -> str:
1040 return 'Setting parameter values'
1041
1042
1043class EndSessionState(EnodebAcsState):
1044 """ To end a TR-069 session, send an empty HTTP response """
1045
1046 def __init__(self, acs: EnodebAcsStateMachine):
1047 super().__init__()
1048 self.acs = acs
1049
1050 def read_msg(self, message: Any) -> AcsReadMsgResult:
1051 """
1052 No message is expected after enodebd sends the eNodeB
1053 an empty HTTP response.
1054
1055 If a device sends an empty HTTP request, we can just
1056 ignore it and send another empty response.
1057 """
1058 if isinstance(message, models.DummyInput):
1059 return AcsReadMsgResult(True, None)
1060 return AcsReadMsgResult(False, None)
1061
1062 def get_msg(self, message: Any) -> AcsMsgAndTransition:
Wei-Yu Chenb91af852022-03-15 22:24:49 +08001063 # Switch enodeb status to disconnected
1064 metrics.set_enb_status(
1065 self.acs.device_cfg.get_parameter("Serial number"),
1066 status="disconnected"
1067 )
1068
Wei-Yu Chen49950b92021-11-08 19:19:18 +08001069 request = models.DummyInput()
1070 return AcsMsgAndTransition(request, None)
1071
1072 def state_description(self) -> str:
1073 return 'Completed provisioning eNB. Awaiting new Inform.'
1074
1075
1076class EnbSendRebootState(EnodebAcsState):
1077 def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
1078 super().__init__()
1079 self.acs = acs
1080 self.done_transition = when_done
1081 self.prev_msg_was_inform = False
1082
1083 def read_msg(self, message: Any) -> AcsReadMsgResult:
1084 """
1085 This state can be transitioned into through user command.
1086 All messages received by enodebd will be ignored in this state.
1087 """
1088 if self.prev_msg_was_inform \
1089 and not isinstance(message, models.DummyInput):
1090 return AcsReadMsgResult(False, None)
1091 elif isinstance(message, models.Inform):
1092 self.prev_msg_was_inform = True
1093 process_inform_message(
1094 message, self.acs.data_model,
1095 self.acs.device_cfg,
1096 )
1097 return AcsReadMsgResult(True, None)
1098 self.prev_msg_was_inform = False
1099 return AcsReadMsgResult(True, None)
1100
1101 def get_msg(self, message: Any) -> AcsMsgAndTransition:
1102 if self.prev_msg_was_inform:
1103 response = models.InformResponse()
1104 # Set maxEnvelopes to 1, as per TR-069 spec
1105 response.MaxEnvelopes = 1
1106 return AcsMsgAndTransition(response, None)
1107 logger.info('Sending reboot request to eNB')
1108 request = models.Reboot()
1109 request.CommandKey = ''
1110 self.acs.are_invasive_changes_applied = True
1111 return AcsMsgAndTransition(request, self.done_transition)
1112
1113 def state_description(self) -> str:
1114 return 'Rebooting eNB'
1115
1116
1117class SendRebootState(EnodebAcsState):
1118 def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
1119 super().__init__()
1120 self.acs = acs
1121 self.done_transition = when_done
1122 self.prev_msg_was_inform = False
1123
1124 def read_msg(self, message: Any) -> AcsReadMsgResult:
1125 """
1126 This state can be transitioned into through user command.
1127 All messages received by enodebd will be ignored in this state.
1128 """
1129 if self.prev_msg_was_inform \
1130 and not isinstance(message, models.DummyInput):
1131 return AcsReadMsgResult(False, None)
1132 elif isinstance(message, models.Inform):
1133 self.prev_msg_was_inform = True
1134 process_inform_message(
1135 message, self.acs.data_model,
1136 self.acs.device_cfg,
1137 )
1138 return AcsReadMsgResult(True, None)
1139 self.prev_msg_was_inform = False
1140 return AcsReadMsgResult(True, None)
1141
1142 def get_msg(self, message: Any) -> AcsMsgAndTransition:
1143 if self.prev_msg_was_inform:
1144 response = models.InformResponse()
1145 # Set maxEnvelopes to 1, as per TR-069 spec
1146 response.MaxEnvelopes = 1
1147 return AcsMsgAndTransition(response, None)
1148 logger.info('Sending reboot request to eNB')
1149 request = models.Reboot()
1150 request.CommandKey = ''
1151 return AcsMsgAndTransition(request, self.done_transition)
1152
1153 def state_description(self) -> str:
1154 return 'Rebooting eNB'
1155
1156
1157class WaitRebootResponseState(EnodebAcsState):
1158 def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
1159 super().__init__()
1160 self.acs = acs
1161 self.done_transition = when_done
1162
1163 def read_msg(self, message: Any) -> AcsReadMsgResult:
1164 if not isinstance(message, models.RebootResponse):
1165 return AcsReadMsgResult(False, None)
1166 return AcsReadMsgResult(True, None)
1167
1168 def get_msg(self, message: Any) -> AcsMsgAndTransition:
1169 """ Reply with empty message """
1170 return AcsMsgAndTransition(models.DummyInput(), self.done_transition)
1171
1172 def state_description(self) -> str:
1173 return 'Rebooting eNB'
1174
1175
1176class WaitInformMRebootState(EnodebAcsState):
1177 """
1178 After sending a reboot request, we expect an Inform request with a
1179 specific 'inform event code'
1180 """
1181
1182 # Time to wait for eNodeB reboot. The measured time
1183 # (on BaiCells indoor eNodeB)
1184 # is ~110secs, so add healthy padding on top of this.
1185 REBOOT_TIMEOUT = 300 # In seconds
1186 # We expect that the Inform we receive tells us the eNB has rebooted
1187 INFORM_EVENT_CODE = 'M Reboot'
1188
1189 def __init__(
1190 self,
1191 acs: EnodebAcsStateMachine,
1192 when_done: str,
1193 when_timeout: str,
1194 ):
1195 super().__init__()
1196 self.acs = acs
1197 self.done_transition = when_done
1198 self.timeout_transition = when_timeout
1199 self.timeout_timer = None
1200 self.timer_handle = None
1201
1202 def enter(self):
1203 self.timeout_timer = StateMachineTimer(self.REBOOT_TIMEOUT)
1204
1205 def check_timer() -> None:
1206 if self.timeout_timer.is_done():
1207 self.acs.transition(self.timeout_transition)
1208 raise Tr069Error(
1209 'Did not receive Inform response after '
1210 'rebooting',
1211 )
1212
1213 self.timer_handle = \
1214 self.acs.event_loop.call_later(
1215 self.REBOOT_TIMEOUT,
1216 check_timer,
1217 )
1218
1219 def exit(self):
1220 self.timer_handle.cancel()
1221 self.timeout_timer = None
1222
1223 def read_msg(self, message: Any) -> AcsReadMsgResult:
1224 if not isinstance(message, models.Inform):
1225 return AcsReadMsgResult(False, None)
1226 if not does_inform_have_event(message, self.INFORM_EVENT_CODE):
1227 raise Tr069Error(
1228 'Did not receive M Reboot event code in '
1229 'Inform',
1230 )
1231 process_inform_message(
1232 message, self.acs.data_model,
1233 self.acs.device_cfg,
1234 )
1235 return AcsReadMsgResult(True, self.done_transition)
1236
1237 def state_description(self) -> str:
1238 return 'Waiting for M Reboot code from Inform'
1239
1240
1241class WaitRebootDelayState(EnodebAcsState):
1242 """
1243 After receiving the Inform notifying us that the eNodeB has successfully
1244 rebooted, wait a short duration to prevent unspecified race conditions
1245 that may occur w.r.t reboot
1246 """
1247
1248 # Short delay timer to prevent race conditions w.r.t. reboot
1249 SHORT_CONFIG_DELAY = 10
1250
1251 def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
1252 super().__init__()
1253 self.acs = acs
1254 self.done_transition = when_done
1255 self.config_timer = None
1256 self.timer_handle = None
1257
1258 def enter(self):
1259 self.config_timer = StateMachineTimer(self.SHORT_CONFIG_DELAY)
1260
1261 def check_timer() -> None:
1262 if self.config_timer.is_done():
1263 self.acs.transition(self.done_transition)
1264
1265 self.timer_handle = \
1266 self.acs.event_loop.call_later(
1267 self.SHORT_CONFIG_DELAY,
1268 check_timer,
1269 )
1270
1271 def exit(self):
1272 self.timer_handle.cancel()
1273 self.config_timer = None
1274
1275 def read_msg(self, message: Any) -> AcsReadMsgResult:
1276 return AcsReadMsgResult(True, None)
1277
1278 def get_msg(self, message: Any) -> AcsMsgAndTransition:
1279 return AcsMsgAndTransition(models.DummyInput(), None)
1280
1281 def state_description(self) -> str:
1282 return 'Waiting after eNB reboot to prevent race conditions'
1283
1284
Wei-Yu Chen678f0a52021-12-21 13:50:52 +08001285class DownloadState(EnodebAcsState):
1286 """
1287 The eNB handler will enter this state when firmware version is older than desired version.
1288 """
1289
1290 def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
1291 super().__init__()
1292 self.acs = acs
1293 self.done_transition = when_done
1294
1295 def get_msg(self, message: Any) -> AcsMsgAndTransition:
1296
Wei-Yu Chenb91af852022-03-15 22:24:49 +08001297 # Switch enodeb status to firmware upgrading
1298 metrics.set_enb_status(
1299 self.acs.device_cfg.get_parameter("Serial number"),
1300 status="firmware_upgrading"
1301 )
Wei-Yu Chen678f0a52021-12-21 13:50:52 +08001302
1303 request = models.Download()
1304 request.CommandKey = "20220206215200"
1305 request.FileType = "1 Firmware Upgrade Image"
Wei-Yu Chenb91af852022-03-15 22:24:49 +08001306 request.URL = "http://10.128.250.131/firmware/Qproject_RC3923_2202151120.ffw"
Wei-Yu Chen678f0a52021-12-21 13:50:52 +08001307 request.Username = ""
1308 request.Password = ""
Wei-Yu Chenb91af852022-03-15 22:24:49 +08001309 request.FileSize = 57399275
1310 request.TargetFileName = "Qproject_RC3923_2202151120.ffw"
Wei-Yu Chen678f0a52021-12-21 13:50:52 +08001311 request.DelaySeconds = 0
1312 request.SuccessURL = ""
1313 request.FailureURL = ""
Wei-Yu Chenb91af852022-03-15 22:24:49 +08001314
Wei-Yu Chen678f0a52021-12-21 13:50:52 +08001315 return AcsMsgAndTransition(request, self.done_transition)
1316
1317 def state_description(self) -> str:
1318 return 'Upgrade the firmware the desired version'
1319
1320class WaitDownloadResponseState(EnodebAcsState):
1321 """
1322 The eNB handler will enter this state after the Download command sent.
1323 """
1324
1325 def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
1326 super().__init__()
1327 self.acs = acs
1328 self.done_transition = when_done
1329
1330 def read_msg(self, message: Any) -> AcsReadMsgResult:
1331 if not isinstance(message, models.DownloadResponse):
1332 return AcsReadMsgResult(False, None)
1333 return AcsReadMsgResult(True, None)
1334
1335 def get_msg(self, message: Any) -> AcsMsgAndTransition:
1336 """ Reply with empty message """
1337 logger.info("Received Download Response from eNodeB")
1338 return AcsMsgAndTransition(models.DummyInput(), self.done_transition)
1339
1340 def state_description(self) -> str:
1341 return "Wait DownloadResponse message"
1342
1343class WaitInformTransferCompleteState(EnodebAcsState):
1344 """
1345 The eNB handler will enter this state after firmware upgraded and rebooted
1346 """
1347
1348 REBOOT_TIMEOUT = 300 # In seconds
1349 INFORM_EVENT_CODE = "7 TRANSFER COMPLETE"
1350 PREIODIC_EVENT_CODE = "2 PERIODIC"
1351
1352 def __init__(self, acs: EnodebAcsStateMachine, when_done: str, when_periodic: str, when_timeout: str):
1353 super().__init__()
1354 self.acs = acs
1355 self.done_transition = when_done
1356 self.periodic_update_transition = when_periodic
1357 self.timeout_transition = when_timeout
1358 self.timeout_timer = None
1359 self.timer_handle = None
1360
1361 def enter(self):
1362 print("Get into the TransferComplete State")
1363 self.timeout_timer = StateMachineTimer(self.REBOOT_TIMEOUT)
1364
1365 def check_timer() -> None:
1366 if self.timeout_timer.is_done():
1367 self.acs.transition(self.timeout_transition)
1368 raise Tr069Error("Didn't receive Inform response after rebooting")
1369
1370 self.timer_handle = self.acs.event_loop.call_later(
1371 self.REBOOT_TIMEOUT,
1372 check_timer,
1373 )
1374
1375 def exit(self):
1376 self.timer_handle.cancel()
1377 self.timeout_timer = None
1378
1379 def get_msg(self, message: Any) -> AcsMsgAndTransition:
1380 return AcsMsgAndTransition(models.DummyInput(), None)
1381
1382 def read_msg(self, message: Any) -> AcsReadMsgResult:
1383 if not isinstance(message, models.Inform):
1384 return AcsReadMsgResult(False, None)
1385 if does_inform_have_event(message, self.PREIODIC_EVENT_CODE):
1386 logger.info("Receive Periodic update from enodeb")
1387 return AcsReadMsgResult(True, self.periodic_update_transition)
1388 if does_inform_have_event(message, self.INFORM_EVENT_CODE):
1389 logger.info("Receive Transfer complete")
1390 return AcsReadMsgResult(True, self.done_transition)
1391
1392 # Unhandled situation
1393 return AcsReadMsgResult(False, None)
1394
1395 def state_description(self) -> str:
1396 return "Wait DownloadResponse message"
1397
Wei-Yu Chenb91af852022-03-15 22:24:49 +08001398class CheckStatusState(EnodebAcsState):
1399 """
1400 Sent a request to enodeb to get the basic status from device
1401 """
1402
1403 def __init__(
1404 self,
1405 acs: EnodebAcsStateMachine,
1406 when_done: str,
1407 ):
1408 super().__init__()
1409 self.acs = acs
1410 self.done_transition = when_done
1411
1412 def get_msg(self, message: Any) -> AcsMsgAndTransition:
1413 """
1414 Send with GetParameterValuesRequest
1415 """
1416
1417 self.PARAMETERS = [
1418 ParameterName.RF_TX_STATUS,
1419 ParameterName.GPS_STATUS,
1420 ParameterName.GPS_LAT,
1421 ParameterName.GPS_LONG,
1422 ]
1423
1424 request = models.GetParameterValues()
1425 request.ParameterNames = models.ParameterNames()
1426 request.ParameterNames.arrayType = 'xsd:string[1]'
1427 request.ParameterNames.string = []
1428
1429 for name in self.PARAMETERS:
1430 if self.acs.data_model.is_parameter_present(name):
1431 path = self.acs.data_model.get_parameter(name).path
1432 request.ParameterNames.string.append(path)
1433
1434 request.ParameterNames.arrayType = \
1435 'xsd:string[%d]' % len(request.ParameterNames.string)
1436
1437 return AcsMsgAndTransition(request, self.done_transition)
1438
1439 def read_msg(self, message: Any) -> AcsReadMsgResult:
1440
1441 if not isinstance(message, models.GetParameterValuesResponse):
1442 return AcsReadMsgResult(msg_handled=False, next_state=when_done)
1443
1444 name_to_val = parse_get_parameter_values_response(self.acs.data_model, message, )
1445 logger.info("CheckStatusState: %s", str(name_to_val))
1446
1447 # Call set_enb_gps_status to update the parameter in prometheus api
1448 metrics.set_enb_gps_status(
1449 self.acs.device_cfg.get_parameter("Serial number"),
1450 name_to_val["GPS lat"], name_to_val["GPS long"],
1451 name_to_val["gps_status"]
1452 )
1453
1454 # Call set_enb_op_status to update the parameter in prometheus api
1455 metrics.set_enb_op_status(
1456 self.acs.device_cfg.get_parameter("Serial number"),
1457 name_to_val["Opstate"]
1458 )
1459
1460 # Sleep 1 minute and check status again
1461 time.sleep(60)
1462
1463 return AcsReadMsgResult(msg_handled=True, next_state=self.done_transition)
1464
1465 def state_description(self) -> str:
1466 return 'Getting'
1467
1468
Wei-Yu Chen49950b92021-11-08 19:19:18 +08001469class ErrorState(EnodebAcsState):
1470 """
1471 The eNB handler will enter this state when an unhandled Fault is received.
1472
1473 If the inform_transition_target constructor parameter is non-null, this
1474 state will attempt to autoremediate by transitioning to the specified
1475 target state when an Inform is received.
1476 """
1477
1478 def __init__(
1479 self, acs: EnodebAcsStateMachine,
1480 inform_transition_target: Optional[str] = None,
1481 ):
1482 super().__init__()
1483 self.acs = acs
1484 self.inform_transition_target = inform_transition_target
1485
1486 def read_msg(self, message: Any) -> AcsReadMsgResult:
1487 return AcsReadMsgResult(True, None)
1488
1489 def get_msg(self, message: Any) -> AcsMsgAndTransition:
1490 if not self.inform_transition_target:
1491 return AcsMsgAndTransition(models.DummyInput(), None)
1492
1493 if isinstance(message, models.Inform):
1494 return AcsMsgAndTransition(
1495 models.DummyInput(),
1496 self.inform_transition_target,
1497 )
1498 return AcsMsgAndTransition(models.DummyInput(), None)
1499
1500 def state_description(self) -> str:
1501 return 'Error state - awaiting manual restart of enodebd service or ' \
Wei-Yu Chen678f0a52021-12-21 13:50:52 +08001502 'an Inform to be received from the eNB'