Merge "Add a method to download an image from glance"
This commit is contained in:
commit
ad6e8419e1
@ -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
|
||||
|
||||
|
@ -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")
|
||||
|
99
shade/tests/unit/test_image.py
Normal file
99
shade/tests/unit/test_image.py
Normal 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())
|
Loading…
x
Reference in New Issue
Block a user