]>
git.ipfire.org Git - ipfire.org.git/blob - src/backend/mirrors.py
6652207848d8bc0c23765064ead2878b4b10f8f1
12 import tornado
.httpclient
13 import tornado
.netutil
16 from . import countries
17 from .misc
import Object
18 from .decorators
import *
20 class Mirrors(Object
):
21 def _get_mirrors(self
, query
, *args
):
22 res
= self
.db
.query(query
, *args
)
25 yield Mirror(self
.backend
, row
.id, data
=row
)
27 def _get_mirror(self
, query
, *args
):
28 res
= self
.db
.get(query
, *args
)
31 return Mirror(self
.backend
, res
.id, data
=res
)
34 mirrors
= self
._get
_mirrors
("SELECT * FROM mirrors \
35 WHERE enabled IS TRUE ORDER BY hostname")
39 @tornado.gen
.coroutine
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
)
74 def init(self
, id, data
=None):
82 return "<%s %s>" % (self
.__class
__.__name
__, self
.url
)
84 def __eq__(self
, other
):
85 if isinstance(other
, self
.__class
__):
86 return self
.id == other
.id
88 def __lt__(self
, other
):
89 if isinstance(other
, self
.__class
__):
90 return self
.hostname
< other
.hostname
94 url
= "%s://%s" % ("https" if self
.supports_https
else "http", self
.hostname
)
96 if not self
.path
.startswith("/"):
99 url
+= "%s" % self
.path
101 if not self
.path
.endswith("/"):
108 return self
.data
.hostname
112 return self
.data
.path
115 def supports_https(self
):
116 return self
.data
.supports_https
120 for addr
in self
.addresses4
:
123 for addr
in self
.addresses6
:
128 return self
.data
.owner
132 return self
.geoip
.get_location(self
.address
)
137 return self
.location
.latitude
142 return self
.location
.longitude
145 def country_code(self
):
146 return self
.data
.country_code
149 def country_name(self
):
150 return self
.geoip
.get_country_name(self
.country_code
)
154 return countries
.get_zone(self
.country_name
)
158 return self
.geoip
.get_asn(self
.address
)
162 filelist
= self
.db
.query("SELECT filename FROM mirror_files WHERE mirror=%s ORDER BY filename", self
.id)
163 return [f
.filename
for f
in filelist
]
169 def build_url(self
, filename
):
170 return urllib
.parse
.urljoin(self
.url
, filename
)
173 def last_update(self
):
174 return self
.data
.last_update
178 return self
.data
.state
180 def set_state(self
, state
):
181 logging
.debug("Setting state of %s to %s" % (self
.hostname
, state
))
183 if self
.state
== state
:
186 self
.db
.execute("UPDATE mirrors SET state = %s WHERE id = %s", state
, self
.id)
188 # Reload changed settings
189 self
.data
["state"] = state
193 return self
.data
.enabled
197 return not self
.enabled
199 @tornado.gen
.coroutine
201 logging
.debug("Running check for mirror %s" % self
.hostname
)
203 self
.db
.execute("UPDATE mirrors SET address = %s WHERE id = %s",
204 self
.address
, self
.id)
206 success
= yield self
.check_timestamp()
209 yield self
.check_filelist()
211 def check_state(self
, last_update
):
212 logging
.debug("Checking state of mirror %s" % self
.id)
215 self
.set_state("DOWN")
218 now
= datetime
.datetime
.utcnow()
220 time_delta
= now
- last_update
221 time_diff
= time_delta
.total_seconds()
223 time_down
= self
.settings
.get_int("mirrors_time_down", 3*24*60*60)
224 if time_diff
>= time_down
:
225 self
.set_state("DOWN")
228 time_outofsync
= self
.settings
.get_int("mirrors_time_outofsync", 6*60*60)
229 if time_diff
>= time_outofsync
:
230 self
.set_state("OUTOFSYNC")
235 @tornado.gen
.coroutine
236 def check_timestamp(self
):
237 http
= tornado
.httpclient
.AsyncHTTPClient()
240 response
= yield http
.fetch(self
.url
+ ".timestamp",
241 headers
={ "Pragma" : "no-cache" })
242 except tornado
.httpclient
.HTTPError
as e
:
243 logging
.error("Error getting timestamp from %s: %s" % (self
.hostname
, e
))
244 self
.set_state("DOWN")
247 except ssl
.SSLError
as e
:
248 logging
.error("SSL error when getting timestamp from %s: %s" % (self
.hostname
, e
))
249 self
.set_state("DOWN")
253 logging
.debug("Error getting timestamp from %s" % self
.hostname
)
254 self
.set_state("DOWN")
258 timestamp
= int(response
.body
.strip())
262 timestamp
= datetime
.datetime
.utcfromtimestamp(timestamp
)
264 self
.db
.execute("UPDATE mirrors SET last_update = %s WHERE id = %s",
268 self
.check_state(timestamp
)
270 logging
.debug("Successfully updated timestamp from %s" % self
.hostname
)
274 @tornado.gen
.coroutine
275 def check_filelist(self
):
276 # XXX need to remove data from disabled mirrors
280 http
= tornado
.httpclient
.AsyncHTTPClient()
283 response
= yield http
.fetch(self
.url
+ ".filelist",
284 headers
={ "Pragma" : "no-cache" })
285 except tornado
.httpclient
.HTTPError
as e
:
286 logging
.error("Error getting filelist from %s: %s" % (self
.hostname
, e
))
287 self
.set_state("DOWN")
291 logging
.debug("Error getting filelist from %s" % self
.hostname
)
294 # Drop the old filelist
295 self
.db
.execute("DELETE FROM mirror_files WHERE mirror = %s", self
.id)
298 for file in response
.body
.splitlines():
299 file = os
.path
.join(self
.prefix
, file.decode())
301 self
.db
.execute("INSERT INTO mirror_files(mirror, filename) \
302 VALUES(%s, %s)", self
.id, file)
304 logging
.debug("Successfully updated mirror filelist from %s" % self
.hostname
)
307 def development(self
):
308 return self
.data
.get("mirrorlist_devel", False)
311 def mirrorlist(self
):
312 return self
.data
.get("mirrorlist", False)
317 addrinfo
= socket
.getaddrinfo(self
.hostname
, 0, socket
.AF_UNSPEC
, socket
.SOCK_STREAM
)
319 raise Exception("Could not resolve %s" % self
.hostname
)
322 for family
, socktype
, proto
, canonname
, address
in addrinfo
:
323 if family
== socket
.AF_INET
:
324 address
, port
= address
325 elif family
== socket
.AF_INET6
:
326 address
, port
, flowid
, scopeid
= address
327 ret
.append((family
, address
))
332 def addresses6(self
):
333 return [address
for family
, address
in self
.addresses
if family
== socket
.AF_INET6
]
336 def addresses4(self
):
337 return [address
for family
, address
in self
.addresses
if family
== socket
.AF_INET
]