Arthur de Jong

Open Source / Free Software developer

summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--pynslcd/alias.py18
-rw-r--r--pynslcd/cache.py312
-rw-r--r--pynslcd/common.py26
-rw-r--r--pynslcd/ether.py5
-rw-r--r--pynslcd/group.py20
-rw-r--r--pynslcd/host.py26
-rw-r--r--pynslcd/netgroup.py5
-rw-r--r--pynslcd/network.py26
-rw-r--r--pynslcd/passwd.py5
-rw-r--r--pynslcd/protocol.py9
-rw-r--r--pynslcd/rpc.py9
-rw-r--r--pynslcd/service.py54
-rw-r--r--pynslcd/shadow.py5
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,