SimpleClient http proxying

Previously, this code was attempting to set up http proxying but it
wasn't working. We noticed after a while when we saw traffic going
through an alternate route instead of our set of http proxies with
container sync.

Additional work and testing by clayg; thanks!

Change-Id: I840b8e55a80c13ae85c65bf68de261d735685b27
This commit is contained in:
gholt 2014-06-25 18:19:25 +00:00
parent 177e9a36fc
commit 7fcbbebbeb
2 changed files with 84 additions and 12 deletions

View File

@ -728,14 +728,14 @@ class SimpleClient(object):
max_backoff=5, retries=5): max_backoff=5, retries=5):
self.url = url self.url = url
self.token = token self.token = token
self.attempts = 0
self.starting_backoff = starting_backoff self.starting_backoff = starting_backoff
self.max_backoff = max_backoff self.max_backoff = max_backoff
self.retries = retries self.retries = retries
def base_request(self, method, container=None, name=None, prefix=None, def base_request(self, method, container=None, name=None, prefix=None,
headers=None, proxy=None, contents=None, headers=None, proxy=None, contents=None,
full_listing=None, logger=None, additional_info=None): full_listing=None, logger=None, additional_info=None,
timeout=None):
# Common request method # Common request method
trans_start = time() trans_start = time()
url = self.url url = self.url
@ -756,15 +756,12 @@ class SimpleClient(object):
if prefix: if prefix:
url += '&prefix=%s' % prefix url += '&prefix=%s' % prefix
req = urllib2.Request(url, headers=headers, data=contents)
if proxy: if proxy:
proxy = urlparse.urlparse(proxy) proxy = urlparse.urlparse(proxy)
proxy = urllib2.ProxyHandler({proxy.scheme: proxy.netloc}) req.set_proxy(proxy.netloc, proxy.scheme)
opener = urllib2.build_opener(proxy)
urllib2.install_opener(opener)
req = urllib2.Request(url, headers=headers, data=contents)
req.get_method = lambda: method req.get_method = lambda: method
conn = urllib2.urlopen(req) conn = urllib2.urlopen(req, timeout=timeout)
body = conn.read() body = conn.read()
try: try:
body_data = json.loads(body) body_data = json.loads(body)
@ -798,14 +795,15 @@ class SimpleClient(object):
return [None, body_data] return [None, body_data]
def retry_request(self, method, **kwargs): def retry_request(self, method, **kwargs):
self.attempts = 0 retries = kwargs.pop('retries', self.retries)
attempts = 0
backoff = self.starting_backoff backoff = self.starting_backoff
while self.attempts <= self.retries: while attempts <= retries:
self.attempts += 1 attempts += 1
try: try:
return self.base_request(method, **kwargs) return self.base_request(method, **kwargs)
except (socket.error, httplib.HTTPException, urllib2.URLError): except (socket.error, httplib.HTTPException, urllib2.URLError):
if self.attempts > self.retries: if attempts > retries:
raise raise
sleep(backoff) sleep(backoff)
backoff = min(backoff * 2, self.max_backoff) backoff = min(backoff * 2, self.max_backoff)

View File

@ -23,6 +23,7 @@ from textwrap import dedent
import os import os
from test.unit import FakeLogger from test.unit import FakeLogger
import eventlet
from eventlet.green import urllib2 from eventlet.green import urllib2
from swift.common import internal_client from swift.common import internal_client
from swift.common import swob from swift.common import swob
@ -1210,6 +1211,79 @@ class TestSimpleClient(unittest.TestCase):
headers={'X-Auth-Token': 'token'}) headers={'X-Auth-Token': 'token'})
self.assertEqual([None, None], retval) self.assertEqual([None, None], retval)
@mock.patch('eventlet.green.urllib2.urlopen')
def test_get_with_retries_param(self, mock_urlopen):
mock_response = mock.MagicMock()
mock_response.read.return_value = ''
mock_urlopen.side_effect = internal_client.httplib.BadStatusLine('')
c = internal_client.SimpleClient(url='http://127.0.0.1', token='token')
self.assertEqual(c.retries, 5)
# first without retries param
with mock.patch('swift.common.internal_client.sleep') as mock_sleep:
self.assertRaises(internal_client.httplib.BadStatusLine,
c.retry_request, 'GET')
self.assertEqual(mock_sleep.call_count, 5)
self.assertEqual(mock_urlopen.call_count, 6)
# then with retries param
mock_urlopen.reset_mock()
with mock.patch('swift.common.internal_client.sleep') as mock_sleep:
self.assertRaises(internal_client.httplib.BadStatusLine,
c.retry_request, 'GET', retries=2)
self.assertEqual(mock_sleep.call_count, 2)
self.assertEqual(mock_urlopen.call_count, 3)
# and this time with a real response
mock_urlopen.reset_mock()
mock_urlopen.side_effect = [internal_client.httplib.BadStatusLine(''),
mock_response]
with mock.patch('swift.common.internal_client.sleep') as mock_sleep:
retval = c.retry_request('GET', retries=1)
self.assertEqual(mock_sleep.call_count, 1)
self.assertEqual(mock_urlopen.call_count, 2)
self.assertEqual([None, None], retval)
def test_proxy(self):
running = True
def handle(sock):
while running:
try:
with eventlet.Timeout(0.1):
(conn, addr) = sock.accept()
except eventlet.Timeout:
continue
else:
conn.send('HTTP/1.1 503 Server Error')
conn.close()
sock.close()
sock = eventlet.listen(('', 0))
port = sock.getsockname()[1]
proxy = 'http://127.0.0.1:%s' % port
url = 'https://127.0.0.1:1/a'
server = eventlet.spawn(handle, sock)
try:
headers = {'Content-Length': '0'}
with mock.patch('swift.common.internal_client.sleep'):
try:
internal_client.put_object(
url, container='c', name='o1', headers=headers,
contents='', proxy=proxy, timeout=0.1, retries=0)
except urllib2.HTTPError as e:
self.assertEqual(e.code, 503)
except urllib2.URLError as e:
if 'ECONNREFUSED' in str(e):
self.fail(
"Got %s which probably means the http proxy "
"settings were not used" % e)
else:
raise e
else:
self.fail('Unexpected successful response')
finally:
running = False
server.wait()
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()