From 78a6f413aa43d7c1155db9fc1993975a474ffb8d Mon Sep 17 00:00:00 2001 From: Pim van Pelt Date: Mon, 28 Oct 2024 15:49:35 +0100 Subject: [PATCH] Add sflow schema, validators (currently nothing to do), and dumper --- vppcfg/config/__init__.py | 2 ++ vppcfg/config/sflow.py | 30 ++++++++++++++++++++++++++++++ vppcfg/example.yaml | 7 +++++++ vppcfg/schema.yaml | 7 +++++++ vppcfg/vpp/dumper.py | 30 ++++++++++++++++++------------ vppcfg/vpp/vppapi.py | 29 +++++++++++++++++++++++++++++ 6 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 vppcfg/config/sflow.py diff --git a/vppcfg/config/__init__.py b/vppcfg/config/__init__.py index d7b469e..ee03d65 100644 --- a/vppcfg/config/__init__.py +++ b/vppcfg/config/__init__.py @@ -40,6 +40,7 @@ from .vxlan_tunnel import validate_vxlan_tunnels from .tap import validate_taps from .prefixlist import validate_prefixlists from .acl import validate_acls +from .sflow import validate_sflow class IPInterfaceWithPrefixLength(validators.Validator): @@ -94,6 +95,7 @@ class Validator: validate_taps, validate_prefixlists, validate_acls, + validate_sflow, ] def validate(self, yaml): diff --git a/vppcfg/config/sflow.py b/vppcfg/config/sflow.py new file mode 100644 index 0000000..b220e4b --- /dev/null +++ b/vppcfg/config/sflow.py @@ -0,0 +1,30 @@ +# +# Copyright (c) 2024 Pim van Pelt +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +""" A vppcfg configuration module that validates sflow config """ +import logging + + +def validate_sflow(yaml): + """Validate the semantics of all YAML 'sflow' config entries""" + result = True + msgs = [] + logger = logging.getLogger("vppcfg.config") + logger.addHandler(logging.NullHandler()) + + if not "sflow" in yaml: + return result, msgs + + ## NOTE(pim): Nothing to validate. sflow config values are all + ## integers and enforced by yamale. + return result, msgs diff --git a/vppcfg/example.yaml b/vppcfg/example.yaml index d9bcbff..88be079 100644 --- a/vppcfg/example.yaml +++ b/vppcfg/example.yaml @@ -10,10 +10,12 @@ interfaces: device-type: dpdk mtu: 9000 description: "LAG #1" + sflow: true GigabitEthernet3/0/1: device-type: dpdk mtu: 9000 description: "LAG #2" + sflow: false HundredGigabitEthernet12/0/0: device-type: dpdk @@ -163,3 +165,8 @@ acls: icmp-code: any - description: "Deny any IPv4 or IPv6" action: deny + +sflow: + header-bytes: 128 + polling-interval: 30 + sample-rate: 1000 diff --git a/vppcfg/schema.yaml b/vppcfg/schema.yaml index 7e60b2f..0a95dae 100644 --- a/vppcfg/schema.yaml +++ b/vppcfg/schema.yaml @@ -6,6 +6,7 @@ vxlan_tunnels: map(include('vxlan'),key=str(matches='vxlan_tunnel[0-9]+'),requir taps: map(include('tap'),key=str(matches='tap[0-9]+'),required=False) prefixlists: map(include('prefixlist'),key=str(matches='[a-z][a-z0-9\-]+',min=1,max=64),required=False) acls: map(include('acl'),key=str(matches='[a-z][a-z0-9\-]+',min=1,max=56),required=False) +sflow: include('sflow',required=False) --- vxlan: description: str(exclude='\'"',len=64,required=False) @@ -57,6 +58,7 @@ interface: state: enum('up', 'down', required=False) mpls: bool(required=False) device-type: enum('dpdk', required=False) + sflow: bool(required=False) --- sub-interface: description: str(exclude='\'"',len=64,required=False) @@ -113,3 +115,8 @@ acl-term: acl: description: str(exclude='\'"',len=64,required=False) terms: list(include('acl-term'), min=1, max=100, required=True) +--- +sflow: + header-bytes: int(min=1,max=256,required=False) + polling-interval: int(min=5,max=600,required=False) + sample-rate: int(min=100,max=1000000,required=False) diff --git a/vppcfg/vpp/dumper.py b/vppcfg/vpp/dumper.py index 7463f3a..fa08d09 100644 --- a/vppcfg/vpp/dumper.py +++ b/vppcfg/vpp/dumper.py @@ -67,6 +67,7 @@ class Dumper(VPPApi): "taps": {}, "prefixlists": {}, "acls": {}, + "sflow": {}, } for idx, bond_iface in self.cache["bondethernets"].items(): bond = {"description": ""} @@ -301,9 +302,9 @@ class Dumper(VPPApi): acl_rule.srcport_or_icmptype_first ) else: - config_term["icmp-type"] = ( - f"{acl_rule.srcport_or_icmptype_first}-{maxval}" - ) + config_term[ + "icmp-type" + ] = f"{acl_rule.srcport_or_icmptype_first}-{maxval}" maxval = acl_rule.dstport_or_icmpcode_last if maxval > 255: @@ -316,9 +317,9 @@ class Dumper(VPPApi): acl_rule.dstport_or_icmpcode_first ) else: - config_term["icmp-code"] = ( - f"{acl_rule.dstport_or_icmpcode_first}-{maxval}" - ) + config_term[ + "icmp-code" + ] = f"{acl_rule.dstport_or_icmpcode_first}-{maxval}" elif acl_rule.proto in [6, 17]: if acl_rule.proto == 6: config_term["protocol"] = "tcp" @@ -332,9 +333,9 @@ class Dumper(VPPApi): acl_rule.srcport_or_icmptype_first ) else: - config_term["source-port"] = ( - f"{acl_rule.srcport_or_icmptype_first}-{acl_rule.srcport_or_icmptype_last}" - ) + config_term[ + "source-port" + ] = f"{acl_rule.srcport_or_icmptype_first}-{acl_rule.srcport_or_icmptype_last}" if ( acl_rule.dstport_or_icmpcode_first == acl_rule.dstport_or_icmpcode_last @@ -343,9 +344,9 @@ class Dumper(VPPApi): acl_rule.dstport_or_icmpcode_first ) else: - config_term["destination-port"] = ( - f"{acl_rule.dstport_or_icmpcode_first}-{acl_rule.dstport_or_icmpcode_last}" - ) + config_term[ + "destination-port" + ] = f"{acl_rule.dstport_or_icmpcode_first}-{acl_rule.dstport_or_icmpcode_last}" else: config_term["protocol"] = int(acl_rule.proto) @@ -353,4 +354,9 @@ class Dumper(VPPApi): config["acls"][aclname] = config_acl + config["sflow"] = self.cache["sflow"] + for hw_if_index in self.cache["interface_sflow"]: + vpp_iface = self.cache["interfaces"][hw_if_index] + config["interfaces"][vpp_iface.interface_name]["sflow"] = True + return config diff --git a/vppcfg/vpp/vppapi.py b/vppcfg/vpp/vppapi.py index 660667a..631deb1 100644 --- a/vppcfg/vpp/vppapi.py +++ b/vppcfg/vpp/vppapi.py @@ -130,6 +130,8 @@ class VPPApi: "taps": {}, "acls": {}, "acl_tags": {}, + "interface_sflow": {}, + "sflow": {}, } return True @@ -415,6 +417,33 @@ class VPPApi: for tap in api_response: self.cache["taps"][tap.sw_if_index] = tap + try: + self.logger.debug("Retrieving sFlow") + + api_response = self.vpp.api.sflow_sampling_rate_get() + if api_response: + self.cache["sflow"]["sampling-rate"] = api_response.sampling_N + api_response = self.vpp.api.sflow_polling_interval_get() + if api_response: + self.cache["sflow"]["polling-interval"] = api_response.polling_S + api_response = self.vpp.api.sflow_header_bytes_get() + if api_response: + self.cache["sflow"]["header-bytes"] = api_response.header_B + + api_response = self.vpp.api.sflow_interface_dump() + for iface in api_response: + self.cache["interface_sflow"][iface.hw_if_index] = True + except AttributeError as err: + self.logger.warning(f"sFlow API not found - missing plugin: {err}") + + self.logger.debug("Retrieving interface Unnumbered state") + api_response = self.vpp.api.ip_unnumbered_dump() + for iface in api_response: + self.cache["interface_unnumbered"][iface.sw_if_index] = iface.ip_sw_if_index + + self.logger.debug("Retrieving bondethernets") + api_response = self.vpp.api.sw_bond_interface_dump() + self.cache_read = True return self.cache_read