]>
git.ipfire.org Git - ipfire.org.git/blob - src/backend/mirrors.py
11 import tornado
.httpclient
12 import tornado
.netutil
15 from . import countries
16 from .misc
import Object
17 from .decorators
import *
19 class Mirrors(Object
):
20 def _get_mirrors(self
, query
, *args
):
21 res
= self
.db
.query(query
, *args
)
24 yield Mirror(self
.backend
, row
.id, data
=row
)
26 def _get_mirror(self
, query
, *args
):
27 res
= self
.db
.get(query
, *args
)
30 return Mirror(self
.backend
, res
.id, data
=res
)
33 mirrors
= self
._get
_mirrors
("SELECT * FROM mirrors \
34 WHERE enabled IS TRUE ORDER BY hostname")
38 @tornado.gen
.coroutine
41 with self
.db
.transaction():
44 def get_for_download(self
, filename
, country_code
=None):
45 # Try to find a good mirror for this country first
47 zone
= countries
.get_zone(country_code
)
49 mirror
= self
._get
_mirror
("SELECT mirrors.* FROM mirror_files files \
50 LEFT JOIN mirrors ON files.mirror = mirrors.id \
51 WHERE files.filename = %s \
52 AND mirrors.enabled IS TRUE AND mirrors.state = %s \
53 AND mirrors.country_code = ANY(%s) \
54 ORDER BY RANDOM() LIMIT 1", filename
, "UP",
55 countries
.get_in_zone(zone
))
60 # Get a random mirror that serves the file
61 return self
._get
_mirror
("SELECT mirrors.* FROM mirror_files files \
62 LEFT JOIN mirrors ON files.mirror = mirrors.id \
63 WHERE files.filename = %s \
64 AND mirrors.enabled IS TRUE AND mirrors.state = %s \
65 ORDER BY RANDOM() LIMIT 1", filename
, "UP")
67 def get_by_hostname(self
, hostname
):
68 return self
._get
_mirror
("SELECT * FROM mirrors \
69 WHERE hostname = %s", hostname
)
73 def init(self
, id, data
=None):
81 return "<%s %s>" % (self
.__class
__.__name
__, self
.url
)
83 def __eq__(self
, other
):
84 if isinstance(other
, self
.__class
__):
85 return self
.id == other
.id
87 def __lt__(self
, other
):
88 if isinstance(other
, self
.__class
__):
89 return self
.hostname
< other
.hostname
93 url
= "%s://%s" % ("https" if self
.supports_https
else "http", self
.hostname
)
95 if not self
.path
.startswith("/"):
98 url
+= "%s" % self
.path
100 if not self
.path
.endswith("/"):
107 return self
.data
.hostname
111 return self
.data
.path
114 def supports_https(self
):
115 return self
.data
.supports_https
119 for addr
in self
.addresses4
:
122 for addr
in self
.addresses6
:
127 return self
.data
.owner
131 return self
.geoip
.get_location(self
.address
)
136 return self
.location
.latitude
141 return self
.location
.longitude
144 def country_code(self
):
145 return self
.data
.country_code
148 def country_name(self
):
149 return self
.geoip
.get_country_name(self
.country_code
)
153 return countries
.get_zone(self
.country_name
)
157 return self
.geoip
.get_asn(self
.address
)
161 filelist
= self
.db
.query("SELECT filename FROM mirror_files WHERE mirror=%s ORDER BY filename", self
.id)
162 return [f
.filename
for f
in filelist
]
168 def build_url(self
, filename
):
169 return urllib
.parse
.urljoin(self
.url
, filename
)
172 def last_update(self
):
173 return self
.data
.last_update
177 return self
.data
.state
179 def set_state(self
, state
):
180 logging
.info("Setting state of %s to %s" % (self
.hostname
, state
))
182 if self
.state
== state
:
185 self
.db
.execute("UPDATE mirrors SET state = %s WHERE id = %s", state
, self
.id)
187 # Reload changed settings
188 self
.data
["state"] = state
192 return self
.data
.enabled
196 return not self
.enabled
198 @tornado.gen
.coroutine
200 logging
.info("Running check for mirror %s" % self
.hostname
)
202 self
.db
.execute("UPDATE mirrors SET address = %s WHERE id = %s",
203 self
.address
, self
.id)
205 success
= yield self
.check_timestamp()
208 yield self
.check_filelist()
210 def check_state(self
, last_update
):
211 logging
.debug("Checking state of mirror %s" % self
.id)
214 self
.set_state("DOWN")
217 now
= datetime
.datetime
.utcnow()
219 time_delta
= now
- last_update
220 time_diff
= time_delta
.total_seconds()
222 time_down
= self
.settings
.get_int("mirrors_time_down", 3*24*60*60)
223 if time_diff
>= time_down
:
224 self
.set_state("DOWN")
227 time_outofsync
= self
.settings
.get_int("mirrors_time_outofsync", 6*60*60)
228 if time_diff
>= time_outofsync
:
229 self
.set_state("OUTOFSYNC")
234 @tornado.gen
.coroutine
235 def check_timestamp(self
):
236 http
= tornado
.httpclient
.AsyncHTTPClient()
239 response
= yield http
.fetch(self
.url
+ ".timestamp",
240 headers
={ "Pragma" : "no-cache" })
241 except tornado
.httpclient
.HTTPError
as e
:
242 logging
.error("Error getting timestamp from %s: %s" % (self
.hostname
, e
))
243 self
.set_state("DOWN")
247 logging
.debug("Error getting timestamp from %s" % self
.hostname
)
248 self
.set_state("DOWN")
252 timestamp
= int(response
.body
.strip())
256 timestamp
= datetime
.datetime
.utcfromtimestamp(timestamp
)
258 self
.db
.execute("UPDATE mirrors SET last_update = %s WHERE id = %s",
262 self
.check_state(timestamp
)
264 logging
.info("Successfully updated timestamp from %s" % self
.hostname
)
267 @tornado.gen
.coroutine
268 def check_filelist(self
):
269 # XXX need to remove data from disabled mirrors
273 http
= tornado
.httpclient
.AsyncHTTPClient()
276 response
= yield http
.fetch(self
.url
+ ".filelist",
277 headers
={ "Pragma" : "no-cache" })
278 except tornado
.httpclient
.HTTPError
as e
:
279 logging
.error("Error getting filelist from %s: %s" % (self
.hostname
, e
))
280 self
.set_state("DOWN")
284 logging
.debug("Error getting filelist from %s" % self
.hostname
)
287 # Drop the old filelist
288 self
.db
.execute("DELETE FROM mirror_files WHERE mirror = %s", self
.id)
291 for file in response
.body
.splitlines():
292 file = os
.path
.join(self
.prefix
, file.decode())
294 self
.db
.execute("INSERT INTO mirror_files(mirror, filename) \
295 VALUES(%s, %s)", self
.id, file)
297 logging
.info("Successfully updated mirror filelist from %s" % self
.hostname
)
300 def development(self
):
301 return self
.data
.get("mirrorlist_devel", False)
304 def mirrorlist(self
):
305 return self
.data
.get("mirrorlist", False)
310 addrinfo
= socket
.getaddrinfo(self
.hostname
, 0, socket
.AF_UNSPEC
, socket
.SOCK_STREAM
)
312 raise Exception("Could not resolve %s" % self
.hostname
)
315 for family
, socktype
, proto
, canonname
, address
in addrinfo
:
316 if family
== socket
.AF_INET
:
317 address
, port
= address
318 elif family
== socket
.AF_INET6
:
319 address
, port
, flowid
, scopeid
= address
320 ret
.append((family
, address
))
325 def addresses6(self
):
326 return [address
for family
, address
in self
.addresses
if family
== socket
.AF_INET6
]
329 def addresses4(self
):
330 return [address
for family
, address
in self
.addresses
if family
== socket
.AF_INET
]