You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
FreeDMR/tests/test_data_gateway_upgrade.py

314 lines
9.8 KiB

import configparser
import json
import os
import re
import subprocess
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SCRIPT = ROOT / "docker-configs" / "upgrade-daprs-to-data-gateway.sh"
class DataGatewayUpgradeTests(unittest.TestCase):
def setUp(self):
self.temporary = tempfile.TemporaryDirectory(dir=ROOT / "tests")
self.addCleanup(self.temporary.cleanup)
self.root = Path(self.temporary.name)
self.config_dir = self.root / "freedmr"
self.config_dir.mkdir()
self.config = self.config_dir / "freedmr.cfg"
self.compose = self.config_dir / "docker-compose.yml"
self.config.write_text(CONFIG, encoding="utf-8")
self.compose.write_text(COMPOSE, encoding="utf-8")
bin_dir = self.root / "bin"
bin_dir.mkdir()
docker = bin_dir / "docker"
docker.write_text(
"#!/bin/sh\n"
"[ \"$1\" = compose ] || exit 1\n"
"exit 0\n",
encoding="utf-8",
)
docker.chmod(0o755)
self.environment = os.environ.copy()
self.environment["PATH"] = (
str(bin_dir) + os.pathsep + self.environment["PATH"]
)
def run_upgrade(self, *arguments):
return subprocess.run(
[str(SCRIPT), "--config-dir", str(self.config_dir), *arguments],
check=False,
capture_output=True,
text=True,
env=self.environment,
)
def test_migrates_using_values_from_config_and_compose(self):
result = self.run_upgrade("--yes")
self.assertEqual(result.returncode, 0, result.stderr)
parser = configparser.ConfigParser()
parser.read(self.config)
self.assertTrue(parser.getboolean("GLOBAL", "DATA_GATEWAY"))
self.assertTrue(parser.getboolean("DATA-GATEWAY", "ENABLED"))
self.assertEqual(parser.get("DATA-GATEWAY", "MODE"), "OPENBRIDGE")
self.assertEqual(parser.getint("DATA-GATEWAY", "PROTO_VER"), 5)
self.assertEqual(
parser.get("DATA-GATEWAY", "TARGET_IP"),
"freedmr-data-gateway",
)
self.assertFalse(parser.getboolean("D-APRS", "ENABLED"))
compose = self.compose.read_text(encoding="utf-8")
self.assertNotIn(" D-APRS:\n", compose)
self.assertIn(" data-gateway:\n", compose)
self.assertIn(" freedmrmonitor2:\n", compose)
self.assertIn("FREEDMR_GATEWAY_APRS_LOGIN_CALL: 'M0OLD'", compose)
self.assertIn("FREEDMR_GATEWAY_APRS_PASSCODE: '54321'", compose)
relationships = self._relationships(compose)
self.assertEqual(
[item["link_id"] for item in relationships],
["hdstack-1", "hdstack-2"],
)
self.assertEqual(
[item["bind_port"] for item in relationships],
[62031, 62032],
)
self.assertEqual(
[item["peer_port"] for item in relationships],
[62041, 62042],
)
self.assertEqual(
[item["peer_network_id"] for item in relationships],
[23401, 23402],
)
generated_key = parser.get("DATA-GATEWAY", "PASSPHRASE")
self.assertRegex(generated_key, r"^[0-9a-f]{20}$")
self.assertTrue(all(item["key"] == generated_key for item in relationships))
self.assertEqual(
len(list(self.config_dir.glob("freedmr.cfg.before-data-gateway.*"))),
1,
)
self.assertEqual(
len(
list(
self.config_dir.glob(
"docker-compose.yml.before-data-gateway.*"
)
)
),
1,
)
def test_single_worker_uses_main_server_id(self):
self.compose.write_text(
COMPOSE.replace("- HDSTACK=2", "- HDSTACK=1"),
encoding="utf-8",
)
result = self.run_upgrade("--yes")
self.assertEqual(result.returncode, 0, result.stderr)
relationships = self._relationships(self.compose.read_text(encoding="utf-8"))
self.assertEqual(len(relationships), 1)
self.assertEqual(relationships[0]["peer_network_id"], 23400)
self.assertEqual(relationships[0]["direct_source_server_id"], 23400)
def test_reads_three_worker_base_id_from_compose(self):
self.compose.write_text(
COMPOSE.replace("- HDSTACK=2", "- HDSTACK=3").replace(
"- HDSTACK_BASEID=23400",
"- HDSTACK_BASEID=24500",
),
encoding="utf-8",
)
result = self.run_upgrade("--yes")
self.assertEqual(result.returncode, 0, result.stderr)
relationships = self._relationships(self.compose.read_text(encoding="utf-8"))
self.assertEqual(len(relationships), 3)
self.assertEqual(
[item["peer_network_id"] for item in relationships],
[24501, 24502, 24503],
)
def test_omitted_hdstack_uses_current_cpu_derived_default(self):
self.compose.write_text(
COMPOSE.replace(" - HDSTACK=2\n", ""),
encoding="utf-8",
)
cpu_count = int(
subprocess.check_output(
["getconf", "_NPROCESSORS_ONLN"],
text=True,
)
)
expected = max(1, min(5, cpu_count - 1))
result = self.run_upgrade("--yes")
self.assertEqual(result.returncode, 0, result.stderr)
relationships = self._relationships(self.compose.read_text(encoding="utf-8"))
self.assertEqual(len(relationships), expected)
def test_reuses_existing_non_placeholder_gateway_key(self):
self.config.write_text(CONFIG_WITH_GATEWAY, encoding="utf-8")
result = self.run_upgrade("--yes")
self.assertEqual(result.returncode, 0, result.stderr)
updated = self.config.read_text(encoding="utf-8")
self.assertEqual(updated.count("[DATA-GATEWAY]"), 1)
self.assertIn("PASSPHRASE: old-key", updated)
self.assertIn("PORT: 63041", updated)
self.assertIn("TARGET_PORT: 63031", updated)
self.assertIn("[SYSTEM]\nMODE: MASTER", updated)
relationships = self._relationships(self.compose.read_text(encoding="utf-8"))
self.assertTrue(all(item["key"] == "old-key" for item in relationships))
self.assertEqual(
[item["bind_port"] for item in relationships],
[63031, 63032],
)
self.assertEqual(
[item["peer_port"] for item in relationships],
[63041, 63042],
)
def test_dry_run_validates_without_modifying_or_backing_up(self):
original_config = self.config.read_bytes()
original_compose = self.compose.read_bytes()
result = self.run_upgrade("--dry-run")
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("Dry run successful", result.stdout)
self.assertEqual(self.config.read_bytes(), original_config)
self.assertEqual(self.compose.read_bytes(), original_compose)
self.assertEqual(list(self.config_dir.glob("*.before-data-gateway.*")), [])
def test_refuses_existing_native_service_without_changes(self):
self.compose.write_text(
COMPOSE.replace(" D-APRS:\n", " data-gateway:\n"),
encoding="utf-8",
)
original_config = self.config.read_bytes()
original_compose = self.compose.read_bytes()
result = self.run_upgrade("--yes")
self.assertEqual(result.returncode, 2)
self.assertIn("already exists", result.stderr)
self.assertEqual(self.config.read_bytes(), original_config)
self.assertEqual(self.compose.read_bytes(), original_compose)
def test_refuses_unrecognised_legacy_service_without_changes(self):
self.compose.write_text(
COMPOSE.replace("container_name: D-APRS", "container_name: unrelated"),
encoding="utf-8",
)
original_config = self.config.read_bytes()
original_compose = self.compose.read_bytes()
result = self.run_upgrade("--yes")
self.assertEqual(result.returncode, 2)
self.assertIn("not the recognised legacy container shape", result.stderr)
self.assertEqual(self.config.read_bytes(), original_config)
self.assertEqual(self.compose.read_bytes(), original_compose)
@staticmethod
def _relationships(compose):
match = re.search(
r"FREEDMR_GATEWAY_FBP_RELATIONSHIPS: '([^']+)'",
compose,
)
if match is None:
raise AssertionError("relationship JSON was not found")
return json.loads(match.group(1))
CONFIG = """\
[GLOBAL]
SERVER_ID: 23400
DATA_GATEWAY: False
[D-APRS]
MODE: MASTER
ENABLED: True
PORT: 52555
PASSPHRASE:
[SYSTEM]
MODE: MASTER
ENABLED: True
PORT: 54000
"""
CONFIG_WITH_GATEWAY = """\
[GLOBAL]
SERVER_ID: 23400
DATA_GATEWAY: False
[DATA-GATEWAY]
MODE: OPENBRIDGE
ENABLED: False
PORT: 63041
PASSPHRASE: old-key
TARGET_PORT: 63031
PROTO_VER: 5
[D-APRS]
MODE: MASTER
ENABLED: True
[SYSTEM]
MODE: MASTER
"""
COMPOSE = """\
version: '2.4'
services:
freedmr:
container_name: freedmr
image: freedmr:test
environment:
- HDSTACK=2
- HDSTACK_BASEID=23400
networks:
app_net:
ipv4_address: 172.16.238.10
D-APRS:
container_name: D-APRS
environment:
- APRS_CALL=M0OLD
- APRS_PASSCODE=54321
image: legacy-daprs:test
restart: "unless-stopped"
networks:
app_net:
ipv4_address: 172.16.238.40
freedmrmonitor2:
container_name: freedmrmonitor2
image: monitor:test
networks:
app_net:
ipv4_address: 172.16.238.20
networks:
app_net:
driver: bridge
"""
if __name__ == "__main__":
unittest.main()

Powered by TurnKey Linux.