2
# Copyright 2011 Canonical Ltd.
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU General Public License version 3, as published
6
# by the Free Software Foundation.
8
# This program is distributed in the hope that it will be useful, but
9
# WITHOUT ANY WARRANTY; without even the implied warranties of
10
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11
# PURPOSE. See the GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License along
14
# with this program. If not, see <http://www.gnu.org/licenses/>.
15
"""Utilities for finding and running a squid proxy for testing."""
19
# pylint:disable=W0402
25
from json import dumps, loads
26
from os import environ, makedirs, kill, unlink
27
from os.path import abspath, exists, join
29
from distutils.spawn import find_executable
31
from ubuntuone.devtools.services import (
36
SQUID_CONFIG_FILE = 'squid.conf.in'
38
AUTH_PROCESS_PATH = '/usr/lib/%s/ncsa_auth'
40
AUTH_FILE = 'htpasswd'
41
PROXY_ENV_VAR = 'SQUID_PROXY_SETTINGS'
44
def get_squid_executable():
45
"""Return the squid executable of the system."""
46
# try with squid and if not present try with squid3 for newer systems
47
# (Ubuntu P). We also return the path to the auth process so that we can
48
# point to the correct one.
49
squid = find_executable('squid3')
50
auth_process = AUTH_PROCESS_PATH % 'squid3'
52
squid = find_executable('squid')
53
auth_process = AUTH_PROCESS_PATH % 'squid'
54
return squid, auth_process
57
def get_htpasswd_executable():
58
"""Return the htpasswd executable."""
59
return find_executable('htpasswd')
62
def _make_random_string(count):
63
"""Make a random string of the given length."""
64
entropy = random.SystemRandom()
65
return ''.join([entropy.choice(string.letters) for _ in
69
def _get_basedir(tempdir):
70
"""Return the base squid config."""
71
basedir = join(tempdir, SQUID_DIR)
72
basedir = abspath(basedir)
73
if not exists(basedir):
78
def _get_spool_temp_path(tempdir=''):
79
"""Return the temp dir to be used for spool."""
80
basedir = _get_basedir(tempdir)
81
path = join(basedir, SPOOL_DIR)
88
def _get_squid_temp_path(tempdir=''):
89
"""Return the temp dir to be used by squid."""
90
basedir = _get_basedir(tempdir)
91
path = join(basedir, SQUID_DIR)
98
def _get_auth_temp_path(tempdir=''):
99
"""Return the path for the auth file."""
100
basedir = _get_basedir(tempdir)
101
auth_file = join(basedir, AUTH_FILE)
102
if not exists(basedir):
107
def store_proxy_settings(settings):
108
"""Store the proxy setting in an env var."""
109
environ[PROXY_ENV_VAR] = dumps(settings)
112
def retrieve_proxy_settings():
113
"""Return the proxy settings of the env."""
114
if PROXY_ENV_VAR in environ:
115
return loads(environ[PROXY_ENV_VAR])
119
def delete_proxy_settings():
120
"""Delete the proxy env settings."""
121
if PROXY_ENV_VAR in environ:
122
del environ[PROXY_ENV_VAR]
125
class SquidLaunchError(Exception):
126
"""Error while launching squid."""
129
class SquidRunner(object):
130
"""Class for running a squid proxy with the local config."""
133
"""Create a new instance."""
134
self.squid, self.auth_process = get_squid_executable()
135
if self.squid is None:
136
raise SquidLaunchError('Could not locate "squid".')
138
self.htpasswd = get_htpasswd_executable()
139
if self.htpasswd is None:
140
raise SquidLaunchError('Could not locate "htpasswd".')
142
self.settings = dict(noauth_port=None, auth_port=None,
143
username=None, password=None)
144
self.squid_pid = None
146
self.config_file = None
147
self.auth_file = None
149
def _generate_config_file(self, tempdir=''):
150
"""Find the first appropiate squid.conf to use."""
151
# load the config file
152
path = find_config_file(SQUID_CONFIG_FILE)
153
# replace config settings
154
basedir = join(tempdir, 'squid')
155
basedir = abspath(basedir)
156
if not exists(basedir):
158
self.config_file = join(basedir, 'squid.conf')
159
with open(path) as in_file:
160
template = string.Template(in_file.read())
162
self.settings['noauth_port'] = get_arbitrary_port()
163
self.settings['auth_port'] = get_arbitrary_port()
164
spool_path = _get_spool_temp_path(tempdir)
165
squid_path = _get_squid_temp_path(tempdir)
166
with open(self.config_file, 'w') as out_file:
167
out_file.write(template.safe_substitute(
168
auth_file=self.auth_file,
169
auth_process=self.auth_process,
170
noauth_port_number=self.settings['noauth_port'],
171
auth_port_number=self.settings['auth_port'],
172
spool_temp=spool_path,
173
squid_temp=squid_path))
175
def _generate_swap(self, config_file):
176
"""Generate the squid swap files."""
177
squid_args = ['-z', '-f', config_file]
178
sp = subprocess.Popen([self.squid] + squid_args,
179
stdout=subprocess.PIPE,
180
stderr=subprocess.PIPE)
183
def _generate_auth_file(self, tempdir=''):
184
"""Generates a auth file using htpasswd."""
185
if self.settings['username'] is None:
186
self.settings['username'] = _make_random_string(10)
187
if self.settings['password'] is None:
188
self.settings['password'] = _make_random_string(10)
190
self.auth_file = _get_auth_temp_path(tempdir)
191
# remove possible old auth file
192
if exists(self.auth_file):
193
unlink(self.auth_file)
194
# create a new htpasswrd
195
htpasswd_args = ['-bc',
197
self.settings['username'],
198
self.settings['password']]
199
sp = subprocess.Popen([self.htpasswd] + htpasswd_args,
200
stdout=subprocess.PIPE,
201
stderr=subprocess.PIPE)
204
def _is_squid_running(self):
205
"""Return if squid is running."""
206
squid_args = ['-k', 'check', '-f', self.config_file]
207
print 'Starting squid version...'
208
message = 'Waiting for squid to start...'
209
for timeout in (0.4, 0.1, 0.1, 0.2, 0.5, 1, 3, 5):
211
# Do not use stdout=PIPE or stderr=PIPE with this function.
212
subprocess.check_call([self.squid] + squid_args,
213
stdout=subprocess.PIPE,
214
stderr=subprocess.PIPE)
216
except subprocess.CalledProcessError:
222
def start_service(self, tempdir=None):
223
"""Start our own proxy."""
224
# generate auth, config and swap dirs
225
self._generate_auth_file(tempdir)
226
self._generate_config_file(tempdir)
227
self._generate_swap(self.config_file)
228
squid_args = ['-N', '-X', '-f', self.config_file]
229
sp = subprocess.Popen([self.squid] + squid_args,
230
stdout=subprocess.PIPE,
231
stderr=subprocess.PIPE)
232
store_proxy_settings(self.settings)
233
if not self._is_squid_running():
234
raise SquidLaunchError('Could not start squid.')
235
self.squid_pid = sp.pid
238
def stop_service(self):
239
"""Stop our proxy,"""
240
kill(self.squid_pid, signal.SIGKILL)
241
delete_proxy_settings()
243
unlink(self.config_file)
244
unlink(self.auth_file)
245
self.config_file = None