#!/usr/bin/env python # -*- coding: utf-8 -*- # # LDAP_LOCKS.PY-- Locks for lc_ldap # ## Copyright (C) 2010 Cr@ns # Author: Antoine Durand-Gasselin # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the Cr@ns nor the names of its contributors may # be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import ldap, os base_lock = 'ou=lock,dc=crans,dc=org' class CransLock: def __init__(self, conn): self.conn = conn self.locks = {} self._active = [] def __enter__(self): self.lock() def __exit__(self, *args): # XXX - connecter correctement les tracebacks. print "exiting with exception", args self.release() return True def add(self, item, valeur): '''rajoute un lock, après avoir vérifié qu'il peut être posé''' try: locked = self._islocked(item, valeur) if locked: raise EnvironmentError(u'Object déjà locké', locked) except ldap.NO_SUCH_OBJECT: pass locked_values = self.locks.get(item, []) if valeur not in locked_values: locked_values.append(valeur) self.locks[item] = locked_values def remove(self, item, valeur): '''Enlève un lock''' self.locks[item].remove(valeur) def lock(self): '''Essaie de prendre tous les verrous''' items = self.locks.items() items.sort() try: for item, valeurs in items: for valeur in valeurs: self._lockitem(item, valeur) except Exception, e: # XXX - connecter proprement les traceback self.release() raise e def release(self): '''Relâche tous les verrous''' exceptions = [] print "releasing", self._active for item in self._active[:]: try: self._releaseitem(item) except Exception, e: exceptions.append(e) if len(exceptions) == 1: # XXX - connecter proprement les tracebacks raise exceptions[0] elif len(exceptions) > 1: raise Exception(exceptions) def _islocked(self, item, valeur): # XXX - return self.conn.search_s(base_dn, 2, '%s=%s' % (item, valeur)) return self.conn.search_s('%s=%s,%s' % (item, valeur, base_lock), 0) def _lockitem(self, item, valeur): u""" Lock un item avec la valeur valeur, les items possibles peuvent être : aid $ chbre $ mail $ mailAlias $ canonicalAlias $ mid $ macAddress $ host $ hostAlias $ ipHostNumber Retourne le dn du lock """ valeur = valeur.encode('utf-8') lock_dn = '%s=%s,%s' % (item, valeur, base_lock) lockid = '%s-%s' % ('localhost', os.getpid()) modlist = ldap.modlist.addModlist({ 'objectClass': 'lock', 'lockid': lockid, item: valeur }) print "locking", lock_dn try: self.conn.add_s(lock_dn, modlist) except ldap.ALREADY_EXISTS: # # Pas de chance, le lock est déja pris # try: # res = self.conn.search_s(lock_dn, 2, 'objectClass=lock')[0] # l = res[1]['lockid'][0] # except: l = '%s-1' % hostname # if l != lockid: # # C'est locké par un autre process que le notre # # il tourne encore ? # if l.split('-')[0] == hostname and os.system('ps %s > /dev/null 2>&1' % l.split('-')[1] ): # # Il ne tourne plus # self._releaseitem(res[0]) # delock # return self._lockitem(item, valeur) # relock raise EnvironmentError(u'Objet (%s=%s) locké, patienter.' % (item, valeur), lockid) self._active.append(lock_dn) return lock_dn def _releaseitem(self, lockdn): u"""Destruction d'un lock""" # Mettre des verifs ? print "releasing", lockdn self._active.remove(lockdn) self.conn.delete_s(lockdn)