diff --git a/src/sonic-yang-models/doc/Configuration.md b/src/sonic-yang-models/doc/Configuration.md index 953942f360..e16e6342ea 100644 --- a/src/sonic-yang-models/doc/Configuration.md +++ b/src/sonic-yang-models/doc/Configuration.md @@ -79,6 +79,7 @@ Table of Contents * [Telemetry](#telemetry) * [Telemetry client](#telemetry-client) * [Tunnel](#tunnel) + * [Trimming](#trimming) * [Versions](#versions) * [VLAN](#vlan) * [VLAN_MEMBER](#vlan_member) @@ -380,6 +381,43 @@ and migration plan } } ``` + +***ACL fine-grained packet trimming control with disable trimming action configuration example*** +``` +{ + "ACL_TABLE_TYPE": { + "TRIMMING_L3": { + "MATCHES": [ + "SRC_IP" + ], + "ACTIONS": [ + "DISABLE_TRIM_ACTION" + ], + "BIND_POINTS": [ + "PORT" + ] + } + }, + "ACL_TABLE": { + "TRIM_TABLE": { + "POLICY_DESC": "Packet trimming", + "TYPE": "TRIMMING_L3", + "STAGE": "INGRESS", + "PORTS": [ + "Ethernet0" + ] + } + }, + "ACL_RULE": { + "TRIM_TABLE|TRIM_RULE": { + "PRIORITY": "999", + "SRC_IP": "1.1.1.1/32", + "PACKET_ACTION": "DISABLE_TRIM" + } + } +} +``` + ### BGP BBR The **BGP_BBR** table contains device-level BBR state. @@ -661,6 +699,18 @@ This kind of profiles will be handled by buffer manager and won't be applied to } ``` +***Packet trimming configuration example*** +``` +{ + "q_lossy_profile": { + "dynamic_th": "3", + "pool": "egress_lossy_pool", + "size": "0", + "packet_discard_action": "drop" + } +} +``` + ### Buffer queue ``` @@ -2452,6 +2502,34 @@ example mux tunnel configuration for when tunnel_qos_remap is enabled } ``` +### Trimming + +When the lossy queue exceeds a buffer threshold, it drops packets without any notification to the destination host. + +When a packet is lost, it can be recovered through fast retransmission or by using timeouts. +Retransmission triggered by timeouts typically incurs significant latency. + +To help the host recover data more quickly and accurately, packet trimming is introduced. +This feature upon a failed packet admission to a shared buffer, will trim a packet to a configured size, +and try sending it on a different queue to deliver a packet drop notification to an end host. + +***TRIMMING*** + +``` +{ + "SWITCH_TRIMMING": { + "GLOBAL": { + "size": "128", + "dscp_value": "48", + "queue_index": "6" + } + } +} +``` + +**Note:** +* when `queue_index` is set to `dynamic`, the `dscp_value` is used for mapping to queue + ### Versions This table is where the curret version of the software is recorded. diff --git a/src/sonic-yang-models/setup.py b/src/sonic-yang-models/setup.py index 0266803018..d23ad0b52f 100644 --- a/src/sonic-yang-models/setup.py +++ b/src/sonic-yang-models/setup.py @@ -131,6 +131,7 @@ def run(self): './yang-models/sonic-feature.yang', './yang-models/sonic-fips.yang', './yang-models/sonic-hash.yang', + './yang-models/sonic-trimming.yang', './yang-models/sonic-system-defaults.yang', './yang-models/sonic-interface.yang', './yang-models/sonic-kdump.yang', @@ -241,6 +242,7 @@ def run(self): './cvlyang-models/sonic-fine-grained-ecmp.yang', './cvlyang-models/sonic-fips.yang', './cvlyang-models/sonic-hash.yang', + './cvlyang-models/sonic-trimming.yang', './cvlyang-models/sonic-system-defaults.yang', './cvlyang-models/sonic-interface.yang', './cvlyang-models/sonic-kdump.yang', diff --git a/src/sonic-yang-models/tests/files/sample_config_db.json b/src/sonic-yang-models/tests/files/sample_config_db.json index afcf987367..167962c595 100644 --- a/src/sonic-yang-models/tests/files/sample_config_db.json +++ b/src/sonic-yang-models/tests/files/sample_config_db.json @@ -367,6 +367,13 @@ "lag_hash_algorithm": "XOR" } }, + "SWITCH_TRIMMING": { + "GLOBAL": { + "size": "128", + "dscp_value": "48", + "queue_index": "6" + } + }, "DEVICE_METADATA": { "localhost": { "buffer_model": "dynamic", diff --git a/src/sonic-yang-models/tests/yang_model_pytests/test_acl.py b/src/sonic-yang-models/tests/yang_model_pytests/test_acl.py new file mode 100644 index 0000000000..98dbc16ebf --- /dev/null +++ b/src/sonic-yang-models/tests/yang_model_pytests/test_acl.py @@ -0,0 +1,108 @@ +import pytest + + +class TestACL: + def test_valid_data_trimming(self, yang_model): + data = { + "sonic-port:sonic-port": { + "sonic-port:PORT": { + "PORT_LIST": [ + { + "name": "Ethernet0", + "lanes": "0,1,2,3", + "speed": "100000" + } + ] + } + }, + "sonic-acl:sonic-acl": { + "sonic-acl:ACL_TABLE_TYPE": { + "ACL_TABLE_TYPE_LIST": [ + { + "ACL_TABLE_TYPE_NAME": "TRIMMING_L3", + "BIND_POINTS": [ "PORT" ], + "MATCHES": [ "SRC_IP" ], + "ACTIONS": [ "DISABLE_TRIM_ACTION" ] + } + ] + }, + "sonic-acl:ACL_TABLE": { + "ACL_TABLE_LIST": [ + { + "ACL_TABLE_NAME": "TRIM_TABLE", + "type": "TRIMMING_L3", + "stage": "INGRESS", + "ports": [ "Ethernet0" ] + } + ] + }, + "sonic-acl:ACL_RULE": { + "ACL_RULE_LIST": [ + { + "ACL_TABLE_NAME": "TRIM_TABLE", + "RULE_NAME": "TRIM_RULE", + "PRIORITY": "999", + "SRC_IP": "1.1.1.1/32", + "PACKET_ACTION": "DISABLE_TRIM" + } + ] + } + } + } + + yang_model.load_data(data) + + @pytest.mark.parametrize( + "action,error_message", [ + pytest.param('INVALID_VALUE', 'Invalid value', id="invalid-value") + ] + ) + def test_neg_rule_packet_action(self, yang_model, action, error_message): + data = { + "sonic-port:sonic-port": { + "sonic-port:PORT": { + "PORT_LIST": [ + { + "name": "Ethernet0", + "lanes": "0,1,2,3", + "speed": "100000" + } + ] + } + }, + "sonic-acl:sonic-acl": { + "sonic-acl:ACL_TABLE_TYPE": { + "ACL_TABLE_TYPE_LIST": [ + { + "ACL_TABLE_TYPE_NAME": "TRIMMING_L3", + "BIND_POINTS": [ "PORT" ], + "MATCHES": [ "SRC_IP" ], + "ACTIONS": [ "DISABLE_TRIM_ACTION" ] + } + ] + }, + "sonic-acl:ACL_TABLE": { + "ACL_TABLE_LIST": [ + { + "ACL_TABLE_NAME": "TRIM_TABLE", + "type": "TRIMMING_L3", + "stage": "INGRESS", + "ports": [ "Ethernet0" ] + } + ] + }, + "sonic-acl:ACL_RULE": { + "ACL_RULE_LIST": [ + { + "ACL_TABLE_NAME": "TRIM_TABLE", + "RULE_NAME": "TRIM_RULE", + "PRIORITY": "999", + "SRC_IP": "1.1.1.1/32", + "PACKET_ACTION": action + } + ] + } + } + } + + yang_model.load_data(data, error_message) diff --git a/src/sonic-yang-models/tests/yang_model_pytests/test_buffer_profile.py b/src/sonic-yang-models/tests/yang_model_pytests/test_buffer_profile.py new file mode 100644 index 0000000000..2e56ae8e2c --- /dev/null +++ b/src/sonic-yang-models/tests/yang_model_pytests/test_buffer_profile.py @@ -0,0 +1,69 @@ +import pytest + + +class TestBufferProfile: + @pytest.mark.parametrize("action", ["drop", "trim"]) + def test_valid_data_lossy(self, yang_model, action): + data = { + "sonic-buffer-pool:sonic-buffer-pool": { + "sonic-buffer-pool:BUFFER_POOL": { + "BUFFER_POOL_LIST": [ + { + "name": "egress_lossy_pool", + "type": "egress", + "mode": "dynamic" + } + ] + } + }, + "sonic-buffer-profile:sonic-buffer-profile": { + "sonic-buffer-profile:BUFFER_PROFILE": { + "BUFFER_PROFILE_LIST": [ + { + "name": "q_lossy_trim_profile", + "pool": "egress_lossy_pool", + "dynamic_th": "0", + "size": "0", + "packet_discard_action": action + } + ] + } + } + } + + yang_model.load_data(data) + + @pytest.mark.parametrize( + "action,error_message", [ + pytest.param('INVALID_VALUE', 'Invalid value', id="invalid-value") + ] + ) + def test_neg_packet_discard_action(self, yang_model, action, error_message): + data = { + "sonic-buffer-pool:sonic-buffer-pool": { + "sonic-buffer-pool:BUFFER_POOL": { + "BUFFER_POOL_LIST": [ + { + "name": "egress_lossy_pool", + "type": "egress", + "mode": "dynamic" + } + ] + } + }, + "sonic-buffer-profile:sonic-buffer-profile": { + "sonic-buffer-profile:BUFFER_PROFILE": { + "BUFFER_PROFILE_LIST": [ + { + "name": "q_lossy_trim_profile", + "pool": "egress_lossy_pool", + "dynamic_th": "0", + "size": "0", + "packet_discard_action": action + } + ] + } + } + } + + yang_model.load_data(data, error_message) diff --git a/src/sonic-yang-models/tests/yang_model_pytests/test_trimming.py b/src/sonic-yang-models/tests/yang_model_pytests/test_trimming.py new file mode 100644 index 0000000000..a8a580d2f0 --- /dev/null +++ b/src/sonic-yang-models/tests/yang_model_pytests/test_trimming.py @@ -0,0 +1,81 @@ +import pytest + + +class TestTrimming: + @pytest.mark.parametrize( + "index", [ + pytest.param('6', id="static"), + pytest.param('dynamic', id="dynamic") + ] + ) + def test_valid_data(self, yang_model, index): + data = { + "sonic-trimming:sonic-trimming": { + "sonic-trimming:SWITCH_TRIMMING": { + "GLOBAL": { + "size": "128", + "dscp_value": "48", + "queue_index": index + } + } + } + } + + yang_model.load_data(data) + + @pytest.mark.parametrize( + "size,error_message", [ + pytest.param('-1', 'Invalid value', id="min-1"), + pytest.param('4294967296', 'Invalid value', id="max+1") + ] + ) + def test_neg_size(self, yang_model, size, error_message): + data = { + "sonic-trimming:sonic-trimming": { + "sonic-trimming:SWITCH_TRIMMING": { + "GLOBAL": { + "size": size + } + } + } + } + + yang_model.load_data(data, error_message) + + @pytest.mark.parametrize( + "dscp,error_message", [ + pytest.param('-1', 'Invalid value', id="min-1"), + pytest.param('64', 'Invalid DSCP value', id="max+1") + ] + ) + def test_neg_dscp_value(self, yang_model, dscp, error_message): + data = { + "sonic-trimming:sonic-trimming": { + "sonic-trimming:SWITCH_TRIMMING": { + "GLOBAL": { + "dscp_value": dscp + } + } + } + } + + yang_model.load_data(data, error_message) + + @pytest.mark.parametrize( + "index,error_message", [ + pytest.param('-1', 'Invalid value', id="min-1"), + pytest.param('256', 'Invalid value', id="max+1") + ] + ) + def test_neg_queue_index(self, yang_model, index, error_message): + data = { + "sonic-trimming:sonic-trimming": { + "sonic-trimming:SWITCH_TRIMMING": { + "GLOBAL": { + "queue_index": index + } + } + } + } + + yang_model.load_data(data, error_message) diff --git a/src/sonic-yang-models/yang-models/sonic-buffer-profile.yang b/src/sonic-yang-models/yang-models/sonic-buffer-profile.yang index 418302e9fc..b9684b0767 100644 --- a/src/sonic-yang-models/yang-models/sonic-buffer-profile.yang +++ b/src/sonic-yang-models/yang-models/sonic-buffer-profile.yang @@ -85,6 +85,14 @@ module sonic-buffer-profile { } description "Profile is dynamically calculated or user configured"; } + + leaf packet_discard_action { + type enumeration { + enum drop; + enum trim; + } + description "Action on failure to admit a packet to Shared Buffer/MMU"; + } } } } diff --git a/src/sonic-yang-models/yang-models/sonic-trimming.yang b/src/sonic-yang-models/yang-models/sonic-trimming.yang new file mode 100644 index 0000000000..f524b1bc7f --- /dev/null +++ b/src/sonic-yang-models/yang-models/sonic-trimming.yang @@ -0,0 +1,56 @@ +module sonic-trimming { + + yang-version 1.1; + + namespace "http://github.com/sonic-net/sonic-trimming"; + prefix trim; + + description "TRIMMING YANG Module for SONiC OS"; + + revision 2024-11-01 { + description "First Revision"; + } + + container sonic-trimming { + + container SWITCH_TRIMMING { + + description "SWITCH_TRIMMING part of config_db.json"; + + container GLOBAL { + + leaf size { + description "Size (in bytes) to trim eligible packet"; + type uint32; + } + + leaf dscp_value { + description "DSCP value assigned to a packet after trimming"; + type uint8 { + range "0..63" { + error-message "Invalid DSCP value"; + error-app-tag dscp-invalid; + } + } + } + + leaf queue_index { + description + "Queue index to use for transmission of a packet after trimming. + When set to dynamic, the dscp_value is used for mapping to queue"; + type union { + type uint8; + type string { + pattern "dynamic"; + } + } + } + + } + /* end of container GLOBAL */ + } + /* end of container SWITCH_TRIMMING */ + } + /* end of container sonic-trimming */ +} +/* end of module sonic-trimming */ diff --git a/src/sonic-yang-models/yang-templates/sonic-types.yang.j2 b/src/sonic-yang-models/yang-templates/sonic-types.yang.j2 index 7a5b0b5a03..cbd9f137a5 100644 --- a/src/sonic-yang-models/yang-templates/sonic-types.yang.j2 +++ b/src/sonic-yang-models/yang-templates/sonic-types.yang.j2 @@ -65,6 +65,7 @@ module sonic-types { enum FORWARD; enum REDIRECT; enum DO_NOT_NAT; + enum DISABLE_TRIM; } }