Merge lp:~mandel/ubuntuone-dev-tools/fix-squid-tests into lp:ubuntuone-dev-tools

Proposed by Manuel de la Peña
Status: Merged
Approved by: Manuel de la Peña
Approved revision: 76
Merged at revision: 70
Proposed branch: lp:~mandel/ubuntuone-dev-tools/fix-squid-tests
Merge into: lp:ubuntuone-dev-tools
Diff against target: 509 lines (+266/-33)
9 files modified
data/squid.conf.in (+5/-5)
run-tests (+2/-2)
run-tests.bat (+3/-1)
setup.py (+17/-9)
ubuntuone/devtools/services/squid.py (+54/-9)
ubuntuone/devtools/services/tests/test_squid.py (+9/-6)
ubuntuone/devtools/services/tests/test_squid_linux.py (+67/-0)
ubuntuone/devtools/services/tests/test_squid_windows.py (+108/-0)
ubuntuone/devtools/testcases/tests/test_squid_testcase.py (+1/-1)
To merge this branch: bzr merge lp:~mandel/ubuntuone-dev-tools/fix-squid-tests
Reviewer Review Type Date Requested Status
dobey (community) Approve
Eric Casteleijn (community) Approve
Review via email: mp+103325@code.launchpad.net

Commit message

- Ensured that the ncsa_auth.exe can be found on windows (LP: #987213).
- Formatted the path correctly so that the squid config can read them (LP: #987225).
- Use win32api to kill the squid process (LP: #987256)
- Use dirspec to place the data files correctly on windows (LP: #988228).

Description of the change

- Ensured that the ncsa_auth.exe can be found on windows (LP: #987213).
- Formatted the path correctly so that the squid config can read them (LP: #987225).
- Use win32api to kill the squid process (LP: #987256)
- Use dirspec to place the data files correctly on windows (LP: #988228).

In order to tests the branch you have to follow these steps:

1- Download and copy to C:\squid the windows squid binaries from here: http://squid.acmeconsulting.it/
2- Downlaod the apacache msi and install it: http://httpd.apache.org/download.cgi
3- Ensure the following is in your path: C:\squid\sbin;C:\squid\libexec;C:\Program Files\Apache Software Foundation\Apache2.2\bin

All the above will allow you to run the proxy tests on windows.

Note: The path to the apache bins might differ depending on your system.

To post a comment you must log in.
Revision history for this message
Eric Casteleijn (thisfred) wrote :

98 +if sys.platform == 'win32':
99 + from ubuntuone.devtools.services.squid import windows
100 + source = windows
101 +else:
102 + from ubuntuone.devtools.services.squid import linux
103 + source = linux

why not import windows as source, and import linux as source?

Revision history for this message
Eric Casteleijn (thisfred) wrote :

Also, what does "source" mean in this context?

Revision history for this message
Eric Casteleijn (thisfred) wrote :

I would suggest something like "squid_util" or "squid_helpers" instead of source.

review: Needs Information
Revision history for this message
Eric Casteleijn (thisfred) wrote :

Looks good otherwise, use your own judgement on the above...

review: Approve
Revision history for this message
dobey (dobey) wrote :

+# Do use doble \ because squids config needs \ to be escaped
+ """Return the path for the formated for the config."""

Some spelling and grammar fixes need to happen. :)

Also, why do we need to split the Linux and Windows pieces up like this? We need to find a way to run all the tests on all platforms, and just skip the ones that are platform-specific on other platforms.

review: Needs Fixing
Revision history for this message
Manuel de la Peña (mandel) wrote :

> +# Do use doble \ because squids config needs \ to be escaped
> + """Return the path for the formated for the config."""
>
> Some spelling and grammar fixes need to happen. :)

Fixing!

>
> Also, why do we need to split the Linux and Windows pieces up like this? We
> need to find a way to run all the tests on all platforms, and just skip the
> ones that are platform-specific on other platforms.

Well this is not splitting the tests according to the platform but working around the fact that squid on windows is not a good citizen. The following is a list of the workarounds we have to do to ensure that we can run all squid tests on windows:

* os.kill does not work on windows, we need to use the win api or we will leave the proxy running.
* The squid config is stupid, it needs paths to be C:\\path\\to\\cache rather than C:\path\to\cache, if you don't do that squid wont start.
* The location of the ncsa_auth is diff.

With that fixed we are able to run all tests as long as squid and htpasswd are present in the path.

Revision history for this message
Manuel de la Peña (mandel) wrote :

> I would suggest something like "squid_util" or "squid_helpers" instead of
> source.

Fixing!

Revision history for this message
dobey (dobey) :
review: Approve
Revision history for this message
Ubuntu One Auto Pilot (otto-pilot) wrote :

Attempt to merge into lp:ubuntuone-dev-tools failed due to conflicts:

text conflict in run-tests.bat

76. By Manuel de la Peña

Fixed conflicts issues with trunk.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'data/squid.conf.in'
2--- data/squid.conf.in 2012-01-05 16:42:37 +0000
3+++ data/squid.conf.in 2012-04-30 11:07:20 +0000
4@@ -80,19 +80,19 @@
5 cache_dir ufs ${spool_temp} 1000 16 256
6
7 # access log settings
8-access_log ${squid_temp}/access.log squid
9+access_log ${squid_temp}access.log squid
10
11 # cache log settings
12-cache_log ${squid_temp}/cache.log
13+cache_log ${squid_temp}cache.log
14
15 # cache store log settings
16-cache_store_log ${squid_temp}/store.log
17+cache_store_log ${squid_temp}store.log
18
19 # mime table conf
20 # mime_table /usr/share/squid/mime.conf
21
22 #Default pid file name
23-pid_filename ${squid_temp}/squid.pid
24+pid_filename ${squid_temp}squid.pid
25
26 # debug options (Full debugging)
27 debug_options ALL,1
28@@ -117,4 +117,4 @@
29 hosts_file /etc/hosts
30
31 # Leave coredumps in the first cache dir
32-coredump_dir ${spool_temp}/squid
33+coredump_dir ${spool_temp}squid
34
35=== modified file 'run-tests'
36--- run-tests 2012-01-12 12:08:22 +0000
37+++ run-tests 2012-04-30 11:07:20 +0000
38@@ -16,8 +16,8 @@
39 # with this program. If not, see <http://www.gnu.org/licenses/>.
40 set -e
41
42-bin/u1trial -c ubuntuone
43-bin/u1trial --reactor=twisted ubuntuone
44+bin/u1trial -i "test_squid_windows.py" -c ubuntuone
45+bin/u1trial --reactor=twisted -i "test_squid_windows.py" ubuntuone
46 echo "Running style checks..."
47 bin/u1lint
48 pep8 --repeat . bin/* --exclude=*.bat
49
50=== modified file 'run-tests.bat'
51--- run-tests.bat 2012-04-25 12:33:48 +0000
52+++ run-tests.bat 2012-04-30 11:07:20 +0000
53@@ -28,6 +28,8 @@
54 :: files in the program, then also delete it here.
55 @ECHO off
56
57+SET IGNORED="test_squid_linux.py"
58+
59 ECHO Checking for Python on the path
60 :: Look for Python from buildout
61 FOR %%A in (python.exe) do (SET PYTHONEXEPATH=%%~$PATH:A)
62@@ -67,7 +69,7 @@
63
64 ECHO Python found at %PYTHONEXEPATH%, executing the tests...
65 :: execute the tests with a number of ignored linux only modules
66-"%PYTHONEXEPATH%" "%TRIALPATH%" --reactor=twisted -c %PARAMS% ubuntuone
67+"%PYTHONEXEPATH%" bin/u1trial --reactor=twisted -i %IGNORED% -c %PARAMS% ubuntuone
68
69 IF %SKIPLINT% == 1 (
70 ECHO Skipping style checks
71
72=== modified file 'setup.py'
73--- setup.py 2012-03-30 17:44:03 +0000
74+++ setup.py 2012-04-30 11:07:20 +0000
75@@ -28,10 +28,12 @@
76 # files in the program, then also delete it here.
77 """setup.py"""
78
79+import os
80 import subprocess
81 import sys
82
83 from distutils.core import setup, Command
84+from dirspec import basedir
85
86 PACKAGE = 'ubuntuone-dev-tools'
87 VERSION = '3.1'
88@@ -65,6 +67,20 @@
89 if sys.platform == 'win32':
90 # lets add the .bat so that windows users are happy
91 scripts.extend(['bin/u1lint.bat', 'bin/u1trial.bat'])
92+ DATA_FILES = [(os.path.join(basedir.default_data_path, PACKAGE),
93+ ['pylintrc',
94+ 'data/dbus-session.conf.in',
95+ 'data/squid.conf.in']),
96+ ]
97+else:
98+ DATA_FILES = [('share/%s' % PACKAGE,
99+ ['pylintrc',
100+ 'data/dbus-session.conf.in',
101+ 'data/squid.conf.in']),
102+ ('share/man/man1',
103+ ['man/u1lint.1',
104+ 'man/u1trial.1']),
105+ ]
106
107 setup(name=PACKAGE,
108 version=VERSION,
109@@ -78,15 +94,7 @@
110 'ubuntuone.devtools.testcases'],
111 extra_path='ubuntuone-dev-tools',
112 scripts=scripts,
113- data_files=[('share/%s' % PACKAGE,
114- ['pylintrc',
115- 'data/dbus-session.conf.in',
116- 'data/squid.conf.in']),
117- ('share/man/man1',
118- ['man/u1lint.1',
119- 'man/u1trial.1']),
120- ],
121-
122+ data_files=DATA_FILES,
123 cmdclass={
124 'lint': Lint,
125 },
126
127=== modified file 'ubuntuone/devtools/services/squid.py'
128--- ubuntuone/devtools/services/squid.py 2012-04-20 17:00:51 +0000
129+++ ubuntuone/devtools/services/squid.py 2012-04-30 11:07:20 +0000
130@@ -33,10 +33,11 @@
131 import string
132 # pylint:enable=W0402
133 import subprocess
134+import sys
135 import time
136
137 from json import dumps, loads
138-from os import environ, makedirs, kill, unlink
139+from os import environ, kill, makedirs, unlink
140 from os.path import abspath, exists, join
141
142 from distutils.spawn import find_executable
143@@ -46,24 +47,52 @@
144 get_arbitrary_port,
145 )
146
147+# pylint: disable=C0103
148+if sys.platform == 'win32':
149+ AUTH_PROCESS_PATH = r'C:\squid\libexec\ncsa_auth.exe'
150+ SQUID_START_ARGS = ['-f']
151+else:
152+ AUTH_PROCESS_PATH = '/usr/lib/%s/ncsa_auth'
153+ SQUID_START_ARGS = ['-N', '-X', '-f']
154+# pylint: enable=C0103
155+
156 SQUID_CONFIG_FILE = 'squid.conf.in'
157 SQUID_DIR = 'squid'
158-AUTH_PROCESS_PATH = '/usr/lib/%s/ncsa_auth'
159 SPOOL_DIR = 'spool'
160 AUTH_FILE = 'htpasswd'
161 PROXY_ENV_VAR = 'SQUID_PROXY_SETTINGS'
162
163
164+def format_config_path(path):
165+ """Return the path correctly formatted for the config file."""
166+ # squid cannot handle correctly paths with a single \
167+ return path.replace('\\', '\\\\')
168+
169+
170+def get_auth_process_path(squid_version):
171+ """Return the path to the auth executable."""
172+ if sys.platform == 'win32':
173+ path = find_executable('ncsa_auth')
174+ if path is None:
175+ path = AUTH_PROCESS_PATH
176+ return format_config_path(path)
177+ else:
178+ squid = 'squid3' if squid_version == 3 else 'squid'
179+ path = AUTH_PROCESS_PATH % squid
180+ return path
181+
182+
183 def get_squid_executable():
184 """Return the squid executable of the system."""
185 # try with squid and if not present try with squid3 for newer systems
186 # (Ubuntu P). We also return the path to the auth process so that we can
187 # point to the correct one.
188 squid = find_executable('squid3')
189- auth_process = AUTH_PROCESS_PATH % 'squid3'
190+ version = 3
191 if squid is None:
192+ version = 2
193 squid = find_executable('squid')
194- auth_process = AUTH_PROCESS_PATH % 'squid'
195+ auth_process = get_auth_process_path(version)
196 return squid, auth_process
197
198
199@@ -72,6 +101,21 @@
200 return find_executable('htpasswd')
201
202
203+def kill_squid(squid_pid):
204+ """Kill the squid process."""
205+ if sys.platform == 'win32':
206+ # pylint: disable=F0401
207+ import win32api
208+ import win32con
209+ # pylint: enable=F0401
210+
211+ handle = win32api.OpenProcess(win32con.PROCESS_TERMINATE, 0, squid_pid)
212+ win32api.TerminateProcess(handle, 0)
213+ win32api.CloseHandle(handle)
214+ else:
215+ kill(squid_pid, signal.SIGKILL)
216+
217+
218 def _make_random_string(count):
219 """Make a random string of the given length."""
220 entropy = random.SystemRandom()
221@@ -95,7 +139,7 @@
222 path = abspath(path)
223 if not exists(path):
224 makedirs(path)
225- return path
226+ return format_config_path(path)
227
228
229 def _get_squid_temp_path(tempdir=''):
230@@ -105,7 +149,7 @@
231 path = abspath(path)
232 if not exists(path):
233 makedirs(path)
234- return path
235+ return format_config_path(join(path, ''))
236
237
238 def _get_auth_temp_path(tempdir=''):
239@@ -114,7 +158,7 @@
240 auth_file = join(basedir, AUTH_FILE)
241 if not exists(basedir):
242 makedirs(basedir)
243- return auth_file
244+ return format_config_path(auth_file)
245
246
247 def store_proxy_settings(settings):
248@@ -238,7 +282,8 @@
249 self._generate_auth_file(tempdir)
250 self._generate_config_file(tempdir)
251 self._generate_swap(self.config_file)
252- squid_args = ['-N', '-X', '-f', self.config_file]
253+ squid_args = SQUID_START_ARGS
254+ squid_args.append(self.config_file)
255 sp = subprocess.Popen([self.squid] + squid_args,
256 stdout=subprocess.PIPE,
257 stderr=subprocess.PIPE)
258@@ -255,7 +300,7 @@
259
260 def stop_service(self):
261 """Stop our proxy,"""
262- kill(self.squid_pid, signal.SIGKILL)
263+ kill_squid(self.squid_pid)
264 delete_proxy_settings()
265 self.running = False
266 unlink(self.config_file)
267
268=== modified file 'ubuntuone/devtools/services/tests/test_squid.py'
269--- ubuntuone/devtools/services/tests/test_squid.py 2012-04-20 16:42:54 +0000
270+++ ubuntuone/devtools/services/tests/test_squid.py 2012-04-30 11:07:20 +0000
271@@ -72,6 +72,7 @@
272 self.patch(squid, '_get_basedir', fake_basedir_fn)
273 self.patch(squid, 'makedirs', fake_makedirs)
274 self.patch(squid, 'exists', fake_exists)
275+ self.patch(squid, 'format_config_path', lambda path: path)
276
277 def test_get_basedir_missing(self):
278 """Test the base dir creation."""
279@@ -116,24 +117,26 @@
280
281 def test_get_squid_temp_path_missing(self):
282 """Test the squid path creation."""
283- expected_path = os.path.join(self.basedir, squid.SQUID_DIR)
284+ expected_path = os.path.join(self.basedir, squid.SQUID_DIR, '')
285+ abspath = os.path.abspath(expected_path)
286 result = squid._get_squid_temp_path()
287 self.assertEqual(expected_path, result)
288 self.assertTrue(('fake_basedir_fn', '') in self.called)
289- self.assertTrue(('fake_makedirs', expected_path) in self.called)
290- self.assertTrue(expected_path in self.created_paths)
291- self.assertTrue(('fake_exists', expected_path) in self.called)
292+ self.assertTrue(('fake_makedirs', abspath) in self.called)
293+ self.assertTrue(abspath in self.created_paths)
294+ self.assertTrue(('fake_exists', abspath) in self.called)
295
296 def test_get_squid_temp_path_present(self):
297 """Test the squid path creation."""
298 self.path_exists = True
299- expected_path = os.path.join(self.basedir, squid.SQUID_DIR)
300+ expected_path = os.path.join(self.basedir, squid.SQUID_DIR, '')
301 result = squid._get_squid_temp_path()
302 self.assertEqual(expected_path, result)
303 self.assertTrue(('fake_basedir_fn', '') in self.called)
304 self.assertTrue(('fake_makedirs', expected_path) not in self.called)
305 self.assertTrue(expected_path not in self.created_paths)
306- self.assertTrue(('fake_exists', expected_path) in self.called)
307+ self.assertTrue(('fake_exists',
308+ os.path.abspath(expected_path)) in self.called)
309
310 def test_get_auth_temp_path(self):
311 """Test the creation of the auth path."""
312
313=== added file 'ubuntuone/devtools/services/tests/test_squid_linux.py'
314--- ubuntuone/devtools/services/tests/test_squid_linux.py 1970-01-01 00:00:00 +0000
315+++ ubuntuone/devtools/services/tests/test_squid_linux.py 2012-04-30 11:07:20 +0000
316@@ -0,0 +1,67 @@
317+#
318+# Copyright 2012 Canonical Ltd.
319+#
320+# This program is free software: you can redistribute it and/or modify it
321+# under the terms of the GNU General Public License version 3, as published
322+# by the Free Software Foundation.
323+#
324+# This program is distributed in the hope that it will be useful, but
325+# WITHOUT ANY WARRANTY; without even the implied warranties of
326+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
327+# PURPOSE. See the GNU General Public License for more details.
328+#
329+# You should have received a copy of the GNU General Public License along
330+# with this program. If not, see <http://www.gnu.org/licenses/>.
331+#
332+# In addition, as a special exception, the copyright holders give
333+# permission to link the code of portions of this program with the
334+# OpenSSL library under certain conditions as described in each
335+# individual source file, and distribute linked combinations
336+# including the two.
337+# You must obey the GNU General Public License in all respects
338+# for all of the code used other than OpenSSL. If you modify
339+# file(s) with this exception, you may extend this exception to your
340+# version of the file(s), but you are not obligated to do so. If you
341+# do not wish to do so, delete this exception statement from your
342+# version. If you delete this exception statement from all source
343+# files in the program, then also delete it here.
344+"""Tests for the linux squid bits."""
345+
346+import signal
347+
348+from twisted.trial.unittest import TestCase
349+
350+from ubuntuone.devtools.services import squid
351+
352+
353+class SquidLinuxTestCase(TestCase):
354+ """"Test the different linux bits."""
355+
356+ def test_get_auth_process_3(self):
357+ """Test getting the auth process for squid3."""
358+ expected = squid.AUTH_PROCESS_PATH % 'squid3'
359+ self.assertEqual(expected, squid.get_auth_process_path(3))
360+
361+ def test_get_auth_process(self):
362+ """Test getting the auth process."""
363+ expected = squid.AUTH_PROCESS_PATH % 'squid'
364+ self.assertEqual(expected, squid.get_auth_process_path(2))
365+
366+ def test_format_config_path(self):
367+ """Test formating a config path."""
368+ path = '/a/config/path'
369+ self.assertEqual(path, squid.format_config_path(path))
370+
371+ def test_kill_squid(self):
372+ """Test killing squid."""
373+ called = []
374+
375+ def fake_kill(pid, kill_signal):
376+ """Fake os.kill."""
377+ called.append(('kill', pid, kill_signal))
378+
379+ self.patch(squid, 'kill', fake_kill)
380+
381+ squid_pid = 4
382+ squid.kill_squid(squid_pid)
383+ self.assertIn(('kill', squid_pid, signal.SIGKILL), called)
384
385=== added file 'ubuntuone/devtools/services/tests/test_squid_windows.py'
386--- ubuntuone/devtools/services/tests/test_squid_windows.py 1970-01-01 00:00:00 +0000
387+++ ubuntuone/devtools/services/tests/test_squid_windows.py 2012-04-30 11:07:20 +0000
388@@ -0,0 +1,108 @@
389+#
390+# Copyright 2012 Canonical Ltd.
391+#
392+# This program is free software: you can redistribute it and/or modify it
393+# under the terms of the GNU General Public License version 3, as published
394+# by the Free Software Foundation.
395+#
396+# This program is distributed in the hope that it will be useful, but
397+# WITHOUT ANY WARRANTY; without even the implied warranties of
398+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
399+# PURPOSE. See the GNU General Public License for more details.
400+#
401+# You should have received a copy of the GNU General Public License along
402+# with this program. If not, see <http://www.gnu.org/licenses/>.
403+#
404+# In addition, as a special exception, the copyright holders give
405+# permission to link the code of portions of this program with the
406+# OpenSSL library under certain conditions as described in each
407+# individual source file, and distribute linked combinations
408+# including the two.
409+# You must obey the GNU General Public License in all respects
410+# for all of the code used other than OpenSSL. If you modify
411+# file(s) with this exception, you may extend this exception to your
412+# version of the file(s), but you are not obligated to do so. If you
413+# do not wish to do so, delete this exception statement from your
414+# version. If you delete this exception statement from all source
415+# files in the program, then also delete it here.
416+"""Tests for the windows squid bits."""
417+
418+# pylint: disable=F0401
419+import win32api
420+import win32con
421+# pylint: enable=F0401
422+
423+from twisted.trial.unittest import TestCase
424+
425+from ubuntuone.devtools.services import squid
426+
427+
428+class SquidWindowsTestCase(TestCase):
429+ """"Test the different windows bits."""
430+
431+ def test_get_auth_process(self):
432+ """Test getting the auth process for squid3."""
433+ called = []
434+
435+ self.patch(squid, 'find_executable', lambda _: None)
436+
437+ def fake_format(path):
438+ """Fake format of a config path."""
439+ called.append(('format', path))
440+ return path
441+
442+ self.patch(squid, 'format_config_path', fake_format)
443+ self.assertEqual(squid.AUTH_PROCESS_PATH,
444+ squid.get_auth_process_path(3))
445+ self.assertIn(('format', squid.AUTH_PROCESS_PATH), called)
446+
447+ def test_get_auth_process_path(self):
448+ """Test getting the auth process."""
449+ called = []
450+
451+ exec_path = '/path/to/exec'
452+ self.patch(squid, 'find_executable', lambda _: exec_path)
453+
454+ def fake_format(path):
455+ """Fake format of a config path."""
456+ called.append(('format', path))
457+ return path
458+
459+ self.patch(squid, 'format_config_path', fake_format)
460+ self.assertEqual(exec_path, squid.get_auth_process_path(3))
461+ self.assertIn(('format', exec_path), called)
462+
463+ def test_format_config_path(self):
464+ """Test formating a config path."""
465+ path = '\\a\\config\\path'
466+ expected = path.replace('\\', '\\\\')
467+ self.assertEqual(expected, squid.format_config_path(path))
468+
469+ def test_kill_squid(self):
470+ """Test killing squid."""
471+ called = []
472+
473+ def fake_open_process(access, inherit, pid):
474+ """A fake open process."""
475+ called.append(('OpenProcess', access, inherit, pid))
476+ return pid
477+
478+ self.patch(win32api, 'OpenProcess', fake_open_process)
479+
480+ def fake_terminate(handle, exit_code):
481+ """Fake terminate the process."""
482+ called.append(('TerminateProcess', handle, exit_code))
483+
484+ self.patch(win32api, 'TerminateProcess', fake_terminate)
485+
486+ def fake_close_handle(handle):
487+ """Fale closing a handle."""
488+ called.append(('CloseHandle', handle))
489+
490+ self.patch(win32api, 'CloseHandle', fake_close_handle)
491+ squid_pid = 4
492+ squid.kill_squid(squid_pid)
493+ self.assertIn(('OpenProcess', win32con.PROCESS_TERMINATE, 0,
494+ squid_pid), called)
495+ self.assertIn(('TerminateProcess', squid_pid, 0), called)
496+ self.assertIn(('CloseHandle', squid_pid), called)
497
498=== modified file 'ubuntuone/devtools/testcases/tests/test_squid_testcase.py'
499--- ubuntuone/devtools/testcases/tests/test_squid_testcase.py 2012-03-30 17:44:03 +0000
500+++ ubuntuone/devtools/testcases/tests/test_squid_testcase.py 2012-04-30 11:07:20 +0000
501@@ -192,7 +192,7 @@
502 """Build the url for this mock server."""
503 #pylint: disable=W0212
504 port_num = self.tcpserver._port.getHost().port
505- return "http://localhost:%d/" % port_num
506+ return "http://127.0.0.1:%d/" % port_num
507
508 @defer.inlineCallbacks
509 def stop(self):

Subscribers

People subscribed via source and target branches

to all changes: