From fd7fa9102a50dedb2f1cbad4993fa2c7427407aa Mon Sep 17 00:00:00 2001 From: Alex Legler Date: Mon, 4 Jan 2016 02:18:48 +0100 Subject: Add initial set of tools - rsync-cat: rsync-to-stdout utility - gm-lag: displays the current lag of a mirror --- .rubocop.yml | 23 +++++++++++++ bin/gm-lag | 27 ++++++++++++++++ bin/rsync-cat | 14 ++++++++ lib/mirror_toolkit.rb | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 153 insertions(+) create mode 100644 .rubocop.yml create mode 100755 bin/gm-lag create mode 100755 bin/rsync-cat create mode 100644 lib/mirror_toolkit.rb diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..bc15b92 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,23 @@ +Style/AsciiComments: + Enabled: false + +Style/FormatString: + Enabled: false + +Style/FileName: + Enabled: false + +Style/Documentation: + Enabled: false + +Style/PerlBackrefs: + Enabled: false + +Metrics/LineLength: + Max: 120 + +Metrics/MethodLength: + Max: 20 + +Metrics/ModuleLength: + Max: 200 diff --git a/bin/gm-lag b/bin/gm-lag new file mode 100755 index 0000000..ae81875 --- /dev/null +++ b/bin/gm-lag @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# Displays the number of seconds a mirror is lagging behind + +require 'optparse' +require 'time' +require 'uri' +require 'net/http' +require_relative '../lib/mirror_toolkit' + +options = {} +OptionParser.new do |opts| + opts.on('-h', '--human', 'Display human times') { |v| options[:human] = v } + opts.on('-d', '--distfiles', 'Treat as distfiles mirror') { |v| options[:distfiles] = v } + opts.on('-r', '--rsync', 'Treat as rsync mirror') { |v| options[:rsync] = v } +end.parse! + +abort '-d and -r are exclusive.' if options[:distfiles] && options[:rsync] + +lag = MirrorToolkit.get_lag(ARGV[0], options[:rsync] ? MirrorToolkit::TYPE_RSYNC : MirrorToolkit::TYPE_DISTFILES) + +if lag.nil? + puts 'unknown' +elsif options[:human] + puts MirrorToolkit.humanize_seconds(lag) +else + puts lag.to_i +end diff --git a/bin/rsync-cat b/bin/rsync-cat new file mode 100755 index 0000000..e2b7c3c --- /dev/null +++ b/bin/rsync-cat @@ -0,0 +1,14 @@ +#!/bin/bash +# Prints an rsync file to stdout + +me=$(basename $0) +TMPFILE=$(mktemp /tmp/${me}.XXXXXX) || exit 1 +trap "rm ${TMPFILE}" EXIT + +if [ -z "${1}" ]; then + echo "Usage: ${0} " + exit 1 +fi + +rsync -q "${1}" "${TMPFILE}" || exit 1 +cat "${TMPFILE}" diff --git a/lib/mirror_toolkit.rb b/lib/mirror_toolkit.rb new file mode 100644 index 0000000..cc53090 --- /dev/null +++ b/lib/mirror_toolkit.rb @@ -0,0 +1,89 @@ +require 'open3' +require 'time' +require 'net/http' + +# Mirror toolkit functionality and other shared stuff +module MirrorToolkit + RSYNC_TIMESTAMP_FILE = '/gentoo-portage/metadata/timestamp' + DISTFILES_TIMESTAMP_FILE = '/distfiles/timestamp.mirmon' + + TYPE_RSYNC = 1 + TYPE_DISTFILES = 2 + + module_function + + # Calls `rsync_cat` to fetch a file from rsync + def rsync_cat(uri) + rsync_cat = File.join(File.dirname(__FILE__), '..', 'bin', 'rsync-cat') + stdin, stdout, stderr, wait_thr = Open3.popen3(rsync_cat, uri) + stdin.close + + if wait_thr.value == 0 + return stdout.gets + else + fail "Rsync call unsuccessful: '#{stderr.gets.chomp}'" + end + end + + # Fetches a URI from any of the Gentoo mirror types + def remote_fetch(url) + case url.scheme + when 'rsync' + rsync_cat(url.to_s) + when 'http', 'https', 'ftp' + Net::HTTP.get(url) + else + fail 'Unknown URI scheme.' + end + end + + # Tries to return a Time object for every kind of timestamp used on Gentoo mirrors + def parse_timestamp(ts) + if ts.numeric? + Time.at(ts.to_i).utc + else + Time.parse(ts).utc + end + end + + # Returns a URI object where to find the timestamp for the given url and mirror type. + def get_timestamp_url(url, type) + mirror = URI(url) + + case mirror.scheme + when 'rsync' + if type == TYPE_RSYNC + mirror.path = MirrorToolkit::RSYNC_TIMESTAMP_FILE + elsif type == TYPE_DISTFILES + mirror.path += MirrorToolkit::DISTFILES_TIMESTAMP_FILE + end + when 'http', 'https', 'ftp' + mirror.path = MirrorToolkit::DISTFILES_TIMESTAMP_FILE + end + + mirror + end + + # Returns the number of seconds a mirror is lagging behind, or nil if it cannot be determined. + def get_lag(url, type) + Time.now.utc - parse_timestamp(remote_fetch(get_timestamp_url(url, type))) + rescue + nil + end + + # Renders seconds as a nice human-printable string + def humanize_seconds(secs) + [[60, :seconds], [60, :minutes], [24, :hours], [1000, :days]].map do |count, name| + if secs > 0 + secs, n = secs.divmod(count) + "#{n.to_i} #{name}" + end + end.compact.reverse.join(' ') + end +end + +class String + def numeric? + true if Float(self) rescue false + end +end -- cgit v1.2.3-65-gdbad