[LLDP] Add lldpmgrd Daemon to Manage LLDP Configuration (#1428)

This commit is contained in:
Joe LeVeque 2018-03-02 16:46:22 -08:00 committed by GitHub
parent eaea792d47
commit c689253b3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 245 additions and 86 deletions

View File

@ -1,37 +1,49 @@
FROM docker-config-engine
COPY \
{% for deb in docker_lldp_sv2_debs.split(' ') -%}
debs/{{ deb }}{{' '}}
{%- endfor -%}
debs/
COPY python-wheels /python-wheels
## Make apt-get non-interactive
# Make apt-get non-interactive
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y python-pip libbsd0 libevent-2.0-5 libjansson4 libwrap0 libxml2 libpci3 libperl5.20
# Update apt's cache of available packages
RUN apt-get update
# Pre-install the fundamental packages
# Install Python SwSS SDK
# Install LLDP Sync Daemon
# Install dependencies
RUN apt-get install -y python-pip libbsd0 libevent-2.0-5 libjansson4 libwrap0 libxml2 libpci3 libperl5.20 libpython2.7
RUN dpkg -i \
{% for deb in docker_lldp_sv2_debs.split(' ') -%}
debs/{{ deb }}{{' '}}
{% if docker_lldp_sv2_debs.strip() -%}
# Copy locally-built Debian package dependencies
{%- for deb in docker_lldp_sv2_debs.split(' ') %}
COPY debs/{{ deb }} /debs/
{%- endfor %}
RUN pip install /python-wheels/swsssdk-2.0.1-py2-none-any.whl && \
pip install /python-wheels/sonic_d-2.0.0-py2-none-any.whl && \
apt-get remove -y python-pip && \
apt-get purge -y && apt-get autoclean -y && apt-get autoremove -y && \
rm -rf /debs /python-wheels ~/.cache
# Install locally-built Debian packages and implicitly install their dependencies
{%- for deb in docker_lldp_sv2_debs.split(' ') %}
RUN dpkg_apt() { [ -f $1 ] && { dpkg -i $1 || apt-get -y install -f; } || return 1; }; dpkg_apt /debs/{{ deb }}
{%- endfor %}
{%- endif %}
{% if docker_lldp_sv2_whls.strip() -%}
# Copy locally-built Python wheel dependencies
{%- for whl in docker_lldp_sv2_whls.split(' ') %}
COPY python-wheels/{{ whl }} /python-wheels/
{%- endfor %}
# Install locally-built Python wheel dependencies
{%- for whl in docker_lldp_sv2_whls.split(' ') %}
RUN pip install /python-wheels/{{ whl }}
{%- endfor %}
{% endif %}
# Clean up
RUN apt-get remove -y python-pip
RUN apt-get clean -y
RUN apt-get autoclean -y
RUN apt-get autoremove -y
RUN rm -rf /debs /python-wheels ~/.cache
COPY ["start.sh", "/usr/bin/"]
COPY ["supervisord.conf", "/etc/supervisor/conf.d/"]
COPY ["reconfigure.sh", "/usr/bin/"]
COPY ["lldpd.conf.j2", "/usr/share/sonic/templates/"]
COPY ["lldpd", "/etc/default/"]
COPY ["lldpmgrd", "/usr/bin/"]
ENTRYPOINT ["/usr/bin/supervisord"]

199
dockers/docker-lldp-sv2/lldpmgrd Executable file
View File

@ -0,0 +1,199 @@
#!/usr/bin/env python
"""
lldpmgrd
LLDP manager daemon for SONiC
Daemon which listens for changes in the PORT table of the State DB
and updates LLDP configuration accordingly for that port by calling
lldpcli.
TODO: Also listen for changes in DEVICE_NEIGHBOR and PORT tables in
Config DB and update LLDP config upon changes.
"""
try:
import os
import signal
import subprocess
import sys
import syslog
from swsscommon import swsscommon
except ImportError as err:
raise ImportError("%s - required module not found" % str(err))
VERSION = "1.0"
SYSLOG_IDENTIFIER = "lldpmgrd"
# ========================== Syslog wrappers ==========================
def log_info(msg):
syslog.openlog(SYSLOG_IDENTIFIER)
syslog.syslog(syslog.LOG_INFO, msg)
syslog.closelog()
def log_warning(msg):
syslog.openlog(SYSLOG_IDENTIFIER)
syslog.syslog(syslog.LOG_WARNING, msg)
syslog.closelog()
def log_error(msg):
syslog.openlog(SYSLOG_IDENTIFIER)
syslog.syslog(syslog.LOG_ERR, msg)
syslog.closelog()
# ========================== Signal Handling ==========================
def signal_handler(sig, frame):
if sig == signal.SIGHUP:
log_info("Caught SIGHUP - ignoring...")
return
elif sig == signal.SIGINT:
log_info("Caught SIGINT - exiting...")
sys.exit(128 + sig)
elif sig == signal.SIGTERM:
log_info("Caught SIGTERM - exiting...")
sys.exit(128 + sig)
else:
log_warning("Caught unhandled signal '" + sig + "'")
# ============================== Classes ==============================
class LldpManager(object):
"""
Class which subscribes to notifications of changes in the PORT table of
the Redis State database and updates LLDP configuration accordingly for
that port by calling lldpcli.
Attributes:
state_db: Handle to Redis State database via swsscommon lib
config_db: Handle to Redis Config database via swsscommon lib
"""
REDIS_HOSTNAME = "localhost"
REDIS_PORT = 6379
REDIS_TIMEOUT_USECS = 0
def __init__(self):
# Open a handle to the State database
self.state_db = swsscommon.DBConnector(swsscommon.STATE_DB,
self.REDIS_HOSTNAME,
self.REDIS_PORT,
self.REDIS_TIMEOUT_USECS)
# Open a handle to the Config database
self.config_db = swsscommon.DBConnector(swsscommon.CONFIG_DB,
self.REDIS_HOSTNAME,
self.REDIS_PORT,
self.REDIS_TIMEOUT_USECS)
def update_lldp_config_for_port(self, port_name):
"""
For port `port_name`, look up the neighboring device's hostname and
corresponding port alias in the Config database, then form the
appropriate lldpcli configuration command and run it.
"""
TABLE_SEPARATOR = "|"
# Retrieve all entires for this port from the Port table
port_table = swsscommon.Table(self.config_db, swsscommon.CFG_PORT_TABLE_NAME, TABLE_SEPARATOR)
(status, fvp) = port_table.get(port_name)
if status:
# Convert list of tuples to a dictionary
port_table_dict = dict(fvp)
# Get the port alias. If None or empty string, use port name instead
port_alias = port_table_dict.get("alias")
if not port_alias:
log_info("Unable to retrieve port alias for port '{}'. Using port name instead.".format(port_name))
port_alias = port_name
else:
log_error("Port '{}' not found in {} table in Config DB. Using port name instead of port alias.".format(port_name, swsscommon.CFG_PORT_TABLE_NAME))
port_alias = port_name
lldpcli_cmd = "lldpcli configure ports {0} lldp portidsubtype local {1}".format(port_name, port_alias)
# Retrieve all entires for this port from the Device Neighbor table
device_neighbor_table = swsscommon.Table(self.config_db, swsscommon.CFG_DEVICE_NEIGHBOR_TABLE_NAME, TABLE_SEPARATOR)
(status, fvp) = device_neighbor_table.get(port_name)
if status:
# Convert list of tuples to a dictionary
device_neighbor_table_dict = dict(fvp)
# Get neighbor host name and port name
neighbor_hostname = device_neighbor_table_dict.get("name")
neighbor_portname = device_neighbor_table_dict.get("port")
# If we sucessfully obtained the neighbor's host name and port name, append a port description to the command
if neighbor_hostname and neighbor_portname:
lldpcli_cmd += " description {0}:{1}".format(neighbor_hostname, neighbor_portname)
else:
if not neighbor_hostname:
log_info("Failed to retrieve neighbor host name for port '{}'. Not adding port description.".format(port_name))
if not neighbor_portname:
log_info("Failed to retrieve neighbor port name for port '{}'. Not adding port description.".format(port_name))
else:
log_info("Unable to retrieve neighbor information for port '{}'. Not adding port description.".format(port_name))
log_info("Running command: '{}'".format(lldpcli_cmd))
proc = subprocess.Popen(lldpcli_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdout, stderr) = proc.communicate()
if proc.returncode != 0:
log_error("Error running command '{}': {}".format(cmd, stderr))
def run(self):
"""
Infinite loop. Subscribes to notifications of changes in the PORT table
of the Redis State database. When we are notified of the creation of an
interface, update LLDP configuration accordingly.
"""
# Subscribe to PORT table notifications in the State DB
sel = swsscommon.Select()
sst = swsscommon.SubscriberStateTable(self.state_db, swsscommon.STATE_PORT_TABLE_NAME)
sel.addSelectable(sst)
# Listen indefinitely for changes to the PORT table in the State DB
while True:
(state, c, fd) = sel.select()
if state != swsscommon.Select.OBJECT:
log_warning("sel.select() did not return swsscommon.Select.OBJECT")
continue
(key, op, fvp) = sst.pop()
fvp_dict = dict(fvp)
if op == "SET" and fvp_dict.get("state") == "ok":
self.update_lldp_config_for_port(key)
# ============================= Functions =============================
def main():
log_info("Starting up...")
if not os.geteuid() == 0:
log_error("Must be root to run this daemon")
print "Error: Must be root to run this daemon"
sys.exit(1)
# Register our signal handlers
signal.signal(signal.SIGHUP, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
# Instantiate a LldpManager object
lldpmgr = LldpManager()
lldpmgr.run()
if __name__ == "__main__":
main()

View File

@ -1,52 +0,0 @@
#!/bin/bash
set -e
# TODO: Listen to state database when it is ready
interfaces=$(sonic-cfggen -d -v "PORT.keys() | join(' ')")
function wait_until_if_exists
{
if=$1
while [ ! -L /sys/class/net/"$if" ] ;
do
sleep 1
done
echo interface "$if" is created
}
function wait_until_if_not_exists
{
if=$1
while [ -L /sys/class/net/"$if" ] ;
do
sleep 1
done
echo interface "$if" is destroyed
}
while /bin/true ;
do
# wait until all interfaces are created
echo Wait until all interfaces are created
for i in $interfaces
do
wait_until_if_exists $i
done
echo Wait 10 seconds while lldpd finds new interfaces
sleep 10
# apply lldpd configuration
echo Apply lldpd configuration
lldpcli -c /etc/lldpd.conf
# wait until all interfaces are destroyed
echo Wait until all ifaces are destroyed
for i in $interfaces
do
wait_until_if_not_exists $i
done
done

View File

@ -9,5 +9,5 @@ rm -f /var/run/rsyslogd.pid
supervisorctl start rsyslogd
supervisorctl start lldpd
supervisorctl start lldpd-conf-reload
supervisorctl start lldp-syncd
supervisorctl start lldpmgrd

View File

@ -26,15 +26,7 @@ stderr_logfile=syslog
# - `-ddd` means to stay in foreground, log warnings and info to console
# - `-dddd` means to stay in foreground, log all to console
command=/usr/sbin/lldpd -d -I Ethernet*,eth*
priority=100
autostart=false
autorestart=false
stdout_logfile=syslog
stderr_logfile=syslog
[program:lldpd-conf-reload]
command=/usr/bin/reconfigure.sh
priority=150
priority=3
autostart=false
autorestart=false
stdout_logfile=syslog
@ -42,7 +34,15 @@ stderr_logfile=syslog
[program:lldp-syncd]
command=/usr/bin/env python2 -m lldp_syncd
priority=200
priority=4
autostart=false
autorestart=false
stdout_logfile=syslog
stderr_logfile=syslog
[program:lldpmgrd]
command=/usr/bin/lldpmgrd
priority=5
autostart=false
autorestart=false
stdout_logfile=syslog

View File

@ -2,7 +2,7 @@
DOCKER_LLDP_SV2 = docker-lldp-sv2.gz
$(DOCKER_LLDP_SV2)_PATH = $(DOCKERS_PATH)/docker-lldp-sv2
$(DOCKER_LLDP_SV2)_DEPENDS += $(LLDPD)
$(DOCKER_LLDP_SV2)_DEPENDS += $(LLDPD) $(LIBSWSSCOMMON) $(PYTHON_SWSSCOMMON)
$(DOCKER_LLDP_SV2)_PYTHON_WHEELS += $(DBSYNCD_PY2)
$(DOCKER_LLDP_SV2)_LOAD_DOCKERS += $(DOCKER_CONFIG_ENGINE)
SONIC_DOCKER_IMAGES += $(DOCKER_LLDP_SV2)