[locks] Création d'une nouvelle gestion de locks.
J'ai pas mal regardé le travail d'adg, certains trucs ne me convenaient pas dans la gestion, du coup j'ai réécrit un truc, qui me semble mieux. J'ai laissé la licence telle quelle, mais j'ai mis mon nom en author
This commit is contained in:
parent
c288f2b60b
commit
924f5c0684
2 changed files with 133 additions and 104 deletions
|
@ -29,11 +29,19 @@
|
||||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
import calendar, netaddr, re, time, smtplib, sys
|
import calendar
|
||||||
|
import netaddr
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
import smtplib
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
sys.path.append('/usr/scripts/gestion')
|
sys.path.append('/usr/scripts/gestion')
|
||||||
import config
|
import config
|
||||||
from unicodedata import normalize
|
from unicodedata import normalize
|
||||||
|
|
||||||
|
DEVNULL = open(os.devnull, 'w')
|
||||||
|
|
||||||
def ip4_of_rid(rid):
|
def ip4_of_rid(rid):
|
||||||
"""Convertit un rid en son IP associée"""
|
"""Convertit un rid en son IP associée"""
|
||||||
# Au cas où
|
# Au cas où
|
||||||
|
@ -181,5 +189,12 @@ def ldap_sanitize(s):
|
||||||
except KeyError: return c
|
except KeyError: return c
|
||||||
return "".join([conv(c) for c in s])
|
return "".join([conv(c) for c in s])
|
||||||
|
|
||||||
|
def process_status(pid):
|
||||||
|
"""
|
||||||
|
Vérifie l'état du processus pid
|
||||||
|
"""
|
||||||
|
cmd = subprocess.check(['ps', '%s' % pid], stdout=DEVNULL, stderr=subprocess.STDOUT)
|
||||||
|
if cmd != 0:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
214
ldap_locks.py
214
ldap_locks.py
|
@ -3,8 +3,11 @@
|
||||||
#
|
#
|
||||||
# LDAP_LOCKS.PY-- Locks for lc_ldap
|
# LDAP_LOCKS.PY-- Locks for lc_ldap
|
||||||
#
|
#
|
||||||
## Copyright (C) 2010 Cr@ns <roots@crans.org>
|
## Copyright (C) 2013 Cr@ns <roots@crans.org>
|
||||||
# Author: Antoine Durand-Gasselin <adg@crans.org>
|
# Authors:
|
||||||
|
# * Antoine Durand-Gasselin <adg@crans.org>
|
||||||
|
# * Pierre-Elliott Bécue <becue@crans.org>
|
||||||
|
#
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
# Redistribution and use in source and binary forms, with or without
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
@ -30,115 +33,126 @@
|
||||||
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
import ldap, os
|
import ldap
|
||||||
|
import os
|
||||||
|
import exceptions
|
||||||
|
import socket
|
||||||
|
import crans_utils
|
||||||
|
|
||||||
base_lock = 'ou=lock,dc=crans,dc=org'
|
class LdapLockedByYou(exceptions.StandardError):
|
||||||
|
"""
|
||||||
class CransLock:
|
Classe d'erreur pour les locks par le process courant
|
||||||
|
"""
|
||||||
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
|
pass
|
||||||
|
|
||||||
locked_values = self.locks.get(item, [])
|
class LdapLockedByOther(exceptions.StandardError):
|
||||||
if valeur not in locked_values:
|
"""
|
||||||
locked_values.append(valeur)
|
Erreur car le lock est occupé par un autre process.
|
||||||
self.locks[item] = locked_values
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def remove(self, item, valeur):
|
class LockFormatError(exceptions.StandardError):
|
||||||
'''Enlève un lock'''
|
"""
|
||||||
self.locks[item].remove(valeur)
|
L'objet lock qu'on a récupéré n'est pas tel qu'on le voudrait
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def lock(self):
|
LOCKS_DN = 'ou=lock,dc=crans,dc=org'
|
||||||
'''Essaie de prendre tous les verrous'''
|
|
||||||
items = self.locks.items()
|
class LdapLockHolder:
|
||||||
items.sort()
|
"""
|
||||||
|
Système de gestion des locks pour une instance de lc_ldap.
|
||||||
|
"""
|
||||||
|
def __init__(self, conn):
|
||||||
|
"""
|
||||||
|
On crée la connexion, et on crée un dico vide.
|
||||||
|
"""
|
||||||
|
self.locks = {}
|
||||||
|
self.host = socket.gethostbyname()
|
||||||
|
self.pid = os.getpid()
|
||||||
|
self.conn = conn
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
"""
|
||||||
|
On essaye de détruire tous les verrous hébergés par
|
||||||
|
l'objet mourant.
|
||||||
|
"""
|
||||||
|
for item, value in self.locks:
|
||||||
try:
|
try:
|
||||||
for item, valeurs in items:
|
self.removelock(item, value)
|
||||||
for valeur in valeurs:
|
except:
|
||||||
self._lockitem(item, valeur)
|
pass
|
||||||
except Exception, e:
|
del self.conn
|
||||||
# XXX - connecter proprement les traceback
|
|
||||||
self.release()
|
|
||||||
raise e
|
|
||||||
|
|
||||||
def release(self):
|
def addlock(self, item, value):
|
||||||
'''Relâche tous les verrous'''
|
"""
|
||||||
exceptions = []
|
Applique un verrou sur "$item=$value,$LOCKS_DN",
|
||||||
print "releasing", self._active
|
si le précédent verrou était géré par la session
|
||||||
for item in self._active[:]:
|
courante de lc_ldap, on prévient l'utilisateur
|
||||||
try:
|
de la session, pour qu'il puisse éventuellement
|
||||||
self._releaseitem(item)
|
libérer le lock.
|
||||||
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):
|
Sinon, on ne peut pas override le lock, et on laisse
|
||||||
# XXX - return self.conn.search_s(base_dn, 2, '%s=%s' % (item, valeur))
|
tomber.
|
||||||
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')
|
try:
|
||||||
|
host, pid = self.getlock(item, value)
|
||||||
|
if host == self.host and pid == self.pid():
|
||||||
|
raise LdapLockedByYou("La donnée %r=%r est lockée par vous-même." % (item, value))
|
||||||
|
elif host == self.host:
|
||||||
|
status = crans_utils.process_status(pid)
|
||||||
|
if status:
|
||||||
|
raise LdapLockedByOther("La donnée %r=%r est lockée par quelqu'un d'autre ou un processus." % (item, value))
|
||||||
|
else:
|
||||||
|
self.removelock(item, value)
|
||||||
|
else:
|
||||||
|
raise LdapLockedByOther("La donnée %r=%r est lockée par quelqu'un d'autre ou un processus." % (item, value))
|
||||||
|
except ldap.NO_SUCH_OBJECT:
|
||||||
|
pass
|
||||||
|
except LockFormatError:
|
||||||
|
self.removelock(item, value)
|
||||||
|
|
||||||
lock_dn = '%s=%s,%s' % (item, valeur, base_lock)
|
dn = "%s=%s,%s" % (item, value, LOCKS_DN)
|
||||||
lockid = '%s-%s' % ('localhost', os.getpid())
|
lockid = "%s-%s" % (self.host, self.pid)
|
||||||
modlist = ldap.modlist.addModlist({'objectClass' : 'lock',
|
modlist = ldap.modlist.addModlist({'objectClass' : 'lock',
|
||||||
'lockid' : lockid,
|
'lockid' : lockid,
|
||||||
item: valeur })
|
item : value})
|
||||||
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):
|
try:
|
||||||
u"""Destruction d'un lock"""
|
self.conn.add_s(dn, modlist)
|
||||||
# Mettre des verifs ?
|
self.locks[item] = value
|
||||||
print "releasing", lockdn
|
except ldap.ALREADY_EXISTS:
|
||||||
self._active.remove(lockdn)
|
status = crans_utils.process_status(pid)
|
||||||
self.conn.delete_s(lockdn)
|
if status:
|
||||||
|
raise LdapLockedByOther("La donnée %r=%r est lockée par quelqu'un d'autre ou un processus." % (item, value))
|
||||||
|
else:
|
||||||
|
self.removelock(item, value)
|
||||||
|
try:
|
||||||
|
self.conn.add_s(dn, modlist)
|
||||||
|
self.locks[item] = value
|
||||||
|
except:
|
||||||
|
raise StandardError("Quelque chose a planté durant la pose du lock %s=%s" % (item, value))
|
||||||
|
|
||||||
|
def removelock(self, item, value):
|
||||||
|
"""
|
||||||
|
Libère le lock "$item=$value,$LOCKS_DN".
|
||||||
|
"""
|
||||||
|
print "Deleting %s=%s,%s\n" % (item, value, LOCKS_DN)
|
||||||
|
if self.locks.get(item, "") == value:
|
||||||
|
self.locks.pop(item)
|
||||||
|
self.conn.delete_s("%s=%s,%s" % (item, value, LOCKS_DN))
|
||||||
|
else:
|
||||||
|
print "Lock %s=%s,%s does not exist on this session" % (item, value, LOCKS_DN)
|
||||||
|
|
||||||
|
def getlock(self, item, value):
|
||||||
|
"""
|
||||||
|
Trouve le lock item=value, et renvoie le contenu de lockinfo
|
||||||
|
via un couple host, pid
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = self.conn.search_s('%s=%s,%s' % (item, value, LOCKS_DN), 0)
|
||||||
|
try:
|
||||||
|
return result[0][1]['lockid'][0].split('-')
|
||||||
|
except:
|
||||||
|
raise LockFormatError
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue