slapos/promise/plugin/check_cpri_lock: Don't check whatever device blindly

Currently this promise is implemented by grepping whole rf_info output
for "HW" and "SW" strings. But this won't work ok in the presence of
multiple CPRI devices. Imagine, for example if one device has CPRI lock,
while the other does not:

    PCIe CPRI /dev/sdr2@1:
      Hardware ID: 0x4b12
      DNA: [0x0048248a334a7054]
      Serial: ''
      FPGA revision: 2023-06-23  10:05:24
      FPGA vccint: 0.98 V
      FPGA vccaux: 1.76 V
      FPGA vccbram: 0.98 V
      FPGA temperature: 71.9 °C
      Clock tune: 0.0 ppm
      NUMA: 0
      CPRI_option: '5' (x8) lock=no                     <-- NOTE
      DMA0: TX fifo: 66.67us  Usage=16/32768 (0%)
      DMA0: RX fifo: 66.67us  Usage=16/32768 (0%)
      DMA0 Underflows: 0
      DMA0 Overflows: 0
    PCIe CPRI /dev/sdr3@1:
      Hardware ID: 0x4b12
      DNA: [0x0048248a334a7054]
      Serial: ''
      FPGA revision: 2023-06-23  10:05:24
      FPGA vccint: 0.98 V
      FPGA vccaux: 1.77 V
      FPGA vccbram: 0.98 V
      FPGA temperature: 71.7 °C
      Clock tune: 0.0 ppm
      NUMA: 0
      CPRI_option: '5' (x8) lock=HW+SW rx/tx=46.606us   <-- NOTE
        Port #0: T14=46.606us
      DMA0: TX fifo: 66.67us  Usage=16/32768 (0%)
      DMA0: RX fifo: 66.67us  Usage=16/32768 (0%)
      DMA0 Underflows: 0
      DMA0 Overflows: 0

the old code would still report "CPRI locked all ok" and also globally
without indicating which CPRI channel is locked.

-> Fix it by adjusting check_cpri_lock to parse rf_info text more
precisely, detect devices there and to understand which device has CPRI
lock and which does not.

For now this change is accompanied by the following change in
ors-amarisoft SR to keep it working:

    --- a/software/ors-amarisoft/instance-enb.jinja2.cfg
    +++ b/software/ors-amarisoft/instance-enb.jinja2.cfg
    @@ -35,7 +35,6 @@ parts =
       check-lopcomm-sync.py
       check-lopcomm-config-log.py
       check-lopcomm-stats-log.py
    -  check-cpri-lock.py
     {% endif %}
     {% if slapparameter_dict.get("dnsmasq", None) %}
       dnsmasq-service
    @@ -48,6 +47,7 @@ parts =
     {% endif %}
       monitor-base
       publish-connection-information
    +{% set extra_part_list = [] %}

     extends = {{ monitor_template }}

    @@ -688,12 +688,21 @@ config-testing = {{ slapparameter_dict.get("testing", False) }}
     config-config-log = ${lopcomm-rrh-config-template:log-output}
     config-stats-period = {{ slapparameter_dict.get("enb_stats_fetch_period", 60) }}

    -[check-cpri-lock.py]
    +{%  if ru == "lopcomm" %}
    +{%-   set cell_list = slapparameter_dict.get('cell_list', {'default': {}}) %}
    +{%-   for i, k in enumerate(cell_list) %}
    +{%-     set sfp_port = cell_list[k].get('cpri_port_number', i) %}
    +{%-     do extra_part_list.append('SFP{{sfp_port}}-cpri-lock.py') %}
    +[SFP{{sfp_port}}-cpri-lock.py]
     <= macro.promise
     promise = check_cpri_lock
     config-testing = {{ slapparameter_dict.get("testing", False) }}
    +config-sdr_dev  = {{ slapparameter_dict.get('sdr_number', 0) }}
    +config-sfp_port = {{ sfp_port }}
     config-amarisoft-rf-info-log = ${amarisoft-rf-info-template:log-output}
     config-stats-period = {{ slapparameter_dict.get("enb_stats_fetch_period", 60) }}
    +{%-  endfor %}
    +{% endif %}

     [check-rx-saturated.py]
     <= macro.promise
    @@ -702,3 +711,9 @@ config-testing = {{ slapparameter_dict.get("testing", False) }}
     config-amarisoft-stats-log = ${amarisoft-stats-template:log-output}
     config-stats-period = {{ slapparameter_dict.get("enb_stats_fetch_period", 60) }}
     config-max-rx-sample-db = {{ slapparameter_dict.get("max_rx_sample_db", 0) }}
    +
    +[buildout]
    +parts +=
    +{%- for part in extra_part_list %}
    +    {{ part }}
    +{%- endfor %}

(posted in slapos!1461)

The way rf_info text is parsed could be also useful in the future to
e.g. detect FPGA revision of the boards and report their recency status
via promise.

/cc @jhuge, @tomo, @xavier_thompson, @Daetalus
/reviewed-by @lu.xu
/reviewed-on !127
53 jobs for master in 0 seconds
Status Job ID Name Coverage
  External
passed SlapOS.Eggs.UnitTest-Master.Python3

00:15:35

passed SlapOS.Eggs.UnitTest-Master.Python3

00:17:58

passed SlapOS.Eggs.UnitTest-Master.Python3

00:18:03

passed SlapOS.Eggs.UnitTest-Master.Python3

00:18:36

passed SlapOS.Eggs.UnitTest-Master.Python3

00:17:39

passed SlapOS.Eggs.UnitTest-Master.Python3

00:20:47

passed SlapOS.Eggs.UnitTest-Master.Python3

01:52:18

passed SlapOS.Eggs.UnitTest-Master.Python3

00:20:40

failed SlapOS.Eggs.UnitTest-Master.Python3

00:16:53

passed SlapOS.Eggs.UnitTest-Master.Python3

00:18:13

passed SlapOS.Eggs.UnitTest-Master.Python3

00:16:23

failed SlapOS.Eggs.UnitTest-Master.Python3

00:20:38

failed SlapOS.Eggs.UnitTest-Master.Python3

00:15:46

passed SlapOS.Eggs.UnitTest-Master.Python3

00:15:50

passed SlapOS.Eggs.UnitTest-Master.Python3

00:16:38

passed SlapOS.Eggs.UnitTest-Master.Python3

00:14:44

passed SlapOS.Eggs.UnitTest-Master.Python3

00:16:05

passed SlapOS.Eggs.UnitTest-Master.Python3

00:15:55

passed SlapOS.Eggs.UnitTest-Master.Python3

00:14:41

failed SlapOS.Eggs.UnitTest-Master.Python3

00:14:48

failed SlapOS.Eggs.UnitTest-Master.Python3

00:16:25

passed SlapOS.Eggs.UnitTest-Master.Python3

02:01:33

passed SlapOS.Eggs.UnitTest-Master.Python3

00:15:30

passed SlapOS.Eggs.UnitTest-Master.Python3

00:15:31

passed SlapOS.Eggs.UnitTest-Master.Python3

00:17:37

passed SlapOS.Eggs.UnitTest-Master.Python3

00:16:48

passed SlapOS.Eggs.UnitTest-Master.Python3

00:16:09

failed SlapOS.Eggs.UnitTest-Master.Python3

00:20:28

passed SlapOS.Eggs.UnitTest-Master.Python3

00:16:25

passed SlapOS.Eggs.UnitTest-Master.Python3

00:15:47

passed SlapOS.Eggs.UnitTest-Master.Python3

00:18:20

passed SlapOS.Eggs.UnitTest-Master.Python3

00:16:12

passed SlapOS.Eggs.UnitTest-Master.Python3

00:17:09

passed SlapOS.Eggs.UnitTest-Master.Python3

00:16:48

passed SlapOS.Eggs.UnitTest-Master.Python3

00:17:42

passed SlapOS.Eggs.UnitTest-Master.Python3

00:17:17

passed SlapOS.Eggs.UnitTest-Master.Python3

00:18:50

passed SlapOS.Eggs.UnitTest-Master.Python3

00:20:07

passed SlapOS.Eggs.UnitTest-Master.Python3

01:59:41

passed SlapOS.Eggs.UnitTest-Master.Python3

00:16:13

passed SlapOS.Eggs.UnitTest-Master.Python3

00:15:36

passed SlapOS.Eggs.UnitTest-Master.Python3

00:15:26

passed SlapOS.Eggs.UnitTest-Master.Python3

02:08:17

passed SlapOS.Eggs.UnitTest-Master.Python3

01:48:27

passed SlapOS.Eggs.UnitTest-Master.Python3

00:19:50

passed SlapOS.Eggs.UnitTest-Master.Python3

00:17:19

passed SlapOS.Eggs.UnitTest-Master.Python3

00:20:26

passed SlapOS.Eggs.UnitTest-Master.Python3

00:17:49

passed SlapOS.Eggs.UnitTest-Master.Python3

00:18:21

passed SlapOS.Eggs.UnitTest-Master.Python3

00:15:54

passed SlapOS.Eggs.UnitTest-Master.Python3

00:16:21

passed SlapOS.Eggs.UnitTest-Master.Python3

00:16:03

passed SlapOS.Eggs.UnitTest-Master.Python3

00:15:45