aboutsummaryrefslogtreecommitdiffstats
path: root/modules/mga-mirrors/files/check_mirrors_status
diff options
context:
space:
mode:
Diffstat (limited to 'modules/mga-mirrors/files/check_mirrors_status')
-rwxr-xr-xmodules/mga-mirrors/files/check_mirrors_status271
1 files changed, 271 insertions, 0 deletions
diff --git a/modules/mga-mirrors/files/check_mirrors_status b/modules/mga-mirrors/files/check_mirrors_status
new file mode 100755
index 00000000..9c00ac8d
--- /dev/null
+++ b/modules/mga-mirrors/files/check_mirrors_status
@@ -0,0 +1,271 @@
+#!/usr/bin/ruby
+
+require 'date'
+require 'net/http'
+require 'optparse'
+require 'thread'
+require 'uri'
+
+def get_dates(base, archs_per_distro, optional=true)
+ r = {}
+ begin
+ r['base'] = get_timestamp(base)
+ rescue Net::OpenTimeout, Timeout::Error, ArgumentError, NoMethodError, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Errno::ECONNRESET, IOError, OpenSSL::SSL::SSLError => e
+ end
+
+ archs_per_distro.each{|d, archs|
+ r[d] = {}
+ archs.each{|a|
+ begin
+ r[d][a] = get_date(base, d, a)
+ rescue Net::OpenTimeout, Timeout::Error, ArgumentError, NoMethodError, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Errno::ECONNRESET, IOError, OpenSSL::SSL::SSLError => e
+ if !optional then
+ STDERR.puts "Failed to fetch #{version_url(base, d, a)}"
+ raise
+ end
+ end
+ }
+ }
+ r
+end
+
+def get_mirrors
+ # TODO Get it from the DB
+ mirrors = []
+ url = nil
+ tier1 = false
+ fetch_url("https://mirrors.mageia.org/").each_line{|l|
+ if l =~ /rsync.mageia.org/ then
+ tier1 = true
+ next
+ end
+ if l=~ /<\/tr>/ && !url.nil? then
+ if tier1 then
+ mirrors.prepend url
+ tier1 = false
+ else
+ mirrors.append url
+ end
+ url = nil
+ next
+ end
+ next unless l =~ /https?:.*>http/
+ # No need to check twice mirrors available in http + https
+ if !url.nil? && url =~ /https:/ && l =~ /https:\/\//
+ # Skip http:// if https:// already seen for current mirror
+ # If the are in the other order http one will just be replaced
+ next
+ end
+ url = l.sub(/<a href="(http[^"]*)".*\n/, '\1')
+ url += "/" unless url =~ /\/$/
+ }
+ mirrors
+end
+
+def fetch_url(url, redirect_limit = 3)
+ return if redirect_limit < 0
+ if url =~ /^\// then
+ open(url){|f|
+ return f.read
+ }
+ else
+ uri = URI.parse(url)
+ http = Net::HTTP.new(uri.host, uri.port)
+ http.open_timeout = 30
+ http.read_timeout = 30
+ if uri.scheme == 'https' then
+ http.use_ssl = true
+ end
+ # Ruby 1.8.7 doesn't set a default User-Agent which causes at
+ # least one mirror to return 403
+ response = http.get(uri.path, {'User-Agent' => 'check_mirrors'})
+ case response
+ when Net::HTTPSuccess then
+ return response.body
+ when Net::HTTPRedirection then
+ location = response['location']
+ # Make location absolute if it was not
+ if location =~ /:\/\// then
+ fetch_url(location, redirect_limit - 1)
+ else
+ uri.path = location
+ fetch_url(uri.to_s, redirect_limit - 1)
+ end
+ end
+ end
+end
+
+def timestamp_url(url)
+ "#{url}mageia_timestamp"
+end
+
+def get_timestamp(url)
+ ti = fetch_url(timestamp_url(url)).to_i
+ if ti == 0 then
+ return nil
+ end
+ return DateTime.strptime(ti.to_s, '%s')
+end
+
+def parse_version(version)
+ date = version.sub(/.* (........ ..:..)$/, '\1').rstrip
+ DateTime.strptime(date, '%Y%m%d %H:%M')
+end
+
+def version_url(url, distrib, arch)
+ "#{url}distrib/#{distrib}/#{arch}/VERSION"
+end
+
+def get_date(url, distrib, arch)
+ return parse_version(fetch_url(version_url(url, distrib, arch)))
+end
+
+def format_age(ref_time, time)
+ return " <td class='broken'>X</td>" unless ref_time and time
+
+ diff = ref_time - time
+ cls = 'broken'
+ if diff == 0 then
+ cls = 'ok'
+ elsif diff < 0.5 then
+ cls = 'almost'
+ elsif diff < 2 then
+ cls = 'bad'
+ end
+ if cls == 'ok' then
+ return " <td class='#{cls}'>&nbsp;</td>"
+ else
+ return " <td class='#{cls}'>#{time.strftime("%F %R")}</td>"
+ end
+end
+
+def print_output(archs_per_distro, mirrors, ref_times, times)
+ puts "<html><head><title>Mageia Mirror Status #{Time.now.utc.strftime("%F")}</title>
+<link rel=\"icon\" type=\"image/png\" href=\"//www.mageia.org/g/favicon.png\">
+<style>
+td.broken {background-color:#FF0033;}
+td.bad {background-color:#FF9933;}
+td.almost {background-color:#CCFF66;}
+td.ok {background-color:#00FF66;}
+
+td {text-align:center;}
+td.name {text-align:left;}
+
+td.sep {width:12px;}
+table.legend td {padding:4px;}
+
+th {background-color:#EEEEEE;}
+</style>
+</head>
+<body>"
+ puts "Last checked on #{Time.now.utc.strftime("%F %R %Z")}<br/>"
+ puts "<table class='legend'><tr><td class='ok'>Up to date</td><td class='almost'>Less than 12h old</td><td class='bad'>Less than 2 days old</td><td class='broken'>Old or broken</td></tr></table>"
+ puts "<table><thead>"
+ puts "<tr><td/>"
+ puts "<td/><th>Base directory</th>"
+ archs_per_distro.each{|d, archs|
+ nb_arches = archs.size
+ puts " <td/><th colspan='#{nb_arches}'>#{d}</th>"
+ }
+ puts "</tr>"
+ puts "<tr><td/><td/><td/>"
+ archs_per_distro.each{|d, archs|
+ puts " <td class='sep' />"
+ archs.each{|a|
+ puts " <th>#{a}</th>"
+ }
+ }
+ puts "</tr></thead>"
+ puts "<tbody>"
+ puts "<tr><td class='name'>Reference</td>"
+ puts " <td class='sep' />"
+ puts " <td>#{!ref_times['base'].nil? ? ref_times['base'].strftime("%F %R") : "?"}</td>"
+ archs_per_distro.each{|d, archs|
+ puts " <td class='sep' />"
+ archs.each{|a|
+ puts " <td>#{ref_times[d][a].strftime("%F %R")}</td>"
+ }
+ }
+ puts "</tr>"
+
+ mirrors.each{|u|
+ puts "<tr><td class='name'><a href='#{u}'>#{u}</a></td>"
+ puts " <td class='sep' />"
+ puts format_age(ref_times['base'], times[u]['base'])
+ archs_per_distro.each{|d, archs|
+ puts " <td class='sep' />"
+ archs.each{|a|
+ puts format_age(ref_times[d][a], times[u][d][a])
+ }
+ }
+ puts "</tr>"
+ }
+ puts "</tbody></table>"
+ puts "</body></html>"
+end
+
+
+
+# Defaults
+ref = 'http://repository.mageia.org/'
+archs_per_distro = {
+ 'cauldron' => ['i686', 'x86_64', 'armv7hl', 'aarch64'],
+ '9' => ['i586', 'x86_64', 'armv7hl', 'aarch64']
+}
+parallel = 8
+
+OptionParser.new {|opts|
+ opts.banner = "Usage: #{$0} [options]"
+ opts.on("--repository URL",
+ "Reference repository. Default: #{ref}") {
+ |url| ref = url
+ }
+ opts.on("--parallel n", Integer,
+ "Max number of parallel connections. Default: #{parallel}") {
+ |n| $parallel = n
+ }
+ opts.on("--output file",
+ "Write output into given file. Default to STDOUT") {
+ |f| $stdout.reopen(f, "w")
+ }
+}.parse!
+
+# Get dates from the reference repository, and fail if some requested distros
+# or archs are missing
+ref_times = get_dates(ref, archs_per_distro, false)
+
+# Get the list of mirror URLs to check
+mirrors = get_mirrors
+
+workqueue = Queue.new
+times = {}
+
+# Create all the thread and have them loop on the work queue
+threads = (1..parallel).map{|n|
+ Thread.new {
+ loop do
+ u = workqueue.pop
+ break if u == :exit
+ times[u] = get_dates(u, archs_per_distro)
+ end
+ }
+}
+
+# Push all mirrors into the queue
+mirrors.each{|u|
+ workqueue << u
+}
+
+# Get all the threads to exit after all the work is done
+parallel.times{|i|
+ workqueue << :exit
+}
+
+# Wait for the threads to exit
+threads.each{|t|
+ t.join
+}
+
+# Generate output
+print_output(archs_per_distro, mirrors, ref_times, times)
+