diff --git a/glanceclient/common/utils.py b/glanceclient/common/utils.py index 2723841c..c36c58f5 100644 --- a/glanceclient/common/utils.py +++ b/glanceclient/common/utils.py @@ -283,10 +283,8 @@ def save_image(data, path): :param path: path to save the image to """ if path is None: - if six.PY3: - image = sys.stdout.buffer - else: - image = sys.stdout + image = getattr(sys.stdout, 'buffer', + sys.stdout) else: image = open(path, 'wb') try: diff --git a/glanceclient/tests/unit/test_shell.py b/glanceclient/tests/unit/test_shell.py index 1350554b..05af9240 100644 --- a/glanceclient/tests/unit/test_shell.py +++ b/glanceclient/tests/unit/test_shell.py @@ -36,6 +36,8 @@ import six from glanceclient.common import utils from glanceclient import exc from glanceclient import shell as openstack_shell +from glanceclient.tests.unit.v2.fixtures import image_show_fixture +from glanceclient.tests.unit.v2.fixtures import image_versions_fixture from glanceclient.tests import utils as testutils # NOTE (esheffield) Used for the schema caching tests @@ -844,3 +846,115 @@ class ShellCacheSchemaTest(testutils.TestCase): client, home_dir=self.cache_dir) self.assertEqual(True, switch_version) + + +class ShellTestRequests(testutils.TestCase): + """Shell tests using the requests mock library.""" + def _make_args(self, args): + # NOTE(venkatesh): this conversion from a dict to an object + # is required because the test_shell.do_xxx(gc, args) methods + # expects the args to be attributes of an object. If passed as + # dict directly, it throws an AttributeError. + class Args(object): + def __init__(self, entries): + self.__dict__.update(entries) + + return Args(args) + + def setUp(self): + super(ShellTestRequests, self).setUp() + self._old_env = os.environ + os.environ = {} + + def tearDown(self): + super(ShellTestRequests, self).tearDown() + os.environ = self._old_env + + def test_download_has_no_stray_output_to_stdout(self): + """Regression test for bug 1488914""" + saved_stdout = sys.stdout + try: + sys.stdout = output = testutils.FakeNoTTYStdout() + id = image_show_fixture['id'] + self.requests = self.useFixture(rm_fixture.Fixture()) + self.requests.get('http://example.com/versions', + json=image_versions_fixture) + + headers = {'Content-Length': '4', + 'Content-type': 'application/octet-stream'} + fake = testutils.FakeResponse(headers, six.StringIO('DATA')) + self.requests.get('http://example.com/v1/images/%s' % id, + raw=fake) + + self.requests.get('http://example.com/v1/images/detail' + '?sort_key=name&sort_dir=asc&limit=20') + + headers = {'X-Image-Meta-Id': id} + self.requests.head('http://example.com/v1/images/%s' % id, + headers=headers) + + with mock.patch.object(openstack_shell.OpenStackImagesShell, + '_cache_schemas') as mocked_cache_schema: + mocked_cache_schema.return_value = True + shell = openstack_shell.OpenStackImagesShell() + argstr = ('--os-auth-token faketoken ' + '--os-image-url http://example.com ' + 'image-download %s' % id) + shell.main(argstr.split()) + self.assertTrue(mocked_cache_schema.called) + # Ensure we have *only* image data + self.assertEqual('DATA', output.getvalue()) + finally: + sys.stdout = saved_stdout + + def test_v1_download_has_no_stray_output_to_stdout(self): + """Ensure no stray print statements corrupt the image""" + saved_stdout = sys.stdout + try: + sys.stdout = output = testutils.FakeNoTTYStdout() + id = image_show_fixture['id'] + + self.requests = self.useFixture(rm_fixture.Fixture()) + headers = {'X-Image-Meta-Id': id} + self.requests.head('http://example.com/v1/images/%s' % id, + headers=headers) + + headers = {'Content-Length': '4', + 'Content-type': 'application/octet-stream'} + fake = testutils.FakeResponse(headers, six.StringIO('DATA')) + self.requests.get('http://example.com/v1/images/%s' % id, + headers=headers, raw=fake) + + shell = openstack_shell.OpenStackImagesShell() + argstr = ('--os-image-api-version 1 --os-auth-token faketoken ' + '--os-image-url http://example.com ' + 'image-download %s' % id) + shell.main(argstr.split()) + # Ensure we have *only* image data + self.assertEqual('DATA', output.getvalue()) + finally: + sys.stdout = saved_stdout + + def test_v2_download_has_no_stray_output_to_stdout(self): + """Ensure no stray print statements corrupt the image""" + saved_stdout = sys.stdout + try: + sys.stdout = output = testutils.FakeNoTTYStdout() + id = image_show_fixture['id'] + headers = {'Content-Length': '4', + 'Content-type': 'application/octet-stream'} + fake = testutils.FakeResponse(headers, six.StringIO('DATA')) + + self.requests = self.useFixture(rm_fixture.Fixture()) + self.requests.get('http://example.com/v2/images/%s/file' % id, + headers=headers, raw=fake) + + shell = openstack_shell.OpenStackImagesShell() + argstr = ('--os-image-api-version 2 --os-auth-token faketoken ' + '--os-image-url http://example.com ' + 'image-download %s' % id) + shell.main(argstr.split()) + # Ensure we have *only* image data + self.assertEqual('DATA', output.getvalue()) + finally: + sys.stdout = saved_stdout diff --git a/glanceclient/tests/unit/v2/fixtures.py b/glanceclient/tests/unit/v2/fixtures.py index 703d43b9..3d897709 100644 --- a/glanceclient/tests/unit/v2/fixtures.py +++ b/glanceclient/tests/unit/v2/fixtures.py @@ -319,3 +319,68 @@ schema_fixture = { } } } + +image_versions_fixture = { + "versions": [ + { + "id": "v2.3", + "links": [ + { + "href": "http://localhost:9292/v2/", + "rel": "self" + } + ], + "status": "CURRENT" + }, + { + "id": "v2.2", + "links": [ + { + "href": "http://localhost:9292/v2/", + "rel": "self" + } + ], + "status": "SUPPORTED" + }, + { + "id": "v2.1", + "links": [ + { + "href": "http://localhost:9292/v2/", + "rel": "self" + } + ], + "status": "SUPPORTED" + }, + { + "id": "v2.0", + "links": [ + { + "href": "http://localhost:9292/v2/", + "rel": "self" + } + ], + "status": "SUPPORTED" + }, + { + "id": "v1.1", + "links": [ + { + "href": "http://localhost:9292/v1/", + "rel": "self" + } + ], + "status": "SUPPORTED" + }, + { + "id": "v1.0", + "links": [ + { + "href": "http://localhost:9292/v1/", + "rel": "self" + } + ], + "status": "SUPPORTED" + } + ] +} diff --git a/glanceclient/tests/utils.py b/glanceclient/tests/utils.py index 377d3f45..6b03f312 100644 --- a/glanceclient/tests/utils.py +++ b/glanceclient/tests/utils.py @@ -117,6 +117,10 @@ class FakeResponse(object): self.raw = RawRequest(headers, body=body, reason=reason, version=version, status=status_code) + @property + def status(self): + return self.status_code + @property def ok(self): return (self.status_code < 400 or @@ -151,6 +155,9 @@ class FakeResponse(object): break yield chunk + def release_conn(self, **kwargs): + pass + class TestCase(testtools.TestCase): TEST_REQUEST_BASE = {