Skip to content

Commit eab4be0

Browse files
authored
support for multiple prefixes and related fixes (#116)
* make code multiprefix-able * fix some autoformatting * change quoting * add unittests
1 parent 6156edc commit eab4be0

13 files changed

+189
-77
lines changed

.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ dmypy.json
132132
bazel-*
133133

134134
# docker-compose
135-
.env
136135
docker-compose.override.yaml
137136
# docker-compose volumes
138137
/volumes
@@ -141,3 +140,6 @@ docker-compose.override.yaml
141140

142141
# config file
143142
wgkex.yaml
143+
144+
# pycharm project metadata
145+
.idea/

docker-compose.yml

+7-7
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,19 @@ services:
99
- ./volumes/mosquitto/data:/mosquitto/data
1010
- ./volumes/mosquitto/log:/mosquitto/log
1111
ports:
12-
- "9001:9001"
12+
- "9001:9001"
1313

1414
broker:
1515
image: ghcr.io/freifunkmuc/wgkex:latest
1616
command: broker
1717
restart: unless-stopped
1818
ports:
1919
- "5000:5000"
20-
#volumes:
20+
#volumes:
2121
#- ./config/broker/wgkex.yaml:/etc/wgkex.yaml
2222
environment:
23-
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}
24-
WGKEX_DOMAIN_PREFIX: ${WGKEX_DOMAIN_PREFIX-ffmuc_}
23+
WGKEX_DOMAINS: ${WGKEX_DOMAINS-ffmuc_muc_cty, ffmuc_muc_nord, ffmuc_muc_ost, ffmuc_muc_sued, ffmuc_muc_west, ffmuc_welt, ffwert_city}
24+
WGKEX_DOMAIN_PREFIXES: ${WGKEX_DOMAIN_PREFIXES-ffmuc_, ffdon_, ffwert_}
2525
WGKEX_DEBUG: ${WGKEX_DEBUG-DEBUG}
2626
MQTT_BROKER_URL: ${MQTT_BROKER_URL-mqtt}
2727
MQTT_BROKER_PORT: ${MQTT_BROKER_PORT-1883}
@@ -35,10 +35,10 @@ services:
3535
command: worker
3636
restart: unless-stopped
3737
#volumes:
38-
#- ./config/worker/wgkex.yaml:/etc/wgkex.yaml
38+
#- ./config/worker/wgkex.yaml:/etc/wgkex.yaml
3939
environment:
40-
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}
41-
WGKEX_DOMAIN_PREFIX: ${WGKEX_DOMAIN_PREFIX-ffmuc_}
40+
WGKEX_DOMAINS: ${WGKEX_DOMAINS-ffmuc_muc_cty, ffmuc_muc_nord, ffmuc_muc_ost, ffmuc_muc_sued, ffmuc_muc_west, ffmuc_welt, ffwert_city}
41+
WGKEX_DOMAIN_PREFIXES: ${WGKEX_DOMAIN_PREFIXES-ffmuc_, ffdon_, ffwert_}
4242
WGKEX_DEBUG: ${WGKEX_DEBUG-DEBUG}
4343
MQTT_BROKER_URL: ${MQTT_BROKER_URL-mqtt}
4444
MQTT_BROKER_PORT: ${MQTT_BROKER_PORT-1883}

entrypoint

+13-10
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
#!/bin/bash
22
set -e
33

4-
: ${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"}
5-
: ${WGKEX_DOMAIN_PREFIX:="ffmuc_"}
6-
: ${WGKEX_DEBUG:="DEBUG"}
7-
: ${MQTT_BROKER_URL:="mqtt"}
8-
: ${MQTT_BROKER_PORT:="1883"}
9-
: ${MQTT_USERNAME:=""}
10-
: ${MQTT_PASSWORD:=""}
11-
: ${MQTT_KEEPALIVE:="5"}
12-
: ${MQTT_TLS:="False"}
4+
: "${WGKEX_DOMAINS:=ffmuc_muc_cty, ffmuc_muc_nord, ffmuc_muc_ost, ffmuc_muc_sued, ffmuc_muc_west, ffmuc_welt, ffwert_city}"
5+
: "${WGKEX_DOMAIN_PREFIXES:=ffmuc_, ffdon_, ffwert_}"
6+
: "${WGKEX_DEBUG:=DEBUG}"
7+
: "${MQTT_BROKER_URL:=mqtt}"
8+
: "${MQTT_BROKER_PORT:=1883}"
9+
: "${MQTT_USERNAME:=}"
10+
: "${MQTT_PASSWORD:=}"
11+
: "${MQTT_KEEPALIVE:=5}"
12+
: "${MQTT_TLS:=False}"
1313

1414
mk_config() {
1515
if [ ! -e /etc/wgkex.yaml ] ; then
@@ -19,9 +19,12 @@ IFS=", "
1919
for i in $WGKEX_DOMAINS; do
2020
echo " - $i"
2121
done
22+
echo "domain_prefixes:"
23+
for i in $WGKEX_DOMAIN_PREFIXES; do
24+
echo " - $i"
25+
done
2226
cat <<EOF
2327
log_level: $WGKEX_DEBUG
24-
domain_prefix: $WGKEX_DOMAIN_PREFIX
2528
mqtt:
2629
broker_url: $MQTT_BROKER_URL
2730
broker_port: $MQTT_BROKER_PORT

env.example

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Copy or rename this file to .env and modify if for your needs
22

3-
#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"
4-
#WGKEX_DOMAIN_PREFIX="ffmuc_"
3+
#WGKEX_DOMAINS="ffmuc_muc_cty, ffmuc_muc_nord, ffmuc_muc_ost, ffmuc_muc_sued, ffmuc_muc_west, ffmuc_welt, ffwert_city"
4+
#WGKEX_DOMAIN_PREFIXES="ffmuc_, ffdon_, ffwert_"
55
#WGKEX_DEBUG="DEBUG"
66

77
#MQTT_BROKER_URL="mqtt"
@@ -10,5 +10,3 @@
1010
#MQTT_PASSWORD=""
1111
#MQTT_KEEPALIVE="5"
1212
#MQTT_TLS="False"
13-
14-

requirements.txt

+9-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
NetLink
1+
NetLink~=0.1
22
flask-mqtt
3-
pyroute2
4-
PyYAML
5-
Flask
6-
waitress
3+
pyroute2~=0.7.9
4+
PyYAML~=6.0.1
5+
Flask~=3.0.0
6+
waitress~=2.1.2
77

88
# Common
9-
ipaddress
10-
mock
11-
coverage
9+
ipaddress~=1.0.23
10+
mock~=5.1.0
11+
coverage
12+
paho-mqtt~=1.6.1

wgkex.yaml.example

+15-17
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
domains:
2-
- ffmuc_freising
3-
- ffmuc_gauting
42
- ffmuc_muc_cty
53
- ffmuc_muc_nord
64
- ffmuc_muc_ost
75
- ffmuc_muc_sued
86
- ffmuc_muc_west
9-
- ffmuc_uml_nord
10-
- ffmuc_uml_ost
11-
- ffmuc_uml_sued
12-
- ffmuc_uml_west
137
- ffmuc_welt
8+
- ffwert_city
149
mqtt:
1510
broker_url: broker.hivemq.com
1611
broker_port: 1883
@@ -21,17 +16,20 @@ mqtt:
2116
broker_listen:
2217
host: 0.0.0.0
2318
port: 5000
24-
domain_prefix: myprefix-
19+
domain_prefixes:
20+
- ffmuc_
21+
- ffdon_
22+
- ffwert_
2523
logging_config:
26-
formatters:
27-
standard:
28-
format: '%(asctime)s,%(msecs)d %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s'
24+
formatters:
25+
standard:
26+
format: '%(asctime)s,%(msecs)d %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s'
27+
handlers:
28+
console:
29+
class: logging.StreamHandler
30+
formatter: standard
31+
root:
2932
handlers:
30-
console:
31-
class: logging.StreamHandler
32-
formatter: standard
33-
root:
34-
handlers:
3533
- console
36-
level: DEBUG
37-
version: 1
34+
level: DEBUG
35+
version: 1

wgkex/broker/app.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#!/usr/bin/env python3
2+
"""wgkex broker"""
23
import re
34
import dataclasses
45
import logging
@@ -17,7 +18,6 @@
1718
from wgkex.config import config
1819
from wgkex.common import logger
1920

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

2323

wgkex/broker/templates/index.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<html>
22
<head>
3-
<title>wgkex</title>
3+
<title>wgkex</title>
44
</head>
55
<body>
66
<h1>WGKEX</h1>

wgkex/config/config.py

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Configuration handling class."""
2+
import logging
23
import os
34
import sys
45
import yaml
@@ -41,6 +42,14 @@ class MQTT:
4142

4243
@classmethod
4344
def from_dict(cls, mqtt_cfg: Dict[str, str]) -> "MQTT":
45+
"""seems to generate a mqtt config object from dictionary
46+
47+
Args:
48+
mqtt_cfg ():
49+
50+
Returns:
51+
mqtt config object
52+
"""
4453
return cls(
4554
broker_url=mqtt_cfg["broker_url"],
4655
username=mqtt_cfg["username"],
@@ -60,12 +69,11 @@ class Config:
6069
Attributes:
6170
domains: The list of domains to listen for.
6271
mqtt: The MQTT configuration.
63-
domain_prefix: The prefix to pre-pend to a given domain.
64-
"""
72+
domain_prefixes: The list of prefixes to pre-pend to a given domain."""
6573

6674
domains: List[str]
6775
mqtt: MQTT
68-
domain_prefix: str
76+
domain_prefixes: List[str]
6977

7078
@classmethod
7179
def from_dict(cls, cfg: Dict[str, str]) -> "Config":
@@ -79,7 +87,7 @@ def from_dict(cls, cfg: Dict[str, str]) -> "Config":
7987
return cls(
8088
domains=cfg["domains"],
8189
mqtt=mqtt_cfg,
82-
domain_prefix=cfg["domain_prefix"],
90+
domain_prefixes=cfg["domain_prefixes"],
8391
)
8492

8593

@@ -124,6 +132,7 @@ def fetch_config_from_disk() -> str:
124132
The file contents as string.
125133
"""
126134
config_file = os.environ.get(WG_CONFIG_OS_ENV, WG_CONFIG_DEFAULT_LOCATION)
135+
logging.debug("getting config_file: %s", repr(config_file))
127136
try:
128137
with open(config_file, "r") as stream:
129138
return stream.read()

wgkex/config/config_test.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1+
"""Tests for configuration handling class."""
12
import unittest
23
import mock
34
import config
45
import yaml
56

6-
_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"
7-
_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"
7+
_VALID_CFG = (
8+
"domain_prefixes:\n- ffmuc_\n- ffdon_\n- ffwert_\nlog_level: DEBUG\ndomains:\n- a\n- b\nmqtt:\n broker_port: 1883"
9+
"\n broker_url: mqtt://broker\n keepalive: 5\n password: pass\n tls: true\n username: user\n"
10+
)
11+
_INVALID_LINT = (
12+
"domain_prefixes: ffmuc_\nBAD_KEY_FOR_DOMAIN:\n- a\n- b\nmqtt:\n broker_port: 1883\n broker_url: "
13+
"mqtt://broker\n keepalive: 5\n password: pass\n tls: true\n username: user\n"
14+
)
815
_INVALID_CFG = "asdasdasdasd"
916

1017

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

5461
def test_fetch_from_config_no_key_in_config(self):
55-
"""Test fetch non existent key from configuration."""
62+
"""Test fetch non-existent key from configuration."""
5663
mock_open = mock.mock_open(read_data=_VALID_CFG)
5764
with mock.patch("builtins.open", mock_open):
5865
self.assertIsNone(config.fetch_from_config("key_does_not_exist"))

wgkex/worker/app.py

+63-13
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ class DomainsNotInConfig(Error):
2020
"""If no domains exist in configuration file."""
2121

2222

23+
class PrefixesNotInConfig(Error):
24+
"""If no prefixes exist in configuration file."""
25+
26+
27+
class DomainsAreNotUnique(Error):
28+
"""If non-unique domains exist in configuration file."""
29+
30+
2331
def flush_workers(domain: Text) -> None:
2432
"""Calls peer flush every _CLEANUP_TIME interval."""
2533
while True:
@@ -35,31 +43,73 @@ def clean_up_worker(domains: List[Text]) -> None:
3543
domains: list of domains.
3644
"""
3745
logger.debug("Cleaning up the following domains: %s", domains)
38-
prefix = config.load_config().get("domain_prefix")
46+
prefixes = config.load_config().get("domain_prefixes")
47+
cleanup_counter = 0
48+
# ToDo: do we need a check if every domain got gleaned?
49+
for prefix in prefixes:
50+
for domain in domains:
51+
if prefix in domain:
52+
logger.info("Scheduling cleanup task for %s, ", domain)
53+
try:
54+
cleaned_domain = domain.split(prefix)[1]
55+
cleanup_counter += 1
56+
except IndexError:
57+
logger.error(
58+
"Cannot strip domain with prefix %s from passed value %s. Skipping cleanup operation",
59+
prefix,
60+
domain,
61+
)
62+
continue
63+
thread = threading.Thread(target=flush_workers, args=(cleaned_domain,))
64+
thread.start()
65+
if cleanup_counter < len(domains):
66+
logger.error(
67+
"Not every domain got cleaned. Check domains for missing prefixes",
68+
repr(domains),
69+
repr(prefixes),
70+
)
71+
72+
73+
def check_all_domains_unique(domains, prefixes):
74+
"""strips off prefixes and checks if domains are unique
75+
76+
Args:
77+
domains: [str]
78+
Returns:
79+
boolean
80+
"""
81+
if not prefixes:
82+
raise PrefixesNotInConfig("Could not locate prefixes in configuration.")
83+
if not isinstance(prefixes, list):
84+
raise TypeError("prefixes is not a list")
85+
unique_domains = []
3986
for domain in domains:
40-
logger.info("Scheduling cleanup task for %s, ", domain)
41-
try:
42-
cleaned_domain = domain.split(prefix)[1]
43-
except IndexError:
44-
logger.error(
45-
"Cannot strip domain with prefix %s from passed value %s. Skipping cleanup operation",
46-
prefix,
47-
domain,
48-
)
49-
continue
50-
thread = threading.Thread(target=flush_workers, args=(cleaned_domain,))
51-
thread.start()
87+
for prefix in prefixes:
88+
if prefix in domain:
89+
stripped_domain = domain.split(prefix)[1]
90+
if stripped_domain in unique_domains:
91+
logger.error(
92+
"We have a non-unique domain here",
93+
domain,
94+
)
95+
return False
96+
unique_domains.append(stripped_domain)
97+
return True
5298

5399

54100
def main():
55101
"""Starts MQTT listener.
56102
57103
Raises:
58104
DomainsNotInConfig: If no domains were found in configuration file.
105+
DomainsAreNotUnique: If there were non-unique domains after stripping prefix
59106
"""
60107
domains = config.load_config().get("domains")
108+
prefixes = config.load_config().get("domain_prefixes")
61109
if not domains:
62110
raise DomainsNotInConfig("Could not locate domains in configuration.")
111+
if not check_all_domains_unique(domains, prefixes):
112+
raise DomainsAreNotUnique("There are non-unique domains! Check config.")
63113
clean_up_worker(domains)
64114
watch_queue()
65115
mqtt.connect()

0 commit comments

Comments
 (0)