diff options
Diffstat (limited to 'test/unit/test_swiftclient.py')
-rw-r--r-- | test/unit/test_swiftclient.py | 3328 |
1 files changed, 3328 insertions, 0 deletions
diff --git a/test/unit/test_swiftclient.py b/test/unit/test_swiftclient.py new file mode 100644 index 0000000..2d45deb --- /dev/null +++ b/test/unit/test_swiftclient.py @@ -0,0 +1,3328 @@ +# Copyright (c) 2010-2012 OpenStack, LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import gzip +import json +import logging +import mock +import six +import socket +import string +import unittest +import warnings +import tempfile +from hashlib import md5 +from six import binary_type +from six.moves.urllib.parse import urlparse +from requests.exceptions import RequestException + +from .utils import (MockHttpTest, fake_get_auth_keystone, StubResponse, + FakeKeystone) + +from swiftclient.utils import EMPTY_ETAG +from swiftclient.exceptions import ClientException +from swiftclient import client as c +import swiftclient.utils +import swiftclient + + +class TestClientException(unittest.TestCase): + + def test_is_exception(self): + self.assertTrue(issubclass(c.ClientException, Exception)) + + def test_format(self): + exc = c.ClientException('something failed') + self.assertIn('something failed', str(exc)) + test_kwargs = ( + 'scheme', + 'host', + 'port', + 'path', + 'query', + 'status', + 'reason', + 'device', + 'response_content', + ) + for value in test_kwargs: + kwargs = { + 'http_%s' % value: value, + } + exc = c.ClientException('test', **kwargs) + self.assertIn(value, str(exc)) + + def test_attrs(self): + test_kwargs = ( + 'scheme', + 'host', + 'port', + 'path', + 'query', + 'status', + 'reason', + 'device', + 'response_content', + 'response_headers', + ) + for value in test_kwargs: + key = 'http_%s' % value + kwargs = {key: value} + exc = c.ClientException('test', **kwargs) + self.assertIs(True, hasattr(exc, key)) + self.assertEqual(getattr(exc, key), value) + + +class MockHttpResponse(object): + def __init__(self, status=0, headers=None, verify=False): + self.status = status + self.status_code = status + self.reason = "OK" + self.buffer = [] + self.requests_params = None + self.verify = verify + self.md5sum = md5() + self.headers = {'etag': '"%s"' % EMPTY_ETAG} + if headers: + self.headers.update(headers) + self.closed = False + + class Raw(object): + def __init__(self, headers): + self.headers = headers + + def read(self, **kw): + return "" + + def getheader(self, name, default): + return self.headers.get(name, default) + + self.raw = Raw(headers) + + def read(self): + return "" + + def close(self): + self.closed = True + + def getheader(self, name, default): + return self.headers.get(name, default) + + def getheaders(self): + return dict(self.headers).items() + + def fake_response(self): + return self + + def _fake_request(self, *arg, **kwarg): + self.status = 200 + self.requests_params = kwarg + if self.verify: + for chunk in kwarg['data']: + self.md5sum.update(chunk) + + # This simulate previous httplib implementation that would do a + # putrequest() and then use putheader() to send header. + for k, v in kwarg['headers'].items(): + self.buffer.append((k, v)) + return self.fake_response() + + +class TestHttpHelpers(MockHttpTest): + + def test_quote(self): + value = b'bytes\xff' + self.assertEqual('bytes%FF', c.quote(value)) + value = 'native string' + self.assertEqual('native%20string', c.quote(value)) + value = u'unicode string' + self.assertEqual('unicode%20string', c.quote(value)) + value = u'unicode:\xe9\u20ac' + self.assertEqual('unicode%3A%C3%A9%E2%82%AC', c.quote(value)) + + def test_parse_header_string(self): + value = b'bytes' + self.assertEqual(u'bytes', c.parse_header_string(value)) + value = u'unicode:\xe9\u20ac' + self.assertEqual(u'unicode:\xe9\u20ac', c.parse_header_string(value)) + value = 'native%20string' + self.assertEqual(u'native string', c.parse_header_string(value)) + + value = b'encoded%20bytes%E2%82%AC' + self.assertEqual(u'encoded bytes\u20ac', c.parse_header_string(value)) + value = 'encoded%20unicode%E2%82%AC' + self.assertEqual(u'encoded unicode\u20ac', + c.parse_header_string(value)) + + value = b'bad%20bytes%ff%E2%82%AC' + self.assertEqual(u'bad%20bytes%ff%E2%82%AC', + c.parse_header_string(value)) + value = u'bad%20unicode%ff\u20ac' + self.assertEqual(u'bad%20unicode%ff\u20ac', + c.parse_header_string(value)) + + value = b'really%20bad\xffbytes' + self.assertEqual(u'really%2520bad%FFbytes', + c.parse_header_string(value)) + + def test_http_connection(self): + url = 'http://www.test.com' + _junk, conn = c.http_connection(url) + self.assertIs(type(conn), c.HTTPConnection) + url = 'https://www.test.com' + _junk, conn = c.http_connection(url) + self.assertIs(type(conn), c.HTTPConnection) + url = 'ftp://www.test.com' + self.assertRaises(c.ClientException, c.http_connection, url) + + def test_encode_meta_headers(self): + headers = {'abc': '123', + u'x-container-meta-\u0394': 123, + u'x-account-meta-\u0394': 12.3, + u'x-object-meta-\u0394': True} + + r = swiftclient.encode_meta_headers(headers) + + self.assertEqual(len(headers), len(r)) + # ensure non meta headers are not encoded + self.assertIs(type(r.get('abc')), binary_type) + del r['abc'] + + for k, v in r.items(): + self.assertIs(type(k), binary_type) + self.assertIs(type(v), binary_type) + self.assertIn(v, (b'123', b'12.3', b'True')) + + def test_set_user_agent_default(self): + _junk, conn = c.http_connection('http://www.example.com') + req_headers = {} + + def my_request_handler(*a, **kw): + req_headers.update(kw.get('headers', {})) + conn._request = my_request_handler + + # test the default + conn.request('GET', '/') + ua = req_headers.get('user-agent', 'XXX-MISSING-XXX') + self.assertTrue(ua.startswith('python-swiftclient-')) + + def test_set_user_agent_per_request_override(self): + _junk, conn = c.http_connection('http://www.example.com') + req_headers = {} + + def my_request_handler(*a, **kw): + req_headers.update(kw.get('headers', {})) + conn._request = my_request_handler + + # test if it's actually set + conn.request('GET', '/', headers={'User-Agent': 'Me'}) + ua = req_headers.get('user-agent', 'XXX-MISSING-XXX') + self.assertEqual(ua, b'Me', req_headers) + + def test_set_user_agent_default_override(self): + _junk, conn = c.http_connection( + 'http://www.example.com', + default_user_agent='a-new-default') + req_headers = {} + + def my_request_handler(*a, **kw): + req_headers.update(kw.get('headers', {})) + conn._request = my_request_handler + + # test setting a default + conn._request = my_request_handler + conn.request('GET', '/') + ua = req_headers.get('user-agent', 'XXX-MISSING-XXX') + self.assertEqual(ua, 'a-new-default') + + +class TestGetAuth(MockHttpTest): + + def test_ok(self): + c.http_connection = self.fake_http_connection(200) + url, token = c.get_auth('http://www.test.com', 'asdf', 'asdf') + self.assertIsNone(url) + self.assertIsNone(token) + + def test_invalid_auth(self): + self.assertRaises(c.ClientException, c.get_auth, + 'http://www.tests.com', 'asdf', 'asdf', + auth_version="foo") + + def test_auth_v1(self): + c.http_connection = self.fake_http_connection(200, auth_v1=True) + url, token = c.get_auth('http://www.test.com', 'asdf', 'asdf', + auth_version="1.0") + self.assertEqual(url, 'storageURL') + self.assertEqual(token, 'someauthtoken') + + def test_auth_v1_insecure(self): + c.http_connection = self.fake_http_connection(200, 200, auth_v1=True) + url, token = c.get_auth('http://www.test.com/invalid_cert', + 'asdf', 'asdf', + auth_version='1.0', + insecure=True) + self.assertEqual(url, 'storageURL') + self.assertEqual(token, 'someauthtoken') + + with self.assertRaises(c.ClientException) as exc_context: + c.get_auth('http://www.test.com/invalid_cert', + 'asdf', 'asdf', auth_version='1.0') + # TODO: this test is really on validating the mock and not the + # the full plumbing into the requests's 'verify' option + self.assertIn('invalid_certificate', str(exc_context.exception)) + + def test_auth_v1_timeout(self): + # this test has some overlap with + # TestConnection.test_timeout_passed_down but is required to check that + # get_auth does the right thing when it is not passed a timeout arg + orig_http_connection = c.http_connection + timeouts = [] + + def fake_request_handler(*a, **kw): + if 'timeout' in kw: + timeouts.append(kw['timeout']) + else: + timeouts.append(None) + return MockHttpResponse( + status=200, + headers={ + 'x-auth-token': 'a_token', + 'x-storage-url': 'http://files.example.com/v1/AUTH_user'}) + + def fake_connection(*a, **kw): + url, conn = orig_http_connection(*a, **kw) + conn._request = fake_request_handler + return url, conn + + with mock.patch('swiftclient.client.http_connection', fake_connection): + c.get_auth('http://www.test.com', 'asdf', 'asdf', + auth_version="1.0", timeout=42.0) + c.get_auth('http://www.test.com', 'asdf', 'asdf', + auth_version="1.0", timeout=None) + c.get_auth('http://www.test.com', 'asdf', 'asdf', + auth_version="1.0") + + self.assertEqual(timeouts, [42.0, None, None]) + + def test_auth_v2_timeout(self): + # this test has some overlap with + # TestConnection.test_timeout_passed_down but is required to check that + # get_auth does the right thing when it is not passed a timeout arg + fake_ks = FakeKeystone(endpoint='http://some_url', token='secret') + with mock.patch('swiftclient.client.ksclient_v2', fake_ks): + c.get_auth('http://www.test.com', 'asdf', 'asdf', + os_options=dict(tenant_name='tenant'), + auth_version="2.0", timeout=42.0) + c.get_auth('http://www.test.com', 'asdf', 'asdf', + os_options=dict(tenant_name='tenant'), + auth_version="2.0", timeout=None) + c.get_auth('http://www.test.com', 'asdf', 'asdf', + os_options=dict(tenant_name='tenant'), + auth_version="2.0") + self.assertEqual(3, len(fake_ks.calls)) + timeouts = [call['timeout'] for call in fake_ks.calls] + self.assertEqual([42.0, None, None], timeouts) + + def test_auth_v2_with_tenant_name(self): + os_options = {'tenant_name': 'asdf'} + req_args = {'auth_version': '2.0'} + ks = fake_get_auth_keystone(os_options, required_kwargs=req_args) + with mock.patch('swiftclient.client.get_auth_keystone', ks): + url, token = c.get_auth('http://www.test.com', 'asdf', 'asdf', + os_options=os_options, + auth_version="2.0") + self.assertTrue(url.startswith("http")) + self.assertTrue(token) + + def test_auth_v2_with_tenant_id(self): + os_options = {'tenant_id': 'asdf'} + req_args = {'auth_version': '2.0'} + ks = fake_get_auth_keystone(os_options, required_kwargs=req_args) + with mock.patch('swiftclient.client.get_auth_keystone', ks): + url, token = c.get_auth('http://www.test.com', 'asdf', 'asdf', + os_options=os_options, + auth_version="2.0") + self.assertTrue(url.startswith("http")) + self.assertTrue(token) + + def test_auth_v2_with_project_name(self): + os_options = {'project_name': 'asdf'} + req_args = {'auth_version': '2.0'} + ks = fake_get_auth_keystone(os_options, required_kwargs=req_args) + with mock.patch('swiftclient.client.get_auth_keystone', ks): + url, token = c.get_auth('http://www.test.com', 'asdf', 'asdf', + os_options=os_options, + auth_version="2.0") + self.assertTrue(url.startswith("http")) + self.assertTrue(token) + + def test_auth_v2_with_project_id(self): + os_options = {'project_id': 'asdf'} + req_args = {'auth_version': '2.0'} + + ks = fake_get_auth_keystone(os_options, required_kwargs=req_args) + with mock.patch('swiftclient.client.get_auth_keystone', ks): + url, token = c.get_auth('http://www.test.com', 'asdf', 'asdf', + os_options=os_options, + auth_version="2.0") + self.assertTrue(url.startswith("http")) + self.assertTrue(token) + + def test_auth_v2_no_tenant_name_or_tenant_id(self): + with mock.patch('swiftclient.client.get_auth_keystone', + fake_get_auth_keystone({})): + self.assertRaises(c.ClientException, c.get_auth, + 'http://www.tests.com', 'asdf', 'asdf', + os_options={}, + auth_version='2.0') + + def test_auth_v2_with_tenant_name_none_and_tenant_id_none(self): + os_options = {'tenant_name': None, + 'tenant_id': None} + with mock.patch('swiftclient.client.get_auth_keystone', + fake_get_auth_keystone(os_options)): + self.assertRaises(c.ClientException, c.get_auth, + 'http://www.tests.com', 'asdf', 'asdf', + os_options=os_options, + auth_version='2.0') + + def test_auth_v2_with_tenant_user_in_user(self): + tenant_option = {'tenant_name': 'foo'} + with mock.patch('swiftclient.client.get_auth_keystone', + fake_get_auth_keystone(tenant_option)): + url, token = c.get_auth('http://www.test.com', 'foo:bar', 'asdf', + os_options={}, + auth_version="2.0") + self.assertTrue(url.startswith("http")) + self.assertTrue(token) + + def test_auth_v2_tenant_name_no_os_options(self): + tenant_option = {'tenant_name': 'asdf'} + with mock.patch('swiftclient.client.get_auth_keystone', + fake_get_auth_keystone(tenant_option)): + url, token = c.get_auth('http://www.test.com', 'asdf', 'asdf', + tenant_name='asdf', + os_options={}, + auth_version="2.0") + self.assertTrue(url.startswith("http")) + self.assertTrue(token) + + def test_auth_v2_with_os_options(self): + os_options = {'service_type': 'object-store', + 'endpoint_type': 'internalURL', + 'tenant_name': 'asdf'} + with mock.patch('swiftclient.client.get_auth_keystone', + fake_get_auth_keystone(os_options)): + url, token = c.get_auth('http://www.test.com', 'asdf', 'asdf', + os_options=os_options, + auth_version="2.0") + self.assertTrue(url.startswith("http")) + self.assertTrue(token) + + def test_auth_v2_with_tenant_user_in_user_no_os_options(self): + tenant_option = {'tenant_name': 'foo'} + with mock.patch('swiftclient.client.get_auth_keystone', + fake_get_auth_keystone(tenant_option)): + url, token = c.get_auth('http://www.test.com', 'foo:bar', 'asdf', + auth_version="2.0") + self.assertTrue(url.startswith("http")) + self.assertTrue(token) + + def test_auth_v2_with_os_region_name(self): + os_options = {'region_name': 'good-region', + 'tenant_name': 'asdf'} + with mock.patch('swiftclient.client.get_auth_keystone', + fake_get_auth_keystone(os_options)): + url, token = c.get_auth('http://www.test.com', 'asdf', 'asdf', + os_options=os_options, + auth_version="2.0") + self.assertTrue(url.startswith("http")) + self.assertTrue(token) + + def test_auth_v2_no_endpoint(self): + os_options = {'region_name': 'unknown_region', + 'tenant_name': 'asdf'} + with mock.patch('swiftclient.client.get_auth_keystone', + fake_get_auth_keystone(os_options, c.ClientException)): + self.assertRaises(c.ClientException, c.get_auth, + 'http://www.tests.com', 'asdf', 'asdf', + os_options=os_options, auth_version='2.0') + + def test_auth_v2_ks_exception(self): + with mock.patch('swiftclient.client.get_auth_keystone', + fake_get_auth_keystone({}, c.ClientException)): + self.assertRaises(c.ClientException, c.get_auth, + 'http://www.tests.com', 'asdf', 'asdf', + os_options={}, + auth_version='2.0') + + def test_auth_v2_cacert(self): + os_options = {'tenant_name': 'foo'} + auth_url_secure = 'https://www.tests.com' + auth_url_insecure = 'https://www.tests.com/self-signed-certificate' + + with mock.patch('swiftclient.client.get_auth_keystone', + fake_get_auth_keystone(os_options, None)): + url, token = c.get_auth(auth_url_secure, 'asdf', 'asdf', + os_options=os_options, auth_version='2.0', + insecure=False) + self.assertTrue(url.startswith("http")) + self.assertTrue(token) + + url, token = c.get_auth(auth_url_insecure, 'asdf', 'asdf', + os_options=os_options, auth_version='2.0', + cacert='ca.pem', insecure=False) + self.assertTrue(url.startswith("http")) + self.assertTrue(token) + + self.assertRaises(c.ClientException, c.get_auth, + auth_url_insecure, 'asdf', 'asdf', + os_options=os_options, auth_version='2.0') + self.assertRaises(c.ClientException, c.get_auth, + auth_url_insecure, 'asdf', 'asdf', + os_options=os_options, auth_version='2.0', + insecure=False) + + def test_auth_v2_insecure(self): + os_options = {'tenant_name': 'foo'} + auth_url_secure = 'https://www.tests.com' + auth_url_insecure = 'https://www.tests.com/invalid-certificate' + + with mock.patch('swiftclient.client.get_auth_keystone', + fake_get_auth_keystone(os_options, None)): + url, token = c.get_auth(auth_url_secure, 'asdf', 'asdf', + os_options=os_options, auth_version='2.0') + self.assertTrue(url.startswith("http")) + self.assertTrue(token) + + url, token = c.get_auth(auth_url_insecure, 'asdf', 'asdf', + os_options=os_options, auth_version='2.0', + insecure=True) + self.assertTrue(url.startswith("http")) + self.assertTrue(token) + + self.assertRaises(c.ClientException, c.get_auth, + auth_url_insecure, 'asdf', 'asdf', + os_options=os_options, auth_version='2.0') + self.assertRaises(c.ClientException, c.get_auth, + auth_url_insecure, 'asdf', 'asdf', + os_options=os_options, auth_version='2.0', + insecure=False) + + def test_auth_v2_cert(self): + os_options = {'tenant_name': 'foo'} + auth_url_no_sslauth = 'https://www.tests.com' + auth_url_sslauth = 'https://www.tests.com/client-certificate' + + with mock.patch('swiftclient.client.get_auth_keystone', + fake_get_auth_keystone(os_options, None)): + url, token = c.get_auth(auth_url_no_sslauth, 'asdf', 'asdf', + os_options=os_options, auth_version='2.0') + self.assertTrue(url.startswith("http")) + self.assertTrue(token) + + url, token = c.get_auth(auth_url_sslauth, 'asdf', 'asdf', + os_options=os_options, auth_version='2.0', + cert='minnie', cert_key='mickey') + self.assertTrue(url.startswith("http")) + self.assertTrue(token) + + self.assertRaises(c.ClientException, c.get_auth, + auth_url_sslauth, 'asdf', 'asdf', + os_options=os_options, auth_version='2.0') + self.assertRaises(c.ClientException, c.get_auth, + auth_url_sslauth, 'asdf', 'asdf', + os_options=os_options, auth_version='2.0', + cert='minnie') + + def test_auth_v3_with_tenant_name(self): + # check the correct auth version is passed to get_auth_keystone + os_options = {'tenant_name': 'asdf'} + req_args = {'auth_version': '3'} + + ks = fake_get_auth_keystone(os_options, required_kwargs=req_args) + with mock.patch('swiftclient.client.get_auth_keystone', ks): + url, token = c.get_auth('http://www.test.com', 'asdf', 'asdf', + os_options=os_options, + auth_version="3") + + self.assertTrue(url.startswith("http")) + self.assertTrue(token) + + def test_get_keystone_client_2_0(self): + # check the correct auth version is passed to get_auth_keystone + os_options = {'tenant_name': 'asdf'} + req_args = {'auth_version': '2.0'} + + ks = fake_get_auth_keystone(os_options, required_kwargs=req_args) + with mock.patch('swiftclient.client.get_auth_keystone', ks): + url, token = c.get_keystoneclient_2_0('http://www.test.com', + 'asdf', 'asdf', + os_options=os_options) + + self.assertTrue(url.startswith("http")) + self.assertTrue(token) + + def test_get_auth_keystone_versionless(self): + fake_ks = FakeKeystone(endpoint='http://some_url', token='secret') + + with mock.patch('swiftclient.client.ksclient_v3', fake_ks): + c.get_auth_keystone('http://authurl', 'user', 'key', {}) + self.assertEqual(1, len(fake_ks.calls)) + self.assertEqual('http://authurl/v3', fake_ks.calls[0].get('auth_url')) + + def test_get_auth_keystone_versionless_auth_version_set(self): + fake_ks = FakeKeystone(endpoint='http://some_url', token='secret') + + with mock.patch('swiftclient.client.ksclient_v2', fake_ks): + c.get_auth_keystone('http://auth_url', 'user', 'key', + {}, auth_version='2.0') + self.assertEqual(1, len(fake_ks.calls)) + self.assertEqual('http://auth_url/v2.0', + fake_ks.calls[0].get('auth_url')) + + def test_get_auth_keystone_versionful(self): + fake_ks = FakeKeystone(endpoint='http://some_url', token='secret') + + with mock.patch('swiftclient.client.ksclient_v3', fake_ks): + c.get_auth_keystone('http://auth_url/v3', 'user', 'key', + {}, auth_version='3') + self.assertEqual(1, len(fake_ks.calls)) + self.assertEqual('http://auth_url/v3', + fake_ks.calls[0].get('auth_url')) + + def test_get_auth_keystone_devstack_versionful(self): + fake_ks = FakeKeystone( + endpoint='http://storage.example.com/v1/AUTH_user', token='secret') + with mock.patch('swiftclient.client.ksclient_v3', fake_ks): + c.get_auth_keystone('https://192.168.8.8/identity/v3', + 'user', 'key', {}, auth_version='3') + self.assertEqual(1, len(fake_ks.calls)) + self.assertEqual('https://192.168.8.8/identity/v3', + fake_ks.calls[0].get('auth_url')) + + def test_get_auth_keystone_devstack_versionless(self): + fake_ks = FakeKeystone( + endpoint='http://storage.example.com/v1/AUTH_user', token='secret') + with mock.patch('swiftclient.client.ksclient_v3', fake_ks): + c.get_auth_keystone('https://192.168.8.8/identity', + 'user', 'key', {}, auth_version='3') + self.assertEqual(1, len(fake_ks.calls)) + self.assertEqual('https://192.168.8.8/identity/v3', + fake_ks.calls[0].get('auth_url')) + + def test_auth_keystone_url_some_junk_nonsense(self): + fake_ks = FakeKeystone( + endpoint='http://storage.example.com/v1/AUTH_user', + token='secret') + with mock.patch('swiftclient.client.ksclient_v3', fake_ks): + c.get_auth_keystone('http://blah.example.com/v2moo', + 'user', 'key', {}, auth_version='3') + self.assertEqual(1, len(fake_ks.calls)) + # v2 looks sorta version-y, but it's not an exact match, so this is + # probably about just as bad as anything else we might guess at + self.assertEqual('http://blah.example.com/v2moo/v3', + fake_ks.calls[0].get('auth_url')) + + def test_auth_with_session(self): + mock_session = mock.MagicMock() + mock_session.get_endpoint.return_value = 'http://storagehost/v1/acct' + mock_session.get_token.return_value = 'token' + url, token = c.get_auth('http://www.test.com', 'asdf', 'asdf', + session=mock_session) + self.assertEqual(url, 'http://storagehost/v1/acct') + self.assertTrue(token) + + +class TestGetAccount(MockHttpTest): + + def test_no_content(self): + c.http_connection = self.fake_http_connection(204) + value = c.get_account('http://www.test.com/v1/acct', 'asdf')[1] + self.assertEqual(value, []) + self.assertRequests([ + ('GET', '/v1/acct?format=json', '', { + 'accept-encoding': 'gzip', + 'x-auth-token': 'asdf'}), + ]) + + def test_param_marker(self): + c.http_connection = self.fake_http_connection( + 204, + query_string="format=json&marker=marker") + c.get_account('http://www.test.com/v1/acct', 'asdf', marker='marker') + self.assertRequests([ + ('GET', '/v1/acct?format=json&marker=marker', '', { + 'accept-encoding': 'gzip', + 'x-auth-token': 'asdf'}), + ]) + + def test_param_limit(self): + c.http_connection = self.fake_http_connection( + 204, + query_string="format=json&limit=10") + c.get_account('http://www.test.com/v1/acct', 'asdf', limit=10) + self.assertRequests([ + ('GET', '/v1/acct?format=json&limit=10', '', { + 'accept-encoding': 'gzip', + 'x-auth-token': 'asdf'}), + ]) + + def test_param_prefix(self): + c.http_connection = self.fake_http_connection( + 204, + query_string="format=json&prefix=asdf/") + c.get_account('http://www.test.com/v1/acct', 'asdf', prefix='asdf/') + self.assertRequests([ + ('GET', '/v1/acct?format=json&prefix=asdf/', '', { + 'accept-encoding': 'gzip', + 'x-auth-token': 'asdf'}), + ]) + + def test_param_end_marker(self): + c.http_connection = self.fake_http_connection( + 204, + query_string="format=json&end_marker=end_marker") + c.get_account('http://www.test.com/v1/acct', 'asdf', + end_marker='end_marker') + self.assertRequests([ + ('GET', '/v1/acct?format=json&end_marker=end_marker', '', { + 'accept-encoding': 'gzip', + 'x-auth-token': 'asdf'}), + ]) + + def test_param_delimiter(self): + c.http_connection = self.fake_http_connection( + 204, + query_string="format=json&delimiter=-") + c.get_account('http://www.test.com/v1/acct', 'asdf', + delimiter='-') + self.assertRequests([ + ('GET', '/v1/acct?format=json&delimiter=-', '', { + 'accept-encoding': 'gzip', + 'x-auth-token': 'asdf'}), + ]) + + +class TestHeadAccount(MockHttpTest): + + def test_ok(self): + c.http_connection = self.fake_http_connection(200, headers={ + 'x-account-meta-color': 'blue', + }) + resp_headers = c.head_account('http://www.tests.com', 'asdf') + self.assertEqual(resp_headers['x-account-meta-color'], 'blue') + self.assertRequests([ + ('HEAD', 'http://www.tests.com', '', {'x-auth-token': 'asdf'}) + ]) + + def test_server_error(self): + body = 'c' * 65 + headers = {'foo': 'bar'} + c.http_connection = self.fake_http_connection( + StubResponse(500, body, headers)) + with self.assertRaises(c.ClientException) as exc_context: + c.head_account('http://www.tests.com', 'asdf') + e = exc_context.exception + self.assertEqual(e.http_response_content, body) + self.assertEqual(e.http_status, 500) + self.assertRequests([ + ('HEAD', 'http://www.tests.com', '', {'x-auth-token': 'asdf'}) + ]) + # TODO: this is a fairly brittle test of the __repr__ on the + # ClientException which should probably be in a targeted test + new_body = "[first 60 chars of response] " + body[0:60] + self.assertEqual(e.__str__()[-89:], new_body) + + +class TestPostAccount(MockHttpTest): + + def test_ok(self): + c.http_connection = self.fake_http_connection(200, headers={ + 'X-Account-Meta-Color': 'blue', + }, body='foo') + headers = {'x-account-meta-shape': 'square'} + resp_headers, body = c.post_account( + 'http://www.tests.com/path/to/account', 'asdf', + headers, query_string='bar=baz', + data='some data') + self.assertEqual('blue', resp_headers.get('x-account-meta-color')) + self.assertEqual('foo', body) + self.assertRequests([ + ('POST', 'http://www.tests.com/path/to/account?bar=baz', + 'some data', {'x-auth-token': 'asdf', + 'x-account-meta-shape': 'square'}) + ]) + # Check that we didn't mutate the request ehader dict + self.assertEqual(headers, {'x-account-meta-shape': 'square'}) + + def test_server_error(self): + body = 'c' * 65 + c.http_connection = self.fake_http_connection(500, body=body) + with self.assertRaises(c.ClientException) as exc_mgr: + c.post_account('http://www.tests.com', 'asdf', {}) + self.assertEqual(exc_mgr.exception.http_response_content, body) + self.assertEqual(exc_mgr.exception.http_status, 500) + self.assertRequests([ + ('POST', 'http://www.tests.com', None, {'x-auth-token': 'asdf'}) + ]) + # TODO: this is a fairly brittle test of the __repr__ on the + # ClientException which should probably be in a targeted test + new_body = "[first 60 chars of response] " + body[0:60] + self.assertEqual(exc_mgr.exception.__str__()[-89:], new_body) + + +class TestGetContainer(MockHttpTest): + + def test_no_content(self): + c.http_connection = self.fake_http_connection(204) + value = c.get_container('http://www.test.com/v1/acct', 'token', + 'container')[1] + self.assertEqual(value, []) + self.assertRequests([ + ('GET', '/v1/acct/container?format=json', '', { + 'accept-encoding': 'gzip', + 'x-auth-token': 'token'}), + ]) + + def test_param_marker(self): + c.http_connection = self.fake_http_connection( + 204, + query_string="format=json&marker=marker") + c.get_container('http://www.test.com/v1/acct', 'token', 'container', + marker='marker') + self.assertRequests([ + ('GET', '/v1/acct/container?format=json&marker=marker', '', { + 'accept-encoding': 'gzip', + 'x-auth-token': 'token'}), + ]) + + def test_param_limit(self): + c.http_connection = self.fake_http_connection( + 204, + query_string="format=json&limit=10") + c.get_container('http://www.test.com/v1/acct', 'token', 'container', + limit=10) + self.assertRequests([ + ('GET', '/v1/acct/container?format=json&limit=10', '', { + 'accept-encoding': 'gzip', + 'x-auth-token': 'token'}), + ]) + + def test_param_prefix(self): + c.http_connection = self.fake_http_connection( + 204, + query_string="format=json&prefix=asdf/") + c.get_container('http://www.test.com/v1/acct', 'token', 'container', + prefix='asdf/') + self.assertRequests([ + ('GET', '/v1/acct/container?format=json&prefix=asdf/', '', { + 'accept-encoding': 'gzip', + 'x-auth-token': 'token'}), + ]) + + def test_param_delimiter(self): + c.http_connection = self.fake_http_connection( + 204, + query_string="format=json&delimiter=/") + c.get_container('http://www.test.com/v1/acct', 'token', 'container', + delimiter='/') + self.assertRequests([ + ('GET', '/v1/acct/container?format=json&delimiter=/', '', { + 'accept-encoding': 'gzip', + 'x-auth-token': 'token'}), + ]) + + def test_param_end_marker(self): + c.http_connection = self.fake_http_connection( + 204, + query_string="format=json&end_marker=end_marker") + c.get_container('http://www.test.com/v1/acct', 'token', 'container', + end_marker='end_marker') + self.assertRequests([ + ('GET', '/v1/acct/container?format=json&end_marker=end_marker', + '', {'x-auth-token': 'token', 'accept-encoding': 'gzip'}), + ]) + + def test_param_path(self): + c.http_connection = self.fake_http_connection( + 204, + query_string="format=json&path=asdf") + c.get_container('http://www.test.com/v1/acct', 'token', 'container', + path='asdf') + self.assertRequests([ + ('GET', '/v1/acct/container?format=json&path=asdf', '', { + 'accept-encoding': 'gzip', + 'x-auth-token': 'token'}), + ]) + + def test_request_headers(self): + c.http_connection = self.fake_http_connection( + 204, query_string="format=json") + conn = c.http_connection('http://www.test.com') + headers = {'x-client-key': 'client key'} + c.get_container('url_is_irrelevant', 'TOKEN', 'container', + http_conn=conn, headers=headers) + self.assertRequests([ + ('GET', '/container?format=json', '', { + 'x-auth-token': 'TOKEN', + 'x-client-key': 'client key', + 'accept-encoding': 'gzip', + }), + ]) + + def test_query_string(self): + c.http_connection = self.fake_http_connection( + 200, query_string="format=json&hello=20", body=b'[]') + c.get_container('http://www.test.com', 'asdf', 'asdf', + query_string="hello=20") + self.assertRequests([ + ('GET', '/asdf?format=json&hello=20', '', { + 'accept-encoding': 'gzip', + 'x-auth-token': 'asdf'}), + ]) + + +class TestHeadContainer(MockHttpTest): + + def test_head_ok(self): + fake_conn = self.fake_http_connection( + 200, headers={'x-container-meta-color': 'blue'}) + with mock.patch('swiftclient.client.http_connection', + new=fake_conn): + resp = c.head_container('https://example.com/v1/AUTH_test', + 'token', 'container') + self.assertEqual(resp['x-container-meta-color'], 'blue') + self.assertRequests([ + ('HEAD', 'https://example.com/v1/AUTH_test/container', '', + {'x-auth-token': 'token'}), + ]) + + def test_server_error(self): + body = 'c' * 60 + headers = {'foo': 'bar'} + c.http_connection = self.fake_http_connection( + StubResponse(500, body, headers)) + with self.assertRaises(c.ClientException) as exc_context: + c.head_container('http://www.test.com', 'asdf', 'container') + e = exc_context.exception + self.assertRequests([ + ('HEAD', '/container', '', {'x-auth-token': 'asdf'}), + ]) + self.assertEqual(e.http_status, 500) + self.assertEqual(e.http_response_content, body) + self.assertEqual(e.http_response_headers, headers) + + +class TestPutContainer(MockHttpTest): + + def test_ok(self): + c.http_connection = self.fake_http_connection(200) + value = c.put_container('http://www.test.com', 'token', 'container') + self.assertIsNone(value) + self.assertRequests([ + ('PUT', '/container', '', { + 'x-auth-token': 'token', + 'content-length': '0'}), + ]) + + def test_server_error(self): + body = 'c' * 60 + headers = {'foo': 'bar'} + c.http_connection = self.fake_http_connection( + StubResponse(500, body, headers)) + with self.assertRaises(c.ClientException) as exc_context: + c.put_container('http://www.test.com', 'token', 'container') + self.assertEqual(exc_context.exception.http_response_content, body) + self.assertEqual(exc_context.exception.http_response_headers, headers) + self.assertRequests([ + ('PUT', '/container', '', { + 'x-auth-token': 'token', + 'content-length': '0'}), + ]) + + def test_query_string(self): + c.http_connection = self.fake_http_connection(200, + query_string="hello=20") + c.put_container('http://www.test.com', 'asdf', 'asdf', + query_string="hello=20") + for req in self.iter_request_log(): + self.assertEqual(req['method'], 'PUT') + self.assertEqual(req['parsed_path'].path, '/asdf') + self.assertEqual(req['parsed_path'].query, 'hello=20') + self.assertEqual(req['headers']['x-auth-token'], 'asdf') + + +class TestDeleteContainer(MockHttpTest): + + def test_ok(self): + c.http_connection = self.fake_http_connection(200) + value = c.delete_container('http://www.test.com', 'token', 'container') + self.assertIsNone(value) + self.assertRequests([ + ('DELETE', '/container', '', { + 'x-auth-token': 'token'}), + ]) + + def test_query_string(self): + c.http_connection = self.fake_http_connection(200, + query_string="hello=20") + c.delete_container('http://www.test.com', 'token', 'container', + query_string="hello=20") + self.assertRequests([ + ('DELETE', 'http://www.test.com/container?hello=20', '', { + 'x-auth-token': 'token'}) + ]) + + +class TestGetObject(MockHttpTest): + + def test_server_error(self): + body = 'c' * 60 + headers = {'foo': 'bar'} + c.http_connection = self.fake_http_connection( + StubResponse(500, body, headers)) + with self.assertRaises(c.ClientException) as exc_context: + c.get_object('http://www.test.com', 'asdf', 'asdf', 'asdf') + self.assertEqual(exc_context.exception.http_response_content, body) + self.assertEqual(exc_context.exception.http_response_headers, headers) + + def test_query_string(self): + c.http_connection = self.fake_http_connection(200, + query_string="hello=20") + c.get_object('http://www.test.com', 'asdf', 'asdf', 'asdf', + query_string="hello=20") + self.assertRequests([ + ('GET', '/asdf/asdf?hello=20', '', { + 'x-auth-token': 'asdf'}), + ]) + + def test_get_object_as_string(self): + c.http_connection = self.fake_http_connection(200, body='abcde') + __, resp = c.get_object('http://storage.example.com', 'TOKEN', + 'container_name', 'object_name') + self.assertEqual(resp, 'abcde') + + def test_request_headers(self): + c.http_connection = self.fake_http_connection(200) + conn = c.http_connection('http://www.test.com') + headers = {'Range': 'bytes=1-2'} + c.get_object('url_is_irrelevant', 'TOKEN', 'container', 'object', + http_conn=conn, headers=headers) + self.assertRequests([ + ('GET', '/container/object', '', { + 'x-auth-token': 'TOKEN', + 'range': 'bytes=1-2', + }), + ]) + + def test_response_headers(self): + c.http_connection = self.fake_http_connection( + 200, headers={'X-Utf-8-Header': b't%c3%a9st', + 'X-Non-Utf-8-Header': b'%ff', + 'X-Binary-Header': b'\xff'}) + conn = c.http_connection('http://www.test.com') + headers, data = c.get_object('url_is_irrelevant', 'TOKEN', + 'container', 'object', http_conn=conn) + self.assertEqual(u't\xe9st', headers.get('x-utf-8-header', '')) + self.assertEqual(u'%ff', headers.get('x-non-utf-8-header', '')) + self.assertEqual(u'%FF', headers.get('x-binary-header', '')) + + def test_chunk_size_read_method(self): + conn = c.Connection('http://auth.url/', 'some_user', 'some_key') + with mock.patch('swiftclient.client.get_auth_1_0') as mock_get_auth: + mock_get_auth.return_value = ('http://auth.url/', 'tToken') + c.http_connection = self.fake_http_connection(200, body='abcde') + __, resp = conn.get_object('asdf', 'asdf', resp_chunk_size=3) + self.assertTrue(hasattr(resp, 'read')) + self.assertEqual(resp.read(3), 'abc') + self.assertEqual(resp.read(None), 'de') + self.assertEqual(resp.read(), '') + + def test_chunk_size_iter(self): + conn = c.Connection('http://auth.url/', 'some_user', 'some_key') + with mock.patch('swiftclient.client.get_auth_1_0') as mock_get_auth: + mock_get_auth.return_value = ('http://auth.url/', 'tToken') + c.http_connection = self.fake_http_connection(200, body='abcde') + __, resp = conn.get_object('asdf', 'asdf', resp_chunk_size=3) + self.assertTrue(hasattr(resp, 'next')) + self.assertEqual(next(resp), 'abc') + self.assertEqual(next(resp), 'de') + self.assertRaises(StopIteration, next, resp) + + def test_chunk_size_read_and_iter(self): + conn = c.Connection('http://auth.url/', 'some_user', 'some_key') + with mock.patch('swiftclient.client.get_auth_1_0') as mock_get_auth: + mock_get_auth.return_value = ('http://auth.url/', 'tToken') + c.http_connection = self.fake_http_connection(200, body='abcdef') + __, resp = conn.get_object('asdf', 'asdf', resp_chunk_size=2) + self.assertTrue(hasattr(resp, 'read')) + self.assertEqual(resp.read(3), 'abc') + self.assertEqual(next(resp), 'de') + self.assertEqual(resp.read(), 'f') + self.assertRaises(StopIteration, next, resp) + self.assertEqual(resp.read(), '') + + def test_chunk_size_iter_chunked_no_retry(self): + conn = c.Connection('http://auth.url/', 'some_user', 'some_key') + with mock.patch('swiftclient.client.get_auth_1_0') as mock_get_auth: + mock_get_auth.return_value = ('http://auth.url/', 'tToken') + c.http_connection = self.fake_http_connection( + 200, body='abcdef', headers={'Transfer-Encoding': 'chunked'}) + __, resp = conn.get_object('asdf', 'asdf', resp_chunk_size=2) + self.assertEqual(next(resp), 'ab') + # simulate a dropped connection + resp.resp.read() + self.assertRaises(StopIteration, next, resp) + + def test_chunk_size_iter_retry(self): + conn = c.Connection('http://auth.url/', 'some_user', 'some_key') + with mock.patch('swiftclient.client.get_auth_1_0') as mock_get_auth: + mock_get_auth.return_value = ('http://auth.url', 'tToken') + c.http_connection = self.fake_http_connection( + StubResponse(200, 'abcdef', {'etag': 'some etag', + 'content-length': '6'}), + StubResponse(206, 'cdef', {'etag': 'some etag', + 'content-length': '4', + 'content-range': 'bytes 2-5/6'}), + StubResponse(206, 'ef', {'etag': 'some etag', + 'content-length': '2', + 'content-range': 'bytes 4-5/6'}), + ) + __, resp = conn.get_object('asdf', 'asdf', resp_chunk_size=2) + self.assertEqual(next(resp), 'ab') + self.assertEqual(1, conn.attempts) + # simulate a dropped connection + resp.resp.read() + self.assertEqual(next(resp), 'cd') + self.assertEqual(2, conn.attempts) + # simulate a dropped connection + resp.resp.read() + self.assertEqual(next(resp), 'ef') + self.assertEqual(3, conn.attempts) + self.assertRaises(StopIteration, next, resp) + self.assertRequests([ + ('GET', '/asdf/asdf', '', { + 'x-auth-token': 'tToken', + }), + ('GET', '/asdf/asdf', '', { + 'range': 'bytes=2-', + 'if-match': 'some etag', + 'x-auth-token': 'tToken', + }), + ('GET', '/asdf/asdf', '', { + 'range': 'bytes=4-', + 'if-match': 'some etag', + 'x-auth-token': 'tToken', + }), + ]) + + def test_chunk_size_iter_retry_no_range_support(self): + conn = c.Connection('http://auth.url/', 'some_user', 'some_key') + with mock.patch('swiftclient.client.get_auth_1_0') as mock_get_auth: + mock_get_auth.return_value = ('http://auth.url', 'tToken') + c.http_connection = self.fake_http_connection(*[ + StubResponse(200, 'abcdef', {'etag': 'some etag', + 'content-length': '6'}) + ] * 3) + __, resp = conn.get_object('asdf', 'asdf', resp_chunk_size=2) + self.assertEqual(next(resp), 'ab') + self.assertEqual(1, conn.attempts) + # simulate a dropped connection + resp.resp.read() + self.assertEqual(next(resp), 'cd') + self.assertEqual(2, conn.attempts) + # simulate a dropped connection + resp.resp.read() + self.assertEqual(next(resp), 'ef') + self.assertEqual(3, conn.attempts) + self.assertRaises(StopIteration, next, resp) + self.assertRequests([ + ('GET', '/asdf/asdf', '', { + 'x-auth-token': 'tToken', + }), + ('GET', '/asdf/asdf', '', { + 'range': 'bytes=2-', + 'if-match': 'some etag', + 'x-auth-token': 'tToken', + }), + ('GET', '/asdf/asdf', '', { + 'range': 'bytes=4-', + 'if-match': 'some etag', + 'x-auth-token': 'tToken', + }), + ]) + + def test_chunk_size_iter_retry_bad_range_response(self): + conn = c.Connection('http://auth.url/', 'some_user', 'some_key') + with mock.patch('swiftclient.client.get_auth_1_0') as mock_get_auth: + mock_get_auth.return_value = ('http://auth.url', 'tToken') + c.http_connection = self.fake_http_connection( + StubResponse(200, 'abcdef', {'etag': 'some etag', + 'content-length': '6'}), + StubResponse(206, 'abcdef', {'etag': 'some etag', + 'content-length': '6', + 'content-range': 'chunk 1-2/3'}) + ) + __, resp = conn.get_object('asdf', 'asdf', resp_chunk_size=2) + self.assertEqual(next(resp), 'ab') + self.assertEqual(1, conn.attempts) + # simulate a dropped connection + resp.resp.read() + self.assertRaises(c.ClientException, next, resp) + self.assertRequests([ + ('GET', '/asdf/asdf', '', { + 'x-auth-token': 'tToken', + }), + ('GET', '/asdf/asdf', '', { + 'range': 'bytes=2-', + 'if-match': 'some etag', + 'x-auth-token': 'tToken', + }), + ]) + + def test_get_object_with_resp_chunk_size_zero(self): + def get_connection(self): + def get_auth(): + return 'http://auth.test.com', 'token' + + conn = c.Connection('http://www.test.com', 'asdf', 'asdf') + self.assertIs(type(conn), c.Connection) + conn.get_auth = get_auth + self.assertEqual(conn.attempts, 0) + return conn + + with mock.patch('swiftclient.client.http_connection', + self.fake_http_connection(200)): + conn = get_connection(self) + conn.get_object('container1', 'obj1', resp_chunk_size=0) + self.assertEqual(conn.attempts, 1) + + +class TestHeadObject(MockHttpTest): + + def test_server_error(self): + body = 'c' * 60 + headers = {'foo': 'bar'} + c.http_connection = self.fake_http_connection( + StubResponse(500, body, headers)) + with self.assertRaises(c.ClientException) as exc_context: + c.head_object('http://www.test.com', 'asdf', 'asdf', 'asdf') + self.assertEqual(exc_context.exception.http_response_content, body) + self.assertEqual(exc_context.exception.http_response_headers, headers) + + def test_request_headers(self): + c.http_connection = self.fake_http_connection(204) + conn = c.http_connection('http://www.test.com') + headers = {'x-client-key': 'client key'} + c.head_object('url_is_irrelevant', 'TOKEN', 'container', + 'asdf', http_conn=conn, headers=headers) + self.assertRequests([ + ('HEAD', '/container/asdf', '', { + 'x-auth-token': 'TOKEN', + 'x-client-key': 'client key', + }), + ]) + + def test_query_string(self): + c.http_connection = self.fake_http_connection(204) + conn = c.http_connection('http://www.test.com') + query_string = 'foo=bar' + c.head_object('url_is_irrelevant', 'token', 'container', 'key', + http_conn=conn, query_string=query_string) + self.assertRequests([ + ('HEAD', '/container/key?foo=bar', '', {'x-auth-token': 'token'}) + ]) + + +class TestPutObject(MockHttpTest): + + @mock.patch('swiftclient.requests.__version__', '2.2.0') + def test_ok(self): + c.http_connection = self.fake_http_connection(200) + args = ('http://www.test.com', 'TOKEN', 'container', 'obj', 'body', 4) + value = c.put_object(*args) + self.assertIsInstance(value, six.string_types) + self.assertEqual(value, EMPTY_ETAG) + self.assertRequests([ + ('PUT', '/container/obj', 'body', { + 'x-auth-token': 'TOKEN', + 'content-length': '4', + 'content-type': '' + }), + ]) + + def test_unicode_ok(self): + conn = c.http_connection(u'http://www.test.com/') + mock_file = six.StringIO(u'\u5929\u7a7a\u4e2d\u7684\u4e4c\u4e91') + args = (u'\u5929\u7a7a\u4e2d\u7684\u4e4c\u4e91', + u'\u5929\u7a7a\u4e2d\u7684\u4e4c\u4e91', + u'\u5929\u7a7a\u4e2d\u7684\u4e4c\u4e91', + u'\u5929\u7a7a\u4e2d\u7684\u4e4c\u4e91', + mock_file) + text = u'\u5929\u7a7a\u4e2d\u7684\u4e4c\u4e91' + headers = {'X-Header1': text, + 'X-2': '1', 'X-3': "{'a': 'b'}", 'a-b': '.x:yz mn:fg:lp'} + + resp = MockHttpResponse() + conn[1].getresponse = resp.fake_response + conn[1]._request = resp._fake_request + value = c.put_object(*args, headers=headers, http_conn=conn) + self.assertIsInstance(value, six.string_types) + # Test for RFC-2616 encoded symbols + self.assertIn(("a-b", b".x:yz mn:fg:lp"), + resp.buffer) + # Test unicode header + self.assertIn(('x-header1', text.encode('utf8')), + resp.buffer) + + def test_chunk_warning(self): + conn = c.http_connection('http://www.test.com/') + mock_file = six.StringIO('asdf') + args = ('asdf', 'asdf', 'asdf', 'asdf', mock_file) + resp = MockHttpResponse() + conn[1].getresponse = resp.fake_response + conn[1]._request = resp._fake_request + with warnings.catch_warnings(record=True) as w: + c.put_object(*args, chunk_size=20, headers={}, http_conn=conn) + self.assertEqual(len(w), 0) + + body = 'c' * 60 + c.http_connection = self.fake_http_connection(200, body=body) + args = ('http://www.test.com', 'asdf', 'asdf', 'asdf', 'asdf') + with warnings.catch_warnings(record=True) as w: + c.put_object(*args, chunk_size=20) + self.assertEqual(len(w), 1) + self.assertTrue(issubclass(w[-1].category, UserWarning)) + + @mock.patch('swiftclient.requests.__version__', '2.2.0') + def test_server_error(self): + body = 'c' * 60 + headers = {'foo': 'bar'} + c.http_connection = self.fake_http_connection( + StubResponse(500, body, headers)) + args = ('http://www.test.com', 'asdf', 'asdf', 'asdf', 'asdf') + with self.assertRaises(c.ClientException) as exc_context: + c.put_object(*args) + e = exc_context.exception + self.assertEqual(e.http_response_content, body) + self.assertEqual(e.http_response_headers, headers) + self.assertEqual(e.http_status, 500) + self.assertRequests([ + ('PUT', '/asdf/asdf', 'asdf', { + 'x-auth-token': 'asdf', + 'content-type': ''}), + ]) + + def test_query_string(self): + c.http_connection = self.fake_http_connection(200, + query_string="hello=20") + c.put_object('http://www.test.com', 'asdf', 'asdf', 'asdf', + query_string="hello=20") + for req in self.iter_request_log(): + self.assertEqual(req['method'], 'PUT') + self.assertEqual(req['parsed_path'].path, '/asdf/asdf') + self.assertEqual(req['parsed_path'].query, 'hello=20') + self.assertEqual(req['headers']['x-auth-token'], 'asdf') + + def test_raw_upload(self): + # Raw upload happens when content_length is passed to put_object + conn = c.http_connection(u'http://www.test.com/') + resp = MockHttpResponse(status=200) + conn[1].getresponse = resp.fake_response + conn[1]._request = resp._fake_request + raw_data = b'asdf' * 256 + raw_data_len = len(raw_data) + + for kwarg in ({'headers': {'Content-Length': str(raw_data_len)}}, + {'content_length': raw_data_len}): + with tempfile.TemporaryFile() as mock_file: + mock_file.write(raw_data) + mock_file.seek(0) + + c.put_object(url='http://www.test.com', http_conn=conn, + contents=mock_file, **kwarg) + + req_data = resp.requests_params['data'] + self.assertIs(type(req_data), swiftclient.utils.LengthWrapper) + self.assertEqual(raw_data_len, len(req_data.read())) + + def test_chunk_upload(self): + # Chunked upload happens when no content_length is passed to put_object + conn = c.http_connection(u'http://www.test.com/') + resp = MockHttpResponse(status=200) + conn[1].getresponse = resp.fake_response + conn[1]._request = resp._fake_request + raw_data = b'asdf' * 256 + chunk_size = 16 + + with tempfile.TemporaryFile() as mock_file: + mock_file.write(raw_data) + mock_file.seek(0) + + c.put_object(url='http://www.test.com', http_conn=conn, + contents=mock_file, chunk_size=chunk_size) + req_data = resp.requests_params['data'] + self.assertTrue(hasattr(req_data, '__iter__')) + data = b'' + for chunk in req_data: + self.assertEqual(chunk_size, len(chunk)) + data += chunk + self.assertEqual(data, raw_data) + + def test_iter_upload(self): + def data(): + for chunk in ('foo', '', 'bar'): + yield chunk + conn = c.http_connection(u'http://www.test.com/') + resp = MockHttpResponse(status=200) + conn[1].getresponse = resp.fake_response + conn[1]._request = resp._fake_request + + c.put_object(url='http://www.test.com', http_conn=conn, + contents=data()) + req_headers = resp.requests_params['headers'] + self.assertNotIn('Content-Length', req_headers) + req_data = resp.requests_params['data'] + self.assertTrue(hasattr(req_data, '__iter__')) + # If we emit an empty chunk, requests will go ahead and send it, + # causing the server to close the connection. So make sure we don't + # do that. + self.assertEqual(['foo', 'bar'], list(req_data)) + + def test_md5_mismatch(self): + conn = c.http_connection('http://www.test.com') + resp = MockHttpResponse(status=200, verify=True, + headers={'etag': '"badresponseetag"'}) + conn[1].getresponse = resp.fake_response + conn[1]._request = resp._fake_request + raw_data = b'asdf' * 256 + raw_data_md5 = md5(raw_data).hexdigest() + chunk_size = 16 + + with tempfile.TemporaryFile() as mock_file: + mock_file.write(raw_data) + mock_file.seek(0) + + contents = swiftclient.utils.ReadableToIterable(mock_file, + md5=True) + + etag = c.put_object(url='http://www.test.com', + http_conn=conn, + contents=contents, + chunk_size=chunk_size) + + self.assertNotEqual(etag, contents.get_md5sum()) + self.assertEqual(etag, 'badresponseetag') + self.assertEqual(raw_data_md5, contents.get_md5sum()) + + def test_md5_match(self): + conn = c.http_connection('http://www.test.com') + raw_data = b'asdf' * 256 + raw_data_md5 = md5(raw_data).hexdigest() + resp = MockHttpResponse(status=200, verify=True, + headers={'etag': '"' + raw_data_md5 + '"'}) + conn[1].getresponse = resp.fake_response + conn[1]._request = resp._fake_request + chunk_size = 16 + + with tempfile.TemporaryFile() as mock_file: + mock_file.write(raw_data) + mock_file.seek(0) + contents = swiftclient.utils.ReadableToIterable(mock_file, + md5=True) + + etag = c.put_object(url='http://www.test.com', + http_conn=conn, + contents=contents, + chunk_size=chunk_size) + + self.assertEqual(raw_data_md5, contents.get_md5sum()) + self.assertEqual(etag, contents.get_md5sum()) + + def test_params(self): + conn = c.http_connection(u'http://www.test.com/') + resp = MockHttpResponse(status=200) + conn[1].getresponse = resp.fake_response + conn[1]._request = resp._fake_request + + c.put_object(url='http://www.test.com', http_conn=conn, + etag='1234-5678', content_type='text/plain') + request_header = resp.requests_params['headers'] + self.assertEqual(request_header['etag'], b'1234-5678') + self.assertEqual(request_header['content-type'], b'text/plain') + + @mock.patch('swiftclient.requests.__version__', '2.2.0') + def test_no_content_type_old_requests(self): + conn = c.http_connection(u'http://www.test.com/') + resp = MockHttpResponse(status=200) + conn[1].getresponse = resp.fake_response + conn[1]._request = resp._fake_request + + c.put_object(url='http://www.test.com', http_conn=conn) + request_header = resp.requests_params['headers'] + self.assertEqual(request_header['content-type'], b'') + + @mock.patch('swiftclient.requests.__version__', '2.4.0') + def test_no_content_type_new_requests(self): + conn = c.http_connection(u'http://www.test.com/') + resp = MockHttpResponse(status=200) + conn[1].getresponse = resp.fake_response + conn[1]._request = resp._fake_request + + c.put_object(url='http://www.test.com', http_conn=conn) + request_header = resp.requests_params['headers'] + self.assertNotIn('content-type', request_header) + + def test_content_type_in_headers(self): + conn = c.http_connection(u'http://www.test.com/') + resp = MockHttpResponse(status=200) + conn[1].getresponse = resp.fake_response + conn[1]._request = resp._fake_request + + # title-case header + hdrs = {'Content-Type': 'text/Plain'} + c.put_object(url='http://www.test.com', http_conn=conn, headers=hdrs) + request_header = resp.requests_params['headers'] + self.assertEqual(request_header['content-type'], b'text/Plain') + + # method param overrides headers + c.put_object(url='http://www.test.com', http_conn=conn, headers=hdrs, + content_type='image/jpeg') + request_header = resp.requests_params['headers'] + self.assertEqual(request_header['content-type'], b'image/jpeg') + + +class TestPostObject(MockHttpTest): + + def test_ok(self): + c.http_connection = self.fake_http_connection(200) + delete_at = 2.1 # not str! we don't know what other devs will use! + args = ('http://www.test.com', 'token', 'container', 'obj', + {'X-Object-Meta-Test': 'mymeta', + 'X-Delete-At': delete_at}) + c.post_object(*args) + self.assertRequests([ + ('POST', '/container/obj', '', { + 'x-auth-token': 'token', + 'X-Object-Meta-Test': 'mymeta', + 'X-Delete-At': delete_at}), + ]) + # Check that the request header dict didn't get mutated + self.assertEqual(args[-1], { + 'X-Object-Meta-Test': 'mymeta', + 'X-Delete-At': delete_at, + }) + + def test_unicode_ok(self): + conn = c.http_connection(u'http://www.test.com/') + args = (u'\u5929\u7a7a\u4e2d\u7684\u4e4c\u4e91', + u'\u5929\u7a7a\u4e2d\u7684\u4e4c\u4e91', + u'\u5929\u7a7a\u4e2d\u7684\u4e4c\u4e91', + u'\u5929\u7a7a\u4e2d\u7684\u4e4c\u4e91') + text = u'\u5929\u7a7a\u4e2d\u7684\u4e4c\u4e91' + headers = {'X-Header1': text, + b'X-Header2': 'value', + 'X-2': '1', 'X-3': "{'a': 'b'}", 'a-b': '.x:yz mn:kl:qr', + 'X-Object-Meta-Header-not-encoded': text, + b'X-Object-Meta-Header-encoded': 'value'} + + resp = MockHttpResponse() + conn[1].getresponse = resp.fake_response + conn[1]._request = resp._fake_request + c.post_object(*args, headers=headers, http_conn=conn) + # Test for RFC-2616 encoded symbols + self.assertIn(('a-b', b".x:yz mn:kl:qr"), resp.buffer) + # Test unicode header + self.assertIn(('x-header1', text.encode('utf8')), + resp.buffer) + self.assertIn((b'x-object-meta-header-not-encoded', + text.encode('utf8')), resp.buffer) + self.assertIn((b'x-object-meta-header-encoded', b'value'), + resp.buffer) + self.assertIn((b'x-header2', b'value'), resp.buffer) + + def test_server_error(self): + body = 'c' * 60 + headers = {'foo': 'bar'} + c.http_connection = self.fake_http_connection( + StubResponse(500, body, headers)) + args = ('http://www.test.com', 'token', 'container', 'obj', {}) + with self.assertRaises(c.ClientException) as exc_context: + c.post_object(*args) + self.assertEqual(exc_context.exception.http_response_content, body) + self.assertEqual(exc_context.exception.http_response_headers, headers) + self.assertRequests([ + ('POST', 'http://www.test.com/container/obj', '', { + 'x-auth-token': 'token', + }), + ]) + + +class TestCopyObject(MockHttpTest): + + def test_server_error(self): + c.http_connection = self.fake_http_connection(500) + self.assertRaises( + c.ClientException, c.copy_object, + 'http://www.test.com/v1/AUTH', 'asdf', 'asdf', 'asdf') + + def test_ok(self): + c.http_connection = self.fake_http_connection(200) + c.copy_object( + 'http://www.test.com/v1/AUTH', 'token', 'container', 'obj', + destination='/container2/obj') + self.assertRequests([ + ('COPY', 'http://www.test.com/v1/AUTH/container/obj', '', { + 'X-Auth-Token': 'token', + 'Destination': '/container2/obj', + }), + ]) + + def test_service_token(self): + c.http_connection = self.fake_http_connection(200) + c.copy_object('http://www.test.com/v1/AUTH', None, 'container', + 'obj', destination='/container2/obj', + service_token="TOKEN") + self.assertRequests([ + ('COPY', 'http://www.test.com/v1/AUTH/container/obj', '', { + 'X-Service-Token': 'TOKEN', + 'Destination': '/container2/obj', + + }), + ]) + + def test_headers(self): + c.http_connection = self.fake_http_connection(200) + c.copy_object( + 'http://www.test.com/v1/AUTH', 'token', 'container', 'obj', + destination='/container2/obj', + headers={'some-hdr': 'a', 'other-hdr': 'b'}) + self.assertRequests([ + ('COPY', 'http://www.test.com/v1/AUTH/container/obj', '', { + 'X-Auth-Token': 'token', + 'Destination': '/container2/obj', + 'some-hdr': 'a', + 'other-hdr': 'b', + }), + ]) + + def test_fresh_metadata_default(self): + c.http_connection = self.fake_http_connection(200) + c.copy_object( + 'http://www.test.com/v1/AUTH', 'token', 'container', 'obj', + '/container2/obj', {'x-fresh-metadata': 'hdr-value'}) + self.assertRequests([ + ('COPY', 'http://www.test.com/v1/AUTH/container/obj', '', { + 'X-Auth-Token': 'token', + 'Destination': '/container2/obj', + 'X-Fresh-Metadata': 'hdr-value', + }), + ]) + + def test_fresh_metadata_true(self): + c.http_connection = self.fake_http_connection(200) + c.copy_object( + 'http://www.test.com/v1/AUTH', 'token', 'container', 'obj', + destination='/container2/obj', + headers={'x-fresh-metadata': 'hdr-value'}, + fresh_metadata=True) + self.assertRequests([ + ('COPY', 'http://www.test.com/v1/AUTH/container/obj', '', { + 'X-Auth-Token': 'token', + 'Destination': '/container2/obj', + 'X-Fresh-Metadata': 'true', + }), + ]) + + def test_fresh_metadata_false(self): + c.http_connection = self.fake_http_connection(200) + c.copy_object( + 'http://www.test.com/v1/AUTH', 'token', 'container', 'obj', + destination='/container2/obj', + headers={'x-fresh-metadata': 'hdr-value'}, + fresh_metadata=False) + self.assertRequests([ + ('COPY', 'http://www.test.com/v1/AUTH/container/obj', '', { + 'x-auth-token': 'token', + 'Destination': '/container2/obj', + 'X-Fresh-Metadata': 'false', + }), + ]) + + def test_no_destination(self): + c.http_connection = self.fake_http_connection(200) + c.copy_object( + 'http://www.test.com/v1/AUTH', 'token', 'container', 'obj') + self.assertRequests([ + ('COPY', 'http://www.test.com/v1/AUTH/container/obj', '', { + 'x-auth-token': 'token', + 'Destination': '/container/obj', + }), + ]) + + +class TestDeleteObject(MockHttpTest): + + def test_ok(self): + c.http_connection = self.fake_http_connection(200) + c.delete_object('http://www.test.com', 'token', 'container', 'obj') + self.assertRequests([ + ('DELETE', 'http://www.test.com/container/obj', '', { + 'x-auth-token': 'token', + }), + ]) + + def test_server_error(self): + body = 'c' * 60 + headers = {'foo': 'bar'} + c.http_connection = self.fake_http_connection( + StubResponse(500, body, headers)) + with self.assertRaises(c.ClientException) as exc_context: + c.delete_object('http://www.test.com', 'asdf', 'asdf', 'asdf') + self.assertEqual(exc_context.exception.http_response_content, body) + self.assertEqual(exc_context.exception.http_response_headers, headers) + + def test_query_string(self): + c.http_connection = self.fake_http_connection(200, + query_string="hello=20") + c.delete_object('http://www.test.com', 'token', 'container', 'obj', + query_string="hello=20") + self.assertRequests([ + ('DELETE', 'http://www.test.com/container/obj?hello=20', '', { + 'x-auth-token': 'token', + }), + ]) + + +class TestGetCapabilities(MockHttpTest): + + def test_ok(self): + conn = self.fake_http_connection(200, body=b'{}') + http_conn = conn('http://www.test.com/info') + info = c.get_capabilities(http_conn) + self.assertRequests([ + ('GET', '/info', '', {'Accept-Encoding': 'gzip'}), + ]) + self.assertEqual(info, {}) + self.assertTrue(http_conn[1].resp.has_been_read) + + def test_server_error(self): + body = 'c' * 60 + headers = {'foo': 'bar'} + conn = self.fake_http_connection( + StubResponse(500, body, headers)) + http_conn = conn('http://www.test.com/info') + with self.assertRaises(c.ClientException) as exc_context: + c.get_capabilities(http_conn) + self.assertEqual(exc_context.exception.http_response_content, body) + self.assertEqual(exc_context.exception.http_response_headers, headers) + + def test_conn_get_capabilities_with_auth(self): + auth_headers = { + 'x-auth-token': 'token', + 'x-storage-url': 'http://storage.example.com/v1/AUTH_test' + } + auth_v1_response = StubResponse(headers=auth_headers) + stub_info = {'swift': {'fake': True}} + info_response = StubResponse(body=b'{"swift":{"fake":true}}') + fake_conn = self.fake_http_connection(auth_v1_response, info_response) + + conn = c.Connection('http://auth.example.com/auth/v1.0', + 'user', 'key') + with mock.patch('swiftclient.client.http_connection', + new=fake_conn): + info = conn.get_capabilities() + self.assertEqual(info, stub_info) + self.assertRequests([ + ('GET', '/auth/v1.0', '', { + 'x-auth-user': 'user', + 'x-auth-key': 'key'}), + ('GET', 'http://storage.example.com/info', '', { + 'accept-encoding': 'gzip'}), + ]) + + def test_conn_get_capabilities_with_os_auth(self): + fake_keystone = fake_get_auth_keystone( + storage_url='http://storage.example.com/v1/AUTH_test') + stub_info = {'swift': {'fake': True}} + info_response = StubResponse(body=b'{"swift":{"fake":true}}') + fake_conn = self.fake_http_connection(info_response) + + os_options = {'project_id': 'test'} + conn = c.Connection('http://keystone.example.com/v3.0', + 'user', 'key', os_options=os_options, + auth_version=3) + with mock.patch.multiple('swiftclient.client', + get_auth_keystone=fake_keystone, + http_connection=fake_conn): + info = conn.get_capabilities() + self.assertEqual(info, stub_info) + self.assertRequests([ + ('GET', 'http://storage.example.com/info'), + ]) + + def test_conn_get_capabilities_with_url_param(self): + stub_info = {'swift': {'fake': True}} + info_response = StubResponse(body=b'{"swift":{"fake":true}}') + fake_conn = self.fake_http_connection(info_response) + + conn = c.Connection('http://auth.example.com/auth/v1.0', + 'user', 'key') + with mock.patch('swiftclient.client.http_connection', + new=fake_conn): + info = conn.get_capabilities( + 'http://other-storage.example.com/info') + self.assertEqual(info, stub_info) + self.assertRequests([ + ('GET', 'http://other-storage.example.com/info'), + ]) + + def test_conn_get_capabilities_with_preauthurl_param(self): + stub_info = {'swift': {'fake': True}} + info_response = StubResponse(body=b'{"swift":{"fake":true}}') + fake_conn = self.fake_http_connection(info_response) + + storage_url = 'http://storage.example.com/v1/AUTH_test' + conn = c.Connection('http://auth.example.com/auth/v1.0', + 'user', 'key', preauthurl=storage_url) + with mock.patch('swiftclient.client.http_connection', + new=fake_conn): + info = conn.get_capabilities() + self.assertEqual(info, stub_info) + self.assertRequests([ + ('GET', 'http://storage.example.com/info'), + ]) + + def test_conn_get_capabilities_with_os_options(self): + stub_info = {'swift': {'fake': True}} + info_response = StubResponse(body=b'{"swift":{"fake":true}}') + fake_conn = self.fake_http_connection(info_response) + + storage_url = 'http://storage.example.com/v1/AUTH_test' + os_options = { + 'project_id': 'test', + 'object_storage_url': storage_url, + } + conn = c.Connection('http://keystone.example.com/v3.0', + 'user', 'key', os_options=os_options, + auth_version=3) + with mock.patch('swiftclient.client.http_connection', + new=fake_conn): + info = conn.get_capabilities() + self.assertEqual(info, stub_info) + self.assertRequests([ + ('GET', 'http://storage.example.com/info'), + ]) + + +class TestHTTPConnection(MockHttpTest): + + def test_bad_url_scheme(self): + url = u'www.test.com' + with self.assertRaises(c.ClientException) as exc_context: + c.http_connection(url) + exc = exc_context.exception + expected = u'Unsupported scheme "" in url "www.test.com"' + self.assertEqual(expected, str(exc)) + + url = u'://www.test.com' + with self.assertRaises(c.ClientException) as exc_context: + c.http_connection(url) + exc = exc_context.exception + expected = u'Unsupported scheme "" in url "://www.test.com"' + self.assertEqual(expected, str(exc)) + + url = u'blah://www.test.com' + with self.assertRaises(c.ClientException) as exc_context: + c.http_connection(url) + exc = exc_context.exception + expected = u'Unsupported scheme "blah" in url "blah://www.test.com"' + self.assertEqual(expected, str(exc)) + + def test_ok_url_scheme(self): + for scheme in ('http', 'https', 'HTTP', 'HTTPS'): + url = u'%s://www.test.com' % scheme + parsed_url, conn = c.http_connection(url) + self.assertEqual(scheme.lower(), parsed_url.scheme) + self.assertEqual(u'%s://www.test.com' % scheme, conn.url) + + def test_ok_proxy(self): + conn = c.http_connection(u'http://www.test.com/', + proxy='http://localhost:8080') + self.assertEqual(conn[1].requests_args['proxies']['http'], + 'http://localhost:8080') + + def test_bad_proxy(self): + try: + c.http_connection(u'http://www.test.com/', proxy='localhost:8080') + except c.ClientException as e: + self.assertEqual(e.msg, "Proxy's missing scheme") + + def test_cacert(self): + conn = c.http_connection(u'http://www.test.com/', + cacert='/dev/urandom') + self.assertEqual(conn[1].requests_args['verify'], '/dev/urandom') + + def test_insecure(self): + conn = c.http_connection(u'http://www.test.com/', insecure=True) + self.assertEqual(conn[1].requests_args['verify'], False) + + def test_cert(self): + conn = c.http_connection(u'http://www.test.com/', cert='minnie') + self.assertEqual(conn[1].requests_args['cert'], 'minnie') + + def test_cert_key(self): + conn = c.http_connection( + u'http://www.test.com/', cert='minnie', cert_key='mickey') + self.assertEqual(conn[1].requests_args['cert'], ('minnie', 'mickey')) + + def test_response_connection_released(self): + _parsed_url, conn = c.http_connection(u'http://www.test.com/') + conn.resp = MockHttpResponse() + conn.resp.raw = mock.Mock() + conn.resp.raw.read.side_effect = ["Chunk", ""] + resp = conn.getresponse() + self.assertFalse(resp.closed) + self.assertEqual("Chunk", resp.read()) + self.assertFalse(resp.read()) + self.assertTrue(resp.closed) + + @unittest.skipIf(six.PY3, 'python2 specific test') + def test_response_python2_headers(self): + '''Test utf-8 headers in Python 2. + ''' + _, conn = c.http_connection(u'http://www.test.com/') + conn.resp = MockHttpResponse( + status=200, + headers={ + '\xd8\xaa-unicode': '\xd8\xaa-value', + 'empty-header': '' + } + ) + + resp = conn.getresponse() + self.assertEqual( + '\xd8\xaa-value', resp.getheader('\xd8\xaa-unicode')) + self.assertEqual( + '\xd8\xaa-value', resp.getheader('\xd8\xaa-UNICODE')) + self.assertEqual('', resp.getheader('empty-header')) + self.assertEqual( + dict([('\xd8\xaa-unicode', '\xd8\xaa-value'), + ('empty-header', ''), + ('etag', '"%s"' % EMPTY_ETAG)]), + dict(resp.getheaders())) + + @unittest.skipIf(six.PY2, 'python3 specific test') + def test_response_python3_headers(self): + '''Test latin1-encoded headers in Python 3. + ''' + _, conn = c.http_connection(u'http://www.test.com/') + conn.resp = MockHttpResponse( + status=200, + headers={ + b'\xd8\xaa-unicode'.decode('iso-8859-1'): + b'\xd8\xaa-value'.decode('iso-8859-1'), + 'empty-header': '' + } + ) + + resp = conn.getresponse() + self.assertEqual( + '\u062a-value', resp.getheader('\u062a-unicode')) + self.assertEqual( + '\u062a-value', resp.getheader('\u062a-UNICODE')) + self.assertEqual('', resp.getheader('empty-header')) + self.assertEqual( + dict([('\u062a-unicode', '\u062a-value'), + ('empty-header', ''), + ('etag', ('"%s"' % EMPTY_ETAG))]), + dict(resp.getheaders())) + + +class TestConnection(MockHttpTest): + + def test_instance(self): + conn = c.Connection('http://www.test.com', 'asdf', 'asdf') + self.assertEqual(conn.retries, 5) + + def test_instance_kwargs(self): + args = {'user': 'ausername', + 'key': 'secretpass', + 'authurl': 'http://www.test.com', + 'tenant_name': 'atenant'} + conn = c.Connection(**args) + self.assertEqual(type(conn), c.Connection) + + def test_instance_kwargs_token(self): + args = {'preauthtoken': 'atoken123', + 'preauthurl': 'http://www.test.com:8080/v1/AUTH_123456'} + conn = c.Connection(**args) + self.assertEqual(conn.url, args['preauthurl']) + self.assertEqual(conn.token, args['preauthtoken']) + + def test_instance_kwargs_os_token(self): + storage_url = 'http://storage.example.com/v1/AUTH_test' + token = 'token' + args = { + 'os_options': { + 'object_storage_url': storage_url, + 'auth_token': token, + } + } + conn = c.Connection(**args) + self.assertEqual(conn.url, storage_url) + self.assertEqual(conn.token, token) + + def test_instance_kwargs_token_precedence(self): + storage_url = 'http://storage.example.com/v1/AUTH_test' + token = 'token' + args = { + 'preauthurl': storage_url, + 'preauthtoken': token, + 'os_options': { + 'auth_token': 'less-specific-token', + 'object_storage_url': 'less-specific-storage-url', + } + } + conn = c.Connection(**args) + self.assertEqual(conn.url, storage_url) + self.assertEqual(conn.token, token) + + def test_storage_url_override(self): + static_url = 'http://overridden.storage.url' + conn = c.Connection('http://auth.url/', 'some_user', 'some_key', + os_options={ + 'object_storage_url': static_url}) + method_signatures = ( + (conn.head_account, []), + (conn.get_account, []), + (conn.head_container, ('asdf',)), + (conn.get_container, ('asdf',)), + (conn.put_container, ('asdf',)), + (conn.delete_container, ('asdf',)), + (conn.head_object, ('asdf', 'asdf')), + (conn.get_object, ('asdf', 'asdf')), + (conn.put_object, ('asdf', 'asdf', 'asdf')), + (conn.post_object, ('asdf', 'asdf', {})), + (conn.delete_object, ('asdf', 'asdf')), + ) + + with mock.patch('swiftclient.client.get_auth_1_0') as mock_get_auth: + mock_get_auth.return_value = ('http://auth.storage.url', 'tToken') + + for method, args in method_signatures: + c.http_connection = self.fake_http_connection( + 200, body=b'[]', storage_url=static_url) + method(*args) + self.assertEqual(len(self.request_log), 1) + for request in self.iter_request_log(): + self.assertEqual(request['parsed_path'].netloc, + 'overridden.storage.url') + self.assertEqual(request['headers']['x-auth-token'], + 'tToken') + + def test_get_capabilities(self): + conn = c.Connection() + with mock.patch('swiftclient.client.get_capabilities') as get_cap: + conn.get_capabilities('http://storage2.test.com') + parsed = get_cap.call_args[0][0][0] + self.assertEqual(parsed.path, '/info') + self.assertEqual(parsed.netloc, 'storage2.test.com') + conn.get_auth = lambda: ('http://storage.test.com/v1/AUTH_test', + 'token') + conn.get_capabilities() + parsed = get_cap.call_args[0][0][0] + self.assertEqual(parsed.path, '/info') + self.assertEqual(parsed.netloc, 'storage.test.com') + + def test_retry(self): + def quick_sleep(*args): + pass + c.sleep = quick_sleep + conn = c.Connection('http://www.test.com', 'asdf', 'asdf') + code_iter = [500] * (conn.retries + 1) + c.http_connection = self.fake_http_connection(*code_iter) + + self.assertRaises(c.ClientException, conn.head_account) + self.assertEqual(conn.attempts, conn.retries + 1) + + def test_retry_on_ratelimit(self): + + def quick_sleep(*args): + pass + c.sleep = quick_sleep + + # test retries + conn = c.Connection('http://www.test.com/auth/v1.0', 'asdf', 'asdf', + retry_on_ratelimit=True) + code_iter = [200] + [498] * (conn.retries + 1) + auth_resp_headers = { + 'x-auth-token': 'asdf', + 'x-storage-url': 'http://storage/v1/test', + } + c.http_connection = self.fake_http_connection( + *code_iter, headers=auth_resp_headers) + with self.assertRaises(c.ClientException) as exc_context: + conn.head_account() + self.assertIn('Account HEAD failed', str(exc_context.exception)) + self.assertEqual(conn.attempts, conn.retries + 1) + + # test default no-retry + c.http_connection = self.fake_http_connection( + 200, 498, + headers=auth_resp_headers) + conn = c.Connection('http://www.test.com/auth/v1.0', 'asdf', 'asdf') + with self.assertRaises(c.ClientException) as exc_context: + conn.head_account() + self.assertIn('Account HEAD failed', str(exc_context.exception)) + self.assertEqual(conn.attempts, 1) + + def test_retry_with_socket_error(self): + def quick_sleep(*args): + pass + c.sleep = quick_sleep + conn = c.Connection('http://www.test.com', 'asdf', 'asdf') + with mock.patch('swiftclient.client.http_connection') as \ + fake_http_connection, \ + mock.patch('swiftclient.client.get_auth_1_0') as mock_auth: + mock_auth.return_value = ('http://mock.com', 'mock_token') + fake_http_connection.side_effect = socket.error + self.assertRaises(socket.error, conn.head_account) + self.assertEqual(mock_auth.call_count, 1) + self.assertEqual(conn.attempts, conn.retries + 1) + + def test_retry_with_force_auth_retry_exceptions(self): + def quick_sleep(*args): + pass + + def do_test(exception): + c.sleep = quick_sleep + conn = c.Connection( + 'http://www.test.com', 'asdf', 'asdf', + force_auth_retry=True) + with mock.patch('swiftclient.client.http_connection') as \ + fake_http_connection, \ + mock.patch('swiftclient.client.get_auth_1_0') as mock_auth: + mock_auth.return_value = ('http://mock.com', 'mock_token') + fake_http_connection.side_effect = exception + self.assertRaises(exception, conn.head_account) + self.assertEqual(mock_auth.call_count, conn.retries + 1) + self.assertEqual(conn.attempts, conn.retries + 1) + + do_test(socket.error) + do_test(RequestException) + + def test_retry_with_force_auth_retry_client_exceptions(self): + def quick_sleep(*args): + pass + + def do_test(http_status, count): + + def mock_http_connection(*args, **kwargs): + raise ClientException('fake', http_status=http_status) + + c.sleep = quick_sleep + conn = c.Connection( + 'http://www.test.com', 'asdf', 'asdf', + force_auth_retry=True) + with mock.patch('swiftclient.client.http_connection') as \ + fake_http_connection, \ + mock.patch('swiftclient.client.get_auth_1_0') as mock_auth: + mock_auth.return_value = ('http://mock.com', 'mock_token') + fake_http_connection.side_effect = mock_http_connection + self.assertRaises(ClientException, conn.head_account) + self.assertEqual(mock_auth.call_count, count) + self.assertEqual(conn.attempts, count) + + # sanity, in case of 401, the auth will be called only twice because of + # retried_auth mechanism + do_test(401, 2) + # others will be tried until retry limits + do_test(408, 6) + do_test(500, 6) + do_test(503, 6) + + def test_resp_read_on_server_error(self): + conn = c.Connection('http://www.test.com', 'asdf', 'asdf', retries=0) + + def get_auth(*args, **kwargs): + return 'http://www.new.com', 'new' + conn.get_auth = get_auth + self.url, self.token = conn.get_auth() + + method_signatures = ( + (conn.head_account, []), + (conn.get_account, []), + (conn.head_container, ('asdf',)), + (conn.get_container, ('asdf',)), + (conn.put_container, ('asdf',)), + (conn.delete_container, ('asdf',)), + (conn.head_object, ('asdf', 'asdf')), + (conn.get_object, ('asdf', 'asdf')), + (conn.put_object, ('asdf', 'asdf', 'asdf')), + (conn.post_object, ('asdf', 'asdf', {})), + (conn.delete_object, ('asdf', 'asdf')), + ) + + for method, args in method_signatures: + c.http_connection = self.fake_http_connection(500) + self.assertRaises(c.ClientException, method, *args) + requests = list(self.iter_request_log()) + self.assertEqual(len(requests), 1) + for req in requests: + msg = '%s did not read resp on server error' % method.__name__ + self.assertTrue(req['resp'].has_been_read, msg) + + def test_reauth(self): + c.http_connection = self.fake_http_connection(401, 200) + + def get_auth(*args, **kwargs): + # this mock, and by extension this test are not + # representative of the unit under test. The real get_auth + # method will always return the os_option dict's + # object_storage_url which will be overridden by the + # preauthurl parameter to Connection if it is provided. + return 'http://www.new.com', 'new' + + def swap_sleep(*args): + self.swap_sleep_called = True + c.get_auth = get_auth + c.sleep = swap_sleep + self.swap_sleep_called = False + + conn = c.Connection('http://www.test.com', 'asdf', 'asdf', + preauthurl='http://www.old.com', + preauthtoken='old', + ) + + self.assertEqual(conn.attempts, 0) + self.assertEqual(conn.url, 'http://www.old.com') + self.assertEqual(conn.token, 'old') + + conn.head_account() + + self.assertTrue(self.swap_sleep_called) + self.assertEqual(conn.attempts, 2) + self.assertEqual(conn.url, 'http://www.new.com') + self.assertEqual(conn.token, 'new') + + def test_reauth_preauth(self): + conn = c.Connection( + 'http://auth.example.com', 'user', 'password', + preauthurl='http://storage.example.com/v1/AUTH_test', + preauthtoken='expired') + auth_v1_response = StubResponse(200, headers={ + 'x-auth-token': 'token', + 'x-storage-url': 'http://storage.example.com/v1/AUTH_user', + }) + fake_conn = self.fake_http_connection(401, auth_v1_response, 200) + with mock.patch.multiple('swiftclient.client', + http_connection=fake_conn, + sleep=mock.DEFAULT): + conn.head_account() + self.assertRequests([ + ('HEAD', '/v1/AUTH_test', '', {'x-auth-token': 'expired'}), + ('GET', 'http://auth.example.com', '', { + 'x-auth-user': 'user', + 'x-auth-key': 'password'}), + ('HEAD', '/v1/AUTH_test', '', {'x-auth-token': 'token'}), + ]) + + def test_reauth_os_preauth(self): + os_preauth_options = { + 'tenant_name': 'demo', + 'object_storage_url': 'http://storage.example.com/v1/AUTH_test', + 'auth_token': 'expired', + } + conn = c.Connection('http://auth.example.com', 'user', 'password', + os_options=os_preauth_options, auth_version=2) + fake_keystone = fake_get_auth_keystone(os_preauth_options) + fake_conn = self.fake_http_connection(401, 200) + with mock.patch.multiple('swiftclient.client', + get_auth_keystone=fake_keystone, + http_connection=fake_conn, + sleep=mock.DEFAULT): + conn.head_account() + self.assertRequests([ + ('HEAD', '/v1/AUTH_test', '', {'x-auth-token': 'expired'}), + ('HEAD', '/v1/AUTH_test', '', {'x-auth-token': 'token'}), + ]) + + def test_session_no_invalidate(self): + mock_session = mock.MagicMock() + mock_session.get_endpoint.return_value = 'http://storagehost/v1/acct' + mock_session.get_token.return_value = 'expired' + mock_session.invalidate.return_value = False + conn = c.Connection(session=mock_session) + fake_conn = self.fake_http_connection(401) + with mock.patch.multiple('swiftclient.client', + http_connection=fake_conn, + sleep=mock.DEFAULT): + self.assertRaises(c.ClientException, conn.head_account) + self.assertEqual(mock_session.get_token.mock_calls, [mock.call()]) + self.assertEqual(mock_session.invalidate.mock_calls, [mock.call()]) + + def test_session_can_invalidate(self): + mock_session = mock.MagicMock() + mock_session.get_endpoint.return_value = 'http://storagehost/v1/acct' + mock_session.get_token.side_effect = ['expired', 'token'] + mock_session.invalidate.return_value = True + conn = c.Connection(session=mock_session) + fake_conn = self.fake_http_connection(401, 200) + with mock.patch.multiple('swiftclient.client', + http_connection=fake_conn, + sleep=mock.DEFAULT): + conn.head_account() + self.assertRequests([ + ('HEAD', '/v1/acct', '', {'x-auth-token': 'expired'}), + ('HEAD', '/v1/acct', '', {'x-auth-token': 'token'}), + ]) + self.assertEqual(mock_session.get_token.mock_calls, [ + mock.call(), mock.call()]) + self.assertEqual(mock_session.invalidate.mock_calls, [mock.call()]) + + def test_preauth_token_with_no_storage_url_requires_auth(self): + conn = c.Connection( + 'http://auth.example.com', 'user', 'password', + preauthtoken='expired') + auth_v1_response = StubResponse(200, headers={ + 'x-auth-token': 'token', + 'x-storage-url': 'http://storage.example.com/v1/AUTH_user', + }) + fake_conn = self.fake_http_connection(auth_v1_response, 200) + with mock.patch.multiple('swiftclient.client', + http_connection=fake_conn, + sleep=mock.DEFAULT): + conn.head_account() + self.assertRequests([ + ('GET', 'http://auth.example.com', '', { + 'x-auth-user': 'user', + 'x-auth-key': 'password'}), + ('HEAD', '/v1/AUTH_user', '', {'x-auth-token': 'token'}), + ]) + + def test_os_preauth_token_with_no_storage_url_requires_auth(self): + os_preauth_options = { + 'tenant_name': 'demo', + 'auth_token': 'expired', + } + conn = c.Connection('http://auth.example.com', 'user', 'password', + os_options=os_preauth_options, auth_version=2) + storage_url = 'http://storage.example.com/v1/AUTH_user' + fake_keystone = fake_get_auth_keystone(storage_url=storage_url) + fake_conn = self.fake_http_connection(200) + with mock.patch.multiple('swiftclient.client', + get_auth_keystone=fake_keystone, + http_connection=fake_conn, + sleep=mock.DEFAULT): + conn.head_account() + self.assertRequests([ + ('HEAD', '/v1/AUTH_user', '', {'x-auth-token': 'token'}), + ]) + + def test_preauth_url_trumps_auth_url(self): + storage_url = 'http://storage.example.com/v1/AUTH_pre_url' + conn = c.Connection( + 'http://auth.example.com', 'user', 'password', + preauthurl=storage_url) + auth_v1_response = StubResponse(200, headers={ + 'x-auth-token': 'post_token', + 'x-storage-url': 'http://storage.example.com/v1/AUTH_post_url', + }) + fake_conn = self.fake_http_connection(auth_v1_response, 200) + with mock.patch.multiple('swiftclient.client', + http_connection=fake_conn, + sleep=mock.DEFAULT): + conn.head_account() + self.assertRequests([ + ('GET', 'http://auth.example.com', '', { + 'x-auth-user': 'user', + 'x-auth-key': 'password'}), + ('HEAD', '/v1/AUTH_pre_url', '', {'x-auth-token': 'post_token'}), + ]) + + def test_os_preauth_url_trumps_auth_url(self): + storage_url = 'http://storage.example.com/v1/AUTH_pre_url' + os_preauth_options = { + 'tenant_name': 'demo', + 'object_storage_url': storage_url, + } + conn = c.Connection('http://auth.example.com', 'user', 'password', + os_options=os_preauth_options, auth_version=2) + fake_keystone = fake_get_auth_keystone( + storage_url='http://storage.example.com/v1/AUTH_post_url', + token='post_token') + fake_conn = self.fake_http_connection(200) + with mock.patch.multiple('swiftclient.client', + get_auth_keystone=fake_keystone, + http_connection=fake_conn, + sleep=mock.DEFAULT): + conn.head_account() + self.assertRequests([ + ('HEAD', '/v1/AUTH_pre_url', '', {'x-auth-token': 'post_token'}), + ]) + + def test_preauth_url_trumps_os_preauth_url(self): + storage_url = 'http://storage.example.com/v1/AUTH_pre_url' + os_storage_url = 'http://storage.example.com/v1/AUTH_os_pre_url' + os_preauth_options = { + 'tenant_name': 'demo', + 'object_storage_url': os_storage_url, + } + orig_os_preauth_options = dict(os_preauth_options) + conn = c.Connection('http://auth.example.com', 'user', 'password', + os_options=os_preauth_options, auth_version=2, + preauthurl=storage_url, tenant_name='not_demo') + fake_keystone = fake_get_auth_keystone( + storage_url='http://storage.example.com/v1/AUTH_post_url', + token='post_token') + fake_conn = self.fake_http_connection(200) + with mock.patch.multiple('swiftclient.client', + get_auth_keystone=fake_keystone, + http_connection=fake_conn, + sleep=mock.DEFAULT): + conn.head_account() + self.assertRequests([ + ('HEAD', '/v1/AUTH_pre_url', '', {'x-auth-token': 'post_token'}), + ]) + + # check that Connection has not modified our os_options + self.assertEqual(orig_os_preauth_options, os_preauth_options) + + def test_get_auth_sets_url_and_token(self): + with mock.patch('swiftclient.client.get_auth') as mock_get_auth: + mock_get_auth.return_value = ( + "https://storage.url/v1/AUTH_storage_acct", "AUTH_token" + ) + conn = c.Connection("https://auth.url/auth/v2.0", + "user", "passkey", tenant_name="tenant") + conn.get_auth() + self.assertEqual("https://storage.url/v1/AUTH_storage_acct", conn.url) + self.assertEqual("AUTH_token", conn.token) + + def test_timeout_passed_down(self): + # We want to avoid mocking http_connection(), and most especially + # avoid passing it down in argument. However, we cannot simply + # instantiate C=Connection(), then shim C.http_conn. Doing so would + # avoid some of the code under test (where _retry() invokes + # http_connection()), and would miss get_auth() completely. + # So, with regret, we do mock http_connection(), but with a very + # light shim that swaps out _request() as originally intended. + + orig_http_connection = c.http_connection + + timeouts = [] + + def my_request_handler(*a, **kw): + if 'timeout' in kw: + timeouts.append(kw['timeout']) + else: + timeouts.append(None) + return MockHttpResponse( + status=200, + headers={ + 'x-auth-token': 'a_token', + 'x-storage-url': 'http://files.example.com/v1/AUTH_user'}) + + def shim_connection(*a, **kw): + url, conn = orig_http_connection(*a, **kw) + conn._request = my_request_handler + return url, conn + + # v1 auth + conn = c.Connection( + 'http://auth.example.com', 'user', 'password', timeout=33.0) + with mock.patch.multiple('swiftclient.client', + http_connection=shim_connection, + sleep=mock.DEFAULT): + conn.head_account() + + # 1 call is through get_auth, 1 call is HEAD for account + self.assertEqual(timeouts, [33.0, 33.0]) + + # v2 auth + timeouts = [] + os_options = {'tenant_name': 'tenant', 'auth_token': 'meta-token'} + conn = c.Connection( + 'http://auth.example.com', 'user', 'password', timeout=33.0, + os_options=os_options, auth_version=2.0) + fake_ks = FakeKeystone(endpoint='http://some_url', token='secret') + with mock.patch('swiftclient.client.ksclient_v2', fake_ks): + with mock.patch.multiple('swiftclient.client', + http_connection=shim_connection, + sleep=mock.DEFAULT): + conn.head_account() + + # check timeout is passed to keystone client + self.assertEqual(1, len(fake_ks.calls)) + self.assertEqual(33.0, fake_ks.calls[0].get('timeout')) + # check timeout passed to HEAD for account + self.assertEqual(timeouts, [33.0]) + + # check token passed to keystone client + self.assertIn('token', fake_ks.calls[0]) + self.assertEqual('meta-token', fake_ks.calls[0].get('token')) + + def test_reset_stream(self): + + class LocalContents(object): + + def __init__(self, tell_value=0): + self.data = six.BytesIO(string.ascii_letters.encode() * 10) + self.data.seek(tell_value) + self.reads = [] + self.seeks = [] + self.tells = [] + + def tell(self): + self.tells.append(self.data.tell()) + return self.tells[-1] + + def seek(self, position, mode=0): + self.seeks.append((position, mode)) + self.data.seek(position, mode) + + def read(self, size=-1): + read_data = self.data.read(size) + self.reads.append((size, read_data)) + return read_data + + class LocalConnection(object): + + def __init__(self, parsed_url=None): + self.reason = "" + if parsed_url: + self.host = parsed_url.netloc + self.port = parsed_url.netloc + + def putrequest(self, *args, **kwargs): + self.send('PUT', *args, **kwargs) + + def putheader(self, *args, **kwargs): + return + + def endheaders(self, *args, **kwargs): + return + + def send(self, *args, **kwargs): + data = kwargs.get('data') + if data is not None: + if hasattr(data, 'read'): + data.read() + else: + for datum in data: + pass + raise socket.error('oops') + + def request(self, *args, **kwargs): + return + + def getresponse(self, *args, **kwargs): + self.status = 200 + return self + + def getheader(self, *args, **kwargs): + return 'header' + + def getheaders(self): + return [('key1', 'value1'), ('key2', 'value2')] + + def read(self, *args, **kwargs): + return '' + + def close(self): + pass + + def local_http_connection(url, proxy=None, cacert=None, + insecure=False, cert=None, cert_key=None, + ssl_compression=True, timeout=None): + parsed = urlparse(url) + return parsed, LocalConnection() + + with mock.patch.object(c, 'http_connection', local_http_connection): + conn = c.Connection('http://www.example.com', 'asdf', 'asdf', + retries=1, starting_backoff=.0001) + + contents = LocalContents() + exc = None + try: + conn.put_object('c', 'o', contents) + except socket.error as err: + exc = err + self.assertEqual(contents.tells, [0]) + self.assertEqual(contents.seeks, [(0, 0)]) + # four reads: two in the initial pass, two in the retry + self.assertEqual(4, len(contents.reads)) + self.assertEqual((65536, b''), contents.reads[1]) + self.assertEqual((65536, b''), contents.reads[3]) + self.assertEqual(str(exc), 'oops') + + contents = LocalContents(tell_value=123) + exc = None + try: + conn.put_object('c', 'o', contents) + except socket.error as err: + exc = err + self.assertEqual(contents.tells, [123]) + self.assertEqual(contents.seeks, [(123, 0)]) + # four reads: two in the initial pass, two in the retry + self.assertEqual(4, len(contents.reads)) + self.assertEqual((65536, b''), contents.reads[1]) + self.assertEqual((65536, b''), contents.reads[3]) + self.assertEqual(str(exc), 'oops') + + contents = LocalContents(tell_value=123) + wrapped_contents = swiftclient.utils.LengthWrapper( + contents, 6, md5=True) + exc = None + try: + conn.put_object('c', 'o', wrapped_contents) + except socket.error as err: + exc = err + self.assertEqual(contents.tells, [123]) + self.assertEqual(contents.seeks, [(123, 0)]) + self.assertEqual(contents.reads, [(6, b'tuvwxy')] * 2) + self.assertEqual(str(exc), 'oops') + self.assertEqual(md5(b'tuvwxy').hexdigest(), + wrapped_contents.get_md5sum()) + + contents = LocalContents() + contents.tell = None + exc = None + try: + conn.put_object('c', 'o', contents) + except c.ClientException as err: + exc = err + self.assertEqual(contents.seeks, []) + self.assertEqual(str(exc), "put_object('c', 'o', ...) failure " + "and no ability to reset contents for reupload.") + + def test_get_container(self): + headers = {'X-Favourite-Pet': 'Aardvark'} + with mock.patch('swiftclient.client.http_connection', + self.fake_http_connection(200, body=b'{}')): + with mock.patch('swiftclient.client.get_auth', + lambda *a, **k: ('http://url:8080/v1/a', 'token')): + conn = c.Connection() + conn.get_container('c1', prefix='p', limit=5, + headers=headers) + self.assertEqual(1, len(self.request_log), self.request_log) + self.assertRequests([ + ('GET', '/v1/a/c1?format=json&limit=5&prefix=p', '', { + 'x-auth-token': 'token', + 'X-Favourite-Pet': 'Aardvark', + 'accept-encoding': 'gzip', + }), + ]) + self.assertEqual(conn.attempts, 1) + + def test_head_container(self): + headers = {'X-Favourite-Pet': 'Aardvark'} + with mock.patch('swiftclient.client.http_connection', + self.fake_http_connection(200, body=b'{}')): + with mock.patch('swiftclient.client.get_auth', + lambda *a, **k: ('http://url:8080/v1/a', 'token')): + conn = c.Connection() + conn.head_container('c1', headers=headers) + self.assertEqual(1, len(self.request_log), self.request_log) + self.assertRequests([ + ('HEAD', '/v1/a/c1', '', { + 'x-auth-token': 'token', + 'X-Favourite-Pet': 'Aardvark', + }), + ]) + self.assertEqual(conn.attempts, 1) + + def test_head_object(self): + headers = {'X-Favourite-Pet': 'Aardvark'} + query_string = 'foo=bar' + with mock.patch('swiftclient.client.http_connection', + self.fake_http_connection(200)): + with mock.patch('swiftclient.client.get_auth', + lambda *a, **k: ('http://url:8080/v1/a', 'token')): + conn = c.Connection() + conn.head_object('c1', 'o1', + headers=headers, query_string=query_string) + self.assertEqual(1, len(self.request_log), self.request_log) + self.assertRequests([ + ('HEAD', '/v1/a/c1/o1?foo=bar', '', { + 'x-auth-token': 'token', + 'X-Favourite-Pet': 'Aardvark', + }), + ]) + self.assertEqual(conn.attempts, 1) + + +class TestResponseDict(MockHttpTest): + """ + Verify handling of optional response_dict argument. + """ + calls = [('post_account', {}), + ('post_container', 'c', {}), + ('put_container', 'c'), + ('delete_container', 'c'), + ('post_object', 'c', 'o', {}), + ('put_object', 'c', 'o', 'body'), + ('copy_object', 'c', 'o'), + ('delete_object', 'c', 'o')] + + def fake_get_auth(*args, **kwargs): + return 'http://url', 'token' + + def test_response_dict_with_auth_error(self): + def bad_get_auth(*args, **kwargs): + raise c.ClientException('test') + + for call in self.calls: + resp_dict = {'test': 'should be untouched'} + with mock.patch('swiftclient.client.get_auth', + bad_get_auth): + conn = c.Connection('http://127.0.0.1:8080', 'user', 'key') + self.assertRaises(c.ClientException, getattr(conn, call[0]), + *call[1:], response_dict=resp_dict) + + self.assertEqual({'test': 'should be untouched'}, resp_dict) + + def test_response_dict_with_request_error(self): + for call in self.calls: + resp_dict = {'test': 'should be untouched'} + with mock.patch('swiftclient.client.get_auth', + self.fake_get_auth): + exc = c.ClientException('test') + with mock.patch('swiftclient.client.http_connection', + self.fake_http_connection(200, exc=exc)): + conn = c.Connection('http://127.0.0.1:8080', 'user', 'key') + self.assertRaises(c.ClientException, + getattr(conn, call[0]), + *call[1:], + response_dict=resp_dict) + + self.assertEqual('should be untouched', resp_dict.get('test')) + self.assertEqual([{}], resp_dict.get('response_dicts')) + + def test_response_dict(self): + # test response_dict is populated and + # new list of response_dicts is created + for call in self.calls: + resp_dict = {'test': 'should be untouched'} + with mock.patch('swiftclient.client.get_auth', + self.fake_get_auth): + with mock.patch('swiftclient.client.http_connection', + self.fake_http_connection(200)): + conn = c.Connection('http://127.0.0.1:8080', 'user', 'key') + getattr(conn, call[0])(*call[1:], response_dict=resp_dict) + + self.assertEqual('should be untouched', + resp_dict.pop('test', None)) + self.assertEqual('Fake', resp_dict.get('reason')) + self.assertEqual(200, resp_dict.get('status')) + self.assertIn('headers', resp_dict) + self.assertEqual('yes', resp_dict['headers'].get('x-works')) + children = resp_dict.pop('response_dicts', []) + self.assertEqual(1, len(children)) + self.assertEqual(resp_dict, children[0]) + + def test_response_dict_with_existing(self): + # check response_dict is populated and new dict is appended + # to existing response_dicts list + for call in self.calls: + resp_dict = {'test': 'should be untouched', + 'response_dicts': [{'existing': 'response dict'}]} + with mock.patch('swiftclient.client.get_auth', + self.fake_get_auth): + with mock.patch('swiftclient.client.http_connection', + self.fake_http_connection(200)): + conn = c.Connection('http://127.0.0.1:8080', 'user', 'key') + getattr(conn, call[0])(*call[1:], response_dict=resp_dict) + + self.assertEqual('should be untouched', + resp_dict.pop('test', None)) + self.assertEqual('Fake', resp_dict.get('reason')) + self.assertEqual(200, resp_dict.get('status')) + self.assertIn('headers', resp_dict) + self.assertEqual('yes', resp_dict['headers'].get('x-works')) + children = resp_dict.pop('response_dicts', []) + self.assertEqual(2, len(children)) + self.assertEqual({'existing': 'response dict'}, children[0]) + self.assertEqual(resp_dict, children[1]) + + +class TestLogging(MockHttpTest): + """ + Make sure all the lines in http_log are covered. + """ + + def setUp(self): + super(TestLogging, self).setUp() + self.swiftclient_logger = logging.getLogger("swiftclient") + self.log_level = self.swiftclient_logger.getEffectiveLevel() + self.swiftclient_logger.setLevel(logging.INFO) + + def tearDown(self): + self.swiftclient_logger.setLevel(self.log_level) + super(TestLogging, self).tearDown() + + def test_put_ok(self): + c.http_connection = self.fake_http_connection(200) + args = ('http://www.test.com', 'asdf', 'asdf', 'asdf', 'asdf') + value = c.put_object(*args) + self.assertIsInstance(value, six.string_types) + + def test_head_error(self): + c.http_connection = self.fake_http_connection(500) + self.assertRaises(c.ClientException, c.head_object, + 'http://www.test.com', 'asdf', 'asdf', 'asdf') + + def test_get_error(self): + c.http_connection = self.fake_http_connection(404) + with self.assertRaises(c.ClientException) as exc_context: + c.get_object('http://www.test.com', 'asdf', 'asdf', 'asdf') + self.assertEqual(exc_context.exception.http_status, 404) + + def test_content_encoding_gzip_body_is_logged_decoded(self): + buf = six.BytesIO() + gz = gzip.GzipFile(fileobj=buf, mode='w') + data = {"test": u"\u2603"} + decoded_body = json.dumps(data).encode('utf-8') + gz.write(decoded_body) + gz.close() + # stub a gzip encoded body + body = buf.getvalue() + headers = {'content-encoding': 'gzip'} + # ... and make a content-encoding gzip error response + stub_response = StubResponse(500, body, headers) + with mock.patch('swiftclient.client.logger.info') as mock_log: + # ... if the client gets such a response + c.http_connection = self.fake_http_connection(stub_response) + with self.assertRaises(c.ClientException) as exc_context: + c.get_object('http://www.test.com', 'asdf', 'asdf', 'asdf') + self.assertEqual(exc_context.exception.http_status, 500) + # it will log the decoded body + self.assertEqual([ + mock.call('REQ: %s', u'curl -i http://www.test.com/asdf/asdf ' + '-X GET -H "X-Auth-Token: ..."'), + mock.call('RESP STATUS: %s %s', 500, 'Fake'), + mock.call('RESP HEADERS: %s', {'content-encoding': 'gzip'}), + mock.call('RESP BODY: %s', decoded_body) + ], mock_log.mock_calls) + + def test_redact_token(self): + with mock.patch('swiftclient.client.logger.debug') as mock_log: + token_value = 'tkee96b40a8ca44fc5ad72ec5a7c90d9b' + token_encoded = token_value.encode('utf8') + unicode_token_value = (u'\u5929\u7a7a\u4e2d\u7684\u4e4c\u4e91' + u'\u5929\u7a7a\u4e2d\u7684\u4e4c\u4e91' + u'\u5929\u7a7a\u4e2d\u7684\u4e4c') + unicode_token_encoded = unicode_token_value.encode('utf8') + set_cookie_value = 'X-Auth-Token=%s' % token_value + set_cookie_encoded = set_cookie_value.encode('utf8') + c.http_log( + ['GET'], + {'headers': { + 'X-Auth-Token': token_encoded, + 'X-Storage-Token': unicode_token_encoded + }}, + MockHttpResponse( + status=200, + headers={ + 'X-Auth-Token': token_encoded, + 'X-Storage-Token': unicode_token_encoded, + 'Etag': b'mock_etag', + 'Set-Cookie': set_cookie_encoded + } + ), + '' + ) + out = [] + for _, args, kwargs in mock_log.mock_calls: + for arg in args: + out.append(u'%s' % arg) + output = u''.join(out) + self.assertIn('X-Auth-Token', output) + self.assertIn(token_value[:16] + '...', output) + self.assertIn('X-Storage-Token', output) + self.assertIn(unicode_token_value[:8] + '...', output) + self.assertIn('Set-Cookie', output) + self.assertIn(set_cookie_value[:16] + '...', output) + self.assertNotIn(token_value, output) + self.assertNotIn(unicode_token_value, output) + self.assertNotIn(set_cookie_value, output) + + def test_show_token(self): + with mock.patch('swiftclient.client.logger.debug') as mock_log: + token_value = 'tkee96b40a8ca44fc5ad72ec5a7c90d9b' + token_encoded = token_value.encode('utf8') + unicode_token_value = (u'\u5929\u7a7a\u4e2d\u7684\u4e4c\u4e91' + u'\u5929\u7a7a\u4e2d\u7684\u4e4c\u4e91' + u'\u5929\u7a7a\u4e2d\u7684\u4e4c') + c.logger_settings['redact_sensitive_headers'] = False + unicode_token_encoded = unicode_token_value.encode('utf8') + c.http_log( + ['GET'], + {'headers': { + 'X-Auth-Token': token_encoded, + 'X-Storage-Token': unicode_token_encoded + }}, + MockHttpResponse( + status=200, + headers=[ + ('X-Auth-Token', token_encoded), + ('X-Storage-Token', unicode_token_encoded), + ('Etag', b'mock_etag') + ] + ), + '' + ) + out = [] + for _, args, kwargs in mock_log.mock_calls: + for arg in args: + out.append(u'%s' % arg) + output = u''.join(out) + self.assertIn('X-Auth-Token', output) + self.assertIn(token_value, output) + self.assertIn('X-Storage-Token', output) + self.assertIn(unicode_token_value, output) + + @mock.patch('swiftclient.client.logger.debug') + def test_unicode_path(self, mock_log): + path = u'http://swift/v1/AUTH_account-\u062a'.encode('utf-8') + c.http_log(['GET', path], {}, + MockHttpResponse(status=200, headers=[]), '') + request_log_line = mock_log.mock_calls[0] + self.assertEqual('REQ: %s', request_log_line[1][0]) + self.assertEqual(u'curl -i -X GET %s' % path.decode('utf-8'), + request_log_line[1][1]) + + +class TestCloseConnection(MockHttpTest): + + def test_close_none(self): + c.http_connection = self.fake_http_connection() + conn = c.Connection('http://www.test.com', 'asdf', 'asdf') + self.assertIsNone(conn.http_conn) + conn.close() + self.assertIsNone(conn.http_conn) + # Can re-close + conn.close() + self.assertIsNone(conn.http_conn) + + def test_close_ok(self): + url = 'http://www.test.com' + conn = c.Connection(url, 'asdf', 'asdf') + self.assertIsNone(conn.http_conn) + conn.http_conn = c.http_connection(url) + self.assertEqual(type(conn.http_conn), tuple) + self.assertEqual(len(conn.http_conn), 2) + http_conn_obj = conn.http_conn[1] + self.assertIsInstance(http_conn_obj, c.HTTPConnection) + self.assertTrue(hasattr(http_conn_obj, 'close')) + conn.close() + + +class TestServiceToken(MockHttpTest): + + def setUp(self): + super(TestServiceToken, self).setUp() + self.os_options = { + 'object_storage_url': 'http://storage_url.com', + 'service_username': 'service_username', + 'service_project_name': 'service_project_name', + 'service_key': 'service_key'} + + def get_connection(self): + conn = c.Connection('http://www.test.com', 'asdf', 'asdf', + os_options=self.os_options) + + self.assertIs(type(conn), c.Connection) + conn.get_auth = self.get_auth + conn.get_service_auth = self.get_service_auth + + self.assertEqual(conn.attempts, 0) + self.assertIsNone(conn.service_token) + + self.assertIs(type(conn), c.Connection) + return conn + + def get_auth(self): + # The real get_auth function will always return the os_option + # dict's object_storage_url which will be overridden by the + # preauthurl parameter to Connection if it is provided. + return self.os_options.get('object_storage_url'), 'token' + + def get_service_auth(self): + # The real get_auth function will always return the os_option + # dict's object_storage_url which will be overridden by the + # preauthurl parameter to Connection if it is provided. + return self.os_options.get('object_storage_url'), 'stoken' + + def test_service_token_reauth(self): + get_auth_call_list = [] + + def get_auth(url, user, key, **kwargs): + # The real get_auth function will always return the os_option + # dict's object_storage_url which will be overridden by the + # preauthurl parameter to Connection if it is provided. + args = {'url': url, 'user': user, 'key': key, 'kwargs': kwargs} + get_auth_call_list.append(args) + return_dict = {'asdf': 'new', 'service_username': 'newserv'} + storage_url = kwargs['os_options'].get('object_storage_url') + return storage_url, return_dict[user] + + def swap_sleep(*args): + self.swap_sleep_called = True + c.get_auth = get_auth + + with mock.patch('swiftclient.client.http_connection', + self.fake_http_connection(401, 200)): + with mock.patch('swiftclient.client.sleep', swap_sleep): + self.swap_sleep_called = False + + conn = c.Connection('http://www.test.com', 'asdf', 'asdf', + preauthurl='http://www.old.com', + preauthtoken='old', + os_options=self.os_options) + + self.assertEqual(conn.attempts, 0) + self.assertEqual(conn.url, 'http://www.old.com') + self.assertEqual(conn.token, 'old') + + conn.head_account() + + self.assertTrue(self.swap_sleep_called) + self.assertEqual(conn.attempts, 2) + # The original 'preauth' storage URL *must* be preserved + self.assertEqual(conn.url, 'http://www.old.com') + self.assertEqual(conn.token, 'new') + self.assertEqual(conn.service_token, 'newserv') + + # Check get_auth was called with expected args + auth_args = get_auth_call_list[0] + auth_kwargs = get_auth_call_list[0]['kwargs'] + self.assertEqual('asdf', auth_args['user']) + self.assertEqual('asdf', auth_args['key']) + self.assertEqual('service_key', + auth_kwargs['os_options']['service_key']) + self.assertEqual('service_username', + auth_kwargs['os_options']['service_username']) + self.assertEqual('service_project_name', + auth_kwargs['os_options']['service_project_name']) + + auth_args = get_auth_call_list[1] + auth_kwargs = get_auth_call_list[1]['kwargs'] + self.assertEqual('service_username', auth_args['user']) + self.assertEqual('service_key', auth_args['key']) + self.assertEqual('service_project_name', + auth_kwargs['os_options']['tenant_name']) + + def test_service_token_reauth_retries_0(self): + get_auth_call_list = [] + + def get_auth(url, user, key, **kwargs): + # The real get_auth function will always return the os_option + # dict's object_storage_url which will be overridden by the + # preauthurl parameter to Connection if it is provided. + args = {'url': url, 'user': user, 'key': key, 'kwargs': kwargs} + get_auth_call_list.append(args) + return_dict = {'asdf': 'new', 'service_username': 'newserv'} + storage_url = kwargs['os_options'].get('object_storage_url') + return storage_url, return_dict[user] + + def swap_sleep(*args): + self.swap_sleep_called = True + c.get_auth = get_auth + + with mock.patch('swiftclient.client.http_connection', + self.fake_http_connection(401, 200)): + with mock.patch('swiftclient.client.sleep', swap_sleep): + self.swap_sleep_called = False + + conn = c.Connection('http://www.test.com', 'asdf', 'asdf', + preauthurl='http://www.old.com', + preauthtoken='old', + os_options=self.os_options, + retries=0) + + self.assertEqual(conn.attempts, 0) + self.assertEqual(conn.url, 'http://www.old.com') + self.assertEqual(conn.token, 'old') + + conn.head_account() + + self.assertTrue(self.swap_sleep_called) + self.assertEqual(conn.attempts, 2) + # The original 'preauth' storage URL *must* be preserved + self.assertEqual(conn.url, 'http://www.old.com') + self.assertEqual(conn.token, 'new') + self.assertEqual(conn.service_token, 'newserv') + + # Check get_auth was called with expected args + auth_args = get_auth_call_list[0] + auth_kwargs = get_auth_call_list[0]['kwargs'] + self.assertEqual('asdf', auth_args['user']) + self.assertEqual('asdf', auth_args['key']) + self.assertEqual('service_key', + auth_kwargs['os_options']['service_key']) + self.assertEqual('service_username', + auth_kwargs['os_options']['service_username']) + self.assertEqual('service_project_name', + auth_kwargs['os_options']['service_project_name']) + + auth_args = get_auth_call_list[1] + auth_kwargs = get_auth_call_list[1]['kwargs'] + self.assertEqual('service_username', auth_args['user']) + self.assertEqual('service_key', auth_args['key']) + self.assertEqual('service_project_name', + auth_kwargs['os_options']['tenant_name']) + + # Ensure this is not an endless loop - it fails after the second 401 + with mock.patch('swiftclient.client.http_connection', + self.fake_http_connection(401, 401, 401, 401)): + with mock.patch('swiftclient.client.sleep', swap_sleep): + self.swap_sleep_called = False + + conn = c.Connection('http://www.test.com', 'asdf', 'asdf', + preauthurl='http://www.old.com', + preauthtoken='old', + os_options=self.os_options, + retries=0) + + self.assertEqual(conn.attempts, 0) + self.assertRaises(c.ClientException, conn.head_account) + self.assertEqual(conn.attempts, 2) + unused_responses = list(self.fake_connect.code_iter) + self.assertEqual(unused_responses, [401, 401]) + + def test_service_token_get_account(self): + with mock.patch('swiftclient.client.http_connection', + self.fake_http_connection(200)): + with mock.patch('swiftclient.client.parse_api_response'): + conn = self.get_connection() + conn.get_account() + self.assertEqual(1, len(self.request_log), self.request_log) + for actual in self.iter_request_log(): + self.assertEqual('GET', actual['method']) + actual_hdrs = actual['headers'] + self.assertEqual('stoken', actual_hdrs.get('X-Service-Token')) + self.assertEqual('token', actual_hdrs['X-Auth-Token']) + self.assertEqual('http://storage_url.com/?format=json', + actual['full_path']) + self.assertEqual(conn.attempts, 1) + + def test_service_token_head_account(self): + with mock.patch('swiftclient.client.http_connection', + self.fake_http_connection(200)): + conn = self.get_connection() + conn.head_account() + self.assertEqual(1, len(self.request_log), self.request_log) + for actual in self.iter_request_log(): + self.assertEqual('HEAD', actual['method']) + actual_hdrs = actual['headers'] + self.assertEqual('stoken', actual_hdrs.get('X-Service-Token')) + self.assertEqual('token', actual_hdrs['X-Auth-Token']) + self.assertEqual('http://storage_url.com', actual['full_path']) + + self.assertEqual(conn.attempts, 1) + + def test_service_token_post_account(self): + with mock.patch('swiftclient.client.http_connection', + self.fake_http_connection(201)): + conn = self.get_connection() + conn.post_account(headers={}) + self.assertEqual(1, len(self.request_log), self.request_log) + for actual in self.iter_request_log(): + self.assertEqual('POST', actual['method']) + actual_hdrs = actual['headers'] + self.assertEqual('stoken', actual_hdrs.get('X-Service-Token')) + self.assertEqual('token', actual_hdrs['X-Auth-Token']) + self.assertEqual('http://storage_url.com', actual['full_path']) + self.assertEqual(conn.attempts, 1) + + def test_service_token_delete_container(self): + with mock.patch('swiftclient.client.http_connection', + self.fake_http_connection(204)): + conn = self.get_connection() + conn.delete_container('container1') + self.assertEqual(1, len(self.request_log), self.request_log) + for actual in self.iter_request_log(): + self.assertEqual('DELETE', actual['method']) + actual_hdrs = actual['headers'] + self.assertEqual('stoken', actual_hdrs.get('X-Service-Token')) + self.assertEqual('token', actual_hdrs['X-Auth-Token']) + self.assertEqual('http://storage_url.com/container1', + actual['full_path']) + self.assertEqual(conn.attempts, 1) + + def test_service_token_get_container(self): + with mock.patch('swiftclient.client.http_connection', + self.fake_http_connection(200)): + with mock.patch('swiftclient.client.parse_api_response'): + conn = self.get_connection() + conn.get_container('container1') + self.assertEqual(1, len(self.request_log), self.request_log) + for actual in self.iter_request_log(): + self.assertEqual('GET', actual['method']) + actual_hdrs = actual['headers'] + self.assertEqual('stoken', actual_hdrs.get('X-Service-Token')) + self.assertEqual('token', actual_hdrs['X-Auth-Token']) + self.assertEqual('http://storage_url.com/container1?format=json', + actual['full_path']) + self.assertEqual(conn.attempts, 1) + + def test_service_token_get_container_full_listing(self): + # verify service token is sent with each request for a full listing + with mock.patch('swiftclient.client.http_connection', + self.fake_http_connection(200, 200)): + with mock.patch('swiftclient.client.parse_api_response') as resp: + resp.side_effect = ([{"name": "obj1"}], []) + conn = self.get_connection() + conn.get_container('container1', full_listing=True) + self.assertEqual(2, len(self.request_log), self.request_log) + expected_urls = iter(( + 'http://storage_url.com/container1?format=json', + 'http://storage_url.com/container1?format=json&marker=obj1' + )) + for actual in self.iter_request_log(): + self.assertEqual('GET', actual['method']) + actual_hdrs = actual['headers'] + self.assertEqual('stoken', actual_hdrs.get('X-Service-Token')) + self.assertEqual('token', actual_hdrs['X-Auth-Token']) + self.assertEqual(next(expected_urls), + actual['full_path']) + self.assertEqual(conn.attempts, 1) + + def test_service_token_head_container(self): + with mock.patch('swiftclient.client.http_connection', + self.fake_http_connection(200)): + conn = self.get_connection() + conn.head_container('container1') + self.assertEqual(1, len(self.request_log), self.request_log) + for actual in self.iter_request_log(): + self.assertEqual('HEAD', actual['method']) + actual_hdrs = actual['headers'] + self.assertEqual('stoken', actual_hdrs.get('X-Service-Token')) + self.assertEqual('token', actual_hdrs['X-Auth-Token']) + self.assertEqual('http://storage_url.com/container1', + actual['full_path']) + self.assertEqual(conn.attempts, 1) + + def test_service_token_post_container(self): + headers = {'X-Container-Meta-Color': 'blue'} + with mock.patch('swiftclient.client.http_connection', + self.fake_http_connection(201)): + conn = self.get_connection() + conn.post_container('container1', headers) + self.assertEqual(1, len(self.request_log), self.request_log) + for actual in self.iter_request_log(): + self.assertEqual('POST', actual['method']) + actual_hdrs = actual['headers'] + self.assertEqual('stoken', actual_hdrs.get('X-Service-Token')) + self.assertEqual('token', actual_hdrs['X-Auth-Token']) + self.assertEqual('http://storage_url.com/container1', + actual['full_path']) + self.assertEqual(conn.attempts, 1) + # Check that we didn't mutate the request header dict + self.assertEqual(headers, {'X-Container-Meta-Color': 'blue'}) + + def test_service_token_put_container(self): + with mock.patch('swiftclient.client.http_connection', + self.fake_http_connection(200)): + conn = self.get_connection() + conn.put_container('container1') + self.assertEqual(1, len(self.request_log), self.request_log) + for actual in self.iter_request_log(): + self.assertEqual('PUT', actual['method']) + actual_hdrs = actual['headers'] + self.assertEqual('stoken', actual_hdrs.get('X-Service-Token')) + self.assertEqual('token', actual_hdrs['X-Auth-Token']) + self.assertEqual('http://storage_url.com/container1', + actual['full_path']) + self.assertEqual(conn.attempts, 1) + + def test_service_token_get_object(self): + with mock.patch('swiftclient.client.http_connection', + self.fake_http_connection(200)): + conn = self.get_connection() + conn.get_object('container1', 'obj1') + self.assertEqual(1, len(self.request_log), self.request_log) + for actual in self.iter_request_log(): + self.assertEqual('GET', actual['method']) + actual_hdrs = actual['headers'] + self.assertEqual('stoken', actual_hdrs.get('X-Service-Token')) + self.assertEqual('token', actual_hdrs['X-Auth-Token']) + self.assertEqual('http://storage_url.com/container1/obj1', + actual['full_path']) + self.assertEqual(conn.attempts, 1) + + def test_service_token_head_object(self): + with mock.patch('swiftclient.client.http_connection', + self.fake_http_connection(200)): + conn = self.get_connection() + conn.head_object('container1', 'obj1') + self.assertEqual(1, len(self.request_log), self.request_log) + for actual in self.iter_request_log(): + self.assertEqual('HEAD', actual['method']) + actual_hdrs = actual['headers'] + self.assertEqual('stoken', actual_hdrs.get('X-Service-Token')) + self.assertEqual('token', actual_hdrs['X-Auth-Token']) + self.assertEqual('http://storage_url.com/container1/obj1', + actual['full_path']) + self.assertEqual(conn.attempts, 1) + + def test_service_token_put_object(self): + with mock.patch('swiftclient.client.http_connection', + self.fake_http_connection(200)): + conn = self.get_connection() + conn.put_object('container1', 'obj1', 'a_string') + self.assertEqual(1, len(self.request_log), self.request_log) + for actual in self.iter_request_log(): + self.assertEqual('PUT', actual['method']) + actual_hdrs = actual['headers'] + self.assertEqual('stoken', actual_hdrs.get('X-Service-Token')) + self.assertEqual('token', actual_hdrs['X-Auth-Token']) + self.assertEqual('http://storage_url.com/container1/obj1', + actual['full_path']) + self.assertEqual(conn.attempts, 1) + + def test_service_token_post_object(self): + with mock.patch('swiftclient.client.http_connection', + self.fake_http_connection(202)): + conn = self.get_connection() + conn.post_object('container1', 'obj1', {}) + self.assertEqual(1, len(self.request_log), self.request_log) + for actual in self.iter_request_log(): + self.assertEqual('POST', actual['method']) + actual_hdrs = actual['headers'] + self.assertEqual('stoken', actual_hdrs.get('X-Service-Token')) + self.assertEqual('token', actual_hdrs['X-Auth-Token']) + self.assertEqual('http://storage_url.com/container1/obj1', + actual['full_path']) + self.assertEqual(conn.attempts, 1) + + def test_service_token_delete_object(self): + with mock.patch('swiftclient.client.http_connection', + self.fake_http_connection(202)): + conn = self.get_connection() + conn.delete_object('container1', 'obj1', query_string='a_string') + self.assertEqual(1, len(self.request_log), self.request_log) + for actual in self.iter_request_log(): + self.assertEqual('DELETE', actual['method']) + actual_hdrs = actual['headers'] + self.assertEqual('stoken', actual_hdrs.get('X-Service-Token')) + self.assertEqual('token', actual_hdrs['X-Auth-Token']) + self.assertEqual('http://storage_url.com/container1/obj1?a_string', + actual['full_path']) + self.assertEqual(conn.attempts, 1) |