{#- Package ru/libinstance provides common instance code for handling Radio Units and cells.

    Use buildout() macro to emit instance-level code to
    handle configured RUs.

    NOTE: before importing package slaplte.jinja2 needs to already loaded as

      {%- import 'slaplte.jinja2'  as slaplte     with context %}

    NOTE: driver-specific logic is implemented in rudrv .buildout_ru() and .buildout() .
#}

{#- cell_list keeps cell registry #}
{%- set cell_list = {} %}
{%- do slaplte.load_cell(cell_list) %}


{%- macro buildout()  %}
{%-   set testing = slapparameter_dict.get("testing", False)  %}

{#-   part emits new buildout section and registers it into buildout.parts  #}
{%-   set parts_list = [] %}
{%-   macro part(name) %}
{%-     do parts_list.append(name)  %}
[{{ name }}]
{%-   endmacro    %}

{#-   promise emits new buildout section for a promise    #}
{%-   macro promise(name)                                 %}
{{ part('promise-'+name) }}
<= monitor-promise-base
name = {{ name }}.py
config-testing = {{ testing }}
config-stats-period = {{ slapparameter_dict.get("enb_stats_fetch_period", 60) }}
{%-   endmacro %}

{#-   import RU drivers                           #}
{%-   import 'ru_sdr_libinstance.jinja2.cfg'      as rudrv_sdr      with context %}
{%-   import 'ru_lopcomm_libinstance.jinja2.cfg'  as rudrv_lopcomm  with context %}
{%-   import 'ru_sunwave_libinstance.jinja2.cfg'  as rudrv_sunwave  with context %}
{%-   set rudrv_dict = namespace(sdr=rudrv_sdr,
                                 lopcomm=rudrv_lopcomm,
                                 sunwave=rudrv_sunwave) %}
{%-   set rudrv_init = {} %}

{#-   split slapos tap interface for each RU
      fallback to non-split approach for ntap <= 1 to avoid hard-dependecy on setcap/tapsplit

      TODO Relying on setcap and tapsplit should be removed once SlapOS is improved to
           provide several TAP interfaces to instances. See discussion at
           https://lab.nexedi.com/nexedi/slapos/merge_requests/1471#note_194356
           for details. #}
{%-   set ntap = len(list(cell_list|dictsort)) %}
{%-   set vtap_list = []  %}
[vtap]
recipe = plone.recipe.command
ntap = {{ ntap }}
command = {{ netcapdo }} {{ pythonwitheggs }} {{ ru_tapsplit }} {{ slaplte.tap }} ${:ntap}
update-command = ${:command}
stop-on-error = true
{%-   if testing  %}
# StandaloneSlapOS does not provide slaptap
command = :
{%-   endif %}
{%-   set test_slapnet = netaddr.IPNetwork('1234::/71') %}

{%-   if ntap <= 1  %}
[vtap]
ntap = 0
stop-on-error = false

{%-     if ntap == 1  %}
{%-       do vtap_list.append(slaplte.tap) %}
[vtap.{{ slaplte.tap }}]
{%-       if testing  %}
network = {{ str(test_slapnet) }}
gateway = {{ str(test_slapnet[1]) }}
addr    = {{ str(test_slapnet[-1]) }}
{%-       else  %}
network = {{ slap_configuration['tap-ipv6-network'] }}
gateway = {{ slap_configuration['tap-ipv6-gateway'] }}
addr    = {{ slap_configuration['tap-ipv6-addr'] }}
{%-       endif %}
{%-     endif %}

{%-   else  %}

{%-   for i in range(1,1+ntap)  %}
{%-     set tap = '%s-%d' % (slaplte.tap, i)   %}
{%-     do vtap_list.append(tap) %}
[vtap.{{ tap }}]
recipe = slapos.recipe.build
depends = ${vtap:recipe}
init =
  import types
  def readfile(path):
    with open(path) as f:
      return f.read()

  import netaddr
  # ~ import tapsplit
  tapsplit = types.ModuleType('tapsplit')
  exec(readfile('{{ ru_tapsplit }}'), tapsplit.__dict__)

  # simulate what tapsplit would assign to the tap
  # ( tap subinterface will be created for real later at install time - when it
  #   is too late to update section options )
  if {{ testing }}:
    slapnet = netaddr.IPNetwork('{{ str(test_slapnet) }}')
  else:
    slapnet = tapsplit.ifnet6('{{ slaplte.tap }}')
  tapnet  = tapsplit.netsplit(slapnet, {{ 1+ntap }}) [{{ i }}]

  options['network'] = str(tapnet)
  options['gateway'] = str(tapnet[1])
  options['addr']    = str(tapnet[-1])
{%-   endfor  %}

{%-   endif %}

# vtap_jdict maps tapname -> json(interface-info)
[vtap_jdict]
recipe = slapos.recipe.build
depends = {% for tap in vtap_list %}  ${vtap.{{tap}}:addr}  {% endfor %}
init =
  import json
{%- for tap in vtap_list %}
  tap = self.buildout['vtap.{{tap}}']
  tap = {k: tap[k]  for k in ('network', 'gateway', 'addr')}
  options['{{tap}}'] = json.dumps(tap)
{%- endfor %}


{#-   provide CPRI-based RUs IP address via DHCP #}
{%-   if trx == 'cpri'  %}
[dnsmasq-config]
recipe = slapos.recipe.template:jinja2
url = {{ru_dnsmasq_template}}
filename = dnsmasq.cfg
extensions = jinja2.ext.do
output = ${directory:etc}/${:filename}
context =
  import json_module json
  import netaddr netaddr
  section directory directory
  section vtap_jdict vtap_jdict
  json cell_list {{ cell_list | tojson }}

{{ part('dnsmasq-service') }}
recipe = slapos.cookbook:wrapper
port = 5353
ip = ${slap-configuration:tun-ipv4-addr}
command-line = {{ dnsmasq_location }}/sbin/dnsmasq --conf-file=${dnsmasq-config:output} -x ${directory:run}/dnsmasq.pid --local-service --keep-in-foreground
wrapper-path = ${directory:service}/dnsmasq
mode = 0775
hash-files =
  ${dnsmasq-config:output}

# {# promise('dnsmasq-listen') #}
#promise = check_socket_listening
#config-host = ${dnsmasq-service:ip}
#config-port = ${dnsmasq-service:port}
{%-   endif  %}


{#-   go through all RUs and for each RU emit generic promises and invoke
      RU-specific buildout handler #}
{%-   set ru_type = {'lopcomm': 'lopcomm', 'm2ru': 'sunwave'}.get(ru, 'sdr') %}
{%-   set rudrv = rudrv_dict[ru_type] %}
{%-   for i, (cell_ref, cell) in enumerate(cell_list|dictsort) %}
{%-     set ru_ref = cell_ref  if cell_ref != 'default'  else 'RU' %}
{%-     set n_antenna_ul = int(slapparameter_dict.get('n_antenna_ul', default_n_antenna_ul))  %}

# {{ ru_ref }}  ({{ ru_type}})
{%-     if trx == 'sdr'  %}
{{ promise('%s-sdr-busy' % ru_ref) }}
promise = check_sdr_busy
config-sdr = {{ sdr }}
config-sdr_dev  = 0
config-dma_chan = 0

{%-     elif trx == 'cpri'  %}
{{ promise('%s-sdr-busy' % ru_ref) }}
promise = check_sdr_busy
config-sdr = {{ sdr }}
config-sdr_dev  = {{ slapparameter_dict.get('sdr_number', 0) }}
config-dma_chan = {{ cell.cpri_port_number }}

{{ promise('%s-cpri-lock' % ru_ref) }}
promise = check_cpri_lock
config-sdr_dev  = {{ slapparameter_dict.get('sdr_number', 0) }}
config-sfp_port = {{ cell.cpri_port_number }}
config-amarisoft-rf-info-log = ${ru_amarisoft-rf-info-template:log-output}

{%-     else %}
{%-       do bug('unreachable') %}
{%-     endif %}

{{ promise('%s-rx-saturated' % ru_ref) }}
promise = check_rx_saturated
config-rf-rx-chan-list = {{ list(range(i*n_antenna_ul, (i+1)*n_antenna_ul)) }}
config-amarisoft-stats-log = ${ru_amarisoft-stats-template:log-output}
config-max-rx-sample-db = {{ slapparameter_dict.get("max_rx_sample_db", 0) }}

{#-     driver-specific part #}
{%-     if not rudrv_init.get(ru_type) %}
{{        rudrv.buildout()  }}
{%-       do rudrv_init.update({ru_type: 1}) %}
{%-     endif %}
{{      rudrv.buildout_ru(ru_ref, cell) }}
{%-   endfor %}

{#- retrieve rf and stats[rf,samples] data from amarisoft service for promises
    such as check_cpri_lock and check_rx_saturated.
#}
[ru_amarisoft-rf-info-template]
recipe = slapos.recipe.template:jinja2
extensions = jinja2.ext.do
log-output = ${directory:var}/log/amarisoft-rf-info.json.log
context =
  section directory directory
  key slapparameter_dict slap-configuration:configuration
  key log_file :log-output
  raw stats_period {{ slapparameter_dict.get("enb_stats_fetch_period", 60) }}
  raw testing {{ testing }}
  raw python_path {{ buildout_directory}}/bin/pythonwitheggs
mode = 0775
url = {{ ru_amarisoft_rf_info_template }}
output = ${directory:bin}/amarisoft-rf-info.py

{{ part('amarisoft-rf-info-service') }}
recipe = slapos.cookbook:wrapper
command-line = ${ru_amarisoft-rf-info-template:output}
wrapper-path = ${directory:service}/amarisoft-rf-info
mode = 0775
hash-files =
  ${ru_amarisoft-rf-info-template:output}

[ru_amarisoft-stats-template]
recipe = slapos.recipe.template:jinja2
extensions = jinja2.ext.do
log-output = ${directory:var}/log/amarisoft-stats.json.log
context =
  section directory directory
  key slapparameter_dict slap-configuration:configuration
  key log_file :log-output
  raw stats_period {{ slapparameter_dict.get("enb_stats_fetch_period", 60) }}
  raw testing {{ testing }}
  raw python_path {{ buildout_directory}}/bin/pythonwitheggs
mode = 0775
url = {{ ru_amarisoft_stats_template }}
output = ${directory:bin}/amarisoft-stats.py

{{ part('amarisoft-stats-service') }}
recipe = slapos.cookbook:wrapper
command-line = ${ru_amarisoft-stats-template:output}
wrapper-path = ${directory:service}/amarisoft-stats
mode = 0775
hash-files =
  ${ru_amarisoft-stats-template:output}

{{ promise('amarisoft-stats-log') }}
promise = check_amarisoft_stats_log
config-amarisoft-stats-log = ${ru_amarisoft-stats-template:log-output}


[buildout]
parts +=
{%- for part in parts_list %}
    {{ part }}
{%- endfor %}
{%- endmacro  %}
