# instance-enb implements eNB/gNB service.
{#- defaults for global eNB/gNB parameters.
    TODO automatically load enb defaults from JSON schema #}
{%- set enb_defaults = {
  'com_ws_port':  9001,
  'com_addr':     '127.0.1.2',
  'use_ipv4':     False,
  'gnb_id_bits':  28,
  'nssai':        {'1': {'sst': 1}},
  'handover_a1_rsrp':            -70,
  'handover_a1_hysteresis':      0,
  'handover_a1_time_to_trigger': 640,
  'handover_a2_rsrp':            -80,
  'handover_a2_hysteresis':      0,
  'handover_a2_time_to_trigger': 640,
  'handover_meas_gap_config': 'Gap Pattern 1',
  'xlog_forwarding_enabled': True,
  'wendelin_telecom_software_release_url': 'wendelin-telecom-enb-shared-instance',
  'xlog_fluentbit_forward_port': 24224,
  'nr_nr_handover': {
      'event': {
          'a3_rsrp': 6
      },
      'hysteresis': 0,
      'time_to_trigger': 480
  },
  'nr_eutra_handover': {
      'event': {
          'b1_rsrp': -80
      },
      'hysteresis': 0,
      'time_to_trigger': 100
  },
  'eutra_eutra_handover': {
      'event': {
          'a3_rsrp': 6
      },
      'hysteresis': 0,
      'time_to_trigger': 480
  },
  'eutra_nr_handover': {
      'event': {
          'b1_rsrp': -80
      },
      'hysteresis': 0,
      'time_to_trigger': 100
  }
} %}
{%- set gtp_addr_lo = '127.0.1.1' %}
{%- for k,v in enb_defaults|dictsort %}
{%-   do slapparameter_dict.setdefault(k, v) %}
{%- endfor %}
{%- set B = xbuildout.encode %}


[buildout]
extra-parts =
parts =
  directory
  enb-config
  enb-service
  xamari-xlog-service
  request-wendelin-telecom-shared
{%- if slapparameter_dict.xlog_forwarding_enabled %}
  xlog-fluentbit-service
  check-xlog-fluentbit-forward-host.py
  check-xlog-fluentbit-health.py
{%- endif %}
{%- if slapparameter_dict.get('gps_sync', False) %}
  check-gps-lock.py
{%- endif %}
  nginx-launcher
  nginx-graceful
  check-baseband-latency.py
  monitor-base
  publish-connection-information
  ${:extra-parts}

extends = {{ monitor_template }}

eggs-directory = {{ eggs_directory }}
develop-eggs-directory = {{ develop_eggs_directory }}
offline = true

{%- set icell_kind='enb'                                        %}
{%- import 'slaplte.jinja2'            as slaplte with context  %}
{%- import 'ru_libinstance.jinja2.cfg' as rulib   with context  %}
{%- set ipeer_dict = {}                                         %}
{%- set ipeercell_dict = {}                                     %}
{%- do slaplte.load_ipeer(ipeer_dict)                           %}
{%- do slaplte.load_ipeercell(ipeercell_dict)                   %}
{%- do slaplte.check_loaded_everything()                        %}

{{ rulib.buildout() }}

[myslap]
# NOTE we don't query slapos.cookbook:slapconfiguration the second time because
# slapparameter_dict is potentially modified with defaults.
parameter_dict = {{ dumps(slapparameter_dict) }}
configuration = {{ dumps(slap_configuration) }}


[monitor-httpd-conf-parameter]
httpd-include-file = {{ buildout_directory }}/etc/httpd-include-file.conf
port = ${monitor-instance-parameter:monitor-httpd-port}
url = https://[${monitor-instance-parameter:monitor-httpd-ipv6}]:${:port}

[monitor-instance-parameter]
monitor-httpd-port = ${monitor-address:port}

[monitor-address]
recipe = slapos.cookbook:free_port
minimum = 8035
maximum = 8055
ip = ${monitor-instance-parameter:monitor-httpd-ipv6}


[directory]
recipe = slapos.cookbook:mkdirectory
software = {{ buildout_directory }}
home = ${buildout:directory}
var = ${:home}/var
etc = ${:home}/etc
bin = ${:home}/bin
tmp = ${:home}/tmp
run = ${:var}/run
script = ${:etc}/run
service = ${:etc}/service
promise = ${:etc}/promise
log = ${:var}/log
xlog-fluentbit = ${:var}/xlog-fluentbit

[enb-sh-wrapper]
recipe = slapos.recipe.template
output = ${directory:bin}/${:_buildout_section_name_}
enb-info-log = ${directory:log}/enb-info.log
enb-info-archive-log = ${directory:log}/enb-info.log
enb-radio-log = ${directory:log}/enb.log
enb-start-date = ${directory:run}/enb-start.date
inline =
  #!/bin/sh
  # Amarisoft init scripts
  sudo -n {{ amarisoft['dir'] }}/rm-tmp-lte
  sudo -n {{ amarisoft['dir'] }}/init-sdr {{ amarisoft['dir'] }}/v{{ amarisoft['version'] }};
  sudo -n {{ amarisoft['dir'] }}/init-enb
  # Add useful information to enb-info log
  (echo && echo && date "+[%Y/%m/%d %T.%N %Z] Starting eNB software...") >> ${:enb-info-log}
  (echo -n "PCB: " ; for o in t b v s ; do sudo -n {{ sdr['dir'] }}/get-sdr-info -$o 2> /dev/null ; echo -n " " ; done ; echo) >> ${:enb-info-log}
  echo "System info: $(uname -a)" >> ${:enb-info-log}
  ({{ amarisoft['sdr_dir'] }}/sdr_util version && echo) >> ${:enb-info-log}
  # Remove obsolete logs
  rm -f ${directory:log}/enb-2024*
  rm -f ${directory:log}/gnb*
  rm -f ${directory:log}/enb-output.log
  # Keep the 50 latest enb radio log
  stat ${:enb-start-date} && mv ${:enb-radio-log} ${directory:log}/enb.log.$(cat ${:enb-start-date})
  rm -f $(ls -1t ${directory:log}/enb-2* | tail -n+50)
  rm -f $(ls -1t ${directory:log}/enb-info-2* | tail -n+50)
  date +"%Y%m%d.%T" > ${:enb-start-date}
  # Trim enb info log to 500k and keep a 100M archive of enb info log
  head -c -500k ${:enb-info-log} >> ${:enb-info-archive-log}
  tail -c 500k ${:enb-info-log} > ${:enb-info-log}.tmp
  mv ${:enb-info-log}.tmp ${:enb-info-log}
  tail -c 100M ${:enb-info-archive-log} > ${:enb-info-archive-log}.tmp
  mv ${:enb-info-archive-log}.tmp ${:enb-info-archive-log}
  # Launch lteenb
  {{ amarisoft['enb_dir'] }}/lteenb ${directory:etc}/enb.cfg >> ${:enb-info-log} 2>> ${:enb-info-log}

[enb-service]
recipe = slapos.cookbook:wrapper
command-line = ${enb-sh-wrapper:output}
wrapper-path = ${directory:service}/enb
mode = 0775
reserve-cpu = True
pidfile = ${directory:run}/enb.pid
hash-files =
  ${enb-config:output}
  ${enb-sh-wrapper:output}
environment =
  LD_LIBRARY_PATH={{ openssl_location }}/lib
  AMARISOFT_PATH={{ amarisoft['license_dir'] }}

[xamari-xlog-script]
recipe = slapos.recipe.template
output = ${directory:bin}/${:_buildout_section_name_}
period = {{ slapparameter_dict.get("enb_stats_fetch_period", 60) }}
stats_logspec = stats[samples,rf]/${:period}s
{%- if slapparameter_dict.get("enb_drb_stats_enabled", True) %}
drb_stats_logspec = x.drb_stats/${:period}s
{%- else %}
drb_stats_logspec =
{%- endif %}
rotatespec = 100MB.9
logspec = ${:stats_logspec} ${:drb_stats_logspec}
xamari = {{ buildout_directory }}/bin/xamari
logfile = ${monitor-directory:public}/enb.xlog
inline =
  #!/bin/sh
  exec ${:xamari} xlog --rotate ${:rotatespec} --password ${websocket-password:passwd} ws://{{ slapparameter_dict.com_addr }}:{{ slapparameter_dict.com_ws_port }} ${:logfile} ${:logspec}

[xamari-xlog-service]
recipe = slapos.cookbook:wrapper
wrapper-path = ${directory:service}/${:_buildout_section_name_}
command-line = ${xamari-xlog-script:output}
hash-files = ${:command-line}

[request-wendelin-telecom-shared]
<= slap-connection
recipe = slapos.cookbook:requestoptional
name = Wendelin Telecom Registration
software-url = {{ slapparameter_dict.wendelin_telecom_software_release_url }}
shared = true
{%- if not slapparameter_dict.xlog_forwarding_enabled or slapparameter_dict.get("xlog_fluentbit_forward_host") %}
state = destroyed
{%- else %}
config-fluentbit-tag = ${xlog-fluentbit-tag:xlog-fluentbit-tag}
return = gateway-host
{%- endif %}

{% if slapparameter_dict.xlog_forwarding_enabled %}
[xlog-fluentbit-tag]
recipe = slapos.recipe.build
computer = ${slap-connection:computer-id}
enb-id = {{ slapparameter_dict.get("enb_id", "") }}
gnb-id = {{ slapparameter_dict.get("gnb_id", "") }}
init =
  import socket

  options['hostname'] = socket.gethostname()

  radio_id = ''
  if options['enb-id']:
    radio_id = 'e%s' % options['enb-id'] 
  elif options['gnb-id']:
    radio_id = 'g%s' % options['gnb-id']
  options['radio-id'] = radio_id

  xlog_fluentbit_tag = '_'.join(options[x] for x in ('hostname', 'computer', 'radio-id') if options[x])
  options['xlog-fluentbit-tag'] = xlog_fluentbit_tag

[xlog-fluentbit-config]
recipe = slapos.recipe.template
output = ${directory:etc}/${:_buildout_section_name_}.cfg
logfile = ${xamari-xlog-script:logfile}
forward-host = {{ slapparameter_dict.get('xlog_fluentbit_forward_host', '') or '${request-wendelin-telecom-shared:connection-gateway-host}' }}
forward-port = {{ slapparameter_dict.xlog_fluentbit_forward_port }}
forward-shared-key = {{ slapparameter_dict.get('xlog_fluentbit_forward_shared_key', '') }}
forward-self-hostname = {{ B(comp_id['comp-id']) }}
monitoring-host = {{ my_ipv4 }}
monitoring-port = {{ slapparameter_dict.xlog_fluentbit_forward_port + 1 }}
inline =
  [SERVICE]
      flush           5
      HTTP_Server     On
      HTTP_Listen     ${:monitoring-host}
      HTTP_PORT       ${:monitoring-port}
      Health_Check    On
      HC_Errors_Count 0
      HC_Retry_Failure_Count 0
      HC_Period       60
  [INPUT]
      name            tail
      path            ${:logfile}
      tag             ${xlog-fluentbit-tag:xlog-fluentbit-tag}
      Read_from_Head  True
      db              ${directory:xlog-fluentbit}/tail-state
      Buffer_Max_Size 1M
  [OUTPUT]
      name            forward
      match           *
      Host            ${:forward-host}
      Port            ${:forward-port}
{%- if slapparameter_dict.get('xlog_fluentbit_forward_shared_key') %}
      Shared_Key      ${:forward-shared-key}
{%- endif %}
      Self_Hostname   ${:forward-self-hostname}
      Retry_Limit     50
      tls             on
      tls.verify      off

[xlog-fluentbit-service]
recipe  = slapos.cookbook:wrapper
fluentbit =  {{ fluent_bit_location }}/bin/fluent-bit
fluentbit-config = ${xlog-fluentbit-config:output}
command-line = ${:fluentbit} -c ${:fluentbit-config}
wrapper-path = ${directory:service}/${:_buildout_section_name_}
hash-files = ${:fluentbit-config}

[check-xlog-fluentbit-forward-host.py]
<= macro.promise
promise = check_socket_listening
config-host = ${xlog-fluentbit-config:forward-host}
config-port = ${xlog-fluentbit-config:forward-port}

[check-xlog-fluentbit-health.py]
<= macro.promise
promise = check_url_available
config-url = http://${xlog-fluentbit-config:monitoring-host}:${xlog-fluentbit-config:monitoring-port}/api/v1/health
{%- endif %}

[config-base]
recipe = slapos.recipe.template:jinja2
extensions = jinja2.ext.do
extra-context =
context =
  json ors false
  section directory directory
  key slap_configuration myslap:configuration
  key slapparameter_dict myslap:parameter_dict
  raw gtp_addr_v6 {{ my_ipv6 }}
  raw gtp_addr_v4 {{ lan_ipv4 }}
  raw gtp_addr_lo {{ gtp_addr_lo }}
  import  xbuildout xbuildout
  import  netaddr netaddr
  ${:extra-context}

[enb-config]
<= config-base
url = {{ enb_template }}
output = ${directory:etc}/enb.cfg
import-list =
    rawfile slaplte.jinja2 {{ slaplte_template }}
extra-context =
    import json_module json
    key iru_dict       :iru_dict
    key icell_dict     :icell_dict
    key ipeer_dict     :ipeer_dict
    key ipeercell_dict :ipeercell_dict
    key websocket_password websocket-password:passwd
iru_dict       = {{ dumps(rulib.iru_dict) }}
icell_dict     = {{ dumps(rulib.icell_dict) }}
ipeer_dict     = {{ dumps(ipeer_dict) }}
ipeercell_dict = {{ dumps(ipeercell_dict) }}


[publish-connection-information]
<= monitor-publish
recipe = slapos.cookbook:publish.serialised
websocket-hostname = ${frontend-urlparse:hostname}
websocket-port = ${frontend-urlparse:port}
websocket-password = ${websocket-password:passwd}
enb-ipv6 = {{ my_ipv6 }}
enb-ipv4 = {{ lan_ipv4 }}
enb-mac = {{ mac }}
amarisoft-version = {{ amarisoft['version'] }}
amarisoft-host-id = {{ amarisoft['lteenb_host_id'] }}
amarisoft-available-versions = {{ amarisoft['version_installed'] }}
license-expiration = {{ amarisoft['lteenb_expiration'] }}
monitor-gadget-url = ${:monitor-base-url}/gadget/software.cfg.html
ru-list   = {{ dumps(rulib.iru_dict.keys()   | sort) }}
cell-list = {{ dumps(rulib.icell_dict.keys() | sort) }}
peer-list = {{ dumps(ipeer_dict.keys()       | sort) }}
peer-cell-list = {{ dumps(ipeercell_dict.keys() | sort) }}
{%- if slapparameter_dict.xlog_forwarding_enabled %}
fluentbit-tag = ${xlog-fluentbit-tag:xlog-fluentbit-tag}
{%- endif %}


[monitor-instance-parameter]
{% if slapparameter_dict.get("name", None) %}
monitor-title = {{ slapparameter_dict['name'] | string }}
{% endif %}
{% if slapparameter_dict.get("monitor-password", None) %}
password = {{ slapparameter_dict['monitor-password'] | string }}
{% endif %}

[macro.promise]
<= monitor-promise-base
name = ${:_buildout_section_name_}

[check-baseband-latency.py]
<= macro.promise
promise = check_baseband_latency
config-amarisoft-stats-log = ${ru_amarisoft-stats-template:log-output}
config-stats-period = {{ slapparameter_dict.get("enb_stats_fetch_period", 60) }}
config-min-rxtx-delay = {{ slapparameter_dict.get("min_rxtx_delay", 0) }}

{%- if slapparameter_dict.get('gps_sync', False) %}
[check-gps-lock.py]
<= macro.promise
promise = check_gps_lock
config-amarisoft-rf-info-log = ${ru_amarisoft-rf-info-template:log-output}
config-stats-period = {{ slapparameter_dict.get("enb_stats_fetch_period", 60) }}
{%- endif %}
