ansible/ManagedSP/templates/daemon/fr_configuration.py.j2

Summary

Maintainability
Test Coverage
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# pylint: disable=invalid-name
"""
CAT requests listener
"""
import socket
import os
import sys
import time
import base64
from shutil import chown, move
import logging
import posix_ipc

# set server IP
HOSTIP = '{{ radius_sp_ip }}'
HOSTIPv6 = '{{ radius_sp_ipv6 }}'

SOCKET_C = '/opt/Socket/CAT_requests/queue'
SEM_RR = '/FR_RESTART'
SEM_JUST_SLEEPING = '/FR_SLEEPING'
TEMPLATE_DIR = '/opt/FR/templates/'
TEMPLATE_SITE = 'site_xxx'
TEMPLATE_DETAIL = 'detail_xxx'
TEMP_DIR = '/opt/FR/scripts/tmp/'
FR_SITES_A = '/opt/FR/HostedSP/etc/raddb/sites-available/'
FR_SITES_E = '/opt/FR/HostedSP/etc/raddb/sites-enabled/'
FR_SITES_A_REL = '../sites-available/'
FR_MODS_A = '/opt/FR/HostedSP/etc/raddb/mods-available/'
FR_MODS_E = '/opt/FR/HostedSP/etc/raddb/mods-enabled/'
FR_MODS_A_REL = '../mods-available/'
TIME_F = "%Y%m%d%H%M%S"
DATE_F = "%Y%m%d"
NL = "\n"
REPLY_USER_NAME = "%{reply:User-Name}"
NAS_ID = "%{base64:%{NAS-Identifier}/%{NAS-IP-Address}/%{NAS-IPv6-Address}/%{Called-Station-Id}}"
OPERATOR_NAME = "        Operator-Name = "
UNLANG_VLAN = "        %s ( Stripped-User-Domain == '%s' ) {" + NL + \
              "            update reply {" + NL + \
              "                Tunnel-Private-Group-Id=%s" + NL + \
              "                Tunnel-Medium-Type:=6" + NL + \
              "                Tunnel-Type:=VLAN" + NL + \
              "            }" + NL + \
              "        }"
CAT_LOG = '/opt/FR/scripts/logs/fr_configuration.log'
MAX_RESTART_REQUESTS = 10
SOCKET_TIMEOUT = 5.0


def init_log():
    """
    Initialise logging
    """
    sys.getfilesystemencoding = lambda: 'UTF-8'
    _logger = logging.getLogger(__name__)
    _logger.setLevel(logging.INFO)
    _handler = logging.FileHandler(CAT_LOG, encoding='UTF-8')
    _handler.setLevel(logging.INFO)
    _handler.setFormatter(logging.Formatter(
        '%(asctime)s - %(levelname)s - %(message)s'))
    _logger.addHandler(_handler)
    return _logger


def make_conf(data):
    """
    Make FR configuration for new site or update configuration
    or remove a site configuration
    """
    _start = time.time()
    _country = data[0]
    _instid = data[1]
    _deploymentid = data[2]
    _port = data[3]
    _secret = base64.b64decode(data[4]).decode('utf-8')
    _toremove = data[7]
    _operatorname = ''
    if int(_toremove) == 0:
        if data[5] != '':
            _operatorname = base64.b64decode(data[5]).decode('utf-8')
        _vlans = []
        _realm_vlan = ''
        if data[6] != '':
            _el = base64.b64decode(data[6]).decode('utf-8').split('#')
            _idx = 1
            while _idx < len(_el):
                _if = 'if'
                if _idx > 1:
                    _if = 'els' + _if
                _vlans.append(UNLANG_VLAN % (_if, _el[_idx], _el[0]))
                _idx += 1
            _realm_vlan = NL + '\n'.join(_vlans)
        logger.info('Create/update port: %s, secret: %s, operatorname: %s',
                    _port, _secret, _operatorname)
    else:
        logger.info('Remove port: %s',
                    _port)
        _res = remove_site(_port)
        if _res == 0:
            logger.info('Nothing to remove')
            return 1
        return _res
    _site = []
    _detail = []
    for _line in site_template:
        _site.append(_line % {'hostip': HOSTIP,
                              'hostipv6': HOSTIPv6,
                              'country': _country,
                              'instid': _instid,
                              'deploymentid': _deploymentid,
                              'port': _port,
                              'secret': _secret,
                              'operatorname': _operatorname,
                              'nas_id': NAS_ID,
                              'vlans': _realm_vlan,
                              'reply_username': REPLY_USER_NAME})
    with open(TEMP_DIR + 'site_' + str(_port), 'w') as _out:
        _out.write(''.join(_site))
    if not os.path.isfile(TEMP_DIR + 'site_' + str(_port)):
        logger.error('No ' + TEMP_DIR + 'site_' + str(_port) + ' file')
        return False
    if not os.path.isfile(FR_MODS_A + 'detail_' + str(_port)):
        for _line in detail_template:
            _detail.append(_line % {'port': _port,
                                    'format': DATE_F})
        with open(TEMP_DIR + 'detail_' + str(_port), 'w') as _out:
            _out.write(''.join(_detail))
        if not os.path.isfile(TEMP_DIR + 'detail_' + str(_port)):
            logger.error('No ' + TEMP_DIR + 'detail_' + str(_port) +
                         ' file')
            return False
    move(TEMP_DIR + 'site_' + str(_port),
         FR_SITES_A + 'site_' + str(_port))
    try:
        if not os.path.islink(FR_SITES_E + 'site_' + str(_port)):
            os.chdir(FR_SITES_E)
            os.symlink(FR_SITES_A_REL + 'site_' + str(_port),
                       'site_' + str(_port))
        if os.path.isfile(TEMP_DIR + 'detail_' + str(_port)):
            move(TEMP_DIR + 'detail_' + str(_port),
                 FR_MODS_A + 'detail_' + str(_port))
        if not os.path.islink(FR_MODS_E + 'detail_' + str(_port)):
            os.chdir(FR_MODS_E)
            os.symlink(FR_MODS_A_REL + 'detail_' + str(_port),
                       'detail_' + str(_port))
    except:
        return False
    _end = time.time()
    logger.info('New configuration ready, took ' +
                str(_end-_start))
    return True


def remove_site(site_port):
    """
    Remove site given by site_port 
    if exists
    """
    _del = 0
    if os.path.islink(FR_SITES_E + 'site_' + site_port):
        logger.info('Remove link', FR_SITES_E + 'site_' + site_port)
        os.unlink(FR_SITES_E + 'site_' + site_port)
        _del += 1
    if os.path.isfile(FR_SITES_A + 'site_' + site_port):
        logger.info('Remove file', FR_SITES_A + 'site_' + site_port)
        os.remove(FR_SITES_A + 'site_' + site_port)
        _del += 1
    if os.path.islink(FR_MODS_E + 'detail_' + site_port):
        logger.info('Remove link', FR_MODS_E + 'detail_' + site_port)
        os.unlink(FR_MODS_E + 'detail_' + site_port)
        _del += 1
    if os.path.isfile(FR_MODS_A + 'detail_' + site_port):
        logger.info('Remove file', FR_MODS_A + 'detail_' + site_port)
        os.remove(FR_MODS_A + 'detail_' + site_port)
        _del += 1
    logger.info('Files removed: ' + str(_del))
    if _del == 4:
        return True
    else:
        return False

logger = init_log()
if os.path.exists(SOCKET_C):
    os.remove(SOCKET_C)

server_c = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
server_c.settimeout(SOCKET_TIMEOUT)
server_c.bind(SOCKET_C)
chown(SOCKET_C, 'apache', 'apache')
sem_restart_req = posix_ipc.Semaphore(SEM_RR)
sem_restart_suspended = posix_ipc.Semaphore(SEM_JUST_SLEEPING)

site_template = []
templ = open(TEMPLATE_DIR + TEMPLATE_SITE, 'r')
for _line in templ:
    site_template.append(_line)
templ.close()
detail_template = []
templ = open(TEMPLATE_DIR + TEMPLATE_DETAIL, 'r')
for _line in templ:
    detail_template.append(_line)
templ.close()
logger.info('Listening on socket ' + SOCKET_C)
server_c.listen(1)
req_cnt = 0
waited = 0
while True:
    try:
        conn, addr = server_c.accept()
        waited = 0
        if sem_restart_suspended.value == 0 and req_cnt > MAX_RESTART_REQUESTS:
            logger.info('Semaphore released for fr_restart, request count: ' +
                        str(req_cnt))
            waited = req_cnt = 0
            sem_restart_req.release()
    except socket.error as err:
        if req_cnt > 0:
            if sem_restart_suspended.value > 0:
                logger.info('Postpone, fr_restart process just sleeping ' + str(sem_restart_suspended.value))
                continue
            logger.info('Semaphore released for fr_restart, request count: ' +
                        str(req_cnt))
            waited = req_cnt = 0
            sem_restart_req.release()
        continue
    buff = conn.recv(1024).decode('utf-8')
    elems = buff.split(':')
    logger.info('Received ' + str(len(elems)) + ' elements')
    if len(elems) == 8:
        if make_conf(elems):
            conn.send("OK".encode('utf-8'))
            req_cnt = req_cnt + 1
            logger.info("requests count is " + str(req_cnt))
        else:
            conn.send("FAILURE".encode('utf-8'))
    else:
        conn.send("FAILURE".encode('utf-8'))