comparison MoinMoin/_tests/ldap_testbase.py @ 3650:23851c20e53f

add ldap testing framework, add ldap_login tests
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Sun, 01 Jun 2008 02:18:38 +0200
parents
children df024fd0a129
comparison
equal deleted inserted replaced
3649:ef8511b43788 3650:23851c20e53f
1 # -*- coding: utf-8 -*-
2 """
3 LDAPTestBase: LDAP testing support for py.test based unit tests
4
5 Features
6 --------
7
8 * setup_class
9 * automatic creation of a temporary LDAP server environment
10 * automatic creation of a LDAP server process (slapd)
11
12 * teardown_class
13 * LDAP server process will be killed and termination will be waited for
14 * temporary LDAP environment will be removed
15
16 Usage
17 -----
18
19 Write your own test class and derive from LDAPTestBase:
20
21 class TestLdap(LDAPTestBase):
22 def testFunction(self):
23 server_url = self.ldap_env.slapd.url
24 lo = ldap.initialize(server_url)
25 lo.simple_bind_s('', '')
26
27 Notes
28 -----
29
30 On Ubuntu 8.04 there is apparmor imposing some restrictions on /usr/sbin/slapd,
31 so you need to disable apparmor by invoking this as root:
32
33 # /etc/init.d/apparmor stop
34
35 @copyright: 2008 by Thomas Waldmann
36 @license: GNU GPL, see COPYING for details.
37 """
38
39 import os, shutil, tempfile, time
40 from StringIO import StringIO
41 import signal
42 import subprocess # needs Python 2.4
43
44 import ldap, ldif, ldap.modlist
45
46 class Slapd(object):
47 """ Manage a slapd process for testing purposes """
48 def __init__(self,
49 config=None, # config filename for -f
50 executable='slapd', # slapd executable filename
51 debug_flags='', # None, # for -d stats,acl,args,trace,sync,config
52 proto='ldap', ip='127.0.0.1', port=3890, # use -h proto://ip:port
53 service_name='' # defaults to -n executable:port, use None to not use -n
54 ):
55 self.executable = executable
56 self.config = config
57 self.debug_flags = debug_flags
58 self.proto = proto
59 self.ip = ip
60 self.port = port
61 self.url = '%s://%s:%d' % (proto, ip, port) # can be used for ldap.initialize() call
62 if service_name == '':
63 self.service_name = '%s:%d' % (executable, port)
64 else:
65 self.service_name = service_name
66
67 def start(self, timeout=0):
68 """ start a slapd process and optionally wait up to timeout seconds until it responds """
69 args = [self.executable, '-h', self.url, ]
70 if self.config is not None:
71 args.extend(['-f', self.config])
72 if self.debug_flags is not None:
73 args.extend(['-d', self.debug_flags])
74 if self.service_name:
75 args.extend(['-n', self.service_name])
76 self.process = subprocess.Popen(args)
77 started = None
78 if timeout:
79 lo = ldap.initialize(self.url)
80 ldap.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3) # ldap v2 is outdated
81 started = False
82 wait_until = time.time() + timeout
83 while time.time() < wait_until:
84 try:
85 lo.simple_bind_s('', '')
86 started = True
87 except ldap.SERVER_DOWN, err:
88 time.sleep(0.1)
89 else:
90 break
91 return started
92
93 def stop(self):
94 """ stop this slapd process and wait until it has terminated """
95 pid = self.process.pid
96 os.kill(pid, signal.SIGTERM)
97 os.waitpid(pid, 0)
98
99
100 class LdapEnvironment(object):
101 """ Manage a (temporary) environment for running a slapd in it """
102
103 # default DB_CONFIG bdb configuration file contents
104 DB_CONFIG = """\
105 # STRANGE: if i use those settings, after the test slapd goes to 100% and doesn't terminate on SIGTERM
106 # Set the database in memory cache size.
107 #set_cachesize 0 10000000 1
108
109 # Set log values.
110 #set_lg_regionmax 262144
111 #set_lg_bsize 262144
112 #set_lg_max 10485760
113
114 #set_tas_spins 0
115 """
116
117 def __init__(self,
118 basedn,
119 rootdn, rootpw,
120 instance=0, # use different values when running multiple LdapEnvironments
121 schema_dir='/etc/ldap/schema', # directory with schemas
122 coding='utf-8', # coding used for config files
123 timeout=10, # how long to wait for slapd starting [s]
124 ):
125 self.basedn = basedn
126 self.rootdn = rootdn
127 self.rootpw = rootpw
128 self.instance = instance
129 self.schema_dir = schema_dir
130 self.coding = coding
131 self.ldap_dir = None
132 self.slapd_conf = None
133 self.timeout = timeout
134
135 def create_env(self, slapd_config, db_config=DB_CONFIG):
136 """ create a temporary LDAP server environment in a temp. directory,
137 including writing a slapd.conf (see configure_slapd) and a
138 DB_CONFIG there.
139 """
140 # create directories
141 self.ldap_dir = tempfile.mkdtemp(prefix='LdapEnvironment-%d.' % self.instance)
142 self.ldap_db_dir = os.path.join(self.ldap_dir, 'db')
143 os.mkdir(self.ldap_db_dir)
144
145 # create DB_CONFIG for bdb backend
146 db_config_fname = os.path.join(self.ldap_db_dir, 'DB_CONFIG')
147 f = open(db_config_fname, 'w')
148 f.write(db_config)
149 f.close()
150
151 # create slapd.conf from content template in slapd_config
152 slapd_config = slapd_config % {
153 'ldap_dir': self.ldap_dir,
154 'ldap_db_dir': self.ldap_db_dir,
155 'schema_dir': self.schema_dir,
156 'basedn': self.basedn,
157 'rootdn': self.rootdn,
158 'rootpw': self.rootpw,
159 }
160 if isinstance(slapd_config, unicode):
161 slapd_config = slapd_config.encode(self.coding)
162 self.slapd_conf = os.path.join(self.ldap_dir, "slapd.conf")
163 f = open(self.slapd_conf, 'w')
164 f.write(slapd_config)
165 f.close()
166
167 def start_slapd(self):
168 """ start a slapd and optionally wait until it talks with us """
169 self.slapd = Slapd(config=self.slapd_conf, port=3890+self.instance)
170 self.slapd.start(timeout=self.timeout)
171
172 def load_directory(self, ldif_content):
173 """ load the directory with the ldif_content (str) """
174 lo = ldap.initialize(self.slapd.url)
175 ldap.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3) # ldap v2 is outdated
176 lo.simple_bind_s(self.rootdn, self.rootpw)
177
178 class LDIFLoader(ldif.LDIFParser):
179 def handle(self, dn, entry):
180 lo.add_s(dn, ldap.modlist.addModlist(entry))
181
182 loader = LDIFLoader(StringIO(ldif_content))
183 loader.parse()
184
185 def stop_slapd(self):
186 """ stop a slapd """
187 self.slapd.stop()
188
189 def destroy_env(self):
190 """ remove the temporary LDAP server environment """
191 shutil.rmtree(self.ldap_dir)
192
193
194 class LDAPTestBase:
195 """ Test base class for py.test based tests which need a LDAP server to talk to.
196
197 Inherit your test class from this base class to test LDAP stuff.
198 """
199
200 # You MUST define these in your derived class:
201 slapd_config = None # a string with your slapd.conf template
202 ldif_content = None # a string with your ldif contents
203 basedn = None # your base DN
204 rootdn = None # root DN
205 rootpw = None # root password
206
207 def setup_class(self):
208 """ Create LDAP server environment, start slapd """
209 self.ldap_env = LdapEnvironment(self.basedn, self.rootdn, self.rootpw)
210 self.ldap_env.create_env(slapd_config=self.slapd_config)
211 self.ldap_env.start_slapd()
212 self.ldap_env.load_directory(ldif_content=self.ldif_content)
213
214 def teardown_class(self):
215 """ Stop slapd, remove LDAP server environment """
216 self.ldap_env.stop_slapd()
217 self.ldap_env.destroy_env()
218
219