diff options
Diffstat (limited to 'pynslcd/cache.py')
-rw-r--r-- | pynslcd/cache.py | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/pynslcd/cache.py b/pynslcd/cache.py new file mode 100644 index 0000000..8fcb813 --- /dev/null +++ b/pynslcd/cache.py @@ -0,0 +1,312 @@ + +# cache.py - caching layer for pynslcd +# +# Copyright (C) 2012 Arthur de Jong +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +import datetime +import itertools +import os +import sys + +import sqlite3 + + +# TODO: probably create a config table + +# FIXME: store the cache in the right place and make it configurable +filename = '/var/run/nslcd/cache.sqlite' +dirname = os.path.dirname(filename) +if not os.path.isdir(dirname): + os.mkdir(dirname) +con = sqlite3.connect(filename, + detect_types=sqlite3.PARSE_DECLTYPES, check_same_thread=False) +con.row_factory = sqlite3.Row + +# FIXME: have some way to remove stale entries from the cache if all items from LDAP are queried (perhas use TTL from all request) + +# set up the database +con.executescript(''' + + -- store temporary tables in memory + PRAGMA temp_store = MEMORY; + + -- disable sync() on database (corruption on disk failure) + PRAGMA synchronous = OFF; + + -- put journal in memory (corruption if crash during transaction) + PRAGMA journal_mode = MEMORY; + + -- tables for alias cache + CREATE TABLE IF NOT EXISTS `alias_cache` + ( `cn` TEXT PRIMARY KEY COLLATE NOCASE, + `mtime` TIMESTAMP NOT NULL ); + CREATE TABLE IF NOT EXISTS `alias_1_cache` + ( `alias` TEXT NOT NULL COLLATE NOCASE, + `rfc822MailMember` TEXT NOT NULL, + FOREIGN KEY(`alias`) REFERENCES `alias_cache`(`cn`) + ON DELETE CASCADE ON UPDATE CASCADE ); + CREATE INDEX IF NOT EXISTS `alias_1_idx` ON `alias_1_cache`(`alias`); + + -- table for ethernet cache + CREATE TABLE IF NOT EXISTS `ether_cache` + ( `cn` TEXT NOT NULL COLLATE NOCASE, + `macAddress` TEXT NOT NULL COLLATE NOCASE, + `mtime` TIMESTAMP NOT NULL, + UNIQUE (`cn`, `macAddress`) ); + + -- table for group cache + CREATE TABLE IF NOT EXISTS `group_cache` + ( `cn` TEXT PRIMARY KEY, + `userPassword` TEXT, + `gidNumber` INTEGER NOT NULL UNIQUE, + `mtime` TIMESTAMP NOT NULL ); + CREATE TABLE IF NOT EXISTS `group_3_cache` + ( `group` TEXT NOT NULL, + `memberUid` TEXT NOT NULL, + FOREIGN KEY(`group`) REFERENCES `group_cache`(`cn`) + ON DELETE CASCADE ON UPDATE CASCADE ); + CREATE INDEX IF NOT EXISTS `group_3_idx` ON `group_3_cache`(`group`); + + -- tables for host cache + CREATE TABLE IF NOT EXISTS `host_cache` + ( `cn` TEXT PRIMARY KEY COLLATE NOCASE, + `mtime` TIMESTAMP NOT NULL ); + CREATE TABLE IF NOT EXISTS `host_1_cache` + ( `host` TEXT NOT NULL COLLATE NOCASE, + `cn` TEXT NOT NULL COLLATE NOCASE, + FOREIGN KEY(`host`) REFERENCES `host_cache`(`cn`) + ON DELETE CASCADE ON UPDATE CASCADE ); + CREATE INDEX IF NOT EXISTS `host_1_idx` ON `host_1_cache`(`host`); + CREATE TABLE IF NOT EXISTS `host_2_cache` + ( `host` TEXT NOT NULL COLLATE NOCASE, + `ipHostNumber` TEXT NOT NULL, + FOREIGN KEY(`host`) REFERENCES `host_cache`(`cn`) + ON DELETE CASCADE ON UPDATE CASCADE ); + CREATE INDEX IF NOT EXISTS `host_2_idx` ON `host_2_cache`(`host`); + + -- FIXME: this does not work as entries are never removed from the cache + CREATE TABLE IF NOT EXISTS `netgroup_cache` + ( `cn` TEXT NOT NULL, + `member` TEXT NOT NULL, + `mtime` TIMESTAMP NOT NULL, + UNIQUE (`cn`, `member`) ); + + -- tables for network cache + CREATE TABLE IF NOT EXISTS `network_cache` + ( `cn` TEXT PRIMARY KEY COLLATE NOCASE, + `mtime` TIMESTAMP NOT NULL ); + CREATE TABLE IF NOT EXISTS `network_1_cache` + ( `network` TEXT NOT NULL COLLATE NOCASE, + `cn` TEXT NOT NULL COLLATE NOCASE, + FOREIGN KEY(`network`) REFERENCES `network_cache`(`cn`) + ON DELETE CASCADE ON UPDATE CASCADE ); + CREATE INDEX IF NOT EXISTS `network_1_idx` ON `network_1_cache`(`network`); + CREATE TABLE IF NOT EXISTS `network_2_cache` + ( `network` TEXT NOT NULL, + `ipNetworkNumber` TEXT NOT NULL, + FOREIGN KEY(`network`) REFERENCES `network_cache`(`cn`) + ON DELETE CASCADE ON UPDATE CASCADE ); + CREATE INDEX IF NOT EXISTS `network_2_idx` ON `network_2_cache`(`network`); + + -- table for passwd cache + CREATE TABLE IF NOT EXISTS `passwd_cache` + ( `uid` TEXT PRIMARY KEY, + `userPassword` TEXT, + `uidNumber` INTEGER NOT NULL UNIQUE, + `gidNumber` INTEGER NOT NULL, + `gecos` TEXT, + `homeDirectory` TEXT, + `loginShell` TEXT, + `mtime` TIMESTAMP NOT NULL ); + + -- table for protocol cache + CREATE TABLE IF NOT EXISTS `protocol_cache` + ( `cn` TEXT PRIMARY KEY, + `ipProtocolNumber` INTEGER NOT NULL, + `mtime` TIMESTAMP NOT NULL ); + CREATE TABLE IF NOT EXISTS `protocol_1_cache` + ( `protocol` TEXT NOT NULL, + `cn` TEXT NOT NULL, + FOREIGN KEY(`protocol`) REFERENCES `protocol_cache`(`cn`) + ON DELETE CASCADE ON UPDATE CASCADE ); + CREATE INDEX IF NOT EXISTS `protocol_1_idx` ON `protocol_1_cache`(`protocol`); + + -- table for rpc cache + CREATE TABLE IF NOT EXISTS `rpc_cache` + ( `cn` TEXT PRIMARY KEY, + `oncRpcNumber` INTEGER NOT NULL, + `mtime` TIMESTAMP NOT NULL ); + CREATE TABLE IF NOT EXISTS `rpc_1_cache` + ( `rpc` TEXT NOT NULL, + `cn` TEXT NOT NULL, + FOREIGN KEY(`rpc`) REFERENCES `rpc_cache`(`cn`) + ON DELETE CASCADE ON UPDATE CASCADE ); + CREATE INDEX IF NOT EXISTS `rpc_1_idx` ON `rpc_1_cache`(`rpc`); + + -- tables for service cache + CREATE TABLE IF NOT EXISTS `service_cache` + ( `cn` TEXT NOT NULL, + `ipServicePort` INTEGER NOT NULL, + `ipServiceProtocol` TEXT NOT NULL, + `mtime` TIMESTAMP NOT NULL, + UNIQUE (`ipServicePort`, `ipServiceProtocol`) ); + CREATE TABLE IF NOT EXISTS `service_1_cache` + ( `ipServicePort` INTEGER NOT NULL, + `ipServiceProtocol` TEXT NOT NULL, + `cn` TEXT NOT NULL, + FOREIGN KEY(`ipServicePort`) REFERENCES `service_cache`(`ipServicePort`) + ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY(`ipServiceProtocol`) REFERENCES `service_cache`(`ipServiceProtocol`) + ON DELETE CASCADE ON UPDATE CASCADE ); + CREATE INDEX IF NOT EXISTS `service_1_idx1` ON `service_1_cache`(`ipServicePort`); + CREATE INDEX IF NOT EXISTS `service_1_idx2` ON `service_1_cache`(`ipServiceProtocol`); + + -- table for shadow cache + CREATE TABLE IF NOT EXISTS `shadow_cache` + ( `uid` TEXT PRIMARY KEY, + `userPassword` TEXT, + `shadowLastChange` INTEGER, + `shadowMin` INTEGER, + `shadowMax` INTEGER, + `shadowWarning` INTEGER, + `shadowInactive` INTEGER, + `shadowExpire` INTEGER, + `shadowFlag` INTEGER, + `mtime` TIMESTAMP NOT NULL ); + + ''') + + +class Query(object): + + def __init__(self, query, parameters=None): + self.query = query + self.wheres = [] + self.parameters = [] + if parameters: + for k, v in parameters.items(): + self.add_where('`%s` = ?' % k, [v]) + + def add_query(self, query): + self.query += ' ' + query + + def add_where(self, where, parameters): + self.wheres.append(where) + self.parameters += parameters + + def execute(self, con): + query = self.query + if self.wheres: + query += ' WHERE ' + ' AND '.join(self.wheres) + c = con.cursor() + return c.execute(query, self.parameters) + + +class CnAliasedQuery(Query): + + sql = ''' + SELECT `%(table)s_cache`.*, + `%(table)s_1_cache`.`cn` AS `alias` + FROM `%(table)s_cache` + LEFT JOIN `%(table)s_1_cache` + ON `%(table)s_1_cache`.`%(table)s` = `%(table)s_cache`.`cn` + ''' + + cn_join = ''' + LEFT JOIN `%(table)s_1_cache` `cn_alias` + ON `cn_alias`.`%(table)s` = `%(table)s_cache`.`cn` + ''' + + def __init__(self, table, parameters): + args = dict(table=table) + super(CnAliasedQuery, self).__init__(self.sql % args) + for k, v in parameters.items(): + if k == 'cn': + self.add_query(self.cn_join % args) + self.add_where('(`%(table)s_cache`.`cn` = ? OR `cn_alias`.`cn` = ?)' % args, [v, v]) + else: + self.add_where('`%s` = ?' % k, [v]) + + +class RowGrouper(object): + """Pass in query results and group the results by a certain specified + list of columns.""" + + def __init__(self, results, groupby, columns): + self.groupby = groupby + self.columns = columns + self.results = itertools.groupby(results, key=self.keyfunc) + + def __iter__(self): + return self + + def keyfunc(self, row): + return tuple(row[x] for x in self.groupby) + + def next(self): + groupcols, rows = self.results.next() + tmp = dict((x, list()) for x in self.columns) + for row in rows: + for col in self.columns: + if row[col] is not None: + tmp[col].append(row[col]) + result = dict(row) + result.update(tmp) + return result + + +class Cache(object): + + def __init__(self): + self.con = con + self.table = sys.modules[self.__module__].__name__ + + def store(self, *values): + """Store the values in the cache for the specified table.""" + simple_values = [] + multi_values = {} + for n, v in enumerate(values): + if isinstance(v, (list, tuple, set)): + multi_values[n] = v + else: + simple_values.append(v) + simple_values.append(datetime.datetime.now()) + args = ', '.join(len(simple_values) * ('?', )) + con.execute(''' + INSERT OR REPLACE INTO %s_cache + VALUES + (%s) + ''' % (self.table, args), simple_values) + for n, vlist in multi_values.items(): + con.execute(''' + DELETE FROM %s_%d_cache + WHERE `%s` = ? + ''' % (self.table, n, self.table), (values[0], )) + con.executemany(''' + INSERT INTO %s_%d_cache + VALUES + (?, ?) + ''' % (self.table, n), ((values[0], x) for x in vlist)) + + def retrieve(self, parameters): + """Retrieve all items from the cache based on the parameters supplied.""" + query = Query(''' + SELECT * + FROM %s_cache + ''' % self.table, parameters) + return (list(x)[:-1] for x in query.execute(self.con)) |