diff --git a/lc_ldap.py b/lc_ldap.py index 5bccc6b..ee8bafe 100644 --- a/lc_ldap.py +++ b/lc_ldap.py @@ -59,6 +59,7 @@ import config import crans_utils import attributs import ldap_locks +import services uri = 'ldap://ldap.adm.crans.org/' base_dn = 'ou=data,dc=crans,dc=org' @@ -578,6 +579,7 @@ class CransLdapObject(object): modlist = addModlist(cldif_to_ldif(self._modifs)) # Requête LDAP de création de l'objet self.conn.add_s(self.dn, modlist) + services.services_to_restart(self.conn, {}, self._modifs) def bury(self, comm, login): self.history_add(login, u"destruction (%s)" % comm) @@ -601,6 +603,7 @@ class CransLdapObject(object): raise EnvironmentError("Vous n'avez pas le droit de supprimer %s." % self.dn) self.bury(comm, login) self.conn.delete_s(self.dn) + services.services_to_restart(self.conn, self.attrs, {}) def save(self): """Sauvegarde dans la base les modifications apportées à l'objet. @@ -624,6 +627,9 @@ class CransLdapObject(object): except: raise EnvironmentError("Impossible de modifier l'objet, peut-être n'existe-t-il pas ?") + # On programme le redémarrage des services + services.services_to_restart(self.conn, self.attrs, self._modifs) + # Vérification des modifications self.attrs = attributs.AttrsDict(self.conn, self.conn.search_s(self.dn, 0)[0][1], Parent=self) differences = [] @@ -886,6 +892,7 @@ class proprio(CransLdapObject): machine.delete(comm, login) self.bury(comm, login) self.conn.delete_s(self.dn) + services.services_to_restart(self.conn, self.attrs, {}) class machine(CransLdapObject): u""" Une machine """ diff --git a/services.py b/services.py new file mode 100644 index 0000000..4ec2caa --- /dev/null +++ b/services.py @@ -0,0 +1,244 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import ldap +import lc_ldap +import attributs + +services_dn = 'ou=services,dc=crans,dc=org' + +# liste des attributs dont dépend un service +services_to_attrs = {} +services_to_attrs['macip'] = [ attributs.ipHostNumber, attributs.ip6HostNumber, attributs.macAddress, attributs.paiement, attributs.carteEtudiant ] +services_to_attrs['dns'] = [ attributs.ipHostNumber, attributs.ip6HostNumber, attributs.sshFingerprint, attributs.host, attributs.hostAlias, attributs.dnsIpv6 ] +services_to_attrs['blacklist'] = [ attributs.blacklist, attributs.chbre, attributs.mailInvalide ] + services_to_attrs['macip'] +services_to_attrs['ports'] = [ attributs.portUDPout, attributs.portUDPin, attributs.portTCPout, attributs.portTCPin ] +services_to_attrs['droits'] = [ attributs.droits ] +services_to_attrs['mail_ajout_droits'] = [ attributs.droits ] +services_to_attrs['dhcp'] = services_to_attrs['macip'] +#services_to_attrs['home'] = [ attributs.homeDirectory ] ## Vérifier que ça ne fasse pas de caca + +services_to_attrs['del_user'] = [] +services_to_attrs['mail_bienvenue'] = [] +services_to_attrs['mail_modif'] = [] + + +# génération des arguments du service à redémarrer (par defaut []) +services_to_args={} +services_to_args['macip']=lambda x: issubclass(type(x.parent), lc_ldap.machine) and ([str(x)] if isinstance(x, attributs.ipHostNumber) else [ str(ip) for ip in x.parent['ipHostNumber'] ]) or [ str(ip) for m in x.parent.machines() for ip in m['ipHostNumber']] +## Inutile pour blackliste pour le moment +#services_to_args['blacklist']=lambda x: issubclass(type(x.parent), lc_ldap.machine) and [ str(ip) for m in x.parent.proprio().machines() for ip in m['ipHostNumber'] ] or [ str(ip) for m in x.parent.machines() for ip in m['ipHostNumber'] ] +services_to_args['port']=lambda x: [str(x)] if isinstance(x, attributs.ipHostNumber) or isinstance(x, attributs.ip6HostNumber) else [ str(ip) for ip in x.parent['ipHostNumber'] ] +services_to_args['home']=lambda x: u'cransAccount' in [ str(o) for o in x.parent['objectClass']] and [ "%s,%s,%s" % (x.parent['homeDirectory'][0],x.parent['uidNumber'][0],x.parent['uid'][0]) ] or [ "%s,%s,%s,%s" % (x.parent['homeDirectory'][0],x.parent['uidNumber'][0],x.parent['uid'][0],x.parent['mail'][0]) ] +services_to_args['mail_ajout_droits'] = lambda x: "%s:%s" % (x.parent['uid'][0], x) + +# Quand redémarrer le service (par defaut 0) +services_to_time={} +services_to_time['blacklist']=lambda x: isinstance(x, attributs.blacklist) and (0 if x.value['debut'] == '-' else x.value['debut']) or 0 + +attrs_to_services = {} +for (key, values) in services_to_attrs.items(): + for value in values: + attrs_to_services[value] = attrs_to_services.get(value, []) + [key] + +# Identique à celle dans ldap_crans.py +def preattr(val): + """ + val est : + * un entier + * une chaîne + * une liste avec un seul entier ou une seule chaîne + + Retourne [ len(str(val).strip), str(val).strip en utf-8 ] + """ + + if isinstance(val, list) and len(val) == 1: + return preattr(val[0]) + + elif isinstance(val, str) or isinstance(val, int): + val = str(val).strip() + # On passe tout en utf-8 pour ne pas avoir de problèmes + # d'accents dans la base + return [len(val), val] + elif isinstance(val, unicode): + val = val.strip() + return [len(val), val.encode('utf-8')] + else: + raise TypeError(type(val)) + +# Presque identique à celle dans ldap_crans.py +def service_to_restart(conn, new=None, args=[], start=0): + """ + start indique la date (en secondes depuis epoch) à partir du + moment où cette action sera effectuée. + + Si new = None retourne la liste des services à redémarrer. + + Si new est fourni, mais ne commence pas par '-', on ajoute + le service à la liste avec les arguments args (args doit être + une liste). + + Si new commence par '-', on supprime le service si son start + est dans le futur. + + Si new commence par '--', on supprime le service sans condition. + """ + if new: new = preattr(new)[1] + + # Quels services sont déjà à redémarrer ? + serv = {} # { service: [ arguments ] } + serv_dates = {} # { service: [ dates de restart ] } + services = [] + for s in conn.search_s(services_dn, 1, 'objectClass=service'): + s = s[1] + serv[s['cn'][0]] = s.get('args', []) + serv_dates[s['cn'][0]] = s.get('start', []) + services.append(Service(s['cn'][0], s.get('args', []), s.get('start', []))) + + # Retourne la liste des services à redémarrer + if not new: return services + + # Effacement d'un service + if new[0] == '-': + if new[1] == '-': + # Double -- on enlève quelque soit la date + remove_dn = 'cn=%s,%s' % (new[2:], services_dn) + else: + # On enlève uniquement si la date est passée + remove_dn = 'cn=%s,%s' % (new[1:], services_dn) + if not serv.has_key(new[1:]): + # Existe pas => rien à faire + return + keep_date = [] + for date in serv_dates[new[1:]]: + if time.time() < int(date): + keep_date.append(date) + if keep_date: + mods = [{'start': serv_dates[new[1:]]}, { 'start': keep_date }] + conn.modify_s(remove_dn, ldap.modlist.modifyModlist(*mods)) + remove_dn = None + + if remove_dn: + # Suppression + try: conn.delete_s(remove_dn) + except: pass + # Si n'existe pas => Erreur mais le résultat est là. + return + + serv_dn = 'cn=%s,%s' % (new, services_dn) + + # Conversion avant stockage dans la base + if isinstance(args, basestring): + args = [args] + args = map(lambda x:preattr(x)[1], args) + try: + start = [int(start)] + except TypeError: + pass + start = map(lambda x:preattr(x)[1], start) + + if new in serv.keys(): + modlist = [] + + new_args = [] + # Nouveaux arguments ? + for arg in args: + if arg not in serv[new]: + new_args.append(arg) + if new_args: + modlist += ldap.modlist.modifyModlist({'args': serv[new]}, + {'args': serv[new] + new_args}) + + new_date = [] + # Nouvelle date ? + for date in start: + if date not in serv_dates[new]: + new_date.append(date) + if new_date: + modlist += ldap.modlist.modifyModlist({'start': serv_dates[new]}, + {'start': serv_dates[new] + new_date}) + + if modlist: + try: + conn.modify_s(serv_dn, modlist) + except ldap.TYPE_OR_VALUE_EXISTS: + # Pas grave + pass + # else rien à faire + else: + # Entrée non présente -> ajout + modlist = ldap.modlist.addModlist({ 'objectClass': 'service', + 'cn': new, + 'args': args, + 'start': start }) + try: + conn.add_s(serv_dn, modlist) + except ldap.ALREADY_EXISTS: + # Existe déja => rien à faire + pass + +def services_to_restart(conn, old_attrs={}, new_attrs={}): + """Détermine quels sont les services à reconfigurer""" + old_attrs = dict((attributs.CRANS_ATTRIBUTES[key], value) for key,value in old_attrs.items()) + new_attrs = dict((attributs.CRANS_ATTRIBUTES[key], value) for key,value in new_attrs.items()) + + created_attr = [ attr for name in new_attrs.keys() if not name in old_attrs.keys() for attr in new_attrs[name]] + deleted_attr = [ attr for name in old_attrs.keys() if not name in new_attrs.keys() for attr in old_attrs[name]] + updated_attr = [ (old_attrs[name], new_attrs[name], + [ i for i in old_attrs[name] if i.value not in [ j.value for j in new_attrs[name]]], + [ i for i in new_attrs[name] if i.value not in [ j.value for j in old_attrs[name]]], + ) for name in set(new_attrs.keys()).intersection(old_attrs.keys()) if old_attrs[name][-1].value != new_attrs[name][-1].value ] + updated_attr_new = [ i for j in updated_attr for i in j[3]] + updated_attr_old = [ i for j in updated_attr for i in j[2]] + + services_to_restart = {} + for attr in [ attr for l in [created_attr, deleted_attr, updated_attr_new] for attr in l]: + for service in attrs_to_services.get(attr.__class__, []): + services_to_restart[service] = services_to_restart.get(service, []) + [attr] + + for service in services_to_restart.keys(): + for attr in services_to_restart[service]: + start = 0 + arg = [] + if service in services_to_args.keys(): + arg.extend(services_to_args[service](attr)) + if attr in updated_attr_new: + arg.extend(services_to_args[service](updated_attr_old[updated_attr_new.index(attr)])) + if service in services_to_time.keys(): + start = services_to_time[service](attr) + #print "%s,%s,%s" % (service, arg, start) + service_to_restart(conn, service, arg, start) + + + + + +# Identique à la classe dans ldap_crans.py +class Service: + """ Définit un service à redémarrer """ + def __init__(self, nom, args=[], start=[]): + """ + Nom du service + Liste des arguments + Liste d'horaires de démarrages + """ + + self.nom = nom + self.args = args + self.start = map(int, start) + + def __unicode__(self): + starting = self.start + starting.sort() + dates = u' et '.join(map(lambda t: t < time.time() and \ + u"maintenant" or time.strftime(date_format, + time.localtime(t)), + self.start)) + dates = u" à partir d%s %s" % (dates.startswith(u"maintenant") and u"e" or u"u", + dates) + return (u"%s(%s)%s" % (self.nom, + u','.join([i.decode("UTF-8") for i in self.args]), + dates)).replace(u" et maintenant", u"") + + def __str__(self): + return self.__unicode__().encode("utf-8", "ignore")