1
2
3
4 from twisted.internet import reactor
5 from zephir.backend.config import log
6 from zephir.entpool import IdPool
7 from zephir.backend.uucp_utils import uucp_pool, UUCPError
8 from zephir.backend.lib_backend import cx_pool
9 import random, os, time, traceback, base64
10 from hashlib import md5
11
13
15 super(ZephirIdPool,self).__init__(code_ent)
16
17 self.reserved = []
18 self.pending = []
19 self.free = [(0,self.max_id)]
20 self.free_space = self.max_id + 1
21
23 """renvoie les identifiants minimum/maximum des nouvelles plages
24 et met en place la réservation en attendant une confirmation
25 """
26 if length <= 0:
27 raise InternalError, "Intervalle invalide : %s" % length
28
29
30
31 if length > self.free_space:
32 raise InternalError, "Pas assez d'identifiants disponibles"
33
34 ranges = []
35 for r_min, r_max in self.free:
36 r_len = r_max - r_min + 1
37 if length <= r_len:
38 ranges.append((r_min, r_min+length-1))
39 break
40 else:
41
42 ranges.append((r_min, r_max))
43 length = length - r_len
44 self.pending.append(ranges)
45
46 return [(self.id_to_string(minid), self.id_to_string(maxid)) for minid, maxid in ranges]
47
49 """ajoute une plage dans la liste des plages réservées
50 """
51
52 insert_index = 0
53
54 if self.reserved:
55 for (r_min, r_max, id_s) in self.reserved:
56 if r_min > maxid:
57
58 break
59 insert_index += 1
60
61 self.reserved.insert(insert_index, (minid, maxid, id_serveur))
62 self.update_free_ranges()
63
65 """met à jour la liste des plages disponibles
66 en fonction des plages réservées
67 """
68 reserved = [(r_min, r_max) for r_min, r_max, id_s in self.reserved]
69
70 for p_rng in self.pending:
71 reserved.extend(p_rng)
72
73 free = []
74 start = 0
75 free_space = 0
76 for r_min, r_max in reserved:
77 if r_min != start:
78 free.append((start, r_min - 1))
79 free_space += r_max - r_min + 1
80 start = r_max + 1
81 if start <= self.max_id:
82 free.append((start, self.max_id))
83 free_space += self.max_id - start +1
84 self.free = free
85 self.free_space = free_space
86
88 """réserve une plage spécifique si elle est disponible
89 (utile pour bloquer des intervalles non gérés par zephir)
90 """
91
92 valid = False
93 for r_min, r_max in self.free:
94 if r_min <= minid and r_max > minid:
95 if r_max >= maxid:
96
97 valid = True
98
99 self._add_reserved(minid, maxid)
100 break
101 return valid
102
104 """annule une réservation de plages
105 """
106 if ranges in self.pending:
107 self.pending.remove(ranges)
108 self.update_free_ranges()
109
110 - def validate(self, id_serveur, ranges):
111 """valide la prise en compte de la plage réservée
112 """
113 if ranges in self.pending:
114 self.pending.remove(ranges)
115 for r_min, r_max in ranges:
116 self._add_reserved(r_min, r_max, id_serveur)
117 return True
118 return False
119
121 """représentation par défaut de l'objet
122 """
123 descr = "ENT %s" % self.code_ent
124 if not self.pending:
125 descr += " - identifiants disponibles: %s" % (self.free_space)
126 if self.pending:
127 rngs=[]
128 for ranges in self.pending:
129 for r_min, r_max in ranges:
130 rngs.append("%s->%s" % (self.id_to_string(r_min), self.id_to_string(r_max)))
131 descr += " - plage en attente de validation : %s" % " - ".join(rngs)
132 return descr
133
135 """renvoie les données actuelles sous forme de dictionnaire pour sauvegarde en base de données
136 """
137 return {'code_ent':self.code_ent, 'free_space':str(self.free_space)}
138
140 """classe de gestion d'un ensemble de pool d'identifiants pour les ENT
141 se reporter au Schéma Directeur des Espaces Numériques de Travail
142 http://www.educnet.education.fr/services/ent/sdet
143 """
144
146
147 self.serveur_pool = serveur_pool
148 self.id_pools = self.load_pools()
149 log.msg("chargement des pools d'identifiants ENT")
150 for pool in self.id_pools.values():
151 log.msg(pool)
152 self.timeouts = {}
153
155 """initialise les pools d'identifiant depuis les données stockées en base
156 """
157
158 cursor = cx_pool.create()
159 cursor.execute("select code_ent, serveur, min, max from ent_id_ranges order by code_ent, min")
160 ranges = cursor.fetchall()
161 cx_pool.close(cursor)
162
163 pools = {}
164 if ranges:
165 for code_ent, id_serveur, minid, maxid in ranges:
166 if code_ent not in pools:
167 pools[code_ent] = ZephirIdPool(code_ent)
168 pools[code_ent]._add_reserved(minid, maxid, id_serveur)
169 return pools
170
172 """renvoie les informations d'un pool (plages réservées et nombre d'identifiants disponibles)
173 code_ent: code ENT du pool à renvoyer (tous si rien)
174 """
175 data = []
176 if code_ent:
177 if code_ent in self.id_pools:
178 pools = [self.id_pools[code_ent]]
179 else:
180 pools = []
181 else:
182 pools = self.id_pools.values()
183 for pool in pools:
184 ranges = []
185 for minid, maxid, id_s in pool.reserved:
186 ranges.append([pool.id_to_string(minid), pool.id_to_string(maxid), id_s])
187 free_ranges = []
188 for minid, maxid in pool.free:
189 free_ranges.append([pool.id_to_string(minid), pool.id_to_string(maxid)])
190 data.append([pool.code_ent, pool.free_space, ranges, free_ranges])
191 return 1, data
192
194 """renvoie la liste des codes ent connus
195 """
196 return self.id_pools.keys()
197
199 """réserve une plage dans le pool d'adresses d'un ent
200 - le code ent est récupéré dans la configuration du serveur
201 - si le pool n'a jamais été utilisé, on l'initialise
202 - le pool se met en attente de validation et bloque les autres demandes en attendant
203 (si la validation n'est pas faite après un certain temps, on annule la réservation)
204 - les données sont envoyées par uucp dans un fichier dont on garde le md5
205 (une action de validation est aussi programmée)
206 """
207
208 try:
209 serv = self.serveur_pool[int(id_serveur)]
210 except:
211 return 0, "Serveur inconnu"
212
213 f_cle = os.path.join(serv.get_confdir(), 'cle_publique')
214 data_cle = file(f_cle).read().strip()
215 try:
216 assert data_cle == """command="sudo /usr/sbin/uucico2 -D -l" %s""" % base64.decodestring(cle_pub)
217 except:
218 return 0, "Cle incorrecte pour le serveur %s" % str(id_serveur)
219 try:
220 code_ent = serv.parsedico()['code_ent']
221 assert len(code_ent) == 2
222 except:
223 return 0, "Code ENT invalide pour ce serveur"
224
225 if code_ent not in self.timeouts:
226 self.timeouts[code_ent] = {}
227 if self.timeouts[code_ent].get(id_serveur,None):
228 return 0, "Une réservation est encore en cours pour ce serveur"
229
230 try:
231 if not code_ent in self.id_pools:
232
233 self.id_pools[code_ent] = ZephirIdPool(code_ent)
234 pool = self.id_pools[code_ent]
235 ranges = pool.get_range(nb_id)
236 except Exception, e:
237 return 0, (str(e))
238
239 random_id = self.send_interval(serv, ranges)
240
241
242
243 self.timeouts[code_ent][id_serveur] = (random_id, reactor.callLater(30, self.cancel, pool, id_serveur, ranges))
244 log.msg(str(pool))
245 return 1, "OK"
246
247 - def cancel(self, pool, id_serveur, ranges):
248 """annule une réservation après un délai d'attente
249 """
250 code_ent = pool.code_ent
251 if id_serveur in self.timeouts.get(code_ent,{}):
252 log.msg("ENT %s - timeout de la reservation (serveur %s)" % (str(code_ent), str(id_serveur)))
253 pool.cancel(ranges)
254 del self.timeouts[code_ent][id_serveur]
255
257 """Valide une réservation envoyée à un serveur
258 - vérifie que l'id du serveur et le md5 renvoyé correspond à la réservation en cours
259 - met à jour le pool d'identifiant de ce code ent
260 """
261 err = "Réservation invalide"
262 if id_serveur in self.timeouts.get(code_ent,{}):
263 pool = self.id_pools[code_ent]
264 if pool.pending:
265 stored_id, call_id = self.timeouts[code_ent][id_serveur]
266 try:
267
268 num_ranges = [(pool.string_to_id(minid),pool.string_to_id(maxid)) for minid, maxid in ranges]
269 assert num_ranges in pool.pending
270 assert md5_id == stored_id
271 if pool.validate(id_serveur, num_ranges):
272
273 call_id.cancel()
274 del self.timeouts[code_ent][id_serveur]
275 return self.store_range(code_ent, id_serveur, pool, num_ranges)
276 except:
277 traceback.print_exc()
278
279 pass
280 log.msg("ENT %s - Plage(s) invalide(s) demandée par le serveur %s : " % (str(code_ent), str(id_serveur)), str(ranges))
281 return 0, err
282
283 - def store_range(self, code_ent, id_serveur, pool, ranges):
284 """enregistre une plage réservée dans la base de données"""
285 date = str(time.ctime())
286 cursor = cx_pool.create()
287 for cur_min, cur_max in ranges:
288 query = """insert into ent_id_ranges (code_ent, serveur, min, max, date_valid) values(%s, %s, %s, %s, %s)"""
289 params = (code_ent, id_serveur, cur_min, cur_max, date)
290 cursor.execute(query, params)
291 if id_serveur == -1:
292
293 log.msg("ENT %s - plage bloquée : " % str(code_ent), pool.id_to_string(cur_min), "->", pool.id_to_string(cur_max))
294 else:
295 log.msg("ENT %s - plage validée par le serveur %s : " % (str(code_ent), str(id_serveur)) \
296 , pool.id_to_string(cur_min) , "->", pool.id_to_string(cur_max))
297 cx_pool.commit(cursor)
298 return 1, "OK"
299
301 """Réservation manuelle d'une plage d'identifiant
302 """
303 code_ent = minid[0]+minid[3]
304 if (code_ent == maxid[0] + maxid[3]):
305 if not code_ent in self.id_pools:
306
307 self.id_pools[code_ent] = ZephirIdPool(code_ent)
308 pool = self.id_pools[code_ent]
309 else:
310
311 return 0, "code ENT invalide"
312 minid = pool.string_to_id(minid)
313 maxid = pool.string_to_id(maxid)
314 if minid > maxid:
315 return 0, "Plage d'identifiants invalide"
316 else:
317
318 if pool.reserve_range(minid, maxid):
319 return self.store_range(code_ent, -1, pool, [(minid, maxid)])
320 else:
321 return 0, "Plage non disponible"
322
324 """envoi d'un fichier indiquant l'intervalle réservé
325 """
326
327 rand = random.SystemRandom().random()
328 random_id = md5(str(rand))
329 for rng in ranges:
330 random_id.update(str(rng))
331 random_id = random_id.hexdigest()
332 id_uucp = str(serv.get_rne()) + '-' + str(serv.id_s)
333 try:
334 serveur_dir = serv.get_confdir()
335
336 archive = "ent_ids"
337 content = random_id
338 for r_min, r_max in ranges:
339 content += "\n%s\t%s" % (r_min, r_max)
340 f_arc = open(os.path.join(serveur_dir, archive), 'w')
341 f_arc.write(content)
342 f_arc.close()
343
344 cmd_tar = ['cd ', serveur_dir, ';', '/bin/tar', '--same-owner', '-chpf', archive+'.tar', 'ent_ids']
345
346 cmd_tar.append('>/dev/null 2>&1')
347 res = os.system(" ".join(cmd_tar))
348 cmd_checksum = """cd %s ;md5sum -b %s.tar > %s.md5""" % (serveur_dir, archive, archive)
349 res = os.system(cmd_checksum)
350
351 res = uucp_pool.add_cmd(id_uucp, "zephir_client update_ent_ids")
352 res = uucp_pool.add_file(id_uucp, os.path.join(serveur_dir, archive+".tar"))
353 os.unlink(os.path.join(serveur_dir, archive))
354 except Exception, e:
355 return 0, "Erreur de préparation du fichier (%s)" % str(e)
356 return random_id
357