diff options
Diffstat (limited to 'cucumber/features/support/helpers/misc_helpers.rb')
-rw-r--r-- | cucumber/features/support/helpers/misc_helpers.rb | 139 |
1 files changed, 115 insertions, 24 deletions
diff --git a/cucumber/features/support/helpers/misc_helpers.rb b/cucumber/features/support/helpers/misc_helpers.rb index 7e09411f..865d2978 100644 --- a/cucumber/features/support/helpers/misc_helpers.rb +++ b/cucumber/features/support/helpers/misc_helpers.rb @@ -1,4 +1,6 @@ require 'date' +require 'io/console' +require 'pry' require 'timeout' require 'test/unit' @@ -28,8 +30,12 @@ end # Call block (ignoring any exceptions it may throw) repeatedly with # one second breaks until it returns true, or until `timeout` seconds have -# passed when we throw a Timeout::Error exception. +# passed when we throw a Timeout::Error exception. If `timeout` is `nil`, +# then we just run the code block with no timeout. def try_for(timeout, options = {}) + if block_given? && timeout.nil? + return yield + end options[:delay] ||= 1 last_exception = nil # Create a unique exception used only for this particular try_for @@ -76,11 +82,12 @@ def try_for(timeout, options = {}) # ends up there immediately. rescue unique_timeout_exception => e msg = options[:msg] || 'try_for() timeout expired' + exc_class = options[:exception] || Timeout::Error if last_exception msg += "\nLast ignored exception was: " + "#{last_exception.class}: #{last_exception}" end - raise Timeout::Error.new(msg) + raise exc_class.new(msg) end class TorFailure < StandardError @@ -89,6 +96,19 @@ end class MaxRetriesFailure < StandardError end +def force_new_tor_circuit() + debug_log("Forcing new Tor circuit...") + # Tor rate limits NEWNYM to at most one per 10 second period. + interval = 10 + if $__last_newnym + elapsed = Time.now - $__last_newnym + # We sleep an extra second to avoid tight timings. + sleep interval - elapsed + 1 if 0 < elapsed && elapsed < interval + end + $vm.execute_successfully('tor_control_send "signal NEWNYM"', :libs => 'tor') + $__last_newnym = Time.now +end + # This will retry the block up to MAX_NEW_TOR_CIRCUIT_RETRIES # times. The block must raise an exception for a run to be considered # as a failure. After a failure recovery_proc will be called (if @@ -105,11 +125,6 @@ def retry_tor(recovery_proc = nil, &block) :operation_name => 'Tor operation', &block) end -def retry_i2p(recovery_proc = nil, &block) - retry_action(15, :recovery_proc => recovery_proc, - :operation_name => 'I2P operation', &block) -end - def retry_action(max_retries, options = {}, &block) assert(max_retries.is_a?(Integer), "max_retries must be an integer") options[:recovery_proc] ||= nil @@ -120,6 +135,10 @@ def retry_action(max_retries, options = {}, &block) begin block.call return + rescue NameError => e + # NameError most likely means typos, and hiding that is rarely + # (never?) a good idea, so we rethrow them. + raise e rescue Exception => e if retries <= max_retries debug_log("#{options[:operation_name]} failed (Try #{retries} of " + @@ -136,16 +155,15 @@ def retry_action(max_retries, options = {}, &block) end end +alias :retry_times :retry_action + +class TorBootstrapFailure < StandardError +end + def wait_until_tor_is_working try_for(270) { $vm.execute('/usr/local/sbin/tor-has-bootstrapped').success? } -rescue Timeout::Error => e - c = $vm.execute("journalctl SYSLOG_IDENTIFIER=restart-tor") - if c.success? - debug_log("From the journal:\n" + c.stdout.sub(/^/, " ")) - else - debug_log("Nothing was in the journal about 'restart-tor'") - end - raise e +rescue Timeout::Error + raise TorBootstrapFailure.new('Tor failed to bootstrap') end def convert_bytes_mod(unit) @@ -177,13 +195,14 @@ def convert_from_bytes(size, unit) return size.to_f/convert_bytes_mod(unit).to_f end -def cmd_helper(cmd) +def cmd_helper(cmd, env = {}) if cmd.instance_of?(Array) cmd << {:err => [:child, :out]} elsif cmd.instance_of?(String) cmd += " 2>&1" end - IO.popen(cmd) do |p| + env = ENV.to_h.merge(env) + IO.popen(env, cmd) do |p| out = p.readlines.join("\n") p.close ret = $? @@ -192,11 +211,23 @@ def cmd_helper(cmd) end end -# This command will grab all router IP addresses from the Tor -# consensus in the VM + the hardcoded TOR_AUTHORITIES. -def get_all_tor_nodes - cmd = 'awk "/^r/ { print \$6 }" /var/lib/tor/cached-microdesc-consensus' - $vm.execute(cmd).stdout.chomp.split("\n") + TOR_AUTHORITIES +def all_tor_hosts + nodes = Array.new + chutney_torrcs = Dir.glob( + "#{$config['TMPDIR']}/chutney-data/nodes/*/torrc" + ) + chutney_torrcs.each do |torrc| + open(torrc) do |f| + nodes += f.grep(/^(Or|Dir)Port\b/).map do |line| + { address: $vmnet.bridge_ip_addr, port: line.split.last.to_i } + end + end + end + return nodes +end + +def allowed_hosts_under_tor_enforcement + all_tor_hosts + @lan_hosts end def get_free_space(machine, path) @@ -246,8 +277,68 @@ def info_log_artifact_location(type, path) info_log("#{type.capitalize}: #{path}") end +def notify_user(message) + alarm_script = $config['NOTIFY_USER_COMMAND'] + return if alarm_script.nil? || alarm_script.empty? + cmd_helper(alarm_script.gsub('%m', message)) +end + def pause(message = "Paused") + notify_user(message) + STDERR.puts + STDERR.puts message + # Ring the ASCII bell for a helpful notification in most terminal + # emulators. + STDOUT.write "\a" STDERR.puts - STDERR.puts "#{message} (Press ENTER to continue!)" - STDIN.gets + loop do + STDERR.puts "Return: Continue; d: Debugging REPL" + c = STDIN.getch + case c + when "\r" + return + when "d" + binding.pry(quiet: true) + end + end +end + +def dbus_send(service, object_path, method, *args, **opts) + opts ||= {} + ruby_type_to_dbus_type = { + String => 'string', + Fixnum => 'int32', + } + typed_args = args.map do |arg| + type = ruby_type_to_dbus_type[arg.class] + assert_not_nil(type, "No DBus type conversion for Ruby type '#{arg.class}'") + "#{type}:#{arg}" + end + ret = $vm.execute_successfully( + "dbus-send --print-reply --dest=#{service} #{object_path} " + + " #{method} #{typed_args.join(' ')}", + **opts + ).stdout.lines + # The first line written is about timings and other stuff we don't + # care about; we only care about the return values. + ret.shift + ret.map! do |s| + type, val = /^\s*(\S+)\s+(\S+)$/.match(s)[1,2] + case type + when 'string' + # Unquote + val[1, val.length - 2] + when 'int32' + val.to_i + else + raise "No Ruby type conversion for DBus type '#{type}'" + end + end + if ret.size == 0 + return nil + elsif ret.size == 1 + return ret.first + else + return ret + end end |