fix: get_live_service_vips uses config API, require CIDR prefix for ip_range
- tests/integration/conftest.py: get_live_service_vips() now reads from the config API's service_ips field instead of docker exec. The docker exec approach spawns a fresh Python process that imports firewall_manager with its hardcoded initial SERVICE_IPS, ignoring any update_service_ips() calls made at runtime. The config API always computes VIPs from the current ip_range, so it matches what the running app actually uses when writing iptables rules. - api/app.py: reject ip_range values without a CIDR prefix (e.g. '10.0.0.1') with a 400. Bare IPs are parsed as /32 by ipaddress.ip_network(strict=False), which shifts all VIP offsets and produces unusable Docker subnet configs. - tests/integration/test_config_api.py: update bare-ip test to expect 400 now that the API enforces the prefix requirement. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -73,20 +73,14 @@ def peer_rules(peer_ip: str) -> list[str]:
|
||||
|
||||
def get_live_service_vips() -> dict:
|
||||
"""
|
||||
Read SERVICE_IPS directly from the running API container.
|
||||
More reliable than the config API since SERVICE_IPS may not match ip_range
|
||||
when the container was built before an ip_range change.
|
||||
Read virtual IPs from the config API.
|
||||
|
||||
The config API computes service_ips from the current ip_range at request time,
|
||||
so it always matches what the running firewall_manager will use when applying
|
||||
peer rules. Using docker exec on the API container is NOT reliable because
|
||||
it spawns a fresh Python process that imports firewall_manager with its initial
|
||||
hardcoded SERVICE_IPS, ignoring any update_service_ips() calls made at runtime.
|
||||
"""
|
||||
import json
|
||||
result = subprocess.run(
|
||||
['docker', 'exec', 'cell-api', 'python3', '-c',
|
||||
'import sys; sys.path.insert(0,"/app/api");'
|
||||
' from firewall_manager import SERVICE_IPS; import json; print(json.dumps(SERVICE_IPS))'],
|
||||
capture_output=True, text=True, timeout=10,
|
||||
)
|
||||
if result.returncode == 0 and result.stdout.strip():
|
||||
return json.loads(result.stdout)
|
||||
# Fallback: derive from config API
|
||||
cfg = requests.get(f"{API_BASE}/api/config").json()
|
||||
sips = cfg.get('service_ips', {})
|
||||
return {
|
||||
|
||||
@@ -176,11 +176,11 @@ class TestPutConfigValidation:
|
||||
r = put('/api/config', json={'ip_range': 'not-an-ip'})
|
||||
assert r.status_code == 400
|
||||
|
||||
def test_put_config_ip_range_bare_ip_behavior(self):
|
||||
# Bare IP is interpreted as /32 — the API may accept or reject it,
|
||||
# but it must not crash (no 500).
|
||||
def test_put_config_ip_range_bare_ip_returns_400(self):
|
||||
# Bare IP without CIDR prefix must be rejected — /32 networks are
|
||||
# accepted by Python but useless as a Docker subnet.
|
||||
r = put('/api/config', json={'ip_range': '10.0.0.1'})
|
||||
assert r.status_code in (200, 400)
|
||||
assert r.status_code == 400
|
||||
|
||||
def test_put_config_calendar_port_zero_returns_400(self):
|
||||
r = put('/api/config', json={'calendar': {'port': 0}})
|
||||
|
||||
Reference in New Issue
Block a user