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