Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support for multiple prefixes and related fixes #116

Merged
merged 17 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ dmypy.json
bazel-*

# docker-compose
.env
docker-compose.override.yaml
# docker-compose volumes
/volumes
Expand All @@ -141,3 +140,6 @@ docker-compose.override.yaml

# config file
wgkex.yaml

# pycharm project metadata
.idea/
14 changes: 7 additions & 7 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@ services:
- ./volumes/mosquitto/data:/mosquitto/data
- ./volumes/mosquitto/log:/mosquitto/log
ports:
- "9001:9001"
- "9001:9001"

broker:
image: ghcr.io/freifunkmuc/wgkex:latest
command: broker
restart: unless-stopped
ports:
- "5000:5000"
#volumes:
#volumes:
#- ./config/broker/wgkex.yaml:/etc/wgkex.yaml
environment:
WGKEX_DOMAINS: ${WGKEX_DOMAINS-ffmuc_freising, ffmuc_gauting, ffmuc_muc_cty, ffmuc_muc_nord, ffmuc_muc_ost, ffmuc_muc_sued, ffmuc_muc_west, ffmuc_uml_nord, ffmuc_uml_ost, ffmuc_uml_sued, ffmuc_uml_west, ffmuc_welt}
WGKEX_DOMAIN_PREFIX: ${WGKEX_DOMAIN_PREFIX-ffmuc_}
WGKEX_DOMAINS: ${WGKEX_DOMAINS-ffmuc_muc_cty, ffmuc_muc_nord, ffmuc_muc_ost, ffmuc_muc_sued, ffmuc_muc_west, ffmuc_welt, ffwert_city}
WGKEX_DOMAIN_PREFIXES: ${WGKEX_DOMAIN_PREFIXES-ffmuc_, ffdon_, ffwert_}
WGKEX_DEBUG: ${WGKEX_DEBUG-DEBUG}
MQTT_BROKER_URL: ${MQTT_BROKER_URL-mqtt}
MQTT_BROKER_PORT: ${MQTT_BROKER_PORT-1883}
Expand All @@ -35,10 +35,10 @@ services:
command: worker
restart: unless-stopped
#volumes:
#- ./config/worker/wgkex.yaml:/etc/wgkex.yaml
#- ./config/worker/wgkex.yaml:/etc/wgkex.yaml
environment:
WGKEX_DOMAINS: ${WGKEX_DOMAINS-ffmuc_freising, ffmuc_gauting, ffmuc_muc_cty, ffmuc_muc_nord, ffmuc_muc_ost, ffmuc_muc_sued, ffmuc_muc_west, ffmuc_uml_nord, ffmuc_uml_ost, ffmuc_uml_sued, ffmuc_uml_west, ffmuc_welt}
WGKEX_DOMAIN_PREFIX: ${WGKEX_DOMAIN_PREFIX-ffmuc_}
WGKEX_DOMAINS: ${WGKEX_DOMAINS-ffmuc_muc_cty, ffmuc_muc_nord, ffmuc_muc_ost, ffmuc_muc_sued, ffmuc_muc_west, ffmuc_welt, ffwert_city}
WGKEX_DOMAIN_PREFIXES: ${WGKEX_DOMAIN_PREFIXES-ffmuc_, ffdon_, ffwert_}
WGKEX_DEBUG: ${WGKEX_DEBUG-DEBUG}
MQTT_BROKER_URL: ${MQTT_BROKER_URL-mqtt}
MQTT_BROKER_PORT: ${MQTT_BROKER_PORT-1883}
Expand Down
23 changes: 13 additions & 10 deletions entrypoint
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
#!/bin/bash
set -e

: ${WGKEX_DOMAINS:="ffmuc_freising, ffmuc_gauting, ffmuc_muc_cty, ffmuc_muc_nord, ffmuc_muc_ost, ffmuc_muc_sued, ffmuc_muc_west, ffmuc_uml_nord, ffmuc_uml_ost, ffmuc_uml_sued, ffmuc_uml_west, ffmuc_welt"}
: ${WGKEX_DOMAIN_PREFIX:="ffmuc_"}
: ${WGKEX_DEBUG:="DEBUG"}
: ${MQTT_BROKER_URL:="mqtt"}
: ${MQTT_BROKER_PORT:="1883"}
: ${MQTT_USERNAME:=""}
: ${MQTT_PASSWORD:=""}
: ${MQTT_KEEPALIVE:="5"}
: ${MQTT_TLS:="False"}
: "${WGKEX_DOMAINS:=ffmuc_muc_cty, ffmuc_muc_nord, ffmuc_muc_ost, ffmuc_muc_sued, ffmuc_muc_west, ffmuc_welt, ffwert_city}"
: "${WGKEX_DOMAIN_PREFIXES:=ffmuc_, ffdon_, ffwert_}"
: "${WGKEX_DEBUG:=DEBUG}"
: "${MQTT_BROKER_URL:=mqtt}"
: "${MQTT_BROKER_PORT:=1883}"
: "${MQTT_USERNAME:=}"
: "${MQTT_PASSWORD:=}"
: "${MQTT_KEEPALIVE:=5}"
: "${MQTT_TLS:=False}"

mk_config() {
if [ ! -e /etc/wgkex.yaml ] ; then
Expand All @@ -19,9 +19,12 @@ IFS=", "
for i in $WGKEX_DOMAINS; do
echo " - $i"
done
echo "domain_prefixes:"
for i in $WGKEX_DOMAIN_PREFIXES; do
echo " - $i"
done
cat <<EOF
log_level: $WGKEX_DEBUG
domain_prefix: $WGKEX_DOMAIN_PREFIX
mqtt:
broker_url: $MQTT_BROKER_URL
broker_port: $MQTT_BROKER_PORT
Expand Down
6 changes: 2 additions & 4 deletions env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copy or rename this file to .env and modify if for your needs

#WGKEX_DOMAINS="ffmuc_freising, ffmuc_gauting, ffmuc_muc_cty, ffmuc_muc_nord, ffmuc_muc_ost, ffmuc_muc_sued, ffmuc_muc_west, ffmuc_uml_nord, ffmuc_uml_ost, ffmuc_uml_sued, ffmuc_uml_west, ffmuc_welt"
#WGKEX_DOMAIN_PREFIX="ffmuc_"
#WGKEX_DOMAINS="ffmuc_muc_cty, ffmuc_muc_nord, ffmuc_muc_ost, ffmuc_muc_sued, ffmuc_muc_west, ffmuc_welt, ffwert_city"
#WGKEX_DOMAIN_PREFIXES="ffmuc_, ffdon_, ffwert_"
#WGKEX_DEBUG="DEBUG"

#MQTT_BROKER_URL="mqtt"
Expand All @@ -10,5 +10,3 @@
#MQTT_PASSWORD=""
#MQTT_KEEPALIVE="5"
#MQTT_TLS="False"


17 changes: 9 additions & 8 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
NetLink
NetLink~=0.1
flask-mqtt
pyroute2
PyYAML
Flask
waitress
pyroute2~=0.7.9
PyYAML~=6.0.1
Flask~=3.0.0
waitress~=2.1.2

# Common
ipaddress
mock
coverage
ipaddress~=1.0.23
mock~=5.1.0
coverage
paho-mqtt~=1.6.1
32 changes: 15 additions & 17 deletions wgkex.yaml.example
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
domains:
- ffmuc_freising
- ffmuc_gauting
- ffmuc_muc_cty
- ffmuc_muc_nord
- ffmuc_muc_ost
- ffmuc_muc_sued
- ffmuc_muc_west
- ffmuc_uml_nord
- ffmuc_uml_ost
- ffmuc_uml_sued
- ffmuc_uml_west
- ffmuc_welt
- ffwert_city
mqtt:
broker_url: broker.hivemq.com
broker_port: 1883
Expand All @@ -21,17 +16,20 @@ mqtt:
broker_listen:
host: 0.0.0.0
port: 5000
domain_prefix: myprefix-
domain_prefixes:
- ffmuc_
- ffdon_
- ffwert_
logging_config:
formatters:
standard:
format: '%(asctime)s,%(msecs)d %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s'
formatters:
standard:
format: '%(asctime)s,%(msecs)d %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: standard
root:
handlers:
console:
class: logging.StreamHandler
formatter: standard
root:
handlers:
- console
level: DEBUG
version: 1
level: DEBUG
version: 1
2 changes: 1 addition & 1 deletion wgkex/broker/app.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env python3
"""wgkex broker"""
import re
import dataclasses
import logging
Expand All @@ -17,7 +18,6 @@
from wgkex.config import config
from wgkex.common import logger


WG_PUBKEY_PATTERN = re.compile(r"^[A-Za-z0-9+/]{42}[AEIMQUYcgkosw480]=$")


Expand Down
2 changes: 1 addition & 1 deletion wgkex/broker/templates/index.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<html>
<head>
<title>wgkex</title>
<title>wgkex</title>
</head>
<body>
<h1>WGKEX</h1>
Expand Down
17 changes: 13 additions & 4 deletions wgkex/config/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Configuration handling class."""
import logging
import os
import sys
import yaml
Expand Down Expand Up @@ -41,6 +42,14 @@ class MQTT:

@classmethod
def from_dict(cls, mqtt_cfg: Dict[str, str]) -> "MQTT":
"""seems to generate a mqtt config object from dictionary

Args:
mqtt_cfg ():

Returns:
mqtt config object
"""
return cls(
broker_url=mqtt_cfg["broker_url"],
username=mqtt_cfg["username"],
Expand All @@ -60,12 +69,11 @@ class Config:
Attributes:
domains: The list of domains to listen for.
mqtt: The MQTT configuration.
domain_prefix: The prefix to pre-pend to a given domain.
"""
domain_prefixes: The list of prefixes to pre-pend to a given domain."""

domains: List[str]
mqtt: MQTT
domain_prefix: str
domain_prefixes: List[str]

@classmethod
def from_dict(cls, cfg: Dict[str, str]) -> "Config":
Expand All @@ -79,7 +87,7 @@ def from_dict(cls, cfg: Dict[str, str]) -> "Config":
return cls(
domains=cfg["domains"],
mqtt=mqtt_cfg,
domain_prefix=cfg["domain_prefix"],
domain_prefixes=cfg["domain_prefixes"],
)


Expand Down Expand Up @@ -124,6 +132,7 @@ def fetch_config_from_disk() -> str:
The file contents as string.
"""
config_file = os.environ.get(WG_CONFIG_OS_ENV, WG_CONFIG_DEFAULT_LOCATION)
logging.debug("getting config_file: %s", repr(config_file))
try:
with open(config_file, "r") as stream:
return stream.read()
Expand Down
13 changes: 10 additions & 3 deletions wgkex/config/config_test.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
"""Tests for configuration handling class."""
import unittest
import mock
import config
import yaml

_VALID_CFG = "domain_prefix: ffmuc_\nlog_level: DEBUG\ndomains:\n- a\n- b\nmqtt:\n broker_port: 1883\n broker_url: mqtt://broker\n keepalive: 5\n password: pass\n tls: true\n username: user\n"
_INVALID_LINT = "domain_prefix: ffmuc_\nBAD_KEY_FOR_DOMAIN:\n- a\n- b\nmqtt:\n broker_port: 1883\n broker_url: mqtt://broker\n keepalive: 5\n password: pass\n tls: true\n username: user\n"
_VALID_CFG = (
"domain_prefixes:\n- ffmuc_\n- ffdon_\n- ffwert_\nlog_level: DEBUG\ndomains:\n- a\n- b\nmqtt:\n broker_port: 1883"
"\n broker_url: mqtt://broker\n keepalive: 5\n password: pass\n tls: true\n username: user\n"
)
_INVALID_LINT = (
"domain_prefixes: ffmuc_\nBAD_KEY_FOR_DOMAIN:\n- a\n- b\nmqtt:\n broker_port: 1883\n broker_url: "
"mqtt://broker\n keepalive: 5\n password: pass\n tls: true\n username: user\n"
)
_INVALID_CFG = "asdasdasdasd"


Expand Down Expand Up @@ -52,7 +59,7 @@ def test_fetch_from_config_success(self):
self.assertListEqual(["a", "b"], config.fetch_from_config("domains"))

def test_fetch_from_config_no_key_in_config(self):
"""Test fetch non existent key from configuration."""
"""Test fetch non-existent key from configuration."""
mock_open = mock.mock_open(read_data=_VALID_CFG)
with mock.patch("builtins.open", mock_open):
self.assertIsNone(config.fetch_from_config("key_does_not_exist"))
Expand Down
76 changes: 63 additions & 13 deletions wgkex/worker/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ class DomainsNotInConfig(Error):
"""If no domains exist in configuration file."""


class PrefixesNotInConfig(Error):
"""If no prefixes exist in configuration file."""


class DomainsAreNotUnique(Error):
"""If non-unique domains exist in configuration file."""


def flush_workers(domain: Text) -> None:
"""Calls peer flush every _CLEANUP_TIME interval."""
while True:
Expand All @@ -35,31 +43,73 @@ def clean_up_worker(domains: List[Text]) -> None:
domains: list of domains.
"""
logger.debug("Cleaning up the following domains: %s", domains)
prefix = config.load_config().get("domain_prefix")
prefixes = config.load_config().get("domain_prefixes")
cleanup_counter = 0
# ToDo: do we need a check if every domain got gleaned?
for prefix in prefixes:
for domain in domains:
if prefix in domain:
logger.info("Scheduling cleanup task for %s, ", domain)
try:
cleaned_domain = domain.split(prefix)[1]
cleanup_counter += 1
except IndexError:
logger.error(
"Cannot strip domain with prefix %s from passed value %s. Skipping cleanup operation",
prefix,
domain,
)
continue
thread = threading.Thread(target=flush_workers, args=(cleaned_domain,))
thread.start()
if cleanup_counter < len(domains):
logger.error(
"Not every domain got cleaned. Check domains for missing prefixes",
repr(domains),
repr(prefixes),
)


def check_all_domains_unique(domains, prefixes):
"""strips off prefixes and checks if domains are unique

Args:
domains: [str]
Returns:
boolean
"""
if not prefixes:
raise PrefixesNotInConfig("Could not locate prefixes in configuration.")
if not isinstance(prefixes, list):
raise TypeError("prefixes is not a list")
unique_domains = []
for domain in domains:
logger.info("Scheduling cleanup task for %s, ", domain)
try:
cleaned_domain = domain.split(prefix)[1]
except IndexError:
logger.error(
"Cannot strip domain with prefix %s from passed value %s. Skipping cleanup operation",
prefix,
domain,
)
continue
thread = threading.Thread(target=flush_workers, args=(cleaned_domain,))
thread.start()
for prefix in prefixes:
if prefix in domain:
stripped_domain = domain.split(prefix)[1]
if stripped_domain in unique_domains:
logger.error(
"We have a non-unique domain here",
domain,
)
return False
unique_domains.append(stripped_domain)
return True


def main():
"""Starts MQTT listener.

Raises:
DomainsNotInConfig: If no domains were found in configuration file.
DomainsAreNotUnique: If there were non-unique domains after stripping prefix
"""
domains = config.load_config().get("domains")
prefixes = config.load_config().get("domain_prefixes")
if not domains:
raise DomainsNotInConfig("Could not locate domains in configuration.")
if not check_all_domains_unique(domains, prefixes):
raise DomainsAreNotUnique("There are non-unique domains! Check config.")
clean_up_worker(domains)
watch_queue()
mqtt.connect()
Expand Down
Loading
Loading