Add image.stage methods
Add support for staging (and completing the import) image data. Change-Id: Id865c7ffe5fff5d723074c22d0fd01d817ae932d
This commit is contained in:
parent
1e810595c6
commit
cc51e34cf1
@ -28,6 +28,7 @@ Image Operations
|
||||
.. automethod:: openstack.image.v2._proxy.Proxy.images
|
||||
.. automethod:: openstack.image.v2._proxy.Proxy.deactivate_image
|
||||
.. automethod:: openstack.image.v2._proxy.Proxy.reactivate_image
|
||||
.. automethod:: openstack.image.v2._proxy.Proxy.stage_image
|
||||
.. automethod:: openstack.image.v2._proxy.Proxy.add_tag
|
||||
.. automethod:: openstack.image.v2._proxy.Proxy.remove_tag
|
||||
|
||||
|
@ -64,6 +64,36 @@ class Proxy(_base_proxy.BaseImageProxy):
|
||||
|
||||
image.import_image(self, method=method, uri=uri)
|
||||
|
||||
def stage_image(self, image, filename=None, data=None):
|
||||
"""Stage binary image data
|
||||
|
||||
:param image: The value can be the ID of a image or a
|
||||
:class:`~openstack.image.v2.image.Image` instance.
|
||||
:param filename: Optional name of the file to read data from.
|
||||
:param data: Optional data to be uploaded as an image.
|
||||
|
||||
:returns: The results of image creation
|
||||
:rtype: :class:`~openstack.image.v2.image.Image`
|
||||
"""
|
||||
image = self._get_resource(_image.Image, image)
|
||||
|
||||
if 'queued' != image.status:
|
||||
raise exceptions.SDKException('Image stage is only possible for '
|
||||
'images in the queued state.'
|
||||
' Current state is {status}'
|
||||
.format(status=image.status))
|
||||
|
||||
if filename:
|
||||
image.data = open(filename, 'rb')
|
||||
elif data:
|
||||
image.data = data
|
||||
image.stage(self)
|
||||
|
||||
# Stage does not return content, but updates the object
|
||||
image.fetch(self)
|
||||
|
||||
return image
|
||||
|
||||
def upload_image(self, container_format=None, disk_format=None,
|
||||
data=None, **attrs):
|
||||
"""Create and upload a new image from attributes
|
||||
|
@ -262,6 +262,16 @@ class Image(resource.Resource, resource.TagMixin):
|
||||
headers={"Content-Type": "application/octet-stream",
|
||||
"Accept": ""})
|
||||
|
||||
def stage(self, session):
|
||||
"""Stage binary image data into an existing image"""
|
||||
url = utils.urljoin(self.base_path, self.id, 'stage')
|
||||
response = session.put(
|
||||
url, data=self.data,
|
||||
headers={"Content-Type": "application/octet-stream",
|
||||
"Accept": ""})
|
||||
self._translate_response(response, has_body=False)
|
||||
return self
|
||||
|
||||
def import_image(self, session, method='glance-direct', uri=None):
|
||||
"""Import Image via interoperable image import process"""
|
||||
url = utils.urljoin(self.base_path, self.id, 'import')
|
||||
@ -269,6 +279,8 @@ class Image(resource.Resource, resource.TagMixin):
|
||||
if uri:
|
||||
if method == 'web-download':
|
||||
json['method']['uri'] = uri
|
||||
elif method == 'glance-direct':
|
||||
json['method']['uri'] = uri
|
||||
else:
|
||||
raise exceptions.InvalidRequest('URI is only supported with '
|
||||
'method: "web-download"')
|
||||
|
@ -92,6 +92,7 @@ class FakeResponse(object):
|
||||
def __init__(self, response, status_code=200, headers=None, reason=None):
|
||||
self.body = response
|
||||
self.content = response
|
||||
self.text = response
|
||||
self.status_code = status_code
|
||||
headers = headers if headers else {'content-type': 'application/json'}
|
||||
self.headers = requests.structures.CaseInsensitiveDict(headers)
|
||||
@ -115,7 +116,7 @@ class TestImage(base.TestCase):
|
||||
self.sess.post = mock.Mock(return_value=self.resp)
|
||||
self.sess.put = mock.Mock(return_value=FakeResponse({}))
|
||||
self.sess.delete = mock.Mock(return_value=FakeResponse({}))
|
||||
self.sess.fetch = mock.Mock(return_value=FakeResponse({}))
|
||||
self.sess.get = mock.Mock(return_value=FakeResponse({}))
|
||||
self.sess.default_microversion = None
|
||||
self.sess.retriable_status_codes = None
|
||||
self.sess.log = _log.setup_logging('openstack')
|
||||
@ -259,11 +260,12 @@ class TestImage(base.TestCase):
|
||||
|
||||
def test_import_image_with_uri_not_web_download(self):
|
||||
sot = image.Image(**EXAMPLE)
|
||||
self.assertRaises(exceptions.InvalidRequest,
|
||||
sot.import_image,
|
||||
self.sess,
|
||||
"glance-direct",
|
||||
"such-a-good-uri")
|
||||
|
||||
sot.import_image(self.sess, "glance-direct")
|
||||
self.sess.post.assert_called_with(
|
||||
'images/IDENTIFIER/import',
|
||||
json={"method": {"name": "glance-direct"}}
|
||||
)
|
||||
|
||||
def test_upload(self):
|
||||
sot = image.Image(**EXAMPLE)
|
||||
@ -275,6 +277,22 @@ class TestImage(base.TestCase):
|
||||
"application/octet-stream",
|
||||
"Accept": ""})
|
||||
|
||||
def test_stage(self):
|
||||
sot = image.Image(**EXAMPLE)
|
||||
|
||||
self.assertIsNotNone(sot.stage(self.sess))
|
||||
self.sess.put.assert_called_with('images/IDENTIFIER/stage',
|
||||
data=sot.data,
|
||||
headers={"Content-Type":
|
||||
"application/octet-stream",
|
||||
"Accept": ""})
|
||||
|
||||
def test_stage_error(self):
|
||||
sot = image.Image(**EXAMPLE)
|
||||
|
||||
self.sess.put.return_value = FakeResponse("dummy", status_code=400)
|
||||
self.assertRaises(exceptions.SDKException, sot.stage, self.sess)
|
||||
|
||||
def test_download_checksum_match(self):
|
||||
sot = image.Image(**EXAMPLE)
|
||||
|
||||
|
@ -94,6 +94,38 @@ class TestImageProxy(test_proxy_base.TestProxyBase):
|
||||
'chunk_size': 1,
|
||||
'stream': True})
|
||||
|
||||
@mock.patch("openstack.image.v2.image.Image.fetch")
|
||||
def test_image_stage(self, mock_fetch):
|
||||
img = image.Image(id="id", status="queued")
|
||||
img.stage = mock.Mock()
|
||||
|
||||
self.proxy.stage_image(image=img)
|
||||
mock_fetch.assert_called()
|
||||
img.stage.assert_called_with(self.proxy)
|
||||
|
||||
@mock.patch("openstack.image.v2.image.Image.fetch")
|
||||
def test_image_stage_with_data(self, mock_fetch):
|
||||
img = image.Image(id="id", status="queued")
|
||||
img.stage = mock.Mock()
|
||||
mock_fetch.return_value = img
|
||||
|
||||
rv = self.proxy.stage_image(image=img, data="data")
|
||||
|
||||
img.stage.assert_called_with(self.proxy)
|
||||
mock_fetch.assert_called()
|
||||
self.assertEqual(rv.data, "data")
|
||||
|
||||
def test_image_stage_wrong_status(self):
|
||||
img = image.Image(id="id", status="active")
|
||||
img.stage = mock.Mock()
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.SDKException,
|
||||
self.proxy.stage_image,
|
||||
img,
|
||||
"data"
|
||||
)
|
||||
|
||||
def test_image_delete(self):
|
||||
self.verify_delete(self.proxy.delete_image, image.Image, False)
|
||||
|
||||
|
4
releasenotes/notes/add-image-stage-1dbc3844a042fd26.yaml
Normal file
4
releasenotes/notes/add-image-stage-1dbc3844a042fd26.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Add support for staging image data.
|
Loading…
Reference in New Issue
Block a user