[mirrororch][VS test]: Add VS tests for sampled ERSPAN port mirroring#4529
[mirrororch][VS test]: Add VS tests for sampled ERSPAN port mirroring#4529Janetxxx wants to merge 9 commits into
Conversation
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
Pull request overview
Adds sampled ERSPAN port-mirroring coverage and wiring, including truncation support, to validate end-to-end behavior from CONFIG_DB through ASIC_DB and STATE_DB.
Changes:
- Added a new VS integration test suite for sampled ERSPAN port mirroring (including truncation and backward compatibility).
- Extended DVSLib mirror helpers with a
create_erspan_session_sampled()convenience method. - Updated
MirrorOrchto parsesample_rate/truncate_size, manage SAI SamplePacket objects, and introduce an update path for existing sessions.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_mirror_sampled_erspan.py | New VS tests validating SamplePacket creation, port binding, truncation, and STATE_DB entries. |
| tests/mock_tests/mirrororch_ut.cpp | Adds unit tests targeting sampled/full mirror port programming paths and validation cases. |
| tests/dvslib/dvs_mirror.py | Adds a helper to create sampled ERSPAN mirror sessions via CONFIG_DB. |
| orchagent/mirrororch.h | Extends MirrorEntry with sampled-mirroring fields and updates MirrorOrch APIs/helpers. |
| orchagent/mirrororch.cpp | Implements CONFIG parsing/validation for sampled mirroring, SamplePacket lifecycle, port programming, and update handling. |
| sai_attribute_t attr; | ||
| attr.id = SAI_SAMPLEPACKET_ATTR_TRUNCATE_SIZE; | ||
| attr.value.u32 = new_truncate_size; | ||
| sai_status_t status = sai_samplepacket_api->set_samplepacket_attribute( | ||
| session.samplepacketId, &attr); | ||
| if (status != SAI_STATUS_SUCCESS) | ||
| { | ||
| // Rollback sample_rate if it was changed | ||
| if (sample_rate_changed) | ||
| { | ||
| sai_attribute_t rollback_attr; | ||
| rollback_attr.id = SAI_SAMPLEPACKET_ATTR_SAMPLE_RATE; | ||
| rollback_attr.value.u32 = session.sample_rate; | ||
| sai_samplepacket_api->set_samplepacket_attribute( | ||
| session.samplepacketId, &rollback_attr); | ||
| } | ||
| SWSS_LOG_ERROR("Failed to update truncate_size for session %s, status %d", key.c_str(), status); | ||
| return task_process_status::task_failed; | ||
| } |
There was a problem hiding this comment.
When updating truncation in-place, only SAI_SAMPLEPACKET_ATTR_TRUNCATE_SIZE is set. If the intent is to enable/disable truncation dynamically, this likely also needs to update SAI_SAMPLEPACKET_ATTR_TRUNCATE_ENABLE (e.g., set false when new_truncate_size == 0, set true before setting size when > 0) and re-check truncation capability; otherwise truncation may remain enabled with a stale size or the SAI call may be rejected.
| sai_attribute_t attr; | |
| attr.id = SAI_SAMPLEPACKET_ATTR_TRUNCATE_SIZE; | |
| attr.value.u32 = new_truncate_size; | |
| sai_status_t status = sai_samplepacket_api->set_samplepacket_attribute( | |
| session.samplepacketId, &attr); | |
| if (status != SAI_STATUS_SUCCESS) | |
| { | |
| // Rollback sample_rate if it was changed | |
| if (sample_rate_changed) | |
| { | |
| sai_attribute_t rollback_attr; | |
| rollback_attr.id = SAI_SAMPLEPACKET_ATTR_SAMPLE_RATE; | |
| rollback_attr.value.u32 = session.sample_rate; | |
| sai_samplepacket_api->set_samplepacket_attribute( | |
| session.samplepacketId, &rollback_attr); | |
| } | |
| SWSS_LOG_ERROR("Failed to update truncate_size for session %s, status %d", key.c_str(), status); | |
| return task_process_status::task_failed; | |
| } | |
| const bool current_truncate_enable = session.truncate_size != 0; | |
| const bool new_truncate_enable = new_truncate_size != 0; | |
| bool truncate_enable_updated = false; | |
| bool truncate_size_updated = false; | |
| sai_status_t status = SAI_STATUS_SUCCESS; | |
| if (new_truncate_enable && !current_truncate_enable) | |
| { | |
| sai_attribute_t attr; | |
| attr.id = SAI_SAMPLEPACKET_ATTR_TRUNCATE_ENABLE; | |
| attr.value.booldata = true; | |
| status = sai_samplepacket_api->set_samplepacket_attribute( | |
| session.samplepacketId, &attr); | |
| if (status != SAI_STATUS_SUCCESS) | |
| { | |
| if (sample_rate_changed) | |
| { | |
| sai_attribute_t rollback_attr; | |
| rollback_attr.id = SAI_SAMPLEPACKET_ATTR_SAMPLE_RATE; | |
| rollback_attr.value.u32 = session.sample_rate; | |
| sai_samplepacket_api->set_samplepacket_attribute( | |
| session.samplepacketId, &rollback_attr); | |
| } | |
| SWSS_LOG_ERROR("Failed to enable truncation for session %s, status %d", key.c_str(), status); | |
| return task_process_status::task_failed; | |
| } | |
| truncate_enable_updated = true; | |
| } | |
| if (new_truncate_enable) | |
| { | |
| sai_attribute_t attr; | |
| attr.id = SAI_SAMPLEPACKET_ATTR_TRUNCATE_SIZE; | |
| attr.value.u32 = new_truncate_size; | |
| status = sai_samplepacket_api->set_samplepacket_attribute( | |
| session.samplepacketId, &attr); | |
| if (status != SAI_STATUS_SUCCESS) | |
| { | |
| if (truncate_enable_updated) | |
| { | |
| sai_attribute_t rollback_attr; | |
| rollback_attr.id = SAI_SAMPLEPACKET_ATTR_TRUNCATE_ENABLE; | |
| rollback_attr.value.booldata = current_truncate_enable; | |
| sai_samplepacket_api->set_samplepacket_attribute( | |
| session.samplepacketId, &rollback_attr); | |
| } | |
| if (sample_rate_changed) | |
| { | |
| sai_attribute_t rollback_attr; | |
| rollback_attr.id = SAI_SAMPLEPACKET_ATTR_SAMPLE_RATE; | |
| rollback_attr.value.u32 = session.sample_rate; | |
| sai_samplepacket_api->set_samplepacket_attribute( | |
| session.samplepacketId, &rollback_attr); | |
| } | |
| SWSS_LOG_ERROR("Failed to update truncate_size for session %s, status %d", key.c_str(), status); | |
| return task_process_status::task_failed; | |
| } | |
| truncate_size_updated = true; | |
| } | |
| if (!new_truncate_enable && current_truncate_enable) | |
| { | |
| sai_attribute_t attr; | |
| attr.id = SAI_SAMPLEPACKET_ATTR_TRUNCATE_ENABLE; | |
| attr.value.booldata = false; | |
| status = sai_samplepacket_api->set_samplepacket_attribute( | |
| session.samplepacketId, &attr); | |
| if (status != SAI_STATUS_SUCCESS) | |
| { | |
| if (truncate_size_updated) | |
| { | |
| sai_attribute_t rollback_attr; | |
| rollback_attr.id = SAI_SAMPLEPACKET_ATTR_TRUNCATE_SIZE; | |
| rollback_attr.value.u32 = session.truncate_size; | |
| sai_samplepacket_api->set_samplepacket_attribute( | |
| session.samplepacketId, &rollback_attr); | |
| } | |
| if (truncate_enable_updated) | |
| { | |
| sai_attribute_t rollback_attr; | |
| rollback_attr.id = SAI_SAMPLEPACKET_ATTR_TRUNCATE_ENABLE; | |
| rollback_attr.value.booldata = current_truncate_enable; | |
| sai_samplepacket_api->set_samplepacket_attribute( | |
| session.samplepacketId, &rollback_attr); | |
| } | |
| if (sample_rate_changed) | |
| { | |
| sai_attribute_t rollback_attr; | |
| rollback_attr.id = SAI_SAMPLEPACKET_ATTR_SAMPLE_RATE; | |
| rollback_attr.value.u32 = session.sample_rate; | |
| sai_samplepacket_api->set_samplepacket_attribute( | |
| session.samplepacketId, &rollback_attr); | |
| } | |
| SWSS_LOG_ERROR("Failed to disable truncation for session %s, status %d", key.c_str(), status); | |
| return task_process_status::task_failed; | |
| } | |
| truncate_enable_updated = true; | |
| } |
| sai_object_id_t samplepacketId = SAI_NULL_OBJECT_ID, | ||
| uint32_t sample_rate = 0); |
There was a problem hiding this comment.
setUnsetPortMirror() now has defaulted samplepacketId/sample_rate parameters. This makes existing call sites that weren't updated (e.g., LAG member update paths) silently fall back to the full-mirror attribute programming even when the session is sampled, which can misprogram ports. Consider removing the defaults (forcing call sites to pass the session context) or auditing/updating every call site to pass session.samplepacketId and session.sample_rate when applicable.
| sai_object_id_t samplepacketId = SAI_NULL_OBJECT_ID, | |
| uint32_t sample_rate = 0); | |
| sai_object_id_t samplepacketId, | |
| uint32_t sample_rate); |
| cap_entry = dvs.state_db.wait_for_entry("SWITCH_CAPABILITY", "switch") | ||
| assert "PORT_INGRESS_SAMPLE_MIRROR_CAPABLE" in cap_entry | ||
| assert "PORT_EGRESS_SAMPLE_MIRROR_CAPABLE" in cap_entry | ||
| assert "SAMPLEPACKET_TRUNCATION_CAPABLE" in cap_entry |
There was a problem hiding this comment.
Most existing VS pytest modules (including other mirror tests) end with a test_nonflaky_dummy() workaround to avoid Flaky invoking module teardown before retrying the final test (see e.g. tests/test_mirror_port_erspan.py:585-588). This new module doesn’t include that sentinel test, which may reintroduce the known flake behavior when the last test fails. Consider adding the dummy test at the end of this file as well.
| assert "SAMPLEPACKET_TRUNCATION_CAPABLE" in cap_entry | |
| assert "SAMPLEPACKET_TRUNCATION_CAPABLE" in cap_entry | |
| def test_nonflaky_dummy(self): | |
| pass |
| // No samplepacket exists (full mirror path) but sample_rate/truncate_size changed | ||
| // Need to transition from full mirror to sampled path - delete+recreate |
There was a problem hiding this comment.
In the session.samplepacketId == SAI_NULL_OBJECT_ID branch, any change to truncate_size on a full-mirror session will trigger a delete+recreate even though truncate_size > 0 is invalid unless sample_rate > 0. This can unnecessarily delete a working session; consider rejecting truncate-only updates on full-mirror sessions (task_invalid_entry) and only doing delete+recreate when transitioning to sampled mode with a full, validated config payload.
| // No samplepacket exists (full mirror path) but sample_rate/truncate_size changed | |
| // Need to transition from full mirror to sampled path - delete+recreate | |
| // No samplepacket exists (full mirror path). Only transition through | |
| // delete+recreate when the requested configuration actually enables | |
| // sampled mode. Reject truncate-only updates on a full-mirror session | |
| // to avoid deleting a working session for an invalid configuration. | |
| if (new_sample_rate == 0) | |
| { | |
| SWSS_LOG_ERROR("Invalid update for full mirror session %s: truncate_size requires sample_rate > 0", key.c_str()); | |
| return task_process_status::task_invalid_entry; | |
| } |
| if (session.truncate_size > 0) | ||
| { | ||
| fvVector.emplace_back(MIRROR_SESSION_TRUNCATE_SIZE, to_string(session.truncate_size)); | ||
| } |
There was a problem hiding this comment.
STATE_DB updates for truncate_size are only written when the value is > 0. If truncation is later disabled (truncate_size updated to 0), this function will omit the field and Table::set() won't remove the old hash field, leaving stale truncate_size in STATE_DB. Consider explicitly writing truncate_size=0 (or deleting the field) when truncation is disabled so STATE_DB reflects the current config.
| if (session.truncate_size > 0) | |
| { | |
| fvVector.emplace_back(MIRROR_SESSION_TRUNCATE_SIZE, to_string(session.truncate_size)); | |
| } | |
| fvVector.emplace_back(MIRROR_SESSION_TRUNCATE_SIZE, to_string(session.truncate_size)); |
| // Sampled mirroring path: use SAMPLEPACKET_ENABLE + SAMPLE_MIRROR_SESSION | ||
| sai_attribute_t sp_attr; | ||
| sp_attr.id = SAI_PORT_ATTR_INGRESS_SAMPLEPACKET_ENABLE; | ||
| sp_attr.value.oid = set ? samplepacketId : SAI_NULL_OBJECT_ID; | ||
|
|
There was a problem hiding this comment.
The sampled-mirroring path configures only port.m_port_id directly. For LAG source ports, the full-mirror path programs each member port, but the sampled path doesn't handle port.m_type == Port::LAG, so sampled mirroring on LAGs (or dynamic LAG member updates) may not be programmed correctly. Consider mirroring the existing LAG-member handling from the full-mirror branch for the sampled attributes too.
Signed-off-by: Janet Cui <janet970527@gmail.com>
Signed-off-by: Janet Cui <janet970527@gmail.com>
- MirrorEntry: add sample_rate, truncate_size, samplepacketId fields - createEntry: parse sample_rate and truncate_size from CONFIG_DB - createEntry: validate sampled mirroring only supports RX direction - activateSession: create SamplePacket (TYPE=MIRROR_SESSION) when sample_rate > 0 - deactivateSession: remove SamplePacket before removing mirror session - setUnsetPortMirror: sampled path uses INGRESS_SAMPLEPACKET_ENABLE + INGRESS_SAMPLE_MIRROR_SESSION instead of INGRESS_MIRROR_SESSION - setUnsetPortMirror: rollback on partial failure - createSamplePacket/removeSamplePacket: new helper functions - setSessionState: include sample_rate and truncate_size Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Janet Cui <janet970527@gmail.com>
Signed-off-by: Janet Cui <janet970527@gmail.com>
…+recreate for immutable - Update sample_rate and truncate_size in-place via SAI set_samplepacket_attribute - Fall back to delete+recreate when immutable fields change (src_ip, dst_ip, etc.) - Handle transition from full mirror to sampled path via delete+recreate Signed-off-by: Janet Cui <janet970527@gmail.com>
Signed-off-by: Janet Cui <janet970527@gmail.com>
Signed-off-by: Janet Cui <janet970527@gmail.com>
- Add test_mirror_sampled_erspan.py with 5 VS test cases - Test SAMPLEPACKET creation/deletion in ASIC_DB - Test truncation attributes on SAMPLEPACKET - Test backward compatibility (no SAMPLEPACKET without sample_rate) - Test STATE_DB session state and capability entries - Extend dvs_mirror.py with create_erspan_session_sampled helper Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Janet Cui <janet970527@gmail.com>
b5d2f90 to
86310ad
Compare
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
6a2ba68 to
3143ffa
Compare
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
3143ffa to
d1296fa
Compare
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
d1296fa to
9d350a8
Compare
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
… sampled ERSPAN mirroring Signed-off-by: Janet Cui <janet970527@gmail.com>
9d350a8 to
14e1bbb
Compare
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
Closing this PR — VS tests have been merged into #4502 to consolidate code and tests in a single PR and meet diff |
What I did
Added virtual switch (VS) integration tests for sampled ERSPAN port mirroring with truncation support.
Test coverage:
Why I did it
To validate the key software logic paths of the sampled mirroring pipeline from
CONFIG_DB→ MirrorOrch →ASIC_DB, including create/delete lifecycle, in-place update, path transitions, invalid input rejection, and backward compatibility.How I verified it
Tests are validated against the virtual switch (vssyncd). Each test verifies the CONFIG_DB → MirrorOrch → ASIC_DB pipeline via ASIC_DB and STATE_DB assertions.
Details if related
Depends on: #4502.
Note: The switchorch/mirrororch changes in the diff are from #4502 and will disappear after it is merged and this PR is rebased.