Manual test suite: use more precise data when comparing image size.
[tails.git] / features / step_definitions / chutney.rb
blob11a65f10b9f9cc58b2f45b978121179b6ef61572
1 def chutney_src_dir
2   "#{GIT_DIR}/submodules/chutney"
3 end
5 def ensure_chutney_is_running
6   # Ensure that a fresh chutney instance is running, and that it will
7   # be cleaned upon exit. We only do it once, though, since the same
8   # setup can be used throughout the same test suite run.
9   if not($chutney_initialized)
10     chutney_listen_address = $vmnet.bridge_ip_addr
11     chutney_script = "#{chutney_src_dir}/chutney"
12     assert(
13       File.executable?(chutney_script),
14       "It does not look like '#{chutney_src_dir}' is the Chutney source tree"
15     )
16     network_definition = "#{GIT_DIR}/features/chutney/test-network"
17     env = {
18       'CHUTNEY_LISTEN_ADDRESS' => chutney_listen_address,
19       'CHUTNEY_DATA_DIR' => "#{$config['TMPDIR']}/chutney-data/"
20     }
22     chutney_data_dir_cleanup = Proc.new do
23       if File.directory?(env['CHUTNEY_DATA_DIR'])
24         FileUtils.rm_r(env['CHUTNEY_DATA_DIR'])
25       end
26     end
28     chutney_cmd = Proc.new do |cmd|
29       Dir.chdir(chutney_src_dir) do
30         cmd_helper([chutney_script, cmd, network_definition], env)
31       end
32     end
34     if KEEP_SNAPSHOTS
35       begin
36         chutney_cmd.call('start')
37       rescue Test::Unit::AssertionFailedError
38         if File.directory?(env['CHUTNEY_DATA_DIR'])
39           raise "You are running with --keep-snapshots but Chutney failed " +
40                 "to start with its current data directory. To recover you " +
41                 "likely want to delete '#{env['CHUTNEY_DATA_DIR']}' and " +
42                 "all test suite snapshots and then start over."
43         else
44           chutney_cmd.call('configure')
45           chutney_cmd.call('start')
46         end
47       end
48     else
49       chutney_cmd.call('stop')
50       chutney_data_dir_cleanup.call
51       chutney_cmd.call('configure')
52       chutney_cmd.call('start')
53     end
55     at_exit do
56       chutney_cmd.call('stop')
57       chutney_data_dir_cleanup.call unless KEEP_SNAPSHOTS
58     end
60     # We have to sanity check that all nodes are running because
61     # `chutney start` will return success even if some nodes fail.
62     running, total = 0, -1
63     status = chutney_cmd.call('status')
64     match = Regexp.new('^(\d+)/(\d+) nodes are running$').match(status)
65     assert_not_nil(match, "Chutney's status did not contain the expected " +
66                           "string listing the number of running nodes")
67     running, total = match[1,2].map { |x| x.to_i }
68     assert_equal(
69       total, running, "Chutney is only running #{running}/#{total} nodes"
70     )
72     $chutney_initialized = true
73   end
74 end
76 When /^I configure Tails to use a simulated Tor network$/ do
77   # At the moment this step essentially assumes that we boot with 'the
78   # network is unplugged', run this step, and then 'the network is
79   # plugged'. I believe we can make this pretty transparent without
80   # the need of a dedicated step by using tags (e.g. @fake_tor or
81   # whatever -- possibly we want the opposite, @real_tor,
82   # instead).
83   #
84   # There are two time points where we for a scenario must ensure that
85   # the client configuration below is enabled if and only if the
86   # scenario is tagged, and that is:
87   #
88   # 1. During a proper boot, as soon as the remote shell is up in the
89   #    'the computer boots Tails' step.
90   #
91   # 2. When restoring a snapshot, in restore_background().
92   #
93   # If we do this, it doesn't even matter if a snapshot is made of an
94   # untagged scenario (without the conf), and we later restore it with
95   # a tagged scenario.
96   #
97   # Note: We probably have to clear the /var/lib/tor data dir when we
98   # switch mode. Possibly there are other such problems that make this
99   # abstraction impractical and it's better that we avoid it an go
100   # with the more explicit, step-based approach.
102   assert(not($vm.execute('service tor status').success?),
103          "Running this step when Tor is running is probably not intentional")
104   ensure_chutney_is_running
105   # Most of these lines are taken from chutney's client template.
106   client_torrc_lines = [
107     'TestingTorNetwork 1',
108     'AssumeReachable 1',
109     'PathsNeededToBuildCircuits 0.25',
110     'TestingBridgeDownloadSchedule 0, 5',
111     'TestingClientConsensusDownloadSchedule 0, 5',
112     'TestingClientDownloadSchedule 0, 5',
113     'TestingDirAuthVoteExit *',
114     'TestingDirAuthVoteGuard *',
115     'TestingDirAuthVoteHSDir *',
116     'TestingMinExitFlagThreshold 0',
117     'V3AuthNIntervalsValid 2',
118     # Enabling TestingTorNetwork disables ClientRejectInternalAddresses
119     # so the Tor client will happily try LAN connections. Coupled with
120     # that TestingTorNetwork is enabled on all exits, and their
121     # ExitPolicyRejectPrivate is disabled, we will allow exiting to
122     # LAN hosts. We have at least one test that tries to make sure
123     # that is *not* possible (Scenario: The Tor Browser cannot access
124     # the LAN) so we cannot allow it. We'll have to rethink all this
125     # if we ever want to run all services locally as well (#9520).
126     'ClientRejectInternalAddresses 1',
127   ]
128   # We run one client in chutney so we easily can grep the generated
129   # DirAuthority lines and use them.
130   client_torrcs = Dir.glob(
131     "#{$config['TMPDIR']}/chutney-data/nodes/*client/torrc"
132   )
133   dir_auth_lines = open(client_torrcs.first) do |f|
134     f.grep(/^(Alternate)?(Dir|Bridge)Authority\s/)
135   end
136   client_torrc_lines.concat(dir_auth_lines)
137   $vm.file_append('/etc/tor/torrc', client_torrc_lines)
140 When /^Tails is using the real Tor network$/ do
141   assert($vm.execute('grep "TestingTorNetwork 1" /etc/torrc').failure?)
144 def chutney_onionservice_info
145   hs_hostname_file_path = Dir.glob(
146     "#{$config['TMPDIR']}/chutney-data/nodes/*hs/hidden_service/hostname"
147   ).first
148   hs_hostname = open(hs_hostname_file_path, 'r') do |f|
149     f.read.chomp
150   end
151   hs_torrc_path = Dir.glob(
152     "#{$config['TMPDIR']}/chutney-data/nodes/*hs/torrc"
153   ).first
154   _, hs_port, local_address_port = open(hs_torrc_path, 'r') do |f|
155     f.grep(/^HiddenServicePort/).first.split
156   end
157   local_address, local_port  = local_address_port.split(':')
158   [local_address, local_port, hs_hostname, hs_port]
161 def chutney_onionservice_redir(remote_address, remote_port)
162   kill_redir = Proc.new do
163     begin
164       Process.kill("TERM", $chutney_onionservice_job.pid)
165     rescue
166       # noop
167     end
168   end
169   if $chutney_onionservice_job
170     kill_redir.call
171   end
172   local_address, local_port, _ = chutney_onionservice_info
173   # XXX:Stretch: revert the commit introducing this command once we
174   # stop supporting the test suite on Debian Jessie.
175   redir_cmd = ['/usr/bin/redir']
176   redir_version = Gem::Version.new(cmd_helper(redir_cmd + ['--version']))
177   if redir_version < Gem::Version.new('3.0')
178     redir_cmd += [
179       "--laddr", local_address,
180       "--lport", local_port,
181       "--caddr", remote_address,
182       "--cport", remote_port,
183     ]
184   else
185     redir_cmd += [
186       "#{local_address}:#{local_port}",
187       "#{remote_address}:#{remote_port}",
188     ]
189   end
190   $chutney_onionservice_job = IO.popen(redir_cmd)
191   add_after_scenario_hook { kill_redir.call }
192   return $chutney_onionservice_job