[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:
Pierre-Elliott Bécue 2013-03-09 19:55:21 +01:00
parent c288f2b60b
commit 924f5c0684
2 changed files with 133 additions and 104 deletions

View file

@ -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

View file

@ -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):
"""
Classe d'erreur pour les locks par le process courant
"""
pass
class CransLock: class LdapLockedByOther(exceptions.StandardError):
"""
Erreur car le lock est occupé par un autre process.
"""
pass
class LockFormatError(exceptions.StandardError):
"""
L'objet lock qu'on a récupéré n'est pas tel qu'on le voudrait
"""
pass
LOCKS_DN = 'ou=lock,dc=crans,dc=org'
class LdapLockHolder:
"""
Système de gestion des locks pour une instance de lc_ldap.
"""
def __init__(self, conn): def __init__(self, conn):
self.conn = conn """
On crée la connexion, et on crée un dico vide.
"""
self.locks = {} self.locks = {}
self._active = [] self.host = socket.gethostbyname()
self.pid = os.getpid()
self.conn = conn
def __enter__(self): def __del__(self):
self.lock() """
On essaye de détruire tous les verrous hébergés par
def __exit__(self, *args): l'objet mourant.
# XXX - connecter correctement les tracebacks. """
print "exiting with exception", args for item, value in self.locks:
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: try:
self._releaseitem(item) self.removelock(item, value)
except Exception, e: except:
exceptions.append(e) pass
if len(exceptions) == 1: del self.conn
# XXX - connecter proprement les tracebacks
raise exceptions[0]
elif len(exceptions) > 1:
raise Exception(exceptions)
def _islocked(self, item, valeur): def addlock(self, item, value):
# 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) Applique un verrou sur "$item=$value,$LOCKS_DN",
si le précédent verrou était géré par la session
courante de lc_ldap, on prévient l'utilisateur
de la session, pour qu'il puisse éventuellement
libérer le lock.
def _lockitem(self, item, valeur): Sinon, on ne peut pas override le lock, et on laisse
u""" tomber.
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: try:
self.conn.add_s(lock_dn, modlist) host, pid = self.getlock(item, value)
except ldap.ALREADY_EXISTS: if host == self.host and pid == self.pid():
# # Pas de chance, le lock est déja pris raise LdapLockedByYou("La donnée %r=%r est lockée par vous-même." % (item, value))
# try: elif host == self.host:
# res = self.conn.search_s(lock_dn, 2, 'objectClass=lock')[0] status = crans_utils.process_status(pid)
# l = res[1]['lockid'][0] if status:
# except: l = '%s-1' % hostname raise LdapLockedByOther("La donnée %r=%r est lockée par quelqu'un d'autre ou un processus." % (item, value))
# if l != lockid: else:
# # C'est locké par un autre process que le notre self.removelock(item, value)
# # il tourne encore ? else:
# if l.split('-')[0] == hostname and os.system('ps %s > /dev/null 2>&1' % l.split('-')[1] ): raise LdapLockedByOther("La donnée %r=%r est lockée par quelqu'un d'autre ou un processus." % (item, value))
# # Il ne tourne plus except ldap.NO_SUCH_OBJECT:
# self._releaseitem(res[0]) # delock pass
# return self._lockitem(item, valeur) # relock except LockFormatError:
raise EnvironmentError(u'Objet (%s=%s) locké, patienter.' % (item, valeur), lockid) self.removelock(item, value)
self._active.append(lock_dn)
return lock_dn
def _releaseitem(self, lockdn): dn = "%s=%s,%s" % (item, value, LOCKS_DN)
u"""Destruction d'un lock""" lockid = "%s-%s" % (self.host, self.pid)
# Mettre des verifs ? modlist = ldap.modlist.addModlist({'objectClass' : 'lock',
print "releasing", lockdn 'lockid' : lockid,
self._active.remove(lockdn) item : value})
self.conn.delete_s(lockdn)
try:
self.conn.add_s(dn, modlist)
self.locks[item] = value
except ldap.ALREADY_EXISTS:
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)
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