From 547572b6b2c713912704fbff73bb2ab48cb36aa4 Mon Sep 17 00:00:00 2001 From: Daniel Donoghue Date: Wed, 11 Mar 2026 13:45:24 +0100 Subject: [PATCH] res_pjsip_maintenance: Add tests for PJSIP endpoint maintenance mode Adds tests for the res_pjsip_maintenance module, which provides per- endpoint runtime maintenance mode via AMI and CLI. inbound_rejection: enables maintenance mode on endpoint "alice" via the PJSIPSetMaintenance AMI action, then verifies that a new inbound INVITE is rejected with 503 Service Unavailable before reaching the dialplan. ami_actions: verifies the full AMI control plane - PJSIPSetMaintenance emits a PJSIPMaintenanceStatus event with Status: enabled, which in turn triggers PJSIPShowMaintenance, confirming the list response and PJSIPMaintenanceStatusComplete are correctly produced. outbound_blocking: enables maintenance mode on endpoint "alice" via PJSIPSetMaintenance, then fires an async AMI Originate to PJSIP/alice and verifies that OriginateResponse carries Response: Failure. No registered contact is required; the session_create supplement check in ast_sip_session_create_outgoing() fires before any contact or AOR lookup, so the origination is refused immediately without any SIP traffic being generated. Development was assisted by Claude (Anthropic). All generated code has been reviewed, tested, and is understood by the author. --- .../ami_actions/configs/ast1/modules.conf.inc | 1 + .../ami_actions/configs/ast1/pjsip.conf | 24 ++++++ .../maintenance/ami_actions/test-config.yaml | 82 +++++++++++++++++++ .../configs/ast1/extensions.conf | 8 ++ .../configs/ast1/modules.conf.inc | 1 + .../inbound_rejection/configs/ast1/pjsip.conf | 28 +++++++ .../sipp/invite_expect_503.xml | 53 ++++++++++++ .../inbound_rejection/test-config.yaml | 39 +++++++++ .../configs/ast1/modules.conf.inc | 1 + .../outbound_blocking/configs/ast1/pjsip.conf | 23 ++++++ .../outbound_blocking/test-config.yaml | 77 +++++++++++++++++ tests/channels/pjsip/maintenance/tests.yaml | 4 + tests/channels/pjsip/tests.yaml | 1 + 13 files changed, 342 insertions(+) create mode 100644 tests/channels/pjsip/maintenance/ami_actions/configs/ast1/modules.conf.inc create mode 100644 tests/channels/pjsip/maintenance/ami_actions/configs/ast1/pjsip.conf create mode 100644 tests/channels/pjsip/maintenance/ami_actions/test-config.yaml create mode 100644 tests/channels/pjsip/maintenance/inbound_rejection/configs/ast1/extensions.conf create mode 100644 tests/channels/pjsip/maintenance/inbound_rejection/configs/ast1/modules.conf.inc create mode 100644 tests/channels/pjsip/maintenance/inbound_rejection/configs/ast1/pjsip.conf create mode 100644 tests/channels/pjsip/maintenance/inbound_rejection/sipp/invite_expect_503.xml create mode 100644 tests/channels/pjsip/maintenance/inbound_rejection/test-config.yaml create mode 100644 tests/channels/pjsip/maintenance/outbound_blocking/configs/ast1/modules.conf.inc create mode 100644 tests/channels/pjsip/maintenance/outbound_blocking/configs/ast1/pjsip.conf create mode 100644 tests/channels/pjsip/maintenance/outbound_blocking/test-config.yaml create mode 100644 tests/channels/pjsip/maintenance/tests.yaml diff --git a/tests/channels/pjsip/maintenance/ami_actions/configs/ast1/modules.conf.inc b/tests/channels/pjsip/maintenance/ami_actions/configs/ast1/modules.conf.inc new file mode 100644 index 000000000..58c534519 --- /dev/null +++ b/tests/channels/pjsip/maintenance/ami_actions/configs/ast1/modules.conf.inc @@ -0,0 +1 @@ +load => res_pjsip_maintenance.so diff --git a/tests/channels/pjsip/maintenance/ami_actions/configs/ast1/pjsip.conf b/tests/channels/pjsip/maintenance/ami_actions/configs/ast1/pjsip.conf new file mode 100644 index 000000000..f07e19997 --- /dev/null +++ b/tests/channels/pjsip/maintenance/ami_actions/configs/ast1/pjsip.conf @@ -0,0 +1,24 @@ +[system] +type=system +timer_t1=100 +timer_b=6400 + +[global] +type=global +debug=yes + +[local] +type=transport +protocol=udp +bind=0.0.0.0 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; Endpoint "alice" exists only so that PJSIPSetMaintenance can validate +; it via ast_sorcery_retrieve_by_id. No calls are made in this test. +[alice] +type=endpoint +transport=local +context=default +disallow=all +allow=ulaw diff --git a/tests/channels/pjsip/maintenance/ami_actions/test-config.yaml b/tests/channels/pjsip/maintenance/ami_actions/test-config.yaml new file mode 100644 index 000000000..72e0ea144 --- /dev/null +++ b/tests/channels/pjsip/maintenance/ami_actions/test-config.yaml @@ -0,0 +1,82 @@ +testinfo: + summary: 'Test PJSIPSetMaintenance and PJSIPShowMaintenance AMI actions' + description: | + Verifies the full AMI control plane for res_pjsip_maintenance: + + * PJSIPSetMaintenance State: on emits PJSIPMaintenanceStatus (enabled) + * PJSIPShowMaintenance returns a PJSIPMaintenanceStatus list entry + followed by PJSIPMaintenanceStatusComplete + + Sequence: + 1. On AMI connect, fire PJSIPSetMaintenance for endpoint "alice". + 2. The resulting PJSIPMaintenanceStatus (enabled) notification triggers + PJSIPShowMaintenance (with a known ActionID). trigger-on-count + ensures this fires exactly once even though Show itself also emits + a PJSIPMaintenanceStatus event. + 3. The test stops when PJSIPMaintenanceStatusComplete arrives with the + expected ActionID. + +test-modules: + test-object: + config-section: object-config + typename: 'test_case.TestCaseModule' + modules: + - + config-section: ami-config + typename: 'pluggable_modules.EventActionModule' + +object-config: + reactor-timeout: 15 + connect-ami: True + +ami-config: + # Step 1: enable maintenance for "alice" via AMI + - + ami-start: + ami-actions: + action: + Action: 'PJSIPSetMaintenance' + Endpoint: 'alice' + State: 'on' + + # Step 2: when the State change notification event arrives, trigger + # PJSIPShowMaintenance. trigger-on-count: True with count: '>1' (min=1) + # ensures the action fires only on the *first* matching event + # (count['event'] == min == 1); subsequent events - including the + # PJSIPMaintenanceStatus emitted by the Show list response itself - do + # not re-trigger the action (count==2, not equal to min==1). + # count: '>1' (at-least-1) also keeps the end-of-test count check happy + # when two matching events total have been received. + - + ami-events: + conditions: + match: + Event: 'PJSIPMaintenanceStatus' + Status: 'enabled' + Endpoint: 'alice' + count: '>1' + trigger-on-count: True + ami-actions: + action: + Action: 'PJSIPShowMaintenance' + Endpoint: 'alice' + ActionID: 'maint-show-01' + + # Step 3: stop (pass) when the Show list-complete event arrives + - + ami-events: + conditions: + match: + Event: 'PJSIPMaintenanceStatusComplete' + requirements: + match: + ActionID: 'maint-show-01' + count: 1 + stop_test: + +properties: + dependencies: + - asterisk: 'res_pjsip' + - asterisk: 'res_pjsip_maintenance' + tags: + - pjsip diff --git a/tests/channels/pjsip/maintenance/inbound_rejection/configs/ast1/extensions.conf b/tests/channels/pjsip/maintenance/inbound_rejection/configs/ast1/extensions.conf new file mode 100644 index 000000000..5e8716355 --- /dev/null +++ b/tests/channels/pjsip/maintenance/inbound_rejection/configs/ast1/extensions.conf @@ -0,0 +1,8 @@ +[default] +; This dialplan is not reached during a maintenance-mode test; the +; PJSIP maintenance module rejects the INVITE with 503 before Asterisk +; ever matches a dialplan extension. The context is required by the +; endpoint configuration. +exten => alice,1,Answer() +same => n,Wait(1) +same => n,Hangup() diff --git a/tests/channels/pjsip/maintenance/inbound_rejection/configs/ast1/modules.conf.inc b/tests/channels/pjsip/maintenance/inbound_rejection/configs/ast1/modules.conf.inc new file mode 100644 index 000000000..58c534519 --- /dev/null +++ b/tests/channels/pjsip/maintenance/inbound_rejection/configs/ast1/modules.conf.inc @@ -0,0 +1 @@ +load => res_pjsip_maintenance.so diff --git a/tests/channels/pjsip/maintenance/inbound_rejection/configs/ast1/pjsip.conf b/tests/channels/pjsip/maintenance/inbound_rejection/configs/ast1/pjsip.conf new file mode 100644 index 000000000..ce03f3a93 --- /dev/null +++ b/tests/channels/pjsip/maintenance/inbound_rejection/configs/ast1/pjsip.conf @@ -0,0 +1,28 @@ +[system] +type=system +timer_t1=100 +timer_b=6400 + +[global] +type=global +debug=yes + +[local] +type=transport +protocol=udp +bind=0.0.0.0 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +[alice] +type=endpoint +transport=local +context=default +direct_media=no +disallow=all +allow=ulaw + +[alice-identify] +type=identify +endpoint=alice +match=127.0.0.1 diff --git a/tests/channels/pjsip/maintenance/inbound_rejection/sipp/invite_expect_503.xml b/tests/channels/pjsip/maintenance/inbound_rejection/sipp/invite_expect_503.xml new file mode 100644 index 000000000..cd016ebd0 --- /dev/null +++ b/tests/channels/pjsip/maintenance/inbound_rejection/sipp/invite_expect_503.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + ;tag=[call_number] + To: asterisk + Call-ID: [call_id] + CSeq: 1 INVITE + Contact: + Max-Forwards: 70 + Subject: Maintenance Test + Content-Length: 0 + + ]]> + + + + + + + + + ;tag=[call_number] + To: asterisk [peer_tag_param] + Call-ID: [call_id] + CSeq: 1 ACK + Contact: + Max-Forwards: 70 + Content-Length: 0 + + ]]> + + + + + + diff --git a/tests/channels/pjsip/maintenance/inbound_rejection/test-config.yaml b/tests/channels/pjsip/maintenance/inbound_rejection/test-config.yaml new file mode 100644 index 000000000..335017206 --- /dev/null +++ b/tests/channels/pjsip/maintenance/inbound_rejection/test-config.yaml @@ -0,0 +1,39 @@ +testinfo: + summary: 'Test res_pjsip_maintenance rejects inbound INVITE with 503' + description: | + Places a PJSIP endpoint into maintenance mode via the AMI + PJSIPSetMaintenance action and verifies that a new inbound INVITE + from that endpoint is rejected with "503 Service Unavailable". + The SIPp scenario pauses briefly after startup to allow the AMI + action to complete before sending the INVITE. + +test-modules: + test-object: + config-section: test-object-config + typename: 'sipp.SIPpAMIActionTestCase' + +test-object-config: + memcheck-delay-stop: 7 + reactor-timeout: 30 + fail-on-any: True + test-iterations: + - + scenarios: + - { 'key-args': { 'scenario': 'invite_expect_503.xml', + '-i': '127.0.0.1', '-p': '5061', + '-s': 'alice' } } + ami-action: + delay: 0 + args: + Action: 'PJSIPSetMaintenance' + Endpoint: 'alice' + State: 'on' + +properties: + dependencies: + - sipp: + version: 'v3.0' + - asterisk: 'res_pjsip' + - asterisk: 'res_pjsip_maintenance' + tags: + - pjsip diff --git a/tests/channels/pjsip/maintenance/outbound_blocking/configs/ast1/modules.conf.inc b/tests/channels/pjsip/maintenance/outbound_blocking/configs/ast1/modules.conf.inc new file mode 100644 index 000000000..58c534519 --- /dev/null +++ b/tests/channels/pjsip/maintenance/outbound_blocking/configs/ast1/modules.conf.inc @@ -0,0 +1 @@ +load => res_pjsip_maintenance.so diff --git a/tests/channels/pjsip/maintenance/outbound_blocking/configs/ast1/pjsip.conf b/tests/channels/pjsip/maintenance/outbound_blocking/configs/ast1/pjsip.conf new file mode 100644 index 000000000..6249d00b0 --- /dev/null +++ b/tests/channels/pjsip/maintenance/outbound_blocking/configs/ast1/pjsip.conf @@ -0,0 +1,23 @@ +[system] +type=system +timer_t1=100 +timer_b=6400 + +[global] +type=global +debug=yes + +[local] +type=transport +protocol=udp +bind=0.0.0.0 + +; Endpoint "alice" - no registered contact is needed. The session_create +; supplement check in ast_sip_session_create_outgoing() fires before any +; contact or AOR lookup, so the origination is refused immediately. +[alice] +type=endpoint +transport=local +context=default +disallow=all +allow=ulaw diff --git a/tests/channels/pjsip/maintenance/outbound_blocking/test-config.yaml b/tests/channels/pjsip/maintenance/outbound_blocking/test-config.yaml new file mode 100644 index 000000000..3faa09972 --- /dev/null +++ b/tests/channels/pjsip/maintenance/outbound_blocking/test-config.yaml @@ -0,0 +1,77 @@ +testinfo: + summary: 'Test res_pjsip_maintenance blocks outbound origination' + description: | + Places a PJSIP endpoint into maintenance mode via the AMI + PJSIPSetMaintenance action and verifies that an outbound origination + to that endpoint via AMI Originate is refused before any SIP session + is created. + + Sequence: + 1. On AMI connect, enable maintenance for endpoint "alice". + 2. On PJSIPMaintenanceStatus (enabled), fire an async AMI Originate + to PJSIP/alice with a known ActionID. + 3. The test passes when OriginateResponse arrives with + Response: Failure for that ActionID, confirming the channel was + never created. + +test-modules: + test-object: + config-section: object-config + typename: 'test_case.TestCaseModule' + modules: + - + config-section: ami-config + typename: 'pluggable_modules.EventActionModule' + +object-config: + reactor-timeout: 15 + connect-ami: True + +ami-config: + # Step 1: enable maintenance for "alice" via AMI + - + ami-start: + ami-actions: + action: + Action: 'PJSIPSetMaintenance' + Endpoint: 'alice' + State: 'on' + + # Step 2: once maintenance is confirmed, attempt an outbound origination + - + ami-events: + conditions: + match: + Event: 'PJSIPMaintenanceStatus' + Status: 'enabled' + Endpoint: 'alice' + count: 1 + ami-actions: + action: + Action: 'Originate' + Channel: 'PJSIP/alice' + Context: 'default' + Exten: 's' + Priority: '1' + Async: 'yes' + ActionID: 'maint-outbound-01' + + # Step 3: stop (pass) when the origination fails as expected + - + ami-events: + conditions: + match: + Event: 'OriginateResponse' + Response: 'Failure' + requirements: + match: + ActionID: 'maint-outbound-01' + count: 1 + stop_test: + +properties: + dependencies: + - asterisk: 'res_pjsip' + - asterisk: 'res_pjsip_maintenance' + tags: + - pjsip diff --git a/tests/channels/pjsip/maintenance/tests.yaml b/tests/channels/pjsip/maintenance/tests.yaml new file mode 100644 index 000000000..57ae52d6d --- /dev/null +++ b/tests/channels/pjsip/maintenance/tests.yaml @@ -0,0 +1,4 @@ +tests: + - test: 'inbound_rejection' + - test: 'ami_actions' + - test: 'outbound_blocking' diff --git a/tests/channels/pjsip/tests.yaml b/tests/channels/pjsip/tests.yaml index b0c09da68..bfa0d7144 100644 --- a/tests/channels/pjsip/tests.yaml +++ b/tests/channels/pjsip/tests.yaml @@ -13,6 +13,7 @@ tests: - dir: 'history_info' - dir: 'headers' - dir: 'identify' + - dir: 'maintenance' - dir: 'mediasec' - dir: 'message' - dir: 'nat'