Merge pull request #19 from pimvanpelt/sflow

sFlow plugin support
This commit is contained in:
Pim van Pelt
2024-10-28 16:19:53 +01:00
committed by GitHub
8 changed files with 163 additions and 2 deletions

View File

@ -314,14 +314,15 @@ exist as a PHY in VPP (ie. `HundredGigabitEthernet12/0/0`) or as a specified `Bo
target interface.
* ***state***: An optional string that configures the link admin state, either `up` or `down`.
If it is not specified, the link is considered admin 'up'.
* ***device-type***: An optional interface type in VPP. Currently the only supported vlaue is
* ***device-type***: An optional interface type in VPP. Currently the only supported value is
`dpdk`, and it is used to generate correct mock interfaces if the `--novpp` flag is used.
* ***mpls***: An optional boolean that configures MPLS on the interface or sub-interface. The
default value is `false`, if the field is not specified, which means MPLS will not be enabled.
* ***unnumbered***: An interface name from which this (sub-)interface will borrow IPv4 and
IPv6 addresses. The interface can be either a loopback, an interface or a sub-interface. if
the interface is unnumbered, it can't be L2 and it can't have addresses.
* ***sflow***: An optional boolean value, when true will enable sFlow collection on this
interface. sFlow collection is only supported on PHY (physical) interfaces.
Further, top-level interfaces, that is to say those that do not have an encapsulation, are permitted
to have any number of sub-interfaces specified by `subid`, an integer between [0,2G), which further
@ -510,3 +511,28 @@ interfaces:
The configuration here is tolerant of either a singleton (a literal string referring to the one
ACL that must be applied), or a _list_ of strings to more than one ACL, in which case they will
be tested in order (with a first-match return value).
### sFlow collection
VPP supports sFlow collection using the `sFlow` plugin. The collection of samples occurs only on
physical interfaces (and will include samples for any sub-interfaces or tunnels created), and is
meant to be enabled on all interfaces (using the `sflow: true` key, see the Interfaces definition
above) that are passing traffic. The defaults in the plugin are sensible and should not need to
be changed.
The following configuration elements are provided for the plugin:
* **sample-rate**: Capture 1-in-N packets. Defaults to 10000. A good value is the interface
bitrate divided by 1000, so for GigabitEthernet choose 1000, for TenGigabitEthernet choose
10000 (the default).
* **polling-interval**: Determines the period of interface byte and packet counter reads. This
information will be added to the sFlow collector data automatically.
* **header-bytes**: The number of bytes taken from the IP packet in the sample. By default,
128 bytes are taken. This value should not be changed in normal operation.
```
sflow:
sample-rate: 10000
polling-interval: 20
header-bytes: 128
```

View File

@ -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):

30
vppcfg/config/sflow.py Normal file
View File

@ -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

View File

@ -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
sampling-rate: 1000

View File

@ -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)
sampling-rate: int(min=100,max=1000000,required=False)

View File

@ -67,6 +67,7 @@ class Dumper(VPPApi):
"taps": {},
"prefixlists": {},
"acls": {},
"sflow": {},
}
for idx, bond_iface in self.cache["bondethernets"].items():
bond = {"description": ""}
@ -141,6 +142,8 @@ class Dumper(VPPApi):
i["addresses"] = self.cache["interface_addresses"][
iface.sw_if_index
]
if iface.sw_if_index in self.cache["interface_mpls"]:
i["mpls"] = self.cache["interface_mpls"][iface.sw_if_index]
if iface.sw_if_index in self.cache["l2xcs"]:
l2xc = self.cache["l2xcs"][iface.sw_if_index]
i["l2xc"] = self.cache["interfaces"][
@ -353,4 +356,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

View File

@ -967,6 +967,9 @@ class Reconciler:
if not self.__sync_mpls_state():
self.logger.warning("Could not sync interface MPLS state in VPP")
ret = False
if not self.__sync_sflow_state():
self.logger.warning("Could not sync interface sFlow state in VPP")
ret = False
if not self.__sync_admin_state():
self.logger.warning("Could not sync interface adminstate in VPP")
ret = False
@ -1311,6 +1314,55 @@ class Reconciler:
ret = False
return ret
def __sync_sflow_state(self):
"""Synchronize the VPP Dataplane configuration and phy sFlow state"""
if "sflow" in self.cfg and self.vpp.cache["sflow"]:
if "header-bytes" in self.cfg["sflow"]:
if (
self.vpp.cache["sflow"]["header-bytes"]
!= self.cfg["sflow"]["header-bytes"]
):
cli = f"sflow header-bytes {self.cfg['sflow']['header-bytes']}"
self.cli["sync"].append(cli)
if "polling-interval" in self.cfg["sflow"]:
if (
self.vpp.cache["sflow"]["polling-interval"]
!= self.cfg["sflow"]["polling-interval"]
):
cli = f"sflow polling-interval {self.cfg['sflow']['polling-interval']}"
self.cli["sync"].append(cli)
if "sampling-rate" in self.cfg["sflow"]:
if (
self.vpp.cache["sflow"]["sampling-rate"]
!= self.cfg["sflow"]["sampling-rate"]
):
cli = f"sflow sampling-rate {self.cfg['sflow']['sampling-rate']}"
self.cli["sync"].append(cli)
for ifname in interface.get_interfaces(self.cfg):
vpp_ifname, config_iface = interface.get_by_name(self.cfg, ifname)
try:
config_sflow = config_iface["sflow"]
except KeyError:
config_sflow = False
vpp_sflow = False
if vpp_ifname in self.vpp.cache["interface_names"]:
hw_if_index = self.vpp.cache["interface_names"][vpp_ifname]
try:
vpp_sflow = self.vpp.cache["interface_sflow"][hw_if_index]
except KeyError:
pass
if vpp_sflow != config_sflow:
if config_sflow:
cli = f"sflow enable {vpp_ifname}"
else:
cli = f"sflow enable-disable {vpp_ifname} disable"
self.cli["sync"].append(cli)
return True
def __sync_mpls_state(self):
"""Synchronize the VPP Dataplane configuration for interface and loopback MPLS state"""
for ifname in loopback.get_loopbacks(self.cfg) + interface.get_interfaces(

View File

@ -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