[LLDP] Add lldpmgrd Daemon to Manage LLDP Configuration (#1428)
This commit is contained in:
parent
eaea792d47
commit
c689253b3f
@ -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
199
dockers/docker-lldp-sv2/lldpmgrd
Executable 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()
|
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user