Merge lp:~mandel/ubuntuone-dev-tools/proxy-testcase into lp:ubuntuone-dev-tools
- proxy-testcase
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | dobey | ||||
Approved revision: | 83 | ||||
Merged at revision: | 52 | ||||
Proposed branch: | lp:~mandel/ubuntuone-dev-tools/proxy-testcase | ||||
Merge into: | lp:ubuntuone-dev-tools | ||||
Diff against target: |
1215 lines (+1098/-25) 10 files modified
data/squid.conf.in (+120/-0) ubuntuone/devtools/services/__init__.py (+53/-0) ubuntuone/devtools/services/dbus.py (+8/-23) ubuntuone/devtools/services/squid.py (+245/-0) ubuntuone/devtools/services/tests/test_dbus.py (+1/-1) ubuntuone/devtools/services/tests/test_squid.py (+334/-0) ubuntuone/devtools/testcases/dbus.py (+1/-1) ubuntuone/devtools/testcases/squid.py (+58/-0) ubuntuone/devtools/testcases/tests/__init__.py (+16/-0) ubuntuone/devtools/testcases/tests/test_squid_testcase.py (+262/-0) |
||||
To merge this branch: | bzr merge lp:~mandel/ubuntuone-dev-tools/proxy-testcase | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
dobey (community) | Approve | ||
Alejandro J. Cura (community) | Approve | ||
Review via email: mp+85019@code.launchpad.net |
Commit message
- A SquidTestCase that does the following:
- Creates a new password file.
- Creates a squid.conf
- Launches squid for tests listening to two random ports, one with auth and other without.
- Tests for all the above.
- Cleans a little the dbus testcase.
Description of the change
This branch adds the following:
- A SquidTestCase that does the following:
- Creates a new password file.
- Creates a squid.conf
- Launches squid for tests listening to two random ports, one with auth and other without.
- Tests for all the above.
- Cleans a little the dbus testcase.
dobey (dobey) wrote : | # |
There's a conflict now with trunk. Please merge trunk and fix the conflict, as well as changing the remaining usage of xdg.BaseDirectory in your branch to use dirspec.basedir instead. The dirspec code is packaged in the nightlies PPA now, and is cross-platform and based on the code in ubuntu_sso and xdg.
Also, please fix the rest of the previously mentioned issues.
Alejandro J. Cura (alecu) wrote : | # |
Lovely and well tested branch!
A few small comments:
The first line of this should not be needed:
541 + template = None
542 + with open(path) as in_file:
543 + template = string.
Regarding this:
return dict(address=
Can the key "address" be changed to "host"? I think it's a better name, and it matches other parts.
(PS: I know I might have suggested that name in the first place, sorry about that, but I think it's easier to change now than later)
Alejandro J. Cura (alecu) wrote : | # |
Great branch!
Thanks for working on this.
dobey (dobey) wrote : | # |
So, it seems like we can combine the 2 squid config files. I copied the squid3.conf.in over top of squid2.conf.in, and the tests still pass for me, on Oneiric at least. The diff shows a small set of changes, most of which the older squid possibly just ignores.
>Starting squid version 2...
>squid: ERROR: Could not send signal 0 to process 32571: (3) No such process
>Waiting for squid to start..
This error happens with both versions of squid, both on Oneiric and Precise for me. Do we know why this is happening, and how we can get rid of it?
On Precise, I also get the following:
WARNING: cache_mem is larger than total disk cache space!
WARNING: (B) '::/0' is a subnetwork of (A) '::/0'
WARNING: because of this '::/0' is ignored to keep splay tree searching predictable
WARNING: You should probably remove '::/0' from the ACL named 'all'
Is there any way to get rid of the first warning, via the config? Perhaps setting a value of cache_mem to be quite small?
For the 'all' ACL issue, can we redirect the STDOUT/STDERR output from squid, to a log file (or two) in the tempdir?
Manuel de la Peña (mandel) wrote : | # |
> So, it seems like we can combine the 2 squid config files. I copied the
> squid3.conf.in over top of squid2.conf.in, and the tests still pass for me, on
> Oneiric at least. The diff shows a small set of changes, most of which the
> older squid possibly just ignores.
>
> >Starting squid version 2...
> >squid: ERROR: Could not send signal 0 to process 32571: (3) No such process
> >Waiting for squid to start..
That occurs because I'm trying to talk with squid to ensure that is running before we start the the test case, mainly to ensure that there are no race condition. I can forward the stderr to a log file so that we see if happens but does not leave the terminal buffer full of junk.
>
> This error happens with both versions of squid, both on Oneiric and Precise
> for me. Do we know why this is happening, and how we can get rid of it?
>
> On Precise, I also get the following:
>
> WARNING: cache_mem is larger than total disk cache space!
>
> WARNING: (B) '::/0' is a subnetwork of (A) '::/0'
> WARNING: because of this '::/0' is ignored to keep splay tree searching
> predictable
> WARNING: You should probably remove '::/0' from the ACL named 'all'
>
> Is there any way to get rid of the first warning, via the config? Perhaps
> setting a value of cache_mem to be quite small?
> For the 'all' ACL issue, can we redirect the STDOUT/STDERR output from squid,
> to a log file (or two) in the tempdir?
I'll play around with the settings to see if I can solve the issues.
Manuel de la Peña (mandel) wrote : | # |
Config and code have been updated to remove the errors. It was quite simple.
dobey (dobey) wrote : | # |
With the fix to redirect the warnings and errors, we can keep the "acl all src all" in squid3.conf.in, and use it for both versions, it seems. I added it back, and copied squid3.conf.in to squid2.conf.in, and the tests work in both Precise and Oneiric for me, though squid3 does seem to take a bit long to start for some reason, even without that line in the config. Can we add this config back, and just have a single squid.conf.in for both versions?
Also, can you change the service code to look for squid3 first, rather than squid? It's better to try the new first, then fall back to the old version, I think.
Manuel de la Peña (mandel) wrote : | # |
> With the fix to redirect the warnings and errors, we can keep the "acl all src
> all" in squid3.conf.in, and use it for both versions, it seems. I added it
> back, and copied squid3.conf.in to squid2.conf.in, and the tests work in both
> Precise and Oneiric for me, though squid3 does seem to take a bit long to
> start for some reason, even without that line in the config. Can we add this
> config back, and just have a single squid.conf.in for both versions?
>
Done
> Also, can you change the service code to look for squid3 first, rather than
> squid? It's better to try the new first, then fall back to the old version, I
> think.
Done
dobey (dobey) wrote : | # |
Looks good now, though the comment is wrong for the block of code that finds the squid executable. :)
dobey (dobey) wrote : | # |
Manuel, please add the shortened e-mail address used in your recent commits, to your LP account, so that LP knows who you are. :)
Ubuntu One Auto Pilot (otto-pilot) wrote : | # |
The attempt to merge lp:~mandel/ubuntuone-dev-tools/proxy-testcase into lp:ubuntuone-dev-tools failed. Below is the output from the failed tests.
ubuntuone.
BaseTestCase
runTest ... [OK]
ubuntuone.
EnvironTestCase
test_
test_
test_
PathsTestCase
test_
test_
test_
test_
test_
test_
test_
SquidRunnerIn
test_
test_
SquidRunnerTe
test_
test_
test_
ubuntuone.
DBusTestCase
runTest ... [OK]
ubuntuone.
TestWithDBus
test_
test_
ubuntuone.
TestCheckTwis
test_
test_bare_super ... [OK]
test_
test_
test_
test_
test_
test_
test_
TestTwistedCh
test_
test_
twisted.
TestCase
runTest ... [OK]
ubuntuone.
BaseTestCase
runTest ... [OK]
ubuntuone.
Tes...
Preview Diff
1 | === added file 'data/squid.conf.in' |
2 | --- data/squid.conf.in 1970-01-01 00:00:00 +0000 |
3 | +++ data/squid.conf.in 2012-01-05 16:48:29 +0000 |
4 | @@ -0,0 +1,120 @@ |
5 | +auth_param basic casesensitive on |
6 | +# Use a default auth using ncsa and the passed generated file. |
7 | +auth_param basic program ${auth_process} ${auth_file} |
8 | +#Recommended minimum configuration: |
9 | +acl manager proto cache_object |
10 | +acl localhost src 127.0.0.1/32 |
11 | +acl to_localhost dst 127.0.0.0/32 |
12 | +# |
13 | +# Example rule allowing access from your local networks. |
14 | +# Adapt to list your (internal) IP networks from where browsing |
15 | +# should be allowed |
16 | +acl all src all |
17 | +acl localnet src 10.0.0.0/8 # RFC1918 possible internal network |
18 | +acl localnet src 172.16.0.0/12 # RFC1918 possible internal network |
19 | +acl localnet src 192.168.0.0/16 # RFC1918 possible internal network |
20 | +# |
21 | +acl SSL_ports port 443 # https |
22 | +acl SSL_ports port 563 # snews |
23 | +acl SSL_ports port 873 # rsync |
24 | +acl Safe_ports port 80 # http |
25 | +acl Safe_ports port 21 # ftp |
26 | +acl Safe_ports port 443 # https |
27 | +acl Safe_ports port 70 # gopher |
28 | +acl Safe_ports port 210 # wais |
29 | +acl Safe_ports port 1025-65535 # unregistered ports |
30 | +acl Safe_ports port 280 # http-mgmt |
31 | +acl Safe_ports port 488 # gss-http |
32 | +acl Safe_ports port 591 # filemaker |
33 | +acl Safe_ports port 777 # multiling http |
34 | +acl Safe_ports port 631 # cups |
35 | +acl Safe_ports port 873 # rsync |
36 | +acl Safe_ports port 901 # SWAT |
37 | +acl purge method PURGE |
38 | +acl CONNECT method CONNECT |
39 | + |
40 | +# make an acl for users that have auth |
41 | +acl password proxy_auth REQUIRED myportname ${auth_port_number} |
42 | +acl auth_port_connected myportname ${auth_port_number} |
43 | +acl nonauth_port_connected myportname ${noauth_port_number} |
44 | + |
45 | +# Settings used for the tests: |
46 | +# Allow users connected to the nonauth port |
47 | +# Allow users authenticated AND connected to the auth port |
48 | +http_access allow nonauth_port_connected |
49 | +http_access allow password |
50 | + |
51 | +#Recommended minimum configuration: |
52 | +# |
53 | +# Only allow cachemgr access from localhost |
54 | +http_access allow manager localhost |
55 | +http_access deny manager |
56 | +# Only allow purge requests from localhost |
57 | +http_access allow purge localhost |
58 | +http_access deny purge |
59 | +# Deny requests to unknown ports |
60 | +http_access deny !Safe_ports |
61 | +# Deny CONNECT to other than SSL ports |
62 | +http_access deny CONNECT !SSL_ports |
63 | +# Example rule allowing access from your local networks. |
64 | +# Adapt localnet in the ACL section to list your (internal) IP networks |
65 | +# from where browsing should be allowed |
66 | +#http_access allow localnet |
67 | +http_access allow localhost |
68 | + |
69 | +# And finally deny all other access to this proxy |
70 | +http_access deny all |
71 | + |
72 | +icp_access allow localnet |
73 | +icp_access deny all |
74 | + |
75 | +# Squid normally listens to port 3128 but we are going to listento two |
76 | +# different ports, one for auth one for nonauth. |
77 | +http_port ${noauth_port_number} |
78 | +http_port ${auth_port_number} |
79 | + |
80 | +#We recommend you to use at least the following line. |
81 | +hierarchy_stoplist cgi-bin ? |
82 | + |
83 | +# Default cache settings. |
84 | +cache_dir ufs ${spool_temp} 1000 16 256 |
85 | + |
86 | +# access log settings |
87 | +access_log ${squid_temp}/access.log squid |
88 | + |
89 | +# cache log settings |
90 | +cache_log ${squid_temp}/cache.log |
91 | + |
92 | +# cache store log settings |
93 | +cache_store_log ${squid_temp}/store.log |
94 | + |
95 | +# mime table conf |
96 | +# mime_table /usr/share/squid/mime.conf |
97 | + |
98 | +#Default pid file name |
99 | +pid_filename ${squid_temp}/squid.pid |
100 | + |
101 | +# debug options (Full debugging) |
102 | +debug_options ALL,1 |
103 | + |
104 | +#Default netdb_filename |
105 | + |
106 | +#Suggested default: |
107 | +refresh_pattern ^ftp: 1440 20% 10080 |
108 | +refresh_pattern ^gopher: 1440 0% 1440 |
109 | +refresh_pattern -i (/cgi-bin/|\?) 0 0% 0 |
110 | +refresh_pattern (Release|Packages(.gz)*)$ 0 20% 2880 |
111 | +# example line deb packages |
112 | +refresh_pattern . 0 20% 4320 |
113 | + |
114 | +# Don't upgrade ShoutCast responses to HTTP |
115 | +acl shoutcast rep_header X-HTTP09-First-Line ^ICY.[0-9] |
116 | + |
117 | +# Apache mod_gzip and mod_deflate known to be broken so don't trust |
118 | +# Apache to signal ETag correctly on such responses |
119 | +acl apache rep_header Server ^Apache |
120 | + |
121 | +hosts_file /etc/hosts |
122 | + |
123 | +# Leave coredumps in the first cache dir |
124 | +coredump_dir ${spool_temp}/squid |
125 | |
126 | === modified file 'ubuntuone/devtools/services/__init__.py' |
127 | --- ubuntuone/devtools/services/__init__.py 2010-11-03 21:04:06 +0000 |
128 | +++ ubuntuone/devtools/services/__init__.py 2012-01-05 16:48:29 +0000 |
129 | @@ -1,1 +1,54 @@ |
130 | +# |
131 | +# Copyright 2011 Canonical Ltd. |
132 | +# |
133 | +# This program is free software: you can redistribute it and/or modify it |
134 | +# under the terms of the GNU General Public License version 3, as published |
135 | +# by the Free Software Foundation. |
136 | +# |
137 | +# This program is distributed in the hope that it will be useful, but |
138 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
139 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
140 | +# PURPOSE. See the GNU General Public License for more details. |
141 | +# |
142 | +# You should have received a copy of the GNU General Public License along |
143 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
144 | """Service runners for testing.""" |
145 | + |
146 | +import os |
147 | +import socket |
148 | + |
149 | +from dirspec.basedir import load_data_paths |
150 | + |
151 | + |
152 | +def find_config_file(in_config_file): |
153 | + """Find the first appropriate conf to use.""" |
154 | + # In case we're running from within the source tree |
155 | + path = os.path.abspath(os.path.join(os.path.dirname(__file__), |
156 | + os.path.pardir, os.path.pardir, |
157 | + os.path.pardir, |
158 | + "data", in_config_file)) |
159 | + if not os.path.exists(path): |
160 | + # Use the installed file in $pkgdatadir as source |
161 | + for path in load_data_paths("ubuntuone-dev-tools", |
162 | + in_config_file): |
163 | + if os.path.exists(path): |
164 | + break |
165 | + |
166 | + # Check to make sure we didn't just fall out of the loop |
167 | + if not os.path.exists(path): |
168 | + raise IOError('Could not locate suitable %s' % in_config_file) |
169 | + return path |
170 | + |
171 | + |
172 | +def get_arbitrary_port(): |
173 | + """ |
174 | + Find an unused port, and return it. |
175 | + |
176 | + There might be a small race condition here, but we aren't |
177 | + worried about it. |
178 | + """ |
179 | + sock = socket.socket() |
180 | + sock.bind(('localhost', 0)) |
181 | + _, port = sock.getsockname() |
182 | + sock.close() |
183 | + return port |
184 | |
185 | === modified file 'ubuntuone/devtools/services/dbus.py' |
186 | --- ubuntuone/devtools/services/dbus.py 2011-12-19 22:37:31 +0000 |
187 | +++ ubuntuone/devtools/services/dbus.py 2012-01-05 16:48:29 +0000 |
188 | @@ -20,10 +20,12 @@ |
189 | import signal |
190 | import subprocess |
191 | |
192 | -from dirspec.basedir import load_data_paths |
193 | from distutils.spawn import find_executable |
194 | from urllib import quote |
195 | |
196 | +from ubuntuone.devtools.services import find_config_file |
197 | +DBUS_CONFIG_FILE = 'dbus-session.conf.in' |
198 | + |
199 | |
200 | class DBusLaunchError(Exception): |
201 | """Error while launching dbus-daemon""" |
202 | @@ -44,42 +46,25 @@ |
203 | self.running = False |
204 | self.config_file = None |
205 | |
206 | - def _find_config_file(self, tempdir=None): |
207 | + def _generate_config_file(self, tempdir=None): |
208 | """Find the first appropriate dbus-session.conf to use.""" |
209 | - # In case we're running from within the source tree |
210 | - path = os.path.abspath(os.path.join(os.path.dirname(__file__), |
211 | - os.path.pardir, os.path.pardir, |
212 | - os.path.pardir, |
213 | - "data", "dbus-session.conf.in")) |
214 | - if not os.path.exists(path): |
215 | - # Use the installed file in $pkgdatadir as source |
216 | - for path in load_data_paths("ubuntuone-dev-tools", |
217 | - "dbus-session.conf.in"): |
218 | - if os.path.exists(path): |
219 | - break |
220 | - |
221 | - # Check to make sure we didn't just fall out of the loop |
222 | - if not os.path.exists(path): |
223 | - raise IOError('Could not locate suitable dbus-session.conf.in') |
224 | - |
225 | + # load the config file |
226 | + path = find_config_file(DBUS_CONFIG_FILE) |
227 | + # replace config settings |
228 | self.config_file = os.path.join(tempdir, 'dbus-session.conf') |
229 | dbus_address = 'unix:tmpdir=%s' % quote(tempdir) |
230 | with open(path) as in_file: |
231 | content = in_file.read() |
232 | with open(self.config_file, 'w') as out_file: |
233 | out_file.write(content.replace('@ADDRESS@', dbus_address)) |
234 | - out_file.close() |
235 | - in_file.close() |
236 | |
237 | def start_service(self, tempdir=None): |
238 | """Start our own session bus daemon for testing.""" |
239 | - if not tempdir: |
240 | - tempdir = os.path.join(os.getcwd(), '_trial_temp') |
241 | dbus = find_executable("dbus-daemon") |
242 | if not dbus: |
243 | raise NotFoundError("dbus-daemon was not found.") |
244 | |
245 | - self._find_config_file(tempdir) |
246 | + self._generate_config_file(tempdir) |
247 | |
248 | dbus_args = ["--fork", |
249 | "--config-file=" + self.config_file, |
250 | |
251 | === added file 'ubuntuone/devtools/services/squid.py' |
252 | --- ubuntuone/devtools/services/squid.py 1970-01-01 00:00:00 +0000 |
253 | +++ ubuntuone/devtools/services/squid.py 2012-01-05 16:48:29 +0000 |
254 | @@ -0,0 +1,245 @@ |
255 | +# |
256 | +# Copyright 2011 Canonical Ltd. |
257 | +# |
258 | +# This program is free software: you can redistribute it and/or modify it |
259 | +# under the terms of the GNU General Public License version 3, as published |
260 | +# by the Free Software Foundation. |
261 | +# |
262 | +# This program is distributed in the hope that it will be useful, but |
263 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
264 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
265 | +# PURPOSE. See the GNU General Public License for more details. |
266 | +# |
267 | +# You should have received a copy of the GNU General Public License along |
268 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
269 | +"""Utilities for finding and running a squid proxy for testing.""" |
270 | + |
271 | +import random |
272 | +import signal |
273 | +# pylint:disable=W0402 |
274 | +import string |
275 | +# pylint:enable=W0402 |
276 | +import subprocess |
277 | +import time |
278 | + |
279 | +from json import dumps, loads |
280 | +from os import environ, makedirs, kill, unlink |
281 | +from os.path import abspath, exists, join |
282 | + |
283 | +from distutils.spawn import find_executable |
284 | + |
285 | +from ubuntuone.devtools.services import ( |
286 | + find_config_file, |
287 | + get_arbitrary_port, |
288 | +) |
289 | + |
290 | +SQUID_CONFIG_FILE = 'squid.conf.in' |
291 | +SQUID_DIR = 'squid' |
292 | +AUTH_PROCESS_PATH = '/usr/lib/%s/ncsa_auth' |
293 | +SPOOL_DIR = 'spool' |
294 | +AUTH_FILE = 'htpasswd' |
295 | +PROXY_ENV_VAR = 'SQUID_PROXY_SETTINGS' |
296 | + |
297 | + |
298 | +def get_squid_executable(): |
299 | + """Return the squid executable of the system.""" |
300 | + # try with squid and if not present try with squid3 for newer systems |
301 | + # (Ubuntu P). We also return the path to the auth process so that we can |
302 | + # point to the correct one. |
303 | + squid = find_executable('squid3') |
304 | + auth_process = AUTH_PROCESS_PATH % 'squid3' |
305 | + if squid is None: |
306 | + squid = find_executable('squid') |
307 | + auth_process = AUTH_PROCESS_PATH % 'squid' |
308 | + return squid, auth_process |
309 | + |
310 | + |
311 | +def get_htpasswd_executable(): |
312 | + """Return the htpasswd executable.""" |
313 | + return find_executable('htpasswd') |
314 | + |
315 | + |
316 | +def _make_random_string(count): |
317 | + """Make a random string of the given length.""" |
318 | + entropy = random.SystemRandom() |
319 | + return ''.join([entropy.choice(string.letters) for _ in |
320 | + range(count)]) |
321 | + |
322 | + |
323 | +def _get_basedir(tempdir): |
324 | + """Return the base squid config.""" |
325 | + basedir = join(tempdir, SQUID_DIR) |
326 | + basedir = abspath(basedir) |
327 | + if not exists(basedir): |
328 | + makedirs(basedir) |
329 | + return basedir |
330 | + |
331 | + |
332 | +def _get_spool_temp_path(tempdir=''): |
333 | + """Return the temp dir to be used for spool.""" |
334 | + basedir = _get_basedir(tempdir) |
335 | + path = join(basedir, SPOOL_DIR) |
336 | + path = abspath(path) |
337 | + if not exists(path): |
338 | + makedirs(path) |
339 | + return path |
340 | + |
341 | + |
342 | +def _get_squid_temp_path(tempdir=''): |
343 | + """Return the temp dir to be used by squid.""" |
344 | + basedir = _get_basedir(tempdir) |
345 | + path = join(basedir, SQUID_DIR) |
346 | + path = abspath(path) |
347 | + if not exists(path): |
348 | + makedirs(path) |
349 | + return path |
350 | + |
351 | + |
352 | +def _get_auth_temp_path(tempdir=''): |
353 | + """Return the path for the auth file.""" |
354 | + basedir = _get_basedir(tempdir) |
355 | + auth_file = join(basedir, AUTH_FILE) |
356 | + if not exists(basedir): |
357 | + makedirs(basedir) |
358 | + return auth_file |
359 | + |
360 | + |
361 | +def store_proxy_settings(settings): |
362 | + """Store the proxy setting in an env var.""" |
363 | + environ[PROXY_ENV_VAR] = dumps(settings) |
364 | + |
365 | + |
366 | +def retrieve_proxy_settings(): |
367 | + """Return the proxy settings of the env.""" |
368 | + if PROXY_ENV_VAR in environ: |
369 | + return loads(environ[PROXY_ENV_VAR]) |
370 | + return None |
371 | + |
372 | + |
373 | +def delete_proxy_settings(): |
374 | + """Delete the proxy env settings.""" |
375 | + if PROXY_ENV_VAR in environ: |
376 | + del environ[PROXY_ENV_VAR] |
377 | + |
378 | + |
379 | +class SquidLaunchError(Exception): |
380 | + """Error while launching squid.""" |
381 | + |
382 | + |
383 | +class SquidRunner(object): |
384 | + """Class for running a squid proxy with the local config.""" |
385 | + |
386 | + def __init__(self): |
387 | + """Create a new instance.""" |
388 | + self.squid, self.auth_process = get_squid_executable() |
389 | + if self.squid is None: |
390 | + raise SquidLaunchError('Could not locate "squid".') |
391 | + |
392 | + self.htpasswd = get_htpasswd_executable() |
393 | + if self.htpasswd is None: |
394 | + raise SquidLaunchError('Could not locate "htpasswd".') |
395 | + |
396 | + self.settings = dict(noauth_port=None, auth_port=None, |
397 | + username=None, password=None) |
398 | + self.squid_pid = None |
399 | + self.running = False |
400 | + self.config_file = None |
401 | + self.auth_file = None |
402 | + |
403 | + def _generate_config_file(self, tempdir=''): |
404 | + """Find the first appropiate squid.conf to use.""" |
405 | + # load the config file |
406 | + path = find_config_file(SQUID_CONFIG_FILE) |
407 | + # replace config settings |
408 | + basedir = join(tempdir, 'squid') |
409 | + basedir = abspath(basedir) |
410 | + if not exists(basedir): |
411 | + makedirs(basedir) |
412 | + self.config_file = join(basedir, 'squid.conf') |
413 | + with open(path) as in_file: |
414 | + template = string.Template(in_file.read()) |
415 | + |
416 | + self.settings['noauth_port'] = get_arbitrary_port() |
417 | + self.settings['auth_port'] = get_arbitrary_port() |
418 | + spool_path = _get_spool_temp_path(tempdir) |
419 | + squid_path = _get_squid_temp_path(tempdir) |
420 | + with open(self.config_file, 'w') as out_file: |
421 | + out_file.write(template.safe_substitute( |
422 | + auth_file=self.auth_file, |
423 | + auth_process=self.auth_process, |
424 | + noauth_port_number=self.settings['noauth_port'], |
425 | + auth_port_number=self.settings['auth_port'], |
426 | + spool_temp=spool_path, |
427 | + squid_temp=squid_path)) |
428 | + |
429 | + def _generate_swap(self, config_file): |
430 | + """Generate the squid swap files.""" |
431 | + squid_args = ['-z', '-f', config_file] |
432 | + sp = subprocess.Popen([self.squid] + squid_args, |
433 | + stdout=subprocess.PIPE, |
434 | + stderr=subprocess.PIPE) |
435 | + sp.wait() |
436 | + |
437 | + def _generate_auth_file(self, tempdir=''): |
438 | + """Generates a auth file using htpasswd.""" |
439 | + if self.settings['username'] is None: |
440 | + self.settings['username'] = _make_random_string(10) |
441 | + if self.settings['password'] is None: |
442 | + self.settings['password'] = _make_random_string(10) |
443 | + |
444 | + self.auth_file = _get_auth_temp_path(tempdir) |
445 | + # remove possible old auth file |
446 | + if exists(self.auth_file): |
447 | + unlink(self.auth_file) |
448 | + # create a new htpasswrd |
449 | + htpasswd_args = ['-bc', |
450 | + self.auth_file, |
451 | + self.settings['username'], |
452 | + self.settings['password']] |
453 | + sp = subprocess.Popen([self.htpasswd] + htpasswd_args, |
454 | + stdout=subprocess.PIPE, |
455 | + stderr=subprocess.PIPE) |
456 | + sp.wait() |
457 | + |
458 | + def _is_squid_running(self): |
459 | + """Return if squid is running.""" |
460 | + squid_args = ['-k', 'check', '-f', self.config_file] |
461 | + print 'Starting squid version...' |
462 | + message = 'Waiting for squid to start...' |
463 | + for timeout in (0.4, 0.1, 0.1, 0.2, 0.5, 1, 3, 5): |
464 | + try: |
465 | + # Do not use stdout=PIPE or stderr=PIPE with this function. |
466 | + subprocess.check_call([self.squid] + squid_args, |
467 | + stdout=subprocess.PIPE, |
468 | + stderr=subprocess.PIPE) |
469 | + return True |
470 | + except subprocess.CalledProcessError: |
471 | + message += '.' |
472 | + print message |
473 | + time.sleep(timeout) |
474 | + return False |
475 | + |
476 | + def start_service(self, tempdir=None): |
477 | + """Start our own proxy.""" |
478 | + # generate auth, config and swap dirs |
479 | + self._generate_auth_file(tempdir) |
480 | + self._generate_config_file(tempdir) |
481 | + self._generate_swap(self.config_file) |
482 | + squid_args = ['-N', '-X', '-f', self.config_file] |
483 | + sp = subprocess.Popen([self.squid] + squid_args, |
484 | + stdout=subprocess.PIPE, |
485 | + stderr=subprocess.PIPE) |
486 | + store_proxy_settings(self.settings) |
487 | + if not self._is_squid_running(): |
488 | + raise SquidLaunchError('Could not start squid.') |
489 | + self.squid_pid = sp.pid |
490 | + self.running = True |
491 | + |
492 | + def stop_service(self): |
493 | + """Stop our proxy,""" |
494 | + kill(self.squid_pid, signal.SIGKILL) |
495 | + delete_proxy_settings() |
496 | + self.running = False |
497 | + unlink(self.config_file) |
498 | + unlink(self.auth_file) |
499 | + self.config_file = None |
500 | |
501 | === modified file 'ubuntuone/devtools/services/tests/test_dbus.py' |
502 | --- ubuntuone/devtools/services/tests/test_dbus.py 2011-11-23 11:31:08 +0000 |
503 | +++ ubuntuone/devtools/services/tests/test_dbus.py 2012-01-05 16:48:29 +0000 |
504 | @@ -25,6 +25,6 @@ |
505 | runner = DBusRunner() |
506 | # pylint: disable=W0212 |
507 | os.makedirs(self.tmpdir) |
508 | - runner._find_config_file(tempdir=self.tmpdir) |
509 | + runner._generate_config_file(tempdir=self.tmpdir) |
510 | shutil.rmtree(self.tmpdir) |
511 | self.assertEqual(expected, runner.config_file) |
512 | |
513 | === added file 'ubuntuone/devtools/services/tests/test_squid.py' |
514 | --- ubuntuone/devtools/services/tests/test_squid.py 1970-01-01 00:00:00 +0000 |
515 | +++ ubuntuone/devtools/services/tests/test_squid.py 2012-01-05 16:48:29 +0000 |
516 | @@ -0,0 +1,334 @@ |
517 | +# |
518 | +# Copyright 2011 Canonical Ltd. |
519 | +# |
520 | +# This program is free software: you can redistribute it and/or modify it |
521 | +# under the terms of the GNU General Public License version 3, as published |
522 | +# by the Free Software Foundation. |
523 | +# |
524 | +# This program is distributed in the hope that it will be useful, but |
525 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
526 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
527 | +# PURPOSE. See the GNU General Public License for more details. |
528 | +# |
529 | +# You should have received a copy of the GNU General Public License along |
530 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
531 | +"""Test the squid service.""" |
532 | + |
533 | +import json |
534 | +import os |
535 | + |
536 | +from twisted.internet import defer |
537 | + |
538 | +from ubuntuone.devtools.testcases import BaseTestCase |
539 | +from ubuntuone.devtools.services import squid |
540 | + |
541 | +# yes, we know names with _ are private |
542 | +#pylint: disable=W0212 |
543 | + |
544 | + |
545 | +class PathsTestCase(BaseTestCase): |
546 | + """Test the different path functions.""" |
547 | + |
548 | + @defer.inlineCallbacks |
549 | + def setUp(self): |
550 | + """Set the different tests.""" |
551 | + yield super(PathsTestCase, self).setUp() |
552 | + self.basedir_fn = squid._get_basedir |
553 | + self.basedir = self.mktemp('paths') |
554 | + self.path_exists = False |
555 | + self.created_paths = [] |
556 | + self.called = [] |
557 | + |
558 | + def fake_basedir_fn(tempdir): |
559 | + """Retun the base dir.""" |
560 | + self.called.append(('fake_basedir_fn', tempdir)) |
561 | + return self.basedir |
562 | + |
563 | + def fake_makedirs(path): |
564 | + """Fake the makedirs function.""" |
565 | + self.called.append(('fake_makedirs', path)) |
566 | + self.created_paths.append(path) |
567 | + |
568 | + def fake_exists(path): |
569 | + """Fake the exists method.""" |
570 | + self.called.append(('fake_exists', path)) |
571 | + return path in self.created_paths or self.path_exists |
572 | + |
573 | + self.patch(squid, '_get_basedir', fake_basedir_fn) |
574 | + self.patch(squid, 'makedirs', fake_makedirs) |
575 | + self.patch(squid, 'exists', fake_exists) |
576 | + |
577 | + def test_get_basedir_missing(self): |
578 | + """Test the base dir creation.""" |
579 | + basedir = self.basedir_fn(self.basedir) |
580 | + expected_path = os.path.join(self.basedir, squid.SQUID_DIR) |
581 | + self.assertEqual(expected_path, basedir) |
582 | + self.assertTrue(('fake_makedirs', expected_path) in self.called) |
583 | + self.assertTrue(expected_path in self.created_paths) |
584 | + self.assertTrue(('fake_exists', expected_path) in self.called) |
585 | + |
586 | + def test_get_basedir_present(self): |
587 | + """Test the base dir creation.""" |
588 | + self.path_exists = True |
589 | + basedir = self.basedir_fn(self.basedir) |
590 | + expected_path = os.path.join(self.basedir, squid.SQUID_DIR) |
591 | + self.assertEqual(expected_path, basedir) |
592 | + expected_path = os.path.join(self.basedir, squid.SQUID_DIR) |
593 | + self.assertTrue(('fake_makedirs', expected_path) not in self.called) |
594 | + self.assertTrue(expected_path not in self.created_paths) |
595 | + self.assertTrue(('fake_exists', expected_path) in self.called) |
596 | + |
597 | + def test_get_spool_temp_path_missing(self): |
598 | + """Test the spool path creation.""" |
599 | + expected_path = os.path.join(self.basedir, squid.SPOOL_DIR) |
600 | + result = squid._get_spool_temp_path() |
601 | + self.assertEqual(expected_path, result) |
602 | + self.assertTrue(('fake_basedir_fn', '') in self.called) |
603 | + self.assertTrue(('fake_makedirs', expected_path) in self.called) |
604 | + self.assertTrue(expected_path in self.created_paths) |
605 | + self.assertTrue(('fake_exists', expected_path) in self.called) |
606 | + |
607 | + def test_get_spool_temp_path_present(self): |
608 | + """Test the spool path creation.""" |
609 | + self.path_exists = True |
610 | + expected_path = os.path.join(self.basedir, squid.SPOOL_DIR) |
611 | + result = squid._get_spool_temp_path() |
612 | + self.assertEqual(expected_path, result) |
613 | + self.assertTrue(('fake_basedir_fn', '') in self.called) |
614 | + self.assertTrue(('fake_makedirs', expected_path) not in self.called) |
615 | + self.assertTrue(expected_path not in self.created_paths) |
616 | + self.assertTrue(('fake_exists', expected_path) in self.called) |
617 | + |
618 | + def test_get_squid_temp_path_missing(self): |
619 | + """Test the squid path creation.""" |
620 | + expected_path = os.path.join(self.basedir, squid.SQUID_DIR) |
621 | + result = squid._get_squid_temp_path() |
622 | + self.assertEqual(expected_path, result) |
623 | + self.assertTrue(('fake_basedir_fn', '') in self.called) |
624 | + self.assertTrue(('fake_makedirs', expected_path) in self.called) |
625 | + self.assertTrue(expected_path in self.created_paths) |
626 | + self.assertTrue(('fake_exists', expected_path) in self.called) |
627 | + |
628 | + def test_get_squid_temp_path_present(self): |
629 | + """Test the squid path creation.""" |
630 | + self.path_exists = True |
631 | + expected_path = os.path.join(self.basedir, squid.SQUID_DIR) |
632 | + result = squid._get_squid_temp_path() |
633 | + self.assertEqual(expected_path, result) |
634 | + self.assertTrue(('fake_basedir_fn', '') in self.called) |
635 | + self.assertTrue(('fake_makedirs', expected_path) not in self.called) |
636 | + self.assertTrue(expected_path not in self.created_paths) |
637 | + self.assertTrue(('fake_exists', expected_path) in self.called) |
638 | + |
639 | + def test_get_auth_temp_path(self): |
640 | + """Test the creation of the auth path.""" |
641 | + self.path_exists = False |
642 | + expected_path = os.path.join(self.basedir, squid.AUTH_FILE) |
643 | + result = squid._get_auth_temp_path() |
644 | + self.assertEqual(expected_path, result) |
645 | + self.assertTrue(('fake_basedir_fn', '') in self.called) |
646 | + self.assertTrue(('fake_makedirs', self.basedir) in self.called) |
647 | + self.assertTrue(self.basedir in self.created_paths) |
648 | + self.assertTrue(('fake_exists', self.basedir) in self.called) |
649 | + |
650 | + |
651 | +class EnvironTestCase(BaseTestCase): |
652 | + """Test the different environ functions.""" |
653 | + |
654 | + @defer.inlineCallbacks |
655 | + def setUp(self): |
656 | + """Set the tests.""" |
657 | + yield super(EnvironTestCase, self).setUp() |
658 | + self.called = [] |
659 | + self.settings = dict(noauth_port=3434, auth_port=232323, |
660 | + username='u1', password='test') |
661 | + |
662 | + def fake_dumps(data): |
663 | + """Fake dumps.""" |
664 | + self.called.append(('dumps', data)) |
665 | + return json.dumps(data) |
666 | + |
667 | + def fake_loads(data): |
668 | + """Fake loads.""" |
669 | + self.called.append(('loads', data)) |
670 | + return json.loads(data) |
671 | + |
672 | + self.patch(squid, 'dumps', fake_dumps) |
673 | + self.patch(squid, 'loads', fake_loads) |
674 | + self.env = {} |
675 | + self.old_env = os.environ |
676 | + squid.environ = self.env |
677 | + self.addCleanup(self.set_back_environ) |
678 | + |
679 | + def set_back_environ(self): |
680 | + """Set back the env.""" |
681 | + squid.environ = self.old_env |
682 | + |
683 | + def test_store_settings(self): |
684 | + """Test the storage of the settings.""" |
685 | + squid.store_proxy_settings(self.settings) |
686 | + self.assertTrue(('dumps', self.settings) in self.called) |
687 | + self.assertEqual(self.env[squid.PROXY_ENV_VAR], |
688 | + json.dumps(self.settings)) |
689 | + |
690 | + def test_retrieve_proxy_settings(self): |
691 | + """Test reading the settings.""" |
692 | + self.env[squid.PROXY_ENV_VAR] = json.dumps(self.settings) |
693 | + self.assertTrue(('loads', self.env[squid.PROXY_ENV_VAR])) |
694 | + self.assertEqual(squid.retrieve_proxy_settings(), self.settings) |
695 | + |
696 | + def test_delete_proxy_settings_present(self): |
697 | + """Delete the proxy settings.""" |
698 | + self.env[squid.PROXY_ENV_VAR] = json.dumps(self.settings) |
699 | + squid.delete_proxy_settings() |
700 | + self.assertFalse(squid.PROXY_ENV_VAR in self.env) |
701 | + |
702 | + |
703 | +class SquidRunnerInitTestCase(BaseTestCase): |
704 | + """Test the creation of the runner.""" |
705 | + |
706 | + @defer.inlineCallbacks |
707 | + def setUp(self): |
708 | + """Set the different tests.""" |
709 | + yield super(SquidRunnerInitTestCase, self).setUp() |
710 | + self.executables = {} |
711 | + self.called = [] |
712 | + |
713 | + def fake_find_executable(executable): |
714 | + """Fake the find executable.""" |
715 | + self.called.append(('fake_find_executable', executable)) |
716 | + return self.executables.get(executable, None) |
717 | + |
718 | + self.patch(squid, 'find_executable', fake_find_executable) |
719 | + |
720 | + def _assert_missing_binary(self, binary): |
721 | + """Perform the assertion when a bin is missing.""" |
722 | + self.assertRaises(squid.SquidLaunchError, squid.SquidRunner) |
723 | + self.assertTrue(('fake_find_executable', binary) in self.called, |
724 | + self.called) |
725 | + |
726 | + def test_squid_missing(self): |
727 | + """Test when squid is missing.""" |
728 | + self.executables['htpasswd'] = 'htpasswd' |
729 | + self._assert_missing_binary('squid') |
730 | + |
731 | + def test_htpasswd_missing(self): |
732 | + """Test when htpasswd is missing.""" |
733 | + self.executables['squid'] = 'squid' |
734 | + self.executables['squid3'] = 'squid' |
735 | + self._assert_missing_binary('htpasswd') |
736 | + |
737 | + |
738 | +class FakeSubprocess(object): |
739 | + """Fake the subprocess module.""" |
740 | + |
741 | + # pylint: disable=C0103 |
742 | + def __init__(self): |
743 | + """Create a new instance.""" |
744 | + self.called = [] |
745 | + self.PIPE = 'PIPE' |
746 | + |
747 | + def Popen(self, args, stdout=None, stderr=None): |
748 | + """Fake Popen.""" |
749 | + self.called.append(('Popen', args, stdout, stderr)) |
750 | + return self |
751 | + # pylint: enable=C0103 |
752 | + |
753 | + def wait(self): |
754 | + """Fake wait from a Popen object.""" |
755 | + self.called.append(('wait',)) |
756 | + |
757 | + |
758 | +class FakeTemplate(object): |
759 | + """Fake the string.Template.""" |
760 | + |
761 | + def __init__(self): |
762 | + """Create a new instance.""" |
763 | + self.data = None |
764 | + self.called = [] |
765 | + |
766 | + def __call__(self, data): |
767 | + """Fake constructor.""" |
768 | + self.data = data |
769 | + return self |
770 | + |
771 | + def safe_substitute(self, *args, **kwargs): |
772 | + """Fake the safe_substitute.""" |
773 | + self.called.append(('safe_substitute', args, kwargs)) |
774 | + return self.data |
775 | + |
776 | + |
777 | +class SquidRunnerTestCase(BaseTestCase): |
778 | + """Test the default test case.""" |
779 | + |
780 | + @defer.inlineCallbacks |
781 | + def setUp(self): |
782 | + """Set the different tests.""" |
783 | + yield super(SquidRunnerTestCase, self).setUp() |
784 | + self.subprocess = FakeSubprocess() |
785 | + self.patch(squid, 'subprocess', self.subprocess) |
786 | + |
787 | + self.called = [] |
788 | + self.executables = dict(squid='squid', htpasswd='htpasswd') |
789 | + |
790 | + def fake_find_executable(executable): |
791 | + """Fake the find executable.""" |
792 | + self.called.append(('fake_find_executable', executable)) |
793 | + return self.executables.get(executable, None) |
794 | + |
795 | + self.patch(squid, 'find_executable', fake_find_executable) |
796 | + |
797 | + self.auth_temp = 'path/to/auth' |
798 | + |
799 | + def fake_get_auth_temp_path(tempdir): |
800 | + """Return the path for the auth file.""" |
801 | + self.called.append(('fake_get_auth_temp_path', tempdir)) |
802 | + return self.auth_temp |
803 | + |
804 | + self.patch(squid, '_get_auth_temp_path', fake_get_auth_temp_path) |
805 | + |
806 | + self.port = 2324 |
807 | + |
808 | + def fake_get_port(): |
809 | + """Fake the methos that returns the ports.""" |
810 | + self.called.append(('fake_get_port',)) |
811 | + return self.port |
812 | + |
813 | + self.patch(squid, 'get_arbitrary_port', fake_get_port) |
814 | + self.template = FakeTemplate() |
815 | + self.patch(squid.string, 'Template', self.template) |
816 | + self.runner = squid.SquidRunner() |
817 | + |
818 | + def test_generate_swap(self): |
819 | + """Test the generation of the squid swap.""" |
820 | + config_file = 'path/to/config' |
821 | + expected_args = ['squid', '-z', '-f', config_file] |
822 | + self.runner._generate_swap(config_file) |
823 | + self.assertEqual(expected_args, self.subprocess.called[0][1]) |
824 | + self.assertTrue('wait' in self.subprocess.called[1]) |
825 | + |
826 | + def test_generate_auth_file(self): |
827 | + """Test the generation of the auth file.""" |
828 | + username = self.runner.settings['username'] = 'mandel' |
829 | + password = self.runner.settings['password'] = 'test' |
830 | + expected_args = ['htpasswd', '-bc', self.auth_temp, |
831 | + username, password] |
832 | + self.patch(squid, 'exists', lambda f: False) |
833 | + self.runner._generate_auth_file() |
834 | + self.assertEqual(expected_args, self.subprocess.called[0][1]) |
835 | + self.assertTrue('wait' in self.subprocess.called[1]) |
836 | + |
837 | + def test_generate_config_file(self): |
838 | + """Test the generation of the config file.""" |
839 | + self.runner.auth_file = self.auth_temp |
840 | + self.runner._generate_config_file(self.tmpdir) |
841 | + # remove the generated file |
842 | + self.addCleanup(os.unlink, self.runner.config_file) |
843 | + expected_parameters = ('safe_substitute', (), |
844 | + dict(auth_file=self.runner.auth_file, |
845 | + noauth_port_number=self.port, |
846 | + auth_port_number=self.port, |
847 | + spool_temp=squid._get_spool_temp_path(self.tmpdir), |
848 | + squid_temp=squid._get_squid_temp_path(self.tmpdir))) |
849 | + self.assertTrue(expected_parameters, self.template.called[0]) |
850 | + self.assertEqual(2, self.called.count(('fake_get_port',))) |
851 | |
852 | === modified file 'ubuntuone/devtools/testcases/dbus.py' |
853 | --- ubuntuone/devtools/testcases/dbus.py 2011-11-23 11:14:00 +0000 |
854 | +++ ubuntuone/devtools/testcases/dbus.py 2012-01-05 16:48:29 +0000 |
855 | @@ -14,7 +14,7 @@ |
856 | # You should have received a copy of the GNU General Public License along |
857 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
858 | |
859 | -"""Base tests cases and test utilities.""" |
860 | +"""Base dbus tests cases and test utilities.""" |
861 | |
862 | from __future__ import absolute_import, with_statement |
863 | |
864 | |
865 | === added file 'ubuntuone/devtools/testcases/squid.py' |
866 | --- ubuntuone/devtools/testcases/squid.py 1970-01-01 00:00:00 +0000 |
867 | +++ ubuntuone/devtools/testcases/squid.py 2012-01-05 16:48:29 +0000 |
868 | @@ -0,0 +1,58 @@ |
869 | +# -*- coding: utf-8 -*- |
870 | +# |
871 | +# Copyright 2011 Canonical Ltd. |
872 | +# |
873 | +# This program is free software: you can redistribute it and/or modify it |
874 | +# under the terms of the GNU General Public License version 3, as published |
875 | +# by the Free Software Foundation. |
876 | +# |
877 | +# This program is distributed in the hope that it will be useful, but |
878 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
879 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
880 | +# PURPOSE. See the GNU General Public License for more details. |
881 | +# |
882 | +# You should have received a copy of the GNU General Public License along |
883 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
884 | + |
885 | +"""Base squid tests cases and test utilities.""" |
886 | + |
887 | +from ubuntuone.devtools.testcase import BaseTestCase, skipIf |
888 | +from ubuntuone.devtools.services.squid import ( |
889 | + SquidRunner, |
890 | + SquidLaunchError, |
891 | + get_squid_executable, |
892 | + get_htpasswd_executable, |
893 | + retrieve_proxy_settings) |
894 | + |
895 | +# pylint: disable=C0103 |
896 | +squid, _ = get_squid_executable() |
897 | +htpasswd = get_htpasswd_executable() |
898 | +# pylint: enable=C0103 |
899 | + |
900 | + |
901 | +@skipIf(squid is None or htpasswd is None, |
902 | + 'The test requires squid and htpasswd.') |
903 | +class SquidTestCase(BaseTestCase): |
904 | + """Test that uses a proxy.""" |
905 | + |
906 | + def required_services(self): |
907 | + """Return the list of required services for DBusTestCase.""" |
908 | + services = super(SquidTestCase, self).required_services() |
909 | + services.extend([SquidRunner]) |
910 | + return services |
911 | + |
912 | + def get_nonauth_proxy_settings(self): |
913 | + """Return the settings of the noneauth proxy.""" |
914 | + settings = retrieve_proxy_settings() |
915 | + if settings is None: |
916 | + raise SquidLaunchError('Proxy is not running.') |
917 | + return dict(host='localhost', port=settings['noauth_port']) |
918 | + |
919 | + def get_auth_proxy_settings(self): |
920 | + """Return the settings of the auth proxy.""" |
921 | + settings = retrieve_proxy_settings() |
922 | + if settings is None: |
923 | + raise SquidLaunchError('Proxy is not running.') |
924 | + return dict(host='localhost', port=settings['auth_port'], |
925 | + username=settings['username'], |
926 | + password=settings['password']) |
927 | |
928 | === added directory 'ubuntuone/devtools/testcases/tests' |
929 | === added file 'ubuntuone/devtools/testcases/tests/__init__.py' |
930 | --- ubuntuone/devtools/testcases/tests/__init__.py 1970-01-01 00:00:00 +0000 |
931 | +++ ubuntuone/devtools/testcases/tests/__init__.py 2012-01-05 16:48:29 +0000 |
932 | @@ -0,0 +1,16 @@ |
933 | +# -*- coding: utf-8 -*- |
934 | +# |
935 | +# Copyright 2011 Canonical Ltd. |
936 | +# |
937 | +# This program is free software: you can redistribute it and/or modify it |
938 | +# under the terms of the GNU General Public License version 3, as published |
939 | +# by the Free Software Foundation. |
940 | +# |
941 | +# This program is distributed in the hope that it will be useful, but |
942 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
943 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
944 | +# PURPOSE. See the GNU General Public License for more details. |
945 | +# |
946 | +# You should have received a copy of the GNU General Public License along |
947 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
948 | +"""Tests for the test cases.""" |
949 | |
950 | === added file 'ubuntuone/devtools/testcases/tests/test_squid_testcase.py' |
951 | --- ubuntuone/devtools/testcases/tests/test_squid_testcase.py 1970-01-01 00:00:00 +0000 |
952 | +++ ubuntuone/devtools/testcases/tests/test_squid_testcase.py 2012-01-05 16:48:29 +0000 |
953 | @@ -0,0 +1,262 @@ |
954 | +# -*- coding: utf-8 -*- |
955 | +# |
956 | +# Copyright 2011 Canonical Ltd. |
957 | +# |
958 | +# This program is free software: you can redistribute it and/or modify it |
959 | +# under the terms of the GNU General Public License version 3, as published |
960 | +# by the Free Software Foundation. |
961 | +# |
962 | +# This program is distributed in the hope that it will be useful, but |
963 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
964 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
965 | +# PURPOSE. See the GNU General Public License for more details. |
966 | +# |
967 | +# You should have received a copy of the GNU General Public License along |
968 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
969 | +"""Test the squid test case.""" |
970 | +import base64 |
971 | + |
972 | +from twisted.application import internet, service |
973 | +from twisted.internet import defer, reactor |
974 | +from twisted.web import client, error, http, resource, server |
975 | + |
976 | +from ubuntuone.devtools.testcases.squid import SquidTestCase |
977 | + |
978 | + |
979 | +SAMPLE_RESOURCE = "<p>Hello World!</p>" |
980 | +SIMPLERESOURCE = "simpleresource" |
981 | +THROWERROR = "throwerror" |
982 | +UNAUTHORIZED = "unauthorized" |
983 | + |
984 | +# ignore common twisted lint errors |
985 | +# pylint: disable=C0103, W0212 |
986 | + |
987 | + |
988 | +class ProxyClientFactory(client.HTTPClientFactory): |
989 | + """Factory that supports proxy.""" |
990 | + |
991 | + def __init__(self, proxy_url, proxy_port, url, headers=None): |
992 | + # we set the proxy details before the init because the parent __init__ |
993 | + # calls setURL |
994 | + self.proxy_url = proxy_url |
995 | + self.proxy_port = proxy_port |
996 | + self.disconnected_d = defer.Deferred() |
997 | + client.HTTPClientFactory.__init__(self, url, headers=headers) |
998 | + |
999 | + def setURL(self, url): |
1000 | + self.host = self.proxy_url |
1001 | + self.port = self.proxy_port |
1002 | + self.url = url |
1003 | + self.path = url |
1004 | + |
1005 | + def clientConnectionLost(self, connector, reason, reconnecting=0): |
1006 | + """Connection lost.""" |
1007 | + self.disconnected_d.callback(self) |
1008 | + |
1009 | + |
1010 | +class ProxyWebClient(object): |
1011 | + """Provide useful web methods with proxy.""" |
1012 | + |
1013 | + def __init__(self, proxy_url=None, proxy_port=None, username=None, |
1014 | + password=None): |
1015 | + """Create a new instance with the proxy settings.""" |
1016 | + self.proxy_url = proxy_url |
1017 | + self.proxy_port = proxy_port |
1018 | + self.username = username |
1019 | + self.password = password |
1020 | + self.factory = None |
1021 | + self.connectors = [] |
1022 | + |
1023 | + def _connect(self, url, contextFactory): |
1024 | + """Perform the connection.""" |
1025 | + scheme, _, _, _ = client._parse(url) |
1026 | + # pylint: disable=E1101 |
1027 | + if scheme == 'https': |
1028 | + from twisted.internet import ssl |
1029 | + if contextFactory is None: |
1030 | + contextFactory = ssl.ClientContextFactory() |
1031 | + self.connectors.append(reactor.connectSSL(self.proxy_url, |
1032 | + self.proxy_port, |
1033 | + self.factory, |
1034 | + contextFactory)) |
1035 | + else: |
1036 | + self.connectors.append(reactor.connectTCP(self.proxy_url, |
1037 | + self.proxy_port, |
1038 | + self.factory)) |
1039 | + # pylint: enable=E1101 |
1040 | + |
1041 | + def _process_auth_error(self, failure, url, contextFactory): |
1042 | + """Process an auth failure.""" |
1043 | + failure.trap(error.Error) |
1044 | + if failure.value.status == str(http.PROXY_AUTH_REQUIRED): |
1045 | + # we try to get the page using the basic auth |
1046 | + auth = base64.b64encode('%s:%s' % (self.username, self.password)) |
1047 | + auth_header = 'Basic ' + auth.strip() |
1048 | + self.factory = ProxyClientFactory(self.proxy_url, self.proxy_port, |
1049 | + url, headers={'Proxy-Authorization': auth_header}) |
1050 | + self._connect(url, contextFactory) |
1051 | + return self.factory.deferred |
1052 | + else: |
1053 | + return failure |
1054 | + |
1055 | + def get_page(self, url, contextFactory=None, *args, **kwargs): |
1056 | + """Download a webpage as a string. |
1057 | + |
1058 | + This method relies on the twisted.web.client.getPage but adds and extra |
1059 | + step. If there is an auth error the method will perform a second try |
1060 | + so that the username and password are used. |
1061 | + """ |
1062 | + self.factory = ProxyClientFactory(self.proxy_url, self.proxy_port, url, |
1063 | + headers={'Connection': 'close'}) |
1064 | + self._connect(url, contextFactory) |
1065 | + self.factory.deferred.addErrback(self._process_auth_error, url, |
1066 | + contextFactory) |
1067 | + return self.factory.deferred |
1068 | + |
1069 | + @defer.inlineCallbacks |
1070 | + def shutdown(self): |
1071 | + """Clean all connectors.""" |
1072 | + for connector in self.connectors: |
1073 | + yield connector.disconnect() |
1074 | + defer.returnValue(True) |
1075 | + |
1076 | + |
1077 | +class SimpleResource(resource.Resource): |
1078 | + """A simple web resource.""" |
1079 | + |
1080 | + def render_GET(self, request): |
1081 | + """Make a bit of html out of these resource's |
1082 | + content.""" |
1083 | + return SAMPLE_RESOURCE |
1084 | + |
1085 | + |
1086 | +class SaveHTTPChannel(http.HTTPChannel): |
1087 | + """A save protocol to be used in tests.""" |
1088 | + |
1089 | + protocolInstance = None |
1090 | + |
1091 | + def connectionMade(self): |
1092 | + """Keep track of the given protocol.""" |
1093 | + SaveHTTPChannel.protocolInstance = self |
1094 | + http.HTTPChannel.connectionMade(self) |
1095 | + |
1096 | + |
1097 | +class SaveSite(server.Site): |
1098 | + """A site that let us know when it closed.""" |
1099 | + |
1100 | + protocol = SaveHTTPChannel |
1101 | + |
1102 | + def __init__(self, *args, **kwargs): |
1103 | + """Create a new instance.""" |
1104 | + server.Site.__init__(self, *args, **kwargs) |
1105 | + # we disable the timeout in the tests, we will deal with it manually. |
1106 | + self.timeOut = None |
1107 | + |
1108 | + |
1109 | +class MockWebServer(object): |
1110 | + """A mock webserver for testing""" |
1111 | + |
1112 | + def __init__(self): |
1113 | + """Start up this instance.""" |
1114 | + root = resource.Resource() |
1115 | + root.putChild(SIMPLERESOURCE, SimpleResource()) |
1116 | + |
1117 | + root.putChild(THROWERROR, resource.NoResource()) |
1118 | + |
1119 | + unauthorized_resource = resource.ErrorPage(resource.http.UNAUTHORIZED, |
1120 | + "Unauthorized", "Unauthorized") |
1121 | + root.putChild(UNAUTHORIZED, unauthorized_resource) |
1122 | + |
1123 | + self.site = SaveSite(root) |
1124 | + application = service.Application('web') |
1125 | + self.service_collection = service.IServiceCollection(application) |
1126 | + #pylint: disable=E1101 |
1127 | + self.tcpserver = internet.TCPServer(0, self.site) |
1128 | + self.tcpserver.setServiceParent(self.service_collection) |
1129 | + self.service_collection.startService() |
1130 | + |
1131 | + def get_url(self): |
1132 | + """Build the url for this mock server.""" |
1133 | + #pylint: disable=W0212 |
1134 | + port_num = self.tcpserver._port.getHost().port |
1135 | + return "http://localhost:%d/" % port_num |
1136 | + |
1137 | + @defer.inlineCallbacks |
1138 | + def stop(self): |
1139 | + """Shut it down.""" |
1140 | + #pylint: disable=E1101 |
1141 | + # make the connection time out so that is works with squid3 when |
1142 | + # the connection is kept alive. |
1143 | + if self.site.protocol.protocolInstance: |
1144 | + self.site.protocol.protocolInstance.timeoutConnection() |
1145 | + yield self.service_collection.stopService() |
1146 | + |
1147 | + |
1148 | +class ProxyTestCase(SquidTestCase): |
1149 | + """A squid test with no auth proxy.""" |
1150 | + |
1151 | + @defer.inlineCallbacks |
1152 | + def setUp(self): |
1153 | + """Set the tests.""" |
1154 | + yield super(ProxyTestCase, self).setUp() |
1155 | + self.ws = MockWebServer() |
1156 | + self.proxy_client = None |
1157 | + self.addCleanup(self.teardown_client_server) |
1158 | + self.url = self.ws.get_url() + SIMPLERESOURCE |
1159 | + |
1160 | + def teardown_client_server(self): |
1161 | + """Clean resources.""" |
1162 | + if self.proxy_client is not None: |
1163 | + self.proxy_client.shutdown() |
1164 | + return defer.gatherResults([self.ws.stop(), |
1165 | + self.proxy_client.shutdown(), |
1166 | + self.proxy_client.factory.disconnected_d]) |
1167 | + else: |
1168 | + return self.ws.stop() |
1169 | + |
1170 | + def access_noauth_url(self, address, port): |
1171 | + """Access a url throught the proxy.""" |
1172 | + self.proxy_client = ProxyWebClient(proxy_url=address, proxy_port=port) |
1173 | + return self.proxy_client.get_page(self.url) |
1174 | + |
1175 | + def access_auth_url(self, address, port, username, password): |
1176 | + """Access a url throught the proxy.""" |
1177 | + self.proxy_client = ProxyWebClient(proxy_url=address, proxy_port=port, |
1178 | + username=username, password=password) |
1179 | + return self.proxy_client.get_page(self.url) |
1180 | + |
1181 | + @defer.inlineCallbacks |
1182 | + def test_noauth_url_access(self): |
1183 | + """Test accessing to the url.""" |
1184 | + settings = self.get_nonauth_proxy_settings() |
1185 | + # if there is an exception we fail. |
1186 | + data = yield self.access_noauth_url(settings['host'], |
1187 | + settings['port']) |
1188 | + self.assertEqual(SAMPLE_RESOURCE, data) |
1189 | + |
1190 | + @defer.inlineCallbacks |
1191 | + def test_auth_url_access(self): |
1192 | + """Test accessing to the url.""" |
1193 | + settings = self.get_auth_proxy_settings() |
1194 | + # if there is an exception we fail. |
1195 | + data = yield self.access_auth_url(settings['host'], |
1196 | + settings['port'], |
1197 | + settings['username'], |
1198 | + settings['password']) |
1199 | + self.assertEqual(SAMPLE_RESOURCE, data) |
1200 | + |
1201 | + def test_auth_url_401(self): |
1202 | + """Test failing accessing the url.""" |
1203 | + settings = self.get_auth_proxy_settings() |
1204 | + # swap password for username to fail |
1205 | + d = self.failUnlessFailure(self.access_auth_url(settings['host'], |
1206 | + settings['port'], settings['password'], |
1207 | + settings['username']), error.Error) |
1208 | + return d |
1209 | + |
1210 | + def test_auth_url_407(self): |
1211 | + """Test failing accessing the url.""" |
1212 | + settings = self.get_auth_proxy_settings() |
1213 | + d = self.failUnlessFailure(self.access_noauth_url(settings['host'], |
1214 | + settings['port']), error.Error) |
1215 | + return d |
856 +"""Base swuid tests cases and test utilities."""
swuid -> squid
861 +# DBusRunner for DBusTestCase using tests devtools. services. squid import (
862 +from ubuntuone.
I don't think it's importing DBus bits here. :)
151 +def find_config_ file(in_ config_ file, tempdir=None):
find_config_file doesn't actually even use tempdir, so you can remove the argument, and change the calls to it, to not pass it in.