If VPP were to disconnect either the Stats Segment or the API endpoint, for example if it crashes and restarts, vpp-snmp-agent will not detect this. In such a situation, it will hold on to the stale stats and no longer receive interface updates. Before each run, send a control_ping() API request, and if that were to fail (for example with Broken Pipe, or Connection Refused), disconnect both API and Stats (in the vpp.disconnect() call, also invalidate the interface and LCP cache), and then fail the update. The Agent runner will then retry once per second until the connection (and control_ping()) succeeds. TESTED: - Start vpp-snmp-agent, it connects and starts up per normal. - Exit / Kill vpp - Upon the next update(), the control_ping() call will fail, causing the agent to disconnect - The agent will now loop: [ERROR ] agentx.agent - update : VPP API: [Errno 1] Sendall error: BrokenPipeError(32, 'Broken pipe'), retrying [WARNING ] agentx.agent - run : Update failed, last successful update was 1673345631.7658572 [INFO ] agentx.vppapi - connect : Connecting to VPP [ERROR ] agentx.agent - update : VPP API: Not connected, api definitions not available, retrying - Start VPP again, when its API endpoint is ready: [INFO ] agentx.vppapi - connect : Connecting to VPP [INFO ] agentx.vppapi - connect : VPP version is 23.02-rc0~199-gcfaf44020 [INFO ] agentx.vppapi - connect : Enabling VPP API interface events [DEBUG ] agentx.agent - update : VPP API: control_ping_reply(_0=24, context=12, retval=0, client_index=0, vpe_pid=705326) [INFO ] agentx.vppapi - get_ifaces : Requesting interfaces from VPP API [INFO ] agentx.vppapi - get_lcp : Requesting LCPs from VPP API - The agent resumes where it left off
145 lines
4.3 KiB
Python
145 lines
4.3 KiB
Python
"""
|
|
The functions in this file interact with the VPP API to retrieve certain
|
|
interface metadata.
|
|
"""
|
|
|
|
from vpp_papi import VPPApiClient, VPPApiJSONFiles
|
|
import os
|
|
import fnmatch
|
|
import logging
|
|
import socket
|
|
|
|
|
|
class NullHandler(logging.Handler):
|
|
def emit(self, record):
|
|
pass
|
|
|
|
|
|
logger = logging.getLogger("agentx.vppapi")
|
|
logger.addHandler(NullHandler())
|
|
|
|
|
|
class VPPApi:
|
|
def __init__(self, address="/run/vpp/api.sock", clientname="vppapi-client"):
|
|
self.address = address
|
|
self.connected = False
|
|
self.clientname = clientname
|
|
self.vpp = None
|
|
self.iface_dict = None
|
|
self.lcp_dict = None
|
|
|
|
def _sw_interface_event(self, event):
|
|
# NOTE(pim): this callback runs in a background thread, so we just clear the
|
|
# cached interfaces and LCPs here, subsequent call to get_ifaces() or get_lcp()
|
|
# will refresh them in the main thread.
|
|
logger.info(f"Clearing iface and LCP cache due to interface event")
|
|
self.iface_dict = None
|
|
self.lcp_dict = None
|
|
|
|
def _event_callback(self, msg_type_name, msg_type):
|
|
logger.debug(f"Received callback: {msg_type_name} => {msg_type}")
|
|
if msg_type_name == "sw_interface_event":
|
|
self._sw_interface_event(msg_type)
|
|
else:
|
|
logger.warning(f"Ignoring unkonwn event: {msg_type_name} => {msg_type}")
|
|
|
|
def connect(self):
|
|
if self.connected:
|
|
return True
|
|
|
|
vpp_json_dir = VPPApiJSONFiles.find_api_dir([])
|
|
vpp_jsonfiles = VPPApiJSONFiles.find_api_files(api_dir=vpp_json_dir)
|
|
if not vpp_jsonfiles:
|
|
logger.error("no json api files found")
|
|
return False
|
|
|
|
self.vpp = VPPApiClient(apifiles=vpp_jsonfiles, server_address=self.address)
|
|
self.vpp.register_event_callback(self._event_callback)
|
|
try:
|
|
logger.info("Connecting to VPP")
|
|
self.vpp.connect(self.clientname)
|
|
except:
|
|
return False
|
|
|
|
v = self.vpp.api.show_version()
|
|
logger.info("VPP version is %s" % v.version)
|
|
|
|
logger.info("Enabling VPP API interface events")
|
|
r = self.vpp.api.want_interface_events(enable_disable=True)
|
|
if r.retval != 0:
|
|
logger.error("Could not enable VPP API interface events, disconnecting")
|
|
self.disconnect()
|
|
return False
|
|
|
|
self.connected = True
|
|
return True
|
|
|
|
def disconnect(self):
|
|
if not self.connected:
|
|
return True
|
|
self.vpp.disconnect()
|
|
self.iface_dict = None
|
|
self.lcp_dict = None
|
|
self.connected = False
|
|
return True
|
|
|
|
def get_ifaces(self):
|
|
ret = {}
|
|
if not self.connected and not self.connect():
|
|
logger.warning("Can't connect to VPP API")
|
|
return ret
|
|
|
|
if type(self.iface_dict) is dict:
|
|
logger.debug("Returning cached interfaces")
|
|
return self.iface_dict
|
|
|
|
ret = {}
|
|
try:
|
|
logger.info("Requesting interfaces from VPP API")
|
|
iface_list = self.vpp.api.sw_interface_dump()
|
|
except Exception as e:
|
|
logger.error("VPP API communication error, disconnecting", e)
|
|
self.disconnect()
|
|
return ret
|
|
|
|
if not iface_list:
|
|
logger.error("Can't get interface list, disconnecting")
|
|
self.disconnect()
|
|
return ret
|
|
|
|
for iface in iface_list:
|
|
ret[iface.interface_name] = iface
|
|
|
|
self.iface_dict = ret
|
|
logger.debug(f"Caching interfaces: {ret}")
|
|
return self.iface_dict
|
|
|
|
def get_lcp(self):
|
|
ret = {}
|
|
if not self.connected and not self.connect():
|
|
logger.warning("Can't connect to VPP API")
|
|
return ret
|
|
|
|
if type(self.lcp_dict) is dict:
|
|
logger.debug("Returning cached LCPs")
|
|
return self.lcp_dict
|
|
|
|
try:
|
|
logger.info("Requesting LCPs from VPP API")
|
|
lcp_list = self.vpp.api.lcp_itf_pair_get()
|
|
except Exception as e:
|
|
logger.error("VPP communication error, disconnecting", e)
|
|
self.disconnect()
|
|
return ret
|
|
|
|
if not lcp_list:
|
|
logger.error("Can't get LCP list")
|
|
return ret
|
|
|
|
for lcp in lcp_list[1]:
|
|
ret[lcp.host_if_name] = lcp
|
|
|
|
self.lcp_dict = ret
|
|
logger.debug(f"Caching LCPs: {ret}")
|
|
return self.lcp_dict
|