]>
git.ipfire.org Git - ipfire.org.git/blob - src/backend/mirrors.py
12 import tornado
.httpclient
13 import tornado
.iostream
14 import tornado
.netutil
17 from . import countries
18 from .misc
import Object
19 from .decorators
import *
21 class Mirrors(Object
):
22 def _get_mirrors(self
, query
, *args
):
23 res
= self
.db
.query(query
, *args
)
26 yield Mirror(self
.backend
, row
.id, data
=row
)
28 def _get_mirror(self
, query
, *args
):
29 res
= self
.db
.get(query
, *args
)
32 return Mirror(self
.backend
, res
.id, data
=res
)
35 mirrors
= self
._get
_mirrors
("SELECT * FROM mirrors \
36 WHERE enabled IS TRUE ORDER BY hostname")
40 async def check_all(self
):
42 with self
.db
.transaction():
45 def get_for_download(self
, filename
, country_code
=None):
46 # Try to find a good mirror for this country first
48 zone
= countries
.get_zone(country_code
)
50 mirror
= self
._get
_mirror
("SELECT mirrors.* FROM mirror_files files \
51 LEFT JOIN mirrors ON files.mirror = mirrors.id \
52 WHERE files.filename = %s \
53 AND mirrors.enabled IS TRUE AND mirrors.state = %s \
54 AND mirrors.country_code = ANY(%s) \
55 ORDER BY RANDOM() LIMIT 1", filename
, "UP",
56 countries
.get_in_zone(zone
))
61 # Get a random mirror that serves the file
62 return self
._get
_mirror
("SELECT mirrors.* FROM mirror_files files \
63 LEFT JOIN mirrors ON files.mirror = mirrors.id \
64 WHERE files.filename = %s \
65 AND mirrors.enabled IS TRUE AND mirrors.state = %s \
66 ORDER BY RANDOM() LIMIT 1", filename
, "UP")
68 def get_by_hostname(self
, hostname
):
69 return self
._get
_mirror
("SELECT * FROM mirrors \
70 WHERE hostname = %s", hostname
)
72 def get_by_countries(self
):
77 mirrors
[m
.country
].append(m
)
79 mirrors
[m
.country
] = [m
]
85 def init(self
, id, data
=None):
93 return "<%s %s>" % (self
.__class
__.__name
__, self
.url
)
95 def __eq__(self
, other
):
96 if isinstance(other
, self
.__class
__):
97 return self
.id == other
.id
99 def __lt__(self
, other
):
100 if isinstance(other
, self
.__class
__):
101 return self
.hostname
< other
.hostname
105 url
= "%s://%s" % ("https" if self
.supports_https
else "http", self
.hostname
)
107 if not self
.path
.startswith("/"):
110 url
+= "%s" % self
.path
112 if not self
.path
.endswith("/"):
119 return self
.data
.hostname
123 return self
.data
.path
126 def supports_https(self
):
127 return self
.data
.supports_https
131 for addr
in self
.addresses4
:
134 for addr
in self
.addresses6
:
139 return self
.data
.owner
143 return self
.geoip
.get_location(self
.address
)
148 return self
.location
.latitude
153 return self
.location
.longitude
157 return iso3166
.countries
.get(self
.country_code
)
160 def country_code(self
):
161 return self
.data
.country_code
164 def country_name(self
):
165 return self
.geoip
.get_country_name(self
.country_code
)
169 return countries
.get_zone(self
.country_name
)
173 return self
.geoip
.get_asn(self
.address
)
177 filelist
= self
.db
.query("SELECT filename FROM mirror_files WHERE mirror=%s ORDER BY filename", self
.id)
178 return [f
.filename
for f
in filelist
]
184 def build_url(self
, filename
):
185 return urllib
.parse
.urljoin(self
.url
, filename
)
188 def last_update(self
):
189 return self
.data
.last_update
193 return self
.data
.state
195 def set_state(self
, state
):
196 logging
.debug("Setting state of %s to %s" % (self
.hostname
, state
))
198 if self
.state
== state
:
201 self
.db
.execute("UPDATE mirrors SET state = %s WHERE id = %s", state
, self
.id)
203 # Reload changed settings
204 self
.data
["state"] = state
208 return self
.data
.enabled
212 return not self
.enabled
214 async def check(self
):
215 logging
.debug("Running check for mirror %s" % self
.hostname
)
217 self
.db
.execute("UPDATE mirrors SET address = %s WHERE id = %s",
218 self
.address
, self
.id)
220 success
= await self
.check_timestamp()
222 await self
.check_filelist()
224 def check_state(self
, last_update
):
225 logging
.debug("Checking state of mirror %s" % self
.id)
228 self
.set_state("DOWN")
231 now
= datetime
.datetime
.utcnow()
233 time_delta
= now
- last_update
234 time_diff
= time_delta
.total_seconds()
236 time_down
= self
.settings
.get_int("mirrors_time_down", 3*24*60*60)
237 if time_diff
>= time_down
:
238 self
.set_state("DOWN")
241 time_outofsync
= self
.settings
.get_int("mirrors_time_outofsync", 6*60*60)
242 if time_diff
>= time_outofsync
:
243 self
.set_state("OUTOFSYNC")
248 async def check_timestamp(self
):
249 http
= tornado
.httpclient
.AsyncHTTPClient()
252 response
= await http
.fetch(self
.url
+ ".timestamp",
253 headers
={ "Pragma" : "no-cache" })
254 except tornado
.httpclient
.HTTPError
as e
:
255 logging
.warning("Error getting timestamp from %s: %s" % (self
.hostname
, e
))
256 self
.set_state("DOWN")
259 except ssl
.SSLError
as e
:
260 logging
.warning("SSL error when getting timestamp from %s: %s" % (self
.hostname
, e
))
261 self
.set_state("DOWN")
264 except tornado
.iostream
.StreamClosedError
as e
:
265 logging
.warning("Connection closed unexpectedly for %s: %s" % (self
.hostname
, e
))
266 self
.set_state("DOWN")
270 logging
.warning("Could not connect to %s: %s" % (self
.hostname
, e
))
271 self
.set_state("DOWN")
275 logging
.warning("Error getting timestamp from %s" % self
.hostname
)
276 self
.set_state("DOWN")
280 timestamp
= int(response
.body
.strip())
284 timestamp
= datetime
.datetime
.utcfromtimestamp(timestamp
)
286 self
.db
.execute("UPDATE mirrors SET last_update = %s WHERE id = %s",
290 self
.check_state(timestamp
)
292 logging
.debug("Successfully updated timestamp from %s" % self
.hostname
)
296 async def check_filelist(self
):
297 # XXX need to remove data from disabled mirrors
301 http
= tornado
.httpclient
.AsyncHTTPClient()
304 response
= await http
.fetch(self
.url
+ ".filelist",
305 headers
={ "Pragma" : "no-cache" })
306 except tornado
.httpclient
.HTTPError
as e
:
307 logging
.warning("Error getting filelist from %s: %s" % (self
.hostname
, e
))
308 self
.set_state("DOWN")
312 logging
.debug("Error getting filelist from %s" % self
.hostname
)
315 # Drop the old filelist
316 self
.db
.execute("DELETE FROM mirror_files WHERE mirror = %s", self
.id)
319 for file in response
.body
.splitlines():
320 file = os
.path
.join(self
.prefix
, file.decode())
322 self
.db
.execute("INSERT INTO mirror_files(mirror, filename) \
323 VALUES(%s, %s)", self
.id, file)
325 logging
.debug("Successfully updated mirror filelist from %s" % self
.hostname
)
328 def development(self
):
329 return self
.data
.get("mirrorlist_devel", False)
332 def mirrorlist(self
):
333 return self
.data
.get("mirrorlist", False)
337 addrinfo
= socket
.getaddrinfo(self
.hostname
, 0, socket
.AF_UNSPEC
, socket
.SOCK_STREAM
)
340 for family
, socktype
, proto
, canonname
, address
in addrinfo
:
341 if family
== socket
.AF_INET
:
342 address
, port
= address
343 elif family
== socket
.AF_INET6
:
344 address
, port
, flowid
, scopeid
= address
345 ret
.append((family
, address
))
350 def addresses6(self
):
351 return [address
for family
, address
in self
.addresses
if family
== socket
.AF_INET6
]
354 def addresses4(self
):
355 return [address
for family
, address
in self
.addresses
if family
== socket
.AF_INET
]