Arthur de Jong

Open Source / Free Software developer

summaryrefslogtreecommitdiffstats
path: root/pynslcd/cache.py
diff options
context:
space:
mode:
Diffstat (limited to 'pynslcd/cache.py')
-rw-r--r--pynslcd/cache.py312
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))