-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[LLDP] Add lldpmgrd Daemon to Manage LLDP Configuration #1428
Changes from all commits
5dfe6e8
0211ac2
2ca0e3c
eb06a41
e817b0a
7edeacb
1df26db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,49 @@ | ||
FROM docker-config-engine | ||
|
||
COPY \ | ||
{% for deb in docker_lldp_sv2_debs.split(' ') -%} | ||
debs/{{ deb }}{{' '}} | ||
{%- endfor -%} | ||
debs/ | ||
# Make apt-get non-interactive | ||
ENV DEBIAN_FRONTEND=noninteractive | ||
|
||
COPY python-wheels /python-wheels | ||
# Update apt's cache of available packages | ||
RUN apt-get update | ||
|
||
## Make apt-get non-interactive | ||
ENV DEBIAN_FRONTEND=noninteractive | ||
# Install dependencies | ||
RUN apt-get install -y python-pip libbsd0 libevent-2.0-5 libjansson4 libwrap0 libxml2 libpci3 libperl5.20 libpython2.7 | ||
|
||
{% 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 apt-get update && apt-get install -y python-pip libbsd0 libevent-2.0-5 libjansson4 libwrap0 libxml2 libpci3 libperl5.20 | ||
# 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 %} | ||
|
||
# Pre-install the fundamental packages | ||
# Install Python SwSS SDK | ||
# Install LLDP Sync Daemon | ||
{% 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 %} | ||
|
||
RUN dpkg -i \ | ||
{% for deb in docker_lldp_sv2_debs.split(' ') -%} | ||
debs/{{ deb }}{{' '}} | ||
# Install locally-built Python wheel dependencies | ||
{%- for whl in docker_lldp_sv2_whls.split(' ') %} | ||
RUN pip install /python-wheels/{{ whl }} | ||
{%- endfor %} | ||
{% endif %} | ||
|
||
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 | ||
# 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"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as I noted in my previous comments, we should also listen CFG_PORT_TABLE and CFG_NEIGBHRO_TABLE in case port alias change or neighbor info change, then we can send updated information to the neighbor. We do not need to do this now, but we should mark it as TODO in the comment. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added a TODO in the file header comment (lines 12-13). I think you missed it. |
||
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() |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you need LIBSWSSCOMMON here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it uses python-binding of libswsscommon for getting table notification from redis db. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok |
||
$(DOCKER_LLDP_SV2)_PYTHON_WHEELS += $(DBSYNCD_PY2) | ||
$(DOCKER_LLDP_SV2)_LOAD_DOCKERS += $(DOCKER_CONFIG_ENGINE) | ||
SONIC_DOCKER_IMAGES += $(DOCKER_LLDP_SV2) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about removing of an interface. Do you catch such case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. We decided this was unnecessary.