Following on from the previous post, I found myself with a few hours to myself tonight. The estrogen in the house went out as a group so I managed to do a bit more coding on the FreshPorts listening daemon.
Sometime this past week, I managed to add some triggers to the database that invoke the LISTEN command each time a port is updated. Here is the trigger I created:
CREATE OR REPLACE FUNCTION ports_clear_cache() RETURNS TRIGGER AS $$ -- when a port changes, add an entry to the cache clearing table DECLARE l_cache_clearing_ports_id int8; l_port text; l_category text; BEGIN IF TG_OP = 'UPDATE' THEN -- check first to see if this entry already exists SELECT id INTO l_cache_clearing_ports_id FROM cache_clearing_ports WHERE port_id = NEW.id; IF NOT FOUND THEN SELECT category, name INTO l_category, l_port FROM ports_all WHERE id = NEW.id; INSERT INTO cache_clearing_ports (port_id, category, port) VALUES (NEW.id, l_category, l_port); END IF; NOTIFY port_updated; END IF; RETURN NEW; END $$ LANGUAGE 'plpgsql'; DROP TRIGGER ports_clear_cache ON ports; CREATE TRIGGER ports_clear_cache AFTER UPDATE on ports FOR EACH ROW EXECUTE PROCEDURE ports_clear_cache();
The cache_clearing_ports table looks like this:
create table cache_clearing_ports ( id serial not null, port_id integer not null, category text not null, port text not null, date_added timestamp without time zone not null default CURRENT_TIMESTAMP, primary key (id) ); alter table cache_clearing_ports add foreign key (port_id) references ports (id) on update cascade on delete cascade;
When the FreshPorts listen daemon gets a NOTIFY, it will look at that table, remove the entries from the cache, and delete the rows it just processed.
Here is where we are with the listen daemon:
#!/usr/bin/env python # # This program listens for events on the database and processes them # # # configuration items # DSN = 'dbname=freshports.org user=dan' import sys, psycopg, select import syslog def RemoveCacheEntry(): syslog.syslog(syslog.LOG_NOTICE, 'removing cache entries') def ProcessPortsMoved(): syslog.syslog(syslog.LOG_NOTICE, 'processing ports/MOVED') def ProcessPortsUpdating(): syslog.syslog(syslog.LOG_NOTICE, 'processing ports/UPDATING') def ProcessVUXML(): syslog.syslog(syslog.LOG_NOTICE, 'processing ports/security/portaudit/vuln.xml') syslog.openlog('fp-listen') syslog.syslog(syslog.LOG_NOTICE, 'Starting up') print "Opening connection using dns:", DSN conn = psycopg.connect(DSN) conn.autocommit(1) curs = conn.cursor() curs.execute("SELECT name, script_name FROM listen_for ORDER BY id") listens_for = curs.fetchall() listens = dict() for listen in listens_for: curs.execute("LISTEN %s" % listen[0]) listens[listen[0]] = listen[1] print listen print "Now listening..." while 1: select.select([curs],[],[])==([],[],[]) curs.execute("SELECT 1") notifies = curs.notifies() for n in notifies: print "got %s" % n[0] print "this is index %s" % listens[n[0]] # in real life, do something with each... print "got %s and I need to call %s" % (n[0], listens[n[0]]) syslog.syslog(syslog.LOG_NOTICE, "got %s and I need to call %s" % (n[0], listens[n[0]])) if listens.has_key(n[0]): syslog.syslog(syslog.LOG_NOTICE, 'found that key') if listens[n[0]] == 'listen_port': RemoveCacheEntry() elif listens[n[0]] == 'listen_ports_moved': ProcessPortsMoved() elif listens[n[0]] == 'listen_ports_updating': ProcessPortsUpdating() elif listens[n[0]] == 'listen_vuxml': ProcessVUXML() else: syslog.syslog(syslog.LOG_ERR, "Code does not know what to do when '%s' is found." % n[0]) else: syslog.syslog(syslog.LOG_NOTICE, 'no such key!') logging.error('terminating')
Here is some sample output from /var/log/messages as a commit is processed:
Sep 2 20:34:45 polo fp-listen: got port_updated and I need to call listen_port Sep 2 20:34:45 polo fp-listen: found that key Sep 2 20:34:45 polo fp-listen: removing cache entries Sep 2 20:34:46 polo fp-listen: got port_updated and I need to call listen_port Sep 2 20:34:46 polo fp-listen: found that key Sep 2 20:34:46 polo fp-listen: removing cache entries
I’ll let this run in BETA for a while before I starting fleshing out the stubs at the top of the script. I’ll start by completing RemoveCacheEntry() first.
Watch this space.