diff options
-rw-r--r-- | pynslcd/alias.py | 18 | ||||
-rw-r--r-- | pynslcd/cache.py | 312 | ||||
-rw-r--r-- | pynslcd/common.py | 26 | ||||
-rw-r--r-- | pynslcd/ether.py | 5 | ||||
-rw-r--r-- | pynslcd/group.py | 20 | ||||
-rw-r--r-- | pynslcd/host.py | 26 | ||||
-rw-r--r-- | pynslcd/netgroup.py | 5 | ||||
-rw-r--r-- | pynslcd/network.py | 26 | ||||
-rw-r--r-- | pynslcd/passwd.py | 5 | ||||
-rw-r--r-- | pynslcd/protocol.py | 9 | ||||
-rw-r--r-- | pynslcd/rpc.py | 9 | ||||
-rw-r--r-- | pynslcd/service.py | 54 | ||||
-rw-r--r-- | pynslcd/shadow.py | 5 |
13 files changed, 516 insertions, 4 deletions
diff --git a/pynslcd/alias.py b/pynslcd/alias.py index cdae9da..25d55b3 100644 --- a/pynslcd/alias.py +++ b/pynslcd/alias.py @@ -18,6 +18,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA +import cache import common import constants @@ -33,6 +34,23 @@ class Search(common.Search): required = ('cn', 'rfc822MailMember') +class Cache(cache.Cache): + + retrieve_sql = ''' + SELECT `alias_cache`.`cn` AS `cn`, + `alias_1_cache`.`rfc822MailMember` AS `rfc822MailMember` + FROM `alias_cache` + LEFT JOIN `alias_1_cache` + ON `alias_1_cache`.`alias` = `alias_cache`.`cn` + ''' + + def retrieve(self, parameters): + query = cache.Query(self.retrieve_sql, parameters) + # return results, returning the members as a list + for row in cache.RowGrouper(query.execute(self.con), ('cn', ), ('rfc822MailMember', )): + yield row['cn'], row['rfc822MailMember'] + + class AliasRequest(common.Request): def write(self, name, members): 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)) diff --git a/pynslcd/common.py b/pynslcd/common.py index 7f41a11..7e90248 100644 --- a/pynslcd/common.py +++ b/pynslcd/common.py @@ -25,6 +25,7 @@ import ldap import ldap.dn from attmap import Attributes +import cache import cfg import constants @@ -187,6 +188,10 @@ class Request(object): self.calleruid = calleruid module = sys.modules[self.__module__] self.search = getattr(module, 'Search', None) + if not hasattr(module, 'cache_obj'): + cache_cls = getattr(module, 'Cache', None) + module.cache_obj = cache_cls() if cache_cls else None + self.cache = module.cache_obj def read_parameters(self, fp): """This method should read the parameters from ths stream and @@ -196,10 +201,23 @@ class Request(object): def handle_request(self, parameters): """This method handles the request based on the parameters read with read_parameters().""" - for dn, attributes in self.search(conn=self.conn, parameters=parameters): - for values in self.convert(dn, attributes, parameters): - self.fp.write_int32(constants.NSLCD_RESULT_BEGIN) - self.write(*values) + try: + with cache.con: + for dn, attributes in self.search(conn=self.conn, parameters=parameters): + for values in self.convert(dn, attributes, parameters): + self.fp.write_int32(constants.NSLCD_RESULT_BEGIN) + self.write(*values) + if self.cache: + self.cache.store(*values) + except ldap.SERVER_DOWN: + if self.cache: + logging.debug('read from cache') + # we assume server went down before writing any entries + for values in self.cache.retrieve(parameters): + self.fp.write_int32(constants.NSLCD_RESULT_BEGIN) + self.write(*values) + else: + raise # write the final result code self.fp.write_int32(constants.NSLCD_RESULT_END) diff --git a/pynslcd/ether.py b/pynslcd/ether.py index 20963c3..a26ac3b 100644 --- a/pynslcd/ether.py +++ b/pynslcd/ether.py @@ -20,6 +20,7 @@ import struct +import cache import common import constants @@ -46,6 +47,10 @@ class Search(common.Search): required = ('cn', 'macAddress') +class Cache(cache.Cache): + pass + + class EtherRequest(common.Request): def write(self, name, ether): diff --git a/pynslcd/group.py b/pynslcd/group.py index 2880a6b..396c2b9 100644 --- a/pynslcd/group.py +++ b/pynslcd/group.py @@ -18,9 +18,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA +import itertools import logging from passwd import dn2uid, uid2dn +import cache import common import constants @@ -64,6 +66,24 @@ class Search(common.Search): return super(Search, self).mk_filter() +class Cache(cache.Cache): + + retrieve_sql = ''' + SELECT `cn`, `userPassword`, `gidNumber`, `memberUid` + FROM `group_cache` + LEFT JOIN `group_3_cache` + ON `group_3_cache`.`group` = `group_cache`.`cn` + ''' + + def retrieve(self, parameters): + query = cache.Query(self.retrieve_sql, parameters) + # return results returning the members as a set + q = itertools.groupby(query.execute(self.con), + key=lambda x: (x['cn'], x['userPassword'], x['gidNumber'])) + for k, v in q: + yield k + (set(x['memberUid'] for x in v if x['memberUid'] is not None), ) + + class GroupRequest(common.Request): def write(self, name, passwd, gid, members): diff --git a/pynslcd/host.py b/pynslcd/host.py index 23ab521..49de45b 100644 --- a/pynslcd/host.py +++ b/pynslcd/host.py @@ -18,6 +18,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA +import cache import common import constants @@ -32,6 +33,31 @@ class Search(common.Search): required = ('cn', ) +class HostQuery(cache.CnAliasedQuery): + + sql = ''' + SELECT `host_cache`.`cn` AS `cn`, + `host_1_cache`.`cn` AS `alias`, + `host_2_cache`.`ipHostNumber` AS `ipHostNumber` + FROM `host_cache` + LEFT JOIN `host_1_cache` + ON `host_1_cache`.`host` = `host_cache`.`cn` + LEFT JOIN `host_2_cache` + ON `host_2_cache`.`host` = `host_cache`.`cn` + ''' + + def __init__(self, parameters): + super(HostQuery, self).__init__('host', parameters) + + +class Cache(cache.Cache): + + def retrieve(self, parameters): + query = HostQuery(parameters) + for row in cache.RowGrouper(query.execute(self.con), ('cn', ), ('alias', 'ipHostNumber', )): + yield row['cn'], row['alias'], row['ipHostNumber'] + + class HostRequest(common.Request): def write(self, hostname, aliases, addresses): diff --git a/pynslcd/netgroup.py b/pynslcd/netgroup.py index 31eb02c..4118cd4 100644 --- a/pynslcd/netgroup.py +++ b/pynslcd/netgroup.py @@ -21,6 +21,7 @@ import logging import re +import cache import common import constants @@ -40,6 +41,10 @@ class Search(common.Search): required = ('cn', ) +class Cache(cache.Cache): + pass + + class NetgroupRequest(common.Request): def write(self, name, member): diff --git a/pynslcd/network.py b/pynslcd/network.py index bccc788..88778d7 100644 --- a/pynslcd/network.py +++ b/pynslcd/network.py @@ -18,6 +18,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA +import cache import common import constants @@ -33,6 +34,31 @@ class Search(common.Search): required = ('cn', ) +class NetworkQuery(cache.CnAliasedQuery): + + sql = ''' + SELECT `network_cache`.`cn` AS `cn`, + `network_1_cache`.`cn` AS `alias`, + `network_2_cache`.`ipNetworkNumber` AS `ipNetworkNumber` + FROM `network_cache` + LEFT JOIN `network_1_cache` + ON `network_1_cache`.`network` = `network_cache`.`cn` + LEFT JOIN `network_2_cache` + ON `network_2_cache`.`network` = `network_cache`.`cn` + ''' + + def __init__(self, parameters): + super(NetworkQuery, self).__init__('network', parameters) + + +class Cache(cache.Cache): + + def retrieve(self, parameters): + query = NetworkQuery(parameters) + for row in cache.RowGrouper(query.execute(self.con), ('cn', ), ('alias', 'ipNetworkNumber', )): + yield row['cn'], row['alias'], row['ipNetworkNumber'] + + class NetworkRequest(common.Request): def write(self, networkname, aliases, addresses): diff --git a/pynslcd/passwd.py b/pynslcd/passwd.py index 9b222d0..e78ff64 100644 --- a/pynslcd/passwd.py +++ b/pynslcd/passwd.py @@ -20,6 +20,7 @@ import logging +import cache import common import constants @@ -43,6 +44,10 @@ class Search(common.Search): 'loginShell') +class Cache(cache.Cache): + pass + + class PasswdRequest(common.Request): def write(self, name, passwd, uid, gid, gecos, home, shell): diff --git a/pynslcd/protocol.py b/pynslcd/protocol.py index 3f536ee..91feaea 100644 --- a/pynslcd/protocol.py +++ b/pynslcd/protocol.py @@ -18,6 +18,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA +import cache import common import constants @@ -33,6 +34,14 @@ class Search(common.Search): required = ('cn', 'ipProtocolNumber') +class Cache(cache.Cache): + + def retrieve(self, parameters): + query = cache.CnAliasedQuery('protocol', parameters) + for row in cache.RowGrouper(query.execute(self.con), ('cn', ), ('alias', )): + yield row['cn'], row['alias'], row['ipProtocolNumber'] + + class ProtocolRequest(common.Request): def write(self, name, names, number): diff --git a/pynslcd/rpc.py b/pynslcd/rpc.py index e743960..c667ffa 100644 --- a/pynslcd/rpc.py +++ b/pynslcd/rpc.py @@ -18,6 +18,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA +import cache import common import constants @@ -33,6 +34,14 @@ class Search(common.Search): required = ('cn', 'oncRpcNumber') +class Cache(cache.Cache): + + def retrieve(self, parameters): + query = cache.CnAliasedQuery('rpc', parameters) + for row in cache.RowGrouper(query.execute(self.con), ('cn', ), ('alias', )): + yield row['cn'], row['alias'], row['oncRpcNumber'] + + class RpcRequest(common.Request): def write(self, name, aliases, number): diff --git a/pynslcd/service.py b/pynslcd/service.py index c89ac6f..a3c00f1 100644 --- a/pynslcd/service.py +++ b/pynslcd/service.py @@ -18,9 +18,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA +import datetime import ldap.filter import logging +import cache import common import constants @@ -39,6 +41,58 @@ class Search(common.Search): required = ('cn', 'ipServicePort', 'ipServiceProtocol') +class ServiceQuery(cache.CnAliasedQuery): + + sql = ''' + SELECT `service_cache`.*, + `service_1_cache`.`cn` AS `alias` + FROM `service_cache` + LEFT JOIN `service_1_cache` + ON `service_1_cache`.`ipServicePort` = `service_cache`.`ipServicePort` + AND `service_1_cache`.`ipServiceProtocol` = `service_cache`.`ipServiceProtocol` + ''' + + cn_join = ''' + LEFT JOIN `service_1_cache` `cn_alias` + ON `cn_alias`.`ipServicePort` = `service_cache`.`ipServicePort` + AND `cn_alias`.`ipServiceProtocol` = `service_cache`.`ipServiceProtocol` + ''' + + def __init__(self, parameters): + super(ServiceQuery, self).__init__('service', {}) + for k, v in parameters.items(): + if k == 'cn': + self.add_query(self.cn_join) + self.add_where('(`service_cache`.`cn` = ? OR `cn_alias`.`cn` = ?)', [v, v]) + else: + self.add_where('`service_cache`.`%s` = ?' % k, [v]) + + +class Cache(cache.Cache): + + def store(self, name, aliases, port, protocol): + self.con.execute(''' + INSERT OR REPLACE INTO `service_cache` + VALUES + (?, ?, ?, ?) + ''', (name, port, protocol, datetime.datetime.now())) + self.con.execute(''' + DELETE FROM `service_1_cache` + WHERE `ipServicePort` = ? + AND `ipServiceProtocol` = ? + ''', (port, protocol)) + self.con.executemany(''' + INSERT INTO `service_1_cache` + VALUES + (?, ?, ?) + ''', ((port, protocol, alias) for alias in aliases)) + + def retrieve(self, parameters): + query = ServiceQuery(parameters) + for row in cache.RowGrouper(query.execute(self.con), ('cn', 'ipServicePort', 'ipServiceProtocol'), ('alias', )): + yield row['cn'], row['alias'], row['ipServicePort'], row['ipServiceProtocol'] + + class ServiceRequest(common.Request): def write(self, name, aliases, port, protocol): diff --git a/pynslcd/shadow.py b/pynslcd/shadow.py index 2a4a1ab..99eecde 100644 --- a/pynslcd/shadow.py +++ b/pynslcd/shadow.py @@ -20,6 +20,7 @@ import logging +import cache import common import constants @@ -43,6 +44,10 @@ class Search(common.Search): required = ('uid', ) +class Cache(cache.Cache): + pass + + class ShadowRequest(common.Request): def write(self, name, passwd, lastchangedate, mindays, maxdays, warndays, |