Skip to content
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
18 changes: 18 additions & 0 deletions .github/ci/ci-tests/test_bind_pull_request_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ def test_release_source_pull_requests_test_their_proposed_source():
assert 'if [[ "$PR_BASE" != "release/bind-rp/$SERIES" ]]' in workflow


def test_release_source_pull_requests_materialize_master_ci_helpers():
workflow = workflow_text()
helper_job = workflow.split(' ci-helpers:', 1)[1].split(' discover:', 1)[0]

assert 'PR_BASE: ${{ inputs.pull_request_base || github.event.pull_request.base.ref }}' in helper_job
assert 'if [[ "$PR_BASE" == release/bind-rp/* ]]' in helper_job
assert 'refs/heads/master:refs/remotes/origin/control-plane' in helper_job
assert '.github/ci .resolver-plugins/bind920.json' in helper_job


def test_reusable_workflow_accepts_the_callers_pull_request_context():
workflow = workflow_text()

Expand All @@ -69,6 +79,14 @@ def test_reusable_workflow_accepts_the_callers_pull_request_context():
assert 'PR_BASE: ${{ inputs.pull_request_base || github.event.pull_request.base.ref }}' in workflow


def test_release_source_pull_requests_always_use_master_canonical_tests():
workflow = workflow_text()
test_job = workflow.split(' test:', 1)[1]

assert 'if [[ "$PR_BASE" == release/bind-rp/* ]]' in test_job
assert 'refs/heads/master:refs/remotes/origin/canonical-tests' in test_job


def test_workflow_has_read_only_permissions_and_pinned_actions():
workflow = workflow_text()
references = action_references(workflow)
Expand Down
18 changes: 17 additions & 1 deletion .github/workflows/bind-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,20 @@ jobs:
with:
ref: ${{ inputs.pull_request_sha || github.sha }}
persist-credentials: false
- name: Materialize CI control plane
shell: bash
env:
PR_BASE: ${{ inputs.pull_request_base || github.event.pull_request.base.ref }}
run: |
set -euo pipefail
if [[ "$PR_BASE" == release/bind-rp/* ]]; then
git fetch --no-tags origin \
'refs/heads/master:refs/remotes/origin/control-plane'
git checkout refs/remotes/origin/control-plane -- \
.github/ci .resolver-plugins/bind920.json
fi
test -d .github/ci
test -f .resolver-plugins/bind920.json
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
python-version: '3.12.13'
Expand Down Expand Up @@ -140,12 +154,14 @@ jobs:
SERIES: ${{ matrix.series }}
run: |
set -euo pipefail
if [[ ! -d dns/bind/tests || ! -f .github/ci/metadata_profile.py ]]; then
if [[ "$PR_BASE" == release/bind-rp/* ]]; then
git fetch --no-tags origin \
'refs/heads/master:refs/remotes/origin/canonical-tests'
git checkout refs/remotes/origin/canonical-tests -- \
.github/ci/metadata_profile.py dns/bind/tests
fi
test -d dns/bind/tests
test -f .github/ci/metadata_profile.py
source_commit=$(git rev-parse HEAD)
if [[ "$PR_BASE" != "release/bind-rp/$SERIES" ]]; then
source_ref="refs/heads/release/bind-rp/$SERIES"
Expand Down
27 changes: 27 additions & 0 deletions dns/bind/tests/bounded_shutdown_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import json


BOUNDED_SHUTDOWN_THRESHOLDS = {
"26.1": None,
"26.7": ((26, 7), 6),
}


def current_release_requires_bounded_shutdown(bind_root):
metadata = bind_root.parents[1] / ".resolver-plugins/upstream.json"
series = json.loads(metadata.read_text())["series"]
try:
threshold = BOUNDED_SHUTDOWN_THRESHOLDS[series]
except KeyError as error:
raise ValueError(f"unsupported BIND release series: {series}") from error
if threshold is None:
return False

values = {}
for line in (bind_root / "Makefile").read_text().splitlines():
if line.startswith(("PLUGIN_VERSION=", "PLUGIN_REVISION=")):
key, value = line.split("=", 1)
values[key] = value.strip()
version = tuple(int(part) for part in values["PLUGIN_VERSION"].split("."))
revision = int(values["PLUGIN_REVISION"])
return (version, revision) >= threshold
31 changes: 24 additions & 7 deletions dns/bind/tests/test_journal_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,16 @@
import tempfile
import unittest

from .bounded_shutdown_contract import current_release_requires_bounded_shutdown


BIND_ROOT = pathlib.Path(__file__).resolve().parents[1]
BOUNDED_SHUTDOWN = current_release_requires_bounded_shutdown(BIND_ROOT)


class JournalLifecycleTest(unittest.TestCase):
def test_stop_clears_journals_for_watcher_and_reverse_zones(self):
bind_root = pathlib.Path(__file__).resolve().parents[1]
bind_root = BIND_ROOT
stop_script = bind_root / "src/opnsense/scripts/OPNsense/Bind/bindStop.py"

with tempfile.TemporaryDirectory(dir=bind_root) as directory:
Expand Down Expand Up @@ -62,7 +68,12 @@ def test_stop_clears_journals_for_watcher_and_reverse_zones(self):
state.write_text("{}")
events = temporary / "events"
named = temporary / "named"
named.write_text("#!/bin/sh\nprintf '%s\\n' \"$*\" >> \"$TEST_EVENTS\"\n")
named.write_text(
"#!/bin/sh\n"
"printf '%s\\n' \"$*\" >> \"$TEST_EVENTS\"\n"
"[ \"$1\" = status ] && exit 1\n"
"exit 0\n"
)
named.chmod(0o755)

result = subprocess.run(
Expand All @@ -81,14 +92,14 @@ def test_stop_clears_journals_for_watcher_and_reverse_zones(self):
)

self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(events.read_text(), "stop\n")
self.assertEqual(events.read_text(), "status\n" if BOUNDED_SHUTDOWN else "stop\n")
self.assertFalse(state.exists())
for zone in ("forward.example", "watcher.example", "1.168.192.in-addr.arpa"):
for suffix in (".jnl", ".jnw", ".jbk"):
self.assertFalse((zone_dir / f"{zone}.db{suffix}").exists())

def assert_stop_failure_preserves_journals_and_state(self, status_code):
bind_root = pathlib.Path(__file__).resolve().parents[1]
bind_root = BIND_ROOT
stop_script = bind_root / "src/opnsense/scripts/OPNsense/Bind/bindStop.py"

with tempfile.TemporaryDirectory(dir=bind_root) as directory:
Expand Down Expand Up @@ -127,7 +138,10 @@ def assert_stop_failure_preserves_journals_and_state(self, status_code):
)

self.assertEqual(result.returncode, 1, result.stderr)
self.assertEqual(events.read_text(), "stop\nstatus\n")
self.assertEqual(
events.read_text(),
"status\n" if BOUNDED_SHUTDOWN else "stop\nstatus\n",
)
self.assertTrue(state.exists())
self.assertTrue(journal.exists())

Expand All @@ -137,7 +151,7 @@ def test_stop_failure_preserves_journals_and_state(self):
self.assert_stop_failure_preserves_journals_and_state(status_code)

def test_already_stopped_clears_journals_and_state(self):
bind_root = pathlib.Path(__file__).resolve().parents[1]
bind_root = BIND_ROOT
stop_script = bind_root / "src/opnsense/scripts/OPNsense/Bind/bindStop.py"

with tempfile.TemporaryDirectory(dir=bind_root) as directory:
Expand Down Expand Up @@ -174,7 +188,10 @@ def test_already_stopped_clears_journals_and_state(self):
)

self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(events.read_text(), "stop\nstatus\n")
self.assertEqual(
events.read_text(),
"status\n" if BOUNDED_SHUTDOWN else "stop\nstatus\n",
)
self.assertFalse(state.exists())
self.assertFalse(journal.exists())

Expand Down
23 changes: 23 additions & 0 deletions dns/bind/tests/test_log_visibility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright (C) 2026 Bryan Wiegand <inbox@kw-ventures.com>
# All rights reserved.

from pathlib import Path

import pytest

from .bounded_shutdown_contract import current_release_requires_bounded_shutdown


BIND_ROOT = Path(__file__).resolve().parents[1]
STOP_SCRIPT = BIND_ROOT / "src/opnsense/scripts/OPNsense/Bind/bindStop.py"

if not current_release_requires_bounded_shutdown(BIND_ROOT):
pytestmark = pytest.mark.skip(reason="release predates bounded BIND shutdown")


def test_general_log_includes_informational_lifecycle_messages():
view = (BIND_ROOT / "src/opnsense/mvc/app/views/OPNsense/Bind/logs.volt").read_text()
stop = STOP_SCRIPT.read_text()

assert "'default_log_severity':'Informational'" in view
assert 'syslog.openlog("named")' in stop
2 changes: 1 addition & 1 deletion dns/bind/tests/test_package_install_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ def test_normal_stop_keeps_primary_journals_and_clears_reverse_scope(executable_
for zone in zones:
(zone_dir / f"{zone}.db.jnl").write_text("")
named = executable_tmp_path / "named-stop"
_write_executable(named, "#!/bin/sh\nexit 0\n")
_write_executable(named, '#!/bin/sh\n[ "$1" = status ] && exit 1\nexit 0\n')
watcher = executable_tmp_path / "watcher.conf"
watcher.write_text("")

Expand Down
10 changes: 9 additions & 1 deletion dns/bind/tests/test_package_zone_backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import pytest

from .bounded_shutdown_contract import current_release_requires_bounded_shutdown
from .package_lifecycle_contract import current_release_requires_lifecycle


Expand All @@ -16,6 +17,7 @@
BIND_ROOT / "src/opnsense/scripts/OPNsense/Bind/bindPackageZones.py"
)
MAKEFILE = BIND_ROOT / "Makefile"
BOUNDED_SHUTDOWN = current_release_requires_bounded_shutdown(BIND_ROOT)

if MAKEFILE.is_file():
version_line = next(
Expand Down Expand Up @@ -96,6 +98,7 @@ def _fixture(tmp_path, *, freeze_failure=""):
named,
"""#!/bin/sh
printf 'named %s\n' "$1" >> "$TEST_EVENTS"
[ "$1" = status ] && exit 1
exit 0
""",
)
Expand Down Expand Up @@ -156,11 +159,16 @@ def test_package_backup_preserves_non_watcher_dynamic_record(executable_tmp_path
)
assert discard.returncode == 0, discard.stderr
assert not backup.exists()
stop_event = (
"named status"
if BOUNDED_SHUTDOWN
else "named stop"
)
assert events.read_text().splitlines() == [
"rndc freeze 1.168.192.in-addr.arpa",
"rndc freeze dynamic.example",
"rndc freeze watcher.example",
"named stop",
stop_event,
]


Expand Down
Loading
Loading