Merge "Add a method to download an image from glance"

This commit is contained in:
Jenkins 2016-02-19 21:57:58 +00:00 committed by Gerrit Code Review
commit ad6e8419e1
3 changed files with 161 additions and 0 deletions

View File

@ -1564,6 +1564,46 @@ class OpenStackCloud(object):
"""
return _utils._get_entity(self.search_images, name_or_id, filters)
def download_image(self, name_or_id, output_path=None, output_file=None):
"""Download an image from glance by name or ID
:param str name_or_id: Name or ID of the image.
:param output_path: the output path to write the image to. Either this
or output_file must be specified
:param output_file: a file object (or file-like object) to write the
image data to. Only write() will be called on this object. Either
this or output_path must be specified
:raises: OpenStackCloudException in the event download_image is called
without exactly one of either output_path or output_file
:raises: OpenStackCloudResourceNotFound if no images are found matching
the name or id provided
"""
if output_path is None and output_file is None:
raise OpenStackCloudException('No output specified, an output path'
' or file object is necessary to '
'write the image data to')
elif output_path is not None and output_file is not None:
raise OpenStackCloudException('Both an output path and file object'
' were provided, however only one '
'can be used at once')
image = self.search_images(name_or_id)
if len(image) == 0:
raise OpenStackCloudResourceNotFound(
"No images with name or id %s were found" % name_or_id)
image_contents = self.glance_client.images.data(image[0]['id'])
with _utils.shade_exceptions("Unable to download image"):
if output_path:
with open(output_path, 'wb') as fd:
for chunk in image_contents:
fd.write(chunk)
return
elif output_file:
for chunk in image_contents:
output_file.write(chunk)
return
def get_floating_ip(self, id, filters=None):
"""Get a floating IP by ID

View File

@ -19,6 +19,8 @@ test_compute
Functional tests for `shade` image methods.
"""
import filecmp
import os
import tempfile
from shade import openstack_cloud
@ -47,3 +49,23 @@ class TestImage(base.TestCase):
wait=True)
finally:
self.cloud.delete_image(image_name, wait=True)
def test_download_image(self):
test_image = tempfile.NamedTemporaryFile(delete=False)
self.addCleanup(os.remove, test_image.name)
test_image.write('\0' * 1024 * 1024)
test_image.close()
image_name = self.getUniqueString('image')
self.cloud.create_image(name=image_name,
filename=test_image.name,
disk_format='raw',
container_format='bare',
min_disk=10,
min_ram=1024,
wait=True)
self.addCleanup(self.cloud.delete_image, image_name, wait=True)
output = os.path.join(tempfile.gettempdir(), self.getUniqueString())
self.cloud.download_image(image_name, output)
self.addCleanup(os.remove, output)
self.assertTrue(filecmp.cmp(test_image.name, output),
"Downloaded contents don't match created image")

View File

@ -0,0 +1,99 @@
# Copyright 2016 Hewlett-Packard Development Company, L.P.
#
# 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 tempfile
import uuid
import mock
import six
import shade
from shade import exc
from shade.tests import base
class TestImage(base.TestCase):
def setUp(self):
super(TestImage, self).setUp()
self.cloud = shade.openstack_cloud(validate=False)
self.image_id = str(uuid.uuid4())
self.fake_search_return = [{
u'image_state': u'available',
u'container_format': u'bare',
u'min_ram': 0,
u'ramdisk_id': None,
u'updated_at': u'2016-02-10T05:05:02Z',
u'file': '/v2/images/' + self.image_id + '/file',
u'size': 3402170368,
u'image_type': u'snapshot',
u'disk_format': u'qcow2',
u'id': self.image_id,
u'schema': u'/v2/schemas/image',
u'status': u'active',
u'tags': [],
u'visibility': u'private',
u'locations': [{
u'url': u'http://127.0.0.1/images/' + self.image_id,
u'metadata': {}}],
u'min_disk': 40,
u'virtual_size': None,
u'name': u'fake_image',
u'checksum': u'ee36e35a297980dee1b514de9803ec6d',
u'created_at': u'2016-02-10T05:03:11Z',
u'protected': False}]
self.output = six.BytesIO()
self.output.write(uuid.uuid4().bytes)
self.output.seek(0)
def test_download_image_no_output(self):
self.assertRaises(exc.OpenStackCloudException,
self.cloud.download_image, 'fake_image')
def test_download_image_two_outputs(self):
fake_fd = six.BytesIO()
self.assertRaises(exc.OpenStackCloudException,
self.cloud.download_image, 'fake_image',
output_path='fake_path', output_file=fake_fd)
@mock.patch.object(shade.OpenStackCloud, 'search_images', return_value=[])
def test_download_image_no_images_found(self, mock_search):
self.assertRaises(exc.OpenStackCloudResourceNotFound,
self.cloud.download_image, 'fake_image',
output_path='fake_path')
@mock.patch.object(shade.OpenStackCloud, 'glance_client')
@mock.patch.object(shade.OpenStackCloud, 'search_images')
def test_download_image_with_fd(self, mock_search, mock_glance):
output_file = six.BytesIO()
mock_glance.images.data.return_value = self.output
mock_search.return_value = self.fake_search_return
self.cloud.download_image('fake_image', output_file=output_file)
mock_glance.images.data.assert_called_once_with(self.image_id)
output_file.seek(0)
self.output.seek(0)
self.assertEqual(output_file.read(), self.output.read())
@mock.patch.object(shade.OpenStackCloud, 'glance_client')
@mock.patch.object(shade.OpenStackCloud, 'search_images')
def test_download_image_with_path(self, mock_search, mock_glance):
output_file = tempfile.NamedTemporaryFile()
mock_glance.images.data.return_value = self.output
mock_search.return_value = self.fake_search_return
self.cloud.download_image('fake_image',
output_path=output_file.name)
mock_glance.images.data.assert_called_once_with(self.image_id)
output_file.seek(0)
self.output.seek(0)
self.assertEqual(output_file.read(), self.output.read())